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