1 /*
2  * XPilot NG, a multiplayer space war game.
3  *
4  * Copyright (C) 2000-2004 by
5  *
6  *      Uoti Urpala          <uau@users.sourceforge.net>
7  *      Kristian S�derblom   <kps@users.sourceforge.net>
8  *
9  * Copyright (C) 1991-2001 by
10  *
11  *      Bj�rn Stabell        <bjoern@xpilot.org>
12  *      Ken Ronny Schouten   <ken@xpilot.org>
13  *      Bert Gijsbers        <bert@xpilot.org>
14  *      Dick Balaska         <dick@xpilot.org>
15  *
16  * This program is free software; you can redistribute it and/or modify
17  * it under the terms of the GNU General Public License as published by
18  * the Free Software Foundation; either version 2 of the License, or
19  * (at your option) any later version.
20  *
21  * This program is distributed in the hope that it will be useful,
22  * but WITHOUT ANY WARRANTY; without even the implied warranty of
23  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24  * GNU General Public License for more details.
25  *
26  * You should have received a copy of the GNU General Public License
27  * along with this program; if not, write to the Free Software
28  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
29  */
30 
31 #include "xpserver.h"
32 
33 /* new acd functions */
34 /* doubles because the multiplies might overflow ints */
in_range_acd(double dx,double dy,double dvx,double dvy,double r)35 static bool in_range_acd(double dx, double dy, double dvx, double dvy,
36 				double r)
37 {
38     double tmin, fminx, fminy;
39     double top, bot;
40 
41     dx = CENTER_XCLICK(dx);
42     dy = CENTER_YCLICK(dy);
43 
44     if (dx * dx + dy * dy < r * r)
45 	return true;
46     top = -(dvx * dx + dvy * dy);
47     bot = dvx * dvx + dvy * dvy;
48     if (top < 0 || bot < CLICK * CLICK || top > bot)
49 	return false;
50     tmin = top / bot;
51     fminx = dx + dvx * tmin;
52     fminy = dy + dvy * tmin;
53     if (fminx * fminx + fminy * fminy < r * r)
54 	return true;
55     else
56 	return false;
57 }
58 
in_range_simple(int px,int py,int qx,int qy,double r)59 static bool in_range_simple(int px, int py, int qx, int qy, double r)
60 {
61     int dx = px - qx, dy = py - qy;
62 
63     dx = CENTER_XCLICK(dx);
64     dy = CENTER_YCLICK(dy);
65 
66     if ((double)dx * dx + (double)dy * dy < r * r)
67 	return true;
68     else
69 	return false;
70 }
71 
in_range_partial(double dx,double dy,double dvx,double dvy,double r,double wall_time)72 static bool in_range_partial(double dx, double dy,
73 				    double dvx, double dvy,
74 				    double r, double wall_time)
75 {
76     double tmin, fminx, fminy;
77     double top, bot;
78 
79     dx = CENTER_XCLICK(dx);
80     dy = CENTER_YCLICK(dy);
81 
82     top = -(dvx * dx + dvy * dy);
83     bot = dvx * dvx + dvy * dvy;
84     if (top <= 0)
85 	return false;
86     if (bot < 5 * CLICK * CLICK || top >= bot)
87 	tmin = wall_time;
88     else {
89 	tmin = top / bot;
90 	tmin = MIN(tmin, wall_time);
91     }
92     fminx = dx + dvx * tmin;
93     fminy = dy + dvy * tmin;
94     if (fminx * fminx + fminy * fminy < r * r)
95 	return true;
96     else
97 	return false;
98 }
99 
100 /* Collmodes:
101    0 - Object has not been moved in walls.c after it was created.
102        Check only whether end-of-frame position is on top of a
103        player.
104    1 - Object was moved in walls.c. It did not hit walls and
105        therefore moved at constant speed from obj->prevpos to
106        obj->pos. Check whether it was within range during movement
107        using analytical collision detection.
108    2 - Object was moving from obj->prevpos by obj->extmove but it
109        hit a wall and was destroyed after completing obj->wall_time
110        of the distance. Check whether it was within range similarly
111        to case 1 but only consider hits at the beginning of the
112        frame before obj->wall_time.
113    3 - Object bounced off a wall at least once without getting
114        destroyed. Checking all the linear parts of movement
115        separately is not implemented yet so we don't detect any
116        possible collisions. Note that it would already be possible
117        to check the first linear part until obj->wall_time similarly
118        to case 2. This is not done because we lack the information
119        needed to calculate the effect of non-fatal hits. The
120        direction and speed of the object at the moment of impact
121        were likely completely different from the end-of-frame values
122        we have now.
123 
124        Different collision modes for players have not been implemented
125        yet. It's supposed that they move in a straight line from
126        prevpos to pos. This can lead to some erroneous hits.
127 */
in_range(object_t * obj1,object_t * obj2,double range)128 static inline bool in_range(object_t *obj1, object_t *obj2, double range)
129 {
130     bool hit;
131 
132     switch (obj2->collmode) {
133     case 0:
134 	hit = in_range_simple(obj1->pos.cx, obj1->pos.cy,
135 			      obj2->pos.cx, obj2->pos.cy,
136 			      range);
137 	break;
138     case 1:
139 	hit = in_range_acd((double)(obj1->prevpos.cx - obj2->prevpos.cx),
140 			   (double)(obj1->prevpos.cy - obj2->prevpos.cy),
141 			   (double)(obj1->extmove.cx - obj2->extmove.cx),
142 			   (double)(obj1->extmove.cy - obj2->extmove.cy),
143 			   range);
144 	break;
145     case 2:
146 	hit = in_range_partial((double)(obj1->prevpos.cx - obj2->prevpos.cx),
147 			       (double)(obj1->prevpos.cy - obj2->prevpos.cy),
148 			       (double)(obj1->extmove.cx - obj2->extmove.cx),
149 			       (double)(obj1->extmove.cy - obj2->extmove.cy),
150 			       range, obj2->wall_time);
151 	break;
152     case 3:
153     default:
154 #if 0
155 	warn("Unimplemented collision mode %d", obj2->collmode);
156 #endif
157 	return false;
158     }
159     return hit;
160 }
161 
162 static void PlayerCollision(void);
163 static void PlayerObjectCollision(player_t *pl);
164 static void AsteroidCollision(void);
165 static void BallCollision(void);
166 static void MineCollision(void);
167 static void Player_collides_with_ball(player_t *pl, ballobject_t *ball);
168 static void Player_collides_with_item(player_t *pl, itemobject_t *item);
169 static void Player_collides_with_mine(player_t *pl, mineobject_t *mine);
170 static void Player_collides_with_debris(player_t *pl, object_t *obj);
171 static void Player_collides_with_asteroid(player_t *pl, wireobject_t *obj);
172 static void Player_collides_with_killing_shot(player_t *pl, object_t *obj);
173 
174 
Check_collision(void)175 void Check_collision(void)
176 {
177     BallCollision();
178     MineCollision();
179     PlayerCollision();
180     AsteroidCollision();
181 }
182 
183 
PlayerCollision(void)184 static void PlayerCollision(void)
185 {
186     int i, j;
187     player_t *pl;
188 
189     /* Player - player, checkpoint, treasure, object and wall */
190     for (i = 0; i < NumPlayers; i++) {
191 	pl = Player_by_index(i);
192 	if (!Player_is_alive(pl))
193 	    continue;
194 
195 	if (!World_contains_clpos(pl->pos)) {
196 	    Player_set_state(pl, PL_STATE_KILLED);
197 	    Set_message_f("%s left the known universe.", pl->name);
198 	    Handle_Scoring(SCORE_WALL_DEATH,NULL,pl,NULL,NULL);
199 	    continue;
200 	}
201 
202 	if (Player_is_phasing(pl))
203 	    continue;
204 
205 	/* Player - player */
206 	if (BIT(world->rules->mode, CRASH_WITH_PLAYER | BOUNCE_WITH_PLAYER)) {
207 	    for (j = i + 1; j < NumPlayers; j++) {
208 		player_t *pl_j = Player_by_index(j);
209 		double range;
210 
211 		if (!Player_is_alive(pl_j))
212 		    continue;
213 		if (Player_is_phasing(pl_j))
214 		    continue;
215 
216 		range = (2*SHIP_SZ-6) * CLICK;
217 		if (!in_range(OBJ_PTR(pl), OBJ_PTR(pl_j), range))
218 		    continue;
219 
220 		/*
221 		 * Here we can add code to do more accurate player against
222 		 * player collision detection.
223 		 * A new algorithm could be based on the following idea:
224 		 *
225 		 * - If we can draw an uninterupted line between two players:
226 		 *   - Then test for both ships:
227 		 *     - For the three points which make up a ship:
228 		 *       - If we can draw a line between its previous
229 		 *         position and its current position which does not
230 		 *         cross the first line.
231 		 * Then the ships have not collided even though they may be
232 		 * very close to one another.
233 		 * The choosing of the first line may not be easy however.
234 		 */
235 
236 		if (Team_immune(pl->id, pl_j->id)
237 		    || PSEUDO_TEAM(pl, pl_j))
238 		    continue;
239 
240 		sound_play_sensors(pl->pos, PLAYER_HIT_PLAYER_SOUND);
241 		if (BIT(world->rules->mode, BOUNCE_WITH_PLAYER)) {
242 		    if (!Player_uses_emergency_shield(pl)) {
243 			Player_add_fuel(pl, ED_PL_CRASH);
244 			Item_damage(pl, options.destroyItemInCollisionProb);
245 		    }
246 		    if (!Player_uses_emergency_shield(pl_j)) {
247 			Player_add_fuel(pl_j, ED_PL_CRASH);
248 			Item_damage(pl_j, options.destroyItemInCollisionProb);
249 		    }
250 		    pl->forceVisible = 20;
251 		    pl_j->forceVisible = 20;
252 		    Obj_repel(OBJ_PTR(pl), OBJ_PTR(pl_j),
253 			      PIXEL_TO_CLICK(2*SHIP_SZ));
254 		}
255 		if (!BIT(world->rules->mode, CRASH_WITH_PLAYER))
256 		    continue;
257 
258 		if (pl->fuel.sum <= 0.0
259 		    || (!BIT(pl->used, HAS_SHIELD)
260 			&& !Player_has_armor(pl)))
261 		    Player_set_state(pl, PL_STATE_KILLED);
262 
263 		if (pl_j->fuel.sum <= 0.0
264 		    || (!BIT(pl_j->used, HAS_SHIELD)
265 			&& !Player_has_armor(pl_j)))
266 		    Player_set_state(pl_j, PL_STATE_KILLED);
267 
268 		if (!BIT(pl->used, HAS_SHIELD)
269 		    && Player_has_armor(pl))
270 		    Player_hit_armor(pl);
271 
272 		if (!BIT(pl_j->used, HAS_SHIELD)
273 		    && Player_has_armor(pl_j))
274 		    Player_hit_armor(pl_j);
275 
276 		if (Player_is_killed(pl_j)) {
277 		    if (Player_is_killed(pl)) {
278 			Set_message_f("%s and %s crashed.",
279 				      pl->name, pl_j->name);
280 			Handle_Scoring(SCORE_COLLISION,pl,pl_j,NULL,NULL);
281 		    } else {
282 			Set_message_f("%s ran over %s.", pl->name, pl_j->name);
283 			sound_play_sensors(pl_j->pos, PLAYER_RAN_OVER_PLAYER_SOUND);
284 			Handle_Scoring(SCORE_ROADKILL,pl,pl_j,NULL,NULL);
285 		    }
286 
287 		} else {
288 		    if (Player_is_killed(pl)) {
289 			Set_message_f("%s ran over %s.", pl_j->name, pl->name);
290 			sound_play_sensors(pl->pos, PLAYER_RAN_OVER_PLAYER_SOUND);
291 			Handle_Scoring(SCORE_ROADKILL,pl_j,pl,NULL,NULL);
292 		    }
293 		}
294 
295 		if (Player_is_killed(pl_j)) {
296 		    if (Player_is_robot(pl_j)
297 			&& Robot_war_on_player(pl_j) == pl->id)
298 			Robot_reset_war(pl_j);
299 		}
300 
301 		if (Player_is_killed(pl)) {
302 		    if (Player_is_robot(pl)
303 			&& Robot_war_on_player(pl) == pl_j->id)
304 			Robot_reset_war(pl);
305 		}
306 
307 		/* cannot crash with more than one player at the same time? */
308 		/* if 3 players meet at the same point at the same time? */
309 		/* break; */
310 
311 	    }
312 	}
313 
314 	/* Player picking up ball/treasure */
315 	if (!BIT(pl->used, HAS_CONNECTOR)
316 	    || Player_is_phasing(pl)
317 	    ||(!options.multipleConnectors && BIT(pl->have, HAS_BALL)))
318 	    pl->ball = NULL;
319 	else if (pl->ball != NULL) {
320 	    ballobject_t *ball = pl->ball;
321 
322 	    if (ball->life <= 0.0 || ball->id != NO_ID)
323 		pl->ball = NULL;
324 	    else {
325 		double distance = Wrap_length(pl->pos.cx - ball->pos.cx,
326 					      pl->pos.cy - ball->pos.cy);
327 		int group;
328 
329 		if (distance >= options.ballConnectorLength * CLICK) {
330 		    ball->id = pl->id;
331 		    /* this is only the team of the owner of the ball,
332 		     * not the team the ball belongs to. the latter is
333 		     * found through the ball's treasure */
334 		    ball->team = pl->team;
335 		    if (ball->ball_treasure->have)
336 			ball->ball_loose_ticks = 0;
337 		    ball->ball_owner = pl->id;
338 		    SET_BIT(ball->obj_status, GRAVITY);
339 		    ball->ball_treasure->have = false;
340 		    SET_BIT(pl->have, HAS_BALL);
341 		    pl->ball = NULL;
342 		    sound_play_sensors(pl->pos, CONNECT_BALL_SOUND);
343 		    {
344 			/* The ball might already be inside the team's ball
345 			 * target. */
346 			group = shape_is_inside(ball->pos.cx,
347 						ball->pos.cy,
348 						BALL_BIT | HITMASK(pl->team),
349 						(object_t *)ball,
350 						&ball_wire, 0);
351 			if (group != NO_GROUP) {
352 			    Ball_hits_goal(ball, groupptr_by_id(group));
353 			    ball->life = 0.0;
354 			}
355 		    }
356 		}
357 	    }
358 	} else {
359 	    /*
360 	     * We want a separate list of balls to avoid searching
361 	     * the object list for balls.
362 	     */
363 	    double dist, mindist = options.ballConnectorLength * CLICK;
364 
365 	    for (j = 0; j < NumObjs; j++) {
366 		object_t *obj = Obj[j];
367 
368 		if (obj->type == OBJ_BALL
369 		    && obj->id == NO_ID) {
370 		    dist = Wrap_length(pl->pos.cx - obj->pos.cx,
371 				       pl->pos.cy - obj->pos.cy);
372 		    if (dist < mindist) {
373 			ballobject_t *ball = BALL_PTR(obj);
374 
375 			/*
376 			 * The treasure's team cannot connect before
377 			 * somebody else has owned the ball.
378 			 * This was done to stop team members
379 			 * taking and hiding with the ball... this was
380 			 * considered bad gamesmanship.
381 			 */
382 			if (BIT(world->rules->mode, TEAM_PLAY)
383 			    && ball->ball_treasure->have
384 			    && pl->team == ball->ball_treasure->team)
385 			    continue;
386 			pl->ball = ball;
387 			mindist = dist;
388 		    }
389 		}
390 	    }
391 	}
392 
393 	PlayerObjectCollision(pl);
394 	PlayerCheckpointCollision(pl);
395     }
396 }
397 
IsOffensiveItem(enum Item i)398 int IsOffensiveItem(enum Item i)
399 {
400     if (BIT(1 << i,
401 	    ITEM_BIT_WIDEANGLE |
402 	    ITEM_BIT_REARSHOT |
403 	    ITEM_BIT_MINE |
404 	    ITEM_BIT_MISSILE |
405 	    ITEM_BIT_LASER))
406 	return true;
407     return false;
408 }
409 
IsDefensiveItem(enum Item i)410 int IsDefensiveItem(enum Item i)
411 {
412     if (BIT(1 << i,
413 	    ITEM_BIT_CLOAK |
414 	    ITEM_BIT_ECM |
415 	    ITEM_BIT_TRANSPORTER |
416 	    ITEM_BIT_TRACTOR_BEAM |
417 	    ITEM_BIT_EMERGENCY_SHIELD |
418 	    ITEM_BIT_MIRROR |
419 	    ITEM_BIT_DEFLECTOR |
420 	    ITEM_BIT_HYPERJUMP |
421 	    ITEM_BIT_PHASING |
422 	    ITEM_BIT_TANK |
423 	    ITEM_BIT_ARMOR))
424 	return true;
425     return false;
426 }
427 
CountOffensiveItems(player_t * pl)428 int CountOffensiveItems(player_t *pl)
429 {
430     return (pl->item[ITEM_WIDEANGLE] + pl->item[ITEM_REARSHOT] +
431 	    pl->item[ITEM_MINE] + pl->item[ITEM_MISSILE] +
432 	    pl->item[ITEM_LASER]);
433 }
434 
CountDefensiveItems(player_t * pl)435 int CountDefensiveItems(player_t *pl)
436 {
437     int count;
438 
439     count = pl->item[ITEM_CLOAK] + pl->item[ITEM_ECM] + pl->item[ITEM_ARMOR] +
440 	    pl->item[ITEM_TRANSPORTER] + pl->item[ITEM_TRACTOR_BEAM] +
441 	    pl->item[ITEM_EMERGENCY_SHIELD] + pl->fuel.num_tanks +
442 	    pl->item[ITEM_DEFLECTOR] + pl->item[ITEM_HYPERJUMP] +
443 	    pl->item[ITEM_PHASING] + pl->item[ITEM_MIRROR];
444     if (pl->emergency_shield_left > 0)
445  	count++;
446     if (pl->phasing_left > 0)
447 	count++;
448     return count;
449 }
450 
collision_cost(double mass,double speed)451 static inline double collision_cost(double mass, double speed)
452 {
453     /*
454      * kps - this was ABS(2 * mass * speed), because fuel related
455      * values used to be multiplied by 256 in older code.
456      */
457     return ABS(mass * speed / 128.0);
458 }
459 
PlayerObjectCollision(player_t * pl)460 static void PlayerObjectCollision(player_t *pl)
461 {
462     int j, obj_count;
463     double range, radius;
464     object_t *obj, **obj_list;
465 
466     /*
467      * Collision between a player and an object.
468      */
469     if (!Player_is_alive(pl))
470 	return;
471 
472     if (NumObjs >= options.cellGetObjectsThreshold)
473 	Cell_get_objects(pl->pos, 4, 500, &obj_list, &obj_count);
474     else {
475 	obj_list = Obj;
476 	obj_count = NumObjs;
477     }
478 
479     for (j = 0; j < obj_count; j++) {
480 	bool hit;
481 
482 	obj = obj_list[j];
483 
484 	range = (SHIP_SZ + obj->pl_range) * CLICK;
485 	if (!in_range(OBJ_PTR(pl), obj, range))
486 	    continue;
487 
488 	if (obj->id != NO_ID) {
489 	    if (obj->id == pl->id) {
490 		if ((obj->type == OBJ_SPARK
491 		     || obj->type == OBJ_MINE)
492 		    && BIT(obj->obj_status, OWNERIMMUNE))
493 		    continue;
494 		else if (options.selfImmunity)
495 		    continue;
496 	    } else if (options.selfImmunity &&
497 		       Player_is_tank(pl) &&
498 		       (pl->lock.pl_id == obj->id))
499 		continue;
500 	    else if (Team_immune(obj->id, pl->id))
501 		continue;
502 	    else if (Player_is_paused(Player_by_id(obj->id)))
503 		continue;
504 	} else if (BIT(world->rules->mode, TEAM_PLAY)
505 		   && options.teamImmunity
506 		   && obj->team == pl->team
507 		   /* allow players to destroy their team's unowned balls */
508 		   && obj->type != OBJ_BALL)
509 	    continue;
510 
511 	if (obj->type == OBJ_ITEM) {
512 	    if (BIT(pl->used, HAS_SHIELD) && !options.shieldedItemPickup) {
513 		SET_BIT(obj->obj_status, GRAVITY);
514 		Delta_mv(OBJ_PTR(pl), obj);
515 		continue;
516 	    }
517 	}
518 	else if (obj->type == OBJ_HEAT_SHOT
519 		 || obj->type == OBJ_SMART_SHOT
520 		 || obj->type == OBJ_TORPEDO
521 		 || obj->type == OBJ_SHOT
522 		 || obj->type == OBJ_CANNON_SHOT) {
523 	    if (pl->id == obj->id && obj->fuse > 0)
524 		continue;
525 	}
526 	else if (obj->type == OBJ_MINE) {
527 	    if (BIT(obj->obj_status, CONFUSED))
528 		continue;
529 	}
530 	else if (obj->type == OBJ_BALL
531 		 && obj->id != NO_ID) {
532 	    if (Player_is_phasing(Player_by_id(obj->id)))
533 		continue;
534 	}
535 	else if (obj->type == OBJ_PULSE) {
536 	    pulseobject_t *pulse = PULSE_PTR(obj);
537 
538 	    if (pl->id == pulse->id && !pulse->pulse_refl)
539 		continue;
540 	}
541 	/*
542 	 * Objects actually only hit the player if they are really close.
543 	 */
544 	radius = (SHIP_SZ + obj->pl_radius) * CLICK;
545 
546 	/*
547 	 * kps - why was radius used in 4.3.1X and range in 4.5.4 ?
548 	 */
549 	if (radius >= range)
550 	    hit = true;
551 	else
552 	    hit = in_range(OBJ_PTR(pl), obj, radius);
553 
554 #if 0
555 	if (obj->collmode != 1) {
556 	    char MSG[80];
557 	    sprintf(MSG, "Collision type=%d, hit=%d, cm=%d, time=%f, "
558 		    "frame=%ld [*DEBUG*]", obj->type, hit, obj->collmode,
559 		    obj->wall_time, frame_loops);
560 	    Set_message(MSG);
561 	}
562 #endif
563 
564 	/*
565 	 * Object collision.
566 	 */
567 	switch (obj->type) {
568 	case OBJ_BALL:
569 	    if (!hit)
570 		continue;
571 	    Player_collides_with_ball(pl, BALL_PTR(obj));
572 	    if (Player_is_killed(pl))
573 		return;
574 	    continue;
575 
576 	case OBJ_ITEM:
577 	    Player_collides_with_item(pl, ITEM_PTR(obj));
578 	    /* if life is non-zero then no collision occurred */
579 	    if (obj->life != 0)
580 		continue;
581 	    break;
582 
583 	case OBJ_MINE:
584 	    Player_collides_with_mine(pl, MINE_PTR(obj));
585 	    break;
586 
587 	case OBJ_WRECKAGE:
588 	case OBJ_DEBRIS:
589 	    Player_collides_with_debris(pl, obj);
590 	    if (Player_is_killed(pl))
591 		return;
592 	    break;
593 
594 	case OBJ_ASTEROID:
595 	    if (hit) {
596 		Player_collides_with_asteroid(pl, WIRE_PTR(obj));
597 		Delta_mv_elastic(OBJ_PTR(pl), obj);
598 	    }
599 	    if (Player_is_killed(pl))
600 		return;
601 	    continue;
602 
603 	case OBJ_CANNON_SHOT:
604 	    /* don't explode cannon flak if it hits directly */
605 	    Mods_set(&obj->mods, ModsCluster, 0);
606 	    break;
607 
608 	case OBJ_PULSE:
609 	    Laser_pulse_hits_player(pl, PULSE_PTR(obj));
610 	    if (Player_is_killed(pl))
611 		return;
612 	    continue;
613 
614 	default:
615 	    break;
616 	}
617 	/* KHS Let cannon dodgers shots survive collision */
618 	if (obj->type != OBJ_CANNON_SHOT
619 	    || options.survivalScore == 0.0)
620 	    obj->life = 0;
621 
622 	if (BIT(OBJ_TYPEBIT(obj->type), KILLING_SHOTS)) {
623 	    Player_collides_with_killing_shot(pl, obj);
624 	    if (Player_is_killed(pl))
625 		return;
626 	    else
627 		obj->life = 0;
628 	    /* KHS except when player is shielded - shot would */
629             /* stay with player and kill him anyways, then */
630 	}
631 
632 	if (hit)
633 	    Delta_mv(OBJ_PTR(pl), obj);
634     }
635 }
636 
637 
Player_collides_with_ball(player_t * pl,ballobject_t * ball)638 static void Player_collides_with_ball(player_t *pl, ballobject_t *ball)
639 {
640     /*
641      * The ball is special, usually players bounce off of it with
642      * shields up, or die with shields down.  The treasure may
643      * be destroyed.
644      */
645 
646     if (!Player_uses_emergency_shield(pl))
647 	Player_add_fuel(pl, ED_BALL_HIT);
648 
649     if (options.treasureCollisionDestroys) {
650 	if (BIT(world->rules->mode, TEAM_PLAY)
651 	    && pl->team == ball->ball_treasure->team)
652 	    Rank_saved_ball(pl);
653 	Delta_mv(OBJ_PTR(pl), OBJ_PTR(ball));
654 	ball->life = 0;
655     }
656 
657     if (options.treasureCollisionMayKill && !BIT(pl->used, HAS_SHIELD) ){
658 	if(Player_has_armor(pl))
659 	    Player_hit_armor(pl);
660 	else{
661 	    Delta_mv(OBJ_PTR(pl), OBJ_PTR(ball));
662 	    pl->fuel.sum=0;
663 		}
664     }
665 
666     if (pl->fuel.sum > 0) {
667 	if(ball->fuse > 0){
668 	    ball->fuse+=timeStep;
669 	    return;
670 	}
671 	Delta_mv_partly_elastic(OBJ_PTR(ball),
672 				OBJ_PTR(pl),
673 				options.playerBallBounceBrakeFactor);
674 	/* KHS <evil hack on> */
675 	/* this stops the ball from penetrating the player in most cases */
676 	if(pl->collmode < 3 && ball->collmode < 3){
677 	    /* cannot do this hack after a wallbounce */
678 	    pl->pos=pl->prevpos;
679 	    ball->pos=ball->prevpos;
680 	    Move_player(pl);
681 	    Move_object(OBJ_PTR(ball));
682 	}
683 	/* KHS </evil hack> */
684 	ball->fuse = timeStep;
685 	/* ball was "touched", so set owner, and mark it as loose */
686 
687 		    /* this is only the team of the owner of the ball,
688 		     * not the team the ball belongs to. the latter is
689 		     * found through the ball's treasure */
690 		    ball->team = pl->team;
691 		    if (ball->ball_treasure->have){
692 			ball->ball_loose_ticks = 0;
693 			ball->ball_treasure->have = false;
694 			SET_BIT(ball->obj_status, GRAVITY);
695 		    }
696 		    if (ball->id == NO_ID)
697 			ball->ball_owner = pl->id;
698 		    else if (options.ballCollisionDetaches) {
699 			Detach_ball(Player_by_id(ball->id), ball);
700 			ball->ball_owner = pl->id;
701 		    }
702 		    sound_play_sensors(pl->pos, ASTEROID_HIT_SOUND);
703 	return;
704     }
705 
706     /* Player has died */
707     if (ball->ball_owner == NO_ID) {
708 	Set_message_f("%s was killed by a ball.", pl->name);
709 	Handle_Scoring(SCORE_BALL_KILL,NULL,pl,NULL,NULL);
710     } else {
711 	player_t *kp = Player_by_id(ball->ball_owner);
712 
713 	Set_message_f("%s was killed by a ball owned by %s.%s",
714 		      pl->name, kp->name,
715 		      kp->id == pl->id ? "  How strange!" : "");
716 
717 	Handle_Scoring(SCORE_BALL_KILL,kp,pl,NULL,NULL);
718 
719 	if (kp->id != pl->id) {
720 	    Robot_war(pl, kp);
721 	}
722     }
723     Player_set_state(pl, PL_STATE_KILLED);
724 }
725 
726 
Player_collides_with_item(player_t * pl,itemobject_t * item)727 static void Player_collides_with_item(player_t *pl, itemobject_t *item)
728 {
729     int old_have;
730     enum Item item_index = (enum Item) item->item_type;
731 
732     if (IsOffensiveItem(item_index)) {
733 	int off_items = CountOffensiveItems(pl);
734 
735 	if (off_items >= options.maxOffensiveItems) {
736 	    /* Set_player_message(pl, "No space left for offensive items."); */
737 	    Delta_mv(OBJ_PTR(pl), OBJ_PTR(item));
738 	    return;
739 	}
740 	else if (item->item_count > 1
741 		 && off_items + item->item_count > options.maxOffensiveItems)
742 	    item->item_count = options.maxOffensiveItems - off_items;
743     }
744     else if (IsDefensiveItem(item_index)) {
745 	int def_items = CountDefensiveItems(pl);
746 
747 	if (def_items >= options.maxDefensiveItems) {
748 	    /* Set_player_message(pl,
749 	       "No space for left for defensive items."); */
750 	    Delta_mv(OBJ_PTR(pl), OBJ_PTR(item));
751 	    return;
752 	}
753 	else if (item->item_count > 1
754 		 && def_items + item->item_count > options.maxDefensiveItems)
755 	    item->item_count = options.maxDefensiveItems - def_items;
756     }
757 
758     switch (item_index) {
759     case ITEM_WIDEANGLE:
760 	pl->item[item_index] += item->item_count;
761 	LIMIT(pl->item[item_index], 0, world->items[item_index].limit);
762 	sound_play_sensors(pl->pos, WIDEANGLE_SHOT_PICKUP_SOUND);
763 	break;
764     case ITEM_ECM:
765 	pl->item[item_index] += item->item_count;
766 	LIMIT(pl->item[item_index], 0, world->items[item_index].limit);
767 	sound_play_sensors(pl->pos, ECM_PICKUP_SOUND);
768 	break;
769     case ITEM_ARMOR:
770 	pl->item[item_index]++;
771 	LIMIT(pl->item[item_index], 0, world->items[item_index].limit);
772 	if (pl->item[item_index] > 0)
773 	    SET_BIT(pl->have, HAS_ARMOR);
774 	sound_play_sensors(pl->pos, ARMOR_PICKUP_SOUND);
775 	break;
776     case ITEM_TRANSPORTER:
777 	pl->item[item_index] += item->item_count;
778 	LIMIT(pl->item[item_index], 0, world->items[item_index].limit);
779 	sound_play_sensors(pl->pos, TRANSPORTER_PICKUP_SOUND);
780 	break;
781     case ITEM_MIRROR:
782 	pl->item[ITEM_MIRROR] += item->item_count;
783 	LIMIT(pl->item[item_index], 0, world->items[item_index].limit);
784 	if (pl->item[item_index] > 0)
785 	    SET_BIT(pl->have, HAS_MIRROR);
786 	sound_play_sensors(pl->pos, MIRROR_PICKUP_SOUND);
787 	break;
788     case ITEM_DEFLECTOR:
789 	pl->item[ITEM_DEFLECTOR] += item->item_count;
790 	LIMIT(pl->item[item_index], 0, world->items[item_index].limit);
791 	if (pl->item[item_index] > 0)
792 	    SET_BIT(pl->have, HAS_DEFLECTOR);
793 	sound_play_sensors(pl->pos, DEFLECTOR_PICKUP_SOUND);
794 	break;
795     case ITEM_HYPERJUMP:
796 	pl->item[item_index] += item->item_count;
797 	LIMIT(pl->item[item_index], 0, world->items[item_index].limit);
798 	sound_play_sensors(pl->pos, HYPERJUMP_PICKUP_SOUND);
799 	break;
800     case ITEM_PHASING:
801 	pl->item[item_index] += item->item_count;
802 	LIMIT(pl->item[item_index], 0, world->items[item_index].limit);
803 	if (pl->item[item_index] > 0)
804 	    SET_BIT(pl->have, HAS_PHASING_DEVICE);
805 	sound_play_sensors(pl->pos, PHASING_DEVICE_PICKUP_SOUND);
806 	break;
807     case ITEM_SENSOR:
808 	pl->item[item_index] += item->item_count;
809 	LIMIT(pl->item[item_index], 0, world->items[item_index].limit);
810 	pl->updateVisibility = true;
811 	sound_play_sensors(pl->pos, SENSOR_PACK_PICKUP_SOUND);
812 	break;
813     case ITEM_AFTERBURNER:
814 	pl->item[item_index] += item->item_count;
815 	LIMIT(pl->item[item_index], 0, world->items[item_index].limit);
816 	if (pl->item[item_index] > 0)
817 	    SET_BIT(pl->have, HAS_AFTERBURNER);
818 	sound_play_sensors(pl->pos, AFTERBURNER_PICKUP_SOUND);
819 	break;
820     case ITEM_REARSHOT:
821 	pl->item[item_index] += item->item_count;
822 	LIMIT(pl->item[item_index], 0, world->items[item_index].limit);
823 	sound_play_sensors(pl->pos, BACK_SHOT_PICKUP_SOUND);
824 	break;
825     case ITEM_MISSILE:
826 	pl->item[item_index] += item->item_count;
827 	LIMIT(pl->item[item_index], 0, world->items[item_index].limit);
828 	sound_play_sensors(pl->pos, ROCKET_PACK_PICKUP_SOUND);
829 	break;
830     case ITEM_CLOAK:
831 	pl->item[item_index] += item->item_count;
832 	LIMIT(pl->item[item_index], 0, world->items[item_index].limit);
833 	if (pl->item[item_index] > 0)
834 	    SET_BIT(pl->have, HAS_CLOAKING_DEVICE);
835 	pl->updateVisibility = true;
836 	sound_play_sensors(pl->pos, CLOAKING_DEVICE_PICKUP_SOUND);
837 	break;
838     case ITEM_FUEL:
839 	Player_add_fuel(pl, ENERGY_PACK_FUEL);
840 	sound_play_sensors(pl->pos, ENERGY_PACK_PICKUP_SOUND);
841 	break;
842     case ITEM_MINE:
843 	pl->item[item_index] += item->item_count;
844 	LIMIT(pl->item[item_index], 0, world->items[item_index].limit);
845 	sound_play_sensors(pl->pos, MINE_PACK_PICKUP_SOUND);
846 	break;
847     case ITEM_LASER:
848 	pl->item[item_index] += item->item_count;
849 	LIMIT(pl->item[item_index], 0, world->items[item_index].limit);
850 	sound_play_sensors(pl->pos, LASER_PICKUP_SOUND);
851 	break;
852     case ITEM_EMERGENCY_THRUST:
853 	pl->item[item_index] += item->item_count;
854 	LIMIT(pl->item[item_index], 0, world->items[item_index].limit);
855 	if (pl->item[item_index] > 0)
856 	    SET_BIT(pl->have, HAS_EMERGENCY_THRUST);
857 	sound_play_sensors(pl->pos, EMERGENCY_THRUST_PICKUP_SOUND);
858 	break;
859     case ITEM_EMERGENCY_SHIELD:
860 	old_have = pl->have;
861 	pl->item[item_index] += item->item_count;
862 	LIMIT(pl->item[item_index], 0, world->items[item_index].limit);
863 	if (pl->item[item_index] > 0)
864 	    SET_BIT(pl->have, HAS_EMERGENCY_SHIELD);
865 	sound_play_sensors(pl->pos, EMERGENCY_SHIELD_PICKUP_SOUND);
866 	/*
867 	 * New feature since 3.2.7:
868 	 * If we're playing in a map where shields are not allowed
869 	 * and a player picks up her first emergency shield item
870 	 * then we'll immediately turn on emergency shield.
871 	 */
872 	if (!BIT(old_have, HAS_SHIELD | HAS_EMERGENCY_SHIELD)
873 	    && pl->item[ITEM_EMERGENCY_SHIELD] == 1)
874 	    Emergency_shield(pl, true);
875 	break;
876     case ITEM_TRACTOR_BEAM:
877 	pl->item[item_index] += item->item_count;
878 	LIMIT(pl->item[item_index], 0, world->items[item_index].limit);
879 	if (pl->item[item_index] > 0)
880 	    SET_BIT(pl->have, HAS_TRACTOR_BEAM);
881 	sound_play_sensors(pl->pos, TRACTOR_BEAM_PICKUP_SOUND);
882 	break;
883     case ITEM_AUTOPILOT:
884 	pl->item[item_index] += item->item_count;
885 	LIMIT(pl->item[item_index], 0, world->items[item_index].limit);
886 	if (pl->item[item_index] > 0)
887 	    SET_BIT(pl->have, HAS_AUTOPILOT);
888 	sound_play_sensors(pl->pos, AUTOPILOT_PICKUP_SOUND);
889 	break;
890 
891     case ITEM_TANK:
892 	if (pl->fuel.num_tanks < world->items[ITEM_TANK].limit)
893 	    Player_add_tank(pl, TANK_FUEL(pl->fuel.num_tanks + 1));
894 	else
895 	    Player_add_fuel(pl, TANK_FUEL(MAX_TANKS));
896 	sound_play_sensors(pl->pos, TANK_PICKUP_SOUND);
897 	break;
898     case NUM_ITEMS:
899 	/* impossible */
900 	break;
901     default:
902 	warn("Player_collides_with_item: unknown item.");
903 	break;
904     }
905 
906     item->life = 0.0;
907 }
908 
909 
Player_collides_with_mine(player_t * pl,mineobject_t * mine)910 static void Player_collides_with_mine(player_t *pl, mineobject_t *mine)
911 {
912     player_t *kp = NULL;
913 
914     sound_play_sensors(pl->pos, PLAYER_HIT_MINE_SOUND);
915     if (mine->id == NO_ID && mine->mine_owner == NO_ID)
916 	Set_message_f("%s hit %s.",
917 		      pl->name,
918 		      Describe_shot(mine->type, mine->obj_status,
919 				    mine->mods, 1));
920     else if (mine->mine_owner == mine->id) {
921 	kp = Player_by_id(mine->mine_owner);
922 	Set_message_f("%s hit %s %s by %s.", pl->name,
923 		      Describe_shot(mine->type, mine->obj_status, mine->mods, 1),
924 		      BIT(mine->obj_status, GRAVITY) ? "thrown " : "dropped ",
925 		      kp->name);
926     }
927     else if (mine->mine_owner == NO_ID) {
928 	const char *reprogrammer_name = "some jerk";
929 
930 	if (mine->id != NO_ID) {
931 	    kp = Player_by_id(mine->id);
932 	    reprogrammer_name = kp->name;
933 	}
934 	Set_message_f("%s hit %s reprogrammed by %s.",
935 		      pl->name,
936 		      Describe_shot(mine->type, mine->obj_status, mine->mods, 1),
937 		      reprogrammer_name);
938     }
939     else {
940 	const char *reprogrammer_name = "some jerk";
941 
942 	if (mine->id != NO_ID) {
943 	    kp = Player_by_id(mine->id);
944 	    reprogrammer_name = kp->name;
945 	}
946 	Set_message_f("%s hit %s %s by %s and reprogrammed by %s.",
947 		      pl->name,
948 		      Describe_shot(mine->type, mine->obj_status,
949 				    mine->mods, 1),
950 		      BIT(mine->obj_status, GRAVITY) ? "thrown " : "dropped ",
951 		      Player_by_id(mine->mine_owner)->name,
952 		      reprogrammer_name);
953     }
954     if (kp) {
955 	/*
956 	 * Question with this is if we want to give the same points for
957 	 * a high-scored-player hitting a low-scored-player's mine as
958 	 * for a low-scored-player hitting a high-scored-player's mine.
959 	 * Maybe not.
960 	 */
961 	Handle_Scoring(SCORE_HIT_MINE,kp,pl,NULL,NULL);
962     }
963 }
964 
965 
Player_collides_with_debris(player_t * pl,object_t * obj)966 static void Player_collides_with_debris(player_t *pl, object_t *obj)
967 {
968     player_t *kp = NULL;
969     double cost;
970     char msg[MSG_LEN];
971 
972     cost = collision_cost(obj->mass, VECTOR_LENGTH(obj->vel));
973 
974     if (!Player_uses_emergency_shield(pl))
975 	Player_add_fuel(pl, -cost);
976     if (pl->fuel.sum == 0.0
977 	|| (obj->type == OBJ_WRECKAGE
978 	    && options.wreckageCollisionMayKill
979 	    && !BIT(pl->used, HAS_SHIELD)
980 	    && !Player_has_armor(pl))) {
981 	Player_set_state(pl, PL_STATE_KILLED);
982 	sprintf(msg, "%s succumbed to an explosion.", pl->name);
983 	if (obj->id != NO_ID) {
984 	    kp = Player_by_id(obj->id);
985 	    sprintf(msg + strlen(msg) - 1, " from %s.", kp->name);
986 	    if (obj->id == pl->id)
987 		sprintf(msg + strlen(msg), "  How strange!");
988 	}
989 	Set_message(msg);
990 	Handle_Scoring(SCORE_EXPLOSION,kp,pl,NULL,NULL);
991 	obj->life = 0.0;
992 	return;
993     }
994     if (obj->type == OBJ_WRECKAGE
995 	&& options.wreckageCollisionMayKill
996 	&& !BIT(pl->used, HAS_SHIELD)
997 	&& Player_has_armor(pl))
998 	Player_hit_armor(pl);
999 }
1000 
1001 
Player_collides_with_asteroid(player_t * pl,wireobject_t * ast)1002 static void Player_collides_with_asteroid(player_t *pl, wireobject_t *ast)
1003 {
1004     double v = VECTOR_LENGTH(ast->vel);
1005     double cost = collision_cost(ast->mass, v);
1006 
1007     ast->life += ASTEROID_FUEL_HIT(ED_PL_CRASH, ast->wire_size);
1008     if (ast->life < 0.0)
1009 	ast->life = 0.0;
1010     if (ast->life == 0.0) {
1011     	Handle_Scoring(SCORE_ASTEROID_KILL,pl,NULL,ast,NULL);
1012     }
1013 
1014     if (!Player_uses_emergency_shield(pl))
1015 	Player_add_fuel(pl, -cost);
1016 
1017     if (options.asteroidCollisionMayKill
1018 	&& (pl->fuel.sum == 0.0
1019 	    || (!BIT(pl->used, HAS_SHIELD)
1020 		&& !Player_has_armor(pl)))) {
1021 	Player_set_state(pl, PL_STATE_KILLED);
1022 	if (pl->velocity > v)
1023 	    /* player moves faster than asteroid */
1024 	    Set_message_f("%s smashed into an asteroid.", pl->name);
1025 	else
1026 	    Set_message_f("%s was hit by an asteroid.", pl->name);
1027 	Handle_Scoring(SCORE_ASTEROID_DEATH,NULL,pl,NULL,NULL);
1028 	if (Player_is_tank(pl)) {
1029 	    player_t *owner_pl = Player_by_id(pl->lock.pl_id);
1030 	    Handle_Scoring(SCORE_ASTEROID_KILL,owner_pl,NULL,ast,NULL);
1031 	}
1032 	return;
1033     }
1034     if (options.asteroidCollisionMayKill
1035 	&& !BIT(pl->used, HAS_SHIELD)
1036 	&& Player_has_armor(pl))
1037 	Player_hit_armor(pl);
1038 }
1039 
Missile_hit_drain(missileobject_t * missile)1040 static inline double Missile_hit_drain(missileobject_t *missile)
1041 {
1042     return (ED_SMART_SHOT_HIT /
1043 	    ((Mods_get(missile->mods, ModsMini) + 1)
1044 	     * (Mods_get(missile->mods, ModsPower) + 1)));
1045 }
1046 
Player_collides_with_killing_shot(player_t * pl,object_t * obj)1047 static void Player_collides_with_killing_shot(player_t *pl, object_t *obj)
1048 {
1049     player_t *kp = NULL;
1050     cannon_t *cannon = NULL;
1051     double drainfactor, drain;
1052 
1053     /*
1054      * Player got hit by a potentially deadly object.
1055      *
1056      * When a player has shields up, and not enough fuel
1057      * to 'absorb' the shot then shields are lowered.
1058      * This is not very logical, rather in this case
1059      * the shot should be considered to be deadly too.
1060      *
1061      * Sound effects are missing when shot is deadly.
1062      */
1063 
1064     if (BIT(pl->used, HAS_SHIELD)
1065 	|| Player_has_armor(pl)
1066 	|| (obj->type == OBJ_TORPEDO
1067 	    && Mods_get(obj->mods, ModsNuclear)
1068 	    && (rfrac() >= 0.25))) {
1069 	switch (obj->type) {
1070 	case OBJ_TORPEDO:
1071 	    sound_play_sensors(pl->pos, PLAYER_EAT_TORPEDO_SHOT_SOUND);
1072 	    break;
1073 	case OBJ_HEAT_SHOT:
1074 	    sound_play_sensors(pl->pos, PLAYER_EAT_HEAT_SHOT_SOUND);
1075 	    break;
1076 	case OBJ_SMART_SHOT:
1077 	    sound_play_sensors(pl->pos, PLAYER_EAT_SMART_SHOT_SOUND);
1078 	    break;
1079 	default:
1080 	    break;
1081 	}
1082 
1083 	switch(obj->type) {
1084 	case OBJ_TORPEDO:
1085 	case OBJ_HEAT_SHOT:
1086 	case OBJ_SMART_SHOT:
1087 	    if (obj->id == NO_ID)
1088 		Set_message_f("%s ate %s.", pl->name,
1089 			      Describe_shot(obj->type, obj->obj_status,
1090 					    obj->mods, 1));
1091 	    else {
1092 		kp = Player_by_id(obj->id);
1093 		Set_message_f("%s ate %s from %s.", pl->name,
1094 			      Describe_shot(obj->type, obj->obj_status,
1095 					    obj->mods, 1),
1096 			      kp->name);
1097 	    }
1098 	    drain = Missile_hit_drain(MISSILE_PTR(obj));
1099 	    if (!Player_uses_emergency_shield(pl))
1100 		Player_add_fuel(pl, drain);
1101 	    pl->forceVisible += 2;
1102 	    break;
1103 
1104 	case OBJ_SHOT:
1105 	case OBJ_CANNON_SHOT:
1106 	    sound_play_sensors(pl->pos, PLAYER_EAT_SHOT_SOUND);
1107 	    if (!Player_uses_emergency_shield(pl)) {
1108 		if (options.shotHitFuelDrainUsesKineticEnergy) {
1109 		    double rel_velocity = LENGTH(pl->vel.x - obj->vel.x,
1110 						 pl->vel.y - obj->vel.y);
1111 		    drainfactor
1112 			= ((rel_velocity * rel_velocity * ABS(obj->mass))
1113 			   / (options.shotSpeed * options.shotSpeed
1114 			      * options.shotMass));
1115 		} else
1116 		    drainfactor = 1.0;
1117 		drain = ED_SHOT_HIT * drainfactor * SHOT_MULT(obj);
1118 		Player_add_fuel(pl, drain);
1119 	    }
1120 	    pl->forceVisible += SHOT_MULT(obj);
1121 	    break;
1122 
1123 	default:
1124 	    warn("Player hit by unknown object type %d.", obj->type);
1125 	    break;
1126 	}
1127 
1128 	if (pl->fuel.sum <= 0)
1129 	    CLR_BIT(pl->used, HAS_SHIELD);
1130 	if (!BIT(pl->used, HAS_SHIELD)
1131 	    && Player_has_armor(pl))
1132 	    Player_hit_armor(pl);
1133 
1134     } else {
1135 	switch (obj->type) {
1136 	case OBJ_TORPEDO:
1137 	case OBJ_SMART_SHOT:
1138 	case OBJ_HEAT_SHOT:
1139 	case OBJ_SHOT:
1140 	case OBJ_CANNON_SHOT:
1141 	    if (BIT(obj->obj_status, FROMCANNON)) {
1142 		cannon = Cannon_by_id(obj->id);
1143 
1144 		sound_play_sensors(pl->pos, PLAYER_HIT_CANNONFIRE_SOUND);
1145 		Set_message_f("%s was hit by cannonfire.", pl->name);
1146 	    } else if (obj->id == NO_ID) {
1147 		Set_message_f("%s was killed by %s.", pl->name,
1148 			      Describe_shot(obj->type, obj->obj_status,
1149 					    obj->mods, 1));
1150 	    } else {
1151 		kp = Player_by_id(obj->id);
1152 		Set_message_f("%s was killed by %s from %s.%s", pl->name,
1153 			      Describe_shot(obj->type, obj->obj_status,
1154 					    obj->mods, 1),
1155 			      kp->name,
1156 			      kp->id == pl->id ? "  How strange!" : "");
1157 		if (kp->id == pl->id) {
1158 		    sound_play_sensors(pl->pos, PLAYER_SHOT_THEMSELF_SOUND);
1159 		}
1160 	    }
1161 
1162 	    Handle_Scoring(SCORE_SHOT_DEATH,kp,pl,obj,NULL);
1163 
1164 	    if ((!BIT(obj->obj_status, FROMCANNON)) && (!(obj->id == NO_ID || kp->id == pl->id))) {
1165 		Robot_war(pl, kp);
1166 	    }
1167 
1168 	    /* survival */
1169 	    if (options.survivalScore != 0.0 && (pl->survival_time > 10
1170     	    	|| pl->survival_time > Rank_get_max_survival_time(pl))) {
1171 
1172 		Set_message_f(" < %s has survived %.1f seconds (%.1f)>",
1173 			      pl->name,
1174 			      pl->survival_time,
1175 			      Rank_get_max_survival_time(pl));
1176 
1177 		Rank_survival(pl, pl->survival_time);
1178 
1179 		/* if there are no rounds in survival mode */
1180 		/* deaths act like rounds                  */
1181 		if (!BIT(world->rules->mode, LIMITED_LIVES))
1182 		    Rank_add_round(pl);
1183 
1184 		Rank_write_webpage();
1185 		Rank_write_rankfile();
1186 		Rank_show_ranks();
1187 	    }
1188 	    pl->survival_time = 0;
1189 	    /* survival */
1190 
1191 	    Player_set_state(pl, PL_STATE_KILLED);
1192 	    return;
1193 
1194 	default:
1195 	    break;
1196 	}
1197     }
1198 }
1199 
AsteroidCollision(void)1200 static void AsteroidCollision(void)
1201 {
1202     int j, radius, obj_count;
1203     object_t *ast;
1204     object_t *obj = NULL, **obj_list;
1205     list_t list;
1206     list_iter_t iter;
1207     double damage = 0.0;
1208     bool sound = false;
1209 
1210     list = Asteroid_get_list();
1211     if (!list)
1212 	return;
1213 
1214     for (iter = List_begin(list); iter != List_end(list); LI_FORWARD(iter)) {
1215 	ast = (object_t *)LI_DATA(iter);
1216 
1217 	assert(ast->type == OBJ_ASTEROID);
1218 
1219 	if (ast->life <= 0.0)
1220 	    continue;
1221 
1222 	assert(World_contains_clpos(ast->pos));
1223 
1224 	if (NumObjs >= options.cellGetObjectsThreshold)
1225 	    Cell_get_objects(ast->pos, ast->pl_radius / BLOCK_SZ + 1,
1226 			     300, &obj_list, &obj_count);
1227 	else {
1228 	    obj_list = Obj;
1229 	    obj_count = NumObjs;
1230 	}
1231 
1232 	for (j = 0; j < obj_count; j++) {
1233 	    obj = obj_list[j];
1234 	    assert(obj != NULL);
1235 
1236 	    /* asteroids don't hit these objects */
1237 	    if ((obj->type == OBJ_ITEM
1238 		 || obj->type == OBJ_DEBRIS
1239 		 || obj->type == OBJ_SPARK
1240 		 || obj->type == OBJ_WRECKAGE)
1241 		&& obj->id == NO_ID
1242 		&& !BIT(obj->obj_status, FROMCANNON))
1243 		continue;
1244 	    /* don't collide while still overlapping  after breaking */
1245 	    if (obj->type == OBJ_ASTEROID && ast->fuse > 0)
1246 		continue;
1247 	    /* don't collide with self */
1248 	    if (obj == ast)
1249 		continue;
1250 	    /* don't collide with phased balls */
1251 	    if (obj->type == OBJ_BALL
1252 		&& obj->id != NO_ID
1253 		&& Player_is_phasing(Player_by_id(obj->id)))
1254 		continue;
1255 
1256 	    radius = (ast->pl_radius + obj->pl_radius) * CLICK;
1257 	    if (!in_range(OBJ_PTR(ast), obj, (double)radius))
1258 		continue;
1259 
1260 	    switch (obj->type) {
1261 	    case OBJ_BALL:
1262 		Obj_repel(ast, obj, radius);
1263 		if (options.treasureCollisionDestroys)
1264 		    obj->life = 0.0;
1265 		damage = ED_BALL_HIT;
1266 		sound = true;
1267 		break;
1268 	    case OBJ_ASTEROID:
1269 		obj->life -= ASTEROID_FUEL_HIT(
1270 		    collision_cost(ast->mass, VECTOR_LENGTH(ast->vel)),
1271 		    WIRE_PTR(obj)->wire_size);
1272 		damage = -collision_cost(obj->mass, VECTOR_LENGTH(obj->vel));
1273 		Delta_mv_elastic(ast, obj);
1274 		/* avoid doing collision twice */
1275 		obj->fuse = timeStep;
1276 		sound = true;
1277 		break;
1278 	    case OBJ_SPARK:
1279 		obj->life = 0.0;
1280 		Delta_mv(ast, obj);
1281 		damage = 0.0;
1282 		break;
1283 	    case OBJ_DEBRIS:
1284 	    case OBJ_WRECKAGE:
1285 		obj->life = 0.0;
1286 		damage = -collision_cost(obj->mass, VECTOR_LENGTH(obj->vel));
1287 		Delta_mv(ast, obj);
1288 		break;
1289 	    case OBJ_MINE:
1290 		if (!BIT(obj->obj_status, CONFUSED))
1291 		    obj->life = 0.0;
1292 		break;
1293 	    case OBJ_SHOT:
1294 	    case OBJ_CANNON_SHOT:
1295 		obj->life = 0.0;
1296 		Delta_mv(ast, obj);
1297 		damage = ED_SHOT_HIT;
1298 		sound = true;
1299 		break;
1300 	    case OBJ_SMART_SHOT:
1301 	    case OBJ_TORPEDO:
1302 	    case OBJ_HEAT_SHOT:
1303 		obj->life = 0.0;
1304 		Delta_mv(ast, obj);
1305 		damage = Missile_hit_drain(MISSILE_PTR(obj));
1306 		sound = true;
1307 		break;
1308 	    case OBJ_PULSE:
1309 		obj->life = 0;
1310 		damage = ED_LASER_HIT;
1311 		sound = true;
1312 		break;
1313 	    default:
1314 		Delta_mv(ast, obj);
1315 		damage = 0.0;
1316 		break;
1317 	    }
1318 
1319 	    if (ast->life > 0.0) {
1320 		/* kps - this is some strange sort of hack - fix it*/
1321 		/*if (ast->life <= ast->fuselife) {*/
1322 		ast->life += ASTEROID_FUEL_HIT(damage,
1323 					       WIRE_PTR(ast)->wire_size);
1324 		/*}*/
1325 		if (sound)
1326 		    sound_play_sensors(ast->pos, ASTEROID_HIT_SOUND);
1327 		if (ast->life < 0.0)
1328 		    ast->life = 0.0;
1329 		if (ast->life == 0.0) {
1330 		    if ((obj->id != NO_ID
1331 			 || (obj->type == OBJ_BALL
1332 			     && BALL_PTR(obj)->ball_owner != NO_ID))) {
1333 			int owner_id = ((obj->type == OBJ_BALL)
1334 					? BALL_PTR(obj)->ball_owner
1335 					: obj->id);
1336 			player_t *pl = Player_by_id(owner_id);
1337 			Handle_Scoring(SCORE_ASTEROID_KILL,pl,NULL,ast,NULL);
1338 		    }
1339 
1340 		    /* break; */
1341 		}
1342 	    }
1343 	}
1344     }
1345 }
1346 
1347 
1348 /* do ball - object and ball - checkpoint collisions */
BallCollision(void)1349 static void BallCollision(void)
1350 {
1351     int i, j, obj_count;
1352     int	ignored_object_types;
1353     object_t **obj_list;
1354     object_t *obj;
1355     ballobject_t *ball;
1356 
1357     /*
1358      * These object types ignored;
1359      * some are handled by other code,
1360      * some don't interact.
1361      */
1362     ignored_object_types = OBJ_PLAYER_BIT
1363 	| OBJ_ASTEROID_BIT | OBJ_MINE_BIT | OBJ_ITEM_BIT;
1364     if (!options.ballSparkCollisions)
1365 	ignored_object_types |= OBJ_SPARK_BIT;
1366 
1367     for (i = 0; i < NumObjs; i++) {
1368 	ball = BALL_IND(i);
1369 
1370 	/* ignore if: */
1371 	if (ball->type != OBJ_BALL ||	/* not a ball */
1372 	    ball->life <= 0.0 ||	/* dying ball */
1373 	    (ball->id != NO_ID
1374 	     && Player_is_phasing(Player_by_id(ball->id))) ||
1375 					/* phased ball */
1376 	    ball->ball_treasure->have)	/* safe in a treasure */
1377 	    continue;
1378 
1379 	/* Ball - checkpoint */
1380 	if (BIT(world->rules->mode, TIMING)
1381 	    && options.ballrace
1382 	    && ball->ball_owner != NO_ID) {
1383 	    player_t *owner = Player_by_id(ball->ball_owner);
1384 
1385 	    if (!options.ballrace_connect || ball->id == owner->id) {
1386 		clpos_t cpos = Check_by_index(owner->check)->pos;
1387 
1388 		if (Wrap_length(ball->pos.cx - cpos.cx,
1389 				ball->pos.cy - cpos.cy)
1390 		    < options.checkpointRadius * BLOCK_CLICKS)
1391 		    Player_pass_checkpoint(owner);
1392 	    }
1393 	}
1394 
1395 	/* Ball - object */
1396 	if (!options.ballCollisions)
1397 	    continue;
1398 
1399 	if (NumObjs >= options.cellGetObjectsThreshold)
1400 	    Cell_get_objects(ball->pos, 4, 300, &obj_list, &obj_count);
1401 	else {
1402 	    obj_list = Obj;
1403 	    obj_count = NumObjs;
1404 	}
1405 
1406 	for (j = 0; j < obj_count; j++) {
1407 	    int radius;
1408 
1409 	    obj = obj_list[j];
1410 
1411 	    if (BIT(OBJ_TYPEBIT(obj->type), ignored_object_types))
1412 		continue;
1413 
1414 	    /* have we already done this ball pair? */
1415 	    if (obj->type == OBJ_BALL && obj <= OBJ_PTR(ball))
1416 		continue;
1417 
1418 	    radius = (ball->pl_radius + obj->pl_radius) * CLICK;
1419 	    if (!in_range(OBJ_PTR(ball), obj, (double)radius))
1420 		continue;
1421 
1422 	    /* bang! */
1423 
1424 	    switch (obj->type) {
1425 	    case OBJ_BALL:
1426 		/* Balls bounce off other balls that aren't safe in
1427 		 * the treasure: */
1428 		{
1429 		    ballobject_t *b2 = BALL_PTR(obj);
1430 		    if (b2->ball_treasure->have)
1431 			break;
1432 
1433 		    if (b2->id != NO_ID
1434 			&& Player_is_phasing(Player_by_id(b2->id)))
1435 			break;
1436 		}
1437 
1438 		/* if the collision was too violent, destroy ball and object */
1439 		if ((sqr(ball->vel.x - obj->vel.x) +
1440 		     sqr(ball->vel.y - obj->vel.y)) >
1441 		    sqr(options.maxObjectWallBounceSpeed)) {
1442 		    ball->life = 0.0;
1443 		    obj->life  = 0.0;
1444 		} else
1445 		    /* they bounce */
1446 		    Delta_mv_partly_elastic(OBJ_PTR(ball),
1447 					    obj,
1448 					    options.playerBallBounceBrakeFactor);
1449 		break;
1450 
1451 	    /* balls absorb and destroy all other objects: */
1452 	    case OBJ_SPARK:
1453 	    case OBJ_TORPEDO:
1454 	    case OBJ_SMART_SHOT:
1455 	    case OBJ_HEAT_SHOT:
1456 	    case OBJ_SHOT:
1457 	    case OBJ_CANNON_SHOT:
1458 	    case OBJ_DEBRIS:
1459 	    case OBJ_WRECKAGE:
1460 		Delta_mv(OBJ_PTR(ball), obj);
1461 		obj->life = 0.0;
1462 		break;
1463 	    default:
1464 		break;
1465 	    }
1466 	}
1467     }
1468 }
1469 
1470 
1471 /* do mine - object collisions */
MineCollision(void)1472 static void MineCollision(void)
1473 {
1474     int i, j, obj_count;
1475     object_t **obj_list;
1476     object_t *obj;
1477     mineobject_t *mine;
1478 
1479     if (!options.mineShotDetonateDistance)
1480 	return;
1481 
1482     for (i = 0; i < NumObjs; i++) {
1483 	mine = MINE_IND(i);
1484 
1485 	/* ignore if: */
1486 	if (mine->type != OBJ_MINE ||	/* not a mine */
1487 	    mine->life <= 0.0)		/* dying mine */
1488 	    continue;
1489 
1490 	if (NumObjs >= options.cellGetObjectsThreshold)
1491 	    Cell_get_objects(mine->pos, 4, 300, &obj_list, &obj_count);
1492 	else {
1493 	    obj_list = Obj;
1494 	    obj_count = NumObjs;
1495 	}
1496 
1497 	for (j = 0; j < obj_count; j++) {
1498 	    double radius;
1499 
1500 	    obj = obj_list[j];
1501 
1502 	    /*
1503 	     * These object types ignored;
1504 	     * some are handled by other code,
1505 	     * some don't interact.
1506 	     */
1507 	    if (!(obj->type == OBJ_SHOT
1508 		  || obj->type == OBJ_TORPEDO
1509 		  || obj->type == OBJ_SMART_SHOT
1510 		  || obj->type == OBJ_HEAT_SHOT
1511 		  || obj->type == OBJ_CANNON_SHOT))
1512 		continue;
1513 
1514 	    radius = (options.mineShotDetonateDistance + obj->pl_radius)
1515 		* CLICK;
1516 	    if (!in_range(OBJ_PTR(mine), obj, radius))
1517 		continue;
1518 
1519 	    /* bang! */
1520 	    obj->life = 0.0;
1521 	    mine->life = 0.0;
1522 	    break;
1523 	}
1524     }
1525 }
1526