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