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