1 /*
2 * XPilot NG, a multiplayer space war game.
3 *
4 * Copyright (C) 1991-2001 by
5 *
6 * Bj�rn Stabell <bjoern@xpilot.org>
7 * Ken Ronny Schouten <ken@xpilot.org>
8 * Bert Gijsbers <bert@xpilot.org>
9 * Dick Balaska <dick@xpilot.org>
10 * Kimiko Koopman <kimiko@xpilot.org>
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 */
26
27 #include "xpserver.h"
28
29 static int Cannon_select_weapon(cannon_t *cannon);
30 static void Cannon_aim(cannon_t *cannon, int weapon,
31 player_t **pl_p, int *dir);
32 static void Cannon_fire(cannon_t *cannon, int weapon, player_t *pl, int dir);
33 static int Cannon_in_danger(cannon_t *cannon);
34 static int Cannon_select_defense(cannon_t *cannon);
35 static void Cannon_defend(cannon_t *cannon, int defense);
36
37 /* the items that are useful to cannons.
38 these are the items that cannon get 'for free' once in a while.
39 cannons can get other items, but only by picking them up or
40 stealing them from players. */
41 long CANNON_USE_ITEM = (ITEM_BIT_FUEL|ITEM_BIT_WIDEANGLE
42 |ITEM_BIT_REARSHOT|ITEM_BIT_AFTERBURNER
43 |ITEM_BIT_SENSOR|ITEM_BIT_TRANSPORTER
44 |ITEM_BIT_TANK|ITEM_BIT_MINE
45 |ITEM_BIT_ECM|ITEM_BIT_LASER
46 |ITEM_BIT_EMERGENCY_THRUST|ITEM_BIT_ARMOR
47 |ITEM_BIT_TRACTOR_BEAM|ITEM_BIT_MISSILE
48 |ITEM_BIT_PHASING);
49
Cannon_update(bool tick)50 void Cannon_update(bool tick)
51 {
52 int i;
53
54 for (i = 0; i < Num_cannons(); i++) {
55 cannon_t *c = Cannon_by_index(i);
56
57 if (c->dead_ticks > 0) {
58 if ((c->dead_ticks -= timeStep) <= 0)
59 World_restore_cannon(c);
60 continue;
61 }
62
63 /*
64 * Call cannon "AI" routines only once per tick.
65 */
66 if (tick) {
67 if (rfrac() < 0.65)
68 Cannon_check_defense(c);
69
70 if (!BIT(c->used, HAS_EMERGENCY_SHIELD)
71 && !BIT(c->used, USES_PHASING_DEVICE)
72 && (c->damaged <= 0)
73 && (c->tractor_count <= 0)
74 && rfrac() * 16 < 1)
75 Cannon_check_fire(c);
76
77 if (options.itemProbMult > 0
78 && options.cannonItemProbMult > 0) {
79 int item = (int)(rfrac() * NUM_ITEMS);
80 /* this gives the cannon an item about once every minute */
81 if (world->items[item].cannonprob > 0
82 && options.cannonItemProbMult > 0
83 && (int)(rfrac() * (60 * 12))
84 < (options.cannonItemProbMult
85 * world->items[item].cannonprob))
86 Cannon_add_item(c, item, (item == ITEM_FUEL
87 ? ENERGY_PACK_FUEL : 1));
88 }
89 }
90
91 if ((c->damaged -= timeStep) <= 0)
92 c->damaged = 0;
93 if (c->tractor_count > 0) {
94 player_t *tpl = Player_by_id(c->tractor_target_id);
95
96 if (tpl == NULL) {
97 c->tractor_target_id = NO_ID;
98 c->tractor_count = 0;
99 }
100 else if ((Wrap_length(tpl->pos.cx - c->pos.cx,
101 tpl->pos.cy - c->pos.cy)
102 < TRACTOR_MAX_RANGE(c->item[ITEM_TRACTOR_BEAM]) * CLICK)
103 && Player_is_alive(tpl)) {
104 General_tractor_beam(c->id, c->pos,
105 c->item[ITEM_TRACTOR_BEAM],
106 tpl, c->tractor_is_pressor);
107 if ((c->tractor_count -= timeStep) <= 0)
108 c->tractor_count = 0;
109 } else
110 c->tractor_count = 0;
111 }
112 if (c->emergency_shield_left > 0) {
113 if ((c->emergency_shield_left -= timeStep) <= 0) {
114 CLR_BIT(c->used, HAS_EMERGENCY_SHIELD);
115 sound_play_sensors(c->pos, EMERGENCY_SHIELD_OFF_SOUND);
116 }
117 }
118 if (c->phasing_left > 0) {
119 if ((c->phasing_left -= timeStep) <= 0) {
120 CLR_BIT(c->used, USES_PHASING_DEVICE);
121 sound_play_sensors(c->pos, PHASING_OFF_SOUND);
122 }
123 }
124 }
125 }
126
127
128
129 /* adds the given amount of an item to the cannon's inventory. the number of
130 tanks is taken to be 1. amount is then the amount of fuel in that tank.
131 fuel is given in 'units', but is stored in fuelpacks. */
Cannon_add_item(cannon_t * c,int item_type,int amount)132 void Cannon_add_item(cannon_t *c, int item_type, int amount)
133 {
134 switch (item_type) {
135 case ITEM_TANK:
136 c->item[ITEM_TANK] += amount;
137 LIMIT(c->item[ITEM_TANK], 0, world->items[ITEM_TANK].limit);
138 /* FALLTHROUGH */
139 case ITEM_FUEL:
140 c->item[ITEM_FUEL] += (int)(amount / ENERGY_PACK_FUEL + 0.5);
141 LIMIT(c->item[ITEM_FUEL],
142 0,
143 (int)(world->items[ITEM_FUEL].limit / ENERGY_PACK_FUEL + 0.5));
144 break;
145 default:
146 c->item[item_type] += amount;
147 LIMIT(c->item[item_type], 0, world->items[item_type].limit);
148 break;
149 }
150 }
151
Cannon_get_initial_item(cannon_t * c,Item_t i)152 static inline int Cannon_get_initial_item(cannon_t *c, Item_t i)
153 {
154 int init_amount;
155
156 init_amount = c->initial_items[i];
157 if (init_amount < 0)
158 init_amount = world->items[i].cannon_initial;
159
160 return init_amount;
161 }
162
Cannon_throw_items(cannon_t * c)163 void Cannon_throw_items(cannon_t *c)
164 {
165 int i, dir;
166 itemobject_t *item;
167 double velocity;
168
169 for (i = 0; i < NUM_ITEMS; i++) {
170 if (i == ITEM_FUEL)
171 continue;
172 c->item[i] -= Cannon_get_initial_item(c, (Item_t)i);
173 while (c->item[i] > 0) {
174 int amount = world->items[i].max_per_pack
175 - (int)(rfrac() * (1 + world->items[i].max_per_pack
176 - world->items[i].min_per_pack));
177 LIMIT(amount, 0, c->item[i]);
178 if (rfrac() < (options.dropItemOnKillProb * CANNON_DROP_ITEM_PROB)
179 && (item = ITEM_PTR(Object_allocate())) != NULL) {
180
181 item->type = OBJ_ITEM;
182 item->item_type = i;
183 item->color = RED;
184 item->obj_status = GRAVITY;
185 dir = (int)(c->dir
186 - (CANNON_SPREAD * 0.5)
187 + (rfrac() * CANNON_SPREAD));
188 dir = MOD2(dir, RES);
189 item->id = NO_ID;
190 item->team = TEAM_NOT_SET;
191 Object_position_init_clpos(OBJ_PTR(item), c->pos);
192 velocity = rfrac() * 6;
193 item->vel.x = tcos(dir) * velocity;
194 item->vel.y = tsin(dir) * velocity;
195 item->acc.x = 0;
196 item->acc.y = 0;
197 item->mass = 10;
198 item->life = 1500 + rfrac() * 512;
199 item->item_count = amount;
200 item->pl_range = ITEM_SIZE / 2;
201 item->pl_radius = ITEM_SIZE / 2;
202 world->items[i].num++;
203 Cell_add_object(OBJ_PTR(item));
204 }
205 c->item[i] -= amount;
206 }
207 }
208 }
209
210 /* initializes the given cannon at startup or after death and gives it some
211 items. */
Cannon_init(cannon_t * c)212 void Cannon_init(cannon_t *c)
213 {
214 Cannon_init_items(c);
215 c->last_change = frame_loops;
216 c->damaged = 0;
217 c->tractor_target_id = NO_ID;
218 c->tractor_count = 0;
219 c->tractor_is_pressor = false;
220 c->used = 0;
221 c->emergency_shield_left = 0;
222 c->phasing_left = 0;
223 }
224
Cannon_init_items(cannon_t * c)225 void Cannon_init_items(cannon_t *c)
226 {
227 int i;
228
229 for (i = 0; i < NUM_ITEMS; i++) {
230 c->item[i] = 0;
231 Cannon_add_item(c, i, Cannon_get_initial_item(c, (Item_t)i));
232 }
233 }
234
Cannon_check_defense(cannon_t * c)235 void Cannon_check_defense(cannon_t *c)
236 {
237 int defense = Cannon_select_defense(c);
238
239 if (defense >= 0 && Cannon_in_danger(c))
240 Cannon_defend(c, defense);
241 }
242
Cannon_check_fire(cannon_t * c)243 void Cannon_check_fire(cannon_t *c)
244 {
245 player_t *pl = NULL;
246 int dir = 0,
247 weapon = Cannon_select_weapon(c);
248
249 Cannon_aim(c, weapon, &pl, &dir);
250 if (pl)
251 Cannon_fire(c, weapon, pl, dir);
252 }
253
254 /* selects one of the available defenses. see cannon.h for descriptions. */
Cannon_select_defense(cannon_t * c)255 static int Cannon_select_defense(cannon_t *c)
256 {
257 int smartness = Cannon_get_smartness(c);
258
259 /* mode 0 does not defend */
260 if (smartness == 0)
261 return -1;
262
263 /* still protected */
264 if (BIT(c->used, HAS_EMERGENCY_SHIELD)
265 || BIT(c->used, USES_PHASING_DEVICE))
266 return -1;
267
268 if (c->item[ITEM_EMERGENCY_SHIELD])
269 return CD_EM_SHIELD;
270
271 if (c->item[ITEM_PHASING])
272 return CD_PHASING;
273
274 /* no defense available */
275 return -1;
276 }
277
278 /* checks if a cannon is about to be hit by a hazardous object.
279 mode 0 does not detect danger.
280 modes 1 - 3 use progressively more accurate detection. */
Cannon_in_danger(cannon_t * c)281 static int Cannon_in_danger(cannon_t *c)
282 {
283 const int range = 4 * BLOCK_SZ;
284 const uint32_t kill_shots = (KILLING_SHOTS) | OBJ_MINE_BIT | OBJ_SHOT_BIT
285 | OBJ_PULSE_BIT | OBJ_SMART_SHOT_BIT | OBJ_HEAT_SHOT_BIT
286 | OBJ_TORPEDO_BIT | OBJ_ASTEROID_BIT;
287 object_t *shot, **obj_list;
288 const int max_objs = 100;
289 int obj_count, i, danger = false;
290 int npx, npy, tdx, tdy;
291 int cpx = CLICK_TO_PIXEL(c->pos.cx), cpy = CLICK_TO_PIXEL(c->pos.cy);
292 int smartness = Cannon_get_smartness(c);
293
294 if (smartness == 0)
295 return false;
296
297 if (NumObjs >= options.cellGetObjectsThreshold)
298 Cell_get_objects(c->pos, range, max_objs,
299 &obj_list, &obj_count);
300 else {
301 obj_list = Obj;
302 obj_count = NumObjs;
303 }
304
305 for (i = 0; (i < obj_count) && !danger; i++) {
306 shot = obj_list[i];
307
308 if (shot->life <= 0)
309 continue;
310 if (!BIT(OBJ_TYPEBIT(shot->type), kill_shots))
311 continue;
312 if (BIT(shot->obj_status, FROMCANNON))
313 continue;
314 if (BIT(world->rules->mode, TEAM_PLAY)
315 && options.teamImmunity
316 && shot->team == c->team)
317 continue;
318
319 npx = CLICK_TO_PIXEL(shot->pos.cx);
320 npy = CLICK_TO_PIXEL(shot->pos.cy);
321 if (smartness > 1) {
322 npx += (int)shot->vel.x;
323 npy += (int)shot->vel.y;
324 if (smartness > 2) {
325 npx += (int)shot->acc.x;
326 npy += (int)shot->acc.y;
327 }
328 }
329 tdx = WRAP_DX(npx - cpx);
330 tdy = WRAP_DY(npy - cpy);
331 if (LENGTH(tdx, tdy) <= ((4.5 - smartness) * BLOCK_SZ)) {
332 danger = true;
333 break;
334 }
335 }
336
337 return danger;
338 }
339
340 /* activates the selected defense. */
Cannon_defend(cannon_t * c,int defense)341 static void Cannon_defend(cannon_t *c, int defense)
342 {
343 switch (defense) {
344 case CD_EM_SHIELD:
345 c->emergency_shield_left += 4 * 12;
346 SET_BIT(c->used, HAS_EMERGENCY_SHIELD);
347 c->item[ITEM_EMERGENCY_SHIELD]--;
348 sound_play_sensors(c->pos, EMERGENCY_SHIELD_ON_SOUND);
349 break;
350 case CD_PHASING:
351 c->phasing_left += 4 * 12;
352 SET_BIT(c->used, USES_PHASING_DEVICE);
353 c->tractor_count = 0;
354 c->item[ITEM_PHASING]--;
355 sound_play_sensors(c->pos, PHASING_ON_SOUND);
356 break;
357 default:
358 warn("Cannon_defend: Unknown defense.");
359 break;
360 }
361 }
362
363 /* selects one of the available weapons. see cannon.h for descriptions. */
Cannon_select_weapon(cannon_t * c)364 static int Cannon_select_weapon(cannon_t *c)
365 {
366 if (c->item[ITEM_MINE]
367 && rfrac() < 0.5)
368 return CW_MINE;
369 if (c->item[ITEM_MISSILE]
370 && rfrac() < 0.5)
371 return CW_MISSILE;
372 if (c->item[ITEM_LASER]
373 && (int)(rfrac() * (c->item[ITEM_LASER] + 1)))
374 return CW_LASER;
375 if (c->item[ITEM_ECM]
376 && rfrac() < 0.333)
377 return CW_ECM;
378 if (c->item[ITEM_TRACTOR_BEAM]
379 && rfrac() < 0.5)
380 return CW_TRACTORBEAM;
381 if (c->item[ITEM_TRANSPORTER]
382 && rfrac() < 0.333)
383 return CW_TRANSPORTER;
384 if ((c->item[ITEM_AFTERBURNER]
385 || c->item[ITEM_EMERGENCY_THRUST])
386 && c->item[ITEM_FUEL]
387 && (int)(rfrac() * ((c->item[ITEM_EMERGENCY_THRUST] ?
388 MAX_AFTERBURNER :
389 c->item[ITEM_AFTERBURNER]) + 3)) > 2)
390 return CW_GASJET;
391 return CW_SHOT;
392 }
393
394 /* determines in which direction to fire.
395 mode 0 fires straight ahead.
396 mode 1 in a random direction.
397 mode 2 aims at the current position of the closest player,
398 then limits that to the sector in front of the cannon,
399 then adds a small error.
400 mode 3 calculates where the player will be when the shot reaches her,
401 checks if that position is within limits and selects the player
402 who will be closest in this way.
403 the targeted player is also returned (for all modes).
404 mode 0 always fires if it sees a player.
405 modes 1 and 2 only fire if a player is within range of the selected weapon.
406 mode 3 only fires if a player will be in range when the shot is expected to hit.
407 */
Cannon_aim(cannon_t * c,int weapon,player_t ** pl_p,int * dir)408 static void Cannon_aim(cannon_t *c, int weapon, player_t **pl_p, int *dir)
409 {
410 double speed = Cannon_get_shot_speed(c);
411 double range = Cannon_get_max_shot_life(c) * speed;
412 double visualrange = (CANNON_DISTANCE
413 + 2 * c->item[ITEM_SENSOR] * BLOCK_SZ);
414 bool found = false, ready = false;
415 double closest = range;
416 int ddir, i, smartness = Cannon_get_smartness(c);
417
418 switch (weapon) {
419 case CW_MINE:
420 speed = speed * 0.5 + 0.1 * smartness;
421 range = range * 0.5 + 0.1 * smartness;
422 break;
423 case CW_LASER:
424 speed = options.pulseSpeed;
425 range = CANNON_PULSE_LIFE * speed;
426 break;
427 case CW_ECM:
428 /* smarter cannons wait a little longer before firing an ECM */
429 if (smartness > 1)
430 range = ((ECM_DISTANCE / smartness
431 + (rfrac() * (int)(ECM_DISTANCE
432 - ECM_DISTANCE / smartness))));
433 else
434 range = ECM_DISTANCE;
435 break;
436 case CW_TRACTORBEAM:
437 range = TRACTOR_MAX_RANGE(c->item[ITEM_TRACTOR_BEAM]);
438 break;
439 case CW_TRANSPORTER:
440 /* smarter cannons have a smaller chance of using a transporter when
441 target is out of range */
442 if (smartness > 2
443 || (int)(rfrac() * sqr(smartness + 1)))
444 range = TRANSPORTER_DISTANCE;
445 break;
446 case CW_GASJET:
447 if (c->item[ITEM_EMERGENCY_THRUST]) {
448 speed *= 2.0;
449 range *= 2.0;
450 }
451 break;
452 default:
453 /* no need to do anything specail for this weapon. */
454 break;
455 }
456
457 for (i = 0; i < NumPlayers && !ready; i++) {
458 player_t *pl = Player_by_index(i);
459 double tdist, tdx, tdy;
460
461 /* KHS: cannon dodgers mode: */
462 /* Cannons fire on players in any range */
463 tdx = WRAP_DCX(pl->pos.cx - c->pos.cx) / CLICK;
464 if (ABS(tdx) >= visualrange
465 && options.survivalScore == 0.0)
466 continue;
467 tdy = WRAP_DCY(pl->pos.cy - c->pos.cy) / CLICK;
468 if (ABS(tdy) >= visualrange
469 && options.survivalScore == 0.0)
470 continue;
471 tdist = LENGTH(tdx, tdy);
472 if (tdist > visualrange
473 && options.survivalScore == 0.0)
474 continue;
475
476 /* mode 3 also checks if a player is using a phasing device */
477 if (Player_is_paused(pl)
478 || (BIT(world->rules->mode, TEAM_PLAY)
479 && pl->team == c->team)
480 || ((pl->forceVisible <= 0)
481 && Player_is_cloaked(pl)
482 && (int)(rfrac() * (pl->item[ITEM_CLOAK] + 1))
483 > (int)(rfrac() * (c->item[ITEM_SENSOR] + 1)))
484 || (smartness > 2
485 && Player_is_phasing(pl)))
486 continue;
487
488 switch (smartness) {
489 case 0:
490 ready = true;
491 break;
492 default:
493 case 1:
494
495 /* KHS disable this range check, too */
496 /* in cannon dodgers */
497 if (tdist < range
498 || options.survivalScore != 0.0)
499 ready = true;
500 break;
501 case 2:
502 if (tdist < closest) {
503 double a = findDir(tdx, tdy);
504 *dir = (int) a;
505 found = true;
506 }
507 break;
508 case 3:
509 if (tdist < range) {
510 double a, t = tdist / speed; /* time */
511 int npx = (int)(pl->pos.cx
512 + pl->vel.x * t * CLICK
513 + pl->acc.x * t * t * CLICK);
514 int npy = (int)(pl->pos.cy
515 + pl->vel.y * t * CLICK
516 + pl->acc.y * t * t * CLICK);
517 int tdir;
518
519 tdx = WRAP_DCX(npx - c->pos.cx) / CLICK;
520 tdy = WRAP_DCY(npy - c->pos.cy) / CLICK;
521 a = findDir(tdx, tdy);
522 tdir = (int) a;
523 ddir = MOD2(tdir - c->dir, RES);
524 if ((ddir < (CANNON_SPREAD * 0.5)
525 || ddir > RES - (CANNON_SPREAD * 0.5))
526 && LENGTH(tdx, tdy) < closest) {
527 *dir = tdir;
528 found = true;
529 }
530 }
531 break;
532 }
533 if (found || ready) {
534 closest = tdist;
535 *pl_p = pl;
536 }
537 }
538 if (!(found || ready)) {
539 *pl_p = NULL;
540 return;
541 }
542
543 switch (smartness) {
544 case 0:
545 *dir = c->dir;
546 break;
547 default:
548 case 1:
549 *dir = c->dir;
550 *dir += (int)((rfrac() - 0.5f) * CANNON_SPREAD);
551 break;
552 case 2:
553 ddir = MOD2(*dir - c->dir, RES);
554 if (ddir > (CANNON_SPREAD * 0.5) && ddir < RES / 2)
555 *dir = (int)(c->dir + (CANNON_SPREAD * 0.5) + 3);
556 else if (ddir < RES - (CANNON_SPREAD * 0.5) && ddir > RES / 2)
557 *dir = (int)(c->dir - (CANNON_SPREAD * 0.5) - 3);
558 *dir += (int)(rfrac() * 7) - 3;
559 break;
560 case 3:
561 /* nothing to be done for mode 3 */
562 break;
563 }
564 *dir = MOD2(*dir, RES);
565 }
566
567 /* does the actual firing. also determines in which way to use weapons that
568 have more than one possible use. */
Cannon_fire(cannon_t * c,int weapon,player_t * pl,int dir)569 static void Cannon_fire(cannon_t *c, int weapon, player_t *pl, int dir)
570 {
571 modifiers_t mods;
572 bool played = false;
573 int i, smartness = Cannon_get_smartness(c);
574 double speed = Cannon_get_shot_speed(c);
575 vector_t zero_vel = { 0.0, 0.0 };
576
577 Mods_clear(&mods);
578 switch (weapon) {
579 case CW_MINE:
580 if (rfrac() < 0.25)
581 Mods_set(&mods, ModsCluster, 1);
582
583 if (rfrac() >= 0.2)
584 Mods_set(&mods, ModsImplosion, 1);
585
586 Mods_set(&mods, ModsPower,
587 (int)(rfrac() * (MODS_POWER_MAX + 1)));
588 Mods_set(&mods, ModsVelocity,
589 (int)(rfrac() * (MODS_VELOCITY_MAX + 1)));
590
591 if (rfrac() < 0.5) { /* place mine in front of cannon */
592 Place_general_mine(c->id, c->team, FROMCANNON,
593 c->pos, zero_vel, mods);
594 sound_play_sensors(c->pos, DROP_MINE_SOUND);
595 played = true;
596 } else { /* throw mine at player */
597 vector_t vel;
598
599 Mods_set(&mods, ModsMini,
600 (int)(rfrac() * MODS_MINI_MAX) + 1);
601 Mods_set(&mods, ModsSpread,
602 (int)(rfrac() * (MODS_SPREAD_MAX + 1)));
603
604 speed = speed * 0.5 + 0.1 * smartness;
605 vel.x = tcos(dir) * speed;
606 vel.y = tsin(dir) * speed;
607 Place_general_mine(c->id, c->team, GRAVITY|FROMCANNON,
608 c->pos, vel, mods);
609 sound_play_sensors(c->pos, DROP_MOVING_MINE_SOUND);
610 played = true;
611 }
612 c->item[ITEM_MINE]--;
613 break;
614 case CW_MISSILE:
615 if (rfrac() < 0.333)
616 Mods_set(&mods, ModsCluster, 1);
617
618 if (rfrac() >= 0.25)
619 Mods_set(&mods, ModsImplosion, 1);
620
621 Mods_set(&mods, ModsPower,
622 (int)(rfrac() * (MODS_POWER_MAX + 1)));
623 Mods_set(&mods, ModsVelocity,
624 (int)(rfrac() * (MODS_VELOCITY_MAX + 1)));
625
626 /* Because cannons don't have missile racks, all mini missiles
627 would be fired from the same point and appear to the players
628 as 1 missile (except heatseekers, which would appear to split
629 in midair because of navigation errors (see Move_smart_shot)).
630 Therefore, we don't minify cannon missiles.
631 mods.mini = (int)(rfrac() * MODS_MINI_MAX) + 1;
632 mods.spread = (int)(rfrac() * (MODS_SPREAD_MAX + 1));
633 */
634
635 /* smarter cannons use more advanced missile types */
636 switch ((int)(rfrac() * (1 + smartness))) {
637 default:
638 if (options.allowSmartMissiles) {
639 Fire_general_shot(c->id, c->team, c->pos,
640 OBJ_SMART_SHOT, dir, mods, pl->id);
641 sound_play_sensors(c->pos, FIRE_SMART_SHOT_SOUND);
642 played = true;
643 break;
644 }
645 /* FALLTHROUGH */
646 case 1:
647 if (options.allowHeatSeekers
648 && Player_is_thrusting(pl)) {
649 Fire_general_shot(c->id, c->team, c->pos,
650 OBJ_HEAT_SHOT, dir, mods, pl->id);
651 sound_play_sensors(c->pos, FIRE_HEAT_SHOT_SOUND);
652 played = true;
653 break;
654 }
655 /* FALLTHROUGH */
656 case 0:
657 Fire_general_shot(c->id, c->team, c->pos,
658 OBJ_TORPEDO, dir, mods, NO_ID);
659 sound_play_sensors(c->pos, FIRE_TORPEDO_SOUND);
660 played = true;
661 break;
662 }
663 c->item[ITEM_MISSILE]--;
664 break;
665 case CW_LASER:
666 /* stun and blinding lasers are very dangerous,
667 so we don't use them often */
668 if ((rfrac() * (8 - smartness)) >= 1)
669 Mods_set(&mods, ModsLaser,
670 (int)(rfrac() * (MODS_LASER_MAX + 1)));
671
672 Fire_general_laser(c->id, c->team, c->pos, dir, mods);
673 sound_play_sensors(c->pos, FIRE_LASER_SOUND);
674 played = true;
675 break;
676 case CW_ECM:
677 Fire_general_ecm(c->id, c->team, c->pos);
678 c->item[ITEM_ECM]--;
679 sound_play_sensors(c->pos, ECM_SOUND);
680 played = true;
681 break;
682 case CW_TRACTORBEAM:
683 /* smarter cannons use tractors more often and also push/pull longer */
684 c->tractor_is_pressor = (rfrac() * (smartness + 1) >= 1);
685 c->tractor_target_id = pl->id;
686 c->tractor_count = 11 + rfrac() * (3 * smartness + 1);
687 break;
688 case CW_TRANSPORTER:
689 c->item[ITEM_TRANSPORTER]--;
690 if (Wrap_length(pl->pos.cx - c->pos.cx, pl->pos.cy - c->pos.cy)
691 < TRANSPORTER_DISTANCE * CLICK) {
692 int item = -1;
693 double amount = 0.0;
694
695 Do_general_transporter(c->id, c->pos, pl, &item, &amount);
696 if (item != -1)
697 Cannon_add_item(c, item, amount);
698 } else {
699 sound_play_sensors(c->pos, TRANSPORTER_FAIL_SOUND);
700 played = true;
701 }
702 break;
703 case CW_GASJET:
704 /* use emergency thrusts to make extra big jets */
705 if ((rfrac() * (c->item[ITEM_EMERGENCY_THRUST] + 1)) >= 1) {
706 Make_debris(c->pos,
707 zero_vel,
708 NO_ID,
709 c->team,
710 OBJ_SPARK,
711 THRUST_MASS,
712 GRAVITY|FROMCANNON,
713 RED,
714 8,
715 (int)(300 + 400 * rfrac()),
716 dir - 4 * (4 - smartness),
717 dir + 4 * (4 - smartness),
718 0.1, speed * 4,
719 3.0, 20.0);
720 c->item[ITEM_EMERGENCY_THRUST]--;
721 } else {
722 Make_debris(c->pos,
723 zero_vel,
724 NO_ID,
725 c->team,
726 OBJ_SPARK,
727 THRUST_MASS,
728 GRAVITY|FROMCANNON,
729 RED,
730 8,
731 (int)(150 + 200 * rfrac()),
732 dir - 3 * (4 - smartness),
733 dir + 3 * (4 - smartness),
734 0.1, speed * 2,
735 3.0, 20.0);
736 }
737 c->item[ITEM_FUEL]--;
738 sound_play_sensors(c->pos, THRUST_SOUND);
739 played = true;
740 break;
741 case CW_SHOT:
742 default:
743 if (options.cannonFlak)
744 Mods_set(&mods, ModsCluster, 1);
745 /* smarter cannons fire more accurately and
746 can therefore narrow their bullet streams */
747 for (i = 0; i < (1 + 2 * c->item[ITEM_WIDEANGLE]); i++) {
748 int a_dir = dir
749 + (4 - smartness)
750 * (-c->item[ITEM_WIDEANGLE] + i);
751 a_dir = MOD2(a_dir, RES);
752 Fire_general_shot(c->id, c->team, c->pos,
753 OBJ_CANNON_SHOT, a_dir, mods, NO_ID);
754 }
755 /* I'm not sure cannons should use rearshots.
756 After all, they are restricted to 60 degrees when picking their
757 target. */
758 for (i = 0; i < c->item[ITEM_REARSHOT]; i++) {
759 int a_dir = (int)(dir + (RES / 2)
760 + (4 - smartness)
761 * (-((c->item[ITEM_REARSHOT] - 1) * 0.5) + i));
762 a_dir = MOD2(a_dir, RES);
763 Fire_general_shot(c->id, c->team, c->pos,
764 OBJ_CANNON_SHOT, a_dir, mods, NO_ID);
765 }
766 }
767
768 /* finally, play sound effect */
769 if (!played) {
770 sound_play_sensors(c->pos, CANNON_FIRE_SOUND);
771 }
772 }
773
Object_hits_cannon(object_t * obj,cannon_t * c)774 void Object_hits_cannon(object_t *obj, cannon_t *c)
775 {
776 if (obj->type == OBJ_ITEM) {
777 itemobject_t *item = ITEM_PTR(obj);
778
779 Cannon_add_item(c, item->item_type, item->item_count);
780 }
781 else {
782 player_t *pl = Player_by_id(obj->id);
783
784 if (!BIT(c->used, HAS_EMERGENCY_SHIELD)) {
785 if (c->item[ITEM_ARMOR] > 0)
786 c->item[ITEM_ARMOR]--;
787 else
788 Cannon_dies(c, pl);
789 }
790 }
791 }
792
Cannon_dies(cannon_t * c,player_t * pl)793 void Cannon_dies(cannon_t *c, player_t *pl)
794 {
795 vector_t zero_vel = { 0.0, 0.0 };
796
797 World_remove_cannon(c);
798 Cannon_throw_items(c);
799 Cannon_init(c);
800 sound_play_sensors(c->pos, CANNON_EXPLOSION_SOUND);
801 Make_debris(c->pos,
802 zero_vel,
803 NO_ID,
804 c->team,
805 OBJ_DEBRIS,
806 4.5,
807 GRAVITY,
808 RED,
809 6,
810 (int)(20 + 20 * rfrac()),
811 (int)(c->dir - (RES * 0.2)), (int)(c->dir + (RES * 0.2)),
812 20.0, 50.0,
813 8.0, 68.0);
814 Make_wreckage(c->pos,
815 zero_vel,
816 NO_ID,
817 c->team,
818 3.5, 23.0,
819 28.0,
820 GRAVITY,
821 10,
822 (int)(c->dir - (RES * 0.2)), (int)(c->dir + (RES * 0.2)),
823 10.0, 25.0,
824 8.0, 68.0);
825
826 if (pl) {
827 Handle_Scoring(SCORE_CANNON_KILL,pl,NULL,c,NULL);
828 }
829 }
830
831
Cannon_hitmask(cannon_t * cannon)832 hitmask_t Cannon_hitmask(cannon_t *cannon)
833 {
834 if (cannon->dead_ticks > 0)
835 return ALL_BITS;
836 if (BIT(world->rules->mode, TEAM_PLAY) && options.teamImmunity)
837 return HITMASK(cannon->team);
838 return 0;
839 }
840
Cannon_set_hitmask(int group,cannon_t * cannon)841 void Cannon_set_hitmask(int group, cannon_t *cannon)
842 {
843 assert(group == cannon->group);
844
845 P_set_hitmask(cannon->group, Cannon_hitmask(cannon));
846 }
847
848
World_restore_cannon(cannon_t * cannon)849 void World_restore_cannon(cannon_t *cannon)
850 {
851 blkpos_t blk = Clpos_to_blkpos(cannon->pos);
852 int i;
853
854 World_set_block(blk, CANNON);
855
856 for (i = 0; i < num_polys; i++) {
857 poly_t *poly = &pdata[i];
858
859 if (poly->group == cannon->group) {
860 poly->current_style = poly->style;
861 poly->update_mask = ~0;
862 poly->last_change = frame_loops;
863 }
864 }
865
866 cannon->conn_mask = 0;
867 cannon->last_change = frame_loops;
868 cannon->dead_ticks = 0;
869
870 P_set_hitmask(cannon->group, Cannon_hitmask(cannon));
871 }
872
World_remove_cannon(cannon_t * cannon)873 void World_remove_cannon(cannon_t *cannon)
874 {
875 blkpos_t blk = Clpos_to_blkpos(cannon->pos);
876 int i;
877
878 cannon->dead_ticks = options.cannonDeadTicks;
879 cannon->conn_mask = 0;
880
881 World_set_block(blk, SPACE);
882
883 for (i = 0; i < num_polys; i++) {
884 poly_t *poly = &pdata[i];
885
886 if (poly->group == cannon->group) {
887 poly->current_style = poly->destroyed_style;
888 poly->update_mask = ~0;
889 poly->last_change = frame_loops;
890 }
891 }
892
893 P_set_hitmask(cannon->group, Cannon_hitmask(cannon));
894 }
895
896
897 extern struct move_parameters mp;
898 /*
899 * This function is called when something would hit a cannon.
900 *
901 * Ideas stolen from Move_segment in walls_old.c
902 */
Cannon_hitfunc(group_t * gp,const move_t * move)903 bool Cannon_hitfunc(group_t *gp, const move_t *move)
904 {
905 const object_t *obj = move->obj;
906 cannon_t *cannon = Cannon_by_index(gp->mapobj_ind);
907 unsigned long cannon_mask;
908
909 /* this should never happen if hitmasks are ok */
910 assert (! (cannon->dead_ticks > 0));
911
912 /* if cannon is phased nothing will hit it */
913 if (BIT(cannon->used, USES_PHASING_DEVICE))
914 return false;
915
916 if (obj == NULL)
917 return true;
918
919 cannon_mask = mp.obj_cannon_mask | OBJ_PLAYER_BIT;
920 if (!BIT(cannon_mask, OBJ_TYPEBIT(obj->type)))
921 return false;
922
923 /*
924 * kps - if no team play, both cannons have team == TEAM_NOT_SET,
925 * this code should work, no matter if team play is true or not.
926 */
927 if (BIT(obj->obj_status, FROMCANNON)
928 && obj->team == cannon->team) {
929 return false;
930 }
931
932 return true;
933 }
934
Cannon_set_option(cannon_t * cannon,const char * name,const char * value)935 void Cannon_set_option(cannon_t *cannon, const char *name, const char *value)
936 {
937 Item_t item;
938 const char *origname = name;
939
940 /* Remove possible cannon prefix from option name. */
941 if (!strncasecmp(name, "cannon", 6))
942 name += 6;
943
944 item = Item_by_option_name(name);
945 if (item != NO_ITEM) {
946 cannon->initial_items[item] = atoi(value);
947 return;
948 }
949
950 if (!strcasecmp(name, "smartness")) {
951 int smartness = atoi(value);
952
953 LIMIT(smartness, 0, CANNON_SMARTNESS_MAX);
954 cannon->smartness = smartness;
955 return;
956 }
957
958 if (!strcasecmp(name, "shotspeed")) {
959 float shot_speed = atof(value);
960
961 /* limit ? */
962 cannon->shot_speed = shot_speed;
963 return;
964 }
965
966 warn("This server doesn't support option %s for cannons.", origname);
967 }
968