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