1 /* $Id: shot.c,v 5.29 2001/12/11 12:45:13 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 "cannon.h"
45 #include "objpos.h"
46 #include "netserver.h"
47 #include "error.h"
48 #include "commonproto.h"
49
50 char shot_version[] = VERSION;
51
52
53 #define MISSILE_POWER_SPEED_FACT 0.25
54 #define MISSILE_POWER_TURNSPEED_FACT 0.75
55 #define MINI_TORPEDO_SPREAD_TIME 6
56 #define MINI_TORPEDO_SPREAD_SPEED 20
57 #define MINI_TORPEDO_SPREAD_ANGLE 90
58 #define MINI_MINE_SPREAD_TIME 18
59 #define MINI_MINE_SPREAD_SPEED 8
60 #define MINI_MISSILE_SPREAD_ANGLE 45
61
62 #define CONFUSED_UPDATE_GRANULARITY 10
63 #define CONFUSED_TIME 3
64
65
66 /***********************
67 * Functions for shots.
68 */
69
70
Place_mine(int ind)71 void Place_mine(int ind)
72 {
73 player *pl = Players[ind];
74
75 if (pl->item[ITEM_MINE] <= 0
76 || (BIT(pl->used, HAS_SHIELD|HAS_PHASING_DEVICE) && !shieldedMining)) {
77 return;
78 }
79
80 if (minMineSpeed > 0) {
81 Place_moving_mine(ind);
82 return;
83 }
84
85 Place_general_mine(ind, pl->team, 0,
86 pl->pos.x, pl->pos.y, 0.0, 0.0, pl->mods);
87 }
88
89
Place_moving_mine(int ind)90 void Place_moving_mine(int ind)
91 {
92 player *pl = Players[ind];
93 DFLOAT vx = pl->vel.x;
94 DFLOAT vy = pl->vel.y;
95
96 if (pl->item[ITEM_MINE] <= 0
97 || (BIT(pl->used, HAS_SHIELD|HAS_PHASING_DEVICE) && !shieldedMining)) {
98 return;
99 }
100
101 if (minMineSpeed > 0) {
102 if (pl->velocity < minMineSpeed) {
103 if (pl->velocity >= 1) {
104 vx *= (minMineSpeed / pl->velocity);
105 vy *= (minMineSpeed / pl->velocity);
106 }
107 else {
108 vx = minMineSpeed * tcos(pl->dir);
109 vy = minMineSpeed * tsin(pl->dir);
110 }
111 }
112 }
113
114 Place_general_mine(ind, pl->team, GRAVITY,
115 pl->pos.x, pl->pos.y, vx, vy, pl->mods);
116 }
117
Place_general_mine(int ind,unsigned short team,long status,DFLOAT x,DFLOAT y,DFLOAT vx,DFLOAT vy,modifiers mods)118 void Place_general_mine(int ind, unsigned short team, long status,
119 DFLOAT x, DFLOAT y,
120 DFLOAT vx, DFLOAT vy, modifiers mods)
121 {
122 char msg[MSG_LEN];
123 player *pl = (ind == -1 ? NULL : Players[ind]);
124 int used, life;
125 long drain;
126 DFLOAT mass;
127 int i, minis;
128 vector mv;
129
130 if (NumObjs + mods.mini >= MAX_TOTAL_SHOTS)
131 return;
132 if (BIT(World.rules->mode, WRAP_PLAY)) {
133 if (x < 0) x += World.width;
134 else if (x >= World.width) x -= World.width;
135 if (y < 0) y += World.height;
136 else if (y >= World.height) y -= World.height;
137 }
138 if (x < 0 || x >= World.width || y < 0 || y >= World.height) {
139 return;
140 }
141
142 if (pl && BIT(pl->status, KILLED)) {
143 life = (int)(rfrac() * FPS);
144 } else if (BIT(status, FROMCANNON)) {
145 life = CANNON_SHOT_LIFE;
146 } else {
147 life = (mineLife ? mineLife : MINE_LIFETIME);
148 }
149
150 if (!BIT(mods.warhead, CLUSTER))
151 mods.velocity = 0;
152 if (!mods.mini)
153 mods.spread = 0;
154
155 if (nukeMinSmarts <= 0) {
156 CLR_BIT(mods.nuclear, NUCLEAR);
157 }
158 if (BIT(mods.nuclear, NUCLEAR)) {
159 if (pl) {
160 used = (BIT(mods.nuclear, FULLNUCLEAR)
161 ? pl->item[ITEM_MINE]
162 : nukeMinMines);
163 if (pl->item[ITEM_MINE] < nukeMinMines) {
164 sprintf(msg, "You need at least %d mines to %s %s!",
165 nukeMinMines,
166 (BIT(status, GRAVITY) ? "throw" : "drop"),
167 Describe_shot (OBJ_MINE, status, mods, 0));
168 Set_player_message (pl, msg);
169 return;
170 }
171 } else {
172 used = nukeMinMines;
173 }
174 mass = MINE_MASS * used * NUKE_MASS_MULT;
175 } else {
176 mass = (BIT(status, FROMCANNON) ? MINE_MASS * 0.6 : MINE_MASS);
177 used = 1;
178 }
179
180 if (pl) {
181 drain = ED_MINE;
182 if (BIT(mods.warhead, CLUSTER)) {
183 drain += (long)(CLUSTER_MASS_DRAIN(mass));
184 }
185 if (pl->fuel.sum < -drain) {
186 sprintf(msg, "You need at least %ld fuel units to %s %s!",
187 (-drain) >> FUEL_SCALE_BITS,
188 (BIT(status, GRAVITY) ? "throw" : "drop"),
189 Describe_shot(OBJ_MINE, status, mods, 0));
190 Set_player_message (pl, msg);
191 return;
192 }
193 if (baseMineRange) {
194 for (i = 0; i < NumPlayers; i++) {
195 if (i != ind
196 && !Team_immune(Players[i]->id, pl->id)
197 && !IS_TANK_IND(i)) {
198 int dx = (int)(x/BLOCK_SZ - World.base[Players[i]->home_base].pos.x);
199 int dy = (int)(y/BLOCK_SZ - World.base[Players[i]->home_base].pos.y);
200 if (sqr(dx) + sqr(dy) <= sqr(baseMineRange)) {
201 Set_player_message(pl, "No base mining!");
202 return;
203 }
204 }
205 }
206 }
207 Add_fuel(&(pl->fuel), drain);
208 pl->item[ITEM_MINE] -= used;
209
210 if (used > 1) {
211 sprintf(msg, "%s has %s %s!", pl->name,
212 (BIT(status, GRAVITY) ? "thrown" : "dropped"),
213 Describe_shot(OBJ_MINE, status, mods, 0));
214 Set_message(msg);
215 sound_play_all(NUKE_LAUNCH_SOUND);
216 } else {
217 sound_play_sensors(pl->pos.x, pl->pos.y,
218 BIT(status, GRAVITY) ? DROP_MOVING_MINE_SOUND : DROP_MINE_SOUND);
219 }
220 }
221
222 minis = (mods.mini + 1);
223 SET_BIT(status, OWNERIMMUNE);
224
225 for (i = 0; i < minis; i++) {
226
227 mineobject *mine;
228
229 if ((mine = MINE_PTR(Object_allocate())) == NULL) {
230 break;
231 }
232
233 mine->type = OBJ_MINE;
234 mine->color = BLUE;
235 mine->info = mineFuseTime;
236 mine->status = status;
237 mine->id = (pl ? pl->id : NO_ID);
238 mine->team = team;
239 mine->owner = mine->id;
240 Object_position_init_pixels(OBJ_PTR(mine), x, y);
241 if (minis > 1) {
242 int space = RES/minis;
243 int dir;
244 DFLOAT spread;
245
246 spread = (DFLOAT)((unsigned)mods.spread + 1);
247 /*
248 * Dir gives (S is ship upwards);
249 *
250 * o o o
251 * X2: o S o X3: S X4: S
252 * o o o o
253 */
254 dir = (i * space) + space/2 + (minis-2)*(RES/2) + (pl?pl->dir:0);
255 dir += (int)((rfrac() - 0.5f) * space * 0.5f);
256 dir = MOD2(dir, RES);
257 mv.x = MINI_MINE_SPREAD_SPEED * tcos(dir) / spread;
258 mv.y = MINI_MINE_SPREAD_SPEED * tsin(dir) / spread;
259 /*
260 * This causes the added initial velocity to reduce to
261 * zero over the MINI_MINE_SPREAD_TIME.
262 */
263 mine->spread_left = MINI_MINE_SPREAD_TIME;
264 mine->acc.x = -mv.x / MINI_MINE_SPREAD_TIME;
265 mine->acc.y = -mv.y / MINI_MINE_SPREAD_TIME;
266 } else {
267 mv.x = mv.y = mine->acc.x = mine->acc.y = 0.0;
268 mine->spread_left = 0;
269 }
270 mine->vel = mv;
271 mine->vel.x += vx * MINE_SPEED_FACT;
272 mine->vel.y += vy * MINE_SPEED_FACT;
273 mine->mass = mass / minis;
274 mine->life = life / minis;
275 mine->mods = mods;
276 mine->pl_range = (int)(MINE_RANGE / minis);
277 mine->pl_radius = MINE_RADIUS;
278 Cell_add_object((object *) mine);
279 }
280 }
281
282 /*
283 * Up to and including 3.2.6 it was:
284 * Cause all of the given player's dropped/thrown mines to explode.
285 * Since this caused a slowdown when many mines detonated it
286 * is changed into:
287 * Cause the mine which is closest to a player and owned
288 * by that player to detonate.
289 */
Detonate_mines(int ind)290 void Detonate_mines(int ind)
291 {
292 player *pl = Players[ind];
293 int i;
294 int closest = -1;
295 DFLOAT dist;
296 DFLOAT min_dist = World.hypotenuse + 1;
297
298 if (BIT(pl->used, HAS_PHASING_DEVICE))
299 return;
300
301 for (i = 0; i < NumObjs; i++) {
302 object *mine = Obj[i];
303
304 if (! BIT(mine->type, OBJ_MINE))
305 continue;
306 /*
307 * Mines which have been ECM reprogrammed should only be detonatable
308 * by the reprogrammer, not by the original mine placer:
309 */
310 if (mine->id == pl->id) {
311 dist = Wrap_length(pl->pos.x - mine->pos.x, pl->pos.y - mine->pos.y);
312 if (dist < min_dist) {
313 min_dist = dist;
314 closest = i;
315 }
316 }
317 }
318 if (closest != -1) {
319 Obj[closest]->life = 0;
320 }
321
322 return;
323 }
324
Make_treasure_ball(int treasure)325 void Make_treasure_ball(int treasure)
326 {
327 ballobject *ball;
328 treasure_t *t = &(World.treasures[treasure]);
329 DFLOAT x = (t->pos.x + 0.5) * BLOCK_SZ,
330 y = (t->pos.y * BLOCK_SZ) + 10;
331
332 if (t->empty)
333 return;
334 if (t->have) {
335 xpprintf("%s Failed Make_treasure_ball(treasure=%d):\n",
336 showtime(), treasure);
337 xpprintf("\ttreasure: destroyed = %d, team = %d, have = %d\n",
338 t->destroyed, t->team, t->have);
339 return;
340 }
341
342 if ((ball = BALL_PTR(Object_allocate())) == NULL) {
343 return;
344 }
345
346 ball->length = ballConnectorLength;
347 ball->life = LONG_MAX;
348 ball->mass = ballMass;
349 ball->vel.x = 0; /* make the ball stuck a little */
350 ball->vel.y = 0; /* longer to the ground */
351 ball->acc.x = 0;
352 ball->acc.y = 0;
353 Object_position_init_pixels(OBJ_PTR(ball), x, y);
354 ball->id = NO_ID;
355 ball->owner = NO_ID;
356 ball->team = t->team;
357 ball->type = OBJ_BALL;
358 ball->color = WHITE;
359 ball->count = 0;
360 ball->pl_range = BALL_RADIUS;
361 ball->pl_radius = BALL_RADIUS;
362 CLEAR_MODS(ball->mods);
363 ball->status = RECREATE;
364 ball->treasure = treasure;
365 Cell_add_object(OBJ_PTR(ball));
366
367 t->have = true;
368 }
369
370
371 /*
372 * Describes shot of `type' which has `status' and `mods'. If `hit' is
373 * non-zero this description is part of a collision, otherwise its part
374 * of a launch message.
375 */
Describe_shot(int type,long status,modifiers mods,int hit)376 char *Describe_shot(int type, long status, modifiers mods, int hit)
377 {
378 const char *name, *howmany = "a ", *plural = "";
379 static char msg[MSG_LEN];
380
381 switch (type) {
382 case OBJ_MINE:
383 if (BIT(status, GRAVITY))
384 name = "bomb";
385 else
386 name = "mine";
387 break;
388 case OBJ_SMART_SHOT:
389 name = "smart missile";
390 break;
391 case OBJ_TORPEDO:
392 name = "torpedo";
393 break;
394 case OBJ_HEAT_SHOT:
395 name = "heatseeker";
396 break;
397 case OBJ_CANNON_SHOT:
398 if (BIT(mods.warhead, CLUSTER)) {
399 howmany = "";
400 name = "flak";
401 } else {
402 name = "shot";
403 }
404 break;
405 default:
406 /*
407 * Cluster shots are actual debris from a cluster explosion
408 * so we describe it as "cluster debris".
409 */
410 if (BIT(mods.warhead, CLUSTER)) {
411 howmany = "";
412 name = "debris";
413 } else {
414 name = "shot";
415 }
416 break;
417 }
418
419 if (mods.mini && !hit) {
420 howmany = "some ";
421 plural = (type == OBJ_TORPEDO) ? "es" : "s";
422 }
423
424 sprintf (msg, "%s%s%s%s%s%s%s%s%s",
425 howmany,
426 ((mods.velocity || mods.spread || mods.power) ? "modified " : ""),
427 (mods.mini ? "mini " : ""),
428 (BIT(mods.nuclear, FULLNUCLEAR) ? "full " : ""),
429 (BIT(mods.nuclear, NUCLEAR) ? "nuclear " : ""),
430 (BIT(mods.warhead, IMPLOSION) ? "imploding " : ""),
431 (BIT(mods.warhead, CLUSTER) ? "cluster " : ""),
432 name,
433 plural);
434
435 return msg;
436 }
437
Fire_main_shot(int ind,int type,int dir)438 void Fire_main_shot(int ind, int type, int dir)
439 {
440 player *pl = Players[ind];
441 DFLOAT x,
442 y;
443
444 if (pl->shots >= pl->shot_max || BIT(pl->used, HAS_SHIELD|HAS_PHASING_DEVICE))
445 return;
446
447 x = pl->pos.x + pl->ship->m_gun[pl->dir].x;
448 y = pl->pos.y + pl->ship->m_gun[pl->dir].y;
449
450 Fire_general_shot(ind, pl->team, 0, x, y, type, dir, pl->mods, -1);
451 }
452
Fire_shot(int ind,int type,int dir)453 void Fire_shot(int ind, int type, int dir)
454 {
455 player *pl = Players[ind];
456
457 if (pl->shots >= pl->shot_max || BIT(pl->used, HAS_SHIELD|HAS_PHASING_DEVICE))
458 return;
459
460 Fire_general_shot(ind, pl->team, 0, pl->pos.x, pl->pos.y,
461 type, dir, pl->mods, -1);
462 }
463
Fire_left_shot(int ind,int type,int dir,int gun)464 void Fire_left_shot(int ind, int type, int dir, int gun)
465 {
466 player *pl = Players[ind];
467 DFLOAT x,
468 y;
469
470 if (pl->shots >= pl->shot_max || BIT(pl->used, HAS_SHIELD|HAS_PHASING_DEVICE))
471 return;
472
473 x = pl->pos.x + pl->ship->l_gun[gun][pl->dir].x;
474 y = pl->pos.y + pl->ship->l_gun[gun][pl->dir].y;
475
476 Fire_general_shot(ind, pl->team, 0, x, y, type, dir, pl->mods, -1);
477
478 }
479
Fire_right_shot(int ind,int type,int dir,int gun)480 void Fire_right_shot(int ind, int type, int dir, int gun)
481 {
482 player *pl = Players[ind];
483 DFLOAT x,
484 y;
485
486 if (pl->shots >= pl->shot_max || BIT(pl->used, HAS_SHIELD|HAS_PHASING_DEVICE))
487 return;
488
489 x = pl->pos.x + pl->ship->r_gun[gun][pl->dir].x;
490 y = pl->pos.y + pl->ship->r_gun[gun][pl->dir].y;
491
492 Fire_general_shot(ind, pl->team, 0, x, y, type, dir, pl->mods, -1);
493
494 }
495
Fire_left_rshot(int ind,int type,int dir,int gun)496 void Fire_left_rshot(int ind, int type, int dir, int gun)
497 {
498 player *pl = Players[ind];
499 DFLOAT x,
500 y;
501
502 if (pl->shots >= pl->shot_max || BIT(pl->used, HAS_SHIELD|HAS_PHASING_DEVICE))
503 return;
504
505 x = pl->pos.x + pl->ship->l_rgun[gun][pl->dir].x;
506 y = pl->pos.y + pl->ship->l_rgun[gun][pl->dir].y;
507
508 Fire_general_shot(ind, pl->team, 0, x, y, type, dir, pl->mods, -1);
509
510 }
511
Fire_right_rshot(int ind,int type,int dir,int gun)512 void Fire_right_rshot(int ind, int type, int dir, int gun)
513 {
514 player *pl = Players[ind];
515 DFLOAT x,
516 y;
517
518 if (pl->shots >= pl->shot_max || BIT(pl->used, HAS_SHIELD|HAS_PHASING_DEVICE))
519 return;
520
521 x = pl->pos.x + pl->ship->r_rgun[gun][pl->dir].x;
522 y = pl->pos.y + pl->ship->r_rgun[gun][pl->dir].y;
523
524 Fire_general_shot(ind, pl->team, 0, x, y, type, dir, pl->mods, -1);
525
526 }
527
Fire_general_shot(int ind,unsigned short team,bool cannon,DFLOAT x,DFLOAT y,int type,int dir,modifiers mods,int target)528 void Fire_general_shot(int ind, unsigned short team, bool cannon,
529 DFLOAT x, DFLOAT y,
530 int type, int dir,
531 modifiers mods, int target)
532 {
533 char msg[MSG_LEN];
534 player *pl = (ind == -1 ? NULL : Players[ind]);
535 int used,
536 life = ShotsLife,
537 fuse = 0,
538 lock = 0,
539 status = GRAVITY,
540 i, ldir, minis,
541 pl_range,
542 pl_radius,
543 rack_no = 0,
544 racks_left = 0,
545 r,
546 on_this_rack = 0,
547 side = 0,
548 fired = 0;
549 long drain;
550 DFLOAT mass = ShotsMass,
551 speed = ShotsSpeed,
552 turnspeed = 0,
553 max_speed = SPEED_LIMIT,
554 angle,
555 spread;
556 vector mv;
557 position shotpos;
558 object *mini_objs[MODS_MINI_MAX + 1];
559
560 if (NumObjs >= MAX_TOTAL_SHOTS)
561 return;
562
563 if (!BIT(mods.warhead, CLUSTER))
564 mods.velocity = 0;
565 if (!mods.mini)
566 mods.spread = 0;
567
568 if (cannon) {
569 mass = CANNON_SHOT_MASS;
570 life = CANNON_SHOT_LIFE;
571 SET_BIT(status, FROMCANNON);
572 }
573
574
575 switch (type) {
576 default:
577 return;
578
579 case OBJ_SHOT:
580 CLEAR_MODS(mods); /* Shots can't be modified! */
581 /* FALLTHROUGH */
582 case OBJ_CANNON_SHOT:
583 pl_range = pl_radius = 0;
584 if (pl) {
585 if (pl->fuel.sum < -ED_SHOT)
586 return;
587 Add_fuel(&(pl->fuel), (long)(ED_SHOT));
588 sound_play_sensors(pl->pos.x, pl->pos.y, FIRE_SHOT_SOUND);
589 pl->shots++;
590 }
591 if (!ShotsGravity) {
592 CLR_BIT(status, GRAVITY);
593 }
594 break;
595
596 case OBJ_SMART_SHOT:
597 case OBJ_HEAT_SHOT:
598 if ((type == OBJ_HEAT_SHOT) ? !allowHeatSeekers : !allowSmartMissiles) {
599 if (allowTorpedoes) {
600 type = OBJ_TORPEDO;
601 } else {
602 return;
603 }
604 }
605 /* FALLTHROUGH */
606 case OBJ_TORPEDO:
607 /*
608 * Make sure there are enough object entries for the mini shots.
609 */
610 if (NumObjs + mods.mini >= MAX_TOTAL_SHOTS)
611 return;
612
613 if (pl && pl->item[ITEM_MISSILE] <= 0)
614 return;
615
616 if (nukeMinSmarts <= 0) {
617 CLR_BIT(mods.nuclear, NUCLEAR);
618 }
619 if (BIT(mods.nuclear, NUCLEAR)) {
620 if (pl) {
621 used = (BIT(mods.nuclear, FULLNUCLEAR)
622 ? pl->item[ITEM_MISSILE]
623 : nukeMinSmarts);
624 if (pl->item[ITEM_MISSILE] < nukeMinSmarts) {
625 sprintf(msg,
626 "You need at least %d missiles to fire %s!",
627 nukeMinSmarts,
628 Describe_shot (type, status, mods, 0));
629 Set_player_message (pl, msg);
630 return;
631 }
632 } else {
633 used = nukeMinSmarts;
634 }
635 mass = MISSILE_MASS * used * NUKE_MASS_MULT;
636 pl_range = (type == OBJ_TORPEDO) ? (int)NUKE_RANGE : MISSILE_RANGE;
637 } else {
638 mass = MISSILE_MASS;
639 used = 1;
640 pl_range = (type == OBJ_TORPEDO) ? (int)TORPEDO_RANGE : MISSILE_RANGE;
641 }
642 pl_range /= mods.mini + 1;
643 pl_radius = MISSILE_LEN;
644
645 drain = used * ED_SMART_SHOT;
646 if (BIT(mods.warhead, CLUSTER)) {
647 if (pl)
648 drain += (long)(CLUSTER_MASS_DRAIN(mass));
649 }
650
651 if (pl && BIT(pl->status, KILLED)) {
652 life = (int)(rfrac() * FPS);
653 } else if (!cannon) {
654 life = (missileLife ? missileLife : MISSILE_LIFETIME);
655 }
656
657 switch (type) {
658 case OBJ_HEAT_SHOT:
659 #ifndef HEAT_LOCK
660 lock = -1;
661 #else /* HEAT_LOCK */
662 if (pl == NULL) {
663 lock = target;
664 } else {
665 if (!BIT(pl->lock.tagged, LOCK_PLAYER)
666 || ((pl->lock.distance > pl->sensor_range)
667 && BIT(World.rules->mode, LIMITED_VISIBILITY))) {
668 lock = -1;
669 } else {
670 lock = pl->lock.pl_id;
671 }
672 }
673 #endif /* HEAT_LOCK */
674 if (pl) {
675 sound_play_sensors(pl->pos.x, pl->pos.y, FIRE_HEAT_SHOT_SOUND);
676 }
677 max_speed = SMART_SHOT_MAX_SPEED * HEAT_SPEED_FACT;
678 turnspeed = SMART_TURNSPEED * HEAT_SPEED_FACT;
679 speed *= HEAT_SPEED_FACT;
680 break;
681
682 case OBJ_SMART_SHOT:
683 if (pl == NULL) {
684 lock = target;
685 } else {
686 if (!BIT(pl->lock.tagged, LOCK_PLAYER)
687 || ((pl->lock.distance > pl->sensor_range)
688 && BIT(World.rules->mode, LIMITED_VISIBILITY))
689 || !pl->visibility[GetInd[pl->lock.pl_id]].canSee)
690 return;
691 lock = pl->lock.pl_id;
692 }
693 max_speed = SMART_SHOT_MAX_SPEED;
694 turnspeed = SMART_TURNSPEED;
695 break;
696
697 case OBJ_TORPEDO:
698 lock = -1;
699 fuse = 8;
700 break;
701 }
702
703 if (pl) {
704 if (pl->fuel.sum < -drain) {
705 sprintf(msg, "You need at least %ld fuel units to fire %s!",
706 (-drain) >> FUEL_SCALE_BITS,
707 Describe_shot(type, status, mods, 0));
708 Set_player_message (pl, msg);
709 return;
710 }
711 Add_fuel(&(pl->fuel), drain);
712 pl->item[ITEM_MISSILE] -= used;
713
714 if (used > 1) {
715 sprintf(msg, "%s has launched %s!", pl->name,
716 Describe_shot(type, status, mods, 0));
717 Set_message(msg);
718 sound_play_all(NUKE_LAUNCH_SOUND);
719 } else if (type == OBJ_SMART_SHOT) {
720 sound_play_sensors(pl->pos.x, pl->pos.y, FIRE_SMART_SHOT_SOUND);
721 } else if (type == OBJ_TORPEDO) {
722 sound_play_sensors(pl->pos.x, pl->pos.y, FIRE_TORPEDO_SOUND);
723 }
724 }
725 break;
726 }
727
728 minis = (mods.mini + 1);
729 speed *= (1 + (mods.power * MISSILE_POWER_SPEED_FACT));
730 max_speed *= (1 + (mods.power * MISSILE_POWER_SPEED_FACT));
731 turnspeed *= (1 + (mods.power * MISSILE_POWER_TURNSPEED_FACT));
732 spread = (DFLOAT)((unsigned)mods.spread + 1);
733 /*
734 * Calculate the maximum time it would take to cross one ships width,
735 * don't fuse the shot/missile/torpedo for the owner only until that
736 * time passes. This is a hack to stop various odd missile and shot
737 * mounting points killing the player when they're firing.
738 */
739 fuse += (int)((2.0 * (DFLOAT)SHIP_SZ) / speed + 1.0);
740
741 /*
742 * Missile Racks and Spread
743 * ------------------------
744 *
745 * A short story by H. J. Thompson
746 *
747 * Once upon a time, back in the "good old days" of XPilot, it was
748 * relatively easy thing to remember the few keys needed to fly and shoot.
749 * It was the day of Sopwith Camels biplanes, albeit triangular ones,
750 * doing close to-the-death machine gun combat with other triangular
751 * Red Barons, the hard vacuum of space whistling silently by as only
752 * something that doesn't exist could do (this was later augmented by
753 * artificial aural feedback devices on certain advanced hardware).
754 *
755 * Eventually the weapon designers came up with "smart" missiles, and
756 * another key was added to the control board, causing one missile to
757 * launch straight forwards from the front of the triangular ship.
758 * Soon other types of missiles were added, including "heat" seekers,
759 * and fast straight travelling "torpedoes" (hark, is that the sonorous
760 * ping-ping-ping of sonar equipment I hear?).
761 *
762 * Then one day along came a certain fellow who thought, among other
763 * things, that it would be neat to fire up to four missiles with one
764 * key press, just so the enemy pilot would be scared witless by the
765 * sudden appearance of four missiles hot on their tail. To make things
766 * fair these "mini" missiles would have the same total damage of a
767 * normal missile, but would travel at the speed of a normal missile.
768 *
769 * However this fellow mused that simply launching all the missiles in
770 * the same direction and from the same point would cause the missiles
771 * to appear on top of each other. Thus he added code to "spread" the
772 * missiles out at various angular offsets from the ship. Indeed the
773 * angular offsets could be controlled using a spread modifier, and yet
774 * more keys appeared on a now crowded control desk.
775 *
776 * Interestingly the future would see the same fellow adding a two seater
777 * variant of the standard single seater ship, allowing one person
778 * to concentrate on flying the ship, while another could flick through
779 * out-of-date manuals searching for the right key combinations on
780 * the now huge console which would launch four full nuclear slow-cluster
781 * imploding mini super speed close spread torpedoes at the currently
782 * targetted enemy, and then engage emergency thrust and shields before
783 * the ominous looking tri-winged dagger ship recoiled at high velocity
784 * into a rocky wall half way across the other side of the universe.
785 *
786 * Back to our story, and this same fellow was musing at the design of
787 * multiple "mini" missiles, and noted that the angle of launch would
788 * also require a different launch point on the ship (actually it was
789 * the same position as if the front of the ship was rotated to point in
790 * the direction of missile launch, mainly because it was easier to
791 * write the launch/guidance computer software that way).
792 *
793 * Later, some artistically (or sadistically) minded person decided that
794 * triangular ships just didn't look good (even though they were very
795 * spatially dynamic, cheap and easy to build), and wouldn't it be just
796 * fantastic if one could have a ship shaped like a banana! Sensibly,
797 * however, he restricted missiles and guns to the normal single frontal
798 * launching point.
799 *
800 * A few weeks later, somebody else decided how visually pleasing it
801 * would be if one could design where missiles could be fired from by
802 * adding "missile rack" points on the ship. Up to four racks were
803 * available, and missiles would fire from exactly these points on the
804 * ship. Since one to four missiles could be fired in one go, the
805 * combinations with various ship designs were numerous (16).
806 *
807 * What would happen if somebody fired four missiles in one go, from a
808 * ship that only had three missile racks? How about two missiles from
809 * one with four racks? Sadly the missile launch software hadn't been
810 * designed to take this sort of thing into account, and incredibly the
811 * original programmer wasn't notified until after First Customer Ship
812 * [sic], the launch software only slightly modified by the ship
813 * designer, who didn't know the first thing about launch acceleration
814 * curves or electronic owner immunity fuse timers.
815 *
816 * Pilots found their missiles were being fired from random points and
817 * in sometimes very odd directions, occasionally even destroying the
818 * ship without trace, severely annoying the ship's owners and several
819 * insurance underwriters. Not soon after several ship designers were
820 * mysteriously killed in a freak "accident" involving a stray nuclear
821 * cluster bomb, and the remaining ship designers became very careful
822 * to place missile racks and extra gun turrets well away from the
823 * ship's superstructure.
824 *
825 * The original programmer who invented multiple "mini" spreading
826 * missiles quickly decided to revisit his code before any "accidents"
827 * came his way, and spent a good few hours making sure one couldn't
828 * shoot oneself in the "foot", and that missiles where launched in some
829 * reasonable and sensible directions based on the position of the
830 * missile racks.
831 *
832 * How It Actually Works
833 * ---------------------
834 *
835 * The first obstacle is getting the right number of missiles fired
836 * from each combination of missile rack configurations;
837 *
838 *
839 * Minis 1 2 3 4
840 * Racks
841 * 1 1 2 3 4
842 *
843 * 2 1/- 1/1 2/1 2/2
844 * -/1 1/2
845 *
846 * 3 1/-/- 1/1/- 1/1/1 2/1/1
847 * -/1/- -/1/1 1/2/1
848 * -/-/1 1/-/1 1/1/2
849 *
850 * 4 1/-/-/- 1/1/-/- 1/1/1/- 1/1/1/1
851 * -/1/-/- -/1/1/- -/1/1/1
852 * -/-/1/- -/-/1/1 1/-/1/1
853 * -/-/-/1 1/-/-/1 1/1/-/1
854 *
855 * To read; For example with 2 Minis and 3 Racks, the first round will
856 * fire 1/1/-, which is one missile from left and middle racks. The
857 * next time fired will be -/1/1; middle and right, next fire is
858 * 1/-/1; left and right. Next time goes to the beggining state.
859 *
860 * Comment Point 1
861 * ---------------
862 *
863 * The *starting* rack number for each salvo cycles through the number
864 * of missiles racks. This is stored in the player variable
865 * `pl->missile_rack', and is only incremented after each salvo (not
866 * after each mini missile is fired). This value is used to initialise
867 * `rack_no', which stores the current rack that missiles are fired from.
868 *
869 * `on_this_rack' is computed to be the number of missiles that will be
870 * fired from `rack_no', and `r' is used as a counter to this value.
871 *
872 * `racks_left' count how many unused missiles racks are left on the ship
873 * in this mini missile salvo.
874 *
875 * Comment Point 2
876 * ---------------
877 *
878 * When `r' reaches `on_this_rack' all the missiles have been fired for
879 * this rack, and the next rack should be used. `rack_no' is incremented
880 * modulo the number of available racks, and `racks_left' is decremented.
881 * At this point `on_this_rack' is recomputed for the next rack, and `r'
882 * reset to zero. Thus initially these two variables are both zero, and
883 * `rack_no' is one less, such that these variables can be computed inside
884 * the loop to make the code simpler.
885 *
886 * The computation of `on_this_rack' is as follows; Given that there
887 * are M missiles and R racks remaining;
888 *
889 * on_this_rack = int(M / R); (ie. round down to lowest int)
890 *
891 * Then;
892 *
893 * (M - on_this_rack) / (R - 1) < (M / R).
894 *
895 * That is, the number of missiles fired on the next rack will be
896 * more precise, and trivially can be seen that when R is 1, will
897 * give an exact number of missiles to fire on the last rack.
898 *
899 * In the code `M' is (minis - i), and `R' is racks_left.
900 *
901 * Comment Point 3
902 * ---------------
903 *
904 * In order that multiple missiles fired from one rack do not conincide,
905 * each missile has to be "spread" based on the number of missiles
906 * fired from this rack point.
907 *
908 * This is computed similar to the wide shot code;
909 *
910 * angle = (N - 1 - 2 * i) / (N - 1)
911 *
912 * Where N is the number of shots/missiles to be fired, and i is a counter
913 * from 0 .. N-1.
914 *
915 * i 0 1 2 3
916 * N
917 * 1 0
918 * 2 1 -1
919 * 3 1 0 -1
920 * 4 1 0.333 -0.333 -1
921 *
922 * In this code `N' is `on_this_rack'.
923 *
924 * Also the position of the missile rack from the center line of the
925 * ship (stored in `side') has a linear effect on the angle, such that
926 * a point farthest from the center line contributes the largest angle;
927 *
928 * angle += (side / SHIP_SZ)
929 *
930 * Since the eventual `angle' value used in the code should be a
931 * percentage of the unmodified launch angle, it should be ranged between
932 * -1.00 and +1.00, and thus the first angle is reduced by 33% and the
933 * second by 66%.
934 *
935 * Contact: harveyt@sco.com
936 */
937
938 if (pl && type != OBJ_SHOT) {
939 /*
940 * Initialise missile rack spread variables. (See Comment Point 1)
941 */
942 on_this_rack = 0;
943 racks_left = pl->ship->num_m_rack;
944 rack_no = pl->missile_rack - 1;
945 if (++pl->missile_rack >= pl->ship->num_m_rack)
946 pl->missile_rack = 0;
947 }
948
949 for (r = 0, i = 0; i < minis; i++, r++) {
950 object *shot;
951
952 if ((shot = Object_allocate()) == NULL) {
953 break;
954 }
955
956 shot->life = life / minis;
957 shot->fuselife = shot->life - fuse;
958 shot->mass = mass / minis;
959 shot->count = 0;
960 shot->info = lock;
961 shot->type = type;
962 shot->id = (pl ? pl->id : NO_ID);
963 shot->team = team;
964 shot->color = (pl ? pl->color : WHITE);
965
966 if (BIT(shot->type, OBJ_TORPEDO | OBJ_HEAT_SHOT | OBJ_SMART_SHOT)) {
967 MISSILE_PTR(shot)->turnspeed = turnspeed;
968 MISSILE_PTR(shot)->max_speed = max_speed;
969 }
970
971 shotpos.x = x;
972 shotpos.y = y;
973 if (pl && type != OBJ_SHOT) {
974 if (r == on_this_rack) {
975 /*
976 * We've fired all the mini missiles for the current rack,
977 * we now move onto the next one. (See Comment Point 2)
978 */
979 on_this_rack = (minis - i) / racks_left--;
980 if (on_this_rack < 1) on_this_rack = 1;
981 if (++rack_no >= pl->ship->num_m_rack)
982 rack_no = 0;
983 r = 0;
984 }
985 shotpos.x += pl->ship->m_rack[rack_no][pl->dir].x;
986 shotpos.y += pl->ship->m_rack[rack_no][pl->dir].y;
987 side = (int)(pl->ship->m_rack[rack_no][0].y);
988 }
989 shotpos.x = WRAP_XPIXEL(shotpos.x);
990 shotpos.y = WRAP_YPIXEL(shotpos.y);
991 if (shotpos.x < 0 || shotpos.x >= World.width
992 || shotpos.y < 0 || shotpos.y >= World.height) {
993 continue;
994 }
995 Object_position_init_pixels(shot, shotpos.x, shotpos.y);
996
997 if (type == OBJ_SHOT || !pl) {
998 angle = 0.0;
999 } else {
1000 /*
1001 * Calculate the percentage unmodified launch angle for missiles.
1002 * (See Comment Point 3).
1003 */
1004 if (on_this_rack <= 1) {
1005 angle = 0.0;
1006 } else {
1007 angle = (DFLOAT)(on_this_rack - 1 - 2 * r);
1008 angle /= (3.0 * (DFLOAT)(on_this_rack - 1));
1009 }
1010 angle += (DFLOAT)(2 * side) / (DFLOAT)(3 * SHIP_SZ);
1011 }
1012
1013 /*
1014 * Torpedoes spread like mines, except the launch direction
1015 * is preset over the range +/- MINI_TORPEDO_SPREAD_ANGLE.
1016 * (This is not modified by the spread, the initial velocity is)
1017 *
1018 * Other missiles are just launched in a different direction
1019 * which varies over the range +/- MINI_MISSILE_SPREAD_ANGLE,
1020 * which the spread modifier varies.
1021 */
1022 switch (type) {
1023 case OBJ_TORPEDO:
1024 angle *= (MINI_TORPEDO_SPREAD_ANGLE / 360.0) * RES;
1025 ldir = MOD2(dir + (int)angle, RES);
1026 mv.x = MINI_TORPEDO_SPREAD_SPEED * tcos(ldir) / spread;
1027 mv.y = MINI_TORPEDO_SPREAD_SPEED * tsin(ldir) / spread;
1028 /*
1029 * This causes the added initial velocity to reduce to
1030 * zero over the MINI_TORPEDO_SPREAD_TIME.
1031 * FIX: torpedoes should have the same speed
1032 * regardless of minification.
1033 */
1034 TORP_PTR(shot)->spread_left = MINI_TORPEDO_SPREAD_TIME;
1035 shot->acc.x = -mv.x / MINI_TORPEDO_SPREAD_TIME;
1036 shot->acc.y = -mv.y / MINI_TORPEDO_SPREAD_TIME;
1037 ldir = dir;
1038 break;
1039
1040 default:
1041 angle *= (MINI_MISSILE_SPREAD_ANGLE / 360.0) * RES / spread;
1042 ldir = MOD2(dir + (int)angle, RES);
1043 mv.x = mv.y = shot->acc.x = shot->acc.y = 0;
1044 break;
1045 }
1046
1047 shot->vel.x = mv.x + (pl ? pl->vel.x : 0.0) + tcos(ldir) * speed;
1048 shot->vel.y = mv.y + (pl ? pl->vel.y : 0.0) + tsin(ldir) * speed;
1049 shot->status = status;
1050 shot->missile_dir = ldir;
1051 shot->mods = mods;
1052 shot->pl_range = pl_range;
1053 shot->pl_radius = pl_radius;
1054 Cell_add_object(shot);
1055
1056 mini_objs[fired] = shot;
1057 fired++;
1058 }
1059
1060 /*
1061 * Recoil must be done instantaneously otherwise ship moves back after
1062 * firing each mini missile.
1063 */
1064 if (pl) {
1065 for (i = 0; i < fired; i++) {
1066 Recoil((object *)pl, mini_objs[i]);
1067 }
1068 }
1069 }
1070
1071
Fire_normal_shots(int ind)1072 void Fire_normal_shots(int ind)
1073 {
1074 player *pl = Players[ind];
1075 int i, shot_angle;
1076
1077 if (frame_loops < pl->shot_time + fireRepeatRate) {
1078 return;
1079 }
1080 pl->shot_time = frame_loops;
1081
1082 shot_angle = MODS_SPREAD_MAX - pl->mods.spread;
1083
1084 Fire_main_shot(ind, OBJ_SHOT, pl->dir);
1085 for (i = 0; i < pl->item[ITEM_WIDEANGLE]; i++) {
1086 if (pl->ship->num_l_gun > 0) {
1087 Fire_left_shot(ind, OBJ_SHOT, MOD2(pl->dir + (1 + i) * shot_angle,
1088 RES), i % pl->ship->num_l_gun);
1089 }
1090 else {
1091 Fire_main_shot(ind, OBJ_SHOT, MOD2(pl->dir + (1 + i) * shot_angle,
1092 RES));
1093 }
1094 if (pl->ship->num_r_gun > 0) {
1095 Fire_right_shot(ind, OBJ_SHOT, MOD2(pl->dir - (1 + i) * shot_angle,
1096 RES), i % pl->ship->num_r_gun);
1097 }
1098 else {
1099 Fire_main_shot(ind, OBJ_SHOT, MOD2(pl->dir - (1 + i) * shot_angle,
1100 RES));
1101 }
1102 }
1103 for (i = 0; i < pl->item[ITEM_REARSHOT]; i++) {
1104 if ((pl->item[ITEM_REARSHOT] - 1 - 2 * i) < 0) {
1105 if (pl->ship->num_l_rgun > 0) {
1106 Fire_left_rshot(ind, OBJ_SHOT, MOD2(pl->dir + RES/2
1107 + ((pl->item[ITEM_REARSHOT] - 1 - 2 * i) * shot_angle) / 2,
1108 RES), (i - (pl->item[ITEM_REARSHOT] + 1) / 2) % pl->ship->num_l_rgun);
1109 }
1110 else {
1111 Fire_shot(ind, OBJ_SHOT, MOD2(pl->dir + RES/2
1112 + ((pl->item[ITEM_REARSHOT] - 1 - 2 * i) * shot_angle) / 2,
1113 RES));
1114 }
1115 }
1116 if ((pl->item[ITEM_REARSHOT] - 1 - 2 * i) > 0) {
1117 if (pl->ship->num_r_rgun > 0) {
1118 Fire_right_rshot(ind, OBJ_SHOT, MOD2(pl->dir + RES/2
1119 + ((pl->item[ITEM_REARSHOT] - 1 - 2 * i) * shot_angle) / 2,
1120 RES), (pl->item[ITEM_REARSHOT] / 2 - i - 1) % pl->ship->num_r_rgun);
1121 }
1122 else {
1123 Fire_shot(ind, OBJ_SHOT, MOD2(pl->dir + RES/2
1124 + ((pl->item[ITEM_REARSHOT] - 1 - 2 * i) * shot_angle) / 2,
1125 RES));
1126 }
1127 }
1128 if ((pl->item[ITEM_REARSHOT] - 1 - 2 * i) == 0)
1129 Fire_shot(ind, OBJ_SHOT, MOD2(pl->dir + RES/2
1130 + ((pl->item[ITEM_REARSHOT] - 1 - 2 * i) * shot_angle) / 2,
1131 RES));
1132 }
1133 }
1134
1135
1136 /* Removes shot from array */
Delete_shot(int ind)1137 void Delete_shot(int ind)
1138 {
1139 object *shot = Obj[ind]; /* Used when swapping places */
1140 ballobject *ball;
1141 player *pl;
1142 int addMine = 0;
1143 int addHeat = 0;
1144 int addBall = 0;
1145 modifiers mods;
1146 long status;
1147 int i;
1148 int intensity;
1149 int type, color;
1150 DFLOAT modv, speed_modv, life_modv, num_modv;
1151 DFLOAT mass;
1152
1153 switch (shot->type) {
1154
1155 case OBJ_SPARK:
1156 case OBJ_DEBRIS:
1157 case OBJ_WRECKAGE:
1158 break;
1159
1160 case OBJ_ASTEROID:
1161 Break_asteroid(ind);
1162 break;
1163
1164 case OBJ_BALL:
1165 ball = BALL_PTR(shot);
1166 if (ball->id != NO_ID)
1167 Detach_ball(GetInd[ball->id], ind);
1168 else {
1169 /*
1170 * Maybe some player is still busy trying to connect to this ball.
1171 */
1172 for (i = 0; i < NumPlayers; i++) {
1173 if (Players[i]->ball == ball) {
1174 Players[i]->ball = NULL;
1175 }
1176 }
1177 }
1178 if (ball->owner == NO_ID) {
1179 /*
1180 * If the ball has never been owned, the only way it could
1181 * have been destroyed is by being knocked out of the goal.
1182 * Therefore we force the ball to be recreated.
1183 */
1184 World.treasures[ball->treasure].have = false;
1185 SET_BIT(ball->status, RECREATE);
1186 }
1187 if (BIT(ball->status, RECREATE)) {
1188 addBall = 1;
1189 if (BIT(ball->status, NOEXPLOSION))
1190 break;
1191 sound_play_sensors(ball->pos.x, ball->pos.y, EXPLODE_BALL_SOUND);
1192 Make_debris(
1193 /* pos.x, pos.y */ ball->prevpos.x, ball->prevpos.y,
1194 /* vel.x, vel.y */ ball->vel.x, ball->vel.y,
1195 /* owner id */ ball->id,
1196 /* owner team */ ball->team,
1197 /* kind */ OBJ_DEBRIS,
1198 /* mass */ DEBRIS_MASS,
1199 /* status */ GRAVITY,
1200 /* color */ RED,
1201 /* radius */ 8,
1202 /* min,max debris */ 10, 20,
1203 /* min,max dir */ 0, RES-1,
1204 /* min,max speed */ 10, 50,
1205 /* min,max life */ 10, 2*(FPS+15)
1206 );
1207
1208 }
1209 break;
1210 /* Shots related to a player. */
1211
1212 case OBJ_MINE:
1213 case OBJ_HEAT_SHOT:
1214 case OBJ_TORPEDO:
1215 case OBJ_SMART_SHOT:
1216 case OBJ_CANNON_SHOT:
1217 if (shot->mass == 0) {
1218 break;
1219 }
1220
1221 status = GRAVITY;
1222 if (shot->type == OBJ_MINE) {
1223 status |= COLLISIONSHOVE;
1224 }
1225 if (BIT(shot->status, FROMCANNON)) {
1226 status |= FROMCANNON;
1227 }
1228
1229 if (BIT(shot->mods.nuclear, NUCLEAR)) {
1230 sound_play_all(NUKE_EXPLOSION_SOUND);
1231 }
1232 else if (BIT(shot->type, OBJ_MINE)) {
1233 sound_play_sensors(shot->pos.x, shot->pos.y, MINE_EXPLOSION_SOUND);
1234 } else {
1235 sound_play_sensors(shot->pos.x, shot->pos.y, OBJECT_EXPLOSION_SOUND);
1236 }
1237
1238 if (BIT(shot->mods.warhead, CLUSTER)) {
1239 type = OBJ_SHOT;
1240 if (shot->id != NO_ID) {
1241 player *pl = Players[GetInd[shot->id]];
1242 color = pl->color;
1243 }
1244 else {
1245 color = WHITE;
1246 }
1247 mass = ShotsMass;
1248 mass *= 3;
1249 modv = 1 << shot->mods.velocity;
1250 num_modv = 4;
1251 if (BIT(shot->mods.nuclear, NUCLEAR)) {
1252 modv *= 4.0f;
1253 num_modv = 1;
1254 }
1255 life_modv = modv * 0.20f;
1256 speed_modv = 1.0f / modv;
1257 intensity = (int)CLUSTER_MASS_SHOTS(shot->mass);
1258 } else {
1259 type = OBJ_DEBRIS;
1260 color = RED;
1261 mass = DEBRIS_MASS;
1262 modv = 1;
1263 num_modv = 1;
1264 life_modv = modv;
1265 speed_modv = modv;
1266 if (shot->type == OBJ_MINE) {
1267 intensity = 512;
1268 } else {
1269 intensity = 32;
1270 }
1271 /*
1272 * Writing it like this:
1273 * num_modv /= (shot->mods.mini + 1);
1274 * triggers a bug in HP C A.09.19.
1275 */
1276 num_modv = num_modv / ((DFLOAT)(unsigned)shot->mods.mini + 1.0f);
1277 }
1278
1279 if (BIT(shot->mods.nuclear, NUCLEAR)) {
1280 DFLOAT nuke_factor;
1281 if (shot->type == OBJ_MINE) {
1282 nuke_factor = NUKE_MINE_EXPL_MULT * shot->mass / MINE_MASS;
1283 } else {
1284 nuke_factor = NUKE_SMART_EXPL_MULT * shot->mass / MISSILE_MASS;
1285 }
1286 nuke_factor = (nuke_factor * (shot->mods.mini + 1)) / SHOT_MULT(shot);
1287 intensity = (int)(intensity * nuke_factor);
1288 }
1289
1290 if (BIT(shot->mods.warhead, IMPLOSION)) {
1291 /*intensity >>= 1;*/
1292 mass = -mass;
1293 }
1294
1295 if (BIT(shot->type, OBJ_TORPEDO|OBJ_HEAT_SHOT|OBJ_SMART_SHOT)) {
1296 intensity /= (1 + shot->mods.power);
1297 }
1298
1299 Make_debris(
1300 /* pos.x, pos.y */ shot->prevpos.x, shot->prevpos.y,
1301 /* vel.x, vel.y */ shot->vel.x, shot->vel.y,
1302 /* owner id */ shot->id,
1303 /* owner team */ shot->team,
1304 /* kind */ type,
1305 /* mass */ mass,
1306 /* status */ status,
1307 /* color */ color,
1308 /* radius */ 6,
1309 /* min,max debris */ (int)(0.20f * intensity * num_modv),
1310 (int)(0.30f * intensity * num_modv),
1311 /* min,max dir */ 0, RES-1,
1312 /* min,max speed */ 20 * speed_modv,
1313 (intensity >> 2) * speed_modv,
1314 /* min,max life */ (int)(8 * life_modv),
1315 (int)((intensity >> 1) * life_modv)
1316 );
1317 break;
1318
1319 case OBJ_SHOT:
1320 if (shot->id == NO_ID
1321 || BIT(shot->status, FROMCANNON)
1322 || BIT(shot->mods.warhead, CLUSTER)) {
1323 break;
1324 }
1325 pl = Players[GetInd[shot->id]];
1326 if (--pl->shots <= 0) {
1327 pl->shots = 0;
1328 }
1329 break;
1330
1331 /* Special items. */
1332 case OBJ_ITEM:
1333
1334 switch (shot->info) {
1335
1336 case ITEM_MISSILE:
1337 if (shot->life == 0 && shot->color != WHITE) {
1338 shot->color = WHITE;
1339 shot->life = FPS * WARN_TIME;
1340 return;
1341 }
1342 if (shot->life == 0 && rfrac() < rogueHeatProb) {
1343 addHeat = 1;
1344 }
1345 break;
1346
1347 case ITEM_MINE:
1348 if (!shot->life && shot->color != WHITE) {
1349 shot->color = WHITE;
1350 shot->life = FPS * WARN_TIME;
1351 return;
1352 }
1353 if (shot->life == 0 && rfrac() < rogueMineProb) {
1354 addMine = 1;
1355 }
1356 break;
1357 }
1358
1359 World.items[shot->info].num--;
1360
1361 break;
1362
1363 default:
1364 xpprintf("%s Delete_shot(): Unkown shot type %d.\n",
1365 showtime(), shot->type);
1366 printf("%s Delete_shot(): Unkown shot type %d.\n",
1367 showtime(), shot->type);
1368 break;
1369 }
1370
1371 Cell_remove_object(shot);
1372 shot->life = 0;
1373 shot->type = 0;
1374 shot->mass = 0;
1375
1376 Object_free_ind(ind);
1377
1378 if (addMine | addHeat) {
1379 CLEAR_MODS(mods);
1380 if (BIT(World.rules->mode, ALLOW_CLUSTERS) && (rfrac() <= 0.333f)) {
1381 SET_BIT(mods.warhead, CLUSTER);
1382 }
1383 if (BIT(World.rules->mode, ALLOW_MODIFIERS) && (rfrac() <= 0.333f)) {
1384 SET_BIT(mods.warhead, IMPLOSION);
1385 }
1386 if (BIT(World.rules->mode, ALLOW_MODIFIERS)) {
1387 mods.velocity = (int)(rfrac() * (MODS_VELOCITY_MAX + 1));
1388 }
1389 if (BIT(World.rules->mode, ALLOW_MODIFIERS)) {
1390 mods.power = (int)(rfrac() * (MODS_POWER_MAX + 1));
1391 }
1392 if (addMine) {
1393 long gravity_status = ((rfrac() < 0.5f) ? GRAVITY : 0);
1394 Place_general_mine(-1, TEAM_NOT_SET, gravity_status,
1395 shot->pos.x, shot->pos.y,
1396 0.0, 0.0, mods);
1397 }
1398 else if (addHeat) {
1399 Fire_general_shot(-1, TEAM_NOT_SET, 0,
1400 shot->pos.x, shot->pos.y,
1401 OBJ_HEAT_SHOT, (int)(rfrac() * RES),
1402 mods, -1);
1403 }
1404 }
1405 else if (addBall) {
1406 ball = BALL_PTR(shot);
1407 Make_treasure_ball(ball->treasure);
1408 }
1409 }
1410
Fire_laser(int ind)1411 void Fire_laser(int ind)
1412 {
1413 player *pl = Players[ind];
1414 DFLOAT x, y;
1415
1416 if (pl->item[ITEM_LASER] > pl->num_pulses
1417 && pl->velocity < PULSE_SPEED - PULSE_SAMPLE_DISTANCE) {
1418 if (pl->fuel.sum <= -ED_LASER) {
1419 CLR_BIT(pl->used, HAS_LASER);
1420 } else {
1421 x = pl->pos.x + pl->ship->m_gun[pl->dir].x + pl->vel.x;
1422 y = pl->pos.y + pl->ship->m_gun[pl->dir].y + pl->vel.y;
1423 x = WRAP_XPIXEL(x);
1424 y = WRAP_YPIXEL(y);
1425 if (x >= 0 && x < World.width && y >= 0 && y < World.height) {
1426 Fire_general_laser(ind, pl->team, x, y, pl->dir, pl->mods);
1427 }
1428 }
1429 }
1430 }
1431
Fire_general_laser(int ind,unsigned short team,DFLOAT x,DFLOAT y,int dir,modifiers mods)1432 void Fire_general_laser(int ind, unsigned short team, DFLOAT x, DFLOAT y,
1433 int dir, modifiers mods)
1434 {
1435 player *pl = ((ind == -1) ? NULL : Players[ind]);
1436 pulse_t *pulse;
1437 int life;
1438
1439 if (pl) {
1440 Add_fuel(&(pl->fuel), (long)ED_LASER);
1441 sound_play_sensors(x, y, FIRE_LASER_SOUND);
1442 life = (int)PULSE_LIFE(pl->item[ITEM_LASER]);
1443 } else {
1444 life = (int)PULSE_LIFE(CANNON_PULSES);
1445 }
1446
1447 if (NumPulses >= MAX_TOTAL_PULSES) {
1448 return;
1449 }
1450 Pulses[NumPulses] = (pulse_t *)malloc(sizeof(pulse_t));
1451 if (Pulses[NumPulses] == NULL) {
1452 return;
1453 }
1454
1455 pulse = Pulses[NumPulses];
1456 pulse->id = (pl ? pl->id : NO_ID);
1457 pulse->team = team;
1458 pulse->dir = dir;
1459 pulse->len = PULSE_LENGTH;
1460 pulse->life = life;
1461 pulse->mods = mods;
1462 pulse->refl = false;
1463 pulse->pos.x = x - PULSE_SPEED * tcos(dir);
1464 pulse->pos.y = y - PULSE_SPEED * tsin(dir);
1465 NumPulses++;
1466 if (pl)
1467 pl->num_pulses++;
1468 }
1469
1470
Move_ball(int ind)1471 void Move_ball(int ind)
1472 {
1473 #ifdef ORIGINAL_BALL
1474
1475 /*
1476 * This is the original ball code from XPilot versions 2.0 till 3.3.1.
1477 * The `feature' which some people got dissatisfied with
1478 * is that trying to connect to a fast moving ball may result
1479 * in being launched with high speed into a wall.
1480 * Some like that feature reasoning that making everything
1481 * easy is boring. Hence keeping the old code around.
1482 * It can be enabled by adding -DORIGINAL_BALL to the compilation flags.
1483 */
1484
1485 ballobject *ball = BALL_IND(ind);
1486 player *pl = Players[ GetInd[ball->id] ];
1487 vector F;
1488 const DFLOAT k = 10.0,
1489 a = 0.01,
1490 l = Wrap_length(pl->pos.x - ball->pos.x,
1491 pl->pos.y - ball->pos.y),
1492 c = k * (1.0 - ballConnectorLength / l)
1493 - a * ABS(ball->length - l) * (ball->length - l);
1494
1495 if (l > ballConnectorLength * (1.00 + maxBallConnectorRatio)
1496 || l < ballConnectorLength * (1.00 - maxBallConnectorRatio)) {
1497 Detach_ball(GetInd[ball->id], ind);
1498 return;
1499 }
1500
1501 F.x = WRAP_DX(pl->pos.x - ball->pos.x) * c;
1502 F.y = WRAP_DY(pl->pos.y - ball->pos.y) * c;
1503
1504 pl->vel.x -= F.x/pl->mass;
1505 pl->vel.y -= F.y/pl->mass;
1506
1507 ball->vel.x += F.x/ball->mass;
1508 ball->vel.y += F.y/ball->mass;
1509
1510 ball->length = l;
1511
1512 #else /* ORIGINAL_BALL */
1513
1514 /*
1515 * The new ball movement code since XPilot version 3.4.0 as made
1516 * by Bretton Wade. The code was submitted in context diff format
1517 * by Mark Boyns. Here is a an excerpt from a post in
1518 * rec.games.computer.xpilot by Bretton Wade dated 27 Jun 1995:
1519 *
1520 * If I'm not mistaken (not having looked very closely at the code
1521 * because I wasn't sure what it was trying to do), the original move_ball
1522 * routine was trying to model a Hook's law spring, but squared the
1523 * deformation term, which would lead to exagerated behavior as the spring
1524 * stretched too far. Not really a divide by zero, but effectively producing
1525 * large numbers.
1526 *
1527 * When I coded up the spring myself, I found that I could recreate the
1528 * effect by using a VERY strong spring. This can be defeated, however, by
1529 * damping. Specifically, If you compute the critical damping factor, then
1530 * you could have the cable always be the correct length. This makes me
1531 * wonder how to decide when the cable snaps.
1532 *
1533 * I chose a relatively strong spring, and a small damping factor, to make
1534 * for a nice realistic bounce when you grab at the treasure. It also gives a
1535 * fairley close approximation to the "normal" feel of the treasure.
1536 *
1537 * I modeled the cable as having zero mass, or at least insignificant mass as
1538 * compared to the ship and ball. This greatly simplifies the math, and leads
1539 * to the conclusion that there will be no change in velocity when the cable
1540 * breaks. You can check this by integrating the momentum along the cable,
1541 * and the ship or ball.
1542 *
1543 * If you assume that the cable snaps in the middle, then half of the
1544 * potential energy goes to each object attached. However, as you said, the
1545 * total momentum of the system cannot change. Because the weight of the
1546 * cable is small, the vast majority of the potential energy will become
1547 * heat. I've had two physicists verify this for me, and they both worked
1548 * really hard on the problem because they found it interesting.
1549 *
1550 * End of post.
1551 *
1552 * Changes since then:
1553 *
1554 * Comment from people was that the string snaps too soon.
1555 * Changed the value (max_spring_ratio) at which the string snaps
1556 * from 0.25 to 0.30. Not sure if that helps enough, or too much.
1557 */
1558
1559 ballobject *ball = BALL_IND(ind);
1560 player *pl = Players[ GetInd[ball->id] ];
1561 vector D;
1562 DFLOAT length, force, ratio, accell, cosine;
1563 DFLOAT pl_damping, ball_damping;
1564 /* const DFLOAT k = 1500.0, b = 2.0; */
1565 /* const DFLOAT max_spring_ratio = 0.30; */
1566
1567 /* compute the normalized vector between the ball and the player */
1568 D.x = WRAP_DX(pl->pos.x - ball->pos.x);
1569 D.y = WRAP_DY(pl->pos.y - ball->pos.y);
1570 length = VECTOR_LENGTH(D);
1571 if (length > 0.0) {
1572 D.x /= length;
1573 D.y /= length;
1574 }
1575 else
1576 D.x = D.y = 0.0;
1577
1578 /* compute the ratio for the spring action */
1579 ratio = (ballConnectorLength - length) / ballConnectorLength;
1580
1581 /* compute force by spring for this length */
1582 force = ballConnectorSpringConstant * ratio;
1583
1584 /* If we have string-style connectors then it is allowed to be
1585 * shorted than its natural length. */
1586 if (connectorIsString && ratio > 0.0)
1587 return;
1588
1589 /* if the tether is too long or too short, release it */
1590 if (ABS(ratio) > maxBallConnectorRatio) {
1591 Detach_ball(GetInd[ball->id], ind);
1592 return;
1593 }
1594 ball->length = length;
1595
1596 /* compute damping for player */
1597 cosine = (pl->vel.x * D.x) + (pl->vel.y * D.y);
1598 pl_damping = -ballConnectorDamping * cosine;
1599
1600 /* compute damping for ball */
1601 cosine = (ball->vel.x * -D.x) + (ball->vel.y * -D.y);
1602 ball_damping = -ballConnectorDamping * cosine;
1603
1604 /* compute accelleration for player, assume t = 1 */
1605 accell = (force + pl_damping + ball_damping) / pl->mass;
1606 pl->vel.x += D.x * accell;
1607 pl->vel.y += D.y * accell;
1608
1609 /* compute accelleration for ball, assume t = 1 */
1610 accell = (force + ball_damping + pl_damping) / ball->mass;
1611 ball->vel.x += -D.x * accell;
1612 ball->vel.y += -D.y * accell;
1613
1614 #endif /* ORIGINAL_BALL */
1615 }
1616
1617
Move_smart_shot(int ind)1618 void Move_smart_shot(int ind)
1619 {
1620 missileobject *shot = MISSILE_IND(ind);
1621 player *pl;
1622 int angle, theta;
1623 DFLOAT range = 0.0;
1624 DFLOAT acc;
1625 DFLOAT x_dif = 0.0;
1626 DFLOAT y_dif = 0.0;
1627 DFLOAT shot_speed;
1628
1629 if (shot->type == OBJ_TORPEDO) {
1630 torpobject *torp = TORP_PTR(shot);
1631 if (BIT(torp->mods.nuclear, NUCLEAR)) {
1632 acc = (torp->info++ < NUKE_SPEED_TIME) ? NUKE_ACC : 0.0;
1633 } else {
1634 acc = (torp->info++ < TORPEDO_SPEED_TIME) ? TORPEDO_ACC : 0.0;
1635 }
1636 acc *= (1 + (torp->mods.power * MISSILE_POWER_SPEED_FACT));
1637 if (torp->spread_left-- <= 0) {
1638 torp->acc.x = 0;
1639 torp->acc.y = 0;
1640 }
1641 torp->vel.x += acc * tcos(torp->missile_dir);
1642 torp->vel.y += acc * tsin(torp->missile_dir);
1643 return;
1644 }
1645
1646 acc = SMART_SHOT_ACC;
1647
1648 if (shot->type == OBJ_HEAT_SHOT) {
1649 acc = SMART_SHOT_ACC * HEAT_SPEED_FACT;
1650 if (shot->info >= 0) {
1651 /* Get player and set min to distance */
1652 pl = Players[ GetInd[shot->info] ];
1653 range = Wrap_length(pl->pos.x + pl->ship->engine[pl->dir].x
1654 - shot->pos.x,
1655 pl->pos.y + pl->ship->engine[pl->dir].y
1656 - shot->pos.y);
1657 } else {
1658 /* No player. Number of moves so that new target is searched */
1659 pl = 0;
1660 shot->count = HEAT_WIDE_TIMEOUT + HEAT_WIDE_ERROR;
1661 }
1662 if (pl && BIT(pl->status, THRUSTING)) {
1663 /*
1664 * Target is thrusting,
1665 * set number to moves to correct error value
1666 */
1667 if (range < HEAT_CLOSE_RANGE) {
1668 shot->count = HEAT_CLOSE_ERROR;
1669 } else if (range < HEAT_MID_RANGE) {
1670 shot->count = HEAT_MID_ERROR;
1671 } else {
1672 shot->count = HEAT_WIDE_ERROR;
1673 }
1674 } else {
1675 shot->count++;
1676 /* Look for new target */
1677 if ((range < HEAT_CLOSE_RANGE
1678 && shot->count > HEAT_CLOSE_TIMEOUT + HEAT_CLOSE_ERROR)
1679 || (range < HEAT_MID_RANGE
1680 && shot->count > HEAT_MID_TIMEOUT + HEAT_MID_ERROR)
1681 || shot->count > HEAT_WIDE_TIMEOUT + HEAT_WIDE_ERROR) {
1682 DFLOAT l;
1683 int i;
1684
1685 range = HEAT_RANGE * (shot->count/HEAT_CLOSE_TIMEOUT);
1686 for (i=0; i<NumPlayers; i++) {
1687 player *p = Players[i];
1688
1689 if (!BIT(p->status, THRUSTING))
1690 continue;
1691
1692 l = Wrap_length(p->pos.x + p->ship->engine[p->dir].x
1693 - shot->pos.x,
1694 p->pos.y + p->ship->engine[p->dir].y
1695 - shot->pos.y);
1696 /*
1697 * After burners can be detected easier;
1698 * so scale the length:
1699 */
1700 l *= MAX_AFTERBURNER + 1 - p->item[ITEM_AFTERBURNER];
1701 l /= MAX_AFTERBURNER + 1;
1702 if (BIT(p->have, HAS_AFTERBURNER))
1703 l *= 16 - p->item[ITEM_AFTERBURNER];
1704 if (l < range) {
1705 shot->info = Players[i]->id;
1706 range = l;
1707 shot->count =
1708 l < HEAT_CLOSE_RANGE ?
1709 HEAT_CLOSE_ERROR : l < HEAT_MID_RANGE ?
1710 HEAT_MID_ERROR : HEAT_WIDE_ERROR;
1711 pl = p;
1712 }
1713 }
1714 }
1715 }
1716 if (shot->info < 0)
1717 return;
1718 /*
1719 * Heat seekers cannot fly exactly, if target is far away or thrust
1720 * isn't active. So simulate the error:
1721 */
1722 x_dif = (int)(rfrac() * 4 * shot->count);
1723 y_dif = (int)(rfrac() * 4 * shot->count);
1724
1725 }
1726 else if (shot->type == OBJ_SMART_SHOT) {
1727 smartobject *smart = SMART_PTR(shot);
1728
1729 if (BIT(smart->status, CONFUSED)
1730 && (!(frame_loops % CONFUSED_UPDATE_GRANULARITY)
1731 || smart->count == CONFUSED_TIME)) {
1732
1733 if (smart->count) {
1734 smart->info = Players[(int)(rfrac() * NumPlayers)]->id;
1735 smart->count--;
1736 } else {
1737 CLR_BIT(smart->status, CONFUSED);
1738
1739 /* range is percentage from center to periphery of ecm burst */
1740 range = (ECM_DISTANCE - smart->ecm_range) / ECM_DISTANCE;
1741 range *= 100.0;
1742
1743 /*
1744 * range% lock%
1745 * 100 100
1746 * 50 75
1747 * 0 50
1748 */
1749 if ((int)(rfrac() * 100) <= ((int)(range/2)+50)) {
1750 smart->info = smart->new_info;
1751 }
1752 }
1753 }
1754 pl = Players[GetInd[shot->info]];
1755 }
1756 else {
1757 /*NOTREACHED*/
1758 return;
1759 }
1760
1761 /*
1762 * Use a little look ahead to fly more exact
1763 */
1764 acc *= (1 + (shot->mods.power * MISSILE_POWER_SPEED_FACT));
1765 if ((shot_speed = VECTOR_LENGTH(shot->vel)) < 1) shot_speed = 1;
1766 range = Wrap_length(pl->pos.x - shot->pos.x, pl->pos.y - shot->pos.y);
1767 x_dif += pl->vel.x * (range / shot_speed);
1768 y_dif += pl->vel.y * (range / shot_speed);
1769 theta = (int)Wrap_findDir(pl->pos.x + x_dif - shot->pos.x,
1770 pl->pos.y + y_dif - shot->pos.y);
1771
1772 {
1773 DFLOAT x, y, vx, vy;
1774 int i, xi, yi, j, freemax, k, foundw;
1775 static struct {
1776 int dx, dy;
1777 } sur[8] = {
1778 {1,0}, {1,1}, {0,1}, {-1,1}, {-1,0}, {-1,-1}, {0,-1}, {1,-1}
1779 };
1780
1781 #define BLOCK_PARTS 2
1782 vx = shot->vel.x;
1783 vy = shot->vel.y;
1784 x = shot_speed / (BLOCK_SZ*BLOCK_PARTS);
1785 vx /= x; vy /= x;
1786 x = shot->pos.x; y = shot->pos.y;
1787 foundw = 0;
1788
1789 for (i = SMART_SHOT_LOOK_AH; i > 0 && foundw == 0; i--) {
1790 xi = (int)((x += vx) / BLOCK_SZ);
1791 yi = (int)((y += vy) / BLOCK_SZ);
1792 if (BIT(World.rules->mode, WRAP_PLAY)) {
1793 if (xi < 0) xi += World.x;
1794 else if (xi >= World.x) xi -= World.x;
1795 if (yi < 0) yi += World.y;
1796 else if (yi >= World.y) yi -= World.y;
1797 }
1798 if (xi < 0 || xi >= World.x || yi < 0 || yi >= World.y)
1799 break;
1800
1801 switch(World.block[xi][yi]) {
1802 case TARGET:
1803 case TREASURE:
1804 case FUEL:
1805 case FILLED:
1806 case REC_LU:
1807 case REC_RU:
1808 case REC_LD:
1809 case REC_RD:
1810 case CANNON:
1811 if (range > (SMART_SHOT_LOOK_AH-i)*(BLOCK_SZ/BLOCK_PARTS)) {
1812 if (shot_speed > SMART_SHOT_MIN_SPEED)
1813 shot_speed -= acc * (SMART_SHOT_DECFACT+1);
1814 }
1815 foundw = 1;
1816 }
1817 }
1818
1819 i = ((int)(shot->missile_dir * 8 / RES)&7) + 8;
1820 xi = OBJ_X_IN_BLOCKS(shot);
1821 yi = OBJ_Y_IN_BLOCKS(shot);
1822
1823 for (j=2, angle=-1, freemax=0; j>=-2; --j) {
1824 int si, xt, yt;
1825
1826 for (si=1, k=0; si >= -1; --si) {
1827 xt = xi + sur[(i+j+si)&7].dx;
1828 yt = yi + sur[(i+j+si)&7].dy;
1829
1830 if (xt >= 0 && xt < World.x && yt >= 0 && yt < World.y)
1831 switch (World.block[xt][yt]) {
1832 case TARGET:
1833 case TREASURE:
1834 case FUEL:
1835 case FILLED:
1836 case REC_LU:
1837 case REC_RU:
1838 case REC_LD:
1839 case REC_RD:
1840 case CANNON:
1841 if (!si)
1842 k = -32;
1843 break;
1844 default:
1845 ++k;
1846 break;
1847 }
1848 }
1849 if (k > freemax
1850 || (k == freemax
1851 && ((j == -1 && (rfrac() < 0.5f)) || j == 0 || j == 1))) {
1852 freemax = k > 2 ? 2 : k;
1853 angle = i + j;
1854 }
1855
1856 if (k == 3 && !j) {
1857 angle = -1;
1858 break;
1859 }
1860 }
1861
1862 if (angle >= 0) {
1863 i = angle&7;
1864 theta = (int)Wrap_findDir((yi + sur[i].dy) * BLOCK_SZ
1865 - (shot->pos.y + 2 * shot->vel.y),
1866 (xi + sur[i].dx) * BLOCK_SZ
1867 - (shot->pos.x - 2 * shot->vel.x));
1868 #ifdef SHOT_EXTRA_SLOWDOWN
1869 if (!foundw && range > (SHOT_LOOK_AH-i) * BLOCK_SZ) {
1870 if (shot_speed
1871 > (SMART_SHOT_MIN_SPEED + SMART_SHOT_MAX_SPEED)/2)
1872 shot_speed -= SMART_SHOT_DECC+SMART_SHOT_ACC;
1873 }
1874 #endif
1875 }
1876 }
1877 angle = theta;
1878
1879 if (angle < 0)
1880 angle += RES;
1881 angle %= RES;
1882
1883 if (angle < shot->missile_dir)
1884 angle += RES;
1885 angle = angle - shot->missile_dir - RES/2;
1886
1887 if (angle < 0) {
1888 shot->missile_dir += (u_byte)(((-angle < shot->turnspeed)
1889 ? -angle
1890 : shot->turnspeed));
1891 }
1892 else {
1893 shot->missile_dir -= (u_byte)(((angle < shot->turnspeed)
1894 ? angle
1895 : shot->turnspeed));
1896 }
1897
1898 shot->missile_dir = MOD2(shot->missile_dir, RES); /* NOTE!!!! */
1899
1900 if (shot_speed < shot->max_speed)
1901 shot_speed += acc;
1902
1903 /* shot->velocity = MIN(shot->velocity, shot->max_speed); */
1904
1905 shot->vel.x = tcos(shot->missile_dir) * shot_speed;
1906 shot->vel.y = tsin(shot->missile_dir) * shot_speed;
1907 }
1908
Move_mine(int ind)1909 void Move_mine(int ind)
1910 {
1911 mineobject *mine = MINE_IND(ind);
1912
1913 if (BIT(mine->status, CONFUSED) && --mine->count <= 0)
1914 CLR_BIT(mine->status, CONFUSED);
1915
1916 if (BIT(mine->status, OWNERIMMUNE) && mine->info) {
1917 if (--mine->info <= 0)
1918 CLR_BIT(mine->status, OWNERIMMUNE);
1919 }
1920
1921 if (mine->mods.mini && mine->spread_left-- <= 0) {
1922 mine->acc.x = 0;
1923 mine->acc.y = 0;
1924 }
1925 }
1926
1927
1928