1 /*
2  *
3  *   Copyright (c) 1994, 2002, 2003 Johannes Prix
4  *   Copyright (c) 1994, 2002 Reinhard Prix
5  *   Copyright (c) 2004-2010 Arthur Huillet
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 most of the activable's object/user interaction framework.
28  */
29 
30 #define _action_c
31 
32 #include "system.h"
33 
34 #include "defs.h"
35 #include "struct.h"
36 #include "global.h"
37 #include "proto.h"
38 
39 /**
40  * Find a reachable position to drop an item near a chest.
41  *
42  * This function looks for a position near a given chest that
43  * satisfies : DLC(chest, item) && DLW(Tux, item)
44  *
45  * The use of the DLC() call has 2 purposes:
46  * - ensure that the item will be dropped on a position that is in
47  *   line of sight with the chest
48  * - ensure that there are no other items already dropped there
49  *
50  * If no position is found, Tux current position is used as a fallback
51  * (we know that Tux is near the chest, because he is opening it)
52  *
53  * Note: a real random position is not used, in order to minimize the CPU cost
54  */
find_dropable_position_near_chest(float * item_x,float * item_y,int obst_index,level * obst_level)55 static void find_dropable_position_near_chest(float *item_x, float *item_y, int obst_index, level *obst_level)
56 {
57 	float obst_x = obst_level->obstacle_list[obst_index].pos.x;
58 	float obst_y = obst_level->obstacle_list[obst_index].pos.y;
59 	moderately_finepoint offset_vector;
60 	int tries;
61 
62 	// Initialize the item position with the fallback position
63 	*item_x = Me.pos.x;
64 	*item_y = Me.pos.y;
65 
66 	// Step 1: randomly choose one of 16 main 22.5° directions around the chest
67 	float obs_diag = get_obstacle_spec(obst_level->obstacle_list[obst_index].type)->diaglength;
68 	offset_vector.x = obs_diag + 0.5;
69 	offset_vector.y = 0.0;
70 	RotateVectorByAngle(&offset_vector, (float)MyRandom(16) * 22.5);
71 
72 	// Step 2: rotate offset_vector by 45° until an available start position is found
73 
74 	// Filter by walkability, excluding chest
75 	colldet_filter item_filter = WalkableWithMarginPassFilter;
76 	item_filter.extra_margin = COLLDET_DROP_ITEM_MARGIN;
77 
78 	colldet_filter chest_filter = ObstacleByIdPassFilter;
79 	chest_filter.data = &obst_index;
80 	chest_filter.next = &item_filter;
81 
82 	// Filter by walkability
83 	colldet_filter tux_filter = WalkableWithMarginPassFilter;
84 
85 	tries = 0;
86 	while ((!DirectLineColldet(obst_x, obst_y, obst_x + offset_vector.x, obst_y + offset_vector.y, Me.pos.z, &chest_filter) ||
87 			!DirectLineColldet(Me.pos.x, Me.pos.y, obst_x + offset_vector.x, obst_y + offset_vector.y, Me.pos.z, &tux_filter))
88 	       && (tries < 8))
89 	{
90 		RotateVectorByAngle(&offset_vector, 45.0);
91 		++tries;
92 	}
93 	if (tries == 8)
94 		return;		// No start position available : fallback to Tux's feet
95 
96 	// New fallback position will be that start position
97 	*item_x = obst_x + offset_vector.x;
98 	*item_y = obst_y + offset_vector.y;
99 
100 	// Step 3 : randomly choose an available position around that start position
101 	// Note: If we were only using the position computed on step 2, then we would
102 	// limit the potential dropable positions.
103 	tries = 0;
104 	float trimmer_x, trimmer_y;
105 	do {
106 		trimmer_x = (float)MyRandom(10) / 20.0 - 0.25;
107 		trimmer_y = (float)MyRandom(10) / 20.0 - 0.25;
108 		++tries;
109 	}
110 	while ((!DirectLineColldet(obst_x, obst_y, *item_x + trimmer_x, *item_y + trimmer_y, Me.pos.z, &chest_filter) ||
111 		!DirectLineColldet(Me.pos.x, Me.pos.y, *item_x + trimmer_x, *item_y + trimmer_y, Me.pos.z, &tux_filter))
112 	       && (tries < 100));
113 	if (tries == 100)
114 		return;		// No final position available : fallback to start position
115 
116 	// Step 4 : position found
117 	*item_x += trimmer_x;
118 	*item_y += trimmer_y;
119 }
120 
121 /**
122  * This function will drop the content of a chest on the floor, around
123  * the chest.
124  * If the chest does not contain specific items, then eventually drop a
125  * random item.
126  */
throw_out_all_chest_content(int obst_index)127 static void throw_out_all_chest_content(int obst_index)
128 {
129 	int i = 0;
130 	float item_x, item_y;
131 	int drop_count = 0;
132 	level *lvl = CURLEVEL();
133 
134 	struct dynarray *item_list = get_obstacle_extension(CURLEVEL(), &(lvl->obstacle_list[obst_index]), OBSTACLE_EXTENSION_CHEST_ITEMS);
135 
136 	play_open_chest_sound();
137 
138 	if (item_list) {
139 		drop_count = item_list->size;
140 		for (i = 0; i < drop_count; i++) {
141 
142 			item *it = &((item *)item_list->arr)[i];
143 
144 			if (it->type == -1)
145 				continue;
146 
147 			find_dropable_position_near_chest(&item_x, &item_y, obst_index, lvl);
148 			drop_item(it, item_x, item_y, lvl->levelnum);
149 		}
150 
151 		// Empty the item list
152 		dynarray_free(item_list);
153 
154 		// Remove the chest items obstacle extension
155 		del_obstacle_extension(lvl, &(lvl->obstacle_list[obst_index]), OBSTACLE_EXTENSION_CHEST_ITEMS);
156 	}
157 
158 	// If the chest was empty, maybe generate a random item to be dropped
159 	if (!drop_count) {
160 		find_dropable_position_near_chest(&item_x, &item_y, obst_index, lvl);
161 		DropRandomItem(Me.pos.z, item_x, item_y, lvl->drop_class, FALSE);
162 	}
163 }
164 
165 /**
166  * The function detects, if the current mouse cursor is over a given obstacle.
167  * The iso image of the obstacle will be used to check hovering.
168  */
mouse_cursor_is_on_that_obstacle(level * lvl,int obst_index)169 static int mouse_cursor_is_on_that_obstacle(level *lvl, int obst_index)
170 {
171 	// mouse_cursor_is_on_that_iso_image() needs a position defined relatively to
172 	// current level
173 	gps obs_pos = { lvl->obstacle_list[obst_index].pos.x,
174 		lvl->obstacle_list[obst_index].pos.y,
175 		lvl->levelnum
176 	};
177 	gps obs_vpos;
178 	update_virtual_position(&obs_vpos, &obs_pos, Me.pos.z);
179 	if (obs_vpos.z == -1)
180 		return FALSE;
181 
182 	struct image *img = get_obstacle_image(lvl->obstacle_list[obst_index].type, lvl->obstacle_list[obst_index].frame_index);
183 	if (mouse_cursor_is_on_that_image(obs_vpos.x, obs_vpos.y, img))
184 		return TRUE;
185 
186 	return FALSE;
187 }
188 
189 /**
190  * This function checks if there is an obstacle beneath the current
191  * mouse cursor.
192  * It takes into account the actual size of the
193  * graphics and not only the geographic position of the mouse cursor on
194  * the floor.
195  *
196  * If there is an obstacle below the mouse cursor, the function will
197  * return its obstacle index.  Else -1 will be returned.
198  */
clickable_obstacle_below_mouse_cursor(level ** obst_lvl,int clickable_only)199 int clickable_obstacle_below_mouse_cursor(level **obst_lvl, int clickable_only)
200 {
201 #define HOVER_CHECK_DIST 2
202 #define SIDE_LENGTH (HOVER_CHECK_DIST * 2 + 1)
203 	finepoint MapPositionOfMouse;
204 	int i, j, x, y;
205 	int return_index = -1, obst_index = 0;
206 	float obst_normal = 0, max_normal = 0;
207 
208 	if(obst_lvl)
209 		*obst_lvl = NULL;
210 
211 	// If the cursor is not inside the user rectangle,
212 	// there is no obstacle below it.
213 	if (!MouseCursorIsInUserRect(GetMousePos_x(), GetMousePos_y()))
214 		return -1;
215 
216 	// We find the position of the mouse cursor on the floor.
217 	MapPositionOfMouse.x = translate_pixel_to_map_location((float)input_axis.x, (float)input_axis.y, TRUE);
218 	MapPositionOfMouse.y = translate_pixel_to_map_location((float)input_axis.x, (float)input_axis.y, FALSE);
219 
220 	gps mouse_target_vpos = { MapPositionOfMouse.x, MapPositionOfMouse.y, Me.pos.z };
221 	gps mouse_target_pos;
222 	if (!resolve_virtual_position(&mouse_target_pos, &mouse_target_vpos))
223 		return -1;
224 	if (!level_is_visible(mouse_target_pos.z))
225 		return -1;
226 
227 	level *lvl = curShip.AllLevels[mouse_target_pos.z];
228 
229 	// Iterate through a square area of tiles with sides HOVER_CHECK_DIST * 2 + 1
230 	// centered on the tile under the mouse
231 	for (i = 0; i < pow(SIDE_LENGTH, 2); i++) {
232 		y = i / SIDE_LENGTH - HOVER_CHECK_DIST - 1  + mouse_target_pos.y;
233 		x = i % SIDE_LENGTH - HOVER_CHECK_DIST - 1  + mouse_target_pos.x;
234 
235 		if (!pos_inside_level(x, y, lvl))
236 			continue;
237 
238 		// Iterate through all candidate obstacles on this tile
239 		for (j = 0; j < lvl->map[y][x].glued_obstacles.size; j++) {
240 			obst_index = ((int *)(lvl->map[y][x].glued_obstacles.arr))[j];
241 			obst_normal = lvl->obstacle_list[obst_index].pos.x + lvl->obstacle_list[obst_index].pos.y;
242 
243 			if (obst_normal > max_normal &&
244 				(!clickable_only || (get_obstacle_spec(lvl->obstacle_list[obst_index].type)->flags & IS_CLICKABLE)) &&
245 				mouse_cursor_is_on_that_obstacle(lvl, obst_index)) {
246 
247 				max_normal = obst_normal;
248 				return_index = obst_index;
249 				if (obst_lvl)
250 					*obst_lvl = lvl;
251 			}
252 		}
253 	}
254 	return return_index;
255 }
256 
257 /**
258  * In order to perform a click-action, tux must first move to the obstacle.
259  * He can either approach it from a specific direction or any direction. This
260  * function handles the case where direction doesn't matter, like for barrels
261  * and crates. Returns 1 if tux is close enough to the obstacle.
262  */
reach_obstacle_from_any_direction(level * obst_lvl,int obst_index)263 static int reach_obstacle_from_any_direction(level *obst_lvl, int obst_index) {
264 	gps obst_vpos;
265 	obstacle_spec *obstacle_spec = get_obstacle_spec(obst_lvl->obstacle_list[obst_index].type);
266 
267 	update_virtual_position(&obst_vpos, &(obst_lvl->obstacle_list[obst_index].pos), Me.pos.z);
268 	if (calc_distance(Me.pos.x, Me.pos.y, obst_vpos.x, obst_vpos.y)
269 		<= (obstacle_spec->block_area_parm_1 * sqrt(2)) / 2.0 + 0.5) {
270 		// Maybe a combo_action has made us come here and open the chest.  Then of
271 		// course we can remove the combo action setting now...
272 		//
273 		Me.mouse_move_target_combo_action_type = NO_COMBO_ACTION_SET;
274 		Me.mouse_move_target_combo_action_parameter = -1;
275 		return 1;
276 	}
277 
278 	// We set up a course, that will lead us directly to the barrel, that we are
279 	// supposed to smash (upon arrival, later).
280 	//
281 	// For this purpose, we take a vector and rotate it around the barrel center to
282 	// find the 'best' location to go to for the smashing motion...
283 
284 	// First, if the barrel is in direct line with Tux, we search a position that the
285 	// Tux can reach.
286 	// We normalize the distance of the final walk-point to the barrel center just
287 	// so, that it will be within the 'strike_distance' we have used just above in
288 	// the 'distance-met' query.
289 	//
290 
291 	moderately_finepoint step_vector;
292 	float vec_len;
293 	int i;
294 	step_vector.x = Me.pos.x - obst_vpos.x;
295 	step_vector.y = Me.pos.y - obst_vpos.y;
296 	vec_len = vect_len(step_vector);
297 
298 	step_vector.x *= ((obstacle_spec->block_area_parm_1 * sqrt(2)) / 2.0 + 0.05) / vec_len;
299 	step_vector.y *= ((obstacle_spec->block_area_parm_1 * sqrt(2)) / 2.0 + 0.05) / vec_len;
300 
301 	for (i = 0; i < 8; i++) {
302 		if (DirectLineColldet(Me.pos.x, Me.pos.y, obst_vpos.x, obst_vpos.y, Me.pos.z, &WalkablePassFilter)) {
303 			// The obstacle plus the step vector give us the position to move the
304 			// Tux to for the optimal strike...
305 			// This position has to be defined relatively to the barrel's level, so that we
306 			// can retrieve the barrel later (at the end the combo action)
307 			//
308 			Me.mouse_move_target.x = step_vector.x + obst_lvl->obstacle_list[obst_index].pos.x;
309 			Me.mouse_move_target.y = step_vector.y + obst_lvl->obstacle_list[obst_index].pos.y;
310 			Me.mouse_move_target.z = obst_lvl->obstacle_list[obst_index].pos.z;
311 
312 			// We set up the combo_action, so that the barrel can be smashed later...
313 			//
314 			enemy_set_reference(&Me.current_enemy_target_n, &Me.current_enemy_target_addr, NULL);
315 			Me.mouse_move_target_combo_action_type = COMBO_ACTION_OBSTACLE;
316 			Me.mouse_move_target_combo_action_parameter = obst_index;
317 			return 0;
318 		}
319 		// If this vector didn't bring us any luck, we rotate by 45 degrees and try anew...
320 		//
321 		RotateVectorByAngle(&step_vector, 45.0);
322 	}
323 
324 	// Second : if the barrel is not in direct line with Tux, we search a position
325 	// near the barrel that is free of obstacle. However, it could happen that the
326 	// pathfinder will not be able to find a way to go there, but it would be too
327 	// costly to launch the pathfinder here. Anyway, if no path exists, Tux will
328 	// not start to move, so the player will certainly try to get closer to the
329 	// barrel.
330 	// We will rotate step_vector around the center of the barrel
331 	//
332 	step_vector.x = Me.pos.x - obst_vpos.x;
333 	step_vector.y = Me.pos.y - obst_vpos.y;
334 	vec_len = vect_len(step_vector);
335 	step_vector.x /= vec_len;
336 	step_vector.y /= vec_len;
337 
338 	// point_near_barrel is a point very near the barrel in the step_vector's direction,
339 	// point_away_from_barrel is a point in the same direction but a bit farther
340 	moderately_finepoint point_near_obst, point_away_from_obst;
341 
342 	// half-size of the barrel
343 	moderately_finepoint half_size = { (obstacle_spec->block_area_parm_1 * sqrt(2)) / 2.0,
344 		(obstacle_spec->block_area_parm_2 * sqrt(2)) / 2.0
345 	};
346 
347 	for (i = 0; i < 8; i++) {
348 		// (no need to optimize this, the compiler will do its job...)
349 		point_near_obst.x = obst_vpos.x + step_vector.x * (half_size.x + 0.05);
350 		point_near_obst.y = obst_vpos.y + step_vector.y * (half_size.y + 0.05);
351 		point_away_from_obst.x = obst_vpos.x + step_vector.x * (half_size.x + 0.2);
352 		point_away_from_obst.y = obst_vpos.y + step_vector.y * (half_size.y + 0.2);
353 
354 		// check if the line from point_near_barrel to point_away_from_barrel is walkable
355 		if (DirectLineColldet(point_near_obst.x, point_near_obst.y,
356 					  point_away_from_obst.x, point_away_from_obst.y, Me.pos.z, &WalkablePassFilter)) {
357 			// point_to_barrel seems good, move Tux there
358 			// This position has to be defined relatively to the barrel's level, so that we
359 			// can retrieve the barrel later (at the end the combo action)
360 			//
361 			Me.mouse_move_target.x =
362 				obst_lvl->obstacle_list[obst_index].pos.x + step_vector.x * (half_size.x + 0.05);
363 			Me.mouse_move_target.y =
364 				obst_lvl->obstacle_list[obst_index].pos.y + step_vector.y * (half_size.y + 0.05);
365 			Me.mouse_move_target.z = obst_lvl->obstacle_list[obst_index].pos.z;
366 
367 			// We set up the combo_action, so that the barrel can be smashed later, on the
368 			// second call (made by move_tux_towards_intermediate_point)...
369 			//
370 			enemy_set_reference(&Me.current_enemy_target_n, &Me.current_enemy_target_addr, NULL);
371 			Me.mouse_move_target_combo_action_type = COMBO_ACTION_OBSTACLE;
372 			Me.mouse_move_target_combo_action_parameter = obst_index;
373 
374 			return 0;
375 		}
376 		// rotate the step_vector to check an other point
377 		RotateVectorByAngle(&step_vector, 45.0);
378 	}
379 
380 	return 0;
381 };			  // int reach_obstacle_from_any_direction(level *obst_lvl, int obst_index)
382 
383 /**
384  * In order to perform a click-action, tux must first move to the obstacle.
385  * He can either approach it from a specific direction or any direction. This
386  * function handles the case where direction does matter, like for chests,
387  * terminals, and signs. Returns 1 if tux is close enough to the obstacle.
388  */
reach_obstacle_from_specific_direction(level * obst_lvl,int obst_index,int direction)389 static int reach_obstacle_from_specific_direction(level *obst_lvl, int obst_index, int direction) {
390 	gps obst_vpos;
391 	update_virtual_position(&obst_vpos, &(obst_lvl->obstacle_list[obst_index].pos), Me.pos.z);
392 	if (fabsf(Me.pos.x - obst_vpos.x) + fabsf(Me.pos.y - obst_vpos.y) < 1.1) {
393     	// Maybe a combo_action has made us come here and open the chest.  Then of
394     	// course we can remove the combo action setting now...
395     	//
396     	Me.mouse_move_target_combo_action_type = NO_COMBO_ACTION_SET;
397     	Me.mouse_move_target_combo_action_parameter = -1;
398         return 1;
399     }
400 
401 	// So here we know, that we must set the course towards the chest.  We
402 	// do so first.
403 	// The target's position has to be defined relatively to the chest's level, so that we
404 	// can retrieve the chest later (at the end the combo action)
405 	//
406 	DebugPrintf(2, "\nreach_obstacle_from_specific_direction:  setting up combined mouse move target!");
407 
408 	Me.mouse_move_target.x = obst_lvl->obstacle_list[obst_index].pos.x;
409 	Me.mouse_move_target.y = obst_lvl->obstacle_list[obst_index].pos.y;
410 	Me.mouse_move_target.z = obst_lvl->levelnum;
411 	enemy_set_reference(&Me.current_enemy_target_n, &Me.current_enemy_target_addr, NULL);
412 	Me.mouse_move_target_combo_action_type = COMBO_ACTION_OBSTACLE;
413 	Me.mouse_move_target_combo_action_parameter = obst_index;
414 	int obst_type = obst_lvl->obstacle_list[obst_index].type;
415 
416 	obstacle_spec *spec = get_obstacle_spec(obst_type);
417 	switch (direction) {
418 	case EAST:
419 		Me.mouse_move_target.x += spec->block_area_parm_1;
420 		break;
421 	case SOUTH:
422 		Me.mouse_move_target.y += spec->block_area_parm_2;
423 		break;
424 	case WEST:
425 		Me.mouse_move_target.x -= spec->block_area_parm_1;
426 		break;
427 	case NORTH:
428 		Me.mouse_move_target.y -= spec->block_area_parm_2;
429 		break;
430 	default:
431 		error_message(__FUNCTION__, "Invalid direction!!", PLEASE_INFORM | IS_FATAL);
432 		break;
433 	}
434     return 0;
435 };			  // int reach_obstacle_from_specific_direction(level **obst_lvl, int obst_index, int direction)
436 
437 enum interactive_obstacle_type {
438 	ACT_CHEST,
439 	ACT_TERMINAL,
440 	ACT_SIGN,
441 	ACT_BARREL,
442 };
443 
approach(int type)444 static int approach(int type)
445 {
446 	int i;
447 
448 	const struct {
449 		int type;
450 		int direction;
451 	} lookup[] = {
452 			{ ISO_V_CHEST_CLOSED, EAST },
453 			{ ISO_E_CHEST2_CLOSED, EAST },
454 			{ ISO_H_CHEST_CLOSED, SOUTH },
455 			{ ISO_S_CHEST2_CLOSED, SOUTH },
456 			{ ISO_W_CHEST2_CLOSED, WEST },
457 			{ ISO_N_CHEST2_CLOSED, NORTH },
458 			{ ISO_CONSOLE_N, SOUTH },
459 			{ ISO_CONSOLE_S, NORTH },
460 			{ ISO_CONSOLE_E, WEST },
461 			{ ISO_CONSOLE_W, EAST },
462 		        { ISO_CONSOLE_SECURE_E, EAST },
463 		        { ISO_CONSOLE_SECURE_S, SOUTH },
464 		        { ISO_CONSOLE_SECURE_W, WEST },
465         		{ ISO_CONSOLE_SECURE_N, NORTH },
466 			{ ISO_WALL_TERMINAL_S, SOUTH},
467 			{ ISO_WALL_TERMINAL_E, EAST},
468 			{ ISO_WALL_TERMINAL_N, NORTH},
469 			{ ISO_WALL_TERMINAL_W, WEST},
470 			{ ISO_SIGN_1, EAST },
471 			{ ISO_SIGN_1_FLASH, EAST },
472 			{ ISO_SIGN_2, SOUTH },
473 			{ ISO_SIGN_2_FLASH, SOUTH },
474 			{ ISO_SIGN_3, EAST },
475 			{ ISO_SIGN_3_FLASH, EAST },
476 			{ ISO_VENDING_MACHINE_1_E, EAST },
477 			{ ISO_VENDING_MACHINE_1_N, NORTH },
478 			{ ISO_VENDING_MACHINE_1_W, WEST },
479 			{ ISO_VENDING_MACHINE_1_S, SOUTH },
480 			{ ISO_VENDING_MACHINE_2_E, EAST },
481 			{ ISO_VENDING_MACHINE_2_N, NORTH },
482 			{ ISO_VENDING_MACHINE_2_W, WEST },
483 			{ ISO_VENDING_MACHINE_2_S, SOUTH },
484 			{ ISO_VENDING_MACHINE_3_E, EAST },
485 			{ ISO_VENDING_MACHINE_3_N, NORTH },
486 			{ ISO_VENDING_MACHINE_3_W, WEST },
487 			{ ISO_VENDING_MACHINE_3_S, SOUTH },
488 			{ ISO_BOOKSHELF_LOOTABLE_E, EAST },
489 			{ ISO_BOOKSHELF_LOOTABLE_S, SOUTH },
490 			{ ISO_BOOKSHELF_LOOTABLE_W, WEST },
491 			{ ISO_BOOKSHELF_LOOTABLE_N, NORTH },
492 			{ ISO_BOOKSHELF_LONG_LOOTABLE_E, EAST },
493 			{ ISO_BOOKSHELF_LONG_LOOTABLE_S, SOUTH },
494 			{ ISO_BOOKSHELF_LONG_LOOTABLE_W, WEST },
495 			{ ISO_BOOKSHELF_LONG_LOOTABLE_N, NORTH },
496 	};
497 
498 	for (i = 0; i < sizeof(lookup)/sizeof(lookup[0]); i++) {
499 		if (lookup[i].type == type)
500 			return lookup[i].direction;
501 	}
502 
503 
504 	error_message(__FUNCTION__, "Tried to approach obstacle type %d, but this is not a known type.", PLEASE_INFORM, type);
505 
506 	return UNDEFINED;
507 }
508 
act_chest(level * l,obstacle * o)509 static void act_chest(level *l, obstacle *o)
510 {
511 	throw_out_all_chest_content(get_obstacle_index(l, o));
512 
513 	// After emptying the chest we change it to its corresponding "open" type.
514 	// If such a type was not defined, the chest will stay "closed" and can be
515 	// looted indefinitely.
516 	int new_type = get_obstacle_spec(o->type)->result_type_after_looting;
517 	if (new_type != -1)
518 		o->type = new_type;
519 }
520 
act_barrel(level * l,obstacle * o)521 static void act_barrel(level *l, obstacle *o)
522 {
523 	smash_obstacle(o->pos.x, o->pos.y, o->pos.z);
524 
525 	// Do an attack motion with bare hands
526 	int store_weapon_type = Me.weapon_item.type;
527 	Me.weapon_item.type = -1;
528 	tux_wants_to_attack_now(FALSE);
529 	Me.weapon_item.type = store_weapon_type;
530 }
531 
act_terminal(level * l,obstacle * o)532 static void act_terminal(level *l, obstacle *o)
533 {
534 	char *dialog = get_obstacle_extension(l, o, OBSTACLE_EXTENSION_DIALOGFILE);
535 	if (!dialog) {
536 		append_new_game_message(_("Colored patterns appear on the screen, but they do not look like any computer interface you have ever seen. Perhaps is this what they call a screen \"saver\"."));
537 		return;
538 	}
539 
540 	enemy dummy_enemy;
541 	// There are currently two terminal "droids", with different in-dialog images.
542 	if ((o->type >= ISO_CONSOLE_SECURE_E) && (o->type <= ISO_CONSOLE_SECURE_N))
543 		dummy_enemy.type = get_droid_type("STM");
544 	else
545 		dummy_enemy.type = get_droid_type("TRM");
546 	enemy_reset(&dummy_enemy);
547 	dummy_enemy.dialog_section_name = dialog;
548 	dummy_enemy.will_rush_tux = FALSE;
549 	chat_with_droid(&dummy_enemy);
550 }
551 
act_sign(level * l,obstacle * o)552 static void act_sign(level *l, obstacle *o)
553 {
554 	const char *message = get_obstacle_extension(l, o, OBSTACLE_EXTENSION_SIGNMESSAGE);
555 	if (!message) {
556 		message = _("There is nothing on this sign.");
557 	}
558 
559 	append_new_game_message("%s", D_(message));
560 }
561 
__obstacle_action(level * lvl,int index,enum interactive_obstacle_type act)562 static void __obstacle_action(level *lvl, int index, enum interactive_obstacle_type act)
563 {
564 	gps vpos;
565 	int direction;
566 	obstacle *o;
567 
568 	// Retrieve the obstacle
569 	if (index == -1)
570 		return;
571 
572 	o = &lvl->obstacle_list[index];
573 
574 	// Compute the position of the obtacle
575 	update_virtual_position(&vpos, &o->pos, Me.pos.z);
576 	if (vpos.z == -1)
577 		return;
578 
579 	// Compute the approach direction
580 	switch (act) {
581 		case ACT_CHEST:
582 		case ACT_TERMINAL:
583 		case ACT_SIGN:
584 			direction = approach(o->type);
585 			break;
586 		default:
587 			direction = UNDEFINED;
588 	}
589 
590 	// Approach obstacle
591 	if (direction == UNDEFINED) {
592 		int reached = reach_obstacle_from_any_direction(lvl, index);
593 
594 		if (!reached)
595 			return;
596 
597 		// We set a direction of facing the obstacle
598 		// so that the further actions look authentic
599 		moderately_finepoint step_vector;
600 		step_vector.x = -Me.pos.x + vpos.x;
601 		step_vector.y = -Me.pos.y + vpos.y;
602 		Me.angle = -(atan2(step_vector.y, step_vector.x) * 180 / M_PI - 180 - 45);
603 		Me.angle += 360 / (2 * MAX_TUX_DIRECTIONS);
604 		while (Me.angle < 0)
605 			Me.angle += 360;
606 
607 	} else {
608 		if (!reach_obstacle_from_specific_direction(lvl, index, direction))
609 			return;
610 	}
611 
612 	// Check direct reachability
613 	colldet_filter filter = ObstacleByIdPassFilter;
614 	filter.data = &index;
615 
616 	if (!DirectLineColldet(Me.pos.x, Me.pos.y, vpos.x, vpos.y, Me.pos.z, &filter))
617 		return;
618 
619 	event_obstacle_action(o);
620 
621 	// Do the specific action
622 	switch (act) {
623 		case ACT_CHEST:
624 			act_chest(lvl, o);
625 			break;
626 		case ACT_TERMINAL:
627 			act_terminal(lvl, o);
628 			break;
629 		case ACT_SIGN:
630 			act_sign(lvl, o);
631 			break;
632 		case ACT_BARREL:
633 			act_barrel(lvl, o);
634 			break;
635 	}
636 }
637 
638 /**
639  * This function executes a chest's action, if Tux is near enough to activate it.
640  * If Tux is too far, a combo action will be started, to first move Tux near
641  * the chest.
642  */
chest_open_action(level * chest_lvl,int chest_index)643 void chest_open_action(level *chest_lvl, int chest_index)
644 {
645 	__obstacle_action(chest_lvl, chest_index, ACT_CHEST);
646 }
647 
648 /**
649  * This function executes a barrel's action, if Tux is near enough to activate it.
650  * If Tux is too far, a combo action will be started, to first move Tux near
651  * the barrel.
652  */
barrel_action(level * barrel_lvl,int barrel_index)653 void barrel_action(level *barrel_lvl, int barrel_index)
654 {
655 	__obstacle_action(barrel_lvl, barrel_index, ACT_BARREL);
656 }
657 
658 /**
659  * This function connects the player to an interactive computer terminal ingame.
660  */
terminal_connect_action(level * lvl,int terminal_index)661 void terminal_connect_action(level *lvl, int terminal_index)
662 {
663 	__obstacle_action(lvl, terminal_index, ACT_TERMINAL);
664 }
665 
sign_read_action(level * lvl,int index)666 void sign_read_action(level *lvl, int index)
667 {
668 	__obstacle_action(lvl, index, ACT_SIGN);
669 }
670 
671 /**
672  * \brief Check if Tux can pick up an item, and if not, let Tux reach the item.
673  *
674  * Tux can pick up an item if it is close enough and isn't blocked by any
675  * obstacles (e.g. walls).
676  *
677  * If Tux is too far away, a combo action will be started, which will first make
678  * Tux walk towards item.
679  *
680  * \param item_lvl Pointer to the item's level structure
681  * \param item_index Index of the item to pick up
682  *
683  * \return TRUE if the item can be picked up without moving
684  */
check_for_items_to_pickup(level * item_lvl,int item_index)685 int check_for_items_to_pickup(level *item_lvl, int item_index)
686 {
687 	gps item_vpos;
688 
689 	if (item_lvl == NULL || item_index == -1)
690 		return FALSE;
691 
692 	update_virtual_position(&item_vpos, &item_lvl->ItemList[item_index].pos, Me.pos.z);
693 
694 	if ((calc_distance(Me.pos.x, Me.pos.y, item_vpos.x, item_vpos.y) < ITEM_TAKE_DIST)
695 		&& DirectLineColldet(Me.pos.x, Me.pos.y, item_vpos.x, item_vpos.y, Me.pos.z, NULL))
696 	{
697 		return TRUE;
698 	}
699 
700 	// Set up the combo_action
701 	Me.mouse_move_target.x = item_lvl->ItemList[item_index].pos.x;
702 	Me.mouse_move_target.y = item_lvl->ItemList[item_index].pos.y;
703 	Me.mouse_move_target.z = item_lvl->levelnum;
704 
705 	enemy_set_reference(&Me.current_enemy_target_n, &Me.current_enemy_target_addr, NULL);
706 	Me.mouse_move_target_combo_action_type = COMBO_ACTION_PICK_UP_ITEM;
707 	Me.mouse_move_target_combo_action_parameter = item_index;
708 
709 	return FALSE;
710 }
711 
712 /**
713  * This function returns a pointer to the obstacle action function for the given action name.
714  * If action with the given name wasn't found it returns NULL.
715  */
get_action_by_name(const char * action_name)716 action_fptr get_action_by_name(const char *action_name)
717 {
718 	const struct {
719 		const char *name;
720 		action_fptr action;
721 	} action_map[] = {
722 		{ "barrel", barrel_action },
723 		{ "chest", chest_open_action },
724 		{ "terminal", terminal_connect_action },
725 		{ "sign", sign_read_action }
726 	};
727 
728 	if (!action_name)
729 		return NULL;
730 
731 	int i;
732 	for (i = 0; i < sizeof(action_map) / sizeof(action_map[0]); i++) {
733 		if (!strcmp(action_name, action_map[i].name))
734 			return action_map[i].action;
735 	}
736 
737 	error_message(__FUNCTION__, "\nUnknown obstacle action '%s'.", PLEASE_INFORM | IS_FATAL, action_name);
738 	return NULL;
739 }
740 
741 #undef _action_c
742