1 /* $Id: walls.c,v 5.28 2003/09/16 21:02:10 bertg Exp $
2 *
3 * XPilot, a multiplayer gravity war game. Copyright (C) 1991-2001 by
4 *
5 * Bj�rn Stabell <bjoern@xpilot.org>
6 * Ken Ronny Schouten <ken@xpilot.org>
7 * Bert Gijsbers <bert@xpilot.org>
8 * Dick Balaska <dick@xpilot.org>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 */
24
25 #include <stdlib.h>
26 #include <string.h>
27 #include <stdio.h>
28 #include <errno.h>
29 #include <math.h>
30 #include <limits.h>
31
32 #ifdef _WINDOWS
33 # include "NT/winServer.h"
34 #endif
35
36 #define SERVER
37 #include "version.h"
38 #include "config.h"
39 #include "serverconst.h"
40 #include "global.h"
41 #include "proto.h"
42 #include "score.h"
43 #include "saudio.h"
44 #include "item.h"
45 #include "error.h"
46 #include "walls.h"
47 #include "click.h"
48 #include "objpos.h"
49 #include "commonproto.h"
50
51 char walls_version[] = VERSION;
52
53 #define WALLDIST_MASK \
54 (FILLED_BIT | REC_LU_BIT | REC_LD_BIT | REC_RU_BIT | REC_RD_BIT \
55 | FUEL_BIT | CANNON_BIT | TREASURE_BIT | TARGET_BIT \
56 | CHECK_BIT | WORMHOLE_BIT)
57
58 unsigned SPACE_BLOCKS = (
59 SPACE_BIT | BASE_BIT | WORMHOLE_BIT |
60 POS_GRAV_BIT | NEG_GRAV_BIT | CWISE_GRAV_BIT | ACWISE_GRAV_BIT |
61 UP_GRAV_BIT | DOWN_GRAV_BIT | RIGHT_GRAV_BIT | LEFT_GRAV_BIT |
62 DECOR_LU_BIT | DECOR_LD_BIT | DECOR_RU_BIT | DECOR_RD_BIT |
63 DECOR_FILLED_BIT | CHECK_BIT | ITEM_CONCENTRATOR_BIT |
64 FRICTION_BIT | ASTEROID_CONCENTRATOR_BIT
65 );
66
67 static struct move_parameters mp;
68 static DFLOAT wallBounceExplosionMult;
69 static char msg[MSG_LEN];
70
71 /*
72 * Two dimensional array giving for each point the distance
73 * to the nearest wall. Measured in blocks times 2.
74 */
75 static unsigned char **walldist;
76
77
78 /*
79 * Allocate memory for the two dimensional "walldist" array.
80 */
Walldist_alloc(void)81 static void Walldist_alloc(void)
82 {
83 int x;
84 unsigned char *wall_line;
85 unsigned char **wall_ptr;
86
87 walldist = (unsigned char **)malloc(
88 World.x * sizeof(unsigned char *) + World.x * World.y);
89 if (!walldist) {
90 error("No memory for walldist");
91 exit(1);
92 }
93 wall_ptr = walldist;
94 wall_line = (unsigned char *)(wall_ptr + World.x);
95 for (x = 0; x < World.x; x++) {
96 *wall_ptr = wall_line;
97 wall_ptr += 1;
98 wall_line += World.y;
99 }
100 }
101
102 /*
103 * Dump the "walldist" array to file as a Portable PixMap.
104 * Mainly used for debugging purposes.
105 */
Walldist_dump(void)106 static void Walldist_dump(void)
107 {
108 #ifdef DEVELOPMENT
109 char name[1024];
110 FILE *fp;
111 int x, y;
112 unsigned char *line;
113
114 if (!getenv("WALLDISTDUMP")) {
115 return;
116 }
117
118 sprintf(name, "walldist.ppm");
119 fp = fopen(name, "w");
120 if (!fp) {
121 error("%s", name);
122 return;
123 }
124 line = (unsigned char *)malloc(3 * World.x);
125 if (!line) {
126 error("No memory for walldist dump");
127 fclose(fp);
128 return;
129 }
130 fprintf(fp, "P6\n");
131 fprintf(fp, "%d %d\n", World.x, World.y);
132 fprintf(fp, "%d\n", 255);
133 for (y = World.y - 1; y >= 0; y--) {
134 for (x = 0; x < World.x; x++) {
135 if (walldist[x][y] == 0) {
136 line[x * 3 + 0] = 255;
137 line[x * 3 + 1] = 0;
138 line[x * 3 + 2] = 0;
139 }
140 else if (walldist[x][y] == 2) {
141 line[x * 3 + 0] = 0;
142 line[x * 3 + 1] = 255;
143 line[x * 3 + 2] = 0;
144 }
145 else if (walldist[x][y] == 3) {
146 line[x * 3 + 0] = 0;
147 line[x * 3 + 1] = 0;
148 line[x * 3 + 2] = 255;
149 }
150 else {
151 line[x * 3 + 0] = walldist[x][y];
152 line[x * 3 + 1] = walldist[x][y];
153 line[x * 3 + 2] = walldist[x][y];
154 }
155 }
156 fwrite(line, World.x, 3, fp);
157 }
158 free(line);
159 fclose(fp);
160
161 printf("Walldist dumped to %s\n", name);
162 #endif
163 }
164
Walldist_init(void)165 static void Walldist_init(void)
166 {
167 int x, y, dx, dy, wx, wy;
168 int dist;
169 int mindist;
170 int maxdist = 2 * MIN(World.x, World.y);
171 int newdist;
172
173 typedef struct Qelmt { short x, y; } Qelmt_t;
174 Qelmt_t *q;
175 int qfront = 0, qback = 0;
176
177 if (maxdist > 255) {
178 maxdist = 255;
179 }
180 q = (Qelmt_t *)malloc(World.x * World.y * sizeof(Qelmt_t));
181 if (!q) {
182 error("No memory for walldist init");
183 exit(1);
184 }
185 for (x = 0; x < World.x; x++) {
186 for (y = 0; y < World.y; y++) {
187 if (BIT((1 << World.block[x][y]), WALLDIST_MASK)
188 && (World.block[x][y] != WORMHOLE
189 || World.wormHoles[wormXY(x, y)].type != WORM_OUT)) {
190 walldist[x][y] = 0;
191 q[qback].x = x;
192 q[qback].y = y;
193 qback++;
194 } else {
195 walldist[x][y] = maxdist;
196 }
197 }
198 }
199 if (!BIT(World.rules->mode, WRAP_PLAY)) {
200 for (x = 0; x < World.x; x++) {
201 for (y = 0; y < World.y; y += (!x || x == World.x - 1)
202 ? 1 : (World.y - (World.y > 1))) {
203 if (walldist[x][y] > 1) {
204 walldist[x][y] = 2;
205 q[qback].x = x;
206 q[qback].y = y;
207 qback++;
208 }
209 }
210 }
211 }
212 while (qfront != qback) {
213 x = q[qfront].x;
214 y = q[qfront].y;
215 if (++qfront == World.x * World.y) {
216 qfront = 0;
217 }
218 dist = walldist[x][y];
219 mindist = dist + 2;
220 if (mindist >= 255) {
221 continue;
222 }
223 for (dx = -1; dx <= 1; dx++) {
224 if (BIT(World.rules->mode, WRAP_PLAY)
225 || (x + dx >= 0 && x + dx < World.x)) {
226 wx = WRAP_XBLOCK(x + dx);
227 for (dy = -1; dy <= 1; dy++) {
228 if (BIT(World.rules->mode, WRAP_PLAY)
229 || (y + dy >= 0 && y + dy < World.y)) {
230 wy = WRAP_YBLOCK(y + dy);
231 if (walldist[wx][wy] > mindist) {
232 newdist = mindist;
233 if (dist == 0) {
234 if (World.block[x][y] == REC_LD) {
235 if (dx == +1 && dy == +1) {
236 newdist = mindist + 1;
237 }
238 }
239 else if (World.block[x][y] == REC_RD) {
240 if (dx == -1 && dy == +1) {
241 newdist = mindist + 1;
242 }
243 }
244 else if (World.block[x][y] == REC_LU) {
245 if (dx == +1 && dy == -1) {
246 newdist = mindist + 1;
247 }
248 }
249 else if (World.block[x][y] == REC_RU) {
250 if (dx == -1 && dy == -1) {
251 newdist = mindist + 1;
252 }
253 }
254 }
255 if (newdist < walldist[wx][wy]) {
256 walldist[wx][wy] = newdist;
257 q[qback].x = wx;
258 q[qback].y = wy;
259 if (++qback == World.x * World.y) {
260 qback = 0;
261 }
262 }
263 }
264 }
265 }
266 }
267 }
268 }
269 free(q);
270 Walldist_dump();
271 }
272
Walls_init(void)273 void Walls_init(void)
274 {
275 Walldist_alloc();
276 Walldist_init();
277 }
278
Treasure_init(void)279 void Treasure_init(void)
280 {
281 int i;
282 for (i = 0; i < World.NumTreasures; i++) {
283 Make_treasure_ball(i);
284 }
285 }
286
Move_init(void)287 void Move_init(void)
288 {
289 mp.click_width = PIXEL_TO_CLICK(World.width);
290 mp.click_height = PIXEL_TO_CLICK(World.height);
291
292 LIMIT(maxObjectWallBounceSpeed, 0, World.hypotenuse);
293 LIMIT(maxShieldedWallBounceSpeed, 0, World.hypotenuse);
294 LIMIT(maxUnshieldedWallBounceSpeed, 0, World.hypotenuse);
295 LIMIT(maxShieldedWallBounceAngle, 0, 180);
296 LIMIT(maxUnshieldedWallBounceAngle, 0, 180);
297 LIMIT(playerWallBrakeFactor, 0, 1);
298 LIMIT(objectWallBrakeFactor, 0, 1);
299 LIMIT(objectWallBounceLifeFactor, 0, 1);
300 LIMIT(wallBounceFuelDrainMult, 0, 1000);
301 wallBounceExplosionMult = sqrt(wallBounceFuelDrainMult);
302
303 mp.max_shielded_angle = (int)(maxShieldedWallBounceAngle * RES / 360);
304 mp.max_unshielded_angle = (int)(maxUnshieldedWallBounceAngle * RES / 360);
305
306 mp.obj_bounce_mask = 0;
307 if (sparksWallBounce) {
308 SET_BIT(mp.obj_bounce_mask, OBJ_SPARK);
309 }
310 if (debrisWallBounce) {
311 SET_BIT(mp.obj_bounce_mask, OBJ_DEBRIS);
312 }
313 if (shotsWallBounce) {
314 SET_BIT(mp.obj_bounce_mask, OBJ_SHOT|OBJ_CANNON_SHOT);
315 }
316 if (itemsWallBounce) {
317 SET_BIT(mp.obj_bounce_mask, OBJ_ITEM);
318 }
319 if (missilesWallBounce) {
320 SET_BIT(mp.obj_bounce_mask, OBJ_SMART_SHOT|OBJ_TORPEDO|OBJ_HEAT_SHOT);
321 }
322 if (minesWallBounce) {
323 SET_BIT(mp.obj_bounce_mask, OBJ_MINE);
324 }
325 if (ballsWallBounce) {
326 SET_BIT(mp.obj_bounce_mask, OBJ_BALL);
327 }
328 if (asteroidsWallBounce) {
329 SET_BIT(mp.obj_bounce_mask, OBJ_ASTEROID);
330 }
331
332 mp.obj_cannon_mask = (KILLING_SHOTS) | OBJ_MINE | OBJ_SHOT | OBJ_PULSE |
333 OBJ_SMART_SHOT | OBJ_TORPEDO | OBJ_HEAT_SHOT |
334 OBJ_ASTEROID;
335 if (cannonsUseItems)
336 mp.obj_cannon_mask |= OBJ_ITEM;
337 mp.obj_target_mask = mp.obj_cannon_mask | OBJ_BALL | OBJ_SPARK;
338 mp.obj_treasure_mask = mp.obj_bounce_mask | OBJ_BALL | OBJ_PULSE;
339 }
340
Bounce_edge(move_state_t * ms,move_bounce_t bounce)341 static void Bounce_edge(move_state_t *ms, move_bounce_t bounce)
342 {
343 if (bounce == BounceHorLo) {
344 if (ms->mip->edge_bounce) {
345 ms->todo.x = -ms->todo.x;
346 ms->vel.x = -ms->vel.x;
347 if (!ms->mip->pl) {
348 ms->dir = MOD2(RES / 2 - ms->dir, RES);
349 }
350 }
351 else {
352 ms->todo.x = 0;
353 ms->vel.x = 0;
354 if (!ms->mip->pl) {
355 ms->dir = (ms->vel.y < 0) ? (3*RES/4) : RES/4;
356 }
357 }
358 }
359 else if (bounce == BounceHorHi) {
360 if (ms->mip->edge_bounce) {
361 ms->todo.x = -ms->todo.x;
362 ms->vel.x = -ms->vel.x;
363 if (!ms->mip->pl) {
364 ms->dir = MOD2(RES / 2 - ms->dir, RES);
365 }
366 }
367 else {
368 ms->todo.x = 0;
369 ms->vel.x = 0;
370 if (!ms->mip->pl) {
371 ms->dir = (ms->vel.y < 0) ? (3*RES/4) : RES/4;
372 }
373 }
374 }
375 else if (bounce == BounceVerLo) {
376 if (ms->mip->edge_bounce) {
377 ms->todo.y = -ms->todo.y;
378 ms->vel.y = -ms->vel.y;
379 if (!ms->mip->pl) {
380 ms->dir = MOD2(RES - ms->dir, RES);
381 }
382 }
383 else {
384 ms->todo.y = 0;
385 ms->vel.y = 0;
386 if (!ms->mip->pl) {
387 ms->dir = (ms->vel.x < 0) ? (RES/2) : 0;
388 }
389 }
390 }
391 else if (bounce == BounceVerHi) {
392 if (ms->mip->edge_bounce) {
393 ms->todo.y = -ms->todo.y;
394 ms->vel.y = -ms->vel.y;
395 if (!ms->mip->pl) {
396 ms->dir = MOD2(RES - ms->dir, RES);
397 }
398 }
399 else {
400 ms->todo.y = 0;
401 ms->vel.y = 0;
402 if (!ms->mip->pl) {
403 ms->dir = (ms->vel.x < 0) ? (RES/2) : 0;
404 }
405 }
406 }
407 ms->bounce = BounceEdge;
408 }
409
Bounce_wall(move_state_t * ms,move_bounce_t bounce)410 static void Bounce_wall(move_state_t *ms, move_bounce_t bounce)
411 {
412 if (!ms->mip->wall_bounce) {
413 ms->crash = CrashWall;
414 return;
415 }
416 if (bounce == BounceHorLo) {
417 ms->todo.x = -ms->todo.x;
418 ms->vel.x = -ms->vel.x;
419 if (!ms->mip->pl) {
420 ms->dir = MOD2(RES/2 - ms->dir, RES);
421 }
422 }
423 else if (bounce == BounceHorHi) {
424 ms->todo.x = -ms->todo.x;
425 ms->vel.x = -ms->vel.x;
426 if (!ms->mip->pl) {
427 ms->dir = MOD2(RES/2 - ms->dir, RES);
428 }
429 }
430 else if (bounce == BounceVerLo) {
431 ms->todo.y = -ms->todo.y;
432 ms->vel.y = -ms->vel.y;
433 if (!ms->mip->pl) {
434 ms->dir = MOD2(RES - ms->dir, RES);
435 }
436 }
437 else if (bounce == BounceVerHi) {
438 ms->todo.y = -ms->todo.y;
439 ms->vel.y = -ms->vel.y;
440 if (!ms->mip->pl) {
441 ms->dir = MOD2(RES - ms->dir, RES);
442 }
443 }
444 else {
445 clvec t = ms->todo;
446 vector v = ms->vel;
447 if (bounce == BounceLeftDown) {
448 ms->todo.x = -t.y;
449 ms->todo.y = -t.x;
450 ms->vel.x = -v.y;
451 ms->vel.y = -v.x;
452 if (!ms->mip->pl) {
453 ms->dir = MOD2(3*RES/4 - ms->dir, RES);
454 }
455 }
456 else if (bounce == BounceLeftUp) {
457 ms->todo.x = t.y;
458 ms->todo.y = t.x;
459 ms->vel.x = v.y;
460 ms->vel.y = v.x;
461 if (!ms->mip->pl) {
462 ms->dir = MOD2(RES/4 - ms->dir, RES);
463 }
464 }
465 else if (bounce == BounceRightDown) {
466 ms->todo.x = t.y;
467 ms->todo.y = t.x;
468 ms->vel.x = v.y;
469 ms->vel.y = v.x;
470 if (!ms->mip->pl) {
471 ms->dir = MOD2(RES/4 - ms->dir, RES);
472 }
473 }
474 else if (bounce == BounceRightUp) {
475 ms->todo.x = -t.y;
476 ms->todo.y = -t.x;
477 ms->vel.x = -v.y;
478 ms->vel.y = -v.x;
479 if (!ms->mip->pl) {
480 ms->dir = MOD2(3*RES/4 - ms->dir, RES);
481 }
482 }
483 }
484 ms->bounce = bounce;
485 }
486
487 /*
488 * Move a point through one block and detect
489 * wall collisions or bounces within that block.
490 * Complications arise when the point starts at
491 * the edge of a block. E.g., if a point is on the edge
492 * of a block to which block does it belong to?
493 *
494 * The caller supplies a set of input parameters and expects
495 * the following output:
496 * - the number of pixels moved within this block. (ms->done)
497 * - the number of pixels that still remain to be traversed. (ms->todo)
498 * - whether a crash happened, in which case no pixels will have been
499 * traversed. (ms->crash)
500 * - some extra optional output parameters depending upon the type
501 * of the crash. (ms->cannon, ms->wormhole, ms->target, ms->treasure)
502 * - whether the point bounced, in which case no pixels will have been
503 * traversed, only a change in direction. (ms->bounce, ms->vel, ms->todo)
504 */
Move_segment(move_state_t * ms)505 void Move_segment(move_state_t *ms)
506 {
507 int i;
508 int block_type; /* type of block we're going through */
509 int inside; /* inside the block or else on edge */
510 int need_adjust; /* other param (x or y) needs recalc */
511 unsigned wall_bounce; /* are we bouncing? what direction? */
512 ipos block; /* block index */
513 ipos blk2; /* new block index */
514 ivec sign; /* sign (-1 or 1) of direction */
515 clpos delta; /* delta position in clicks */
516 clpos enter; /* enter block position in clicks */
517 clpos leave; /* leave block position in clicks */
518 clpos offset; /* offset within block in clicks */
519 clpos off2; /* last offset in block in clicks */
520 clpos mid; /* the mean of (offset+off2)/2 */
521 const move_info_t *const mi = ms->mip; /* alias */
522 int hole; /* which wormhole */
523 ballobject *ball;
524
525 /*
526 * Fill in default return values.
527 */
528 ms->crash = NotACrash;
529 ms->bounce = NotABounce;
530 ms->done.x = 0;
531 ms->done.y = 0;
532
533 enter = ms->pos;
534 if (enter.x < 0 || enter.x >= mp.click_width
535 || enter.y < 0 || enter.y >= mp.click_height) {
536
537 if (!mi->edge_wrap) {
538 ms->crash = CrashUniverse;
539 return;
540 }
541 if (enter.x < 0) {
542 enter.x += mp.click_width;
543 if (enter.x < 0) {
544 ms->crash = CrashUniverse;
545 return;
546 }
547 }
548 else if (enter.x >= mp.click_width) {
549 enter.x -= mp.click_width;
550 if (enter.x >= mp.click_width) {
551 ms->crash = CrashUniverse;
552 return;
553 }
554 }
555 if (enter.y < 0) {
556 enter.y += mp.click_height;
557 if (enter.y < 0) {
558 ms->crash = CrashUniverse;
559 return;
560 }
561 }
562 else if (enter.y >= mp.click_height) {
563 enter.y -= mp.click_height;
564 if (enter.y >= mp.click_height) {
565 ms->crash = CrashUniverse;
566 return;
567 }
568 }
569 ms->pos = enter;
570 }
571
572 sign.x = (ms->vel.x < 0) ? -1 : 1;
573 sign.y = (ms->vel.y < 0) ? -1 : 1;
574 block.x = enter.x / BLOCK_CLICKS;
575 block.y = enter.y / BLOCK_CLICKS;
576 if (walldist[block.x][block.y] > 2) {
577 int maxcl = ((walldist[block.x][block.y] - 2) * BLOCK_CLICKS) >> 1;
578 if (maxcl >= sign.x * ms->todo.x && maxcl >= sign.y * ms->todo.y) {
579 /* entire movement is possible. */
580 ms->done.x = ms->todo.x;
581 ms->done.y = ms->todo.y;
582 }
583 else if (sign.x * ms->todo.x > sign.y * ms->todo.y) {
584 /* horizontal movement. */
585 ms->done.x = sign.x * maxcl;
586 ms->done.y = ms->todo.y * maxcl / (sign.x * ms->todo.x);
587 }
588 else {
589 /* vertical movement. */
590 ms->done.x = ms->todo.x * maxcl / (sign.y * ms->todo.y);
591 ms->done.y = sign.y * maxcl;
592 }
593 ms->todo.x -= ms->done.x;
594 ms->todo.y -= ms->done.y;
595 return;
596 }
597
598 offset.x = enter.x - block.x * BLOCK_CLICKS;
599 offset.y = enter.y - block.y * BLOCK_CLICKS;
600 inside = 1;
601 if (offset.x == 0) {
602 inside = 0;
603 if (sign.x == -1 && (offset.x = BLOCK_CLICKS, --block.x < 0)) {
604 if (mi->edge_wrap) {
605 block.x += World.x;
606 }
607 else {
608 Bounce_edge(ms, BounceHorLo);
609 return;
610 }
611 }
612 }
613 else if (enter.x == mp.click_width - 1
614 && !mi->edge_wrap
615 && ms->vel.x > 0) {
616 Bounce_edge(ms, BounceHorHi);
617 return;
618 }
619 if (offset.y == 0) {
620 inside = 0;
621 if (sign.y == -1 && (offset.y = BLOCK_CLICKS, --block.y < 0)) {
622 if (mi->edge_wrap) {
623 block.y += World.y;
624 }
625 else {
626 Bounce_edge(ms, BounceVerLo);
627 return;
628 }
629 }
630 }
631 else if (enter.y == mp.click_height - 1
632 && !mi->edge_wrap
633 && ms->vel.y > 0) {
634 Bounce_edge(ms, BounceVerHi);
635 return;
636 }
637
638 need_adjust = 0;
639 if (sign.x == -1) {
640 if (offset.x + ms->todo.x < 0) {
641 leave.x = enter.x - offset.x;
642 need_adjust = 1;
643 }
644 else {
645 leave.x = enter.x + ms->todo.x;
646 }
647 }
648 else {
649 if (offset.x + ms->todo.x > BLOCK_CLICKS) {
650 leave.x = enter.x + BLOCK_CLICKS - offset.x;
651 need_adjust = 1;
652 }
653 else {
654 leave.x = enter.x + ms->todo.x;
655 }
656 if (leave.x == mp.click_width && !mi->edge_wrap) {
657 leave.x--;
658 need_adjust = 1;
659 }
660 }
661 if (sign.y == -1) {
662 if (offset.y + ms->todo.y < 0) {
663 leave.y = enter.y - offset.y;
664 need_adjust = 1;
665 }
666 else {
667 leave.y = enter.y + ms->todo.y;
668 }
669 }
670 else {
671 if (offset.y + ms->todo.y > BLOCK_CLICKS) {
672 leave.y = enter.y + BLOCK_CLICKS - offset.y;
673 need_adjust = 1;
674 }
675 else {
676 leave.y = enter.y + ms->todo.y;
677 }
678 if (leave.y == mp.click_height && !mi->edge_wrap) {
679 leave.y--;
680 need_adjust = 1;
681 }
682 }
683 if (need_adjust && ms->todo.y && ms->todo.x) {
684 double wx = (double)(leave.x - enter.x) / ms->todo.x;
685 double wy = (double)(leave.y - enter.y) / ms->todo.y;
686 if (wx > wy) {
687 double x = ms->todo.x * wy;
688 leave.x = enter.x + DOUBLE_TO_INT(x);
689 }
690 else if (wx < wy) {
691 double y = ms->todo.y * wx;
692 leave.y = enter.y + DOUBLE_TO_INT(y);
693 }
694 }
695
696 delta.x = leave.x - enter.x;
697 delta.y = leave.y - enter.y;
698
699 block_type = World.block[block.x][block.y];
700
701 /*
702 * We test for several different bouncing directions against the wall.
703 * Sometimes there is more than one bounce possible if the point
704 * starts at the corner of a block.
705 * Therefore we maintain a bit mask for the bouncing possibilities
706 * and later we will determine which bounce is appropriate.
707 */
708 wall_bounce = 0;
709
710 if (!mi->phased) {
711
712 switch (block_type) {
713
714 default:
715 break;
716
717 case WORMHOLE:
718 if (!mi->wormhole_warps) {
719 break;
720 }
721 hole = wormXY(block.x, block.y);
722 if (World.wormHoles[hole].type == WORM_OUT) {
723 break;
724 }
725 if (mi->pl) {
726 blk2.x = OBJ_X_IN_BLOCKS(mi->pl);
727 blk2.y = OBJ_Y_IN_BLOCKS(mi->pl);
728 if (BIT(mi->pl->status, WARPED)) {
729 if (World.block[blk2.x][blk2.y] == WORMHOLE) {
730 int oldhole = wormXY(blk2.x, blk2.y);
731 if (World.wormHoles[oldhole].type == WORM_NORMAL
732 && mi->pl->wormHoleDest == oldhole) {
733 /*
734 * Don't warp again if we are still on the
735 * same wormhole we have just been warped to.
736 */
737 break;
738 }
739 }
740 CLR_BIT(mi->pl->status, WARPED);
741 }
742 if (blk2.x == block.x && blk2.y == block.y) {
743 ms->wormhole = hole;
744 ms->crash = CrashWormHole;
745 return;
746 }
747 }
748 else {
749 /*
750 * Warp object if this wormhole has ever warped a player.
751 * Warp the object to the same destination as the
752 * player has been warped to.
753 */
754 int last = World.wormHoles[hole].lastdest;
755 if (last >= 0
756 && (World.wormHoles[hole].countdown > 0 || !wormTime)
757 && last < World.NumWormholes
758 && World.wormHoles[last].type != WORM_IN
759 && last != hole
760 && (OBJ_X_IN_BLOCKS(mi->obj) != block.x
761 || OBJ_Y_IN_BLOCKS(mi->obj) != block.y) ) {
762 ms->done.x += (World.wormHoles[last].pos.x
763 - World.wormHoles[hole].pos.x) * BLOCK_CLICKS;
764 ms->done.y += (World.wormHoles[last].pos.y
765 - World.wormHoles[hole].pos.y) * BLOCK_CLICKS;
766 break;
767 }
768 }
769 break;
770
771 case CANNON:
772 if (!mi->cannon_crashes) {
773 break;
774 }
775 if (BIT(mi->obj->status, FROMCANNON)
776 && !BIT(World.rules->mode, TEAM_PLAY)) {
777 break;
778 }
779 for (i = 0; ; i++) {
780 if (World.cannon[i].blk_pos.x == block.x
781 && World.cannon[i].blk_pos.y == block.y) {
782 break;
783 }
784 }
785 ms->cannon = i;
786
787 if (BIT(World.cannon[i].used, HAS_PHASING_DEVICE)) {
788 break;
789 }
790
791 if (BIT(World.rules->mode, TEAM_PLAY)
792 && (teamImmunity
793 || BIT(mi->obj->status, FROMCANNON))
794 && mi->obj->team == World.cannon[i].team) {
795 break;
796 }
797 {
798 /*
799 * Calculate how far the point can travel in the cannon block
800 * before hitting the cannon.
801 * To reduce duplicate code we first transform all the
802 * different cannon types into one by matrix multiplications.
803 * Later we transform the result back to the real type.
804 */
805
806 ivec mx, my, dir;
807 clpos mirx, miry, start, end, todo, done, diff, a, b;
808 double d, w;
809
810 mirx.x = 0;
811 mirx.y = 0;
812 miry.x = 0;
813 miry.y = 0;
814 switch (World.cannon[i].dir) {
815 case DIR_UP:
816 mx.x = 1; mx.y = 0;
817 my.x = 0; my.y = 1;
818 break;
819 case DIR_DOWN:
820 mx.x = 1; mx.y = 0;
821 my.x = 0; my.y = -1;
822 miry.y = BLOCK_CLICKS;
823 break;
824 case DIR_RIGHT:
825 mx.x = 0; mx.y = 1;
826 my.x = -1; my.y = 0;
827 miry.x = BLOCK_CLICKS;
828 break;
829 case DIR_LEFT:
830 mx.x = 0; mx.y = -1;
831 my.x = 1; my.y = 0;
832 mirx.y = BLOCK_CLICKS;
833 break;
834 }
835 start.x = mirx.x + mx.x * offset.x + miry.x + my.x * offset.y;
836 start.y = mirx.y + mx.y * offset.x + miry.y + my.y * offset.y;
837 diff.x = mx.x * delta.x + my.x * delta.y;
838 diff.y = mx.y * delta.x + my.y * delta.y;
839 dir.x = mx.x * sign.x + my.x * sign.y;
840 dir.y = mx.y * sign.x + my.y * sign.y;
841 todo.x = mx.x * ms->todo.x + my.x * ms->todo.y;
842 todo.y = mx.y * ms->todo.x + my.y * ms->todo.y;
843
844 end.x = start.x + diff.x;
845 end.y = start.y + diff.y;
846
847 if (start.x <= BLOCK_CLICKS/2) {
848 if (3 * start.y <= 2 * start.x) {
849 ms->crash = CrashCannon;
850 return;
851 }
852 if (end.x <= BLOCK_CLICKS/2) {
853 if (3 * end.y > 2 * end.x) {
854 break;
855 }
856 }
857 }
858 else {
859 if (3 * start.y <= 2 * (BLOCK_CLICKS - start.x)) {
860 ms->crash = CrashCannon;
861 return;
862 }
863 if (end.x > BLOCK_CLICKS/2) {
864 if (3 * end.y > 2 * (BLOCK_CLICKS - end.x)) {
865 break;
866 }
867 }
868 }
869
870 done = diff;
871
872 /* is direction x-major? */
873 if (dir.x * diff.x >= dir.y * diff.y) {
874 /* x-major */
875 w = (double) todo.y / todo.x;
876 if (3 * todo.y != 2 * todo.x) {
877 d = (3 * start.y - 2 * start.x) / (2 - 3 * w);
878 a.x = DOUBLE_TO_INT(d);
879 a.y = (int)(a.x * w);
880 if (dir.x * a.x < dir.x * done.x && dir.x * a.x >= 0) {
881 if (start.y + a.y <= BLOCK_CLICKS/3) {
882 done = a;
883 if (!(done.x | done.y)) {
884 ms->crash = CrashCannon;
885 return;
886 }
887 }
888 }
889 }
890 if (-3 * todo.y != 2 * todo.x) {
891 d = (2 * BLOCK_CLICKS - 2 * start.x - 3 * start.y) /
892 (2 + 3 * w);
893 b.x = DOUBLE_TO_INT(d);
894 b.y = (int)(b.x * w);
895 if (dir.x * b.x < dir.x * done.x && dir.x * b.x >= 0) {
896 if (start.y + b.y <= BLOCK_CLICKS/3) {
897 done = b;
898 if (!(done.x | done.y)) {
899 ms->crash = CrashCannon;
900 return;
901 }
902 }
903 }
904 }
905 } else {
906 /* y-major */
907 w = (double) todo.x / todo.y;
908 d = (2 * start.x - 3 * start.y) / (3 - 2 * w);
909 a.y = DOUBLE_TO_INT(d);
910 a.x = (int)(a.y * w);
911 if (dir.y * a.y < dir.y * done.y && dir.y * a.y >= 0) {
912 if (start.y + a.y <= BLOCK_CLICKS/3) {
913 done = a;
914 if (!(done.x | done.y)) {
915 ms->crash = CrashCannon;
916 return;
917 }
918 }
919 }
920 d = (2 * BLOCK_CLICKS - 2 * start.x - 3 * start.y) /
921 (3 + 2 * w);
922 b.y = DOUBLE_TO_INT(d);
923 b.x = (int)(b.y * w);
924 if (dir.y * b.y < dir.y * done.y && dir.y * b.y >= 0) {
925 if (start.y + b.y <= BLOCK_CLICKS/3) {
926 done = b;
927 if (!(done.x | done.y)) {
928 ms->crash = CrashCannon;
929 return;
930 }
931 }
932 }
933 }
934
935 delta.x = mx.x * done.x + mx.y * done.y;
936 delta.y = my.x * done.x + my.y * done.y;
937 }
938 break;
939
940 case TREASURE:
941 if (block_type == TREASURE) {
942 if (mi->treasure_crashes) {
943 /*
944 * Test if the movement is within the upper half of
945 * the treasure, which is the upper half of a circle.
946 * If this is the case then we test if 3 samples
947 * are not hitting the treasure.
948 */
949 const DFLOAT r = 0.5f * BLOCK_CLICKS;
950 off2.x = offset.x + delta.x;
951 off2.y = offset.y + delta.y;
952 mid.x = (offset.x + off2.x) / 2;
953 mid.y = (offset.y + off2.y) / 2;
954 if (offset.y > r
955 && off2.y > r
956 && sqr(mid.x - r) + sqr(mid.y - r) > sqr(r)
957 && sqr(off2.x - r) + sqr(off2.y - r) > sqr(r)
958 && sqr(offset.x - r) + sqr(offset.y - r) > sqr(r)) {
959 break;
960 }
961
962 for (i = 0; ; i++) {
963 if (World.treasures[i].pos.x == block.x
964 && World.treasures[i].pos.y == block.y) {
965 break;
966 }
967 }
968 ms->treasure = i;
969 ms->crash = CrashTreasure;
970
971 /*
972 * We handle balls here, because the reaction
973 * depends on which team the treasure and the ball
974 * belong to.
975 */
976 if (mi->obj->type != OBJ_BALL) {
977 return;
978 }
979
980 ball = BALL_PTR(mi->obj);
981 if (ms->treasure == ball->treasure) {
982 /*
983 * Ball has been replaced back in the hoop from whence
984 * it came. If the player is on the same team as the
985 * hoop, then it should be replaced into the hoop without
986 * exploding and gets the player some points. Otherwise
987 * nothing interesting happens.
988 */
989 player *pl = NULL;
990 treasure_t *tt = &World.treasures[ms->treasure];
991
992 if (ball->owner != NO_ID)
993 pl = Players[GetInd[ball->owner]];
994
995 if (!BIT(World.rules->mode, TEAM_PLAY)
996 || !pl
997 || (pl->team !=
998 World.treasures[ball->treasure].team)) {
999 ball->life = LONG_MAX;
1000 ms->crash = NotACrash;
1001 break;
1002 }
1003
1004 ball->life = 0;
1005 SET_BIT(ball->status, (NOEXPLOSION|RECREATE));
1006
1007 SCORE(GetInd[pl->id], 5,
1008 tt->pos.x, tt->pos.y, "Treasure: ");
1009 sprintf(msg, " < %s (team %d) has replaced the treasure >",
1010 pl->name, pl->team);
1011 Set_message(msg);
1012 break;
1013 }
1014 if (ball->owner == NO_ID) {
1015 ball->life = 0;
1016 return;
1017 }
1018 if (BIT(World.rules->mode, TEAM_PLAY)
1019 && World.treasures[ms->treasure].team ==
1020 Players[GetInd[ball->owner]]->team) {
1021 /*
1022 * Ball has been brought back to home treasure.
1023 * The team should be punished.
1024 */
1025 sprintf(msg," < The ball was loose for %ld frames >",
1026 LONG_MAX - ball->life);
1027 Set_message(msg);
1028 if (captureTheFlag
1029 && !World.treasures[ms->treasure].have
1030 && !World.treasures[ms->treasure].empty) {
1031 strcpy(msg, "Your treasure must be safe before you can cash an opponent's!");
1032 Set_player_message(Players[GetInd[ball->owner]], msg);
1033 } else if (Punish_team(GetInd[ball->owner],
1034 ball->treasure, ms->treasure))
1035 CLR_BIT(ball->status, RECREATE);
1036 }
1037 ball->life = 0;
1038 return;
1039 }
1040 }
1041 /*FALLTHROUGH*/
1042
1043 case TARGET:
1044 if (block_type == TARGET) {
1045 if (mi->target_crashes) {
1046 /*-BA This can be slow for large number of targets.
1047 * added itemID array for extra speed, (at cost of some memory.)
1048 *
1049 *for (i = 0; ; i++) {
1050 * if (World.targets[i].pos.x == block.x
1051 * && World.targets[i].pos.y == block.y) {
1052 * break;
1053 * }
1054 * }
1055 *
1056 * ms->target = i;
1057 */
1058 ms->target = i = World.itemID[block.x][block.y];
1059
1060 if (!targetTeamCollision) {
1061 int team;
1062 if (mi->pl) {
1063 team = mi->pl->team;
1064 }
1065 else if (BIT(mi->obj->type, OBJ_BALL)) {
1066 ballobject *ball = BALL_PTR(mi->obj);
1067 if (ball->owner != NO_ID) {
1068 team = Players[GetInd[ball->owner]]->team;
1069 } else {
1070 team = TEAM_NOT_SET;
1071 }
1072 }
1073 else {
1074 team = mi->obj->team;
1075 }
1076 if (team == World.targets[i].team) {
1077 break;
1078 }
1079 }
1080 if (!mi->pl) {
1081 ms->crash = CrashTarget;
1082 return;
1083 }
1084 }
1085 }
1086 /*FALLTHROUGH*/
1087
1088 case FUEL:
1089 case FILLED:
1090 if (inside) {
1091 /* Could happen for targets reappearing and in case of bugs. */
1092 ms->crash = CrashWall;
1093 return;
1094 }
1095 if (offset.x == 0) {
1096 if (ms->vel.x > 0) {
1097 wall_bounce |= BounceHorLo;
1098 }
1099 }
1100 else if (offset.x == BLOCK_CLICKS) {
1101 if (ms->vel.x < 0) {
1102 wall_bounce |= BounceHorHi;
1103 }
1104 }
1105 if (offset.y == 0) {
1106 if (ms->vel.y > 0) {
1107 wall_bounce |= BounceVerLo;
1108 }
1109 }
1110 else if (offset.y == BLOCK_CLICKS) {
1111 if (ms->vel.y < 0) {
1112 wall_bounce |= BounceVerHi;
1113 }
1114 }
1115 if (wall_bounce) {
1116 break;
1117 }
1118 if (!(ms->todo.x | ms->todo.y)) {
1119 /* no bouncing possible and no movement. OK. */
1120 break;
1121 }
1122 if (!ms->todo.x && (offset.x == 0 || offset.x == BLOCK_CLICKS)) {
1123 /* tricky */
1124 break;
1125 }
1126 if (!ms->todo.y && (offset.y == 0 || offset.y == BLOCK_CLICKS)) {
1127 /* tricky */
1128 break;
1129 }
1130 /* what happened? we should never reach this */
1131 ms->crash = CrashWall;
1132 return;
1133
1134 case REC_LD:
1135 /* test for bounces first. */
1136 if (offset.x == 0) {
1137 if (ms->vel.x > 0) {
1138 wall_bounce |= BounceHorLo;
1139 }
1140 if (offset.y == BLOCK_CLICKS && ms->vel.x + ms->vel.y < 0) {
1141 wall_bounce |= BounceLeftDown;
1142 }
1143 }
1144 if (offset.y == 0) {
1145 if (ms->vel.y > 0) {
1146 wall_bounce |= BounceVerLo;
1147 }
1148 if (offset.x == BLOCK_CLICKS && ms->vel.x + ms->vel.y < 0) {
1149 wall_bounce |= BounceLeftDown;
1150 }
1151 }
1152 if (wall_bounce) {
1153 break;
1154 }
1155 if (offset.x + offset.y < BLOCK_CLICKS) {
1156 ms->crash = CrashWall;
1157 return;
1158 }
1159 if (offset.x + delta.x + offset.y + delta.y >= BLOCK_CLICKS) {
1160 /* movement is entirely within the space part of the block. */
1161 break;
1162 }
1163 /*
1164 * Find out where we bounce exactly
1165 * and how far we can move before bouncing.
1166 */
1167 if (sign.x * ms->todo.x >= sign.y * ms->todo.y) {
1168 double w = (double) ms->todo.y / ms->todo.x;
1169 delta.x = (int)((BLOCK_CLICKS - offset.x - offset.y) / (1 + w));
1170 delta.y = (int)(delta.x * w);
1171 if (offset.x + delta.x + offset.y + delta.y < BLOCK_CLICKS) {
1172 delta.x++;
1173 delta.y = (int)(delta.x * w);
1174 }
1175 leave.x = enter.x + delta.x;
1176 leave.y = enter.y + delta.y;
1177 if (!delta.x) {
1178 wall_bounce |= BounceLeftDown;
1179 break;
1180 }
1181 }
1182 else {
1183 double w = (double) ms->todo.x / ms->todo.y;
1184 delta.y = (int)((BLOCK_CLICKS - offset.x - offset.y) / (1 + w));
1185 delta.x = (int)(delta.y * w);
1186 if (offset.x + delta.x + offset.y + delta.y < BLOCK_CLICKS) {
1187 delta.y++;
1188 delta.x = (int)(delta.y * w);
1189 }
1190 leave.x = enter.x + delta.x;
1191 leave.y = enter.y + delta.y;
1192 if (!delta.y) {
1193 wall_bounce |= BounceLeftDown;
1194 break;
1195 }
1196 }
1197 break;
1198
1199 case REC_LU:
1200 if (offset.x == 0) {
1201 if (ms->vel.x > 0) {
1202 wall_bounce |= BounceHorLo;
1203 }
1204 if (offset.y == 0 && ms->vel.x < ms->vel.y) {
1205 wall_bounce |= BounceLeftUp;
1206 }
1207 }
1208 if (offset.y == BLOCK_CLICKS) {
1209 if (ms->vel.y < 0) {
1210 wall_bounce |= BounceVerHi;
1211 }
1212 if (offset.x == BLOCK_CLICKS && ms->vel.x < ms->vel.y) {
1213 wall_bounce |= BounceLeftUp;
1214 }
1215 }
1216 if (wall_bounce) {
1217 break;
1218 }
1219 if (offset.x < offset.y) {
1220 ms->crash = CrashWall;
1221 return;
1222 }
1223 if (offset.x + delta.x >= offset.y + delta.y) {
1224 break;
1225 }
1226 if (sign.x * ms->todo.x >= sign.y * ms->todo.y) {
1227 double w = (double) ms->todo.y / ms->todo.x;
1228 delta.x = (int)((offset.y - offset.x) / (1 - w));
1229 delta.y = (int)(delta.x * w);
1230 if (offset.x + delta.x < offset.y + delta.y) {
1231 delta.x++;
1232 delta.y = (int)(delta.x * w);
1233 }
1234 leave.x = enter.x + delta.x;
1235 leave.y = enter.y + delta.y;
1236 if (!delta.x) {
1237 wall_bounce |= BounceLeftUp;
1238 break;
1239 }
1240 }
1241 else {
1242 double w = (double) ms->todo.x / ms->todo.y;
1243 delta.y = (int)((offset.x - offset.y) / (1 - w));
1244 delta.x = (int)(delta.y * w);
1245 if (offset.x + delta.x < offset.y + delta.y) {
1246 delta.y--;
1247 delta.x = (int)(delta.y * w);
1248 }
1249 leave.x = enter.x + delta.x;
1250 leave.y = enter.y + delta.y;
1251 if (!delta.y) {
1252 wall_bounce |= BounceLeftUp;
1253 break;
1254 }
1255 }
1256 break;
1257
1258 case REC_RD:
1259 if (offset.x == BLOCK_CLICKS) {
1260 if (ms->vel.x < 0) {
1261 wall_bounce |= BounceHorHi;
1262 }
1263 if (offset.y == BLOCK_CLICKS && ms->vel.x > ms->vel.y) {
1264 wall_bounce |= BounceRightDown;
1265 }
1266 }
1267 if (offset.y == 0) {
1268 if (ms->vel.y > 0) {
1269 wall_bounce |= BounceVerLo;
1270 }
1271 if (offset.x == 0 && ms->vel.x > ms->vel.y) {
1272 wall_bounce |= BounceRightDown;
1273 }
1274 }
1275 if (wall_bounce) {
1276 break;
1277 }
1278 if (offset.x > offset.y) {
1279 ms->crash = CrashWall;
1280 return;
1281 }
1282 if (offset.x + delta.x <= offset.y + delta.y) {
1283 break;
1284 }
1285 if (sign.x * ms->todo.x >= sign.y * ms->todo.y) {
1286 double w = (double) ms->todo.y / ms->todo.x;
1287 delta.x = (int)((offset.y - offset.x) / (1 - w));
1288 delta.y = (int)(delta.x * w);
1289 if (offset.x + delta.x > offset.y + delta.y) {
1290 delta.x--;
1291 delta.y = (int)(delta.x * w);
1292 }
1293 leave.x = enter.x + delta.x;
1294 leave.y = enter.y + delta.y;
1295 if (!delta.x) {
1296 wall_bounce |= BounceRightDown;
1297 break;
1298 }
1299 }
1300 else {
1301 double w = (double) ms->todo.x / ms->todo.y;
1302 delta.y = (int)((offset.x - offset.y) / (1 - w));
1303 delta.x = (int)(delta.y * w);
1304 if (offset.x + delta.x > offset.y + delta.y) {
1305 delta.y++;
1306 delta.x = (int)(delta.y * w);
1307 }
1308 leave.x = enter.x + delta.x;
1309 leave.y = enter.y + delta.y;
1310 if (!delta.y) {
1311 wall_bounce |= BounceRightDown;
1312 break;
1313 }
1314 }
1315 break;
1316
1317 case REC_RU:
1318 if (offset.x == BLOCK_CLICKS) {
1319 if (ms->vel.x < 0) {
1320 wall_bounce |= BounceHorHi;
1321 }
1322 if (offset.y == 0 && ms->vel.x + ms->vel.y > 0) {
1323 wall_bounce |= BounceRightUp;
1324 }
1325 }
1326 if (offset.y == BLOCK_CLICKS) {
1327 if (ms->vel.y < 0) {
1328 wall_bounce |= BounceVerHi;
1329 }
1330 if (offset.x == 0 && ms->vel.x + ms->vel.y > 0) {
1331 wall_bounce |= BounceRightUp;
1332 }
1333 }
1334 if (wall_bounce) {
1335 break;
1336 }
1337 if (offset.x + offset.y > BLOCK_CLICKS) {
1338 ms->crash = CrashWall;
1339 return;
1340 }
1341 if (offset.x + delta.x + offset.y + delta.y <= BLOCK_CLICKS) {
1342 break;
1343 }
1344 if (sign.x * ms->todo.x >= sign.y * ms->todo.y) {
1345 double w = (double) ms->todo.y / ms->todo.x;
1346 delta.x = (int)((BLOCK_CLICKS - offset.x - offset.y) / (1 + w));
1347 delta.y = (int)(delta.x * w);
1348 if (offset.x + delta.x + offset.y + delta.y > BLOCK_CLICKS) {
1349 delta.x--;
1350 delta.y = (int)(delta.x * w);
1351 }
1352 leave.x = enter.x + delta.x;
1353 leave.y = enter.y + delta.y;
1354 if (!delta.x) {
1355 wall_bounce |= BounceRightUp;
1356 break;
1357 }
1358 }
1359 else {
1360 double w = (double) ms->todo.x / ms->todo.y;
1361 delta.y = (int)((BLOCK_CLICKS - offset.x - offset.y) / (1 + w));
1362 delta.x = (int)(delta.y * w);
1363 if (offset.x + delta.x + offset.y + delta.y > BLOCK_CLICKS) {
1364 delta.y--;
1365 delta.x = (int)(delta.y * w);
1366 }
1367 leave.x = enter.x + delta.x;
1368 leave.y = enter.y + delta.y;
1369 if (!delta.y) {
1370 wall_bounce |= BounceRightUp;
1371 break;
1372 }
1373 }
1374 break;
1375 }
1376
1377 if (wall_bounce) {
1378 /*
1379 * Bouncing. As there may be more than one possible bounce
1380 * test which bounce is not feasible because of adjacent walls.
1381 * If there still is more than one possible then pick one randomly.
1382 * Else if it turns out that none is feasible then we must have
1383 * been trapped inbetween two blocks. This happened in the early
1384 * stages of this code.
1385 */
1386 int count = 0;
1387 unsigned bit;
1388 unsigned save_wall_bounce = wall_bounce;
1389 unsigned block_mask = FILLED_BIT | FUEL_BIT;
1390
1391 if (!mi->target_crashes) {
1392 block_mask |= TARGET_BIT;
1393 }
1394 if (!mi->treasure_crashes) {
1395 block_mask |= TREASURE_BIT;
1396 }
1397 for (bit = 1; bit <= wall_bounce; bit <<= 1) {
1398 if (!(wall_bounce & bit)) {
1399 continue;
1400 }
1401
1402 CLR_BIT(wall_bounce, bit);
1403 switch (bit) {
1404
1405 case BounceHorLo:
1406 blk2.x = block.x - 1;
1407 if (blk2.x < 0) {
1408 if (!mi->edge_wrap) {
1409 continue;
1410 }
1411 blk2.x += World.x;
1412 }
1413 blk2.y = block.y;
1414 if (BIT(1 << World.block[blk2.x][blk2.y],
1415 block_mask|REC_RU_BIT|REC_RD_BIT)) {
1416 continue;
1417 }
1418 break;
1419
1420 case BounceHorHi:
1421 blk2.x = block.x + 1;
1422 if (blk2.x >= World.x) {
1423 if (!mi->edge_wrap) {
1424 continue;
1425 }
1426 blk2.x -= World.x;
1427 }
1428 blk2.y = block.y;
1429 if (BIT(1 << World.block[blk2.x][blk2.y],
1430 block_mask|REC_LU_BIT|REC_LD_BIT)) {
1431 continue;
1432 }
1433 break;
1434
1435 case BounceVerLo:
1436 blk2.x = block.x;
1437 blk2.y = block.y - 1;
1438 if (blk2.y < 0) {
1439 if (!mi->edge_wrap) {
1440 continue;
1441 }
1442 blk2.y += World.y;
1443 }
1444 if (BIT(1 << World.block[blk2.x][blk2.y],
1445 block_mask|REC_RU_BIT|REC_LU_BIT)) {
1446 continue;
1447 }
1448 break;
1449
1450 case BounceVerHi:
1451 blk2.x = block.x;
1452 blk2.y = block.y + 1;
1453 if (blk2.y >= World.y) {
1454 if (!mi->edge_wrap) {
1455 continue;
1456 }
1457 blk2.y -= World.y;
1458 }
1459 if (BIT(1 << World.block[blk2.x][blk2.y],
1460 block_mask|REC_RD_BIT|REC_LD_BIT)) {
1461 continue;
1462 }
1463 break;
1464 }
1465
1466 SET_BIT(wall_bounce, bit);
1467 count++;
1468 }
1469
1470 if (!count) {
1471 wall_bounce = save_wall_bounce;
1472 switch (wall_bounce) {
1473 case BounceHorLo|BounceVerLo:
1474 wall_bounce = BounceLeftDown;
1475 break;
1476 case BounceHorLo|BounceVerHi:
1477 wall_bounce = BounceLeftUp;
1478 break;
1479 case BounceHorHi|BounceVerLo:
1480 wall_bounce = BounceRightDown;
1481 break;
1482 case BounceHorHi|BounceVerHi:
1483 wall_bounce = BounceRightUp;
1484 break;
1485 default:
1486 switch (block_type) {
1487 case REC_LD:
1488 if ((offset.x == 0) ? (offset.y == BLOCK_CLICKS)
1489 : (offset.x == BLOCK_CLICKS && offset.y == 0)
1490 && ms->vel.x + ms->vel.y >= 0) {
1491 wall_bounce = 0;
1492 }
1493 break;
1494 case REC_LU:
1495 if ((offset.x == 0) ? (offset.y == 0)
1496 : (offset.x == BLOCK_CLICKS && offset.y == BLOCK_CLICKS)
1497 && ms->vel.x >= ms->vel.y) {
1498 wall_bounce = 0;
1499 }
1500 break;
1501 case REC_RD:
1502 if ((offset.x == 0) ? (offset.y == 0)
1503 : (offset.x == BLOCK_CLICKS && offset.y == BLOCK_CLICKS)
1504 && ms->vel.x <= ms->vel.y) {
1505 wall_bounce = 0;
1506 }
1507 break;
1508 case REC_RU:
1509 if ((offset.x == 0) ? (offset.y == BLOCK_CLICKS)
1510 : (offset.x == BLOCK_CLICKS && offset.y == 0)
1511 && ms->vel.x + ms->vel.y <= 0) {
1512 wall_bounce = 0;
1513 }
1514 break;
1515 }
1516 if (wall_bounce) {
1517 ms->crash = CrashWall;
1518 return;
1519 }
1520 }
1521 }
1522 else if (count > 1) {
1523 /*
1524 * More than one bounce possible.
1525 * Pick one randomly.
1526 */
1527 count = (int)(rfrac() * count);
1528 for (bit = 1; bit <= wall_bounce; bit <<= 1) {
1529 if (wall_bounce & bit) {
1530 if (count == 0) {
1531 wall_bounce = bit;
1532 break;
1533 } else {
1534 count--;
1535 }
1536 }
1537 }
1538 }
1539 }
1540
1541 } /* phased */
1542
1543 if (wall_bounce) {
1544 Bounce_wall(ms, (move_bounce_t) wall_bounce);
1545 }
1546 else {
1547 ms->done.x += delta.x;
1548 ms->done.y += delta.y;
1549 ms->todo.x -= delta.x;
1550 ms->todo.y -= delta.y;
1551 }
1552 }
1553
Cannon_dies(move_state_t * ms)1554 static void Cannon_dies(move_state_t *ms)
1555 {
1556 cannon_t *cannon = World.cannon + ms->cannon;
1557 int x = (int)cannon->pix_pos.x;
1558 int y = (int)cannon->pix_pos.y;
1559 int killer = -1;
1560 player *pl = NULL;
1561
1562 cannon->dead_time = cannonDeadTime;
1563 cannon->conn_mask = 0;
1564 World.block[cannon->blk_pos.x][cannon->blk_pos.y] = SPACE;
1565 Cannon_throw_items(ms->cannon);
1566 Cannon_init(ms->cannon);
1567 sound_play_sensors(x, y, CANNON_EXPLOSION_SOUND);
1568 Make_debris(
1569 /* pos.x, pos.y */ x, y,
1570 /* vel.x, vel.y */ 0.0, 0.0,
1571 /* owner id */ NO_ID,
1572 /* owner team */ cannon->team,
1573 /* kind */ OBJ_DEBRIS,
1574 /* mass */ 4.5,
1575 /* status */ GRAVITY,
1576 /* color */ RED,
1577 /* radius */ 6,
1578 /* min,max debris */ 20, 40,
1579 /* min,max dir */ (int)(cannon->dir - (RES * 0.2)), (int)(cannon->dir + (RES * 0.2)),
1580 /* min,max speed */ 20, 50,
1581 /* min,max life */ 8, 68
1582 );
1583 Make_wreckage(
1584 /* pos.x, pos.y */ x, y,
1585 /* vel.x, vel.y */ 0.0, 0.0,
1586 /* owner id */ NO_ID,
1587 /* owner team */ cannon->team,
1588 /* min,max mass */ 3.5, 23,
1589 /* total mass */ 28,
1590 /* status */ GRAVITY,
1591 /* color */ WHITE,
1592 /* max wreckage */ 10,
1593 /* min,max dir */ (int)(cannon->dir - (RES * 0.2)), (int)(cannon->dir + (RES * 0.2)),
1594 /* min,max speed */ 10, 25,
1595 /* min,max life */ 8, 68
1596 );
1597
1598 if (!ms->mip->pl) {
1599 if (ms->mip->obj->id != NO_ID) {
1600 killer = GetInd[ms->mip->obj->id];
1601 pl = Players[killer];
1602 }
1603 } else if (BIT(ms->mip->pl->used, HAS_SHIELD|HAS_EMERGENCY_SHIELD)
1604 == (HAS_SHIELD|HAS_EMERGENCY_SHIELD)) {
1605 pl = ms->mip->pl;
1606 killer = GetInd[pl->id];
1607 }
1608 if (pl) {
1609 if (cannonPoints > 0) {
1610 if (BIT(World.rules->mode, TEAM_PLAY)
1611 && teamCannons) {
1612 TEAM_SCORE(cannon->team, -cannonPoints);
1613 }
1614 if (pl->score <= cannonMaxScore
1615 && !(BIT(World.rules->mode, TEAM_PLAY)
1616 && pl->team == cannon->team)) {
1617 SCORE(killer, cannonPoints, cannon->blk_pos.x,
1618 cannon->blk_pos.y, "");
1619 }
1620 }
1621 }
1622 }
1623
Object_hits_target(move_state_t * ms,long player_cost)1624 static void Object_hits_target(move_state_t *ms, long player_cost)
1625 {
1626 target_t *targ = &World.targets[ms->target];
1627 object *obj = ms->mip->obj;
1628 int j,
1629 x, y,
1630 killer;
1631 DFLOAT sc, por,
1632 win_score = 0,
1633 lose_score = 0;
1634 int win_team_members = 0,
1635 lose_team_members = 0,
1636 somebody_flag = 0,
1637 targets_remaining = 0,
1638 targets_total = 0;
1639 DFLOAT drainfactor;
1640
1641 /* a normal shot or a direct mine hit work, cannons don't */
1642 /* KK: should shots/mines by cannons of opposing teams work? */
1643 /* also players suiciding on target will cause damage */
1644 if (!BIT(obj->type, KILLING_SHOTS|OBJ_MINE|OBJ_PULSE|OBJ_PLAYER)) {
1645 return;
1646 }
1647 if (obj->id <= 0) {
1648 return;
1649 }
1650 killer = GetInd[obj->id];
1651 if (targ->team == obj->team) {
1652 return;
1653 }
1654
1655 switch(obj->type) {
1656 case OBJ_SHOT:
1657 if (shotHitFuelDrainUsesKineticEnergy) {
1658 drainfactor = VECTOR_LENGTH(obj->vel);
1659 drainfactor = (drainfactor * drainfactor * ABS(obj->mass))
1660 / (ShotsSpeed * ShotsSpeed * ShotsMass);
1661 } else {
1662 drainfactor = 1.0f;
1663 }
1664 targ->damage += (int)(ED_SHOT_HIT * drainfactor * SHOT_MULT(obj));
1665 break;
1666 case OBJ_PULSE:
1667 targ->damage += (int)(ED_LASER_HIT);
1668 break;
1669 case OBJ_SMART_SHOT:
1670 case OBJ_TORPEDO:
1671 case OBJ_HEAT_SHOT:
1672 if (!obj->mass) {
1673 /* happens at end of round reset. */
1674 return;
1675 }
1676 if (BIT(obj->mods.nuclear, NUCLEAR)) {
1677 targ->damage = 0;
1678 }
1679 else {
1680 targ->damage += (int)(ED_SMART_SHOT_HIT / (obj->mods.mini + 1));
1681 }
1682 break;
1683 case OBJ_MINE:
1684 if (!obj->mass) {
1685 /* happens at end of round reset. */
1686 return;
1687 }
1688 targ->damage -= TARGET_DAMAGE / (obj->mods.mini + 1);
1689 break;
1690 case OBJ_PLAYER:
1691 if (player_cost <= 0 || player_cost > TARGET_DAMAGE / 4)
1692 player_cost = TARGET_DAMAGE / 4;
1693 targ->damage -= player_cost;
1694 break;
1695
1696 default:
1697 /*???*/
1698 break;
1699 }
1700
1701 targ->conn_mask = 0;
1702 targ->last_change = frame_loops;
1703 if (targ->damage > 0)
1704 return;
1705
1706 targ->update_mask = (unsigned) -1;
1707 targ->damage = TARGET_DAMAGE;
1708 targ->dead_time = targetDeadTime;
1709
1710 /*
1711 * Destroy target.
1712 * Turn it into a space to simplify other calculations.
1713 */
1714 x = targ->pos.x;
1715 y = targ->pos.y;
1716 World.block[x][y] = SPACE;
1717
1718 Make_debris(
1719 /* pos.x, pos.y */ (x+0.5f) * BLOCK_SZ, (y+0.5f) * BLOCK_SZ,
1720 /* vel.x, vel.y */ 0.0, 0.0,
1721 /* owner id */ NO_ID,
1722 /* owner team */ targ->team,
1723 /* kind */ OBJ_DEBRIS,
1724 /* mass */ 4.5,
1725 /* status */ GRAVITY,
1726 /* color */ RED,
1727 /* radius */ 6,
1728 /* min,max debris */ 75, 150,
1729 /* min,max dir */ 0, RES-1,
1730 /* min,max speed */ 20, 70,
1731 /* min,max life */ 10, 100
1732 );
1733
1734 if (BIT(World.rules->mode, TEAM_PLAY)) {
1735 for (j = 0; j < NumPlayers; j++) {
1736 if (IS_TANK_IND(j)
1737 || (BIT(Players[j]->status, PAUSE)
1738 && Players[j]->count <= 0)
1739 || (BIT(Players[j]->status, GAME_OVER)
1740 && Players[j]->mychar == 'W'
1741 && Players[j]->score == 0)) {
1742 continue;
1743 }
1744 if (Players[j]->team == targ->team) {
1745 lose_score += Players[j]->score;
1746 lose_team_members++;
1747 if (BIT(Players[j]->status, GAME_OVER) == 0) {
1748 somebody_flag = 1;
1749 }
1750 }
1751 else if (Players[j]->team == Players[killer]->team) {
1752 win_score += Players[j]->score;
1753 win_team_members++;
1754 }
1755 }
1756 }
1757 if (somebody_flag) {
1758 for (j = 0; j < World.NumTargets; j++) {
1759 if (World.targets[j].team == targ->team) {
1760 targets_total++;
1761 if (World.targets[j].dead_time == 0) {
1762 targets_remaining++;
1763 }
1764 }
1765 }
1766 }
1767 if (!somebody_flag) {
1768 return;
1769 }
1770
1771 sound_play_sensors(x, y, DESTROY_TARGET_SOUND);
1772
1773 if (targets_remaining > 0) {
1774 sc = Rate(Players[killer]->score, CANNON_SCORE)/4;
1775 sc = sc * (targets_total - targets_remaining) / (targets_total + 1);
1776 if (sc >= 0.01) {
1777 SCORE(killer, sc,
1778 targ->pos.x, targ->pos.y, "Target: ");
1779 }
1780 /*
1781 * If players can't collide with their own targets, we
1782 * assume there are many used as shields. Don't litter
1783 * the game with the message below.
1784 */
1785 if (targetTeamCollision && targets_total < 10) {
1786 sprintf(msg, "%s blew up one of team %d's targets.",
1787 Players[killer]->name, (int) targ->team);
1788 Set_message(msg);
1789 }
1790 return;
1791 }
1792
1793 sprintf(msg, "%s blew up team %d's %starget.",
1794 Players[killer]->name,
1795 (int) targ->team,
1796 (targets_total > 1) ? "last " : "");
1797 Set_message(msg);
1798
1799 if (targetKillTeam) {
1800 Players[killer]->kills++;
1801 }
1802
1803 sc = Rate(win_score, lose_score);
1804 por = (sc*lose_team_members)/win_team_members;
1805
1806 for (j = 0; j < NumPlayers; j++) {
1807 if (IS_TANK_IND(j)
1808 || (BIT(Players[j]->status, PAUSE)
1809 && Players[j]->count <= 0)
1810 || (BIT(Players[j]->status, GAME_OVER)
1811 && Players[j]->mychar == 'W'
1812 && Players[j]->score == 0)) {
1813 continue;
1814 }
1815 if (Players[j]->team == targ->team) {
1816 if (targetKillTeam
1817 && targets_remaining == 0
1818 && !BIT(Players[j]->status, KILLED|PAUSE|GAME_OVER))
1819 SET_BIT(Players[j]->status, KILLED);
1820 SCORE(j, -sc, targ->pos.x, targ->pos.y,
1821 "Target: ");
1822 }
1823 else if (Players[j]->team == Players[killer]->team &&
1824 (Players[j]->team != TEAM_NOT_SET || j == killer)) {
1825 SCORE(j, por, targ->pos.x, targ->pos.y,
1826 "Target: ");
1827 }
1828 }
1829 }
1830
Object_crash(move_state_t * ms)1831 static void Object_crash(move_state_t *ms)
1832 {
1833 object *obj = ms->mip->obj;
1834
1835 switch (ms->crash) {
1836
1837 case CrashWormHole:
1838 default:
1839 break;
1840
1841 case CrashTreasure:
1842 /*
1843 * Ball type has already been handled.
1844 */
1845 if (obj->type == OBJ_BALL) {
1846 break;
1847 }
1848 obj->life = 0;
1849 break;
1850
1851 case CrashTarget:
1852 obj->life = 0;
1853 Object_hits_target(ms, -1);
1854 break;
1855
1856 case CrashWall:
1857 obj->life = 0;
1858 #if 0
1859 /* KK: - Added sparks to wallcrashes for objects != OBJ_SPARK|OBJ_DEBRIS.
1860 ** I'm not sure of the amount of sparks or the direction.
1861 */
1862 if (!BIT(obj->type, OBJ_SPARK | OBJ_DEBRIS)) {
1863 Make_debris(CLICK_TO_FLOAT(ms->pos.x),
1864 CLICK_TO_FLOAT(ms->pos.y),
1865 0, 0,
1866 obj->owner,
1867 obj->team,
1868 OBJ_SPARK,
1869 (obj->mass * VECTOR_LENGTH(obj->vel)) / 3,
1870 GRAVITY,
1871 RED,
1872 1,
1873 5, 10,
1874 MOD2(ms->dir - RES/4, RES), MOD2(ms->dir + RES/4, RES),
1875 15, 25,
1876 5, 15);
1877 }
1878 #endif
1879 break;
1880
1881 case CrashUniverse:
1882 obj->life = 0;
1883 break;
1884
1885 case CrashCannon:
1886 obj->life = 0;
1887 if (BIT(obj->type, OBJ_ITEM)) {
1888 Cannon_add_item(ms->cannon, obj->info, obj->count);
1889 } else {
1890 if (!BIT(World.cannon[ms->cannon].used, HAS_EMERGENCY_SHIELD)) {
1891 if (World.cannon[ms->cannon].item[ITEM_ARMOR] > 0)
1892 World.cannon[ms->cannon].item[ITEM_ARMOR]--;
1893 else
1894 Cannon_dies(ms);
1895 }
1896 }
1897 break;
1898
1899 case CrashUnknown:
1900 obj->life = 0;
1901 break;
1902 }
1903 }
1904
Move_object(object * obj)1905 void Move_object(object *obj)
1906 {
1907 int nothing_done = 0;
1908 int dist;
1909 move_info_t mi;
1910 move_state_t ms;
1911 bool pos_update = false;
1912
1913 Object_position_remember(obj);
1914
1915 dist = walldist[obj->pos.bx][obj->pos.by];
1916 if (dist > 2) {
1917 int max = ((dist - 2) * BLOCK_SZ) >> 1;
1918 if (sqr(max) >= sqr(obj->vel.x) + sqr(obj->vel.y)) {
1919 DFLOAT x = obj->pos.cx + FLOAT_TO_CLICK(obj->vel.x);
1920 DFLOAT y = obj->pos.cy + FLOAT_TO_CLICK(obj->vel.y);
1921 x = WRAP_XCLICK(x);
1922 y = WRAP_YCLICK(y);
1923 Object_position_set_clicks(obj, (int)(x), (int)(y));
1924 Cell_add_object(obj);
1925 return;
1926 }
1927 }
1928
1929 mi.pl = NULL;
1930 mi.obj = obj;
1931 mi.edge_wrap = BIT(World.rules->mode, WRAP_PLAY);
1932 mi.edge_bounce = edgeBounce;
1933 mi.wall_bounce = BIT(mp.obj_bounce_mask, obj->type);
1934 mi.cannon_crashes = BIT(mp.obj_cannon_mask, obj->type);
1935 mi.target_crashes = BIT(mp.obj_target_mask, obj->type);
1936 mi.treasure_crashes = BIT(mp.obj_treasure_mask, obj->type);
1937 mi.wormhole_warps = true;
1938 if (BIT(obj->type, OBJ_BALL) && obj->id != NO_ID) {
1939 mi.phased = BIT(Players[GetInd[obj->id]]->used, HAS_PHASING_DEVICE);
1940 } else {
1941 mi.phased = 0;
1942 }
1943
1944 ms.pos.x = obj->pos.cx;
1945 ms.pos.y = obj->pos.cy;
1946 ms.vel = obj->vel;
1947 ms.todo.x = FLOAT_TO_CLICK(ms.vel.x);
1948 ms.todo.y = FLOAT_TO_CLICK(ms.vel.y);
1949 ms.dir = obj->missile_dir;
1950 ms.mip = &mi;
1951
1952 for (;;) {
1953 Move_segment(&ms);
1954 if (!(ms.done.x | ms.done.y)) {
1955 pos_update |= (ms.crash | ms.bounce);
1956 if (ms.crash) {
1957 break;
1958 }
1959 if (ms.bounce && ms.bounce != BounceEdge) {
1960 if (obj->type != OBJ_BALL)
1961 obj->life = (long)(obj->life * objectWallBounceLifeFactor);
1962 if (obj->life <= 0) {
1963 break;
1964 }
1965 /*
1966 * Any bouncing sparks are no longer owner immune to give
1967 * "reactive" thrust. This is exactly like ground effect
1968 * in the real world. Very useful for stopping against walls.
1969 *
1970 * If the FROMBOUNCE bit is set the spark was caused by
1971 * the player bouncing of a wall and thus although the spark
1972 * should bounce, it is not reactive thrust otherwise wall
1973 * bouncing would cause acceleration of the player.
1974 */
1975 if (!BIT(obj->status, FROMBOUNCE) && BIT(obj->type, OBJ_SPARK))
1976 CLR_BIT(obj->status, OWNERIMMUNE);
1977 if (sqr(ms.vel.x) + sqr(ms.vel.y) > sqr(maxObjectWallBounceSpeed)) {
1978 obj->life = 0;
1979 break;
1980 }
1981 ms.vel.x *= objectWallBrakeFactor;
1982 ms.vel.y *= objectWallBrakeFactor;
1983 ms.todo.x = (int)(ms.todo.x * objectWallBrakeFactor);
1984 ms.todo.y = (int)(ms.todo.y * objectWallBrakeFactor);
1985 }
1986 if (++nothing_done >= 5) {
1987 ms.crash = CrashUnknown;
1988 break;
1989 }
1990 } else {
1991 ms.pos.x += ms.done.x;
1992 ms.pos.y += ms.done.y;
1993 nothing_done = 0;
1994 }
1995 if (!(ms.todo.x | ms.todo.y)) {
1996 break;
1997 }
1998 }
1999 if (mi.edge_wrap) {
2000 if (ms.pos.x < 0) {
2001 ms.pos.x += mp.click_width;
2002 }
2003 if (ms.pos.x >= mp.click_width) {
2004 ms.pos.x -= mp.click_width;
2005 }
2006 if (ms.pos.y < 0) {
2007 ms.pos.y += mp.click_height;
2008 }
2009 if (ms.pos.y >= mp.click_height) {
2010 ms.pos.y -= mp.click_height;
2011 }
2012 }
2013 Object_position_set_clicks(obj, ms.pos.x, ms.pos.y);
2014 obj->vel = ms.vel;
2015 obj->missile_dir = ms.dir;
2016 if (ms.crash) {
2017 Object_crash(&ms);
2018 }
2019 if (pos_update) {
2020 Object_position_remember(obj);
2021 }
2022 Cell_add_object(obj);
2023 }
2024
Player_crash(move_state_t * ms,int pt,bool turning)2025 static void Player_crash(move_state_t *ms, int pt, bool turning)
2026 {
2027 player *pl = ms->mip->pl;
2028 int ind = GetInd[pl->id];
2029 const char *howfmt = NULL;
2030 const char *hudmsg = NULL;
2031
2032 msg[0] = '\0';
2033
2034 switch (ms->crash) {
2035
2036 default:
2037 case NotACrash:
2038 errno = 0;
2039 error("Player_crash not a crash %d", ms->crash);
2040 break;
2041
2042 case CrashWormHole:
2043 SET_BIT(pl->status, WARPING);
2044 pl->wormHoleHit = ms->wormhole;
2045 break;
2046
2047 case CrashWall:
2048 howfmt = "%s crashed%s against a wall";
2049 hudmsg = "[Wall]";
2050 sound_play_sensors(pl->pos.x, pl->pos.y, PLAYER_HIT_WALL_SOUND);
2051 break;
2052
2053 case CrashWallSpeed:
2054 howfmt = "%s smashed%s against a wall";
2055 hudmsg = "[Wall]";
2056 sound_play_sensors(pl->pos.x, pl->pos.y, PLAYER_HIT_WALL_SOUND);
2057 break;
2058
2059 case CrashWallNoFuel:
2060 howfmt = "%s smacked%s against a wall";
2061 hudmsg = "[Wall]";
2062 sound_play_sensors(pl->pos.x, pl->pos.y, PLAYER_HIT_WALL_SOUND);
2063 break;
2064
2065 case CrashWallAngle:
2066 howfmt = "%s was trashed%s against a wall";
2067 hudmsg = "[Wall]";
2068 sound_play_sensors(pl->pos.x, pl->pos.y, PLAYER_HIT_WALL_SOUND);
2069 break;
2070
2071 case CrashTarget:
2072 howfmt = "%s smashed%s against a target";
2073 hudmsg = "[Target]";
2074 sound_play_sensors(pl->pos.x, pl->pos.y, PLAYER_HIT_WALL_SOUND);
2075 Object_hits_target(ms, -1);
2076 break;
2077
2078 case CrashTreasure:
2079 howfmt = "%s smashed%s against a treasure";
2080 hudmsg = "[Treasure]";
2081 sound_play_sensors(pl->pos.x, pl->pos.y, PLAYER_HIT_WALL_SOUND);
2082 break;
2083
2084 case CrashCannon:
2085 if (BIT(pl->used, HAS_SHIELD|HAS_EMERGENCY_SHIELD)
2086 != (HAS_SHIELD|HAS_EMERGENCY_SHIELD)) {
2087 howfmt = "%s smashed%s against a cannon";
2088 hudmsg = "[Cannon]";
2089 sound_play_sensors(pl->pos.x, pl->pos.y, PLAYER_HIT_CANNON_SOUND);
2090 }
2091 if (!BIT(World.cannon[ms->cannon].used, HAS_EMERGENCY_SHIELD)) {
2092 Cannon_dies(ms);
2093 }
2094 break;
2095
2096 case CrashUniverse:
2097 howfmt = "%s left the known universe%s";
2098 hudmsg = "[Universe]";
2099 sound_play_sensors(pl->pos.x, pl->pos.y, PLAYER_HIT_WALL_SOUND);
2100 break;
2101
2102 case CrashUnknown:
2103 howfmt = "%s slammed%s into a programming error";
2104 hudmsg = "[Bug]";
2105 sound_play_sensors(pl->pos.x, pl->pos.y, PLAYER_HIT_WALL_SOUND);
2106 break;
2107 }
2108
2109 if (howfmt && hudmsg) {
2110 player *pushers[MAX_RECORDED_SHOVES];
2111 int cnt[MAX_RECORDED_SHOVES];
2112 int num_pushers = 0;
2113 int total_pusher_count = 0;
2114 DFLOAT total_pusher_score = 0;
2115 int i, j;
2116 DFLOAT sc;
2117
2118 SET_BIT(pl->status, KILLED);
2119 sprintf(msg, howfmt, pl->name, (!pt) ? " head first" : "");
2120
2121 /* get a list of who pushed me */
2122 for (i = 0; i < MAX_RECORDED_SHOVES; i++) {
2123 shove_t *shove = &pl->shove_record[i];
2124 if (shove->pusher_id == NO_ID) {
2125 continue;
2126 }
2127 if (shove->time < frame_loops - 20) {
2128 continue;
2129 }
2130 for (j = 0; j < num_pushers; j++) {
2131 if (shove->pusher_id == pushers[j]->id) {
2132 cnt[j]++;
2133 break;
2134 }
2135 }
2136 if (j == num_pushers) {
2137 pushers[num_pushers++] = Players[GetInd[shove->pusher_id]];
2138 cnt[j] = 1;
2139 }
2140 total_pusher_count++;
2141 total_pusher_score += pushers[j]->score;
2142 }
2143 if (num_pushers == 0) {
2144 sc = Rate(WALL_SCORE, pl->score);
2145 SCORE(ind, -sc,
2146 OBJ_X_IN_BLOCKS(pl),
2147 OBJ_Y_IN_BLOCKS(pl),
2148 hudmsg);
2149 strcat(msg, ".");
2150 Set_message(msg);
2151 }
2152 else {
2153 int msg_len = strlen(msg);
2154 char *msg_ptr = &msg[msg_len];
2155 int average_pusher_score = total_pusher_score
2156 / total_pusher_count;
2157
2158 for (i = 0; i < num_pushers; i++) {
2159 player *pusher = pushers[i];
2160 const char *sep = (!i) ? " with help from "
2161 : (i < num_pushers - 1) ? ", "
2162 : " and ";
2163 int sep_len = strlen(sep);
2164 int name_len = strlen(pusher->name);
2165
2166 if (msg_len + sep_len + name_len + 2 < sizeof msg) {
2167 strcpy(msg_ptr, sep);
2168 msg_len += sep_len;
2169 msg_ptr += sep_len;
2170 strcpy(msg_ptr, pusher->name);
2171 msg_len += name_len;
2172 msg_ptr += name_len;
2173 }
2174 sc = cnt[i] * Rate(pusher->score, pl->score)
2175 * shoveKillScoreMult / total_pusher_count;
2176 SCORE(GetInd[pusher->id], sc,
2177 OBJ_X_IN_BLOCKS(pl),
2178 OBJ_Y_IN_BLOCKS(pl),
2179 pl->name);
2180 if (i >= num_pushers - 1) {
2181 pusher->kills++;
2182 }
2183
2184 }
2185 sc = Rate(average_pusher_score, pl->score)
2186 * shoveKillScoreMult;
2187 SCORE(ind, -sc,
2188 OBJ_X_IN_BLOCKS(pl),
2189 OBJ_Y_IN_BLOCKS(pl),
2190 "[Shove]");
2191
2192 strcpy(msg_ptr, ".");
2193 Set_message(msg);
2194
2195 /* Robots will declare war on anyone who shoves them. */
2196 i = (int)(rfrac() * num_pushers);
2197 Robot_war(ind, GetInd[pushers[i]->id]);
2198 }
2199 }
2200
2201 if (BIT(pl->status, KILLED)
2202 && pl->score < 0
2203 && IS_ROBOT_PTR(pl)) {
2204 pl->home_base = 0;
2205 Pick_startpos(ind);
2206 }
2207 }
2208
Move_player(int ind)2209 void Move_player(int ind)
2210 {
2211 player *pl = Players[ind];
2212 int nothing_done = 0;
2213 int i;
2214 int dist;
2215 move_info_t mi;
2216 move_state_t ms[RES];
2217 int worst = 0;
2218 int crash;
2219 int bounce;
2220 int moves_made = 0;
2221 int cor_res;
2222 clpos pos;
2223 clvec todo;
2224 clvec done;
2225 vector vel;
2226 vector r[RES];
2227 ivec sign; /* sign (-1 or 1) of direction */
2228 ipos block; /* block index */
2229 bool pos_update = false;
2230 DFLOAT fric;
2231 DFLOAT oldvx, oldvy;
2232
2233
2234 if (BIT(pl->status, PLAYING|PAUSE|GAME_OVER|KILLED) != PLAYING) {
2235 if (!BIT(pl->status, KILLED|PAUSE)) {
2236 pos.x = pl->pos.cx + FLOAT_TO_CLICK(pl->vel.x);
2237 pos.y = pl->pos.cy + FLOAT_TO_CLICK(pl->vel.y);
2238 pos.x = WRAP_XCLICK(pos.x);
2239 pos.y = WRAP_YCLICK(pos.y);
2240 if (pos.x != pl->pos.cx || pos.y != pl->pos.cy) {
2241 Player_position_remember(pl);
2242 Player_position_set_clicks(pl, pos.x, pos.y);
2243 }
2244 }
2245 pl->velocity = VECTOR_LENGTH(pl->vel);
2246 return;
2247 }
2248
2249 /* Figure out which friction to use. */
2250 if (BIT(pl->used, HAS_PHASING_DEVICE)) {
2251 fric = friction;
2252 }
2253 else {
2254 switch (World.block[pl->pos.bx][pl->pos.by]) {
2255 case FRICTION:
2256 fric = blockFriction;
2257 break;
2258 default:
2259 fric = friction;
2260 break;
2261 }
2262 }
2263
2264 cor_res = MOD2(coriolis * RES / 360, RES);
2265 oldvx = pl->vel.x;
2266 oldvy = pl->vel.y;
2267 pl->vel.x = (1.0f - fric) * (oldvx * tcos(cor_res) + oldvy * tsin(cor_res));
2268 pl->vel.y = (1.0f - fric) * (oldvy * tcos(cor_res) - oldvx * tsin(cor_res));
2269
2270 Player_position_remember(pl);
2271
2272 dist = walldist[pl->pos.bx][pl->pos.by];
2273 if (dist > 3) {
2274 int max = ((dist - 3) * BLOCK_SZ) >> 1;
2275 if (max >= pl->velocity) {
2276 pos.x = pl->pos.cx + FLOAT_TO_CLICK(pl->vel.x);
2277 pos.y = pl->pos.cy + FLOAT_TO_CLICK(pl->vel.y);
2278 pos.x = WRAP_XCLICK(pos.x);
2279 pos.y = WRAP_YCLICK(pos.y);
2280 Player_position_set_clicks(pl, pos.x, pos.y);
2281 pl->velocity = VECTOR_LENGTH(pl->vel);
2282 return;
2283 }
2284 }
2285
2286 mi.pl = pl;
2287 mi.obj = (object *) pl;
2288 mi.edge_wrap = BIT(World.rules->mode, WRAP_PLAY);
2289 mi.edge_bounce = edgeBounce;
2290 mi.wall_bounce = true;
2291 mi.cannon_crashes = true;
2292 mi.treasure_crashes = true;
2293 mi.target_crashes = true;
2294 mi.wormhole_warps = true;
2295 mi.phased = BIT(pl->used, HAS_PHASING_DEVICE);
2296
2297 vel = pl->vel;
2298 todo.x = FLOAT_TO_CLICK(vel.x);
2299 todo.y = FLOAT_TO_CLICK(vel.y);
2300 for (i = 0; i < pl->ship->num_points; i++) {
2301 DFLOAT x = pl->ship->pts[i][pl->dir].x;
2302 DFLOAT y = pl->ship->pts[i][pl->dir].y;
2303 ms[i].pos.x = pl->pos.cx + FLOAT_TO_CLICK(x);
2304 ms[i].pos.y = pl->pos.cy + FLOAT_TO_CLICK(y);
2305 ms[i].vel = vel;
2306 ms[i].todo = todo;
2307 ms[i].dir = pl->dir;
2308 ms[i].mip = &mi;
2309 ms[i].target = -1;
2310 }
2311
2312 for (;; moves_made++) {
2313
2314 pos.x = ms[0].pos.x - FLOAT_TO_CLICK(pl->ship->pts[0][ms[0].dir].x);
2315 pos.y = ms[0].pos.y - FLOAT_TO_CLICK(pl->ship->pts[0][ms[0].dir].y);
2316 pos.x = WRAP_XCLICK(pos.x);
2317 pos.y = WRAP_YCLICK(pos.y);
2318 block.x = pos.x / BLOCK_CLICKS;
2319 block.y = pos.y / BLOCK_CLICKS;
2320
2321 if (walldist[block.x][block.y] > 3) {
2322 int maxcl = ((walldist[block.x][block.y] - 3) * BLOCK_CLICKS) >> 1;
2323 todo = ms[0].todo;
2324 sign.x = (todo.x < 0) ? -1 : 1;
2325 sign.y = (todo.y < 0) ? -1 : 1;
2326 if (maxcl >= sign.x * todo.x && maxcl >= sign.y * todo.y) {
2327 /* entire movement is possible. */
2328 done.x = todo.x;
2329 done.y = todo.y;
2330 }
2331 else if (sign.x * todo.x > sign.y * todo.y) {
2332 /* horizontal movement. */
2333 done.x = sign.x * maxcl;
2334 done.y = todo.y * maxcl / (sign.x * todo.x);
2335 }
2336 else {
2337 /* vertical movement. */
2338 done.x = todo.x * maxcl / (sign.y * todo.y);
2339 done.y = sign.y * maxcl;
2340 }
2341 todo.x -= done.x;
2342 todo.y -= done.y;
2343 for (i = 0; i < pl->ship->num_points; i++) {
2344 ms[i].pos.x += done.x;
2345 ms[i].pos.y += done.y;
2346 ms[i].todo = todo;
2347 ms[i].crash = NotACrash;
2348 ms[i].bounce = NotABounce;
2349 if (mi.edge_wrap) {
2350 if (ms[i].pos.x < 0) {
2351 ms[i].pos.x += mp.click_width;
2352 }
2353 else if (ms[i].pos.x >= mp.click_width) {
2354 ms[i].pos.x -= mp.click_width;
2355 }
2356 if (ms[i].pos.y < 0) {
2357 ms[i].pos.y += mp.click_height;
2358 }
2359 else if (ms[i].pos.y >= mp.click_height) {
2360 ms[i].pos.y -= mp.click_height;
2361 }
2362 }
2363 }
2364 nothing_done = 0;
2365 if (!(todo.x | todo.y)) {
2366 break;
2367 }
2368 else {
2369 continue;
2370 }
2371 }
2372
2373 bounce = -1;
2374 crash = -1;
2375 for (i = 0; i < pl->ship->num_points; i++) {
2376 Move_segment(&ms[i]);
2377 pos_update |= (ms[i].crash | ms[i].bounce);
2378 if (ms[i].crash) {
2379 crash = i;
2380 break;
2381 }
2382 if (ms[i].bounce) {
2383 if (bounce == -1) {
2384 bounce = i;
2385 }
2386 else if (ms[bounce].bounce != BounceEdge
2387 && ms[i].bounce == BounceEdge) {
2388 bounce = i;
2389 }
2390 else if ((ms[bounce].bounce == BounceEdge)
2391 == (ms[i].bounce == BounceEdge)) {
2392 if ((int)(rfrac() * (pl->ship->num_points - bounce)) == i) {
2393 bounce = i;
2394 }
2395 }
2396 worst = bounce;
2397 }
2398 }
2399 if (crash != -1) {
2400 worst = crash;
2401 break;
2402 }
2403 else if (bounce != -1) {
2404 worst = bounce;
2405 pl->last_wall_touch = frame_loops;
2406 if (ms[worst].bounce != BounceEdge) {
2407 DFLOAT speed = VECTOR_LENGTH(ms[worst].vel);
2408 int v = (int) speed >> 2;
2409 int m = (int) (pl->mass - pl->emptymass * 0.75f);
2410 DFLOAT b = 1 - 0.5f * playerWallBrakeFactor;
2411 long cost = (long) (b * m * v);
2412 int delta_dir,
2413 abs_delta_dir,
2414 wall_dir;
2415 DFLOAT max_speed = BIT(pl->used, HAS_SHIELD)
2416 ? maxShieldedWallBounceSpeed
2417 : maxUnshieldedWallBounceSpeed;
2418 int max_angle = BIT(pl->used, HAS_SHIELD)
2419 ? mp.max_shielded_angle
2420 : mp.max_unshielded_angle;
2421
2422 if (BIT(pl->used, (HAS_SHIELD|HAS_EMERGENCY_SHIELD))
2423 == (HAS_SHIELD|HAS_EMERGENCY_SHIELD)) {
2424 if (max_speed < 100) {
2425 max_speed = 100;
2426 }
2427 max_angle = RES;
2428 }
2429
2430 ms[worst].vel.x *= playerWallBrakeFactor;
2431 ms[worst].vel.y *= playerWallBrakeFactor;
2432 ms[worst].todo.x = (int)(ms[worst].todo.x * playerWallBrakeFactor);
2433 ms[worst].todo.y = (int)(ms[worst].todo.y * playerWallBrakeFactor);
2434
2435 /* only use armor if neccessary */
2436 if (speed > max_speed
2437 && max_speed < maxShieldedWallBounceSpeed
2438 && !BIT(pl->used, HAS_SHIELD)
2439 && BIT(pl->have, HAS_ARMOR)) {
2440 max_speed = maxShieldedWallBounceSpeed;
2441 max_angle = mp.max_shielded_angle;
2442 Player_hit_armor(ind);
2443 }
2444
2445 if (speed > max_speed) {
2446 crash = worst;
2447 ms[worst].crash = (ms[worst].target >= 0 ? CrashTarget :
2448 CrashWallSpeed);
2449 break;
2450 }
2451
2452 switch (ms[worst].bounce) {
2453 case BounceHorLo: wall_dir = 4*RES/8; break;
2454 case BounceHorHi: wall_dir = 0*RES/8; break;
2455 case BounceVerLo: wall_dir = 6*RES/8; break;
2456 default:
2457 case BounceVerHi: wall_dir = 2*RES/8; break;
2458 case BounceLeftDown: wall_dir = 1*RES/8; break;
2459 case BounceLeftUp: wall_dir = 7*RES/8; break;
2460 case BounceRightDown: wall_dir = 3*RES/8; break;
2461 case BounceRightUp: wall_dir = 5*RES/8; break;
2462 }
2463 if (pl->dir >= wall_dir) {
2464 delta_dir = (pl->dir - wall_dir <= RES/2)
2465 ? -(pl->dir - wall_dir)
2466 : (wall_dir + RES - pl->dir);
2467 } else {
2468 delta_dir = (wall_dir - pl->dir <= RES/2)
2469 ? (wall_dir - pl->dir)
2470 : -(pl->dir + RES - wall_dir);
2471 }
2472 abs_delta_dir = ABS(delta_dir);
2473 /* only use armor if neccessary */
2474 if (abs_delta_dir > max_angle
2475 && max_angle < mp.max_shielded_angle
2476 && !BIT(pl->used, HAS_SHIELD)
2477 && BIT(pl->have, HAS_ARMOR)) {
2478 max_speed = maxShieldedWallBounceSpeed;
2479 max_angle = mp.max_shielded_angle;
2480 Player_hit_armor(ind);
2481 }
2482 if (abs_delta_dir > max_angle) {
2483 crash = worst;
2484 ms[worst].crash = (ms[worst].target >= 0 ? CrashTarget :
2485 CrashWallAngle);
2486 break;
2487 }
2488 if (abs_delta_dir <= RES/16) {
2489 pl->float_dir += (1.0f - playerWallBrakeFactor) * delta_dir;
2490 if (pl->float_dir >= RES) {
2491 pl->float_dir -= RES;
2492 }
2493 else if (pl->float_dir < 0) {
2494 pl->float_dir += RES;
2495 }
2496 }
2497
2498 /*
2499 * Small explosion and fuel loss if survived a hit on a wall.
2500 * This doesn't affect the player as the explosion is sparks
2501 * which don't collide with player.
2502 * Clumsy touches (head first) with wall are more costly.
2503 */
2504 cost = (cost * (RES/2 + abs_delta_dir)) / RES;
2505 if (BIT(pl->used, (HAS_SHIELD|HAS_EMERGENCY_SHIELD))
2506 != (HAS_SHIELD|HAS_EMERGENCY_SHIELD)) {
2507 Add_fuel(&pl->fuel, (long)(-((cost << FUEL_SCALE_BITS)
2508 * wallBounceFuelDrainMult)));
2509 Item_damage(ind, wallBounceDestroyItemProb);
2510 }
2511 if (!pl->fuel.sum && wallBounceFuelDrainMult != 0) {
2512 crash = worst;
2513 ms[worst].crash = (ms[worst].target >= 0 ? CrashTarget :
2514 CrashWallNoFuel);
2515 break;
2516 }
2517 if (cost) {
2518 int intensity = (int)(cost * wallBounceExplosionMult);
2519 Make_debris(
2520 /* pos.x, pos.y */ pl->pos.x, pl->pos.y,
2521 /* vel.x, vel.y */ pl->vel.x, pl->vel.y,
2522 /* owner id */ pl->id,
2523 /* owner team */ pl->team,
2524 /* kind */ OBJ_SPARK,
2525 /* mass */ 3.5,
2526 /* status */ GRAVITY | OWNERIMMUNE | FROMBOUNCE,
2527 /* color */ RED,
2528 /* radius */ 1,
2529 /* min,max debris */ intensity>>1, intensity,
2530 /* min,max dir */ wall_dir - (RES/4), wall_dir + (RES/4),
2531 /* min,max speed */ 20, 20 + (intensity>>2),
2532 /* min,max life */ 10, 10 + (intensity>>1)
2533 );
2534 sound_play_sensors(pl->pos.x, pl->pos.y,
2535 PLAYER_BOUNCED_SOUND);
2536 if (ms[worst].target >= 0) {
2537 cost <<= FUEL_SCALE_BITS;
2538 cost = (long)(cost * (wallBounceFuelDrainMult / 4.0));
2539 Object_hits_target(&ms[worst], cost);
2540 }
2541 }
2542 }
2543 }
2544 else {
2545 for (i = 0; i < pl->ship->num_points; i++) {
2546 r[i].x = (vel.x) ? (DFLOAT) ms[i].todo.x / vel.x : 0;
2547 r[i].y = (vel.y) ? (DFLOAT) ms[i].todo.y / vel.y : 0;
2548 r[i].x = ABS(r[i].x);
2549 r[i].y = ABS(r[i].y);
2550 }
2551 worst = 0;
2552 for (i = 1; i < pl->ship->num_points; i++) {
2553 if (r[i].x > r[worst].x || r[i].y > r[worst].y) {
2554 worst = i;
2555 }
2556 }
2557 }
2558
2559 if (!(ms[worst].done.x | ms[worst].done.y)) {
2560 if (++nothing_done >= 5) {
2561 ms[worst].crash = CrashUnknown;
2562 break;
2563 }
2564 } else {
2565 nothing_done = 0;
2566 ms[worst].pos.x += ms[worst].done.x;
2567 ms[worst].pos.y += ms[worst].done.y;
2568 }
2569 if (!(ms[worst].todo.x | ms[worst].todo.y)) {
2570 break;
2571 }
2572
2573 vel = ms[worst].vel;
2574 for (i = 0; i < pl->ship->num_points; i++) {
2575 if (i != worst) {
2576 ms[i].pos.x += ms[worst].done.x;
2577 ms[i].pos.y += ms[worst].done.y;
2578 ms[i].vel = vel;
2579 ms[i].todo = ms[worst].todo;
2580 ms[i].dir = ms[worst].dir;
2581 }
2582 }
2583 }
2584
2585 pos.x = ms[worst].pos.x - FLOAT_TO_CLICK(pl->ship->pts[worst][pl->dir].x);
2586 pos.y = ms[worst].pos.y - FLOAT_TO_CLICK(pl->ship->pts[worst][pl->dir].y);
2587 pos.x = WRAP_XCLICK(pos.x);
2588 pos.y = WRAP_YCLICK(pos.y);
2589 Player_position_set_clicks(pl, pos.x, pos.y);
2590 pl->vel = ms[worst].vel;
2591 pl->velocity = VECTOR_LENGTH(pl->vel);
2592
2593 if (ms[worst].crash) {
2594 Player_crash(&ms[worst], worst, false);
2595 }
2596 if (pos_update) {
2597 Player_position_remember(pl);
2598 }
2599 }
2600
Turn_player(int ind)2601 void Turn_player(int ind)
2602 {
2603 player *pl = Players[ind];
2604 int i;
2605 move_info_t mi;
2606 move_state_t ms[RES];
2607 int dir;
2608 int new_dir = MOD2((int)(pl->float_dir + 0.5f), RES);
2609 int sign;
2610 int crash = -1;
2611 int nothing_done = 0;
2612 int turns_done = 0;
2613 int blocked = 0;
2614 clpos pos;
2615 vector salt;
2616
2617 if (new_dir == pl->dir) {
2618 return;
2619 }
2620 if (BIT(pl->status, PLAYING|PAUSE|GAME_OVER|KILLED) != PLAYING) {
2621 pl->dir = new_dir;
2622 return;
2623 }
2624
2625 if (walldist[pl->pos.bx][pl->pos.by] > 2) {
2626 pl->dir = new_dir;
2627 return;
2628 }
2629
2630 mi.pl = pl;
2631 mi.obj = (object *) pl;
2632 mi.edge_wrap = BIT(World.rules->mode, WRAP_PLAY);
2633 mi.edge_bounce = edgeBounce;
2634 mi.wall_bounce = true;
2635 mi.cannon_crashes = true;
2636 mi.treasure_crashes = true;
2637 mi.target_crashes = true;
2638 mi.wormhole_warps = false;
2639 mi.phased = BIT(pl->used, HAS_PHASING_DEVICE);
2640
2641 if (new_dir > pl->dir) {
2642 sign = (new_dir - pl->dir <= RES + pl->dir - new_dir) ? 1 : -1;
2643 }
2644 else {
2645 sign = (pl->dir - new_dir <= RES + new_dir - pl->dir) ? -1 : 1;
2646 }
2647
2648 #if 0
2649 salt.x = (pl->vel.x > 0) ? 0.1f : (pl->vel.x < 0) ? -0.1f : 0;
2650 salt.y = (pl->vel.y > 0) ? 0.1f : (pl->vel.y < 0) ? -0.1f : 0;
2651 #else
2652 salt.x = (pl->vel.x > 0) ? 1e-6f : (pl->vel.x < 0) ? -1e-6f : 0;
2653 salt.y = (pl->vel.y > 0) ? 1e-6f : (pl->vel.y < 0) ? -1e-6f : 0;
2654 #endif
2655
2656 pos.x = pl->pos.cx;
2657 pos.y = pl->pos.cy;
2658 for (; pl->dir != new_dir; turns_done++) {
2659 dir = MOD2(pl->dir + sign, RES);
2660 if (!mi.edge_wrap) {
2661 if (pos.x <= 22 * CLICK) {
2662 for (i = 0; i < pl->ship->num_points; i++) {
2663 if (pos.x + FLOAT_TO_CLICK(pl->ship->pts[i][dir].x) < 0) {
2664 pos.x = -FLOAT_TO_CLICK(pl->ship->pts[i][dir].x);
2665 }
2666 }
2667 }
2668 if (pos.x >= mp.click_width - 22 * CLICK) {
2669 for (i = 0; i < pl->ship->num_points; i++) {
2670 if (pos.x + FLOAT_TO_CLICK(pl->ship->pts[i][dir].x)
2671 >= mp.click_width) {
2672 pos.x = mp.click_width - 1
2673 - FLOAT_TO_CLICK(pl->ship->pts[i][dir].x);
2674 }
2675 }
2676 }
2677 if (pos.y <= 22 * CLICK) {
2678 for (i = 0; i < pl->ship->num_points; i++) {
2679 if (pos.y + FLOAT_TO_CLICK(pl->ship->pts[i][dir].y) < 0) {
2680 pos.y = -FLOAT_TO_CLICK(pl->ship->pts[i][dir].y);
2681 }
2682 }
2683 }
2684 if (pos.y >= mp.click_height - 22 * CLICK) {
2685 for (i = 0; i < pl->ship->num_points; i++) {
2686 if (pos.y + FLOAT_TO_CLICK(pl->ship->pts[i][dir].y)
2687 >= mp.click_height) {
2688 pos.y = mp.click_height - 1
2689 - FLOAT_TO_CLICK(pl->ship->pts[i][dir].y);
2690 }
2691 }
2692 }
2693 if (pos.x != pl->pos.cx || pos.y != pl->pos.cy) {
2694 Player_position_set_clicks(pl, pos.x, pos.y);
2695 }
2696 }
2697
2698 for (i = 0; i < pl->ship->num_points; i++) {
2699 ms[i].mip = &mi;
2700 ms[i].pos.x = pos.x + FLOAT_TO_CLICK(pl->ship->pts[i][pl->dir].x);
2701 ms[i].pos.y = pos.y + FLOAT_TO_CLICK(pl->ship->pts[i][pl->dir].y);
2702 ms[i].todo.x = pos.x + FLOAT_TO_CLICK(pl->ship->pts[i][dir].x) - ms[i].pos.x;
2703 ms[i].todo.y = pos.y + FLOAT_TO_CLICK(pl->ship->pts[i][dir].y) - ms[i].pos.y;
2704 ms[i].vel.x = ms[i].todo.x + salt.x;
2705 ms[i].vel.y = ms[i].todo.y + salt.y;
2706 ms[i].target = -1;
2707
2708 do {
2709 Move_segment(&ms[i]);
2710 if (ms[i].crash | ms[i].bounce) {
2711 if (ms[i].crash) {
2712 if (ms[i].crash != CrashUniverse) {
2713 crash = i;
2714 }
2715 blocked = 1;
2716 break;
2717 }
2718 if (ms[i].bounce != BounceEdge) {
2719 blocked = 1;
2720 break;
2721 }
2722 if (++nothing_done >= 5) {
2723 ms[i].crash = CrashUnknown;
2724 crash = i;
2725 blocked = 1;
2726 break;
2727 }
2728 }
2729 else if (ms[i].done.x | ms[i].done.y) {
2730 ms[i].pos.x += ms[i].done.x;
2731 ms[i].pos.y += ms[i].done.y;
2732 nothing_done = 0;
2733 }
2734 } while (ms[i].todo.x | ms[i].todo.y);
2735 if (blocked) {
2736 break;
2737 }
2738 }
2739 if (blocked) {
2740 break;
2741 }
2742 pl->dir = dir;
2743 }
2744
2745 if (blocked) {
2746 pl->float_dir = (DFLOAT) pl->dir;
2747 pl->last_wall_touch = frame_loops;
2748 }
2749
2750 if (crash != -1) {
2751 Player_crash(&ms[crash], crash, true);
2752 }
2753
2754 }
2755
2756