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