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