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  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24  */
25 
26 /* Robot code originally submitted by Maurice Abraham. */
27 
28 #include "xpserver.h"
29 
30 #define ROB_LOOK_AH		2
31 
32 #define WITHIN(NOW,THEN,DIFF) (NOW<=THEN && (THEN-NOW)<DIFF)
33 
34 /*
35  * Flags for the default robots being in different modes (or moods).
36  */
37 #define RM_ROBOT_IDLE         	(1 << 2)
38 #define RM_EVADE_LEFT         	(1 << 3)
39 #define RM_EVADE_RIGHT          (1 << 4)
40 #define RM_ROBOT_CLIMB          (1 << 5)
41 #define RM_HARVEST            	(1 << 6)
42 #define RM_ATTACK             	(1 << 7)
43 #define RM_TAKE_OFF           	(1 << 8)
44 #define RM_CANNON_KILL		(1 << 9)
45 #define RM_REFUEL		(1 << 10)
46 #define RM_NAVIGATE		(1 << 11)
47 
48 /* long term modes */
49 #define FETCH_TREASURE		(1 << 0)
50 #define TARGET_KILL		(1 << 1)
51 #define NEED_FUEL		(1 << 2)
52 
Empty_space_for_ball(int bx,int by)53 static bool Empty_space_for_ball(int bx, int by)
54 {
55     int group;
56     hitmask_t hitmask = BALL_BIT; /* kps - ok ? */
57     clpos_t pos;
58 
59     pos.cx = BLOCK_CENTER(bx);
60     pos.cy = BLOCK_CENTER(by);
61     group = shape_is_inside(pos.cx, pos.cy,
62 			    hitmask, NULL, &filled_wire, 0);
63     if (group == NO_GROUP)
64 	return true;
65     return false;
66 }
67 
68 #if 0
69 /* disabled, might make robots spin on base */
70 /*
71  * Map objects a robot can fly through without damage.
72  */
73 static bool Really_empty_space(player_t *pl, int bx, int by)
74 {
75     int group;
76     hitmask_t hitmask = NONBALL_BIT; /* kps - ok ? */
77     clpos_t pos;
78 
79     UNUSED_PARAM(pl);
80 
81     pos.cx = BLOCK_CENTER(bx);
82     pos.cy = BLOCK_CENTER(by);
83     group = shape_is_inside(pos.cx, pos.cy,
84 			    hitmask, NULL, &filled_wire, 0);
85     if (group == NO_GROUP)
86 	return true;
87     return false;
88 }
89 #else
Really_empty_space(player_t * pl,int bx,int by)90 static bool Really_empty_space(player_t *pl, int bx, int by)
91 {
92     int group, cx, cy, i, j;
93     int delta = BLOCK_CLICKS / 4;
94     int inside = 0, outside = 0;
95     hitmask_t hitmask = NONBALL_BIT; /* kps - ok ? */
96 
97     /*
98      * kps hack - check a few positions inside the block, if none of them
99      * are inside, assume it is empty
100      */
101     cx = BLOCK_CENTER(bx);
102     cy = BLOCK_CENTER(by);
103 
104     for (i = -1; i <= 1; i++) {
105 	for (j = -1; j <= 1; j++) {
106 	    clpos_t pos;
107 
108 	    pos.cx = cx + i * delta;
109 	    pos.cy = cy + j * delta;
110 	    pos = World_wrap_clpos(pos);
111 
112 	    group = is_inside(pos.cx, pos.cy, hitmask, NULL);
113 
114 	    /* hack so that robots won't rotate in destination wormholes */
115 	    if (group != NO_GROUP) {
116 		group_t *gp = groupptr_by_id(group);
117 		if (gp != NULL
118 		    && gp->type == WORMHOLE
119 		    && gp->mapobj_ind == pl->wormHoleDest)
120 		    group = NO_GROUP;
121 	        else if (gp!=NULL
122 		         && !options.targetTeamCollision
123 		         && gp->type == TARGET
124 		         && gp->team == pl->team)
125 		         group = NO_GROUP;
126 	    }
127 
128 	    if (group != NO_GROUP)
129 		inside++;
130 	    else
131 		outside++;
132 	}
133     }
134 
135     if (inside > 0)
136 	return false;
137     return true;
138 }
139 #endif
140 
141 /* watch out for strong gravity */
Gravity_is_strong(player_t * pl,clpos_t pos,int travel_dir)142 static inline bool Gravity_is_strong(player_t *pl, clpos_t pos, int travel_dir)
143 {
144     vector_t grav;
145     int gravity_dir;
146 
147     grav = World_gravity(pos);
148     if (sqr(grav.x) + sqr(grav.y) >= 0.5) {
149 	double gdir = findDir(grav.x - CLICK_TO_PIXEL(pl->pos.cx),
150 			      grav.y - CLICK_TO_PIXEL(pl->pos.cy));
151 
152 	gravity_dir = MOD2((int) (gdir + 0.5), RES);
153 	if (MOD2(gravity_dir - travel_dir, RES) <= RES / 4 ||
154 	    MOD2(gravity_dir - travel_dir, RES) >= 3 * RES / 4)
155 	    return true;
156     }
157     return false;
158 }
159 
160 /*
161  * Prototypes for methods of the default robot type.
162  */
163 static void Robot_default_round_tick(void);
164 static void Robot_default_create(player_t *pl, char *str);
165 static void Robot_default_go_home(player_t *pl);
166 static void Robot_default_play(player_t *pl);
167 static void Robot_default_set_war(player_t *pl, int victim_id);
168 static int Robot_default_war_on_player(player_t *pl);
169 static void Robot_default_message(player_t *pl, const char *str);
170 static void Robot_default_destroy(player_t *pl);
171 static void Robot_default_invite(player_t *pl, player_t *inviter);
172        int Robot_default_setup(robot_type_t *type_ptr);
173 
174 
175 /*
176  * Local static variables
177  */
178 static double	Visibility_distance;
179 static double	Max_enemy_distance;
180 
181 
182 /*
183  * The robot type structure for the default robot.
184  */
185 static robot_type_t robot_default_type = {
186     "default",
187     Robot_default_round_tick,
188     Robot_default_create,
189     Robot_default_go_home,
190     Robot_default_play,
191     Robot_default_set_war,
192     Robot_default_war_on_player,
193     Robot_default_message,
194     Robot_default_destroy,
195     Robot_default_invite
196 };
197 
198 
199 /*
200  * The only thing we export from this file.
201  * A function to initialize the robot type structure
202  * with our name and the pointers to our action routines.
203  *
204  * Return 0 if all is OK, anything else will ignore this
205  * robot type forever.
206  */
Robot_default_setup(robot_type_t * type_ptr)207 int Robot_default_setup(robot_type_t *type_ptr)
208 {
209     /* Not much to do for the default robot except init the type structure. */
210 
211     *type_ptr = robot_default_type;
212 
213     return 0;
214 }
215 
216 /*
217  * Private functions.
218  */
219 static bool Check_robot_evade(player_t *pl, int mine_i, int ship_i);
220 static bool Check_robot_target(player_t *pl, clpos_t item_pos, int new_mode);
221 static bool Detect_ship(player_t *pl, player_t *ship);
222 static int Rank_item_value(player_t *pl, enum Item itemtype);
223 static bool Ball_handler(player_t *pl);
224 
225 
226 /*
227  * Function to cast from player structure to robot data structure.
228  * This isolates casts (aka. type violations) to a few places.
229  */
Robot_default_get_data(player_t * pl)230 static robot_default_data_t *Robot_default_get_data(player_t *pl)
231 {
232     return (robot_default_data_t *)pl->robot_data_ptr->private_data;
233 }
234 
235 /*
236  * A default robot is created.
237  */
Robot_default_create(player_t * pl,char * str)238 static void Robot_default_create(player_t *pl, char *str)
239 {
240     robot_default_data_t *my_data;
241 
242     if (!(my_data = XMALLOC(robot_default_data_t, 1))) {
243 	error("no mem for default robot");
244 	End_game();
245     }
246 
247     my_data->robot_mode      = RM_TAKE_OFF;
248     my_data->robot_count     = 0;
249     my_data->robot_lock      = LOCK_NONE;
250     my_data->robot_lock_id   = 0;
251 
252     if (str != NULL
253 	&& *str != '\0'
254 	&& sscanf(str, " %d %d", &my_data->attack, &my_data->defense) != 2) {
255 	if (str && *str) {
256 	    warn("invalid parameters for default robot %s: \"%s\"",
257 		 pl->name, str);
258 	    my_data->attack = (int)(rfrac() * 99.5);
259 	    my_data->defense = 100 - my_data->attack;
260 	}
261 	LIMIT(my_data->attack, 1, 99);
262 	LIMIT(my_data->defense, 1, 99);
263     }
264     /*
265      * some parameters which may be changed to be dependent upon
266      * the 'attack' and 'defense' settings of this robot.
267      */
268     if (BIT(world->rules->mode, TIMING)) {
269 	my_data->robot_normal_speed = 10.0;
270 	my_data->robot_attack_speed = 25.0 + (my_data->attack / 10);
271 	my_data->robot_max_speed
272 	    = 50.0 + (my_data->attack / 20) - (my_data->defense / 50);
273     } else {
274 	my_data->robot_normal_speed = 6.0;
275 	my_data->robot_attack_speed = 15.0 + (my_data->attack / 25);
276 	my_data->robot_max_speed
277 	    = 30.0 + (my_data->attack / 50) - (my_data->defense / 50);
278     }
279 
280     my_data->fuel_l3 = 150.0;
281     my_data->fuel_l2 = 100.0;
282     my_data->fuel_l1 = 50.0;
283 
284     my_data->last_dropped_mine	= 0;
285     my_data->last_fired_missile	= 0;
286     my_data->last_thrown_ball	= 0;
287 
288     my_data->longterm_mode	= 0;
289 
290     pl->robot_data_ptr->private_data = (void *)my_data;
291 }
292 
293 /*
294  * A default robot is placed on its homebase.
295  */
Robot_default_go_home(player_t * pl)296 static void Robot_default_go_home(player_t *pl)
297 {
298     robot_default_data_t *my_data = Robot_default_get_data(pl);
299 
300     my_data->robot_mode      = RM_TAKE_OFF;
301     my_data->longterm_mode   = 0;
302 }
303 
304 /*
305  * A default robot is declaring war (or resetting war).
306  */
Robot_default_set_war(player_t * pl,int victim_id)307 static void Robot_default_set_war(player_t *pl, int victim_id)
308 {
309     robot_default_data_t *my_data = Robot_default_get_data(pl);
310 
311     if (victim_id == NO_ID)
312 	CLR_BIT(my_data->robot_lock, LOCK_PLAYER);
313     else {
314 	my_data->robot_lock_id = victim_id;
315 	SET_BIT(my_data->robot_lock, LOCK_PLAYER);
316     }
317 }
318 
319 /*
320  * Return the id of the player a default robot has war against (or NO_ID).
321  */
Robot_default_war_on_player(player_t * pl)322 static int Robot_default_war_on_player(player_t *pl)
323 {
324     robot_default_data_t *my_data = Robot_default_get_data(pl);
325 
326     if (BIT(my_data->robot_lock, LOCK_PLAYER))
327 	return my_data->robot_lock_id;
328     else
329 	return NO_ID;
330 }
331 
332 /*
333  * A default robot receives a message.
334  */
Robot_default_message(player_t * pl,const char * message)335 static void Robot_default_message(player_t *pl, const char *message)
336 {
337     UNUSED_PARAM(pl); UNUSED_PARAM(message);
338 }
339 
340 /*
341  * A default robot is destroyed.
342  */
Robot_default_destroy(player_t * pl)343 static void Robot_default_destroy(player_t *pl)
344 {
345     XFREE(pl->robot_data_ptr->private_data);
346 }
347 
348 /*
349  * A default robot is asked to join an alliance
350  */
Robot_default_invite(player_t * pl,player_t * inviter)351 static void Robot_default_invite(player_t *pl, player_t *inviter)
352 {
353     int war_id = Robot_default_war_on_player(pl), i;
354     robot_default_data_t *my_data = Robot_default_get_data(pl);
355     double limit;
356     bool we_accept = true;
357 
358     if (pl->alliance != ALLIANCE_NOT_SET) {
359 	/* if there is a human in our alliance, they should decide
360 	   let robots refuse in this case */
361 	for (i = 0; i < NumPlayers; i++) {
362 	    player_t *pl_i = Player_by_index(i);
363 
364 	    if (Player_is_human(pl_i) && Players_are_allies(pl, pl_i)) {
365 		we_accept = false;
366 		break;
367 	    }
368 	}
369 	if (!we_accept) {
370 	    Refuse_alliance(pl, inviter);
371 	    return;
372 	}
373     }
374     limit = MAX(ABS( Get_Score(pl) / MAX((my_data->attack / 10), 10)),
375 		my_data->defense);
376     if (inviter->alliance == ALLIANCE_NOT_SET) {
377 	/* don't accept players we are at war with */
378 	if (inviter->id == war_id)
379 	    we_accept = false;
380 	/* don't accept players who are not active */
381 	if (!Player_is_active(inviter))
382 	    we_accept = false;
383 	/* don't accept players with scores substantially lower than ours */
384 	else if ( Get_Score(inviter) < ( Get_Score(pl) - limit))
385 	    we_accept = false;
386     }
387     else {
388 	double avg_score = 0;
389 	int member_count = Get_alliance_member_count(inviter->alliance);
390 
391 	for (i = 0; i < NumPlayers; i++) {
392 	    player_t *pl_i = Player_by_index(i);
393 	    if (pl_i->alliance == inviter->alliance) {
394 		if (pl_i->id == war_id) {
395 		    we_accept = false;
396 		    break;
397 		}
398 		avg_score +=  Get_Score(pl_i);
399 	    }
400 	}
401 	if (we_accept) {
402 	    avg_score = avg_score / member_count;
403 	    if (avg_score < ( Get_Score(pl) - limit))
404 		we_accept = false;
405 	}
406     }
407     if (we_accept)
408 	Accept_alliance(pl, inviter);
409     else
410 	Refuse_alliance(pl, inviter);
411 }
412 
decide_travel_dir(player_t * pl)413 static inline int decide_travel_dir(player_t *pl)
414 {
415     double gdir;
416 
417     if (pl->velocity <= 0.2) {
418 	vector_t grav = World_gravity(pl->pos);
419 
420 	gdir = findDir(grav.x, grav.y);
421     } else
422 	gdir = findDir(pl->vel.x, pl->vel.y);
423 
424     return MOD2((int) (gdir + 0.5), RES);
425 }
426 
427 
Check_robot_evade(player_t * pl,int mine_i,int ship_i)428 static bool Check_robot_evade(player_t *pl, int mine_i, int ship_i)
429 {
430     object_t *shot;
431     player_t *ship;
432     double stop_dist, dist, velocity;
433     bool evade, left_ok, right_ok;
434     int i, safe_width, travel_dir, delta_dir, aux_dir;
435     int px[3], py[3], dx, dy;
436     robot_default_data_t *my_data = Robot_default_get_data(pl);
437 
438     safe_width = (my_data->defense / 200) * SHIP_SZ;
439     /* Prevent overflow. */
440     velocity = (pl->velocity <= SPEED_LIMIT) ? pl->velocity : SPEED_LIMIT;
441     stop_dist = ((RES * velocity) / (MAX_PLAYER_TURNSPEED * pl->turnresistance)
442 		 + (velocity * velocity * pl->mass) / (2 * MAX_PLAYER_POWER)
443 		 + safe_width);
444     /*
445      * Limit the look ahead.  For very high speeds the current code
446      * is ineffective and much too inefficient.
447      */
448     if (stop_dist > 10 * BLOCK_SZ)
449 	stop_dist = 10 * BLOCK_SZ;
450 
451     evade = false;
452 
453     travel_dir = decide_travel_dir(pl);
454 
455     aux_dir = MOD2(travel_dir + RES / 4, RES);
456     px[0] = CLICK_TO_PIXEL(pl->pos.cx);		/* ship center x */
457     py[0] = CLICK_TO_PIXEL(pl->pos.cy);		/* ship center y */
458     px[1] = (int)(px[0] + safe_width * tcos(aux_dir));	/* ship left side x */
459     py[1] = (int)(py[0] + safe_width * tsin(aux_dir));	/* ship left side y */
460     px[2] = 2 * px[0] - px[1];	/* ship right side x */
461     py[2] = 2 * py[0] - py[1];	/* ship right side y */
462 
463     left_ok = true;
464     right_ok = true;
465 
466     for (dist = 0; dist < stop_dist + BLOCK_SZ / 2; dist += BLOCK_SZ / 2) {
467 	for (i = 0; i < 3; i++) {
468 	    clpos_t d;
469 
470 	    dx = (int)((px[i] + dist * tcos(travel_dir)) / BLOCK_SZ);
471 	    dy = (int)((py[i] + dist * tsin(travel_dir)) / BLOCK_SZ);
472 
473 	    dx = WRAP_XBLOCK(dx);
474 	    dy = WRAP_YBLOCK(dy);
475 	    d.cx = BLOCK_CENTER(dx);
476 	    d.cy = BLOCK_CENTER(dy);
477 
478 	    if (!World_contains_clpos(d)) {
479 		evade = true;
480 		if (i == 1)
481 		    left_ok = false;
482 		if (i == 2)
483 		    right_ok = false;
484 		continue;
485 	    }
486 	    if (!Really_empty_space(pl, dx, dy)) {
487 		evade = true;
488 		if (i == 1)
489 		    left_ok = false;
490 		if (i == 2)
491 		    right_ok = false;
492 		continue;
493 	    }
494 	    if (Gravity_is_strong(pl, d, travel_dir)) {
495 		evade = true;
496 		if (i == 1)
497 		    left_ok = false;
498 		if (i == 2)
499 		    right_ok = false;
500 		continue;
501 	    }
502 	}
503     }
504 
505     if (mine_i >= 0) {
506 	double adir;
507 
508 	shot = Obj[mine_i];
509 	adir = Wrap_cfindDir(shot->pos.cx + PIXEL_TO_CLICK(shot->vel.x)
510 			     - pl->pos.cx,
511 			     shot->pos.cy + PIXEL_TO_CLICK(shot->vel.y)
512 			     - pl->pos.cy);
513 	aux_dir = MOD2((int) (adir + 0.5), RES);
514 	delta_dir = MOD2(aux_dir - travel_dir, RES);
515 	if (delta_dir < RES / 4) {
516 	    left_ok = false;
517 	    evade = true;
518 	}
519 	if (delta_dir > RES * 3 / 4) {
520 	    right_ok = false;
521 	    evade = true;
522 	}
523     }
524     if (ship_i >= 0) {
525 	double adir;
526 
527 	ship = Player_by_index(ship_i);
528 	adir = Wrap_cfindDir(ship->pos.cx - pl->pos.cx
529 			     + PIXEL_TO_CLICK(ship->vel.x * 2),
530 			     ship->pos.cy - pl->pos.cy
531 			     + PIXEL_TO_CLICK(ship->vel.y * 2));
532 	aux_dir = (int) (adir + 0.5);
533 	delta_dir = MOD2(aux_dir - travel_dir, RES);
534 	if (delta_dir < RES / 4) {
535 	    left_ok = false;
536 	    evade = true;
537 	}
538 	if (delta_dir > RES * 3 / 4) {
539 	    right_ok = false;
540 	    evade = true;
541 	}
542     }
543     if (pl->velocity > my_data->robot_max_speed)
544 	evade = true;
545 
546     if (!evade)
547 	return false;
548 
549     delta_dir = 0;
550     while (!left_ok && !right_ok && delta_dir < 7 * RES / 8) {
551 	delta_dir += RES / 16;
552 
553 	left_ok = true;
554 	aux_dir = MOD2(travel_dir + delta_dir, RES);
555 	for (dist = 0; dist < stop_dist + BLOCK_SZ / 2; dist += BLOCK_SZ / 2) {
556 	    clpos_t d;
557 
558 	    dx = (int)((px[0] + dist * tcos(aux_dir)) / BLOCK_SZ);
559 	    dy = (int)((py[0] + dist * tsin(aux_dir)) / BLOCK_SZ);
560 
561 	    dx = WRAP_XBLOCK(dx);
562 	    dy = WRAP_YBLOCK(dy);
563 	    d.cx = BLOCK_CENTER(dx);
564 	    d.cy = BLOCK_CENTER(dy);
565 
566 	    if (!World_contains_clpos(d)) {
567 		left_ok = false;
568 		continue;
569 	    }
570 	    if (!Really_empty_space(pl, dx, dy)) {
571 		left_ok = false;
572 		continue;
573 	    }
574 	    if (Gravity_is_strong(pl, d, travel_dir)) {
575 		left_ok = false;
576 		continue;
577 	    }
578 	}
579 
580 	right_ok = true;
581 	aux_dir = MOD2(travel_dir - delta_dir, RES);
582 	for (dist = 0; dist < stop_dist + BLOCK_SZ / 2; dist += BLOCK_SZ / 2) {
583 	    clpos_t d;
584 
585 	    dx = (int)((px[0] + dist * tcos(aux_dir)) / BLOCK_SZ);
586 	    dy = (int)((py[0] + dist * tsin(aux_dir)) / BLOCK_SZ);
587 
588 	    dx = WRAP_XBLOCK(dx);
589 	    dy = WRAP_YBLOCK(dy);
590 	    d.cx = BLOCK_CENTER(dx);
591 	    d.cy = BLOCK_CENTER(dy);
592 
593 	    if (!World_contains_clpos(d)) {
594 		right_ok = false;
595 		continue;
596 	    }
597 	    if (!Really_empty_space(pl, dx, dy)) {
598 		right_ok = false;
599 		continue;
600 	    }
601 	    if (Gravity_is_strong(pl, d, travel_dir)) {
602 		right_ok = false;
603 		continue;
604 	    }
605 	}
606     }
607 
608     pl->turnspeed = MAX_PLAYER_TURNSPEED;
609     pl->power = MAX_PLAYER_POWER;
610 
611     delta_dir = MOD2(pl->dir - travel_dir, RES);
612 
613     if (my_data->robot_mode != RM_EVADE_LEFT
614 	&& my_data->robot_mode != RM_EVADE_RIGHT) {
615 	if (left_ok && !right_ok)
616 	    my_data->robot_mode = RM_EVADE_LEFT;
617 	else if (right_ok && !left_ok)
618 	    my_data->robot_mode = RM_EVADE_RIGHT;
619 	else
620 	    my_data->robot_mode = (delta_dir < RES / 2 ?
621 			      RM_EVADE_LEFT : RM_EVADE_RIGHT);
622     }
623     /*-BA If facing the way we want to go, thrust
624      *-BA If too far off, stop thrusting
625      *-BA If in between, keep doing whatever we are already doing
626      *-BA In all cases continue to straighten up
627      */
628     if (delta_dir < RES / 4 || delta_dir > 3 * RES / 4) {
629 	pl->turnacc = (my_data->robot_mode == RM_EVADE_LEFT ?
630 		       pl->turnspeed : (-pl->turnspeed));
631 	Thrust(pl, false);
632     }
633     else if (delta_dir < 3 * RES / 8 || delta_dir > 5 * RES / 8)
634 	pl->turnacc = (my_data->robot_mode == RM_EVADE_LEFT ?
635 		       pl->turnspeed : (-pl->turnspeed));
636     else {
637 	pl->turnacc = 0;
638 	Thrust(pl, true);
639 	my_data->robot_mode = (delta_dir < RES/2
640 			       ? RM_EVADE_LEFT : RM_EVADE_RIGHT);
641     }
642 
643     return true;
644 }
645 
Robot_check_new_modifiers(player_t * pl,modifiers_t mods)646 static void Robot_check_new_modifiers(player_t *pl, modifiers_t mods)
647 {
648     Mods_filter(&mods);
649     pl->mods = mods;
650 }
651 
Choose_weapon_modifier(player_t * pl,int weapon_type)652 static void Choose_weapon_modifier(player_t *pl, int weapon_type)
653 {
654     int stock, min;
655     modifiers_t mods;
656     robot_default_data_t *my_data = Robot_default_get_data(pl);
657 
658     Mods_clear(&mods);
659 
660     switch (weapon_type) {
661     case USES_TRACTOR_BEAM:
662 	Robot_check_new_modifiers(pl, mods);
663 	return;
664 
665     case HAS_LASER:
666 	/*
667 	 * Robots choose non-damage laser settings occasionally.
668 	 */
669 	if ((my_data->robot_count % 4) == 0)
670 	    Mods_set(&mods, ModsLaser,
671 		     (int)(rfrac() * (MODS_LASER_MAX + 1)));
672 
673 	Robot_check_new_modifiers(pl, mods);
674 	return;
675 
676     case OBJ_SHOT:
677 	/*
678 	 * Robots usually use wide beam shots, however they may narrow
679 	 * the beam occasionally.
680 	 */
681 	if ((my_data->robot_count % 4) == 0)
682 	    Mods_set(&mods, ModsSpread,
683 		     (int)(rfrac() * (MODS_SPREAD_MAX + 1)));
684 
685 	Robot_check_new_modifiers(pl, mods);
686 	return;
687 
688     case OBJ_MINE:
689 	stock = pl->item[ITEM_MINE];
690 	min = options.nukeMinMines;
691 	break;
692 
693     case OBJ_SMART_SHOT:
694     case OBJ_HEAT_SHOT:
695     case OBJ_TORPEDO:
696 	stock = pl->item[ITEM_MISSILE];
697 	min = options.nukeMinSmarts;
698 	if ((my_data->robot_count % 4) == 0)
699 	    Mods_set(&mods, ModsPower,
700 		     (int)(rfrac() * (MODS_POWER_MAX + 1)));
701 
702 	break;
703 
704     default:
705 	return;
706     }
707 
708     if (stock >= min) {
709 	/*
710 	 * More aggressive robots will choose to use nuclear weapons, this
711 	 * means you can safely approach wimpy robots... perhaps.
712 	 */
713 	if ((my_data->robot_count % 100) <= my_data->attack) {
714 	    Mods_set(&mods, ModsNuclear, MODS_NUCLEAR);
715 	    if (stock > min && (stock < (2 * min)
716 				|| (my_data->robot_count % 2) == 0))
717 		Mods_set(&mods, ModsNuclear,
718 			 MODS_NUCLEAR|MODS_FULLNUCLEAR);
719 	}
720     }
721 
722     if (pl->fuel.sum > my_data->fuel_l3) {
723 	if ((my_data->robot_count % 2) == 0) {
724 	    if ((my_data->robot_count % 8) == 0)
725 		Mods_set(&mods, ModsVelocity,
726 			 (int)(rfrac() * MODS_VELOCITY_MAX) + 1);
727 	    Mods_set(&mods, ModsCluster, 1);
728 	}
729     }
730     else if ((my_data->robot_count % 3) == 0)
731 	Mods_set(&mods, ModsImplosion, 1);
732 
733     /*
734      * Robot may change to use mini device setting occasionally.
735      */
736     if ((my_data->robot_count % 10) == 0) {
737 	Mods_set(&mods, ModsMini, (int)(rfrac() * (MODS_MINI_MAX + 1)));
738 	Mods_set(&mods, ModsSpread,
739 		 (int)(rfrac() * (MODS_SPREAD_MAX + 1)));
740     }
741 
742     Robot_check_new_modifiers(pl, mods);
743 }
744 
745 /*
746  * Calculate minimum of length of hypotenuse in triangle with sides
747  * 'dcx' and 'dcy' and 'min', taking into account wrapping.
748  * Unit is clicks.
749  */
Wrap_length_min(double dcx,double dcy,double min)750 static inline double Wrap_length_min(double dcx, double dcy, double min)
751 {
752     double len;
753 
754     dcx = WRAP_DCX(dcx), dcx = ABS(dcx);
755     if (dcx >= min)
756 	return min;
757     dcy = WRAP_DCY(dcy), dcy = ABS(dcy);
758     if (dcy >= min)
759 	return min;
760 
761     len = LENGTH(dcx, dcy);
762 
763     return MIN(len, min);
764 }
765 
766 
Robotdef_fire_laser(player_t * pl)767 static void Robotdef_fire_laser(player_t *pl)
768 {
769     robot_default_data_t *my_data = Robot_default_get_data(pl);
770     double x2, y2, x3, y3, x4, y4, x5, y5;
771     double ship_dist, dir3, dir4, dir5;
772     clpos_t m_gun;
773     player_t *ship;
774 
775     if (BIT(my_data->robot_lock, LOCK_PLAYER)
776 	&& Player_is_active(Player_by_id(my_data->robot_lock_id)))
777 	ship = Player_by_id(my_data->robot_lock_id);
778     else if (BIT(pl->lock.tagged, LOCK_PLAYER))
779 	ship = Player_by_id(pl->lock.pl_id);
780     else
781 	return;
782 
783     /* kps - this should be Player_is_alive() ? */
784     if (!Player_is_active(ship))
785 	return;
786 
787     m_gun = Ship_get_m_gun_clpos(pl->ship, pl->dir);
788     x2 = CLICK_TO_PIXEL(pl->pos.cx) + pl->vel.x
789 	+ CLICK_TO_PIXEL(m_gun.cx);
790     y2 = CLICK_TO_PIXEL(pl->pos.cy) + pl->vel.y
791 	+ CLICK_TO_PIXEL(m_gun.cy);
792     x3 = CLICK_TO_PIXEL(ship->pos.cx) + ship->vel.x;
793     y3 = CLICK_TO_PIXEL(ship->pos.cy) + ship->vel.y;
794 
795     ship_dist = Wrap_length(PIXEL_TO_CLICK(x3 - x2),
796 			    PIXEL_TO_CLICK(y3 - y2)) / CLICK;
797 
798     if (ship_dist >= options.pulseSpeed * options.pulseLife + SHIP_SZ)
799 	return;
800 
801     dir3 = Wrap_findDir(x3 - x2, y3 - y2);
802     x4 = x3 + tcos(MOD2((int)(dir3 - RES/4), RES)) * SHIP_SZ;
803     y4 = y3 + tsin(MOD2((int)(dir3 - RES/4), RES)) * SHIP_SZ;
804     x5 = x3 + tcos(MOD2((int)(dir3 + RES/4), RES)) * SHIP_SZ;
805     y5 = y3 + tsin(MOD2((int)(dir3 + RES/4), RES)) * SHIP_SZ;
806     dir4 = Wrap_findDir(x4 - x2, y4 - y2);
807     dir5 = Wrap_findDir(x5 - x2, y5 - y2);
808     if ((dir4 > dir5)
809 	? (pl->dir >= dir4 || pl->dir <= dir5)
810 	: (pl->dir >= dir4 && pl->dir <= dir5))
811 	SET_BIT(pl->used, HAS_LASER);
812 }
813 
Robotdef_do_tractor_beam(player_t * pl)814 static void Robotdef_do_tractor_beam(player_t *pl)
815 {
816     robot_default_data_t *my_data = Robot_default_get_data(pl);
817 
818     CLR_BIT(pl->used, USES_TRACTOR_BEAM);
819     pl->tractor_is_pressor = false;
820 
821     if (BIT(pl->lock.tagged, LOCK_PLAYER)
822 	&& pl->fuel.sum > my_data->fuel_l3
823 	&& pl->lock.distance
824 	< TRACTOR_MAX_RANGE(pl->item[ITEM_TRACTOR_BEAM])) {
825 
826 	double xvd, yvd, vel;
827 	int dir, away;
828 	player_t *ship = Player_by_id(pl->lock.pl_id);
829 
830 	xvd = ship->vel.x - pl->vel.x;
831 	yvd = ship->vel.y - pl->vel.y;
832 	vel = LENGTH(xvd, yvd);
833 	dir = (int)(Wrap_cfindDir(pl->pos.cx - ship->pos.cx,
834 				  pl->pos.cy - ship->pos.cy)
835 		    - findDir(xvd, yvd));
836 	dir = MOD2(dir, RES);
837 	away = (dir >= RES/4 && dir <= 3*RES/4);
838 
839 	/*
840 	 * vel  - The relative velocity of ship to us.
841 	 * away - Heading away from us?
842 	 */
843 	if (pl->velocity <= my_data->robot_normal_speed) {
844 	    if (pl->lock.distance < (SHIP_SZ * 4)
845 		|| (!away && vel > my_data->robot_attack_speed)) {
846 		SET_BIT(pl->used, USES_TRACTOR_BEAM);
847 		pl->tractor_is_pressor = true;
848 	    } else if (away
849 		       && vel < my_data->robot_max_speed
850 		       && vel > my_data->robot_normal_speed)
851 		SET_BIT(pl->used, USES_TRACTOR_BEAM);
852 	}
853 	if (Player_uses_tractor_beam(pl))
854 	    SET_BIT(pl->lock.tagged, LOCK_VISIBLE);
855     }
856 }
857 
Check_robot_target(player_t * pl,clpos_t item_pos,int new_mode)858 static bool Check_robot_target(player_t *pl, clpos_t item_pos, int new_mode)
859 {
860     int item_dir, travel_dir, delta_dir;
861     int dx, dy;
862     double dist, item_dist, idir;
863     bool clear_path, slowing;
864     robot_default_data_t *my_data = Robot_default_get_data(pl);
865 
866     dx = CLICK_TO_PIXEL(item_pos.cx - pl->pos.cx), dx = WRAP_DX(dx);
867     dy = CLICK_TO_PIXEL(item_pos.cy - pl->pos.cy), dy = WRAP_DY(dy);
868 
869     item_dist = LENGTH(dy, dx);
870 
871     if (dx == 0 && dy == 0) {
872 	vector_t grav = World_gravity(pl->pos);
873 
874 	idir = findDir(grav.x, grav.y);
875 	item_dir = (int) (idir + 0.5);
876 	item_dir = MOD2(item_dir + RES/2, RES);
877     } else {
878 	idir = findDir((double)dx, (double)dy);
879 	item_dir = (int) (idir + 0.5);
880 	item_dir = MOD2(item_dir, RES);
881     }
882 
883     if (new_mode == RM_REFUEL)
884 	item_dist = item_dist - 90;
885 
886     clear_path = true;
887 
888     for (dist = 0; clear_path && dist < item_dist; dist += BLOCK_SZ / 2) {
889 
890 	dx = (int)((CLICK_TO_PIXEL(pl->pos.cx)
891 		    + dist * tcos(item_dir)) / BLOCK_SZ);
892 	dy = (int)((CLICK_TO_PIXEL(pl->pos.cy)
893 		    + dist * tsin(item_dir)) / BLOCK_SZ);
894 
895 	dx = WRAP_XBLOCK(dx);
896 	dy = WRAP_YBLOCK(dy);
897 	if (dx < 0 || dx >= world->x || dy < 0 || dy >= world->y) {
898 	    clear_path = false;
899 	    continue;
900 	}
901 	if (!Really_empty_space(pl, dx, dy)) {
902 	    clear_path = false;
903 	    continue;
904 	}
905     }
906 
907     if (new_mode == RM_CANNON_KILL)
908 	item_dist -= 4 * BLOCK_SZ;
909 
910     if (!clear_path && new_mode != RM_NAVIGATE)
911 	return false;
912 
913     travel_dir = decide_travel_dir(pl);
914 
915     pl->turnspeed = MAX_PLAYER_TURNSPEED / 2;
916     pl->power = (BIT(world->rules->mode, TIMING) ?
917 		 MAX_PLAYER_POWER :
918 		 MAX_PLAYER_POWER / 2);
919 
920     delta_dir = MOD2(item_dir - travel_dir, RES);
921     if (delta_dir >= RES/4 && delta_dir <= 3*RES/4) {
922 
923 	if (new_mode == RM_HARVEST ||
924 	    (new_mode == RM_NAVIGATE &&
925 		(clear_path || dist > 8 * BLOCK_SZ)))
926 	    /* reverse direction of travel */
927 	    item_dir = MOD2(travel_dir + (delta_dir > RES / 2
928 					    ? -5 * RES / 8
929 					    : 5 * RES / 8), RES);
930 	pl->turnspeed = MAX_PLAYER_TURNSPEED;
931 	slowing = true;
932 
933 	if (pl->item[ITEM_MINE] && item_dist < 8 * BLOCK_SZ) {
934 	    Choose_weapon_modifier(pl, OBJ_MINE);
935 	    if (BIT(world->rules->mode, TIMING))
936 		Place_mine(pl);
937 	    else
938 		Place_moving_mine(pl);
939 	    new_mode = (rfrac() < 0.5) ? RM_EVADE_RIGHT : RM_EVADE_LEFT;
940 	}
941     } else if (new_mode == RM_CANNON_KILL && item_dist <= 0) {
942 
943 	/* too close, so move away */
944 	pl->turnspeed = MAX_PLAYER_TURNSPEED;
945 	item_dir = MOD2(item_dir + RES / 2, RES);
946 	slowing = true;
947     } else
948 	slowing = false;
949 
950     if (new_mode == RM_NAVIGATE && !clear_path) {
951 	if (dist <= 8 * BLOCK_SZ && dist > 4 * BLOCK_SZ)
952 	    item_dir = MOD2(item_dir + (delta_dir > RES / 2
953 					? -3 * RES / 4 : 3 * RES / 4), RES);
954 	else if (dist <= 4 * BLOCK_SZ)
955 	    item_dir = MOD2(item_dir + RES / 2, RES);
956 	pl->turnspeed = MAX_PLAYER_TURNSPEED;
957 	slowing = true;
958     }
959 
960     delta_dir = MOD2(item_dir - pl->dir, RES);
961 
962     if (delta_dir > RES / 8 && delta_dir < 7 * RES / 8)
963 	pl->turnspeed = MAX_PLAYER_TURNSPEED;
964     else if (delta_dir > RES / 16 && delta_dir < 15 * RES / 16)
965 	pl->turnspeed = MAX_PLAYER_TURNSPEED;
966     else if (delta_dir > RES / 64 && delta_dir < 63 * RES / 64)
967 	pl->turnspeed = MAX_PLAYER_TURNSPEED;
968     else
969 	pl->turnspeed = 0.0;
970 
971     pl->turnacc = (delta_dir < RES / 2 ? pl->turnspeed : (-pl->turnspeed));
972 
973     if (slowing || BIT(pl->used, HAS_SHIELD))
974 	Thrust(pl, true);
975     else if (item_dist < 0)
976 	Thrust(pl, false);
977     else if (item_dist < 3*BLOCK_SZ && new_mode != RM_HARVEST) {
978 
979 	if (pl->velocity < my_data->robot_normal_speed / 2)
980 	    Thrust(pl, true);
981 	if (pl->velocity > my_data->robot_normal_speed)
982 	    Thrust(pl, false);
983 
984     } else if ((new_mode != RM_ATTACK
985 		&& new_mode != RM_NAVIGATE)
986 	    || item_dist < 8*BLOCK_SZ
987 	    || (new_mode == RM_NAVIGATE
988 		&& delta_dir > 3 * RES / 8
989 		&& delta_dir < 5 * RES / 8)) {
990 
991 	if (pl->velocity < 2*my_data->robot_normal_speed)
992 	    Thrust(pl, true);
993 	if (pl->velocity > 3*my_data->robot_normal_speed)
994 	    Thrust(pl, false);
995 
996     } else if (new_mode == RM_ATTACK
997 	    || (new_mode == RM_NAVIGATE
998 		&& (dist < 12 * BLOCK_SZ
999 		    || (delta_dir > RES / 8
1000 			&& delta_dir < 7 * RES / 8)))) {
1001 
1002 	if (pl->velocity < my_data->robot_attack_speed / 2)
1003 	    Thrust(pl, true);
1004 	if (pl->velocity > my_data->robot_attack_speed)
1005 	    Thrust(pl, false);
1006     } else if (clear_path
1007 	    && (delta_dir < RES / 8
1008 		|| delta_dir > 7 * RES / 8)
1009 	    && item_dist > 18 * BLOCK_SZ) {
1010 	if (pl->velocity
1011 	    < my_data->robot_max_speed - my_data->robot_normal_speed)
1012 	    Thrust(pl, true);
1013 	if (pl->velocity > my_data->robot_max_speed)
1014 	    Thrust(pl, false);
1015     } else {
1016 	if (pl->velocity < my_data->robot_attack_speed)
1017 	    Thrust(pl, true);
1018 	if (pl->velocity
1019 	    > my_data->robot_max_speed - my_data->robot_normal_speed)
1020 	    Thrust(pl, false);
1021     }
1022 
1023     if (new_mode == RM_ATTACK
1024 	|| (BIT(world->rules->mode, TIMING)
1025 	    && new_mode == RM_NAVIGATE)) {
1026 
1027 	if (pl->item[ITEM_ECM] > 0
1028 	    && item_dist < ECM_DISTANCE / 4)
1029 	    Fire_ecm(pl);
1030 	else if (pl->item[ITEM_TRANSPORTER] > 0
1031 		 && item_dist < TRANSPORTER_DISTANCE
1032 		 && pl->fuel.sum > -ED_TRANSPORTER)
1033 	    Do_transporter(pl);
1034 	else if (pl->item[ITEM_LASER] > pl->num_pulses
1035 		 && pl->fuel.sum + ED_LASER > my_data->fuel_l3
1036 		 && new_mode == RM_ATTACK)
1037 	    Robotdef_fire_laser(pl);
1038 	else if (Player_has_tractor_beam(pl))
1039 	    Robotdef_do_tractor_beam(pl);
1040 
1041 	if (BIT(pl->used, HAS_LASER)) {
1042 	    pl->turnacc = 0.0;
1043 	    Choose_weapon_modifier(pl, HAS_LASER);
1044 	}
1045 	/*-BA Be more agressive, esp if lots of ammo
1046 	 * else if ((my_data->robot_count % 10) == 0
1047 	 * && pl->item[ITEM_MISSILE] > 0)
1048 	 */
1049 	else if ((my_data->robot_count % 10) < pl->item[ITEM_MISSILE]
1050 		  && !WITHIN(my_data->robot_count,
1051 			     my_data->last_fired_missile,10)) {
1052 	    int type;
1053 
1054 	    switch (my_data->robot_count % 5) {
1055 	    case 0: case 1: case 2:	type = OBJ_SMART_SHOT; break;
1056 	    case 3:			type = OBJ_HEAT_SHOT; break;
1057 	    default:			type = OBJ_TORPEDO; break;
1058 	    }
1059 	    if (Detect_ship(pl, Player_by_id(pl->lock.pl_id))
1060 		&& !pl->visibility[GetInd(pl->lock.pl_id)].canSee)
1061 		type = OBJ_HEAT_SHOT;
1062 	    if (type == OBJ_SMART_SHOT && !options.allowSmartMissiles)
1063 		type = OBJ_HEAT_SHOT;
1064 	    Choose_weapon_modifier(pl, type);
1065 	    Fire_shot(pl, type, pl->dir);
1066 	    if (type == OBJ_HEAT_SHOT)
1067 		Thrust(pl, false);
1068 	    my_data->last_fired_missile=my_data->robot_count;
1069 	}
1070 	else if ((my_data->robot_count % 2) == 0
1071 		   && item_dist < Visibility_distance
1072 		   /*&& BIT(my_data->robot_lock, LOCK_PLAYER)*/){
1073 	    if ((int)(rfrac() * 64) < pl->item[ITEM_MISSILE] ) {
1074 		Choose_weapon_modifier(pl, OBJ_SMART_SHOT);
1075 		Fire_shot(pl, OBJ_SMART_SHOT, pl->dir);
1076 		my_data->last_fired_missile=my_data->robot_count;
1077 	    } else {
1078 		if ((new_mode == RM_ATTACK && clear_path)
1079 		    || (my_data->robot_count % 50) == 0) {
1080 		    Choose_weapon_modifier(pl, OBJ_SHOT);
1081 		    Fire_normal_shots(pl);
1082 		}
1083 	    }
1084 	}
1085 	/*-BA Be more agressive, esp if lots of ammo
1086 	 * if ((my_data->robot_count % 32) == 0)
1087 	 */
1088 	else if ((my_data->robot_count % 32) < pl->item[ITEM_MINE]
1089 		  && !WITHIN(my_data->robot_count,
1090 			     my_data->last_dropped_mine, 10)) {
1091 	    if (pl->fuel.sum > my_data->fuel_l3) {
1092 		Choose_weapon_modifier(pl, OBJ_MINE);
1093 		Place_mine(pl);
1094 	    } else /*if (pl->fuel.sum < my_data->fuel_l2)*/ {
1095 		Place_mine(pl);
1096 		CLR_BIT(pl->used, USES_CLOAKING_DEVICE);
1097 	    }
1098 	    my_data->last_dropped_mine=my_data->robot_count;
1099 	}
1100     }
1101     if (new_mode == RM_CANNON_KILL && !slowing) {
1102 	if ((my_data->robot_count % 2) == 0
1103 	    && item_dist < Visibility_distance
1104 	    && clear_path) {
1105 	    Choose_weapon_modifier(pl, OBJ_SHOT);
1106 	    Fire_normal_shots(pl);
1107 	}
1108     }
1109     my_data->robot_mode = new_mode;
1110     return true;
1111 }
1112 
1113 
Check_robot_hunt(player_t * pl)1114 static bool Check_robot_hunt(player_t *pl)
1115 {
1116     player_t *ship;
1117     double sdir;
1118     int ship_dir, travel_dir, delta_dir, adj_dir, toofast, tooslow;
1119     robot_default_data_t *my_data = Robot_default_get_data(pl);
1120 
1121     if (!BIT(my_data->robot_lock, LOCK_PLAYER)
1122 	|| my_data->robot_lock_id == pl->id)
1123 	return false;
1124 
1125     if (pl->fuel.sum < my_data->fuel_l3 /*MAX_PLAYER_FUEL/2*/)
1126 	return false;
1127 
1128     ship = Player_by_id(my_data->robot_lock_id);
1129     if (!Detect_ship(pl, ship))
1130 	return false;
1131 
1132     sdir = Wrap_cfindDir(ship->pos.cx - pl->pos.cx,
1133 			 ship->pos.cy - pl->pos.cy);
1134     ship_dir = MOD2((int) (sdir + 0.5), RES);
1135 
1136     travel_dir = decide_travel_dir(pl);
1137 
1138     delta_dir = MOD2(ship_dir - travel_dir, RES);
1139     tooslow = (pl->velocity < my_data->robot_attack_speed/2);
1140     toofast = (pl->velocity > my_data->robot_attack_speed);
1141 
1142     if (!tooslow && !toofast
1143 	&& (delta_dir <= RES/16 || delta_dir >= 15*RES/16)) {
1144 
1145 	pl->turnacc = 0;
1146 	Thrust(pl, false);
1147 	my_data->robot_mode = RM_ROBOT_IDLE;
1148 	return true;
1149     }
1150 
1151     adj_dir = (delta_dir<RES/2 ? RES/4 : (-RES/4));
1152 
1153     if (tooslow) adj_dir = adj_dir/2;	/* point forwards more */
1154     if (toofast) adj_dir = 3*adj_dir/2;	/* point backwards more */
1155 
1156     adj_dir = MOD2(travel_dir + adj_dir, RES);
1157     delta_dir = MOD2(adj_dir - pl->dir, RES);
1158 
1159     if (delta_dir>=RES/16 && delta_dir<=15*RES/16) {
1160 	pl->turnspeed = MAX_PLAYER_TURNSPEED/4;
1161 	pl->turnacc = (delta_dir<RES/2 ? pl->turnspeed : (-pl->turnspeed));
1162     }
1163 
1164     if (delta_dir<RES/8 || delta_dir>7*RES/8)
1165 	Thrust(pl, true);
1166     else
1167 	Thrust(pl, false);
1168 
1169     my_data->robot_mode = RM_ROBOT_IDLE;
1170     return true;
1171 }
1172 
Detect_ship(player_t * pl,player_t * ship)1173 static bool Detect_ship(player_t *pl, player_t *ship)
1174 {
1175     double distance;
1176 
1177     /* can't go after non-playing ships */
1178     if (!Player_is_alive(ship))
1179 	return false;
1180 
1181     /* can't do anything with phased ships */
1182     if (Player_is_phasing(ship))
1183 	return false;
1184 
1185     /* trivial */
1186     if (pl->visibility[GetInd(ship->id)].canSee)
1187 	return true;
1188 
1189     /*
1190      * can't see it, so it must be cloaked
1191      * maybe we can detect it's presence from other clues?
1192      */
1193     distance = Wrap_length(ship->pos.cx - pl->pos.cx,
1194 			   ship->pos.cy - pl->pos.cy) / CLICK;
1195     /* can't detect ships beyond visual range */
1196     if (distance > Visibility_distance)
1197 	return false;
1198 
1199     if (Player_is_thrusting(ship)
1200 	&& options.cloakedExhaust)
1201 	return true;
1202 
1203     if (BIT(ship->used, HAS_SHOT)
1204 	|| BIT(ship->used, HAS_LASER)
1205 	|| Player_is_refueling(ship)
1206 	|| Player_is_repairing(ship)
1207 	|| Player_uses_connector(ship)
1208 	|| Player_uses_tractor_beam(ship))
1209 	return true;
1210 
1211     if (BIT(ship->have, HAS_BALL))
1212 	return true;
1213 
1214     /* the sky seems clear.. */
1215     return false;
1216 }
1217 
1218 /*
1219  * Determine how important an item is to a given player.
1220  * Return one of the following 3 values:
1221  */
1222 #define ROBOT_MUST_HAVE_ITEM	2	/* must have */
1223 #define ROBOT_HANDY_ITEM	1	/* handy */
1224 #define ROBOT_IGNORE_ITEM	0	/* ignore */
1225 /*
1226  */
Rank_item_value(player_t * pl,enum Item itemtype)1227 static int Rank_item_value(player_t *pl, enum Item itemtype)
1228 {
1229     robot_default_data_t *my_data = Robot_default_get_data(pl);
1230 
1231     if (itemtype == ITEM_AUTOPILOT)
1232 	return ROBOT_IGNORE_ITEM;		/* never useful for robots */
1233     if (pl->item[itemtype] >= world->items[itemtype].limit)
1234 	return ROBOT_IGNORE_ITEM;		/* already full */
1235     if ((IsDefensiveItem(itemtype)
1236 	 && CountDefensiveItems(pl) >= options.maxDefensiveItems)
1237 	|| (IsOffensiveItem(itemtype)
1238 	 && CountOffensiveItems(pl) >= options.maxOffensiveItems))
1239 	return ROBOT_IGNORE_ITEM;
1240     if (itemtype == ITEM_FUEL) {
1241 	if (pl->fuel.sum >= pl->fuel.max * 0.90)
1242 	    return ROBOT_IGNORE_ITEM;		/* already (almost) full */
1243 	else if ((pl->fuel.sum < (BIT(world->rules->mode, TIMING))
1244 		  ? my_data->fuel_l1
1245 		  : my_data->fuel_l2))
1246 	    return ROBOT_MUST_HAVE_ITEM;		/* ahh fuel at last */
1247 	else
1248 	    return ROBOT_HANDY_ITEM;
1249     }
1250     if (BIT(world->rules->mode, TIMING)) {
1251 	switch (itemtype) {
1252 	case ITEM_FUEL:		/* less refuel stops */
1253 	case ITEM_REARSHOT:	/* shoot competitors behind you */
1254 	case ITEM_AFTERBURNER:	/* the more speed the better */
1255 	case ITEM_TRANSPORTER:	/* steal fuel when you overtake someone */
1256 	case ITEM_MINE:		/* blows others off the track */
1257 	case ITEM_ECM:		/* blinded players smash into walls */
1258 	case ITEM_EMERGENCY_THRUST:	/* makes you go really fast */
1259 	case ITEM_EMERGENCY_SHIELD:	/* could be useful when ramming */
1260 	    return ROBOT_MUST_HAVE_ITEM;
1261 	case ITEM_WIDEANGLE:	/* not important in racemode */
1262 	case ITEM_CLOAK:	/* not important in racemode */
1263 	case ITEM_SENSOR:	/* who cares about seeing others? */
1264 	case ITEM_TANK:		/* makes you heavier */
1265 	case ITEM_MISSILE:	/* likely to hit self */
1266 	case ITEM_LASER:	/* cost too much fuel */
1267 	case ITEM_TRACTOR_BEAM:	/* pushes/pulls owner off the track too */
1268 	case ITEM_AUTOPILOT:	/* probably not useful */
1269 	case ITEM_DEFLECTOR:	/* cost too much fuel */
1270 	case ITEM_HYPERJUMP:	/* likely to end up in wrong place */
1271 	case ITEM_PHASING:	/* robots don't know how to use them yet */
1272 	case ITEM_MIRROR:	/* not important in racemode */
1273 	case ITEM_ARMOR:	/* makes you heavier */
1274 	    return ROBOT_IGNORE_ITEM;
1275 	default:		/* unknown */
1276 	    warn("Rank_item_value: unknown item %ld.", itemtype);
1277 	    return ROBOT_IGNORE_ITEM;
1278 	}
1279     } else {
1280 	switch (itemtype) {
1281 	case ITEM_EMERGENCY_SHIELD:
1282 	case ITEM_DEFLECTOR:
1283 	case ITEM_ARMOR:
1284 	    if (BIT(pl->have, HAS_SHIELD))
1285 		return ROBOT_HANDY_ITEM;
1286 	    else
1287 		return ROBOT_MUST_HAVE_ITEM;
1288 
1289 	case ITEM_REARSHOT:
1290 	case ITEM_WIDEANGLE:
1291 	    if (options.maxPlayerShots <= 0
1292 		|| options.shotLife <= 0
1293 		|| !options.allowPlayerKilling)
1294 		return ROBOT_HANDY_ITEM;
1295 	    else
1296 		return ROBOT_MUST_HAVE_ITEM;
1297 
1298 	case ITEM_MISSILE:
1299 	    if (options.maxPlayerShots <= 0
1300 		|| options.shotLife <= 0
1301 		|| !options.allowPlayerKilling)
1302 		return ROBOT_IGNORE_ITEM;
1303 	    else
1304 		return ROBOT_MUST_HAVE_ITEM;
1305 
1306 	case ITEM_MINE:
1307 	case ITEM_CLOAK:
1308 	    return ROBOT_MUST_HAVE_ITEM;
1309 
1310 	case ITEM_LASER:
1311 	    if (options.allowPlayerKilling)
1312 		return ROBOT_MUST_HAVE_ITEM;
1313 	    else
1314 		return ROBOT_HANDY_ITEM;
1315 
1316 	case ITEM_PHASING:	/* robots don't know how to use them yet */
1317 	    return ROBOT_IGNORE_ITEM;
1318 
1319 	default:
1320 	    break;
1321 	}
1322     }
1323     return ROBOT_HANDY_ITEM;
1324 }
1325 
Ball_handler(player_t * pl)1326 static bool Ball_handler(player_t *pl)
1327 {
1328     int i, closest_tr = NO_IND, closest_ntr = NO_IND;
1329     double closest_tr_dist = 1e19, closest_ntr_dist = 1e19, dist, dbdir, dtdir;
1330     int bdir, tdir;
1331     bool clear_path = true;
1332     robot_default_data_t *my_data = Robot_default_get_data(pl);
1333 
1334     for (i = 0; i < Num_treasures(); i++) {
1335 	treasure_t *treasure = Treasure_by_index(i);
1336 
1337 	if ((BIT(pl->have, HAS_BALL) || pl->ball)
1338 	    && treasure->team == pl->team) {
1339 	    dist = Wrap_length(treasure->pos.cx - pl->pos.cx,
1340 			       treasure->pos.cy - pl->pos.cy) / CLICK;
1341 	    if (dist < closest_tr_dist) {
1342 		closest_tr = i;
1343 		closest_tr_dist = dist;
1344 	    }
1345 	} else if (treasure->team != pl->team
1346 		   && Team_by_index(treasure->team)->NumMembers > 0
1347 		   && !BIT(pl->have, HAS_BALL)
1348 		   && !pl->ball
1349 		   && treasure->have) {
1350 	    dist = Wrap_length(treasure->pos.cx - pl->pos.cx,
1351 			       treasure->pos.cy - pl->pos.cy) / CLICK;
1352 	    if (dist < closest_ntr_dist) {
1353 		closest_ntr = i;
1354 		closest_ntr_dist = dist;
1355 	    }
1356 	}
1357     }
1358     if (BIT(pl->have, HAS_BALL) || pl->ball) {
1359 	ballobject_t *ball = NULL;
1360 	blkpos_t bbpos;
1361 	double dist_np = 1e19;
1362 	int xdist, ydist, dx, dy;
1363 	treasure_t *closest_treasure;
1364 
1365 	if (pl->ball)
1366 	    ball = pl->ball;
1367 	else {
1368 	    for (i = 0; i < NumObjs; i++) {
1369 		if (Obj[i]->type == OBJ_BALL
1370 		    && Obj[i]->id == pl->id) {
1371 		    ball = BALL_PTR(Obj[i]);
1372 		    break;
1373 		}
1374 	    }
1375 	}
1376 	for (i = 0; i < NumPlayers; i++) {
1377 	    player_t *pl_i = Player_by_index(i);
1378 
1379 	    dist = LENGTH(ball->pos.cx - pl_i->pos.cx,
1380 			  ball->pos.cy - pl_i->pos.cy) / CLICK;
1381 	    if (pl_i->id != pl->id
1382 		&& Player_is_active(pl_i)
1383 		&& dist < dist_np)
1384 		dist_np = dist;
1385 	}
1386 	closest_treasure = Treasure_by_index(closest_tr);
1387 	dbdir = findDir(ball->vel.x, ball->vel.y);
1388 	dtdir = Wrap_cfindDir(closest_treasure->pos.cx - ball->pos.cx,
1389 			      closest_treasure->pos.cy - ball->pos.cy);
1390 	bdir = MOD2((int) (dbdir + 0.5), RES);
1391 	tdir = MOD2((int) (dtdir + 0.5), RES);
1392 	bbpos = Clpos_to_blkpos(ball->pos);
1393 	xdist = (closest_treasure->pos.cx / BLOCK_CLICKS) - bbpos.bx;
1394 	ydist = (closest_treasure->pos.cy / BLOCK_CLICKS) - bbpos.by;
1395 	for (dist = 0;
1396 	     clear_path && dist < (closest_tr_dist - BLOCK_SZ);
1397 	     dist += BLOCK_SZ / 2) {
1398 	    double fraction = (double)dist / closest_tr_dist;
1399 
1400 	    dx = (int)((fraction * xdist) + bbpos.bx);
1401 	    dy = (int)((fraction * ydist) + bbpos.by);
1402 
1403 	    dx = WRAP_XBLOCK(dx);
1404 	    dy = WRAP_YBLOCK(dy);
1405 	    if (dx < 0 || dx >= world->bwidth_floor
1406 		|| dy < 0 || dy >= world->bheight_floor) {
1407 		clear_path = false;
1408 		continue;
1409 	    }
1410 	    if (!Empty_space_for_ball(dx, dy)) {
1411 		clear_path = false;
1412 		continue;
1413 	    }
1414 	}
1415 	if (tdir == bdir
1416 	    && dist_np > closest_tr_dist
1417 	    && clear_path
1418 	    && sqr(ball->vel.x) + sqr(ball->vel.y) > 60) {
1419 	    Detach_ball(pl, NULL);
1420 	    CLR_BIT(pl->used, USES_CONNECTOR);
1421 	    my_data->last_thrown_ball = my_data->robot_count;
1422 	    CLR_BIT(my_data->longterm_mode, FETCH_TREASURE);
1423 	} else {
1424 	    SET_BIT(my_data->longterm_mode, FETCH_TREASURE);
1425 	    return Check_robot_target(pl,
1426 				      Treasure_by_index(closest_tr)->pos,
1427 				      RM_NAVIGATE);
1428 	}
1429     } else {
1430 	double ball_dist, closest_ball_dist = closest_ntr_dist;
1431 	int closest_ball = NO_IND;
1432 
1433 	for (i = 0; i < NumObjs; i++) {
1434 	    if (Obj[i]->type == OBJ_BALL) {
1435 		ballobject_t *ball = BALL_IND(i);
1436 
1437 		if ((ball->id == NO_ID)
1438 		    ? (ball->ball_owner != NO_ID)
1439 		    : (Player_by_id(ball->id)->team != pl->team)) {
1440 		    ball_dist = LENGTH(pl->pos.cx - ball->pos.cx,
1441 				       pl->pos.cy - ball->pos.cy) / CLICK;
1442 		    if (ball_dist < closest_ball_dist) {
1443 			closest_ball_dist = ball_dist;
1444 			closest_ball = i;
1445 		    }
1446 		}
1447 	    }
1448 	}
1449 	if (closest_ball == NO_IND
1450 	    && closest_ntr_dist < (my_data->robot_count / 10) * BLOCK_SZ) {
1451 	    SET_BIT(my_data->longterm_mode, FETCH_TREASURE);
1452 	    return Check_robot_target(pl,
1453 				      Treasure_by_index(closest_ntr)->pos,
1454 				      RM_NAVIGATE);
1455 	} else if (closest_ball_dist < (my_data->robot_count / 10) * BLOCK_SZ
1456 		   && closest_ball_dist > options.ballConnectorLength) {
1457 	    SET_BIT(my_data->longterm_mode, FETCH_TREASURE);
1458 	    return Check_robot_target(pl,
1459 				      Obj[closest_ball]->pos,
1460 				      RM_NAVIGATE);
1461 	}
1462     }
1463     return false;
1464 }
1465 
Robot_default_play_check_map(player_t * pl)1466 static int Robot_default_play_check_map(player_t *pl)
1467 {
1468     int j, cannon_i = NO_IND, fuel_i = NO_IND, target_i = NO_IND;
1469     double cannon_dist = Visibility_distance;
1470     double fuel_dist = Visibility_distance;
1471     double target_dist = Visibility_distance;
1472     double dcx, dcy, distance;
1473     bool fuel_checked = false;
1474     robot_default_data_t *my_data = Robot_default_get_data(pl);
1475 
1476     for (j = 0; j < Num_fuels(); j++) {
1477 	fuel_t *fs = Fuel_by_index(j);
1478 
1479 	if (fs->fuel < 100.0)
1480 	    continue;
1481 
1482 	if (BIT(world->rules->mode, TEAM_PLAY)
1483 	    && options.teamFuel
1484 	    && fs->team != pl->team)
1485 	    continue;
1486 
1487 	dcx = fs->pos.cx - pl->pos.cx;
1488 	dcy = fs->pos.cy - pl->pos.cy;
1489 	distance = Wrap_length_min(dcx, dcy, fuel_dist * CLICK) / CLICK;
1490 	if (distance < fuel_dist) {
1491 	    fuel_i = j;
1492 	    fuel_dist = distance;
1493 	}
1494     }
1495 
1496     for (j = 0; j < Num_targets(); j++) {
1497 	target_t *targ = Target_by_index(j);
1498 
1499 	/* Ignore dead or owned targets */
1500 	if (targ->dead_ticks > 0
1501 	    || pl->team == targ->team
1502 	    || Team_by_index(targ->team)->NumMembers == 0)
1503 	    continue;
1504 
1505 	dcx = targ->pos.cx - pl->pos.cx;
1506 	dcy = targ->pos.cy - pl->pos.cy;
1507 	distance = Wrap_length_min(dcx, dcy, target_dist * CLICK) / CLICK;
1508 	if (distance < target_dist) {
1509 	    target_i = j;
1510 	    target_dist = distance;
1511 	}
1512     }
1513 
1514 #if 0
1515     if (fuel_i != NO_IND)
1516 	warn("Closest fuel   = %d, distance = %.2f", fuel_i, fuel_dist);
1517     if (target_i != NO_IND)
1518 	warn("Closest target = %d, distance = %.2f", target_i, target_dist);
1519 #endif
1520 
1521     if (fuel_i != NO_IND
1522 	&& (target_dist > fuel_dist
1523 	    || !BIT(world->rules->mode, TEAM_PLAY))
1524 	&& BIT(my_data->longterm_mode, NEED_FUEL)) {
1525 
1526 	fuel_checked = true;
1527 	SET_BIT(pl->used, USES_REFUEL);
1528 	pl->fs = fuel_i;
1529 
1530 	if (Check_robot_target(pl, Fuel_by_index(fuel_i)->pos,
1531 			       RM_REFUEL))
1532 	    return 1;
1533     }
1534     if (target_i != NO_IND) {
1535 	SET_BIT(my_data->longterm_mode, TARGET_KILL);
1536 	if (Check_robot_target(pl, Target_by_index(target_i)->pos,
1537 			       RM_CANNON_KILL))
1538 	    return 1;
1539 
1540 	CLR_BIT(my_data->longterm_mode, TARGET_KILL);
1541     }
1542 
1543     for (j = 0; j < Num_cannons(); j++) {
1544 	cannon_t *cannon = Cannon_by_index(j);
1545 
1546 	if (cannon->dead_ticks > 0)
1547 	    continue;
1548 
1549 	if (BIT(world->rules->mode, TEAM_PLAY)
1550 	    && cannon->team == pl->team)
1551 	    continue;
1552 
1553 	dcx = cannon->pos.cx - pl->pos.cx;
1554 	dcy = cannon->pos.cy - pl->pos.cy;
1555 	distance = Wrap_length_min(dcx, dcy, cannon_dist * CLICK) / CLICK;
1556 	if (distance  < cannon_dist) {
1557 	    cannon_i = j;
1558 	    cannon_dist = distance;
1559 	}
1560     }
1561 
1562 #if 0
1563     if (cannon_i != NO_IND)
1564 	warn("Closest cannon = %d, distance = %.2f", cannon_i, cannon_dist);
1565 #endif
1566 
1567     if (cannon_i != NO_IND) {
1568 	cannon_t *cannon = Cannon_by_index(cannon_i);
1569 	clpos_t d = cannon->pos;
1570 
1571 	d.cx += (click_t)(BLOCK_CLICKS * 0.1 * tcos(cannon->dir));
1572 	d.cy += (click_t)(BLOCK_CLICKS * 0.1 * tsin(cannon->dir));
1573 
1574 	if (Check_robot_target(pl, d, RM_CANNON_KILL))
1575 	    return 1;
1576     }
1577 
1578     if (fuel_i != NO_IND
1579 	&& !fuel_checked
1580 	&& BIT(my_data->longterm_mode, NEED_FUEL)) {
1581 
1582 	SET_BIT(pl->used, USES_REFUEL);
1583 	pl->fs = fuel_i;
1584 
1585 	if (Check_robot_target(pl, Fuel_by_index(fuel_i)->pos,
1586 			       RM_REFUEL))
1587 	    return 1;
1588     }
1589 
1590     return 0;
1591 }
1592 
Robot_default_play_check_objects(player_t * pl,int * item_i,double * item_dist,int * item_imp,int * mine_i,double * mine_dist)1593 static void Robot_default_play_check_objects(player_t *pl,
1594 					     int *item_i, double *item_dist,
1595 					     int *item_imp,
1596 					     int *mine_i, double *mine_dist)
1597 {
1598     int j, obj_count;
1599     object_t *shot, **obj_list;
1600     double distance, shield_range;
1601     long killing_shots;
1602     robot_default_data_t *my_data = Robot_default_get_data(pl);
1603 
1604     /*-BA Neural overload - if NumObjs too high, only consider
1605      *-BA max_objs many objects - improves performance under nukes
1606      *-BA 1000 is a fairly arbitrary choice.  If you wish to tune it,
1607      *-BA take into account the following.  A 4 mine cluster nuke produces
1608      *-BA about 4000 short lived objects.  An 8 mine cluster nuke produces
1609      *-BA about 14000 short lived objects.  By default, there is a limit
1610      *-BA of about 16000 objects.  Each player/robot produces between
1611      *-BA 20 and 40 objects just thrusting, and up to perhaps another 100
1612      *-BA by firing.  If the number is set too low the robots just fly
1613      *-BA around with thier shields on looking stupid and not doing
1614      *-BA much.  If too high, your system will slow down too much when
1615      *-BA the cluster nukes start going off.
1616      */
1617     const int                   max_objs = 1000;
1618 
1619     killing_shots = KILLING_SHOTS;
1620     if (options.treasureCollisionMayKill)
1621 	killing_shots |= OBJ_BALL_BIT;
1622     if (options.wreckageCollisionMayKill)
1623 	killing_shots |= OBJ_WRECKAGE_BIT;
1624     if (options.asteroidCollisionMayKill)
1625 	killing_shots |= OBJ_ASTEROID_BIT;
1626 
1627     if (NumObjs >= options.cellGetObjectsThreshold)
1628 	Cell_get_objects(pl->pos, (int)(Visibility_distance / BLOCK_SZ),
1629 			 max_objs, &obj_list, &obj_count);
1630     else {
1631 	obj_list = Obj;
1632 	obj_count = NumObjs;
1633     }
1634 
1635     for (j = 0; j < obj_count; j++) {
1636 	int dx, dy;
1637 
1638 	shot = obj_list[j];
1639 
1640 	/* Get rid of the most common object types first for speed. */
1641 	if (shot->type == OBJ_SPARK
1642 	    || shot->type == OBJ_DEBRIS)
1643 	    continue;
1644 
1645 	if (shot->type == OBJ_BALL
1646 	    && !WITHIN(my_data->last_thrown_ball,
1647 		       my_data->robot_count,
1648 		       3 * FPS))
1649 	    SET_BIT(pl->used, USES_CONNECTOR);
1650 
1651 	/* Ignore shots and laser pulses if shields already up
1652 	   - nothing else to do anyway */
1653 	if ((shot->type == OBJ_SHOT
1654 	     || shot->type == OBJ_CANNON_SHOT
1655 	     || shot->type == OBJ_PULSE)
1656 	    && BIT(pl->used, HAS_SHIELD))
1657 	    continue;
1658 
1659 	dx = CLICK_TO_PIXEL(shot->pos.cx - pl->pos.cx);
1660 	dy = CLICK_TO_PIXEL(shot->pos.cy - pl->pos.cy);
1661 	dx = WRAP_DX(dx);
1662 	dy = WRAP_DY(dy);
1663 
1664 	/*
1665 	 * The only thing left to do regarding objects is to check if
1666 	 * this robot needs to put up shields to protect against objects.
1667 	 */
1668 	if (!BIT(OBJ_TYPEBIT(shot->type), killing_shots)) {
1669 
1670 	    /* Find closest item */
1671 	    if (shot->type == OBJ_ITEM) {
1672 		itemobject_t *item = ITEM_PTR(shot);
1673 
1674 		if (ABS(dx) < *item_dist
1675 		    && ABS(dy) < *item_dist) {
1676 		    int imp;
1677 
1678 		    if (BIT(item->obj_status, RANDOM_ITEM))
1679 			/* It doesn't know what it is, so get it if it can */
1680 			imp = ROBOT_HANDY_ITEM;
1681 		    else
1682 			imp = Rank_item_value(pl, (enum Item)item->item_type);
1683 
1684 		    if (imp > ROBOT_IGNORE_ITEM && imp >= *item_imp) {
1685 			*item_imp = imp;
1686 			*item_dist = LENGTH(dx, dy);
1687 			*item_i = j;
1688 		    }
1689 		}
1690 	    }
1691 
1692 	    continue;
1693 	}
1694 
1695 	/* Any shot of team members excluding self are passive. */
1696 	if (Team_immune(shot->id, pl->id))
1697 	    continue;
1698 
1699 	/* Self shots may be passive too... */
1700 	if (shot->id == pl->id
1701 	    && options.selfImmunity)
1702 	    continue;
1703 
1704 	/* Own non-reflected laser pulses too. */
1705 	if (shot->type == OBJ_PULSE) {
1706 	    pulseobject_t *pulse = PULSE_PTR(shot);
1707 
1708 	    if (pulse->id == pl->id
1709 		&& !pulse->pulse_refl)
1710 		continue;
1711 	}
1712 
1713 	/* Find nearest missile/mine */
1714 	if (shot->type == OBJ_TORPEDO
1715 	    || shot->type == OBJ_SMART_SHOT
1716 	    || shot->type == OBJ_ASTEROID
1717 	    || shot->type == OBJ_HEAT_SHOT
1718 	    || shot->type == OBJ_BALL
1719 	    || shot->type == OBJ_CANNON_SHOT
1720 	    || (shot->type == OBJ_SHOT
1721 		&& !BIT(world->rules->mode, TIMING)
1722 		&& shot->id != pl->id
1723 		&& shot->id != NO_ID)
1724 	    || (shot->type == OBJ_MINE
1725 		&& shot->id != pl->id)
1726 	    || (shot->type == OBJ_WRECKAGE
1727 		&& !BIT(world->rules->mode, TIMING))) {
1728 	    if (ABS(dx) < *mine_dist
1729 		&&  ABS(dy) < *mine_dist
1730 		&& (distance = LENGTH(dx, dy)) < *mine_dist) {
1731 		*mine_i = j;
1732 		*mine_dist = distance;
1733 	    }
1734 	    if ((dx = (int)((CLICK_TO_PIXEL(shot->pos.cx - pl->pos.cx))
1735 			     + (shot->vel.x - pl->vel.x)),
1736 		    dx = (int)WRAP_DX(dx), ABS(dx)) < *mine_dist
1737 		&& (dy = (int)((CLICK_TO_PIXEL(shot->pos.cy - pl->pos.cy))
1738 				 + (shot->vel.y - pl->vel.y)),
1739 		    dy = (int)WRAP_DY(dy), ABS(dy)) < *mine_dist
1740 		&& (distance = LENGTH(dx, dy)) < *mine_dist) {
1741 		*mine_i = j;
1742 		*mine_dist = distance;
1743 	    }
1744 	}
1745 
1746 	shield_range = 21 + SHIP_SZ + shot->pl_range;
1747 
1748 	if ((dx = (int)(CLICK_TO_PIXEL(shot->pos.cx)
1749 		   + shot->vel.x
1750 		   - (CLICK_TO_PIXEL(pl->pos.cx) + pl->vel.x)),
1751 		dx = WRAP_DX(dx),
1752 		ABS(dx)) < shield_range
1753 	    && (dy = (int)(CLICK_TO_PIXEL(shot->pos.cy) + shot->vel.y
1754 		      - (CLICK_TO_PIXEL(pl->pos.cy) + pl->vel.y)),
1755 		dy = WRAP_DY(dy),
1756 		ABS(dy)) < shield_range
1757 	    && sqr(dx) + sqr(dy) <= sqr(shield_range)
1758 	    && (int)(rfrac() * 100) <
1759 	       (85 + (my_data->defense / 7) - (my_data->attack / 50))) {
1760 	    SET_BIT(pl->used, HAS_SHIELD);
1761 	    Thrust(pl, true);
1762 
1763 	    if ((shot->type == OBJ_TORPEDO
1764 		 || shot->type == OBJ_SMART_SHOT
1765 		 || shot->type == OBJ_ASTEROID
1766 		 || shot->type == OBJ_HEAT_SHOT
1767 		 || shot->type == OBJ_MINE)
1768 		&& (pl->fuel.sum < my_data->fuel_l3
1769 		    || !BIT(pl->have, HAS_SHIELD))) {
1770 	      if (Initiate_hyperjump(pl))
1771 		  break;
1772 	    }
1773 	}
1774 	if (shot->type == OBJ_SMART_SHOT) {
1775 	    if (*mine_dist < ECM_DISTANCE / 4)
1776 		Fire_ecm(pl);
1777 	}
1778 	if (shot->type == OBJ_MINE) {
1779 	    if (*mine_dist < ECM_DISTANCE / 2)
1780 		Fire_ecm(pl);
1781 	}
1782 	if (shot->type == OBJ_HEAT_SHOT) {
1783 	    Thrust(pl, false);
1784 	    if (pl->fuel.sum < my_data->fuel_l3
1785 		&& pl->fuel.sum > my_data->fuel_l1
1786 		&& pl->fuel.num_tanks > 0)
1787 		Tank_handle_detach(pl);
1788 	}
1789 	if (shot->type == OBJ_ASTEROID) {
1790 	    int delta_dir = 0;
1791 	    wireobject_t *wire = WIRE_PTR(shot);
1792 
1793 	    if (*mine_dist
1794 		> (wire->wire_size == 1 ? 2 : 4) * BLOCK_SZ
1795 		&& *mine_dist < 8 * BLOCK_SZ
1796 		&& (delta_dir = (pl->dir
1797 				 - Wrap_cfindDir(shot->pos.cx - pl->pos.cx,
1798 						 shot->pos.cy - pl->pos.cy))
1799 		    < wire->wire_size * (RES / 10)
1800 		    || delta_dir > RES - wire->wire_size * (RES / 10)))
1801 		SET_BIT(pl->used, HAS_SHOT);
1802 	}
1803     }
1804 
1805     /* Convert *item_i from index in local obj_list[] to index in Obj[] */
1806     if (*item_i >= 0) {
1807 	for (j = 0;
1808 	     (j < NumObjs) && (Obj[j]->id != obj_list[*item_i]->id);
1809 	     j++);
1810 	if (j >= NumObjs)
1811 	    /* Perhaps an error should be printed, too? */
1812 	    *item_i = NO_IND;
1813 	else
1814 	    *item_i = j;
1815     }
1816 
1817 }
1818 
1819 
Robot_default_play(player_t * pl)1820 static void Robot_default_play(player_t *pl)
1821 {
1822     player_t *ship;
1823     double distance, ship_dist, enemy_dist, speed, x_speed, y_speed;
1824     double item_dist, mine_dist, shoot_time;
1825     int j, ship_i, item_imp, enemy_i, item_i, mine_i;
1826     bool harvest_checked, evade_checked, navigate_checked;
1827     robot_default_data_t *my_data = Robot_default_get_data(pl);
1828     itemobject_t *item = NULL;
1829 
1830     if (my_data->robot_count <= 0)
1831 	my_data->robot_count = 1000 + (int)(rfrac() * 32);
1832 
1833     my_data->robot_count--;
1834 
1835     CLR_BIT(pl->used, USES_SHOT|USES_SHIELD|USES_CLOAKING_DEVICE|USES_LASER);
1836     if (BIT(pl->have, HAS_EMERGENCY_SHIELD)
1837 	&& !BIT(pl->used, HAS_EMERGENCY_SHIELD))
1838 	Emergency_shield(pl, true);
1839 
1840     harvest_checked = false;
1841     evade_checked = false;
1842     navigate_checked = false;
1843 
1844     mine_i = NO_IND;
1845     mine_dist = SHIP_SZ + 200;
1846     item_i = NO_IND;
1847     item_dist = Visibility_distance;
1848     item_imp = ROBOT_IGNORE_ITEM;
1849 
1850     if (Player_has_cloaking_device(pl)
1851 	&& pl->fuel.sum > my_data->fuel_l2)
1852 	SET_BIT(pl->used, USES_CLOAKING_DEVICE);
1853 
1854     if (Player_has_emergency_thrust(pl)
1855 	&& !Player_uses_emergency_thrust(pl))
1856 	Emergency_thrust(pl, true);
1857 
1858     if (Player_has_deflector(pl)
1859 	&& !BIT(world->rules->mode, TIMING))
1860 	Deflector(pl, true);
1861 
1862     if (pl->fuel.sum
1863 	<= (BIT(world->rules->mode, TIMING) ? 0 : my_data->fuel_l1))
1864 	Player_self_destruct(pl, true);
1865     else
1866 	Player_self_destruct(pl, false);
1867 
1868     /* blinded by ECM. since we're not supposed to see anything,
1869        put up shields and return */
1870     if (pl->damaged > 0) {
1871 	SET_BIT(pl->used, HAS_SHIELD);
1872 	return;
1873     }
1874 
1875     if (pl->fuel.sum < pl->fuel.max * 0.80) {
1876 	for (j = 0; j < Num_fuels(); j++) {
1877 	    fuel_t *fs = Fuel_by_index(j);
1878 
1879 	    if (BIT(world->rules->mode, TEAM_PLAY)
1880 		&& options.teamFuel
1881 		&& fs->team != pl->team)
1882 		continue;
1883 
1884 	    if ((Wrap_length(pl->pos.cx - fs->pos.cx,
1885 			     pl->pos.cy - fs->pos.cy) <= 90.0 * CLICK)
1886 		&& fs->fuel > REFUEL_RATE * timeStep) {
1887 		pl->fs = j;
1888 		SET_BIT(pl->used, USES_REFUEL);
1889 		break;
1890 	    } else
1891 		CLR_BIT(pl->used, USES_REFUEL);
1892 	}
1893     }
1894 
1895     /* don't turn NEED_FUEL off until refueling stops */
1896     if (pl->fuel.sum < (BIT(world->rules->mode, TIMING) ?
1897 			my_data->fuel_l1 : my_data->fuel_l3))
1898 	SET_BIT(my_data->longterm_mode, NEED_FUEL);
1899     else if (!Player_is_refueling(pl))
1900 	CLR_BIT(my_data->longterm_mode, NEED_FUEL);
1901 
1902     if (BIT(world->rules->mode, TEAM_PLAY)) {
1903 	for (j = 0; j < Num_targets(); j++) {
1904 	    target_t *targ = Target_by_index(j);
1905 
1906 	    if (targ->team == pl->team
1907 		&& targ->damage < TARGET_DAMAGE
1908 		&& targ->dead_ticks >= 0) {
1909 
1910 		if (Wrap_length(pl->pos.cx - targ->pos.cx,
1911 				pl->pos.cy - targ->pos.cy) <= 90.0 * CLICK) {
1912 		    pl->repair_target = j;
1913 		    SET_BIT(pl->used, USES_REPAIR);
1914 		    break;
1915 		}
1916 	    }
1917 	}
1918     }
1919 
1920     Robot_default_play_check_objects(pl,
1921 				     &item_i, &item_dist, &item_imp,
1922 				     &mine_i, &mine_dist);
1923 
1924     if (item_i >= 0)
1925 	item = ITEM_PTR(Obj[item_i]);
1926 
1927     /* make sure robots take off from their bases */
1928     if (QUICK_LENGTH(pl->pos.cx - pl->home_base->pos.cx,
1929 		     pl->pos.cy - pl->home_base->pos.cy) < BLOCK_CLICKS)
1930 	Thrust(pl, true);
1931 
1932     ship_i = NO_IND;
1933     ship_dist = SHIP_SZ * 6;
1934     enemy_i = NO_IND;
1935     if (pl->fuel.sum > my_data->fuel_l3)
1936 	enemy_dist = (BIT(world->rules->mode, LIMITED_VISIBILITY) ?
1937 		      MAX(pl->fuel.sum * ENERGY_RANGE_FACTOR,
1938 			  Visibility_distance)
1939 		      : Max_enemy_distance);
1940     else
1941 	enemy_dist = Visibility_distance;
1942 
1943     if (BIT(pl->used, HAS_SHIELD))
1944 	ship_dist = 0;
1945 
1946     if (BIT(my_data->robot_lock, LOCK_PLAYER)) {
1947 	ship = Player_by_id(my_data->robot_lock_id);
1948 	j = GetInd(ship->id);
1949 
1950 	if (Detect_ship(pl, ship)) {
1951 	    distance = Wrap_length(ship->pos.cx - pl->pos.cx,
1952 				   ship->pos.cy - pl->pos.cy) / CLICK;
1953 	    if (distance < ship_dist) {
1954 		ship_i = GetInd(my_data->robot_lock_id);
1955 		ship_dist = distance;
1956 	    }
1957 	    if (distance < enemy_dist) {
1958 		enemy_i = j;
1959 		enemy_dist = distance;
1960 	    }
1961 	}
1962     }
1963 
1964     if (ship_i == NO_IND || enemy_i == NO_IND) {
1965 
1966 	for (j = 0; j < NumPlayers; j++) {
1967 
1968 	    ship = Player_by_index(j);
1969 	    if (j == GetInd(pl->id)
1970 		|| !Player_is_active(ship)
1971 		|| Team_immune(pl->id, ship->id))
1972 		continue;
1973 
1974 	    if (!Detect_ship(pl, ship))
1975 		continue;
1976 
1977 	    distance = Wrap_length(ship->pos.cx - pl->pos.cx,
1978 				   ship->pos.cy - pl->pos.cy) / CLICK;
1979 
1980 	    if (distance < ship_dist) {
1981 		ship_i = j;
1982 		ship_dist = distance;
1983 	    }
1984 
1985 	    if (!BIT(my_data->robot_lock, LOCK_PLAYER)) {
1986 		if ((my_data->robot_count % 3) == 0
1987 		    && ((my_data->robot_count % 100) < my_data->attack)
1988 		    && distance < enemy_dist) {
1989 		    enemy_i    = j;
1990 		    enemy_dist = distance;
1991 		}
1992 	    }
1993 	}
1994     }
1995 
1996     if (ship_dist < 3*SHIP_SZ && BIT(pl->have, HAS_SHIELD))
1997 	SET_BIT(pl->used, HAS_SHIELD);
1998 
1999     if (ship_dist <= 10*BLOCK_SZ && pl->fuel.sum <= my_data->fuel_l3
2000 	&& !BIT(world->rules->mode, TIMING)) {
2001 	if (Initiate_hyperjump(pl))
2002 	    return;
2003     }
2004 
2005     if (ship_i != NO_IND
2006 	&& BIT(my_data->robot_lock, LOCK_PLAYER)
2007 	&& my_data->robot_lock_id == Player_by_index(ship_i)->id)
2008 	ship_i = NO_IND; /* don't avoid target */
2009 
2010     if (enemy_i >= 0) {
2011 	ship = Player_by_index(enemy_i);
2012 	if (!BIT(pl->lock.tagged, LOCK_PLAYER)
2013 	    || (enemy_dist < pl->lock.distance/2
2014 		&& (BIT(world->rules->mode, TIMING) ?
2015 		    (ship->check >= pl->check
2016 		     && ship->round >= pl->round) : 1))
2017 	    || (enemy_dist < pl->lock.distance*2
2018 		&& BIT(world->rules->mode, TEAM_PLAY)
2019 		&& BIT(ship->have, HAS_BALL))
2020 	    || Get_Score(ship) > Get_Score(Player_by_id(pl->lock.pl_id))) {
2021 	    pl->lock.pl_id = ship->id;
2022 	    SET_BIT(pl->lock.tagged, LOCK_PLAYER);
2023 	    pl->lock.distance = enemy_dist;
2024 	    Compute_sensor_range(pl);
2025 	}
2026     }
2027 
2028     if (BIT(pl->lock.tagged, LOCK_PLAYER)) {
2029 	int delta_dir;
2030 
2031 	ship = Player_by_id(pl->lock.pl_id);
2032 	delta_dir = (int)(pl->dir - Wrap_cfindDir(ship->pos.cx - pl->pos.cx,
2033 						  ship->pos.cy - pl->pos.cy));
2034 	delta_dir = MOD2(delta_dir, RES);
2035 	if (!Player_is_active(ship)
2036 	    || (BIT(my_data->robot_lock, LOCK_PLAYER)
2037 		&& my_data->robot_lock_id != pl->lock.pl_id
2038 		&& Player_is_active(Player_by_id(my_data->robot_lock_id)))
2039 	    || !Detect_ship(pl, ship)
2040 	    || (pl->fuel.sum <= my_data->fuel_l3
2041 		&& !BIT(world->rules->mode, TIMING))
2042 	    || (BIT(world->rules->mode, TIMING)
2043 		&& (delta_dir < 3 * RES / 4 || delta_dir > RES / 4))
2044 	    || Team_immune(pl->id, ship->id)) {
2045 	    /* unset the player lock */
2046 	    CLR_BIT(pl->lock.tagged, LOCK_PLAYER);
2047 	    pl->lock.pl_id = 1;
2048 	    pl->lock.distance = 0;
2049 	}
2050     }
2051     if (!evade_checked) {
2052 	if (Check_robot_evade(pl, mine_i, ship_i)) {
2053 	    if (!options.allowShields
2054 		&& options.playerStartsShielded
2055 		&& BIT(pl->have, HAS_SHIELD))
2056 		SET_BIT(pl->used, HAS_SHIELD);
2057 	    else if (options.maxShieldedWallBounceSpeed >
2058 		    options.maxUnshieldedWallBounceSpeed
2059 		&& BIT(pl->have, HAS_SHIELD))
2060 		SET_BIT(pl->used, HAS_SHIELD);
2061 	    return;
2062 	}
2063     }
2064     if (BIT(world->rules->mode, TIMING) && !navigate_checked) {
2065 	int delta_dir;
2066 
2067 	if (item != NULL) {
2068 	    delta_dir =
2069 		(int)(pl->dir
2070 		      - Wrap_cfindDir(item->pos.cx - pl->pos.cx,
2071 				      item->pos.cy - pl->pos.cy));
2072 	    delta_dir = MOD2(delta_dir, RES);
2073 	} else {
2074 	    delta_dir = RES;
2075 	    item_imp = ROBOT_IGNORE_ITEM;
2076 	}
2077 	if ((item_imp == ROBOT_MUST_HAVE_ITEM && item_dist > 4 * BLOCK_SZ)
2078 	    || (item_imp == ROBOT_HANDY_ITEM && item_dist > 2 * BLOCK_SZ)
2079 	    || (item_imp == ROBOT_IGNORE_ITEM)
2080 	    || (delta_dir < 3 * RES / 4 && delta_dir > RES / 4)) {
2081 	    navigate_checked = true;
2082 	    if (Check_robot_target(pl, Check_by_index(pl->check)->pos,
2083 				   RM_NAVIGATE))
2084 		return;
2085 	}
2086     }
2087     if (item != NULL
2088 	&& 3*enemy_dist > 2*item_dist
2089 	&& item_dist < 12*BLOCK_SZ
2090 	&& !BIT(my_data->longterm_mode, FETCH_TREASURE)
2091 	&& (!BIT(my_data->longterm_mode, NEED_FUEL)
2092 	    || item->item_type == ITEM_FUEL
2093 	    || item->item_type == ITEM_TANK)) {
2094 
2095 	if (item_imp != ROBOT_IGNORE_ITEM) {
2096 	    clpos_t d = item->pos;
2097 
2098 	    harvest_checked = true;
2099 	    d.cx += (int)(item->vel.x
2100 			   * (ABS(d.cx - pl->pos.cx) /
2101 			      my_data->robot_normal_speed));
2102 	    d.cy += (int)(item->vel.y
2103 			   * (ABS(d.cy - pl->pos.cy) /
2104 			      my_data->robot_normal_speed));
2105 	    if (Check_robot_target(pl, d, RM_HARVEST))
2106 		return;
2107 	}
2108     }
2109     if (BIT(pl->lock.tagged, LOCK_PLAYER) &&
2110 	Detect_ship(pl, Player_by_id(pl->lock.pl_id))) {
2111 	clpos_t d;
2112 
2113 	ship = Player_by_id(pl->lock.pl_id);
2114 	shoot_time = (int)(pl->lock.distance / (options.shotSpeed + 1));
2115 	d.cx = (int)(ship->pos.cx + ship->vel.x * shoot_time * CLICK);
2116 	d.cy = (int)(ship->pos.cy + ship->vel.y * shoot_time * CLICK);
2117 	/*-BA Also allow for our own momentum. */
2118 	d.cx -= (int)(pl->vel.x * shoot_time * CLICK);
2119 	d.cy -= (int)(pl->vel.y * shoot_time * CLICK);
2120 
2121 	if (Check_robot_target(pl, d, RM_ATTACK)
2122 	    && !BIT(my_data->longterm_mode, FETCH_TREASURE
2123 					    |TARGET_KILL
2124 					    |NEED_FUEL))
2125 	    return;
2126     }
2127     if (BIT(world->rules->mode, TEAM_PLAY)
2128 	&& Num_treasures() > 0
2129 	&& world->teams[pl->team].NumTreasures > 0
2130 	&& !navigate_checked
2131 	&& !BIT(my_data->longterm_mode, TARGET_KILL|NEED_FUEL)) {
2132 	navigate_checked = true;
2133 	if (Ball_handler(pl))
2134 	    return;
2135     }
2136     if (item_i >= 0
2137 	&& !harvest_checked
2138 	&& item_dist < 12*BLOCK_SZ) {
2139 
2140 	if (item_imp != ROBOT_IGNORE_ITEM) {
2141 	    clpos_t d = Obj[item_i]->pos;
2142 
2143 	    d.cx += (int)(Obj[item_i]->vel.x
2144 			  * (ABS(d.cx - pl->pos.cx) /
2145 			     my_data->robot_normal_speed));
2146 	    d.cy += (int)(Obj[item_i]->vel.y
2147 			  * (ABS(d.cy - pl->pos.cy) /
2148 			     my_data->robot_normal_speed));
2149 
2150 	    if (Check_robot_target(pl, d, RM_HARVEST))
2151 		return;
2152 	}
2153     }
2154 
2155     if (Check_robot_hunt(pl)) {
2156 	if (!options.allowShields
2157 	    && options.playerStartsShielded
2158 	    && BIT(pl->have, HAS_SHIELD))
2159 	    SET_BIT(pl->used, HAS_SHIELD);
2160 	return;
2161     }
2162 
2163     if (Robot_default_play_check_map(pl) == 1)
2164 	return;
2165 
2166     if (!options.allowShields
2167 	&& options.playerStartsShielded
2168 	&& BIT(pl->have, HAS_SHIELD))
2169 	SET_BIT(pl->used, HAS_SHIELD);
2170 
2171     x_speed = pl->vel.x - 2 * World_gravity(pl->pos).x;
2172     y_speed = pl->vel.y - 2 * World_gravity(pl->pos).y;
2173 
2174     if (y_speed < (-my_data->robot_normal_speed)
2175 	|| (my_data->robot_count % 64) < 32) {
2176 
2177 	my_data->robot_mode = RM_ROBOT_CLIMB;
2178 	pl->turnspeed = MAX_PLAYER_TURNSPEED / 2;
2179 	pl->power = MAX_PLAYER_POWER / 2;
2180 	if (ABS(pl->dir - RES / 4) > RES / 16)
2181 	    pl->turnacc = (pl->dir < RES / 4
2182 			   || pl->dir >= 3 * RES / 4
2183 			   ? pl->turnspeed : (-pl->turnspeed));
2184 	else
2185 	    pl->turnacc = 0.0;
2186 
2187 	if (y_speed < my_data->robot_normal_speed / 2
2188 	    && pl->velocity < my_data->robot_attack_speed)
2189 	    Thrust(pl, true);
2190 	else if (y_speed > my_data->robot_normal_speed)
2191 	    Thrust(pl, false);
2192 	return;
2193     }
2194     my_data->robot_mode = RM_ROBOT_IDLE;
2195     pl->turnspeed = MAX_PLAYER_TURNSPEED / 2;
2196     pl->turnacc = 0;
2197     pl->power = MAX_PLAYER_POWER / 2;
2198     Thrust(pl, false);
2199     speed = LENGTH(x_speed, y_speed);
2200     if (speed < my_data->robot_normal_speed / 2)
2201 	Thrust(pl, true);
2202     else if (speed > my_data->robot_normal_speed)
2203 	Thrust(pl, false);
2204 }
2205 
2206 
2207 /*
2208  * This is called each round.
2209  * It allows us to adjust our file local parameters.
2210  */
Robot_default_round_tick(void)2211 static void Robot_default_round_tick(void)
2212 {
2213     double min_visibility = 256.0;
2214     double min_enemy_distance = 512.0;
2215 
2216     /* reduce visibility when there are a lot of robots. */
2217     Visibility_distance = min_visibility
2218 	+ (((VISIBILITY_DISTANCE - min_visibility)
2219 	    * (NUM_IDS - NumRobots)) / NUM_IDS);
2220 
2221     /* limit distance to allowable enemies. */
2222     Max_enemy_distance = world->hypotenuse;
2223     if (world->hypotenuse > Visibility_distance)
2224 	Max_enemy_distance = min_enemy_distance
2225 	    + (((world->hypotenuse - min_enemy_distance)
2226 		* (NUM_IDS - NumRobots)) / NUM_IDS);
2227 }
2228