1 /*
2 * Copyright (c) 1994, 2002, 2003 Johannes Prix
3 * Copyright (c) 1994, 2002 Reinhard Prix
4 * Copyright (c) 2004-2010 Arthur Huillet
5 *
6 *
7 * This file is part of Freedroid
8 *
9 * Freedroid is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * Freedroid is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with Freedroid; see the file COPYING. If not, write to the
21 * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
22 * MA 02111-1307 USA
23 *
24 */
25
26 /**
27 * This file contains all enemy related functions. This includes their
28 * whole behavior, healing, initialization, shuffling them around after
29 * elevator-transitions, deleting them, collisions of enemies among
30 * themselves, their fireing, animation and such.
31 */
32
33 #define _enemy_c
34
35 #include "system.h"
36
37 #include "defs.h"
38 #include "struct.h"
39 #include "global.h"
40 #include "proto.h"
41
42 #define COL_SPEED 3
43 #define IS_FRIENDLY_EYE_DISTANCE (2.0)
44
45 static int next_bot_id = 1; // defines the id of the next created enemy.
46
47 static int TurnABitTowardsPosition(Enemy, float, float, float);
48 static void MoveToMeleeCombat(Enemy, gps *, moderately_finepoint *);
49 static void MoveAwayFromMeleeCombat(Enemy, moderately_finepoint *);
50 static void ReachMeleeCombat(Enemy, gps *, moderately_finepoint *, pathfinder_context *);
51 static void RawStartEnemysShot(enemy *, float, float);
52 static int is_potential_target(enemy * this_robot, gps * target_pos, float *squared_best_dist);
53 static int can_see_tux(enemy *);
54
55 LIST_HEAD(alive_bots_head);
56 LIST_HEAD(dead_bots_head);
57 list_head_t level_bots_head[MAX_LEVELS]; //THIS IS NOT STATICALLY PROPERLY INITIALIZED, done in init functions
58
59 /* Definition of the sensors. The flag_set values must be exclusive,
60 * so that given a flag_set we can get a unique associated name. */
61
62 struct {
63 char *name;
64 int flag_set;
65 } enemy_sensors[] = {
66 { "infrared", SENSOR_DETECT_INVISIBLE },
67 { "xray", SENSOR_THROUGH_WALLS },
68 { "radar", SENSOR_DETECT_INVISIBLE | SENSOR_THROUGH_WALLS },
69 { "spectral", SENSOR_FEATURELESS }
70 };
71
72
teleport_to_waypoint(enemy * robot,level * lvl,int wp_idx)73 static void teleport_to_waypoint(enemy *robot, level *lvl, int wp_idx)
74 {
75 waypoint *wpts = lvl->waypoints.arr;
76
77 // Teleport the robot to the waypoint
78 robot->pos.x = wpts[wp_idx].x + 0.5;
79 robot->pos.y = wpts[wp_idx].y + 0.5;
80 robot->pos.z = lvl->levelnum;
81 robot->nextwaypoint = wp_idx;
82 robot->lastwaypoint = wp_idx;
83 }
84
85 /**
86 * Find the closest waypoint to a bot.
87 */
enemy_find_closest_waypoint(struct enemy * this_bot)88 static int enemy_find_closest_waypoint(struct enemy *this_bot)
89 {
90 struct level *lvl = curShip.AllLevels[this_bot->pos.z];
91 struct waypoint *wpts = lvl->waypoints.arr;
92
93 int i;
94 float distance = 0.0;
95 float best_distance = 10000.0;
96 int best_waypoint = -1;
97
98 for (i = 0; i < lvl->waypoints.size; i++) {
99 // A standard bot cannot use a special waypoint
100 if (!this_bot->SpecialForce && wpts[i].suppress_random_spawn)
101 continue;
102
103 // If no best_waypoint is already registered, use the current one as
104 // a fallback result.
105 if (best_waypoint == -1)
106 best_waypoint = i;
107
108 // Check if current waypoint is closer than the stored one.
109 distance = (this_bot->pos.x - wpts[i].x + 0.5) * (this_bot->pos.x - wpts[i].x + 0.5) +
110 (this_bot->pos.y - wpts[i].y + 0.5) * (this_bot->pos.y - wpts[i].y + 0.5);
111 if (distance <= best_distance) {
112 best_distance = distance;
113 best_waypoint = i;
114 }
115 }
116
117 if (best_waypoint == -1)
118 error_message(__FUNCTION__, "Found no closest waypoint on level %d.", PLEASE_INFORM, lvl->levelnum);
119
120 return best_waypoint;
121 }
122
123 /**
124 * In the very beginning of each game, it is not enough to just place the
125 * bots onto the right locations. They must also be integrated into the
126 * waypoint system, i.e. current waypoint and next waypoint initialized.
127 */
teleport_to_closest_waypoint(struct enemy * ThisRobot)128 int teleport_to_closest_waypoint(struct enemy *ThisRobot)
129 {
130 struct level *lvl = curShip.AllLevels[ThisRobot->pos.z];
131
132 // Find the closest waypoint
133 int best_waypoint = enemy_find_closest_waypoint(ThisRobot);
134
135 if (best_waypoint >= 0) {
136 // Teleport the robot to the best waypoint
137 teleport_to_waypoint(ThisRobot, lvl, best_waypoint);
138 } else {
139 // No waypoint found, make this bot wander
140 ThisRobot->combat_state = WAYPOINTLESS_WANDERING;
141 }
142
143 return best_waypoint;
144 }
145
146 /**
147 * Randomly teleports a standard bot to a free (not already occupied) waypoint.
148 *
149 * A random waypoint is chosen. All waypoints are examined in turn, starting
150 * from the random one, until a free one is found.
151 * If no free waypoint is found, the bot is teleported to the last checked
152 * waypoint.
153 *
154 * Note: only the waypoints not forbidden for randomly placed bots are scanned.
155 */
teleport_to_random_waypoint(enemy * erot,level * this_level,char * wp_used)156 int teleport_to_random_waypoint(enemy *erot, level *this_level, char *wp_used)
157 {
158 int start_wp = MyRandom(this_level->waypoints.size - 1);
159 waypoint *wpts = this_level->waypoints.arr;
160 int current_wp = start_wp;
161 int last_checked_wp = -1;
162 int found_wp = -1;
163
164 // Find a random waypoint
165 do {
166 if (!wpts[current_wp].suppress_random_spawn) {
167 last_checked_wp = current_wp;
168 if (!wp_used[current_wp]) {
169 found_wp = current_wp;
170 break;
171 }
172 }
173 // next waypoint, going to 0 if at end of list
174 current_wp++;
175 if (current_wp == this_level->waypoints.size)
176 current_wp = 0;
177 } while (found_wp == -1 && current_wp != start_wp); // stop when found, or when all waypoints have been scanned
178
179 // Use a fallback waypoint if no free waypoint is found
180 if (found_wp == -1) {
181 if (last_checked_wp == -1) {
182 error_message(__FUNCTION__, "All waypoints on level %d are forbidden for random bots. Something is wrong."
183 " Forcing the bot to teleport to a forbidden waypoint.",
184 PLEASE_INFORM, this_level->levelnum);
185 found_wp = start_wp;
186 } else {
187 error_message(__FUNCTION__, "There was no free waypoint found on level %d to place another random bot.",
188 NO_REPORT, this_level->levelnum);
189 found_wp = last_checked_wp;
190 }
191 }
192
193 wp_used[found_wp] = 1;
194
195 // Teleport the robot to the found waypoint
196 teleport_to_waypoint(erot, this_level, found_wp);
197
198 return found_wp;
199 }
200
201 /**
202 * This function teleports an enemy to a new position on the
203 * map.
204 */
teleport_enemy(enemy * robot,int z,float x,float y)205 void teleport_enemy(enemy *robot, int z, float x, float y)
206 {
207 // Check the validity of the teleport destination
208 if (!level_exists(z) || !pos_inside_level(x, y, curShip.AllLevels[z])) {
209 error_message(__FUNCTION__, "\
210 Trying to teleport NPC (dialog name %s) from x=%f y=%f level=%d to x=%f y=%f level=%d\n\
211 is not possible because the target location is not valid.",
212 PLEASE_INFORM, robot->dialog_section_name, robot->pos.x, robot->pos.y, robot->pos.z, x, y, z);
213 return;
214 }
215
216 // Does the robot change level?
217 if (z != robot->pos.z) {
218 robot->pos.z = z;
219
220 // Add the bot on the new level
221 list_move(&robot->level_list, &(level_bots_head[robot->pos.z]));
222 }
223
224 // Prevent the bot from moving this frame
225 clear_out_intermediate_points(&robot->pos, &robot->PrivatePathway[0], 5);
226
227 // Move the bot
228 robot->pos.x = x;
229 robot->pos.y = y;
230
231 // Reset the bot's waypoints
232 int best_waypoint = enemy_find_closest_waypoint(robot);
233
234 if (best_waypoint >= 0) {
235 robot->homewaypoint = best_waypoint;
236 robot->lastwaypoint = best_waypoint;
237 robot->nextwaypoint = best_waypoint;
238 } else {
239 // No waypoint found, make this bot wander
240 robot->combat_state = WAYPOINTLESS_WANDERING;
241 }
242 }
243
244 /**
245 * Enemies recover with time, independently of the current frame rate.
246 */
heal_robots_over_time(void)247 static void heal_robots_over_time(void)
248 {
249 static float time_since_last_heal = 0;
250 #define HEAL_INTERVAL (3.0)
251
252 // Heal the bots every HEAL_INTERVAL seconds
253 time_since_last_heal += Frame_Time();
254 if (time_since_last_heal < HEAL_INTERVAL)
255 return;
256 time_since_last_heal = 0;
257
258 enemy *erot;
259 BROWSE_ALIVE_BOTS(erot) {
260 float heal_factor;
261 if (is_friendly(erot->faction, FACTION_SELF))
262 heal_factor = Droidmap[erot->type].healing_friendly;
263 else
264 heal_factor = Droidmap[erot->type].healing_hostile;
265 if (erot->energy < Droidmap[erot->type].maxenergy)
266 erot->energy += heal_factor * HEAL_INTERVAL;
267 if (erot->energy > Droidmap[erot->type].maxenergy)
268 erot->energy = Droidmap[erot->type].maxenergy;
269 }
270 }
271
272 /**
273 * This function resets the 'transient state' of a bot.
274 * (see the 'struct enemy' comments)
275 */
enemy_reset(enemy * this_enemy)276 void enemy_reset(enemy *this_enemy)
277 {
278 int j;
279
280 this_enemy->speed.x = this_enemy->speed.y = 0.0;
281 this_enemy->energy = Droidmap[this_enemy->type].maxenergy;
282 this_enemy->animation_phase = 0;
283 this_enemy->animation_type = WALK_ANIMATION;
284 this_enemy->frozen = 0.0;
285 this_enemy->poison_duration_left = 0.0;
286 this_enemy->poison_damage_per_sec = 0.0;
287 this_enemy->paralysation_duration_left = 0.0;
288 this_enemy->pure_wait = 0.0;
289 this_enemy->firewait = 0.0;
290 this_enemy->ammo_left = ItemMap[Droidmap[this_enemy->type].weapon_item.type].weapon_ammo_clip_size;
291 this_enemy->attack_target_type = ATTACK_TARGET_IS_NOTHING;
292 enemy_set_reference(&this_enemy->bot_target_n, &this_enemy->bot_target_addr, NULL);
293 this_enemy->previous_angle = 0.0;
294 this_enemy->current_angle = 0.0;
295 this_enemy->previous_phase = 0.0;
296 this_enemy->last_phase_change = WAIT_BEFORE_ROTATE + 1.0;
297 this_enemy->last_combat_step = ATTACK_MOVE_RATE + 1.0;
298 this_enemy->TextVisibleTime = 0.0;
299 this_enemy->TextToBeDisplayed = NULL;
300 for (j = 0; j < 5; j++) {
301 this_enemy->PrivatePathway[j].x = -1;
302 this_enemy->PrivatePathway[j].y = -1;
303 }
304 this_enemy->bot_stuck_in_wall_at_previous_check = FALSE;
305 this_enemy->time_since_previous_stuck_in_wall_check = ((float)MyRandom(1000)) / 1000.1;
306 }
307
308 /**
309 * This function prepares the droid's fabric to create a whole new
310 * set of droids.
311 */
enemy_reset_fabric()312 void enemy_reset_fabric()
313 {
314 next_bot_id = 1;
315 }
316
317 /**
318 * This function creates a new enemy, and initializes its 'identity' and
319 * 'global state'.
320 * (see the 'struct enemy' comments)
321 */
enemy_new(int type)322 enemy *enemy_new(int type)
323 {
324 enemy *this_enemy = (enemy*)MyMalloc(sizeof(enemy));
325 memset(this_enemy, 0, sizeof(enemy));
326
327 this_enemy->id = next_bot_id++;
328 this_enemy->type = type;
329
330 // Init 'identity' attributes.
331 this_enemy->SpecialForce = FALSE;
332 this_enemy->marker = 0;
333 this_enemy->max_distance_to_home = 0;
334 this_enemy->dialog_section_name = NULL;
335 this_enemy->short_description_text = strdup(Droidmap[this_enemy->type].default_short_description);
336 this_enemy->on_death_drop_item_code = -1;
337 this_enemy->sensor_id = Droidmap[this_enemy->type].sensor_id;
338
339 // Set the default value of the 'global state' attributes
340 this_enemy->faction = FACTION_BOTS;
341 this_enemy->will_respawn = TRUE;
342 this_enemy->will_rush_tux = FALSE;
343 this_enemy->combat_state = WAYPOINTLESS_WANDERING;
344 this_enemy->state_timeout = 0.0;
345 this_enemy->CompletelyFixed = 0;
346 this_enemy->has_been_taken_over = FALSE;
347 this_enemy->follow_tux = FALSE;
348 this_enemy->has_greeted_influencer = FALSE;
349 this_enemy->homewaypoint = this_enemy->lastwaypoint = this_enemy->nextwaypoint = -1;
350
351 return this_enemy;
352 }
353
354 /**
355 * Insert an enemy into the global lists and the level lists, depending on its
356 * state (living or dead)
357 */
enemy_insert_into_lists(enemy * this_enemy,int is_living)358 void enemy_insert_into_lists(enemy *this_enemy, int is_living)
359 {
360 list_add(&(this_enemy->global_list), is_living ? &alive_bots_head : &dead_bots_head);
361 if (is_living) {
362 list_add(&this_enemy->level_list, &level_bots_head[this_enemy->pos.z]);
363 }
364 }
365
366 /*
367 * Free an enemy data structure
368 */
enemy_free(enemy * e)369 void enemy_free(enemy *e)
370 {
371 if (e->dialog_section_name) {
372 free(e->dialog_section_name);
373 e->dialog_section_name = NULL;
374 }
375 if (e->short_description_text) {
376 free(e->short_description_text);
377 e->short_description_text = NULL;
378 }
379
380 free(e);
381 }
382
383 /**
384 * This function removes all enemy entries from the list of the
385 * enemies.
386 */
clear_enemies(void)387 void clear_enemies(void)
388 {
389 enemy *erot, *nerot;
390
391 int i;
392 for (i = 0; i < MAX_LEVELS; i++) {
393 INIT_LIST_HEAD(&level_bots_head[i]);
394 }
395
396 BROWSE_ALIVE_BOTS_SAFE(erot, nerot) {
397 list_del(&erot->global_list);
398 enemy_free(erot);
399 }
400
401 BROWSE_DEAD_BOTS_SAFE(erot, nerot) {
402 list_del(&erot->global_list);
403 enemy_free(erot);
404 }
405
406 INIT_LIST_HEAD(&alive_bots_head);
407 INIT_LIST_HEAD(&dead_bots_head);
408
409 enemy_reset_fabric();
410 }
411
412 /** Helper to modify the enemy state
413 * with a constant set of names.
414 */
enemy_set_state(enemy * en,const char * cmd)415 void enemy_set_state(enemy *en, const char *cmd)
416 {
417 if (!strcmp(cmd, "follow_tux")) {
418 en->follow_tux = TRUE;
419 en->CompletelyFixed = FALSE;
420 } else if (!strcmp(cmd, "fixed")) {
421 en->follow_tux = FALSE;
422 en->CompletelyFixed = TRUE;
423 } else if (!strcmp(cmd, "free")) {
424 en->follow_tux = FALSE;
425 en->CompletelyFixed = FALSE;
426 } else if (!strcmp(cmd, "home")) {
427 en->follow_tux = FALSE;
428 en->CompletelyFixed = FALSE;
429 en->combat_state = RETURNING_HOME;
430 } else if (!strcmp(cmd, "patrol")) {
431 en->follow_tux = FALSE;
432 en->CompletelyFixed = FALSE;
433 en->combat_state = SELECT_NEW_WAYPOINT;
434 } else {
435 error_message(__FUNCTION__,
436 "I was called with an invalid state named %s. Accepted values are \"follow_tux\", \"fixed\", \"free\", \"home\", and \"patrol\".",
437 PLEASE_INFORM, cmd);
438 }
439 }
440
enemy_set_destination(enemy * en,const char * label)441 int enemy_set_destination(enemy *en, const char *label)
442 {
443 gps dest_pos = get_map_label_center(label);
444 struct level *lvl = curShip.AllLevels[dest_pos.z];
445 int destinationwaypoint = get_waypoint(lvl, dest_pos.x, dest_pos.y);
446
447 if (dest_pos.z != en->pos.z) {
448 error_message(__FUNCTION__, "\
449 Sending bot %s to map label %s (found on level %d) cannot be done because the bot\n\
450 is not on the same level (z = %d). Doing nothing.",
451 PLEASE_INFORM, en->dialog_section_name, label, dest_pos.z, en->pos.z);
452 return 0;
453 }
454
455 if (destinationwaypoint == -1) {
456 error_message(__FUNCTION__, "\
457 Map label %s (found on level %d) does not have a waypoint. Cannot send bot %s\n\
458 to this location. Doing nothing.\n\
459 GPS center coordinates x=%f, y=%f.",
460 PLEASE_INFORM, label, dest_pos.z, en->dialog_section_name, dest_pos.x, dest_pos.y);
461 return 0;
462 }
463
464 clear_out_intermediate_points(&en->pos, &en->PrivatePathway[0], 5);
465 en->lastwaypoint = destinationwaypoint;
466 en->nextwaypoint = destinationwaypoint;
467 en->combat_state = TURN_TOWARDS_NEXT_WAYPOINT;
468 return 0;
469 }
470
enemy_get_current_walk_target(enemy * ThisRobot,moderately_finepoint * a)471 static void enemy_get_current_walk_target(enemy *ThisRobot, moderately_finepoint *a)
472 {
473 int count;
474
475 // Get the number of path nodes.
476 for (count = 0; count < 5; count++) {
477 if (ThisRobot->PrivatePathway[count].x == -1)
478 break;
479 }
480
481 // If the path isn't empty, return the coordinates of the last
482 // path node. Otherwise, return the current position of the bot.
483 if (count) {
484 a->x = ThisRobot->PrivatePathway[count - 1].x;
485 a->y = ThisRobot->PrivatePathway[count - 1].y;
486 } else {
487 a->x = ThisRobot->pos.x;
488 a->y = ThisRobot->pos.y;
489 }
490 }
491
492 /**
493 * This function moves one robot in an advanced way, that hasn't been
494 * present within the classical paradroid game.
495 */
remaining_distance_to_current_walk_target(Enemy ThisRobot)496 static float remaining_distance_to_current_walk_target(Enemy ThisRobot)
497 {
498 moderately_finepoint remaining_way;
499
500 enemy_get_current_walk_target(ThisRobot, &remaining_way);
501
502 remaining_way.x -= ThisRobot->pos.x;
503 remaining_way.y -= ThisRobot->pos.y;
504
505 return (sqrt(remaining_way.x * remaining_way.x + remaining_way.y * remaining_way.y));
506
507 }; // float remaining_distance_to_current_walk_target ( Enemy ThisRobot )
508
509 /**
510 *
511 *
512 */
DetermineAngleOfFacing(enemy * e)513 static void DetermineAngleOfFacing(enemy * e)
514 {
515 // The phase now depends upon the direction this robot
516 // is heading.
517 //
518 // We calculate the angle of the vector, but only if the robot has at least
519 // some minimal speed. If not, simply the previous angle will be used again.
520 //
521 if ((fabs(e->speed.y) > 0.03) || (fabs(e->speed.x) > 0.03)) {
522 e->current_angle = 180 - (atan2(e->speed.y, e->speed.x) * 180 / M_PI + 90);
523 e->previous_angle = e->current_angle;
524 } else {
525 e->current_angle = e->previous_angle;
526 }
527 }; // void DetermineAngleOfFacing ( int EnemyNum )
528
529 /**
530 * Once the next waypoint or the next private pathway point has been
531 * selected, this generic low_level movement function can be called to
532 * actually move the robot towards this spot.
533 */
move_enemy_to_spot(Enemy ThisRobot,moderately_finepoint next_target_spot)534 static void move_enemy_to_spot(Enemy ThisRobot, moderately_finepoint next_target_spot)
535 {
536 moderately_finepoint remaining_way;
537 float maxspeed;
538 int old_map_level;
539
540 // According to properties of the robot like being frozen or not,
541 // we define the maximum speed of this machine for later use...
542 // A frozen robot is slow while a paralyzed robot can do absolutely nothing.
543 //
544 // if ( ThisRobot -> paralysation_duration_left != 0 ) return;
545
546 if (ThisRobot->frozen == 0)
547 maxspeed = Droidmap[ThisRobot->type].maxspeed;
548 else
549 maxspeed = 0.2 * Droidmap[ThisRobot->type].maxspeed;
550
551 // While getting hit, the bot or person shouldn't be running, but
552 // when standing, it should move over to the 'walk' animation type...
553 //
554 if (ThisRobot->animation_type == GETHIT_ANIMATION)
555 return;
556 if (ThisRobot->animation_type == STAND_ANIMATION) {
557 ThisRobot->animation_type = WALK_ANIMATION;
558 ThisRobot->animation_phase = 0.0;
559 }
560
561 remaining_way.x = next_target_spot.x - ThisRobot->pos.x;
562 remaining_way.y = next_target_spot.y - ThisRobot->pos.y;
563
564 float squared_length = remaining_way.x * remaining_way.x + remaining_way.y * remaining_way.y;
565 gps newpos = ThisRobot->pos;
566
567 if (squared_length < DIST_TO_INTERM_POINT * DIST_TO_INTERM_POINT) {
568 ThisRobot->speed.x = 0;
569 ThisRobot->speed.y = 0;
570 newpos.x = next_target_spot.x;
571 newpos.y = next_target_spot.y;
572 } else {
573 if ((Frame_Time() > 0.001)) {
574 float length = sqrt(squared_length);
575
576 ThisRobot->speed.x = maxspeed * remaining_way.x / length;
577 ThisRobot->speed.y = maxspeed * remaining_way.y / length;
578 if (fabs(ThisRobot->speed.x * Frame_Time()) >= fabsf(remaining_way.x))
579 ThisRobot->speed.x = remaining_way.x / Frame_Time();
580 if (fabs(ThisRobot->speed.y * Frame_Time()) >= fabsf(remaining_way.y))
581 ThisRobot->speed.y = remaining_way.y / Frame_Time();
582 newpos.x = ThisRobot->pos.x + ThisRobot->speed.x * Frame_Time();
583 newpos.y = ThisRobot->pos.y + ThisRobot->speed.y * Frame_Time();
584 }
585 }
586
587 // Now the bot is moving, so maybe it's crossing a level's border ?
588 // In this case, we have to reset the waypoints stored in the bot struct,
589 // because those waypoints have no more sense in the new bot's level.
590
591 if (!resolve_virtual_position(&newpos, &newpos))
592 return;
593
594 old_map_level = ThisRobot->pos.z;
595 ThisRobot->pos.x = newpos.x;
596 ThisRobot->pos.y = newpos.y;
597 ThisRobot->pos.z = newpos.z;
598
599 if (ThisRobot->pos.z != old_map_level) { /* if the bot has changed level */
600 // Prevent the bot from moving this frame
601 clear_out_intermediate_points(&ThisRobot->pos, &ThisRobot->PrivatePathway[0], 5);
602
603 // The waypoints used by the bot have no sense on this new level. They have
604 // to be re-initialized. The closest available waypoint is chosen.
605 int best_waypoint = enemy_find_closest_waypoint(ThisRobot);
606
607 if (best_waypoint >= 0) {
608 ThisRobot->homewaypoint = best_waypoint;
609 ThisRobot->lastwaypoint = best_waypoint;
610 ThisRobot->nextwaypoint = best_waypoint;
611 } else {
612 // No waypoint found, make this bot wander
613 ThisRobot->combat_state = WAYPOINTLESS_WANDERING;
614 }
615 // Move the bot to the appropriate level list
616 list_move(&ThisRobot->level_list, &(level_bots_head[ThisRobot->pos.z]));
617 }
618
619 DetermineAngleOfFacing(ThisRobot);
620 }
621
622 /**
623 * This function moves one robot towards his next waypoint. If already
624 * there, the function does nothing more.
625 */
MoveThisRobotTowardsHisCurrentTarget(enemy * ThisRobot)626 static void MoveThisRobotTowardsHisCurrentTarget(enemy *ThisRobot)
627 {
628 if (ThisRobot->animation_type == ATTACK_ANIMATION)
629 return;
630
631 if ((ThisRobot->PrivatePathway[0].x == ThisRobot->pos.x) && (ThisRobot->PrivatePathway[0].y == ThisRobot->pos.y)) {
632 if (ThisRobot->PrivatePathway[1].x != -1) {
633 int i;
634 for (i = 1; i < 5; i++) {
635 ThisRobot->PrivatePathway[i - 1].x = ThisRobot->PrivatePathway[i].x;
636 ThisRobot->PrivatePathway[i - 1].y = ThisRobot->PrivatePathway[i].y;
637 }
638 ThisRobot->PrivatePathway[4].x = -1;
639 ThisRobot->PrivatePathway[4].y = -1;
640 }
641 return;
642 }
643
644 move_enemy_to_spot(ThisRobot, ThisRobot->PrivatePathway[0]);
645
646 if ((fabsf(ThisRobot->pos.x - ThisRobot->PrivatePathway[0].x) < 0.001)
647 && fabsf(ThisRobot->pos.y - ThisRobot->PrivatePathway[0].y) < 0.001) { /* Have we reached our target ? */
648 int i;
649 for (i = 1; i < 5; i++) {
650 ThisRobot->PrivatePathway[i - 1].x = ThisRobot->PrivatePathway[i].x;
651 ThisRobot->PrivatePathway[i - 1].y = ThisRobot->PrivatePathway[i].y;
652 }
653 ThisRobot->PrivatePathway[4].x = -1;
654 ThisRobot->PrivatePathway[4].y = -1;
655 }
656
657 if (ThisRobot->PrivatePathway[0].x == -1) {
658 ThisRobot->PrivatePathway[0].x = ThisRobot->pos.x;
659 ThisRobot->PrivatePathway[0].y = ThisRobot->pos.y;
660 }
661 }
662
663 /**
664 * This function sets a new waypoint to a bot.
665 *
666 * Returns TRUE if everything was OK, FALSE if couldn't set a new waypoint.
667 *
668 * The new waypoint is randomly chosen in a list of potentially usable
669 * connections. We want the previous waypoint to have a lower probability
670 * in order to avoid bots going back on their steps too often.
671 *
672 * This should imply to generate a non-uniform random value. However this
673 * is not needed here. Let's say that we have 3 potential new waypoints
674 * { A, B, C }, A being the previous waypoint. If p(A) is 3 times lower
675 * than the probability of the other waypoints, then we can use a uniform
676 * random value, to choose a waypoint in the following set:
677 * { A, B, B, B, C, C, C }
678 *
679 * Implementation:
680 *
681 * instead of replicating waypoints, we only keep a restricted { B, C } set,
682 * and use the following trick:
683 *
684 * - rnd is a random value in the [0, 7[ range. (7 = cardinality of the
685 * whole set, with replicated waypoints).
686 * - if rnd is 0, then 'A' is chosen.
687 * - if 1 <= rnd <= 3, so if (rnd-1)/3 == 0, then 'B' is chosen
688 * - if 4 <= rnd <= 6, so if (rnd-1)/3 == 1, then 'C' is chosen
689 *
690 * so, if rnd != 0, then (rnd-1)/3 is the index of the waypoint to
691 * choose in the 'restricted' set.
692 */
set_new_random_waypoint(enemy * this_robot)693 static int set_new_random_waypoint(enemy *this_robot)
694 {
695 const int PMULT = 3; // Probability multiplier
696 int i;
697 int nb_free_waypoints;
698 int lastwaypoint_is_free;
699
700 level *bot_level = curShip.AllLevels[this_robot->pos.z];
701
702 // If the bot is not tied into the waypoint system, do not select a new waypoint
703 if (this_robot->nextwaypoint == -1) {
704 return FALSE;
705 }
706
707 // nextwaypoint is actually the waypoint that the bot just reached.
708 waypoint *current_waypoint = &((waypoint *)bot_level->waypoints.arr)[this_robot->nextwaypoint];
709
710 // Pre-condition: there must be some connections
711 //
712 int num_conn = current_waypoint->connections.size;
713 if (num_conn == 0) // no connections found!
714 {
715 error_message(__FUNCTION__,
716 "Found a waypoint without connection\n"
717 "The offending waypoint nr. is: %d at %d, %d.\n"
718 "The map level in question got nr.: %d.",
719 PLEASE_INFORM,
720 this_robot->nextwaypoint, current_waypoint->x, current_waypoint->y,
721 this_robot->pos.z);
722 return FALSE;
723 }
724
725 // For each connected waypoint, check if the path to this waypoint
726 // is free of droids, and if so, store the waypoint.
727 // Special case (see function's comment): the previous waypoint
728 // (called 'lastwaypoint') is not stored, but a flag is set if its
729 // path is free.
730 //
731 nb_free_waypoints = 0;
732 lastwaypoint_is_free = FALSE;
733 freeway_context frw_ctx = { .check_tux = TRUE, .except_bots = {this_robot, NULL} };
734
735 int free_waypoints[num_conn];
736 int *connections = current_waypoint->connections.arr;
737
738 waypoint *wpts = bot_level->waypoints.arr;
739 for (i = 0; i < num_conn; i++) {
740 waypoint *w = &wpts[connections[i]];
741
742 int is_free = way_free_of_droids(current_waypoint->x + 0.5, current_waypoint->y + 0.5,
743 w->x + 0.5, w->y + 0.5, this_robot->pos.z, &frw_ctx);
744 if (is_free) {
745 if (connections[i] != this_robot->lastwaypoint) {
746 free_waypoints[nb_free_waypoints++] = connections[i];
747 } else {
748 lastwaypoint_is_free = TRUE;
749 }
750 }
751 }
752
753 // If no paths are free, make the bot wait a bit (use a random waiting
754 // time, to help avoid 'deadlocks' between bots).
755 //
756 if (nb_free_waypoints == 0 && !lastwaypoint_is_free) {
757 this_robot->pure_wait = 0.5 + (float)MyRandom(4)/8.0;
758 return TRUE;
759 }
760
761 // Randomly chose one of the free connected waypoints.
762 // (see function's comment)
763 //
764 int next_waypoint_id;
765
766 if (lastwaypoint_is_free) {
767 int rnd = MyRandom(PMULT*nb_free_waypoints); // range: [0, PMULT*nb_free_waypoints + 1[
768 if (rnd != 0) {
769 next_waypoint_id = free_waypoints[(rnd-1)/PMULT];
770 } else {
771 next_waypoint_id = this_robot->lastwaypoint;
772 }
773 } else {
774 // If the previous waypoint is not free, then there's no need for any
775 // specific random law...
776 int rnd = MyRandom(nb_free_waypoints - 1);
777 next_waypoint_id = free_waypoints[rnd];
778 }
779
780 // Set the new path
781 //
782 this_robot->lastwaypoint = this_robot->nextwaypoint;
783 this_robot->nextwaypoint = next_waypoint_id;
784
785 return TRUE;
786 }
787
788 /**
789 * If the droid in question is currently not following the waypoint system
790 * but rather moving around on it's own and without any real destination,
791 * this function sets up randomly chosen targets for the droid.
792 */
set_new_waypointless_walk_target(enemy * ThisRobot,moderately_finepoint * mt)793 static void set_new_waypointless_walk_target(enemy * ThisRobot, moderately_finepoint * mt)
794 {
795 int i;
796 moderately_finepoint target_candidate;
797 int success = FALSE;
798
799 #define MAX_RANDOM_WALK_ATTEMPTS_BEFORE_GIVING_UP 4
800
801 for (i = 0; i < MAX_RANDOM_WALK_ATTEMPTS_BEFORE_GIVING_UP; i++) {
802 // We select a possible new walktarget for this bot, not too
803 // far away from the current position...
804 //
805 target_candidate.x = ThisRobot->pos.x + (MyRandom(600) - 300) / 100;
806 target_candidate.y = ThisRobot->pos.y + (MyRandom(600) - 300) / 100;
807
808
809 if (DirectLineColldet(ThisRobot->pos.x, ThisRobot->pos.y,
810 target_candidate.x, target_candidate.y, ThisRobot->pos.z, &WalkablePassFilter)) {
811 mt->x = target_candidate.x;
812 mt->y = target_candidate.y;
813 success = TRUE;
814 }
815 }
816
817 if (!success) {
818 ThisRobot->pure_wait = WAIT_COLLISION;
819 }
820 }; // void set_new_waypointless_walk_target ( enemy* ThisRobot )
821
822 /**
823 * When a (hostile) robot is defeated and explodes, it will drop some
824 * treasure, i.e. stuff it had or parts that it consisted of or similar
825 * things. Maybe there will even be some extra magical treasures if the
826 * robot in question was a 'boss monster'. This function does the
827 * treasure dropping.
828 */
enemy_drop_treasure(struct enemy * this_droid)829 static void enemy_drop_treasure(struct enemy *this_droid)
830 {
831 int extract_skill_level = Me.skill_level[get_program_index_with_name("Extract bot parts")];
832 if (extract_skill_level > 5)
833 extract_skill_level = 5;
834
835 // If the Tux has the skill to extract certain components from dead bots,
836 // these components will be thrown out automatically, when the bot is killed.
837 //
838 switch (extract_skill_level) {
839 case 5:
840 if (Droidmap[this_droid->type].amount_of_tachyon_condensators
841 && Droidmap[this_droid->type].amount_of_tachyon_condensators > MyRandom(100))
842 DropItemAt(get_item_type_by_id("Tachyon Condensator"), this_droid->pos.z, this_droid->pos.x,
843 this_droid->pos.y, 1);
844 // no break
845 case 4:
846 if (Droidmap[this_droid->type].amount_of_antimatter_converters
847 && Droidmap[this_droid->type].amount_of_antimatter_converters > MyRandom(100))
848 DropItemAt(get_item_type_by_id("Antimatter-Matter Converter"), this_droid->pos.z, this_droid->pos.x,
849 this_droid->pos.y, 1);
850 // no break
851 case 3:
852 if (Droidmap[this_droid->type].amount_of_superconductors
853 && Droidmap[this_droid->type].amount_of_superconductors > MyRandom(100))
854 DropItemAt(get_item_type_by_id("Superconducting Relay Unit"), this_droid->pos.z, this_droid->pos.x,
855 this_droid->pos.y, 1);
856 // no break
857 case 2:
858 if (Droidmap[this_droid->type].amount_of_plasma_transistors
859 && Droidmap[this_droid->type].amount_of_plasma_transistors > MyRandom(100))
860 DropItemAt(get_item_type_by_id("Plasma Transistor"), this_droid->pos.z, this_droid->pos.x,
861 this_droid->pos.y, 1);
862 // no break
863 case 1:
864 if (Droidmap[this_droid->type].amount_of_entropy_inverters
865 && Droidmap[this_droid->type].amount_of_entropy_inverters > MyRandom(100))
866 DropItemAt(get_item_type_by_id("Entropy Inverter"), this_droid->pos.z, this_droid->pos.x,
867 this_droid->pos.y, 1);
868 // no break
869 case 0:
870 break;
871 }
872
873 if (this_droid->on_death_drop_item_code != (-1)) {
874 // We make sure the item created is of a reasonable type
875 //
876 if ((this_droid->on_death_drop_item_code <= 0) || (this_droid->on_death_drop_item_code >= Number_Of_Item_Types)) {
877 error_message(__FUNCTION__, "Bot at %f %f (level %d, dialog %s) is specified to drop an item that doesn't exist (item type %d).", PLEASE_INFORM, this_droid->pos.x, this_droid->pos.y, this_droid->pos.z, this_droid->dialog_section_name, this_droid->on_death_drop_item_code);
878 return;
879 }
880
881 DropItemAt(this_droid->on_death_drop_item_code, this_droid->pos.z, this_droid->pos.x, this_droid->pos.y, 1);
882 this_droid->on_death_drop_item_code = -1;
883 }
884 // Apart from the parts, that the Tux might be able to extract from the bot,
885 // there is still some chance, that the enemy will have (and drop) some other
886 // valuables, that the Tux can then collect afterwards.
887 //
888 DropRandomItem(this_droid->pos.z, this_droid->pos.x, this_droid->pos.y, Droidmap[this_droid->type].drop_class, FALSE);
889 }
890
891 /**
892 * When an enemy is hit, this causes some blood to be sprayed on the floor.
893 * The blood is just an obstacle (several types of blood exist) with
894 * preput flag set, so that the Tux and everyone can really step *on* the
895 * blood.
896 *
897 * Blood will always be sprayed, but there is a toggle available for making
898 * the blood visible/invisible for more a children-friendly version of the
899 * game.
900 *
901 * This function does the blood spraying (adding of these obstacles).
902 */
enemy_spray_blood(struct enemy * droid)903 static void enemy_spray_blood(struct enemy *droid)
904 {
905 // Fix virtual position (e.g. from a dying robot)
906 struct gps droid_pos = { -1, -1, -1 };
907 if (!resolve_virtual_position(&droid_pos, &droid->pos)) {
908 return;
909 }
910
911 // Find a random position that is inside the droid's level
912 // (that's not mandatory, but ease computation), and outside any obstacle
913 struct gps blood_pos = { -1, -1, -1 };
914
915 struct level *rlvl = curShip.AllLevels[droid_pos.z];
916 const int tries = 4;
917 int i;
918 for (i = 0; i < tries; i++) {
919 moderately_finepoint tried_pos = { 0.5, 0 };
920 RotateVectorByAngle(&tried_pos, MyRandom(360));
921 tried_pos.x += droid_pos.x;
922 tried_pos.y += droid_pos.y;
923 if (pos_inside_level(tried_pos.x, tried_pos.y, rlvl)) {
924 if (!SinglePointColldet(tried_pos.x, tried_pos.y, droid_pos.z, NULL))
925 continue;
926 blood_pos.x = tried_pos.x;
927 blood_pos.y = tried_pos.y;
928 blood_pos.z = droid_pos.z;
929 break;
930 }
931 }
932 if (i == tries) {
933 // Was not able to find a random position inside the level, use bot's position
934 blood_pos.x = droid_pos.x;
935 blood_pos.y = droid_pos.y;
936 blood_pos.z = droid_pos.z;
937 }
938
939 // Get a random blood print
940
941 struct obstacle_group *blood_group = NULL;
942
943 if (Droidmap[droid->type].is_human)
944 blood_group = get_obstacle_group_by_name("blood");
945 else
946 blood_group = get_obstacle_group_by_name("oil stains");
947
948 if (!blood_group) {
949 error_message(__FUNCTION__, "Could not find obstacle group for blood.", PLEASE_INFORM);
950 return;
951 }
952
953 int *random_blood_type = dynarray_member(&blood_group->members, MyRandom(blood_group->members.size - 1), sizeof(int));
954 struct obstacle_spec *obs_spec = get_obstacle_spec(*random_blood_type);
955 add_volatile_obstacle(curShip.AllLevels[blood_pos.z], blood_pos.x, blood_pos.y, *random_blood_type, obs_spec->vanish_delay + obs_spec->vanish_duration);
956 }
957
958 /**
959 * When a robot has reached energy <= 1, then this robot will explode and
960 * die, lose some treasure and add up to the kill record of the Tux. All
961 * the things that should happen when energy is that low are handled here
962 * while the check for low energy is done outside of this function namely
963 * somewhere in the movement processing for this enemy.
964 */
kill_enemy(enemy * target,char givexp,int killertype)965 static int kill_enemy(enemy * target, char givexp, int killertype)
966 {
967 int reward = 0;
968
969 /* Give death message */
970 if (givexp) {
971 reward = Droidmap[target->type].experience_reward * Me.experience_factor;
972 Me.Experience += reward;
973 }
974
975 if (is_friendly(target->faction, FACTION_SELF)) {
976 if (killertype > -1) { //killed by someone else, and we know who it is
977 enemy *killer = NULL;
978 killer = enemy_resolve_address(killertype, &killer);
979 // TRANSLATORS: Your friend <bot's short description> was killed by <bot's short description>
980 append_new_game_message(_("Your friend [s]%s[v] was killed by %s."),
981 D_(target->short_description_text), D_(killer->short_description_text));
982 } else if ((killertype == -1) && (givexp)) { //You killed someone
983 // TRANSLATORS: You killed <bot's short description>
984 append_new_game_message(_("You killed [s]%s[v]."), D_(target->short_description_text));
985 Me.destroyed_bots[target->type]++;
986 } else if (killertype == -2) { //bot killed itself
987 // TRANSLATORS: <bot's short description> halted and caught fire
988 append_new_game_message(_("[s]%s[v] halted and caught fire."), D_(target->short_description_text));
989 } else {
990 // TRANSLATORS: <bot's short description> is dead
991 append_new_game_message(_("[s]%s[v] is dead."), D_(target->short_description_text));
992 }
993 } else {
994 if (givexp && (killertype == -1)) {
995 // TRANSLATORS: For defeating <bot's short description>, you receive <10> experience
996 append_new_game_message(_("For defeating [s]%s[v], you receive %d experience."), D_(target->short_description_text),
997 reward);
998 Me.destroyed_bots[target->type]++;
999 }
1000
1001 // The below section is much more of debug info that something that actually should be "spammed" to the user by default.
1002 // Possibly Tux could know about fighting going on in the immediate vicinity, but for sure not on the other side of the world map.
1003 // It just confuses beginners while giving little or no valuable info to even an experienced player.
1004 /*
1005 else if (killertype && killertype != -1)
1006 append_new_game_message(_("[s]%s[v] was killed by %s."), target->short_description_text,
1007 Droidmap[killertype].droidname);
1008 else
1009 append_new_game_message(_("[s]%s[v] died."), target->short_description_text);
1010 */
1011 }
1012
1013 // NOTE: We reset the animation phase to the first death animation image
1014 // here. But this may be WRONG! In the case that the enemy graphics
1015 // hasn't been loaded yet, this will result in '1' for the animation
1016 // phase. That however is unlikely to happen unless the bot is killed
1017 // right now and hasn't been ever visible in the game yet. Also it
1018 // will lead only to minor 'prior animation' before the real death
1019 // phase is reached and so serious bugs other than that, so I think it
1020 // will be tolerable this way.
1021
1022 target->animation_phase = ((float)first_death_animation_image[Droidmap[target->type].individual_shape_nr]) - 1 + 0.1;
1023 target->animation_type = DEATH_ANIMATION;
1024 play_death_sound_for_bot(target);
1025
1026 enemy_drop_treasure(target);
1027
1028 if (MyRandom(15) == 1)
1029 enemy_spray_blood(target);
1030
1031 list_move(&(target->global_list), &dead_bots_head); // bot is dead. move it to dead list
1032 list_del(&(target->level_list)); // bot is dead. remove it from level list
1033
1034 event_enemy_died(target);
1035
1036 return 0;
1037 }
1038
1039 /**
1040 *
1041 *
1042 */
start_gethit_animation(enemy * ThisRobot)1043 static void start_gethit_animation(enemy * ThisRobot)
1044 {
1045 // Maybe this robot is fully animated. In this case, after getting
1046 // hit, the gethit animation should be displayed, which we'll initiate here.
1047 //
1048 if ((last_gethit_animation_image[Droidmap[ThisRobot->type].individual_shape_nr] - first_gethit_animation_image[Droidmap[ThisRobot->type].individual_shape_nr] > 0)) {
1049 if (ThisRobot->animation_type == DEATH_ANIMATION) {
1050 DebugPrintf(-4, "\n%s(): WARNING: animation phase reset for INFOUT bot... ", __FUNCTION__);
1051 }
1052 ThisRobot->animation_phase = ((float)first_gethit_animation_image[Droidmap[ThisRobot->type].individual_shape_nr]) + 0.1;
1053 ThisRobot->animation_type = GETHIT_ANIMATION;
1054 }
1055
1056 }; // void start_gethit_animation_if_applicable ( enemy* ThisRobot )
1057
1058 /*
1059 * Hit an enemy for "hit" HP. This is supposed to be the *only* means
1060 * of removing HPs to a bot.
1061 *
1062 * target is a pointer to the bot to hit
1063 * hit is the amount of HPs to remove
1064 * givexp (0 or 1) indicates whether to give an XP reward to the player or not
1065 * killertype is the id of the bot who is responsible of the attack, or -1 if it is unknown
1066 * or if it is the player, -2 for a non-human dialog-killed droid.
1067 * mine is 0 or 1 depending on whether it's the player who is responsible for the attack
1068 */
hit_enemy(enemy * target,float hit,char givexp,short int killertype,char mine)1069 void hit_enemy(enemy * target, float hit, char givexp, short int killertype, char mine)
1070 {
1071 /*
1072 * turn group hostile
1073 * spray blood
1074 * enter hitstun
1075 * say a funny message
1076 * remove hp
1077 * check if droid is dead
1078 */
1079
1080 if (target->energy <= 0) {
1081 // Do not kill already dead enemies
1082 return;
1083 }
1084
1085 // no XP is given for killing a friendly bot
1086 if (is_friendly(target->faction, FACTION_SELF) && givexp)
1087 givexp = 0;
1088
1089 // hitstun
1090 // a hit that does less than 5% (over max life) damage cannot stun a bot
1091 if (hit / Droidmap[target->type].maxenergy >= 0.05) {
1092 start_gethit_animation(target);
1093
1094 // if the current wait time of the bot is greater than the hitstun duration, we do nothing
1095 if (target->firewait < Droidmap[target->type].recover_time_after_getting_hit) {
1096 target->firewait = Droidmap[target->type].recover_time_after_getting_hit;
1097 }
1098 }
1099
1100 target->energy -= hit;
1101 if (target->energy <= 0) {
1102 kill_enemy(target, givexp, killertype);
1103 } else if (hit > 1 && MyRandom(6) == 1) {
1104 enemy_spray_blood(target);
1105 }
1106 if (mine)
1107 Me.damage_dealt[target->type] += hit;
1108 }
1109
1110 /**
1111 * This function moves a single enemy. It is used by update_enemy().
1112 */
MoveThisEnemy(enemy * ThisRobot)1113 static void MoveThisEnemy(enemy * ThisRobot)
1114 {
1115 // robots that still have to wait also do not need to
1116 // be processed for movement
1117 //
1118 if (ThisRobot->pure_wait > 0)
1119 return;
1120
1121 gps oldpos = { ThisRobot->pos.x, ThisRobot->pos.y, ThisRobot->pos.z };
1122
1123 MoveThisRobotTowardsHisCurrentTarget(ThisRobot);
1124
1125 if (CheckEnemyEnemyCollision(ThisRobot)) {
1126 ThisRobot->pos.x = oldpos.x;
1127 ThisRobot->pos.y = oldpos.y;
1128 ThisRobot->pos.z = oldpos.z;
1129 }
1130 };
1131
1132 /**
1133 * This function returns a gps (position) for a robot's current target,
1134 * or NULL if such target doesn't exist or is dead or is invisible (if target is Tux)
1135 */
enemy_get_target_position(enemy * ThisRobot)1136 static gps *enemy_get_target_position(enemy * ThisRobot)
1137 {
1138 if (ThisRobot->attack_target_type == ATTACK_TARGET_IS_PLAYER) {
1139
1140 if (can_see_tux(ThisRobot))
1141 return &Me.pos;
1142 else
1143 return NULL;
1144
1145 } else if (ThisRobot->attack_target_type == ATTACK_TARGET_IS_ENEMY) {
1146 enemy *bot_enemy = enemy_resolve_address(ThisRobot->bot_target_n, &ThisRobot->bot_target_addr);
1147 if (!bot_enemy || bot_enemy->energy <= 0)
1148 return NULL;
1149 return &bot_enemy->pos;
1150 }
1151
1152 /* No (more) target */
1153 return NULL;
1154 }
1155
1156 /**
1157 * More for debugging purposes, we print out the current state of the
1158 * robot as his in-game text.
1159 */
enemy_say_current_state_on_screen(enemy * ThisRobot)1160 void enemy_say_current_state_on_screen(enemy * ThisRobot)
1161 {
1162 if (ThisRobot->pure_wait <= 0) {
1163 switch (ThisRobot->combat_state) {
1164 case MOVE_ALONG_RANDOM_WAYPOINTS:
1165 ThisRobot->TextToBeDisplayed = ("state: Wandering along waypoints.");
1166 break;
1167 case SELECT_NEW_WAYPOINT:
1168 ThisRobot->TextToBeDisplayed = ("state: Select next WP.");
1169 break;
1170 case TURN_TOWARDS_NEXT_WAYPOINT:
1171 ThisRobot->TextToBeDisplayed = ("state: Turn towards next WP.");
1172 break;
1173 case RUSH_TUX_AND_OPEN_TALK:
1174 ThisRobot->TextToBeDisplayed = ("state: Rush Tux and open talk.");
1175 break;
1176 case STOP_AND_EYE_TARGET:
1177 ThisRobot->TextToBeDisplayed = ("state: Stop and eye target.");
1178 break;
1179 case ATTACK:
1180 ThisRobot->TextToBeDisplayed = ("state: Attack.");
1181 break;
1182 case RETURNING_HOME:
1183 ThisRobot->TextToBeDisplayed = ("state: Returning home.");
1184 break;
1185 case WAYPOINTLESS_WANDERING:
1186 ThisRobot->TextToBeDisplayed = ("state: Waypointless wandering.");
1187 break;
1188 case PARALYZED:
1189 ThisRobot->TextToBeDisplayed = ("state: Paralyzed.");
1190 break;
1191 case COMPLETELY_FIXED:
1192 ThisRobot->TextToBeDisplayed = ("state: Completely fixed.");
1193 break;
1194 case FOLLOW_TUX:
1195 ThisRobot->TextToBeDisplayed = ("state: Follow Tux.");
1196 break;
1197 case UNDEFINED_STATE:
1198 ThisRobot->TextToBeDisplayed = ("state: None.");
1199 break;
1200 default:
1201 ThisRobot->TextToBeDisplayed = ("state: UNHANDLED!!");
1202 break;
1203 }
1204 } else {
1205 switch (ThisRobot->combat_state) {
1206 case MOVE_ALONG_RANDOM_WAYPOINTS:
1207 ThisRobot->TextToBeDisplayed = ("purewait (MARW)");
1208 break;
1209 case SELECT_NEW_WAYPOINT:
1210 ThisRobot->TextToBeDisplayed = ("purewait (SNW)");
1211 break;
1212 case TURN_TOWARDS_NEXT_WAYPOINT:
1213 ThisRobot->TextToBeDisplayed = ("purewait (TTNW)");
1214 break;
1215 case RUSH_TUX_AND_OPEN_TALK:
1216 ThisRobot->TextToBeDisplayed = ("purewait (RTAOT)");
1217 break;
1218 case STOP_AND_EYE_TARGET:
1219 ThisRobot->TextToBeDisplayed = ("purewait (SAET)");
1220 break;
1221 case ATTACK:
1222 ThisRobot->TextToBeDisplayed = ("purewait (A)");
1223 break;
1224 case RETURNING_HOME:
1225 ThisRobot->TextToBeDisplayed = ("purewait (RH)");
1226 break;
1227 case WAYPOINTLESS_WANDERING:
1228 ThisRobot->TextToBeDisplayed = ("purewait (WW)");
1229 break;
1230 case PARALYZED:
1231 ThisRobot->TextToBeDisplayed = ("purewait (P)");
1232 break;
1233 case COMPLETELY_FIXED:
1234 ThisRobot->TextToBeDisplayed = ("purewait (CF)");
1235 break;
1236 case FOLLOW_TUX:
1237 ThisRobot->TextToBeDisplayed = ("purewait (FT)");
1238 break;
1239 case UNDEFINED_STATE:
1240 ThisRobot->TextToBeDisplayed = ("purewait (US)");
1241 break;
1242 default:
1243 ThisRobot->TextToBeDisplayed = ("purewait (UNH)");
1244 break;
1245 }
1246 }
1247
1248 ThisRobot->TextVisibleTime = 0;
1249
1250 }; // void enemy_say_current_state_on_screen ( enemy* ThisRobot )
1251
1252 /**
1253 * Some robots (currently) tend to get stuck in walls. This is an
1254 * annoying bug case we have not yet been able to eliminate completely.
1255 * To provide some safety against this case, some extra fallback handling
1256 * should be introduced, so that the bots can still recover if that
1257 * unlucky case really happens, which is what we provide here.
1258 *
1259 * Since passability checks usually can become quite costly in terms of
1260 * processor time and also because it makes sense to allow for some more
1261 * 'natural' fallbacks to work, we only check for stuck bots every second
1262 * or so. In order to better distribute the checks (and not cause fps
1263 * glitches by doing them all at once) we use individual timers for this
1264 * test.
1265 */
enemy_handle_stuck_in_walls(enemy * ThisRobot)1266 void enemy_handle_stuck_in_walls(enemy * ThisRobot)
1267 {
1268 waypoint *wpts = curShip.AllLevels[ThisRobot->pos.z]->waypoints.arr;
1269
1270 // Maybe the time for the next check for this bot has not yet come.
1271 // in that case we can return right away.
1272 //
1273 ThisRobot->time_since_previous_stuck_in_wall_check += Frame_Time();
1274 if (ThisRobot->time_since_previous_stuck_in_wall_check < 1.0)
1275 return;
1276 ThisRobot->time_since_previous_stuck_in_wall_check = 0;
1277
1278 // First we take a look if this bot is currently stuck in a
1279 // wall somewhere.
1280 //
1281 if (!SinglePointColldet(ThisRobot->pos.x, ThisRobot->pos.y, ThisRobot->pos.z, &WalkablePassFilter)) {
1282 // So at this point we know, that we have a bot that is stuck right now,
1283 // has been stuck one second ago and also is not moving along waypoints, which
1284 // would lead to the bot reaching some sensible spot sooner or later anyway.
1285 // In one word: we have arrived in a situation that might make a crude correction
1286 // sensible. We teleport the robot back to the nearest waypoint. From there, it
1287 // might find a suitable way on it's own again.
1288 //
1289 DebugPrintf(-2, "\n\nFound robot that seems really stuck on position: %f/%f/%d.",
1290 ThisRobot->pos.x, ThisRobot->pos.y, ThisRobot->pos.z);
1291 DebugPrintf(-2, "\nMore details on this robot: Type=%d.", ThisRobot->type);
1292 DebugPrintf(-2, "\nShort Description=%s.", ThisRobot->short_description_text);
1293 DebugPrintf(-2, "\nPrivate Pathway[0]: %f/%f.", ThisRobot->PrivatePathway[0].x, ThisRobot->PrivatePathway[0].y);
1294 DebugPrintf(-2, "\nPrivate Pathway[1]: %f/%f.", ThisRobot->PrivatePathway[1].x, ThisRobot->PrivatePathway[1].y);
1295 DebugPrintf(-2, "\nnextwaypoint: %d at %f/%f",
1296 ThisRobot->nextwaypoint, wpts[ThisRobot->nextwaypoint].x + 0.5, wpts[ThisRobot->nextwaypoint].y + 0.5);
1297
1298 enemy_say_current_state_on_screen(ThisRobot);
1299 DebugPrintf(-2, "\nnextwaypoint=%d. lastwaypoint=%d. combat_%s.",
1300 ThisRobot->nextwaypoint, ThisRobot->lastwaypoint, (ThisRobot->TextToBeDisplayed) ? ThisRobot->TextToBeDisplayed : "NONE");
1301
1302 if (!EscapeFromObstacle(&(ThisRobot->pos.x), &(ThisRobot->pos.y), ThisRobot->pos.z, &WalkablePassFilter)) {
1303 // No free position was found outside the obstacle ???
1304 // It should not happen but since we want the bot to escape in any situation, just have a last fallback
1305 //
1306 ThisRobot->pos.x = wpts[ThisRobot->nextwaypoint].x + 0.5;
1307 ThisRobot->pos.y = wpts[ThisRobot->nextwaypoint].y + 0.5;
1308 }
1309 ThisRobot->combat_state = SELECT_NEW_WAYPOINT;
1310 ThisRobot->bot_stuck_in_wall_at_previous_check = TRUE;
1311 return;
1312 } else {
1313 // this bot isn't currently stuck. what more could anybody want?
1314 ThisRobot->bot_stuck_in_wall_at_previous_check = FALSE;
1315 }
1316 }; // enemy_handle_stuck_in_walls ( enemy* ThisRobot )
1317
1318 /**
1319 * This function selects a target for a friendly bot.
1320 * It takes closest reachable enemy bot in view range.
1321 * A new target is selected at each frame. This should prevent a friendly bot to follow
1322 * an enemy and thus get too far away from its "steady" position.
1323 *
1324 * Note : this function does set ThisRobbot->attack_target_type, and calls enemy_set_reference()
1325 * accordingly to its finding.
1326 */
update_vector_to_shot_target_for_friend(enemy * ThisRobot)1327 void update_vector_to_shot_target_for_friend(enemy * ThisRobot)
1328 {
1329 float aggression_distance = Droidmap[ThisRobot->type].aggression_distance;
1330 float squared_aggression_distance = aggression_distance * aggression_distance;
1331 float squared_best_dist;
1332
1333 /*
1334 if ( ThisRobot->pure_wait > 0 )
1335 { // Target was not reachable
1336 // We could, for example, try to find an other target
1337 // But we should not do that as soon as the bot is pure_waiting,
1338 // because it could be a very temporary state.
1339 // So, a "pure_waiting" counter would be needed...
1340 }
1341 */
1342
1343 // We set some default values, in case there isn't anything attackable
1344 // found below...
1345 ThisRobot->attack_target_type = ATTACK_TARGET_IS_NOTHING;
1346 enemy_set_reference(&ThisRobot->bot_target_n, &ThisRobot->bot_target_addr, NULL);
1347
1348 // This initialization ensures that only targets inside aggression_distance are checked.
1349 squared_best_dist = squared_aggression_distance;
1350
1351 enemy *erot;
1352 BROWSE_LEVEL_BOTS(erot, ThisRobot->pos.z) {
1353 if (is_friendly(ThisRobot->faction, erot->faction))
1354 continue;
1355
1356 if (is_potential_target(ThisRobot, &erot->pos, &squared_best_dist)) {
1357 ThisRobot->attack_target_type = ATTACK_TARGET_IS_ENEMY;
1358 enemy_set_reference(&ThisRobot->bot_target_n, &ThisRobot->bot_target_addr, erot);
1359 }
1360 }
1361
1362 } // void update_vector_to_shot_target_for_friend ( ThisRobot )
1363
1364 /**
1365 * This function selects an attack target for an hostile bot.
1366 * Selected target is the previous target if it is still valid (see paragraph below),
1367 * or the closest potential target.
1368 *
1369 * For gameplay value purposes, it also performs a little hack : the target of
1370 * the previous frame can be selected even if it is "slightly" out of view (2 times the range),
1371 * in order to simulate "pursuit". Sorry for the mess but there is no other proper place for that.
1372 *
1373 * Note : this function does set this_robot->attack_target_type, and calls enemy_set_reference()
1374 * accordingly to its finding.
1375 */
update_vector_to_shot_target_for_enemy(enemy * this_robot)1376 void update_vector_to_shot_target_for_enemy(enemy * this_robot)
1377 {
1378 int our_level = this_robot->pos.z;
1379 float squared_best_dist;
1380 float xdist, ydist;
1381 float aggression_distance = Droidmap[this_robot->type].aggression_distance;
1382 float squared_aggression_distance = aggression_distance * aggression_distance;
1383
1384 // Check validity of old target (if any)
1385 //
1386 // Logic :
1387 // A target is valid if :
1388 // 1- always available (not dead and not invisible)
1389 // 2- was reachable (this_robot was not blocked along its way to the target)
1390 // 3- is not too far
1391 //
1392 gps *tpos = enemy_get_target_position(this_robot);
1393
1394 if (!tpos) { // Old target is no more available
1395 this_robot->attack_target_type = ATTACK_TARGET_IS_NOTHING;
1396 enemy_set_reference(&this_robot->bot_target_n, &this_robot->bot_target_addr, NULL);
1397 }
1398 /*
1399 else if ( this_robot->pure_wait > 0 )
1400 { // Target was not reachable
1401 // We could, for example, try to find an other target
1402 // But we should not do that as soon as the bot is pure_waiting,
1403 // because it could be a very temporary state.
1404 // So, a "pure_waiting" counter would be needed...
1405 }
1406 */
1407 else if (this_robot->attack_target_type != ATTACK_TARGET_IS_NOTHING) {
1408 // Check virtual distance
1409 update_virtual_position(&this_robot->virt_pos, &this_robot->pos, tpos->z);
1410 if (this_robot->virt_pos.z == -1) { // Target is at more than one level away
1411 this_robot->attack_target_type = ATTACK_TARGET_IS_NOTHING;
1412 enemy_set_reference(&this_robot->bot_target_n, &this_robot->bot_target_addr, NULL);
1413 } else {
1414 xdist = tpos->x - this_robot->virt_pos.x;
1415 ydist = tpos->y - this_robot->virt_pos.y;
1416
1417 if ((xdist * xdist + ydist * ydist) > 3.0*3.0) {
1418 /// Previous target is too far away to follow without checking if
1419 // there are enemies located closer
1420 this_robot->attack_target_type = ATTACK_TARGET_IS_NOTHING;
1421 enemy_set_reference(&this_robot->bot_target_n, &this_robot->bot_target_addr, NULL);
1422 } else {
1423 // All tests passed : Continue with same target
1424 return;
1425 }
1426 }
1427 }
1428 // Search for a new target
1429 //
1430
1431 // This initialization ensures that only targets inside aggression_distance are checked.
1432 squared_best_dist = squared_aggression_distance;
1433
1434 // First check if Tux is a potential target
1435 if (can_see_tux(this_robot)) {
1436 if (is_potential_target(this_robot, &Me.pos, &squared_best_dist)) {
1437 this_robot->attack_target_type = ATTACK_TARGET_IS_PLAYER;
1438 }
1439 }
1440 // But maybe there is a friend of the Tux also close. Then maybe we
1441 // should attack this one instead, since it's much closer anyway.
1442 enemy *erot;
1443 BROWSE_LEVEL_BOTS(erot, our_level) {
1444 if (is_friendly(erot->faction, this_robot->faction))
1445 continue;
1446
1447 if (is_potential_target(this_robot, &erot->pos, &squared_best_dist)) {
1448 this_robot->attack_target_type = ATTACK_TARGET_IS_ENEMY;
1449 enemy_set_reference(&this_robot->bot_target_n, &this_robot->bot_target_addr, erot);
1450 }
1451 }
1452
1453 } // void update_vector_to_shot_target_for_enemy ( ThisRobot )
1454
1455 /**
1456 * This function handles the inconditional updates done to the bots by
1457 * the automaton powering them. See update_enemy().
1458 */
state_machine_inconditional_updates(enemy * ThisRobot)1459 static void state_machine_inconditional_updates(enemy * ThisRobot)
1460 {
1461 // Robots that are paralyzed are completely stuck and do not
1462 // see their state machine running
1463
1464 // For debugging purposes we display the current state of the robot
1465 // in game
1466 enemy_say_current_state_on_screen(ThisRobot);
1467
1468 // we check whether the current robot is
1469 // stuck inside a wall or something...
1470 //
1471 enemy_handle_stuck_in_walls(ThisRobot);
1472
1473 // determine the distance vector to the target of this shot. The target
1474 // depends of course on whether it's a friendly device or a hostile device.
1475 //
1476 if (is_friendly(ThisRobot->faction, FACTION_SELF)) {
1477 update_vector_to_shot_target_for_friend(ThisRobot);
1478 } else {
1479 update_vector_to_shot_target_for_enemy(ThisRobot);
1480 }
1481
1482 ThisRobot->speed.x = 0;
1483 ThisRobot->speed.y = 0;
1484
1485 }
1486
1487 /**
1488 * This function handles state transitions based solely (or almost) on
1489 * the external situation and not on the current state of the bot.
1490 * The purpose is to reduce code duplication in the "big switch" that follows.
1491 */
state_machine_situational_transitions(enemy * ThisRobot)1492 static void state_machine_situational_transitions(enemy * ThisRobot)
1493 {
1494 waypoint *wpts = curShip.AllLevels[ThisRobot->pos.z]->waypoints.arr;
1495
1496 /* The various situations are listed in increasing priority order (ie. they may override each other, so the least priority comes first. */
1497 /* In an ideal world, this would not exist and be done for each state. But we're in reality and have to limit code duplication. */
1498
1499 // Rush Tux when he's close & visible
1500 update_virtual_position(&ThisRobot->virt_pos, &ThisRobot->pos, Me.pos.z);
1501 if (ThisRobot->will_rush_tux && ThisRobot->virt_pos.z != -1 && can_see_tux(ThisRobot)
1502 && (powf(Me.pos.x - ThisRobot->virt_pos.x, 2) + powf(Me.pos.y - ThisRobot->virt_pos.y, 2)) < 16) {
1503 ThisRobot->combat_state = RUSH_TUX_AND_OPEN_TALK;
1504 }
1505
1506 // Transition away from Rush Tux gracefully if it is unset or if the bot became aggressive
1507 if (ThisRobot->combat_state == RUSH_TUX_AND_OPEN_TALK) {
1508 if (!ThisRobot->will_rush_tux)
1509 ThisRobot->combat_state = UNDEFINED_STATE;
1510
1511 if (!is_friendly(ThisRobot->faction, FACTION_SELF)) {
1512 ThisRobot->combat_state = UNDEFINED_STATE;
1513 ThisRobot->will_rush_tux = 0;
1514 }
1515 }
1516
1517 /* Return home if we're too far away */
1518 if (ThisRobot->max_distance_to_home != 0 &&
1519 sqrt(powf((wpts[ThisRobot->homewaypoint].x + 0.5) - ThisRobot->pos.x, 2) +
1520 powf((wpts[ThisRobot->homewaypoint].y + 0.5) - ThisRobot->pos.y, 2))
1521 > ThisRobot->max_distance_to_home) {
1522 ThisRobot->combat_state = RETURNING_HOME;
1523 ThisRobot->attack_target_type = ATTACK_TARGET_IS_NOTHING;
1524 enemy_set_reference(&ThisRobot->bot_target_n, &ThisRobot->bot_target_addr, NULL);
1525 }
1526
1527 /* Paralyze if appropriate */
1528 if (ThisRobot->paralysation_duration_left > 0) {
1529 ThisRobot->combat_state = PARALYZED;
1530 }
1531
1532 /* Now we will handle changes, which take place for many - but not exactly all - states */
1533 /* Those are all related to attack behavior */
1534
1535 switch (ThisRobot->combat_state) { /* Get out for all states we don't want to handle attack for */
1536 case STOP_AND_EYE_TARGET:
1537 case ATTACK:
1538 case PARALYZED:
1539 case RETURNING_HOME:
1540 case SELECT_NEW_WAYPOINT:
1541 case RUSH_TUX_AND_OPEN_TALK:
1542 return;
1543 }
1544
1545 /* Fix completely if appropriate */
1546 if (ThisRobot->CompletelyFixed) {
1547 ThisRobot->combat_state = COMPLETELY_FIXED;
1548 }
1549
1550 /* Follow Tux if appropriate */
1551 if (ThisRobot->follow_tux) {
1552 ThisRobot->combat_state = FOLLOW_TUX;
1553 }
1554
1555 /* Switch to stop_and_eye_target if appropriate - it's the prelude to any-on-any attacks */
1556 if (ThisRobot->attack_target_type != ATTACK_TARGET_IS_NOTHING) {
1557 ThisRobot->combat_state = STOP_AND_EYE_TARGET;
1558 }
1559
1560 /* If combat_state is UNDEFINED_STATE, go to SELECT_NEW_WAYPOINT state */
1561 if (ThisRobot->combat_state == UNDEFINED_STATE) {
1562 ThisRobot->combat_state = SELECT_NEW_WAYPOINT;
1563 }
1564 }
1565
1566 /* ----------------------------------------------------------
1567 * "stop and eye tux" state handling
1568 * ---------------------------------------------------------- */
state_machine_stop_and_eye_target(enemy * ThisRobot,moderately_finepoint * new_move_target)1569 static void state_machine_stop_and_eye_target(enemy * ThisRobot, moderately_finepoint * new_move_target)
1570 {
1571 gps *tpos = enemy_get_target_position(ThisRobot);
1572
1573 // Target no more available -> going to default state
1574 if (!tpos) {
1575 ThisRobot->state_timeout = 0;
1576 ThisRobot->combat_state = UNDEFINED_STATE;
1577 ThisRobot->attack_target_type = ATTACK_TARGET_IS_NOTHING;
1578 enemy_set_reference(&ThisRobot->bot_target_n, &ThisRobot->bot_target_addr, NULL);
1579 return;
1580 }
1581
1582 update_virtual_position(&ThisRobot->virt_pos, &ThisRobot->pos, tpos->z);
1583
1584 /* Make sure we're looking at the target */
1585 gps target_vpos;
1586 update_virtual_position(&target_vpos, tpos, ThisRobot->pos.z);
1587 TurnABitTowardsPosition(ThisRobot, target_vpos.x, target_vpos.y, 120);
1588
1589 /* Do greet sound if not already done */
1590 if (!ThisRobot->has_greeted_influencer) {
1591 ThisRobot->has_greeted_influencer = TRUE;
1592 if (Droidmap[ThisRobot->type].greeting_sound_type != (-1)) {
1593 play_greeting_sound(ThisRobot);
1594 }
1595 }
1596
1597 /* Check state timeout */
1598
1599 //XXX if the target is out of sight, we should resume normal operation. not the case currently
1600
1601 // After some time, we'll no longer eye the Tux but rather do something,
1602 // like attack the Tux or maybe also return to 'normal' operation and do
1603 // nothing. When Tux is still visible at timeout, then it will be attacked... otherwise
1604 // the robot resumes normal operation...
1605 //
1606 ThisRobot->state_timeout += Frame_Time();
1607 if (ThisRobot->state_timeout > Droidmap[ThisRobot->type].time_spent_eyeing_tux) {
1608 ThisRobot->state_timeout = 0;
1609 SetRestOfGroupToState(ThisRobot, ATTACK);
1610 ThisRobot->combat_state = ATTACK;
1611 ThisRobot->last_combat_step = ATTACK_MOVE_RATE + 1.0; // So that attack will start immediately
1612 if (Droidmap[ThisRobot->type].greeting_sound_type != (-1)) {
1613 play_enter_attack_run_state_sound(ThisRobot);
1614 }
1615 }
1616 }
1617
1618 /* ---------------------------------
1619 * "attack tux" state
1620 *
1621 * This function will compute the destination position of a bot in order
1622 * to reach its target. In the caller (update_enemy), the pathfinder is
1623 * called, to define the path of the bot up to its target.
1624 *
1625 * This function will also eventually start a shoot.
1626 *
1627 * --------------------------------- */
1628
1629 /*
1630 * Note concerning the modification of pathfinder_context :
1631 *
1632 * When we ask the pathfinder to find a path between two points, the pathfinder
1633 * will, by default, check if no bots are blocking the path.
1634 * Let consider the following case : an attacking bot (B1) with a melee weapon
1635 * is at the entrance of a corridor. Its target (T) is inside the corridor, and an
1636 * other bot (B2) is also inside the corridor :
1637 * ---------------------
1638 * B1 B2 T
1639 * ---------------------
1640 * In this case, the pathfinder will not be able to find a way between B1 and T.
1641 * B1 will thus not move.
1642 *
1643 * However, if B1 were able to go just behind B2, it could be near enough to its
1644 * target to shoot it :
1645 * ---------------------
1646 * B1 B2 T
1647 * ---------------------
1648 * Such behavior will make B1 be as aggressive as possible.
1649 * We will only use it with friendly bots, so that the game is not too hard.
1650 *
1651 * To implement this behavior, we have to tell the pathfinder not to check
1652 * collisions with bots. This is implemented in ReachMeleeCombat().
1653 */
state_machine_attack(enemy * ThisRobot,moderately_finepoint * new_move_target,pathfinder_context * pf_ctx)1654 static void state_machine_attack(enemy * ThisRobot, moderately_finepoint * new_move_target, pathfinder_context * pf_ctx)
1655 {
1656 // Not yet time to computer a new bot's move, or to start a new shoot
1657 if (ThisRobot->firewait > 0.0 && ThisRobot->last_combat_step < ATTACK_MOVE_RATE)
1658 return;
1659
1660 // Get old target's position
1661 gps *tpos = enemy_get_target_position(ThisRobot);
1662
1663 // Target no more available -> going to default state
1664 if (!tpos) {
1665 ThisRobot->combat_state = UNDEFINED_STATE;
1666 ThisRobot->attack_target_type = ATTACK_TARGET_IS_NOTHING;
1667 enemy_set_reference(&ThisRobot->bot_target_n, &ThisRobot->bot_target_addr, NULL);
1668 return;
1669 }
1670 // In case the target is on another level, evaluate the virtual position of the bot
1671 // in the target's level
1672 update_virtual_position(&ThisRobot->virt_pos, &ThisRobot->pos, tpos->z);
1673
1674 // First compute the type of the bot's move, and if the target can be shot,
1675 // depending on the weapon's type and the distance to the target
1676 //
1677 gps move_pos = { tpos->x, tpos->y, tpos->z };
1678
1679 enum {
1680 NO_MOVE,
1681 REACH_MELEE,
1682 MOVE_MELEE,
1683 MOVE_AWAY
1684 } move_type = NO_MOVE;
1685
1686 int shoot_target = FALSE;
1687 int melee_weapon = ItemMap[Droidmap[ThisRobot->type].weapon_item.type].weapon_is_melee;
1688
1689 if (melee_weapon) {
1690 // The bot and its target are on different levels.
1691 if (ThisRobot->pos.z != move_pos.z) {
1692 // Before to start the attack, the bot has to reach its target's level.
1693 // Hence, compute virtual position to reach, and wait until the bot is at
1694 // the right level.
1695 update_virtual_position(&move_pos, tpos, ThisRobot->pos.z);
1696 move_type = REACH_MELEE;
1697 shoot_target = FALSE;
1698 goto EXECUTE_ATTACK;
1699 }
1700
1701 // Check visibility
1702 int target_visible =
1703 DirectLineColldet(ThisRobot->virt_pos.x, ThisRobot->virt_pos.y, move_pos.x, move_pos.y, move_pos.z,
1704 &WalkablePassFilter);
1705
1706 if (!target_visible) {
1707 move_type = REACH_MELEE;
1708 shoot_target = FALSE;
1709 goto EXECUTE_ATTACK;
1710 }
1711 // Check distance
1712 float dist2 =
1713 (ThisRobot->virt_pos.x - move_pos.x) * (ThisRobot->virt_pos.x - move_pos.x) + (ThisRobot->virt_pos.y -
1714 move_pos.y) * (ThisRobot->virt_pos.y -
1715 move_pos.y);
1716
1717 if (dist2 > SQUARED_MELEE_APPROACH_DIST) {
1718 move_type = REACH_MELEE;
1719 shoot_target = FALSE;
1720 goto EXECUTE_ATTACK;
1721 }
1722
1723 if (dist2 > SQUARED_MELEE_MAX_DIST) { // Approaching the target -> find a place near the bot
1724 move_type = MOVE_MELEE;
1725 shoot_target = FALSE;
1726 goto EXECUTE_ATTACK;
1727 }
1728 // All tests passed, the bot can shot.
1729 shoot_target = TRUE;
1730 goto EXECUTE_ATTACK;
1731 }
1732
1733 else // Range weapon
1734
1735 {
1736 // Check visibility
1737 int target_visible =
1738 DirectLineColldet(ThisRobot->virt_pos.x, ThisRobot->virt_pos.y, move_pos.x, move_pos.y, move_pos.z, &FlyablePassFilter);
1739
1740 if (!target_visible) {
1741 move_type = REACH_MELEE;
1742 shoot_target = FALSE;
1743 goto EXECUTE_ATTACK;
1744 }
1745 // Check distance
1746 float dist2 =
1747 (ThisRobot->virt_pos.x - move_pos.x) * (ThisRobot->virt_pos.x - move_pos.x) + (ThisRobot->virt_pos.y -
1748 move_pos.y) * (ThisRobot->virt_pos.y -
1749 move_pos.y);
1750
1751 if (dist2 < SQUARED_RANGE_SHOOT_MIN_DIST) { // Too close -> move away, and start shooting
1752 move_type = MOVE_AWAY;
1753 shoot_target = TRUE;
1754 goto EXECUTE_ATTACK;
1755 }
1756 // Check if outside of bullet range
1757 itemspec *bot_weapon = &ItemMap[Droidmap[ThisRobot->type].weapon_item.type];
1758 float shot_range = bot_weapon->weapon_bullet_lifetime * bot_weapon->weapon_bullet_speed;
1759 float squared_shot_range = shot_range * shot_range;
1760
1761 if (dist2 >= squared_shot_range) {
1762 move_type = REACH_MELEE;
1763 shoot_target = FALSE;
1764 goto EXECUTE_ATTACK;
1765 }
1766 // All tests passed, the bot can shoot.
1767 shoot_target = TRUE;
1768 goto EXECUTE_ATTACK;
1769
1770 }
1771
1772 EXECUTE_ATTACK:
1773
1774 // Execute the bot's move
1775 //
1776 // We will often have to move towards our target.
1777 // But this moving around can lead to jittering of droids moving back and
1778 // forth between two positions very rapidly. Therefore we will not do this
1779 // movement thing every frame, but rather only sometimes
1780 //
1781
1782 if (ThisRobot->last_combat_step >= ATTACK_MOVE_RATE) {
1783 ThisRobot->last_combat_step = 0;
1784
1785 switch (move_type) {
1786 case REACH_MELEE:
1787 ReachMeleeCombat(ThisRobot, &move_pos, new_move_target, pf_ctx);
1788 break;
1789 case MOVE_MELEE:
1790 MoveToMeleeCombat(ThisRobot, &move_pos, new_move_target);
1791 break;
1792 case MOVE_AWAY:
1793 MoveAwayFromMeleeCombat(ThisRobot, new_move_target);
1794 break;
1795 default:
1796 break;
1797 }
1798 } else {
1799 ThisRobot->last_combat_step += Frame_Time();
1800 }
1801
1802 // Execute the bot's shoot
1803
1804 /* Great suggestion of Sarayan : we do not care about friendly fire, and make bullets go through people of the same side. */
1805 if (shoot_target && ThisRobot->firewait <= 0)
1806 RawStartEnemysShot(ThisRobot, move_pos.x - ThisRobot->virt_pos.x, move_pos.y - ThisRobot->virt_pos.y);
1807
1808 }
1809
state_machine_paralyzed(enemy * ThisRobot,moderately_finepoint * new_move_target)1810 static void state_machine_paralyzed(enemy * ThisRobot, moderately_finepoint * new_move_target)
1811 {
1812 /* Move target - none */
1813 new_move_target->x = ThisRobot->pos.x;
1814 new_move_target->y = ThisRobot->pos.y;
1815
1816 if (ThisRobot->paralysation_duration_left <= 0)
1817 ThisRobot->combat_state = SELECT_NEW_WAYPOINT;
1818 }
1819
state_machine_returning_home(enemy * ThisRobot,moderately_finepoint * new_move_target)1820 static void state_machine_returning_home(enemy * ThisRobot, moderately_finepoint * new_move_target)
1821 {
1822 /* Bot too far away from home must go back to home waypoint */
1823
1824 waypoint *wpts = curShip.AllLevels[ThisRobot->pos.z]->waypoints.arr;
1825
1826 /* Move target */
1827 new_move_target->x = wpts[ThisRobot->homewaypoint].x + 0.5;
1828 new_move_target->y = wpts[ThisRobot->homewaypoint].y + 0.5;
1829
1830 /* Action */
1831 if (remaining_distance_to_current_walk_target(ThisRobot) < ThisRobot->max_distance_to_home / 2.0) {
1832 ThisRobot->combat_state = SELECT_NEW_WAYPOINT;
1833 return;
1834 }
1835
1836 ThisRobot->combat_state = RETURNING_HOME;
1837 }
1838
state_machine_select_new_waypoint(enemy * ThisRobot,moderately_finepoint * new_move_target)1839 static void state_machine_select_new_waypoint(enemy * ThisRobot, moderately_finepoint * new_move_target)
1840 {
1841 /* Move target - none */
1842 new_move_target->x = ThisRobot->pos.x;
1843 new_move_target->y = ThisRobot->pos.y;
1844
1845 /* Bot must select a new waypoint randomly, and turn towards it. No move this step. */
1846 if (!set_new_random_waypoint(ThisRobot)) { /* couldn't find a waypoint ? go waypointless */
1847 ThisRobot->combat_state = WAYPOINTLESS_WANDERING;
1848 } else {
1849 ThisRobot->combat_state = TURN_TOWARDS_NEXT_WAYPOINT;
1850 }
1851 }
1852
state_machine_turn_towards_next_waypoint(enemy * ThisRobot,moderately_finepoint * new_move_target)1853 static void state_machine_turn_towards_next_waypoint(enemy * ThisRobot, moderately_finepoint * new_move_target)
1854 {
1855 waypoint *wpts = curShip.AllLevels[ThisRobot->pos.z]->waypoints.arr;
1856
1857 /* Action */
1858 /* XXX */
1859 new_move_target->x = ThisRobot->pos.x;
1860 new_move_target->y = ThisRobot->pos.y;
1861 ThisRobot->last_phase_change = WAIT_BEFORE_ROTATE + 1.0;
1862
1863 if (TurnABitTowardsPosition(ThisRobot, wpts[ThisRobot->nextwaypoint].x + 0.5, wpts[ThisRobot->nextwaypoint].y + 0.5, 90)) {
1864 new_move_target->x = wpts[ThisRobot->nextwaypoint].x + 0.5;
1865 new_move_target->y = wpts[ThisRobot->nextwaypoint].y + 0.5;
1866 ThisRobot->combat_state = MOVE_ALONG_RANDOM_WAYPOINTS;
1867 }
1868 }
1869
state_machine_move_along_random_waypoints(enemy * ThisRobot,moderately_finepoint * new_move_target)1870 static void state_machine_move_along_random_waypoints(enemy * ThisRobot, moderately_finepoint * new_move_target)
1871 {
1872 /* The bot moves towards its next waypoint */
1873
1874 waypoint *wpts = curShip.AllLevels[ThisRobot->pos.z]->waypoints.arr;
1875
1876 /* Move target */
1877 new_move_target->x = wpts[ThisRobot->nextwaypoint].x + 0.5;
1878 new_move_target->y = wpts[ThisRobot->nextwaypoint].y + 0.5;
1879
1880 /* Action */
1881 if ((new_move_target->x - ThisRobot->pos.x) * (new_move_target->x - ThisRobot->pos.x) +
1882 (new_move_target->y - ThisRobot->pos.y) * (new_move_target->y - ThisRobot->pos.y) < DIST_TO_INTERM_POINT*DIST_TO_INTERM_POINT) {
1883 ThisRobot->combat_state = SELECT_NEW_WAYPOINT;
1884 return;
1885 }
1886
1887 ThisRobot->combat_state = MOVE_ALONG_RANDOM_WAYPOINTS;
1888 }
1889
state_machine_rush_tux_and_open_talk(enemy * ThisRobot,moderately_finepoint * new_move_target)1890 static void state_machine_rush_tux_and_open_talk(enemy * ThisRobot, moderately_finepoint * new_move_target)
1891 {
1892 /* Move target */
1893 if (ThisRobot->pos.z == Me.pos.z) {
1894 new_move_target->x = Me.pos.x;
1895 new_move_target->y = Me.pos.y;
1896 } else {
1897 new_move_target->x = ThisRobot->pos.x;
1898 new_move_target->y = ThisRobot->pos.y;
1899 }
1900
1901 /* Action */
1902 if (sqrt((ThisRobot->virt_pos.x - Me.pos.x) * (ThisRobot->virt_pos.x - Me.pos.x) + (ThisRobot->virt_pos.y - Me.pos.y) * (ThisRobot->virt_pos.y - Me.pos.y)) < 1) { //if we are close enough to tux, we talk
1903 chat_with_droid(ThisRobot);
1904 ThisRobot->will_rush_tux = FALSE;
1905 ThisRobot->combat_state = SELECT_NEW_WAYPOINT;
1906 return;
1907 }
1908 }
1909
state_machine_follow_tux(enemy * ThisRobot,moderately_finepoint * new_move_target)1910 static void state_machine_follow_tux(enemy * ThisRobot, moderately_finepoint * new_move_target)
1911 {
1912 if (!ThisRobot->follow_tux || (!can_see_tux(ThisRobot)) ) {
1913 ThisRobot->combat_state = WAYPOINTLESS_WANDERING;
1914 new_move_target->x = ThisRobot->pos.x;
1915 new_move_target->y = ThisRobot->pos.y;
1916 return;
1917 }
1918
1919 /* Move target - The friendly bot will follow Tux, but with a small delay */
1920
1921 // Compute a FrameRate independent delay
1922 int delay = 0.5 * 1.0 / Frame_Time(); // Number of frame for a 0.5 seconds
1923 delay <<= 1; // InfluPosition is only stored every two frames
1924
1925 if (GetInfluPositionHistoryZ(delay) == ThisRobot->pos.z) {
1926 new_move_target->x = GetInfluPositionHistoryX(delay);
1927 new_move_target->y = GetInfluPositionHistoryY(delay);
1928
1929 moderately_finepoint ab = { ThisRobot->pos.x - new_move_target->x, ThisRobot->pos.y - new_move_target->y };
1930 if (fabsf(ab.x) < 1 && fabsf(ab.y) < 1) {
1931 new_move_target->x = ThisRobot->pos.x;
1932 new_move_target->y = ThisRobot->pos.y;
1933 }
1934 } else {
1935 update_virtual_position(&ThisRobot->virt_pos, &ThisRobot->pos, Me.pos.z);
1936 if (ThisRobot->virt_pos.z != -1) {
1937 new_move_target->x = ThisRobot->pos.x + Me.pos.x - ThisRobot->virt_pos.x;
1938 new_move_target->y = ThisRobot->pos.y + Me.pos.y - ThisRobot->virt_pos.y;
1939 } else {
1940 ThisRobot->combat_state = WAYPOINTLESS_WANDERING;
1941 new_move_target->x = ThisRobot->pos.x;
1942 new_move_target->y = ThisRobot->pos.y;
1943 }
1944 }
1945 }
1946
state_machine_completely_fixed(enemy * ThisRobot,moderately_finepoint * new_move_target)1947 static void state_machine_completely_fixed(enemy * ThisRobot, moderately_finepoint * new_move_target)
1948 {
1949 /* Move target */
1950 new_move_target->x = ThisRobot->pos.x;
1951 new_move_target->y = ThisRobot->pos.y;
1952
1953 if (!ThisRobot->CompletelyFixed)
1954 ThisRobot->combat_state = WAYPOINTLESS_WANDERING;
1955 }
1956
state_machine_waypointless_wandering(enemy * ThisRobot,moderately_finepoint * new_move_target)1957 static void state_machine_waypointless_wandering(enemy * ThisRobot, moderately_finepoint * new_move_target)
1958 {
1959 if (remaining_distance_to_current_walk_target(ThisRobot) < 0.1) {
1960 set_new_waypointless_walk_target(ThisRobot, new_move_target);
1961 TurnABitTowardsPosition(ThisRobot, new_move_target->x, new_move_target->y, 90);
1962 ThisRobot->combat_state = WAYPOINTLESS_WANDERING;
1963 }
1964 }
1965
1966 /**
1967 *
1968 * This function runs the finite state automaton that powers the bots.
1969 * It handles attack and movement behaviors.
1970 *
1971 */
update_enemy(enemy * ThisRobot)1972 void update_enemy(enemy * ThisRobot)
1973 {
1974 /* New structure :
1975 *
1976 * Inconditional updates:
1977 * debug stuff (say state on screen)
1978 * unstick from walls if relevant
1979 * certain switches (cleanup to be made here)
1980 * find an attack target (we consider it state independent)
1981 * reset speed to 0 (for now)
1982 *
1983 * Situational state changes (transitions from any state to a given one in certain input conditions)
1984 * switch to RUSH TUX
1985 * switch to RETURN HOME
1986 *
1987 * Per-state actions
1988 * for each state:
1989 * compute a new moving target (no path finding there, just tell where to go)
1990 * do actions if appropriate (attack, talk, whatever)
1991 * transition to a state
1992 *
1993 * Universal actions ("could" be merged with inconditional updates)
1994 * pathfind the new moving target if applicable (it differs from the current moving target)
1995 * move (special case target = cur. position)
1996 *
1997 */
1998
1999 /* Inconditional updates */
2000 state_machine_inconditional_updates(ThisRobot);
2001
2002 /* Situational state changes */
2003 state_machine_situational_transitions(ThisRobot);
2004
2005 moderately_finepoint new_move_target;
2006 enemy_get_current_walk_target(ThisRobot, &new_move_target);
2007
2008 /* Handle per-state switches and actions.
2009 * Each state much set move_target and combat_state.
2010 */
2011
2012 // Default pathfinder execution context
2013 // Can eventually be changed by a state_machine_xxxx function
2014 freeway_context frw_ctx = { FALSE, {ThisRobot, NULL} };
2015 pathfinder_context pf_ctx = { &WalkableWithMarginPassFilter, &frw_ctx };
2016
2017 switch (ThisRobot->combat_state) {
2018 case STOP_AND_EYE_TARGET:
2019 state_machine_stop_and_eye_target(ThisRobot, &new_move_target);
2020 break;
2021
2022 case ATTACK:
2023 state_machine_attack(ThisRobot, &new_move_target, &pf_ctx);
2024 break;
2025
2026 case PARALYZED:
2027 state_machine_paralyzed(ThisRobot, &new_move_target);
2028 break;
2029
2030 case COMPLETELY_FIXED:
2031 state_machine_completely_fixed(ThisRobot, &new_move_target);
2032 break;
2033
2034 case FOLLOW_TUX:
2035 state_machine_follow_tux(ThisRobot, &new_move_target);
2036 break;
2037
2038 case RETURNING_HOME:
2039 state_machine_returning_home(ThisRobot, &new_move_target);
2040 break;
2041
2042 case SELECT_NEW_WAYPOINT:
2043 state_machine_select_new_waypoint(ThisRobot, &new_move_target);
2044 break;
2045
2046 case TURN_TOWARDS_NEXT_WAYPOINT:
2047 state_machine_turn_towards_next_waypoint(ThisRobot, &new_move_target);
2048 break;
2049
2050 case MOVE_ALONG_RANDOM_WAYPOINTS:
2051 state_machine_move_along_random_waypoints(ThisRobot, &new_move_target);
2052 break;
2053
2054 case RUSH_TUX_AND_OPEN_TALK:
2055 state_machine_rush_tux_and_open_talk(ThisRobot, &new_move_target);
2056 break;
2057
2058 case WAYPOINTLESS_WANDERING:
2059 state_machine_waypointless_wandering(ThisRobot, &new_move_target);
2060 break;
2061
2062 }
2063
2064 /* Pathfind current target */
2065 /* I am sorry this is a bit dirty, but I've got time and efficiency constraints. If you're not happy please send a patch. No complaints will
2066 * be accepted.*/
2067
2068 /* The basic design is the following :
2069 * we get the current moving target of the bot (ie. old)
2070 * we compare the new and current moving target
2071 * if they differ : we have to set up a new route (pathfind the route)
2072 * if they do not : we move towards our first waypoint
2073 *
2074 * special case:
2075 * if first waypoint is -1 -1 we have a bug and do nothing (hack around)
2076 */
2077 moderately_finepoint wps[40];
2078 moderately_finepoint old_move_target;
2079 enemy_get_current_walk_target(ThisRobot, &old_move_target);
2080
2081 if ((new_move_target.x == ThisRobot->pos.x) && (new_move_target.y == ThisRobot->pos.y)) { // If the bot stopped moving, create a void path
2082 ThisRobot->PrivatePathway[0].x = ThisRobot->pos.x;
2083 ThisRobot->PrivatePathway[0].y = ThisRobot->pos.y;
2084 ThisRobot->PrivatePathway[1].x = -1;
2085 ThisRobot->PrivatePathway[1].y = -1;
2086 } else if (((new_move_target.x != old_move_target.x) || (new_move_target.y != old_move_target.y))) { // If the current move target differs from the old one
2087 // This implies we do not re-pathfind every frame, which means we may bump into colleagues.
2088 // This is handled in MoveThisEnemy()
2089 if (set_up_intermediate_course_between_positions(&ThisRobot->pos, &new_move_target, &wps[0], 40, &pf_ctx) && wps[5].x == -1) { /* If position was passable *and* streamline course uses max 4 waypoints */
2090 memcpy(&ThisRobot->PrivatePathway[0], &wps[0], 5 * sizeof(moderately_finepoint));
2091 } else {
2092 ThisRobot->PrivatePathway[0].x = ThisRobot->pos.x;
2093 ThisRobot->PrivatePathway[0].y = ThisRobot->pos.y;
2094 ThisRobot->PrivatePathway[1].x = -1;
2095 ThisRobot->PrivatePathway[1].y = -1;
2096 if (ThisRobot->pure_wait < WAIT_COLLISION)
2097 ThisRobot->pure_wait = WAIT_COLLISION;
2098 }
2099 }
2100
2101 if (ThisRobot->PrivatePathway[0].x == -1) {
2102 /* This happens at the very beginning of the game. If it happens afterwards this is a ugly bug. */
2103 ThisRobot->PrivatePathway[0].x = ThisRobot->pos.x;
2104 ThisRobot->PrivatePathway[0].y = ThisRobot->pos.y;
2105 }
2106
2107 MoveThisEnemy(ThisRobot);
2108 }; // void update_enemy()
2109
2110 /**
2111 * This function handles all the logic tied to enemies : animation, movement
2112 * and attack behavior.
2113 *
2114 * Note that no enemy must be killed by the logic function. It's a technical limitation
2115 * and a requirement in FreedroidRPG.
2116 */
move_enemies(void)2117 void move_enemies(void)
2118 {
2119 heal_robots_over_time();
2120
2121 enemy *erot, *nerot;
2122 BROWSE_ALIVE_BOTS_SAFE(erot, nerot) {
2123 // Ignore robots on levels that can't be seen
2124 if (!level_is_visible(erot->pos.z))
2125 continue;
2126
2127 animate_enemy(erot);
2128
2129 // Run a new cycle of the bot's state machine
2130 update_enemy(erot);
2131 }
2132
2133 BROWSE_DEAD_BOTS_SAFE(erot, nerot) {
2134 // Ignore robots on levels that can't be seen
2135 if (!level_is_visible(erot->pos.z))
2136 continue;
2137
2138 animate_enemy(erot);
2139 }
2140 }
2141
2142 /**
2143 * When an enemy is firing a shot, the newly created bullet must be
2144 * assigned a speed, that would lead the bullet towards the intended
2145 * target, which is done here.
2146 */
set_bullet_speed_to_target_direction(bullet * NewBullet,float bullet_speed,float xdist,float ydist)2147 void set_bullet_speed_to_target_direction(bullet * NewBullet, float bullet_speed, float xdist, float ydist)
2148 {
2149 // determine the direction of the shot, so that it will go into the direction of
2150 // the target
2151 //
2152 if (fabsf(xdist) > fabsf(ydist)) {
2153 NewBullet->speed.x = bullet_speed;
2154 NewBullet->speed.y = ydist * NewBullet->speed.x / xdist;
2155 if (xdist < 0) {
2156 NewBullet->speed.x = -NewBullet->speed.x;
2157 NewBullet->speed.y = -NewBullet->speed.y;
2158 }
2159 }
2160
2161 if (fabsf(xdist) < fabsf(ydist)) {
2162 NewBullet->speed.y = bullet_speed;
2163 NewBullet->speed.x = xdist * NewBullet->speed.y / ydist;
2164 if (ydist < 0) {
2165 NewBullet->speed.x = -NewBullet->speed.x;
2166 NewBullet->speed.y = -NewBullet->speed.y;
2167 }
2168 }
2169 }; // void set_bullet_speed_to_target_direction ( bullet* NewBullet , float bullet_speed , float xdist , float ydist )
2170
2171 /**
2172 * This function is low-level: It simply sets off a shot from enemy
2173 * through the pointer ThisRobot at the target VECTOR xdist ydist, which
2174 * is a DISTANCE VECTOR, NOT ABSOLUTE COORDINATES OF THE TARGET!!!
2175 */
RawStartEnemysShot(enemy * ThisRobot,float xdist,float ydist)2176 static void RawStartEnemysShot(enemy * ThisRobot, float xdist, float ydist)
2177 {
2178 // If the robot is not in walk or stand animation, i.e. if it's in
2179 // gethit, death or attack animation, then we can't start another
2180 // shot/attack right now...
2181 //
2182 if ((ThisRobot->animation_type != WALK_ANIMATION) && (ThisRobot->animation_type != STAND_ANIMATION))
2183 return;
2184
2185 /* First of all, check what kind of weapon the bot has : ranged or melee */
2186 struct itemspec weapon_spec = ItemMap[Droidmap[ThisRobot->type].weapon_item.type];
2187
2188 if (!weapon_spec.weapon_is_melee) { /* ranged */
2189
2190 // find a bullet entry, that isn't currently used...
2191 //
2192 int bullet_index = find_free_bullet_index();
2193 if (bullet_index == -1) {
2194 // We are out of free bullet slots.
2195 // This should not happen, an error message was displayed,
2196 return;
2197 }
2198
2199 bullet *new_bullet = &(AllBullets[bullet_index]);
2200
2201 bullet_init_for_enemy(new_bullet, weapon_spec.weapon_bullet_type,
2202 Droidmap[ThisRobot->type].weapon_item.type, ThisRobot);
2203
2204 // We send the bullet onto it's way towards the given target
2205 float bullet_speed = (float)weapon_spec.weapon_bullet_speed;
2206 set_bullet_speed_to_target_direction(new_bullet, bullet_speed, xdist, ydist);
2207
2208 // Enemies also have to respect the angle modifier in their weapons...
2209 new_bullet->angle = -(90 + 45 + 180 * atan2(new_bullet->speed.y, new_bullet->speed.x) / M_PI);
2210
2211 // At this point we mention, that when not moving anywhere, the robot should also
2212 // face into the direction of the shot
2213 ThisRobot->previous_angle = new_bullet->angle + 180;
2214
2215 // Change bullet starting position so that they don't hit the shooter...
2216 new_bullet->pos.x += (new_bullet->speed.x) / (bullet_speed) * 0.5;
2217 new_bullet->pos.y += (new_bullet->speed.y) / (bullet_speed) * 0.5;
2218
2219 } else { /* melee weapon */
2220
2221 int shot_index = find_free_melee_shot_index();
2222 if (shot_index == -1) {
2223 // We are out of free melee shot slots.
2224 // This should not happen, an error message was displayed,
2225 return;
2226 }
2227
2228 melee_shot *NewShot = &(AllMeleeShots[shot_index]);
2229
2230 NewShot->attack_target_type = ThisRobot->attack_target_type;
2231 NewShot->mine = FALSE; /* shot comes from a bot not tux */
2232
2233 if (ThisRobot->attack_target_type == ATTACK_TARGET_IS_ENEMY) {
2234 NewShot->bot_target_n = ThisRobot->bot_target_n;
2235 NewShot->bot_target_addr = ThisRobot->bot_target_addr;
2236 } else { /* enemy bot attacking tux */
2237 enemy_set_reference(&NewShot->bot_target_n, &NewShot->bot_target_addr, NULL);
2238 }
2239
2240 NewShot->to_hit = Droidmap[ThisRobot->type].to_hit;
2241 NewShot->damage = weapon_spec.weapon_base_damage + MyRandom(weapon_spec.weapon_damage_modifier);
2242 NewShot->owner = ThisRobot->id;
2243 }
2244
2245 ThisRobot->ammo_left--;
2246 if (ThisRobot->ammo_left > 0) {
2247 ThisRobot->firewait += weapon_spec.weapon_attack_time;
2248 } else {
2249 ThisRobot->ammo_left = weapon_spec.weapon_ammo_clip_size;
2250 if (ThisRobot->firewait < weapon_spec.weapon_reloading_time)
2251 ThisRobot->firewait = weapon_spec.weapon_reloading_time;
2252 }
2253
2254 if (ThisRobot->firewait < weapon_spec.weapon_attack_time)
2255 ThisRobot->firewait = weapon_spec.weapon_attack_time;
2256
2257 if (last_attack_animation_image[Droidmap[ThisRobot->type].individual_shape_nr] - first_attack_animation_image[Droidmap[ThisRobot->type].individual_shape_nr] > 1) {
2258 ThisRobot->animation_phase = ((float)first_attack_animation_image[Droidmap[ThisRobot->type].individual_shape_nr]) + 0.1;
2259 ThisRobot->animation_type = ATTACK_ANIMATION;
2260 ThisRobot->current_angle = -(-90 + 180 * atan2(ydist, xdist) / M_PI);
2261 }
2262
2263 if (!weapon_spec.weapon_is_melee)
2264 fire_bullet_sound(weapon_spec.weapon_bullet_type, &ThisRobot->pos);
2265 else
2266 play_melee_weapon_missed_sound(&ThisRobot->pos);
2267 }; // void RawStartEnemysShot( enemy* ThisRobot , float xdist , float ydist )
2268
2269 /**
2270 * In some of the movement functions for enemy droids, we consider making
2271 * a step and move a bit into one direction or the other. But not all
2272 * moves are really allowed and feasible. Therefore we need a function
2273 * to check if a certain step makes sense or not, which is exactly what
2274 * this function is supposed to do.
2275 *
2276 */
ConsideredMoveIsFeasible(Enemy ThisRobot,moderately_finepoint StepVector)2277 static int ConsideredMoveIsFeasible(Enemy ThisRobot, moderately_finepoint StepVector)
2278 {
2279 freeway_context frw_ctx = { TRUE, {ThisRobot, NULL} };
2280
2281 if ((DirectLineColldet(ThisRobot->pos.x, ThisRobot->pos.y, ThisRobot->pos.x + StepVector.x,
2282 ThisRobot->pos.y + StepVector.y,
2283 ThisRobot->pos.z, NULL)) &&
2284 (way_free_of_droids(ThisRobot->pos.x, ThisRobot->pos.y,
2285 ThisRobot->pos.x + StepVector.x, ThisRobot->pos.y + StepVector.y, ThisRobot->pos.z, &frw_ctx))) {
2286 return TRUE;
2287 }
2288
2289 return FALSE;
2290
2291 }; // int ConsideredMoveIsFeasible ( Enemy ThisRobot , finepoint StepVector )
2292
2293 /*
2294 * This function will find a place near target_pos that is free of any bots.
2295 *
2296 * During a melee, several bots will try to shoot a common enemy.
2297 * We will "distribute" them around the enemy.
2298 *
2299 * The approach is :
2300 * - compute several positions around the target, by rotating a unit vector
2301 * - halt as soon as one of those positions is free
2302 */
2303
MoveToMeleeCombat(enemy * ThisRobot,gps * target_pos,moderately_finepoint * set_move_tgt)2304 static void MoveToMeleeCombat(enemy *ThisRobot, gps *target_pos, moderately_finepoint *set_move_tgt)
2305 {
2306 freeway_context frw_ctx = { is_friendly(ThisRobot->faction, FACTION_SELF), {ThisRobot->bot_target_addr, NULL} };
2307
2308 // All computations are done in the target's level
2309 gps bot_vpos;
2310 update_virtual_position(&bot_vpos, &ThisRobot->pos, target_pos->z);
2311
2312 // Compute a unit vector from target to ThisRobot
2313 moderately_finepoint vector_from_target = { bot_vpos.x, bot_vpos.y }; //
2314 normalize_vect(target_pos->x, target_pos->y, &(vector_from_target.x), &(vector_from_target.y));
2315 vector_from_target.x -= target_pos->x;
2316 vector_from_target.y -= target_pos->y; // vector_from_target holds the coordinates of normalized target -> bot vector
2317
2318 // Fallback if there are no free positions -> Rush on Tux
2319 gps final_pos =
2320 { target_pos->x + vector_from_target.x * MELEE_MIN_DIST, target_pos->y + vector_from_target.y * MELEE_MIN_DIST, target_pos->z };
2321
2322 // Rotate the unit vector
2323 float angles_to_try[8] = { 0, 45, -45, 90, -90, 135, -135, 180 };
2324 int a;
2325 for (a = 0; a < 8; ++a) {
2326 int target_reachable = FALSE;
2327 moderately_finepoint checked_vector = { vector_from_target.x, vector_from_target.y };
2328 RotateVectorByAngle(&checked_vector, angles_to_try[a]);
2329 moderately_finepoint checked_pos =
2330 { target_pos->x + checked_vector.x * MELEE_MIN_DIST, target_pos->y + checked_vector.y * MELEE_MIN_DIST };
2331
2332 // Lower quantization of checked_pos, to limit bot jittering around the target
2333 checked_pos.x = floorf(checked_pos.x * 10.0) / 10.0;
2334 checked_pos.y = floorf(checked_pos.y * 10.0) / 10.0;
2335
2336 if (way_free_of_droids(target_pos->x, target_pos->y, checked_pos.x, checked_pos.y, target_pos->z, &frw_ctx)) {
2337 // If the checked_pos is free, also check that the target is reachable
2338 target_reachable =
2339 DirectLineColldet(checked_pos.x, checked_pos.y, target_pos->x, target_pos->y, target_pos->z,
2340 &WalkablePassFilter);
2341 if (target_reachable) { // The position has been found
2342 final_pos.x = checked_pos.x;
2343 final_pos.y = checked_pos.y;
2344 break;
2345 }
2346 }
2347 }
2348
2349 // Transform back into ThisRobot's reference level
2350 update_virtual_position(&bot_vpos, &final_pos, ThisRobot->pos.z);
2351 set_move_tgt->x = bot_vpos.x;
2352 set_move_tgt->y = bot_vpos.y;
2353 }
2354
2355 /**
2356 *
2357 */
MoveAwayFromMeleeCombat(Enemy ThisRobot,moderately_finepoint * set_move_tgt)2358 static void MoveAwayFromMeleeCombat(Enemy ThisRobot, moderately_finepoint * set_move_tgt)
2359 {
2360 finepoint VictimPosition = { 0.0, 0.0 };
2361 finepoint CurrentPosition = { 0.0, 0.0 };
2362 moderately_finepoint StepVector;
2363 moderately_finepoint RotatedStepVector;
2364 float StepVectorLen;
2365 int i;
2366
2367 #define ANGLES_TO_TRY 7
2368 float RotationAngleTryList[ANGLES_TO_TRY] = { 0, 30, 360 - 30, 60, 360 - 60, 90, 360 - 90 };
2369
2370 VictimPosition.x = enemy_get_target_position(ThisRobot)->x;
2371 VictimPosition.y = enemy_get_target_position(ThisRobot)->y;
2372
2373 CurrentPosition.x = ThisRobot->virt_pos.x;
2374 CurrentPosition.y = ThisRobot->virt_pos.y;
2375
2376 StepVector.x = VictimPosition.x - CurrentPosition.x;
2377 StepVector.y = VictimPosition.y - CurrentPosition.y;
2378
2379 // Now some protection against division by zero when two bots
2380 // have _exactly_ the same position, i.e. are standing on top
2381 // of each other:
2382 //
2383 if ((fabsf(StepVector.x) < 0.01) && (fabsf(StepVector.y) < 0.01)) {
2384 if (MyRandom(1) == 1) {
2385 StepVector.x = 1.0;
2386 StepVector.y = 1.0;
2387 } else {
2388 StepVector.x = -1.0;
2389 StepVector.y = -1.0;
2390 }
2391 }
2392
2393 StepVectorLen = sqrt((StepVector.x) * (StepVector.x) + (StepVector.y) * (StepVector.y));
2394
2395 StepVector.x /= (-1 * StepVectorLen);
2396 StepVector.y /= (-1 * StepVectorLen);
2397
2398 for (i = 0; i < ANGLES_TO_TRY; i++) {
2399 RotatedStepVector.x = StepVector.x;
2400 RotatedStepVector.y = StepVector.y;
2401 RotateVectorByAngle(&RotatedStepVector, RotationAngleTryList[i]);
2402
2403 // Maybe we've found a solution, then we can take it and quit
2404 // trying around...
2405 //
2406 if ( /*XXX*/ ConsideredMoveIsFeasible(ThisRobot, RotatedStepVector) &&
2407 DirectLineColldet(ThisRobot->virt_pos.x + RotatedStepVector.x, ThisRobot->virt_pos.y + RotatedStepVector.y,
2408 VictimPosition.x, VictimPosition.y, ThisRobot->pos.z, NULL)
2409 ) {
2410 set_move_tgt->x = ThisRobot->pos.x + RotatedStepVector.x;
2411 set_move_tgt->y = ThisRobot->pos.y + RotatedStepVector.y;
2412 break;
2413 }
2414 // No solution, don't move
2415 set_move_tgt->x = ThisRobot->pos.x;
2416 set_move_tgt->y = ThisRobot->pos.y;
2417
2418 }
2419 }; // void MoveAwayFromMeleeCombat( Enemy ThisRobot , moderately_finepoint * set_move_tgt )
2420
2421 /**
2422 *
2423 */
ReachMeleeCombat(enemy * ThisRobot,gps * tpos,moderately_finepoint * new_move_target,pathfinder_context * pf_ctx)2424 static void ReachMeleeCombat(enemy *ThisRobot, gps *tpos, moderately_finepoint *new_move_target, pathfinder_context *pf_ctx)
2425 {
2426 // Target not reachable -> roughly reach the target.
2427 // The exact destination will be computed later.
2428 new_move_target->x = tpos->x;
2429 new_move_target->y = tpos->y;
2430
2431 // If ThisRobot is a friend, we want him to move as far as possible,
2432 // so we de-activate the bot-collision test during the pathfinder call.
2433 // ( see the function's comment about state_machine_attack() )
2434 //
2435 // Else, a classical pathfinder context is used. However, the final position
2436 // is the target position, and so we have to add the target into the bot-collision
2437 // exception's list.
2438
2439 if (is_friendly(ThisRobot->faction, FACTION_SELF))
2440 pf_ctx->frw_ctx = NULL;
2441 else
2442 pf_ctx->frw_ctx->except_bots[1] = ThisRobot->bot_target_addr;
2443 }
2444
2445 /**
2446 * At some points it may be necessary, that an enemy turns around to
2447 * face the Tux. This function does that, but only so much as can be
2448 * done in the current frame, i.e. NON-BLOCKING operation.
2449 *
2450 * The return value indicates, if the turning has already reached it's
2451 * target by now or not.
2452 */
TurnABitTowardsPosition(enemy * ThisRobot,float x,float y,float TurnSpeed)2453 static int TurnABitTowardsPosition(enemy *ThisRobot, float x, float y, float TurnSpeed)
2454 {
2455 float RightAngle;
2456 float AngleInBetween;
2457 float TurningDirection;
2458
2459 if (ThisRobot->pos.x == x && ThisRobot->pos.y == y)
2460 return TRUE;
2461
2462 // Now we find out what the final target direction of facing should
2463 // be.
2464 //
2465 // For this we use the atan2, which gives angles from -pi to +pi.
2466 //
2467 // Attention must be paid, since 'y' in our coordinates ascends when
2468 // moving down and descends when moving 'up' on the screen. So that
2469 // one sign must be corrected, so that everything is right again.
2470 //
2471 RightAngle = (atan2(-(y - ThisRobot->pos.y), +(x - ThisRobot->pos.x)) * 180.0 / M_PI);
2472 //
2473 // Another thing there is, that must also be corrected: '0' begins
2474 // with facing 'down' in the current rotation models. Therefore angle
2475 // 0 corresponds to that. We need to shift again...
2476 //
2477 RightAngle += 90;
2478
2479 // Now it's time do determine which direction to move, i.e. if to
2480 // turn to the left or to turn to the right... For this purpose
2481 // we convert the current angle, which is between 270 and -90 degrees
2482 // to one between -180 and +180 degrees...
2483 //
2484 if (RightAngle > 180.0)
2485 RightAngle -= 360.0;
2486
2487 // Having done these preparations, it's now easy to determine the right
2488 // direction of rotation...
2489 //
2490 AngleInBetween = RightAngle - ThisRobot->current_angle;
2491 if (AngleInBetween > 180)
2492 AngleInBetween -= 360;
2493 if (AngleInBetween <= -180)
2494 AngleInBetween += 360;
2495
2496 if (AngleInBetween > 0)
2497 TurningDirection = +1;
2498 else
2499 TurningDirection = -1;
2500
2501 // Now we turn and show the image until both chat partners are
2502 // facing each other, mostly the chat partner is facing the Tux,
2503 // since the Tux may still turn around to somewhere else all the
2504 // while, if the chose so
2505 //
2506 ThisRobot->current_angle += TurningDirection * TurnSpeed * Frame_Time();
2507
2508 // In case of positive turning direction, we wait, till our angle is greater
2509 // than the right angle.
2510 // Otherwise we wait till our angle is lower than the right angle.
2511 //
2512 AngleInBetween = RightAngle - ThisRobot->current_angle;
2513 if (AngleInBetween > 180)
2514 AngleInBetween -= 360;
2515 if (AngleInBetween <= -180)
2516 AngleInBetween += 360;
2517
2518 if ((TurningDirection > 0) && (AngleInBetween < 0))
2519 return (TRUE);
2520 if ((TurningDirection < 0) && (AngleInBetween > 0))
2521 return (TRUE);
2522
2523 return (FALSE);
2524 }
2525
2526 /**
2527 * Enemies act as groups. If one is hit, all will attack and the like.
2528 * This transmission of information is handled here.
2529 */
SetRestOfGroupToState(Enemy ThisRobot,short NewState)2530 void SetRestOfGroupToState(Enemy ThisRobot, short NewState)
2531 {
2532 int MarkerCode;
2533
2534 MarkerCode = ThisRobot->marker;
2535
2536 if ((MarkerCode == 0) || (MarkerCode == 101))
2537 return;
2538
2539 enemy *erot;
2540 BROWSE_ALIVE_BOTS(erot) {
2541 if (erot->marker == MarkerCode)
2542 erot->combat_state = NewState;
2543 }
2544
2545 }; // void SetRestOfGroupToState ( Enemy ThisRobot , int NewState )
2546
2547 /**
2548 * This function checks for enemy collisions and returns TRUE if enemy
2549 * with number enemynum collided with another enemy from the list.
2550 */
CheckEnemyEnemyCollision(enemy * OurBot)2551 int CheckEnemyEnemyCollision(enemy * OurBot)
2552 {
2553 float check_x, check_y;
2554 int swap;
2555 float xdist, ydist;
2556 float dist2;
2557 check_x = OurBot->pos.x;
2558 check_y = OurBot->pos.y;
2559
2560 if (OurBot->pure_wait)
2561 return FALSE;
2562
2563 // Now we check through all the other enemys on this level if
2564 // there is perhaps a collision with them...
2565
2566 enemy *erot;
2567 BROWSE_LEVEL_BOTS(erot, OurBot->pos.z) {
2568 if (erot == OurBot)
2569 continue;
2570
2571 xdist = check_x - erot->pos.x;
2572 ydist = check_y - erot->pos.y;
2573
2574 dist2 = sqrt(xdist * xdist + ydist * ydist);
2575
2576 // Is there a Collision?
2577 if (dist2 <= 2 * DROIDRADIUSXY) {
2578 if (erot->pure_wait)
2579 continue;
2580
2581 erot->pure_wait = WAIT_COLLISION;
2582
2583 swap = OurBot->nextwaypoint;
2584 OurBot->nextwaypoint = OurBot->lastwaypoint;
2585 OurBot->lastwaypoint = swap;
2586
2587 return TRUE;
2588 } // if collision distance reached
2589
2590 } // for all the bots...
2591
2592 return FALSE;
2593 }; // int CheckEnemyEnemyCollision
2594
2595 /**
2596 * This function increases the phase counters for the animation of a bot.
2597 */
animate_enemy(enemy * our_enemy)2598 void animate_enemy(enemy *our_enemy)
2599 {
2600 switch (our_enemy->animation_type) {
2601
2602 case WALK_ANIMATION:
2603 our_enemy->animation_phase += Frame_Time() * droid_walk_animation_speed_factor[Droidmap[our_enemy->type].individual_shape_nr];
2604
2605 // While we're in the walk animation cycle, we have the walk animation
2606 // images cycle.
2607 //
2608 if (our_enemy->animation_phase >= last_walk_animation_image[Droidmap[our_enemy->type].individual_shape_nr]) {
2609 our_enemy->animation_phase = 0;
2610 our_enemy->animation_type = WALK_ANIMATION;
2611 }
2612 // But as soon as the walk stops and the 'bot' is standing still, we switch
2613 // to the standing cycle...
2614 //
2615 if ((fabs(our_enemy->speed.x) < 0.1) && (fabs(our_enemy->speed.y) < 0.1)) {
2616 our_enemy->animation_phase = first_stand_animation_image[Droidmap[our_enemy->type].individual_shape_nr] - 1;
2617 our_enemy->animation_type = STAND_ANIMATION;
2618 }
2619 break;
2620 case ATTACK_ANIMATION:
2621 our_enemy->animation_phase += Frame_Time() * droid_attack_animation_speed_factor[Droidmap[our_enemy->type].individual_shape_nr];
2622
2623 if (our_enemy->animation_phase >= last_attack_animation_image[Droidmap[our_enemy->type].individual_shape_nr]) {
2624 our_enemy->animation_phase = 0;
2625 our_enemy->animation_type = WALK_ANIMATION;
2626 }
2627 break;
2628 case GETHIT_ANIMATION:
2629 our_enemy->animation_phase += Frame_Time() * droid_gethit_animation_speed_factor[Droidmap[our_enemy->type].individual_shape_nr];
2630
2631 if (our_enemy->animation_phase >= last_gethit_animation_image[Droidmap[our_enemy->type].individual_shape_nr]) {
2632 our_enemy->animation_phase = 0;
2633 our_enemy->animation_type = WALK_ANIMATION;
2634 }
2635 break;
2636 case DEATH_ANIMATION:
2637 our_enemy->animation_phase += Frame_Time() * droid_death_animation_speed_factor[Droidmap[our_enemy->type].individual_shape_nr];
2638
2639 if (our_enemy->animation_phase >= last_death_animation_image[Droidmap[our_enemy->type].individual_shape_nr] - 1) {
2640 our_enemy->animation_phase = last_death_animation_image[Droidmap[our_enemy->type].individual_shape_nr] - 1;
2641 our_enemy->animation_type = DEAD_ANIMATION;
2642 }
2643 break;
2644 case DEAD_ANIMATION:
2645 our_enemy->animation_phase = last_death_animation_image[Droidmap[our_enemy->type].individual_shape_nr] - 1;
2646 break;
2647 case STAND_ANIMATION:
2648 our_enemy->animation_phase += Frame_Time() * droid_stand_animation_speed_factor[Droidmap[our_enemy->type].individual_shape_nr];
2649
2650 if (our_enemy->animation_phase >= last_stand_animation_image[Droidmap[our_enemy->type].individual_shape_nr] - 1) {
2651 our_enemy->animation_phase = first_stand_animation_image[Droidmap[our_enemy->type].individual_shape_nr] - 1;
2652 our_enemy->animation_type = STAND_ANIMATION;
2653 }
2654 break;
2655 default:
2656 fprintf(stderr, "\nThe animation type found is: %d.", our_enemy->animation_type);
2657 error_message(__FUNCTION__, "\
2658 There was an animation type encountered that isn't defined in FreedroidRPG.\n\
2659 That means: Something is going *terribly* wrong!", PLEASE_INFORM | IS_FATAL);
2660 break;
2661 }
2662 }
2663
2664 /******************************************************
2665 * Resolve the address of an enemy, given its number (primary key) and
2666 * the cache value of its address.
2667 * 1- number == -1 means no enemy targeted, means we return NULL
2668 * 2- if the cache value is not NULL, we return it
2669 * 3- if the cache value is NULL, resolve the address by browsing the list and set the cache value
2670 *********************************************************/
enemy_resolve_address(short int enemy_number,enemy ** enemy_addr)2671 enemy *enemy_resolve_address(short int enemy_number, enemy ** enemy_addr)
2672 {
2673 if (enemy_number == -1) {
2674 *enemy_addr = NULL;
2675 return NULL;
2676 }
2677
2678 if (!(*enemy_addr)) {
2679 int i;
2680 for (i = 0; i < 2; i++) {
2681 enemy *erot;
2682 list_for_each_entry(erot, i ? &dead_bots_head : &alive_bots_head, global_list)
2683 if (enemy_number == erot->id) {
2684 *enemy_addr = erot;
2685 return *enemy_addr;
2686 }
2687 }
2688 return NULL;
2689 }
2690 return *enemy_addr;
2691 }
2692
2693 /********************************************
2694 * Sets a reference to an enemy to the given address
2695 * (we could use the number too)
2696 * This sets both the cache value and the number.
2697 ********************************/
enemy_set_reference(short int * enemy_number,enemy ** enemy_addr,enemy * addr)2698 void enemy_set_reference(short int *enemy_number, enemy ** enemy_addr, enemy * addr)
2699 {
2700 if (addr == NULL) {
2701 *enemy_addr = NULL;
2702 *enemy_number = -1;
2703 } else {
2704 *enemy_addr = addr;
2705 *enemy_number = addr->id;
2706 }
2707 }
2708
2709 /**
2710 * This function converts a sensor string human-readable to a sensor ID, computer-readable
2711 */
get_sensor_id_by_name(const char * sensor_name)2712 int get_sensor_id_by_name(const char *sensor_name)
2713 {
2714 int i = 0;
2715 for (i = 0; i < sizeof(enemy_sensors)/sizeof(enemy_sensors[0]); i++) {
2716 if (!strcmp(sensor_name, enemy_sensors[i].name))
2717 return enemy_sensors[i].flag_set;
2718 }
2719
2720 error_message(__FUNCTION__,
2721 "FreedroidRPG was requested to process sensor \"%s\".\n"
2722 "But there is no such sensor! We are now setting the sensor to \"spectral\" (without any feature).",
2723 PLEASE_INFORM, sensor_name);
2724 return SENSOR_FEATURELESS;
2725 }
2726
2727 /**
2728 * This function is the get_sensor_id_by_name but in reverse
2729 */
get_sensor_name_by_id(int sensor_flags)2730 const char *get_sensor_name_by_id(int sensor_flags)
2731 {
2732 int i = 0;
2733 for (i = 0; i < sizeof(enemy_sensors)/sizeof(enemy_sensors[0]); i++) {
2734 if (sensor_flags == enemy_sensors[i].flag_set)
2735 return enemy_sensors[i].name;
2736 }
2737
2738 error_message(__FUNCTION__,
2739 "FreedroidRPG was requested to process sensor with ID \"%d\".\n"
2740 "But there is no name for that sensor! We are now setting the sensor to \"spectral\" (without any feature).",
2741 PLEASE_INFORM, sensor_flags);
2742 return "spectral";
2743 }
2744
2745 /* This helper function checks if a robot can see tux even when invisible. */
can_see_tux(enemy * ThisRobot)2746 int can_see_tux(enemy *ThisRobot) {
2747
2748 return ((Me.invisible_duration <= 0) || (ThisRobot->sensor_id & SENSOR_DETECT_INVISIBLE));
2749
2750 }
2751
2752
2753 /**
2754 * This function checks if the enemy at 'target_pos' is a potential target for
2755 * 'this_robot'.
2756 * To be a potential target, the enemy has :
2757 * 1) to be closer to this_robot than the current best target (defined by its
2758 * distance 'squared_best_dist')
2759 * 2) to be visible
2760 * 3) to be reachable (definition depends on the robot's weapon)
2761 *
2762 * If the enemy is a potential target, 'squared_best_dist' is changed, and
2763 * the function returns TRUE.
2764 *
2765 * Note: All operations are executed in this_robot's level
2766 *
2767 */
is_potential_target(enemy * this_robot,gps * target_pos,float * squared_best_dist)2768 static int is_potential_target(enemy * this_robot, gps * target_pos, float *squared_best_dist)
2769 {
2770 // Get target's virtual position in term of this_robot's level
2771 gps target_vpos;
2772 update_virtual_position(&target_vpos, target_pos, this_robot->pos.z);
2773
2774 // Potentially closer than current best dist ?
2775 // We use the direct line distance as the distance to the target.
2776 // The reason is : if two potential targets are seen through a window,
2777 // we will attack the one that is visually the closest one (the bots are
2778 // not omniscient and do not know the real length of the path to their targets)
2779 float xdist = target_vpos.x - this_robot->pos.x;
2780 float ydist = target_vpos.y - this_robot->pos.y;
2781 float squared_target_dist = (xdist * xdist + ydist * ydist);
2782
2783 if (squared_target_dist > *squared_best_dist) {
2784 return FALSE;
2785 }
2786 // If the target is not visible, then it cannot be attacked...
2787 // Unless if the droid's sensor can see through walls!
2788 if (!(this_robot->sensor_id & SENSOR_THROUGH_WALLS)) {
2789 // Only apply this rule if the bot don't have a compatible sensor.
2790 if (!DirectLineColldet(this_robot->pos.x, this_robot->pos.y, target_vpos.x, target_vpos.y, this_robot->pos.z, &VisiblePassFilter)) {
2791 return FALSE;
2792 }
2793 }
2794 // For a range weapon, check if the target can be directly shot
2795 int melee_weapon = ItemMap[Droidmap[this_robot->type].weapon_item.type].weapon_is_melee;
2796
2797 if (!melee_weapon) {
2798 if (DirectLineColldet(this_robot->pos.x, this_robot->pos.y,
2799 target_vpos.x, target_vpos.y, this_robot->pos.z, &FlyablePassFilter)) {
2800 *squared_best_dist = squared_target_dist;
2801 return TRUE;
2802 }
2803 }
2804 // Else (if melee_weapon or not shootable for a range_weapon), checks if a path exists to reach the target
2805 moderately_finepoint mid_pos[40];
2806 moderately_finepoint to_pos = { target_vpos.x, target_vpos.y };
2807 pathfinder_context pf_ctx = { &WalkableWithMarginPassFilter, NULL };
2808 int path_found = set_up_intermediate_course_between_positions(&(this_robot->pos), &to_pos, mid_pos, 40, &pf_ctx)
2809 && (mid_pos[5].x == -1);
2810 if (!path_found)
2811 return FALSE;
2812
2813 *squared_best_dist = squared_target_dist;
2814 return TRUE;
2815
2816 } // is_potential_target( enemy* this_robot, gps* target_pos, float* squared_best_dist )
2817
2818 /**
2819 * Return the numerical droid type corresponding to a given type name.
2820 */
get_droid_type(const char * type_name)2821 int get_droid_type(const char *type_name)
2822 {
2823 int i;
2824
2825 for (i = 0; i < Number_Of_Droid_Types; i++) {
2826 if (!strcmp(Droidmap[i].droidname, type_name))
2827 return i;
2828 }
2829
2830 error_message(__FUNCTION__, "Droid type \"%s\" does not exist.", PLEASE_INFORM, type_name);
2831
2832 return 0;
2833 }
2834
get_enemy_with_dialog(const char * dialog)2835 enemy *get_enemy_with_dialog(const char *dialog)
2836 {
2837 enemy *en;
2838
2839 BROWSE_ALIVE_BOTS(en) {
2840 if (!strcmp(en->dialog_section_name, dialog)) {
2841 return en;
2842 }
2843 }
2844
2845 BROWSE_DEAD_BOTS(en) {
2846 if (!strcmp(en->dialog_section_name, dialog)) {
2847 return en;
2848 }
2849 }
2850
2851 return NULL;
2852 }
2853 #undef _enemy_c
2854