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 *
8 * This file is part of Freedroid
9 *
10 * Freedroid is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * Freedroid is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with Freedroid; see the file COPYING. If not, write to the
22 * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
23 * MA 02111-1307 USA
24 *
25 */
26
27 /**
28 * This file contains all features, movement, firing, collision and
29 * extras of the influencer.
30 */
31
32 #define _influ_c
33
34 #include "system.h"
35
36 #include "defs.h"
37 #include "struct.h"
38 #include "global.h"
39 #include "proto.h"
40 #include "widgets/widgets.h"
41
42 #define BEST_MELEE_DISTANCE (0.8)
43 #define BEST_CHAT_DISTANCE (BEST_MELEE_DISTANCE+0.2)
44 #define DISTANCE_TOLERANCE (0.00002)
45
46 #define LEVEL_JUMP_DEBUG 1
47
48 static void CheckForTuxOutOfMap();
49 static void AnalyzePlayersMouseClick();
50
51 static int no_left_button_press_in_previous_analyze_mouse_click = FALSE;
52
53 /**
54 *
55 *
56 */
calc_distance(float pos1_x,float pos1_y,float pos2_x,float pos2_y)57 float calc_distance(float pos1_x, float pos1_y, float pos2_x, float pos2_y)
58 {
59 return sqrt((pos1_x - pos2_x) * (pos1_x - pos2_x) + (pos1_y - pos2_y) * (pos1_y - pos2_y));
60 };
61
62 /**
63 *
64 *
65 */
vect_len(moderately_finepoint our_vector)66 float vect_len(moderately_finepoint our_vector)
67 {
68 return (sqrt(powf(our_vector.x, 2) + powf(our_vector.y, 2)));
69 }; // float vect_len ( moderately_finepoint our_vector )
70
get_tux_running_speed(void)71 static float get_tux_running_speed(void)
72 {
73 if (GameConfig.cheat_double_speed)
74 return 2 * TUX_RUNNING_SPEED;
75
76 return TUX_RUNNING_SPEED;
77 }
78
79 /**
80 * This function adapts the influencers current speed to the maximal speed
81 * possible for the influencer.
82 *
83 * This function also stops Tux from moving while fighting.
84 */
limit_tux_speed()85 static void limit_tux_speed()
86 {
87 /* Stop all movement if Tux is currently inside attack animation. This is
88 * to stop Tux from moving without his legs animating.
89 *
90 * NOTE REGARDING MELEE ATTACK. Tux starts to swing before he has reached
91 * BEST_MELEE_DISTANCE. If we stop before reaching this distance, Tux will
92 * continue to try to move to this distance between swings, which causes a
93 * jerking motion towards the enemy. To stop this from happening, we force
94 * Tux NOT to stand still when he is first charging a new target. He stands
95 * still ONLY when he changes target during a swing. */
96 static enemy *previous_target;
97 enemy *current_target = enemy_resolve_address(Me.current_enemy_target_n,
98 &Me.current_enemy_target_addr);
99
100 if (Me.weapon_item.type >= 0) {
101 int has_melee = ItemMap[Me.weapon_item.type].weapon_is_melee;
102 if (Me.weapon_swing_time != -1 && (!has_melee
103 || (has_melee && (previous_target != current_target || current_target == NULL))))
104 {
105 Me.speed.x = 0;
106 Me.speed.y = 0;
107 return;
108 } else {
109 previous_target = current_target;
110 }
111 }
112
113 /* Limit the speed when Tux is not attacking. */
114 float speed = Me.speed.x * Me.speed.x + Me.speed.y * Me.speed.y;
115 float running_speed = get_tux_running_speed();
116 if (speed > running_speed * running_speed) {
117 float ratio = (running_speed * running_speed) / speed;
118 Me.speed.x *= ratio;
119 Me.speed.y *= ratio;
120 }
121 }
122
123 /**
124 * When the player has requested an attack motion, we start the
125 * corresponding code, that should try to attack, if that's currently
126 * possible.
127 */
tux_wants_to_attack_now(int use_mouse_cursor_for_targeting)128 void tux_wants_to_attack_now(int use_mouse_cursor_for_targeting)
129 {
130 // Maybe the player requested an attack before the reload/retract
131 // phase is completed. In that case, we don't attack.
132
133 if (Me.busy_time > 0) {
134 return;
135 }
136
137 // If the Tux has a weapon and this weapon requires some ammunition, then
138 // we have to check for enough ammunition first...
139 int weapon_with_ammo = (Me.weapon_item.type >= 0) && (ItemMap[Me.weapon_item.type].weapon_ammo_type);
140
141 if (weapon_with_ammo) {
142 if (Me.weapon_item.ammo_clip <= 0) {
143 // So no ammunition... We should say so and reload...
144 No_Ammo_Sound();
145 // TRANSLATORS: Console msg when a weapon is empty
146 append_new_game_message(_("%s empty, reloading..."), D_(item_specs_get_name(Me.weapon_item.type)));
147 TuxReloadWeapon();
148
149 return;
150 }
151 }
152
153 // Try to attack
154
155 int hit_something = perform_tux_attack(use_mouse_cursor_for_targeting);
156
157 // If the attack failed, and the attack did not use ammunition, nothing
158 // else is to be done. Same if the attack was done with bare fist.
159
160 if ((!hit_something && !weapon_with_ammo) || (Me.weapon_item.type < 0)) {
161 return;
162 }
163
164 // The weapon was used (i.e. a bullet was fired by a ranged weapon, or a
165 // melee shot actually touched an enemy) and therefore the weapon looses
166 // some of it's durability
167
168 if (weapon_with_ammo || hit_something)
169 DamageWeapon(&(Me.weapon_item));
170
171 // Also if it uses ammunition, one charge is to be removed
172 // But, the weapon can have been destroyed by the call to DamageWeapon(),
173 // so the weapon's type is to be checked again.
174
175 if (weapon_with_ammo)
176 Me.weapon_item.ammo_clip--;
177 }
178
179 /**
180 * The Tux might have cross a level's boundary. In that case, we
181 * must move the Tux silently to the corresponding other level.
182 */
correct_tux_position_according_to_jump()183 void correct_tux_position_according_to_jump()
184 {
185 gps old_mouse_move_target;
186 gps oldpos = { Me.pos.x, Me.pos.y, Me.pos.z };
187 gps newpos;
188
189 // If current Tux position is inside current level, there's nothing to change
190 //
191 if (pos_inside_level(Me.pos.x, Me.pos.y, CURLEVEL()))
192 return;
193
194 // Else, try to retrieve the actual position
195 //
196 int pos_valid = resolve_virtual_position(&newpos, &oldpos);
197
198 if (!pos_valid) {
199 // We were not able to compute the actual position...
200 CheckForTuxOutOfMap();
201 return;
202 }
203 // Tux is on another level, teleport it
204 // (note: Teleport() resets Me.mouse_move_target, so we have to restore it)
205 //
206 old_mouse_move_target.x = Me.mouse_move_target.x;
207 old_mouse_move_target.y = Me.mouse_move_target.y;
208 old_mouse_move_target.z = Me.mouse_move_target.z;
209
210 Teleport(newpos.z, newpos.x, newpos.y, FALSE, FALSE);
211
212 Me.mouse_move_target.x = old_mouse_move_target.x;
213 Me.mouse_move_target.y = old_mouse_move_target.y;
214 Me.mouse_move_target.z = old_mouse_move_target.z;
215
216 // Update the mouse target position, if needed
217 //
218 // The purpose is to define mouse_move_target relatively to new Tux's level.
219 // However, if Tux had to move from one level to one of its diagonal neighbor, it has
220 // eventually not yet reach the final destination level.
221 // So we first have to get the real mouse_target position, and then transform it into a
222 // virtual position according to Tux's new level.
223 //
224 if (old_mouse_move_target.z != -1) {
225 int rtn = resolve_virtual_position(&Me.mouse_move_target, &old_mouse_move_target);
226 if (rtn)
227 update_virtual_position(&Me.mouse_move_target, &Me.mouse_move_target, newpos.z);
228 if (!rtn || (Me.mouse_move_target.x == -1)
229 || !SinglePointColldet(Me.mouse_move_target.x, Me.mouse_move_target.y, Me.mouse_move_target.z, NULL)) {
230 Me.mouse_move_target.x = (-1);
231 Me.mouse_move_target.y = (-1);
232 Me.mouse_move_target.z = (-1);
233 }
234 }
235
236 // Update the intermediate waypoints
237 //
238 // Intermediate waypoints are defined relatively to Tux's current level.
239 // They thus also have to be updated<Fluzz>
240 //
241 int i;
242 for (i = 0; i < MAX_INTERMEDIATE_WAYPOINTS_FOR_TUX; i++)
243 {
244 if (Me.next_intermediate_point[i].x == -1)
245 break;
246
247 gps old_point = { Me.next_intermediate_point[i].x, Me.next_intermediate_point[i].y, oldpos.z };
248 gps new_point;
249
250 int rtn = resolve_virtual_position(&new_point, &old_point);
251 if (rtn)
252 update_virtual_position(&new_point, &new_point, newpos.z);
253
254 if (!rtn || new_point.x == -1) {
255 clear_out_intermediate_points(&newpos, Me.next_intermediate_point, MAX_INTERMEDIATE_WAYPOINTS_FOR_TUX);
256 break;
257 }
258
259 Me.next_intermediate_point[i].x = new_point.x;
260 Me.next_intermediate_point[i].y = new_point.y;
261 }
262
263 // Even the Tux must not leave the map! A sanity check is done
264 // here...
265 //
266 CheckForTuxOutOfMap();
267
268 } // correct_tux_position_according_to_jump ( )
269
270 /**
271 * This function initializes the influencers position history, which is
272 * a ring buffer and is needed for throwing the influencer back (only one
273 * or two positions would be needed for that) and for influencers followers
274 * to be able to track the influencers path (10000 or so positions are used
275 * for that, and that's why it is a ring buffer).
276 */
InitInfluPositionHistory()277 void InitInfluPositionHistory()
278 {
279 int RingPosition;
280
281 for (RingPosition = 0; RingPosition < MAX_INFLU_POSITION_HISTORY; RingPosition++) {
282 Me.Position_History_Ring_Buffer[RingPosition].x = Me.pos.x;
283 Me.Position_History_Ring_Buffer[RingPosition].y = Me.pos.y;
284 Me.Position_History_Ring_Buffer[RingPosition].z = Me.pos.z;
285 }
286 } // void InitInfluPositionHistory( void )
287
GetInfluPositionHistoryX(int HowLongPast)288 float GetInfluPositionHistoryX(int HowLongPast)
289 {
290 int RingPosition;
291
292 HowLongPast >>= 1;
293
294 RingPosition = Me.current_zero_ring_index - HowLongPast;
295
296 RingPosition += MAX_INFLU_POSITION_HISTORY; // We don't want any negative values, for safety
297
298 RingPosition %= MAX_INFLU_POSITION_HISTORY; // We do MODULO for the Ring buffer length
299
300 return Me.Position_History_Ring_Buffer[RingPosition].x;
301 }
302
GetInfluPositionHistoryY(int HowLongPast)303 float GetInfluPositionHistoryY(int HowLongPast)
304 {
305 int RingPosition;
306
307 HowLongPast >>= 1;
308 RingPosition = Me.current_zero_ring_index - HowLongPast;
309
310 RingPosition += MAX_INFLU_POSITION_HISTORY; // We don't want any negative values, for safety
311
312 RingPosition %= MAX_INFLU_POSITION_HISTORY; // We do MODULO for the Ring buffer length
313
314 return Me.Position_History_Ring_Buffer[RingPosition].y;
315 }
316
GetInfluPositionHistoryZ(int HowLongPast)317 float GetInfluPositionHistoryZ(int HowLongPast)
318 {
319 int RingPosition;
320
321 HowLongPast >>= 1;
322 RingPosition = Me.current_zero_ring_index - HowLongPast;
323
324 RingPosition += MAX_INFLU_POSITION_HISTORY; // We don't want any negative values, for safety
325
326 RingPosition %= MAX_INFLU_POSITION_HISTORY; // We do MODULO for the Ring buffer length
327
328 return Me.Position_History_Ring_Buffer[RingPosition].z;
329 }
330
331 /**
332 * This function should check if the Tux is still ok, i.e. if he is still
333 * alive or if the death sequence should be initiated.
334 */
CheckIfCharacterIsStillOk()335 void CheckIfCharacterIsStillOk()
336 {
337
338 // Now we check if the main character is really still ok.
339 //
340 if (Me.energy <= 0) {
341 ThouArtDefeated();
342
343 DebugPrintf(1, "\n%s(): Alternate end of function reached.", __FUNCTION__);
344 return;
345 }
346
347 }; // void CheckIfCharacterIsStillOk ( )
348
349 /**
350 * Even the Tux must not leave the map! A sanity check is done here...
351 */
CheckForTuxOutOfMap()352 static void CheckForTuxOutOfMap()
353 {
354 level *MoveLevel = curShip.AllLevels[Me.pos.z];
355
356 // Now perhaps the influencer is out of bounds, i.e. outside of the map.
357 //
358 if (!pos_inside_level(Me.pos.x, Me.pos.y, MoveLevel)) {
359 fprintf(stderr, "\n\nplayer's last position: X=%f, Y=%f, Z=%d.\n", Me.pos.x, Me.pos.y, Me.pos.z);
360 error_message(__FUNCTION__, "\
361 A player's Tux was found outside the map.\n\
362 This indicates either a bug in the FreedroidRPG code or\n\
363 a bug in the currently used map system of FreedroidRPG.", PLEASE_INFORM | IS_FATAL);
364 }
365 }; // void CheckForTuxOutOfMap ( )
366
367 /**
368 * If an enemy was specified as the mouse move target, this enemy will
369 * maybe move here and there. But this means that also the mouse move
370 * target of the influencer must adapt, which is done in this function.
371 */
tux_get_move_target_and_attack(gps * movetgt)372 void tux_get_move_target_and_attack(gps * movetgt)
373 {
374 moderately_finepoint RemainingWay;
375 float RemainingWayLength;
376
377 // if there is a mouse move target, we are not going to move towards the enemy
378 if (Me.mouse_move_target.x != (-1)) {
379 // If a combo action is pending, the mouse_move_target was defined relatively
380 // to the item's level. We need here to define the position relatively to Tux's level.
381 update_virtual_position(movetgt, &Me.mouse_move_target, Me.pos.z);
382 return;
383 }
384
385 enemy *t = enemy_resolve_address(Me.current_enemy_target_n, &Me.current_enemy_target_addr);
386
387 if (!t || (t->energy <= 0)) //No enemy or dead enemy, remove enemy
388 {
389 enemy_set_reference(&Me.current_enemy_target_n, &Me.current_enemy_target_addr, NULL);
390 movetgt->x = -1;
391 movetgt->y = -1;
392 movetgt->z = -1;
393 return;
394 }
395
396 update_virtual_position(&t->virt_pos, &t->pos, Me.pos.z);
397
398 // If we have a ranged weapon in hand, there is no need to approach the
399 // enemy in question. We just try to fire a shot, and return.
400 //
401 if (Me.weapon_item.type != (-1)) {
402 if (!ItemMap[Me.weapon_item.type].weapon_is_melee) { //ranged weapon
403 if (!is_friendly(t->faction, FACTION_SELF))
404 tux_wants_to_attack_now(FALSE);
405 movetgt->x = -1;
406 movetgt->y = -1;
407 movetgt->z = -1;
408 return;
409 }
410 }
411 // Move to melee distance
412 //
413 RemainingWay.x = t->virt_pos.x - Me.pos.x;
414 RemainingWay.y = t->virt_pos.y - Me.pos.y;
415
416 RemainingWayLength = sqrt(RemainingWay.x * RemainingWay.x + RemainingWay.y * RemainingWay.y);
417
418 if (RemainingWayLength > 0.05) {
419 RemainingWay.x = (RemainingWay.x / RemainingWayLength) * (RemainingWayLength - (BEST_MELEE_DISTANCE - 0.1));
420 RemainingWay.y = (RemainingWay.y / RemainingWayLength) * (RemainingWayLength - (BEST_MELEE_DISTANCE - 0.1));
421 }
422
423 if ((RemainingWayLength <= BEST_MELEE_DISTANCE * sqrt(2) + 0.01) && (!is_friendly(t->faction, FACTION_SELF))) {
424 tux_wants_to_attack_now(FALSE);
425 }
426 // New move target.
427 movetgt->x = Me.pos.x + RemainingWay.x;
428 movetgt->y = Me.pos.y + RemainingWay.y;
429 movetgt->z = Me.pos.z;
430
431 return;
432 } // void tux_get_move_target_and_attack( )
433
434 /**
435 * Actually move Tux towards the target.
436 */
move_tux_according_to_his_speed()437 static void move_tux_according_to_his_speed()
438 {
439
440 float planned_step_x;
441 float planned_step_y;
442
443 // Now we move influence according to current speed. But there has been a problem
444 // reported from people, that the influencer would (*very* rarely) jump through walls
445 // and even out of the ship. This has *never* occurred on my fast machine. Therefore
446 // I assume that the problem is related to sometimes very low frame rates on these machines.
447 // So, we do a sanity check not to make steps too big.
448 //
449 // And on machines with FPS << 20, it will certainly alter the game behavior, so people
450 // should really start using a Pentium or better machine.
451 //
452 planned_step_x = Me.speed.x * Frame_Time();
453 planned_step_y = Me.speed.y * Frame_Time();
454
455 Me.pos.x += planned_step_x;
456 Me.pos.y += planned_step_y;
457
458 if (((int)GetInfluPositionHistoryX(0) != (int)Me.pos.x || ((int)GetInfluPositionHistoryY(0) != (int)Me.pos.y))) {
459 event_position_changed(Me.pos, FALSE);
460 }
461
462 // If the Tux got stuck, i.e. if he got no speed at all and still is
463 // currently not in a 'passable' position, the fallback handling needs
464 // to be applied to move the Tux out of the offending obstacle (i.e.
465 // simply away from the offending obstacles center)
466 //
467 if ((fabs(Me.speed.x) < 0.1) && (fabs(Me.speed.y) < 0.1)) {
468 // So there is no speed, so we check for passability...
469 //
470 if (!SinglePointColldet(Me.pos.x, Me.pos.y, Me.pos.z, &WalkablePassFilter)) {
471 // Now it's time to launch the stuck-fallback handling...
472 //
473 DebugPrintf(-3, "\nTux looks stuck...ESCAPING just for this frame...");
474 float new_x = Me.pos.x;
475 float new_y = Me.pos.y;
476 int rtn = EscapeFromObstacle(&new_x, &new_y, Me.pos.z, &WalkablePassFilter);
477 if (!rtn) {
478 DebugPrintf(-3, "\nNo escape position found around Tux... Looking in position history...");
479 // First : look for a suitable position in Tux's position_history
480 int i;
481 float old_x;
482 float old_y;
483 for (i = 1; i < 10; i++) {
484 if (GetInfluPositionHistoryZ(10 * i) == Me.pos.z) {
485 old_x = GetInfluPositionHistoryX(10 * i);
486 old_y = GetInfluPositionHistoryY(10 * i);
487 if (old_x != Me.pos.x && old_y != Me.pos.y
488 && SinglePointColldet(old_x, old_y, Me.pos.z, &WalkablePassFilter)) {
489 // Found...
490 Me.pos.x = old_x;
491 Me.pos.y = old_y;
492 break;
493 }
494 }
495 }
496 // If no luck, last fallback
497 if (i == 10) {
498 DebugPrintf(-3, "\nNo luck with position_history, last fallback...");
499 // Get a random direction, and move by random length from 0.5 to 1.5.
500 // With some luck, Tux will escape now, or in the future tries
501 Me.pos.x += ((0.5 + (float)MyRandom(10) / 10.0) * (MyRandom(10) < 5)) ? 1 : -1;
502 Me.pos.y += ((0.5 + (float)MyRandom(10) / 10.0) * (MyRandom(10) < 5)) ? 1 : -1;
503 }
504 } else {
505 Me.pos.x = new_x;
506 Me.pos.y = new_y;
507 }
508 }
509 }
510 }
511
512 /**
513 * This function contains dumb movement code that, without any checks nor
514 * refinement, calculates the direction and speed in which Tux has to move to
515 * reach the target position.
516 *
517 * Returns TRUE if the target has been sufficiently approximated.
518 */
move_tux_towards_raw_position(float x,float y)519 static int move_tux_towards_raw_position(float x, float y)
520 {
521 moderately_finepoint RemainingWay;
522 moderately_finepoint planned_step;
523 float squared_length, length;
524
525 if (Me.energy <= 0)
526 return FALSE;
527
528 RemainingWay.x = -Me.pos.x + x;
529 RemainingWay.y = -Me.pos.y + y;
530
531 squared_length = RemainingWay.x * RemainingWay.x + RemainingWay.y * RemainingWay.y;
532
533 // Maybe the remaining way is VERY small! Then we must not do
534 // a division at all. We also need not do any movement, so the
535 // speed can be eliminated and we're done here.
536 //
537 if (squared_length < DIST_TO_INTERM_POINT * DIST_TO_INTERM_POINT) {
538 Me.speed.x = 0;
539 Me.speed.y = 0;
540 return (TRUE);
541 }
542 // Now depending on whether the running key is pressed or not,
543 // we have the Tux go on running speed or on walking speed.
544 //
545 length = sqrt(squared_length);
546
547 if (Me.running_power <= 0) {
548 Me.running_must_rest = TRUE;
549 }
550
551 if (Me.running_must_rest) {
552 GameConfig.autorun_activated = 0;
553 planned_step.x = RemainingWay.x * TUX_WALKING_SPEED / length;
554 planned_step.y = RemainingWay.y * TUX_WALKING_SPEED / length;
555 }
556
557 if ((LeftCtrlPressed() || GameConfig.autorun_activated) &&
558 !(LeftCtrlPressed() && GameConfig.autorun_activated) && (!Me.running_must_rest)) {
559 float running_speed = get_tux_running_speed();
560 planned_step.x = RemainingWay.x * running_speed / length;
561 planned_step.y = RemainingWay.y * running_speed / length;
562 } else {
563 planned_step.x = RemainingWay.x * TUX_WALKING_SPEED / length;
564 planned_step.y = RemainingWay.y * TUX_WALKING_SPEED / length;
565 }
566
567 // Now that the speed is set, we can start to make the step
568 //
569 Me.speed.x = planned_step.x;
570 Me.speed.y = planned_step.y;
571 Me.meters_traveled += 0.1;
572 // If we might step over the target,
573 // we reduce the speed.
574 //
575 if (fabsf(planned_step.x * Frame_Time()) >= fabsf(RemainingWay.x))
576 Me.speed.x = RemainingWay.x / Frame_Time();
577 if (fabsf(planned_step.y * Frame_Time()) >= fabsf(RemainingWay.y))
578 Me.speed.y = RemainingWay.y / Frame_Time();
579
580 // In case we have reached our target, we can remove this mouse_move_target again,
581 // but also if we have been thrown onto a different level, we cancel our current
582 // mouse move target...
583 //
584 if (((fabsf(RemainingWay.y) <= DISTANCE_TOLERANCE) && (fabsf(RemainingWay.x) <= DISTANCE_TOLERANCE))) {
585 return (TRUE);
586 }
587
588 return (FALSE);
589 }
590
591 /**
592 *
593 *
594 */
move_tux_towards_intermediate_point(void)595 static void move_tux_towards_intermediate_point(void)
596 {
597 int i;
598
599 /* If we have no intermediate point, Tux has arrived at the target. */
600 if (Me.next_intermediate_point[0].x == -1) {
601 /* First, stop Tux. */
602 Me.speed.x = 0;
603 Me.speed.y = 0;
604
605 if (Me.mouse_move_target.z == -1) {
606 // Nothing was targeted
607 return;
608 }
609
610 /* We might have a combo_action, that can occur on the end of any
611 * course, like e.g. open a chest or pick up some item. */
612 level *lvl = curShip.AllLevels[Me.mouse_move_target.z];
613 switch (Me.mouse_move_target_combo_action_type) {
614 case NO_COMBO_ACTION_SET:
615 break;
616 case COMBO_ACTION_OBSTACLE:
617 get_obstacle_spec(lvl->obstacle_list[Me.mouse_move_target_combo_action_parameter].type)->action_fn(
618 lvl,
619 Me.mouse_move_target_combo_action_parameter);
620 break;
621 case COMBO_ACTION_PICK_UP_ITEM:
622 // If Tux arrived at destination, pick up the item and give it to the player
623 if (check_for_items_to_pickup(lvl, Me.mouse_move_target_combo_action_parameter)) {
624 item *it = &lvl->ItemList[Me.mouse_move_target_combo_action_parameter];
625
626 if (GameConfig.Inventory_Visible) {
627 // Special case: when the inventory screen is open, and there
628 // is no enough room to place the item, put it in player's hand
629 if (!try_give_item(it)) {
630 item_held_in_hand = it;
631 }
632 } else {
633 // Put the item into player's inventory, or drop it on the floor
634 // if there is no enough room.
635 give_item(it);
636 }
637 }
638
639 break;
640 default:
641 error_message(__FUNCTION__, "Unhandled combo action for intermediate course encountered!", PLEASE_INFORM | IS_FATAL);
642 break;
643 }
644
645 Me.mouse_move_target.x = -1;
646 Me.mouse_move_target.y = -1;
647 Me.mouse_move_target.z = -1;
648 Me.mouse_move_target_combo_action_type = NO_COMBO_ACTION_SET;
649 return;
650 }
651
652 /* Stop Tux when he's close enough to an item to pick it up. */
653 if (Me.mouse_move_target_combo_action_type == COMBO_ACTION_PICK_UP_ITEM) {
654 float distance = calc_distance(Me.pos.x, Me.pos.y,
655 Me.mouse_move_target.x, Me.mouse_move_target.y);
656 if (distance < ITEM_TAKE_DIST &&
657 DirectLineColldet(Me.pos.x, Me.pos.y, Me.mouse_move_target.x,
658 Me.mouse_move_target.y, Me.pos.z, NULL))
659 {
660 for (i = 0; i < MAX_INTERMEDIATE_WAYPOINTS_FOR_TUX; i++) {
661 Me.next_intermediate_point[i].x = -1;
662 Me.next_intermediate_point[i].y = -1;
663 }
664 return;
665 }
666 }
667
668 // Move Tux towards the next intermediate course point
669 if (move_tux_towards_raw_position(Me.next_intermediate_point[0].x, Me.next_intermediate_point[0].y)) {
670 DebugPrintf(DEBUG_TUX_PATHFINDING, "\nMOVING ON TO NEXT INTERMEDIATE WAYPOINT! ");
671 for (i = 1; i < MAX_INTERMEDIATE_WAYPOINTS_FOR_TUX; i++) {
672 Me.next_intermediate_point[i - 1].x = Me.next_intermediate_point[i].x;
673 Me.next_intermediate_point[i - 1].y = Me.next_intermediate_point[i].y;
674 }
675 }
676 }
677
678 /**
679 * This function moves the influencer, adjusts his speed according to
680 * keys pressed and also adjusts his status and current "phase" of his
681 * rotation.
682 */
move_tux()683 void move_tux()
684 {
685 static gps last_given_course_target = { -2, -2, -2 };
686
687 // check, if the influencer is still ok
688 CheckIfCharacterIsStillOk();
689
690 // We store the influencers position for the history record and so that others
691 // can follow his trail.
692 //
693 Me.current_zero_ring_index++;
694 Me.current_zero_ring_index %= MAX_INFLU_POSITION_HISTORY;
695 Me.Position_History_Ring_Buffer[Me.current_zero_ring_index].x = Me.pos.x;
696 Me.Position_History_Ring_Buffer[Me.current_zero_ring_index].y = Me.pos.y;
697 Me.Position_History_Ring_Buffer[Me.current_zero_ring_index].z = Me.pos.z;
698
699
700 if (Me.paralyze_duration) {
701 Me.speed.x = 0;
702 Me.speed.y = 0;
703 return; //If tux is paralyzed, we do nothing more
704 }
705 // As a preparation for the later operations, we see if there is
706 // a living droid set as a target, and if yes, we correct the move
707 // target to something suiting that new droids position.
708 //
709 gps move_target;
710
711 tux_get_move_target_and_attack(&move_target);
712
713 if (move_target.x != -1) {
714 // For optimisation purposes, we'll not do anything unless a new target
715 // has been given.
716 //
717 if (!((fabsf(move_target.x - last_given_course_target.x) < 0.3) &&
718 (fabsf(move_target.y - last_given_course_target.y) < 0.3))) {
719 freeway_context frw_ctx = { FALSE, {NULL, NULL} };
720 pathfinder_context pf_ctx = { &WalkableWithMarginPassFilter, &frw_ctx };
721 moderately_finepoint target_point = { move_target.x, move_target.y };
722 if (!set_up_intermediate_course_between_positions
723 (&Me.pos, &target_point, &Me.next_intermediate_point[0], MAX_INTERMEDIATE_WAYPOINTS_FOR_TUX, &pf_ctx)) {
724 // A path was not found.
725 // If a combo action was set, halt it.
726 if (Me.mouse_move_target_combo_action_type != NO_COMBO_ACTION_SET) {
727 Me.mouse_move_target_combo_action_type = NO_COMBO_ACTION_SET;
728 Me.mouse_move_target.x = Me.pos.x;
729 Me.mouse_move_target.y = Me.pos.y;
730 Me.mouse_move_target.z = Me.pos.z;
731 }
732 } else {
733 last_given_course_target.x = move_target.x;
734 last_given_course_target.y = move_target.y;
735 last_given_course_target.z = move_target.z;
736 }
737 }
738 }
739
740 // If there is a mouse move target present, we move towards that.
741 move_tux_towards_intermediate_point();
742
743 // Perhaps the player has pressed the right mouse button, indicating the use
744 // of the currently selected special function or spell.
745 //
746 HandleCurrentlyActivatedSkill();
747
748 // Maybe we need to fire a bullet or set a new mouse move target
749 // for the new move-to location
750 // There are currently two different input systms in use - event based and state based.
751 // In order to maintain compatibility between the two, a game_map widget is added on the
752 // game main widget to detect how user input should be handled. Therefore, when the mouse
753 // is over the game_map widget (the widget is not in its DEFAULT state), no further event handling
754 // is done by the widget system and AnalyzePlayersMouseClick is called.
755 if (game_map->state != DEFAULT)
756 AnalyzePlayersMouseClick();
757
758 if (MouseLeftPressed())
759 no_left_button_press_in_previous_analyze_mouse_click = FALSE;
760 else
761 no_left_button_press_in_previous_analyze_mouse_click = TRUE;
762
763 // During inventory operations, there should not be any (new) movement
764 //
765 if (item_held_in_hand != NULL) {
766 Me.mouse_move_target.x = Me.pos.x;
767 Me.mouse_move_target.y = Me.pos.y;
768 Me.mouse_move_target.z = Me.pos.z;
769 enemy_set_reference(&Me.current_enemy_target_n, &Me.current_enemy_target_addr, NULL);
770 return;
771 }
772
773 limit_tux_speed();
774
775 move_tux_according_to_his_speed();
776
777 animate_tux();
778 }
779
780 /**
781 * This function decrements Tux's health and increments the relevant statistic
782 * variable.
783 */
hit_tux(float damage)784 void hit_tux(float damage)
785 {
786 if (Me.god_mode)
787 return;
788
789 if (Me.energy < 0)
790 return;
791
792 Me.energy -= damage;
793
794 if (damage > Me.energy / 10)
795 tux_scream_sound();
796 }
797
798
799 /**
800 * This function computes to current animation keyframe of Tux (Me.phase).
801 *
802 * Depending of Tux's current state (attacking, standing, walking, running),
803 * an animation is chosen, defined by a keyframed specification and an "animation
804 * progress cursor". This cursor progresses from 0 to 1 (possibly looping for
805 * some animations).
806 *
807 * The current keyframe is then: first_keyframe + cursor * nb_keyframes
808 *
809 * The way a cursor progresses from 0 to 1 depends on the kind of the animation.
810 * For example, the attack animation is a duration-based animation, so its cursor
811 * progresses as time progresses. The walk animation is based on the distance
812 * covered by Tux, so its cursor is a function of distance.
813 *
814 * Note on walking/running animations:
815 * Those animations are running in loop, and we can change from walk to run
816 * in the middle of the animation. In order to have a seamless transition
817 * between the animations, we do not reset the progress cursor's value.
818 * So those animations share a common animation progress cursor (Me.walk_cycle_phase).
819 */
animate_tux()820 void animate_tux()
821 {
822 static int play_step_sound = 0;
823 int tux_is_running = FALSE;
824
825 // If Tux is paralyzed, show him as standing still.
826 if (Me.paralyze_duration) {
827 Me.walk_cycle_phase = 0.0;
828 Me.phase = tux_anim.standing_keyframe;
829
830 return;
831 }
832
833 // Handle the case of Tux just getting hit
834 //
835 // Note: We do not yet have keyframes for such an animation, so those
836 // commented lines are only a place-holder for future extension.
837
838 /*
839 if (Me.got_hit_time != -1) {
840 Me.phase = .....
841 return;
842 }
843 */
844
845 // Attack animation
846 // (duration-based animation, no loop)
847
848 if (Me.weapon_swing_time != -1) {
849 if (tux_anim.attack.nb_keyframes != 0) {
850 float progress_cursor = (float)Me.weapon_swing_time / tux_anim.attack.duration;
851 Me.phase = tux_anim.attack.first_keyframe + progress_cursor * (float)(tux_anim.attack.nb_keyframes);
852
853 // Clamp to maximum keyframe value
854 if ((int)Me.phase > tux_anim.attack.last_keyframe) {
855 Me.phase = tux_anim.attack.last_keyframe;
856 }
857 } else {
858 Me.phase = 0;
859 }
860
861 // Reset walk/run animation progress cursor
862 Me.walk_cycle_phase = 0.0;
863
864 // Stop the time counter when the end of the animation is reached
865 if (Me.weapon_swing_time > tux_anim.attack.duration)
866 Me.weapon_swing_time = -1;
867
868 return;
869 }
870
871 // Breathe animation (launched when Tux's speed is very low).
872 // Currently, there is no such animation, so we only display Tux at its
873 // standing position.
874
875 if (fabs(Me.speed.x) + fabs(Me.speed.y) < 0.3) {
876 Me.walk_cycle_phase = 0.0;
877 Me.phase = tux_anim.standing_keyframe;
878
879 return;
880 }
881
882 // Walk/run animation
883 // (distance-based animation, loop)
884 // The progress cursor is: distance_covered_by_tux / distance_covered_during_one_sequence
885 //
886 // There is no way to 'statically' compute the distance covered by Tux,
887 // so it is computed 'dynamically', i.e. incrementally, by adding the
888 // distance covered since last frame.
889 // The progress cursor thus has to be stored (Me.walk_cycle_phase)
890
891 /* Choose animation spec depending on Tux speed */
892 struct distancebased_animation *anim_spec;
893
894 float my_speed = sqrt(Me.speed.x * Me.speed.x + Me.speed.y * Me.speed.y);
895
896 if (my_speed <= (TUX_WALKING_SPEED + TUX_RUNNING_SPEED) * 0.5) {
897 anim_spec = &(tux_anim.walk);
898 tux_is_running = FALSE;
899 } else {
900 anim_spec = &(tux_anim.run);
901 tux_is_running = TRUE;
902 }
903
904 if (anim_spec->nb_keyframes != 0) {
905 /* Set the progress cursor */
906 Me.walk_cycle_phase += (Frame_Time() * my_speed) / anim_spec->distance;
907
908 /* Loop of progress cursor */
909 if (tux_is_running) {
910 if (play_step_sound == 0 && Me.walk_cycle_phase >= 0.3) {
911 play_sound_v("effects/tux_footstep.ogg", 0.125);
912 play_step_sound = 1;
913 } else if (play_step_sound == 1 && Me.walk_cycle_phase >= 0.8) {
914 play_sound_v("effects/tux_footstep.ogg", 0.125);
915 play_step_sound = 2;
916 }
917 }
918 while (Me.walk_cycle_phase > 1.0) {
919 Me.walk_cycle_phase -= 1.0;
920 play_step_sound = 0;
921 }
922
923 /* Set current animation keyframe */
924 Me.phase = anim_spec->first_keyframe + Me.walk_cycle_phase * anim_spec->nb_keyframes;
925 if ((int)Me.phase > anim_spec->last_keyframe) {
926 Me.phase = anim_spec->last_keyframe;
927 }
928 } else {
929 Me.walk_cycle_phase = 0.0;
930 Me.phase = tux_anim.standing_keyframe;
931 }
932 }
933
934 /**
935 * This function creates several explosions around the location where the
936 * influencer is (was) positioned. It is used after the influencers
937 * death to make his death more spectacular.
938 */
start_tux_death_explosions(void)939 void start_tux_death_explosions(void)
940 {
941 int i;
942 int counter;
943
944 DebugPrintf(1, "\n%s(): Real function call confirmed.", __FUNCTION__);
945
946 // create a few shifted explosions...
947 for (i = 0; i < 10; i++) {
948
949 // find a free blast
950 counter = 0;
951 while (AllBlasts[counter++].type != INFOUT) ;
952 counter -= 1;
953 if (counter >= MAXBLASTS) {
954 error_message(__FUNCTION__, "Ran out of blasts!!!", PLEASE_INFORM | IS_FATAL);
955 }
956 AllBlasts[counter].type = DROIDBLAST;
957 AllBlasts[counter].pos.x = Me.pos.x - 0.125 / 2 + MyRandom(10) * 0.05;
958 AllBlasts[counter].pos.y = Me.pos.y - 0.125 / 2 + MyRandom(10) * 0.05;
959 AllBlasts[counter].phase = i;
960 }
961
962 DebugPrintf(1, "\n%s(): Usual end of function reached.", __FUNCTION__);
963
964 }; // void start_tux_death_explosions ( void )
965
966 /**
967 * This function opens a menu when tux dies, asking the
968 * player if he/she wants to load latest or backup game,
969 * quit to main menu or quit the game.
970 */
do_death_menu()971 void do_death_menu()
972 {
973 char *MenuTexts[100];
974 int done = FALSE;
975 int MenuPosition = 1;
976 int i;
977
978 game_status = INSIDE_MENU;
979
980 input_handle();
981
982 enum {
983 LOAD_LATEST_POSITION = 1,
984 LOAD_BACKUP_POSITION,
985 QUIT_TO_MAIN_POSITION,
986 QUIT_POSITION
987 };
988 while (!done) {
989 i = 0;
990 MenuTexts[i++] = _("Load Latest");
991 MenuTexts[i++] = _("Load Backup");
992 if (game_root_mode == ROOT_IS_GAME) {
993 MenuTexts[i++] = _("Quit to Main Menu");
994 } else { // if (game_root_mode == ROOT_IS_LVLEDIT) {
995 MenuTexts[i++] = _("Return to Editor");
996 }
997 MenuTexts[i++] = _("Exit FreedroidRPG");
998 MenuTexts[i++] = "";
999
1000 MenuPosition = DoMenuSelection("", MenuTexts, 1, "--GAME_BACKGROUND--", Menu_Font);
1001
1002 switch (MenuPosition) {
1003 case LOAD_LATEST_POSITION:
1004 load_game();
1005 done = !done;
1006 break;
1007 case LOAD_BACKUP_POSITION:
1008 load_backup_game();
1009 done = !done;
1010 break;
1011 case QUIT_TO_MAIN_POSITION:
1012 if (game_root_mode == ROOT_IS_GAME) {
1013 GameOver = TRUE;
1014 }
1015 done = !done;
1016 break;
1017 case QUIT_POSITION:
1018 Terminate(EXIT_SUCCESS);
1019 break;
1020 default:
1021 done = !done;
1022 break;
1023 }
1024 }
1025 }
1026
1027 /**
1028 * This function checks if the influencer is currently colliding with an
1029 * enemy and throws him back in that case.
1030 */
check_tux_enemy_collision(void)1031 void check_tux_enemy_collision(void)
1032 {
1033 float xdist;
1034 float ydist;
1035
1036 enemy *erot, *nerot;
1037 BROWSE_LEVEL_BOTS_SAFE(erot, nerot, Me.pos.z) {
1038
1039 if (erot->type == (-1) || erot->pure_wait)
1040 continue;
1041
1042 // We determine the distance and back out immediately if there
1043 // is still one whole square distance or even more...
1044 //
1045 xdist = Me.pos.x - erot->pos.x;
1046 if (fabs(xdist) > 1)
1047 continue;
1048 ydist = Me.pos.y - erot->pos.y;
1049 if (fabs(ydist) > 1)
1050 continue;
1051
1052 // Now at this point we know, that we are pretty close. It is time
1053 // to calculate the exact distance and to see if the exact distance
1054 // indicates a collision or not, in which case we can again back out
1055 //
1056 //
1057 if ((fabsf(xdist) >= 2.0 * 0.25) || (fabsf(ydist) >= 2.0 * 0.25))
1058 continue;
1059
1060 erot->pure_wait = WAIT_COLLISION;
1061
1062 short int swap = erot->nextwaypoint;
1063 erot->nextwaypoint = erot->lastwaypoint;
1064 erot->lastwaypoint = swap;
1065
1066 }
1067
1068 }; // void check_tux_enemy_collision( void )
1069
1070 /**
1071 * This function checks if there is some living droid below the current
1072 * mouse cursor and returns the index number of this droid in the array.
1073 *
1074 * Earlier we did this by computing the map location the mouse was pointing
1075 * to and using that for the computation of the distance to droid coordinates.
1076 * The problem with this method is, that some droids are very 'high' in
1077 * the sense that the graphics (e.g. 302 body) is very far away from the
1078 * 'foot' point, where the droid is in X-Y coordinates on the map. Therefore
1079 * some correction has to be done to fix this. We can't use the map position
1080 * of the mouse any more... except maybe to exclude some bots from the start.
1081 *
1082 */
GetLivingDroidBelowMouseCursor()1083 enemy *GetLivingDroidBelowMouseCursor()
1084 {
1085 gps mouse_vpos, mouse_pos;
1086 int RotationModel, RotationIndex;
1087 struct image *our_image;
1088 enemy *this_bot;
1089
1090 // no interaction with the game when the world is frozen
1091 if (world_frozen())
1092 return NULL;
1093
1094 mouse_vpos.x = translate_pixel_to_map_location((float)input_axis.x, (float)input_axis.y, TRUE);
1095 mouse_vpos.y = translate_pixel_to_map_location((float)input_axis.x, (float)input_axis.y, FALSE);
1096 mouse_vpos.z = Me.pos.z;
1097
1098 // Find the actual level (and related position) where the mouse cursor is pointing at.
1099 if (!resolve_virtual_position(&mouse_pos, &mouse_vpos))
1100 return NULL;
1101
1102 // Browse all bots on that actual level, to find if the mouse is hovering one of them
1103 BROWSE_LEVEL_BOTS(this_bot, mouse_pos.z) {
1104 if (fabsf(this_bot->pos.x - mouse_pos.x) >= 5.0)
1105 continue;
1106 if (fabsf(this_bot->pos.y - mouse_pos.y) >= 5.0)
1107 continue;
1108
1109 // We properly set the direction this robot is facing.
1110 //
1111 RotationIndex = set_rotation_index_for_this_robot(this_bot);
1112
1113 // We properly set the rotation model number for this robot, i.e.
1114 // which shape (like 302, 247 or proffa) to use for drawing this bot.
1115 //
1116 RotationModel = set_rotation_model_for_this_robot(this_bot);
1117
1118 our_image = &(enemy_images[RotationModel][RotationIndex][(int)this_bot->animation_phase]);
1119
1120 update_virtual_position(&this_bot->virt_pos, &this_bot->pos, Me.pos.z);
1121 if (mouse_cursor_is_on_that_image(this_bot->virt_pos.x, this_bot->virt_pos.y, our_image)) {
1122 return this_bot;
1123 }
1124 }
1125
1126 return (NULL);
1127
1128 }; // int GetLivingDroidBelowMouseCursor ( )
1129
1130 /**
1131 * This function fires a bullet from the influencer in some direction,
1132 * no matter whether this is 'allowed' or not, not questioning anything
1133 * and SILENTLY TRUSTING THAT THIS TUX HAS A RANGED WEAPON EQUIPPED.
1134 */
perform_tux_ranged_attack(short int weapon_type,bullet * bullet_parameters,moderately_finepoint target_location)1135 void perform_tux_ranged_attack(short int weapon_type, bullet *bullet_parameters,
1136 moderately_finepoint target_location)
1137 {
1138 // Standard ranged attack process is to fire a bullet in the direction
1139 // of the target, and have it advance step by step until it reaches 'something'.
1140 // The starting position of the bullet is the gun's muzzle (note that we do not
1141 // know exactly the length of a gun, so we will use an 'average' constant value of
1142 // 'muzzle_offset_factor').
1143 //
1144 // But, sometime the targeted object can be so near Tux that it is actually
1145 // between Tux and the gun's muzzle. In "real life", this should not even
1146 // create a ranged attack, but for simplicity we will fire a bullet in that case.
1147 // The bullet being already at its target position, we immediately call the code
1148 // that detects bullet collisions and create bullet's explosion and damages.
1149 // And only if no collision is detected do we fire the bullet.
1150
1151 float muzzle_offset_factor = 0.5;
1152
1153 // Search for the first free bullet entry in the bullet list and initialize
1154 // with default values
1155
1156 int bullet_index = find_free_bullet_index();
1157 if (bullet_index == -1) {
1158 // We are out of free bullet slots.
1159 // This should not happen, an error message was displayed,
1160 return;
1161 }
1162
1163 struct bullet *new_bullet = &(AllBullets[bullet_index]);
1164 if (bullet_parameters)
1165 memcpy(new_bullet, bullet_parameters, sizeof(struct bullet));
1166 else
1167 bullet_init_for_player(new_bullet, ItemMap[weapon_type].weapon_bullet_type, weapon_type);
1168
1169 // Set up recharging time for the Tux...
1170 // The firewait time is modified by the ranged weapon skill
1171
1172 Me.busy_time = ItemMap[weapon_type].weapon_attack_time;
1173 Me.busy_time *= RangedRechargeMultiplierTable[Me.ranged_weapon_skill];
1174 Me.busy_type = WEAPON_FIREWAIT;
1175 Me.weapon_swing_time = 0; // restart swing animation
1176
1177 // First case: The target is too near for an actual shot.
1178 // We try an immediate hit. If nothing is hurt, or if the bullet traversed
1179 // the target, then we will fire a bullet using the 'second case'.
1180
1181 float const dist_epsilon = 0.05;
1182
1183 if ( (fabs(target_location.x - Me.pos.x) <= muzzle_offset_factor + dist_epsilon) &&
1184 (fabs(target_location.y - Me.pos.y) <= muzzle_offset_factor + dist_epsilon) ) {
1185
1186 new_bullet->pos.x = target_location.x;
1187 new_bullet->pos.y = target_location.y;
1188 CheckBulletCollisions(bullet_index);
1189
1190 /* If the bullet exploded, we're done */
1191 if (new_bullet->type == INFOUT)
1192 return;
1193 }
1194
1195 // Second case: The target is not near Tux (or a first-case bullet traversed
1196 // the target)
1197 // We fire a bullet into the direction of the target.
1198
1199 // Compute bullet's attack vector.
1200 // This is a vector from Tux's position to the target's position.
1201
1202 moderately_finepoint attack_vector = { target_location.x - Me.pos.x,
1203 target_location.y - Me.pos.y };
1204 double attack_norm = sqrt(attack_vector.x * attack_vector.x + attack_vector.y *attack_vector.y);
1205 attack_vector.x /= attack_norm;
1206 attack_vector.y /= attack_norm;
1207
1208 // The bullet starts from the muzzle's position.
1209 // As said in the heading comment, we do not have enough informations to
1210 // compute it, so we just use a small offset in the attack direction.
1211
1212 moderately_finepoint muzzle_position = { Me.pos.x + muzzle_offset_factor * attack_vector.x,
1213 Me.pos.y + muzzle_offset_factor * attack_vector.y };
1214
1215 // Set the bullet parameters
1216
1217 new_bullet->pos.x = muzzle_position.x;
1218 new_bullet->pos.y = muzzle_position.y;
1219 new_bullet->speed.x = attack_vector.x * ItemMap[weapon_type].weapon_bullet_speed;
1220 new_bullet->speed.y = attack_vector.y * ItemMap[weapon_type].weapon_bullet_speed;
1221 new_bullet->angle = -(atan2(attack_vector.y, attack_vector.x) * 180 / M_PI + 90 + 45);
1222 }
1223
1224 /**
1225 * In some cases, the mouse button will be pressed, but still some signs
1226 * might tell us, that this mouse button press was not intended as a move
1227 * or fire command to the Tux. This function checks for these cases.
1228 */
ButtonPressWasNotMeantAsFire()1229 int ButtonPressWasNotMeantAsFire()
1230 {
1231 // If the influencer is holding something from the inventory
1232 // menu via the mouse, also just return
1233 //
1234 if (item_held_in_hand != NULL)
1235 return (TRUE);
1236 if (timeout_from_item_drop > 0)
1237 return (TRUE);
1238
1239 // Maybe the player just pressed the mouse button but INSIDE one of the character/skills/inventory
1240 // screens. Then of course we will not interpret the intention to fire the weapon but rather
1241 // return from here immediately.
1242 //
1243 if (MouseLeftPressed() &&
1244 (GameConfig.Inventory_Visible || GameConfig.CharacterScreen_Visible || GameConfig.SkillScreen_Visible
1245 || GameConfig.skill_explanation_screen_visible)
1246 && !MouseCursorIsInUserRect(GetMousePos_x(), GetMousePos_y())) {
1247 DebugPrintf(0, "\nCursor outside user-rect:\n User_Rect.x=%d, User_Rect.w=%d, User_Rect.y=%d, User_Rect.h=%d.",
1248 User_Rect.x, User_Rect.w, User_Rect.y, User_Rect.h);
1249 DebugPrintf(0, "\nCursor position: X=%d, Y=%d.", input_axis.x, input_axis.y);
1250 return (TRUE);
1251 }
1252
1253 return (FALSE);
1254
1255 }; // int ButtonPressWasNotMeantAsFire ( )
1256
1257 /**
1258 * At some point in the analysis of the users mouse click, we'll be
1259 * certain, that a fireing/weapon swing was meant with the click. Once
1260 * this is knows, this function can be called to do the mechanics of the
1261 * weapon use.
1262 *
1263 * Return 0 if attack failed.
1264 */
perform_tux_attack(int use_mouse_cursor_for_targeting)1265 int perform_tux_attack(int use_mouse_cursor_for_targeting)
1266 {
1267 moderately_finepoint target_location = { -1, -1 };
1268 float target_angle;
1269
1270 // The attack target location can be the targeted enemy (if one was set), or an
1271 // enemy below the mouse cursor, or by default the current mouse position.
1272 // In case of an 'un-targeted' attack (A-pressed), the mouse position defines the
1273 // target location.
1274
1275 if (!APressed() && !use_mouse_cursor_for_targeting) {
1276 enemy *targeted_enemy = enemy_resolve_address(Me.current_enemy_target_n, &Me.current_enemy_target_addr);
1277 if (!targeted_enemy) {
1278 targeted_enemy = GetLivingDroidBelowMouseCursor();
1279 }
1280 if (targeted_enemy) {
1281 // Use enemy's position to compute target location
1282 update_virtual_position(&targeted_enemy->virt_pos, &targeted_enemy->pos, Me.pos.z);
1283 target_location.x = targeted_enemy->virt_pos.x;
1284 target_location.y = targeted_enemy->virt_pos.y;
1285 } else {
1286 // Use mouse position to compute target location
1287 use_mouse_cursor_for_targeting = TRUE;
1288 }
1289 } else {
1290 use_mouse_cursor_for_targeting = TRUE;
1291 }
1292
1293 // If target location is defined by the mouse position, check if there is
1294 // an obstacle or an enemy under the mouse, otherwise use the mouse location
1295 if (use_mouse_cursor_for_targeting) {
1296
1297 // By default, use mouse location
1298 target_location.x = translate_pixel_to_map_location((float)input_axis.x, (float)input_axis.y, TRUE);
1299 target_location.y = translate_pixel_to_map_location((float)input_axis.x, (float)input_axis.y, FALSE);
1300
1301 // If there is an obstacle under the mouse cursor, use it as target
1302 level *obs_lvl = NULL;
1303 gps targeted_obstacle_vpos = { -1, -1, -1 };
1304 int targeted_obstacle_index = clickable_obstacle_below_mouse_cursor(&obs_lvl, FALSE);
1305 if (targeted_obstacle_index != -1) {
1306 update_virtual_position(&targeted_obstacle_vpos, &obs_lvl->obstacle_list[targeted_obstacle_index].pos, Me.pos.z);
1307 target_location.x = targeted_obstacle_vpos.x;
1308 target_location.y = targeted_obstacle_vpos.y;
1309 }
1310
1311 // If there is an enemy under the mouse cursor, use it as target if the enemy is nearest (using iso-norm)
1312 enemy *targeted_enemy = GetLivingDroidBelowMouseCursor();
1313 if (targeted_enemy != NULL) {
1314 update_virtual_position(&targeted_enemy->virt_pos, &targeted_enemy->pos, Me.pos.z);
1315 if ((targeted_obstacle_index == -1) ||
1316 ((targeted_enemy->virt_pos.x + targeted_enemy->virt_pos.y) >= (targeted_obstacle_vpos.x + targeted_obstacle_vpos.y))) {
1317 target_location.x = targeted_enemy->virt_pos.x;
1318 target_location.y = targeted_enemy->virt_pos.y;
1319 }
1320 }
1321
1322 }
1323
1324 // Turn Tux to face its target
1325
1326 target_angle = -(atan2(Me.pos.y - target_location.y, Me.pos.x - target_location.x) * 180/M_PI - 90 + 22.5);
1327 Me.angle = target_angle;
1328
1329 /**
1330 * First case: Attack with a melee weapon (or with fists).
1331 */
1332
1333 if (Me.weapon_item.type == -1 || ItemMap[Me.weapon_item.type].weapon_is_melee) {
1334
1335 int hit_something = FALSE;
1336
1337 // A melee attack is a swing gesture, the impact point is thus a bit in front of
1338 // Tux. We have no idea of the size of the weapon, so we use an 'average' constant
1339 // value of 0.8 (impact_offset_y).
1340
1341 const float impact_offset_y = 0.8;
1342
1343 moderately_finepoint impact_point = { 0, -impact_offset_y };
1344 RotateVectorByAngle(&impact_point, Me.angle);
1345 impact_point.x += Me.pos.x;
1346 impact_point.y += Me.pos.y;
1347
1348 // Find all enemies which are in a small area around the impact point
1349 // and setup up a melee shot.
1350 // TODO: also look for enemies on neighbor levels.
1351
1352 const float impact_area_size = 0.5;
1353
1354 enemy *erot, *nerot;
1355 BROWSE_LEVEL_BOTS_SAFE(erot, nerot, Me.pos.z) {
1356 if ( (fabsf(erot->pos.x - impact_point.x) > impact_area_size) ||
1357 (fabsf(erot->pos.y - impact_point.y) > impact_area_size) ||
1358 !DirectLineColldet(Me.pos.x, Me.pos.y, erot->pos.x, erot->pos.y, Me.pos.z, NULL)
1359 )
1360 continue;
1361
1362 // Set up a melee attack
1363 int shot_index = find_free_melee_shot_index();
1364 if (shot_index == -1) {
1365 // We are out of free melee shot slots.
1366 // This should not happen, an error message was displayed,
1367 return 0;
1368 }
1369
1370 melee_shot *new_shot = &(AllMeleeShots[shot_index]);
1371
1372 new_shot->attack_target_type = ATTACK_TARGET_IS_ENEMY;
1373 new_shot->mine = TRUE;
1374 new_shot->bot_target_n = erot->id;
1375 new_shot->bot_target_addr = erot;
1376 new_shot->to_hit = Me.to_hit;
1377 new_shot->damage = Me.base_damage + MyRandom(Me.damage_modifier);
1378 new_shot->owner = -1; //no "bot class number" owner
1379 new_shot->time_to_hit = tux_anim.attack.duration / 2;
1380
1381 // Slow or paralyze enemies if the player has bonuses with those effects.
1382 erot->frozen += Me.slowing_melee_targets;
1383 erot->paralysation_duration_left += Me.paralyzing_melee_targets;
1384
1385 hit_something = TRUE;
1386 }
1387
1388 // Also, we should check if there was perhaps a chest or box
1389 // or something that can be smashed up, cause in this case, we
1390 // must open Pandora's box now.
1391
1392 if (smash_obstacle(impact_point.x, impact_point.y, Me.pos.z))
1393 hit_something = TRUE;
1394
1395 // Finally we add a new wait-counter, so that swings cannot be started
1396 // in too rapid succession. Adjust that delay to take player's skill
1397 // into account.
1398
1399 Me.busy_type = WEAPON_FIREWAIT;
1400 Me.busy_time = 0.5; // default value
1401 if (Me.weapon_item.type != -1)
1402 Me.busy_time = ItemMap[Me.weapon_item.type].weapon_attack_time;
1403 Me.busy_time *= MeleeRechargeMultiplierTable[Me.melee_weapon_skill];
1404 Me.weapon_swing_time = 0; // restart swing animation
1405
1406 // Play a sound feedback
1407
1408 if (hit_something)
1409 play_melee_weapon_hit_something_sound();
1410 else
1411 play_melee_weapon_missed_sound(&Me.pos);
1412
1413 return hit_something;
1414 }
1415
1416 /**
1417 * Second case: Attack with a ranged weapon.
1418 * Note: we know that weapon_item.type != -1
1419 */
1420
1421 perform_tux_ranged_attack(Me.weapon_item.type, NULL, target_location);
1422
1423 fire_bullet_sound(ItemMap[Me.weapon_item.type].weapon_bullet_type, &Me.pos);
1424
1425 // We do not know if the bullet will hit something, but the bullet was fired,
1426 // so that's a success...
1427
1428 return TRUE;
1429 }
1430
1431 /**
1432 * Reload the ammo clip
1433 *
1434 *
1435 */
1436
TuxReloadWeapon()1437 void TuxReloadWeapon()
1438 {
1439 if (Me.weapon_item.type == -1)
1440 return; // Do not reload Tux's fists.
1441
1442 if (Me.paralyze_duration)
1443 return; // Do not reload when paralyzed.
1444
1445 if (ItemMap[Me.weapon_item.type].weapon_ammo_clip_size == Me.weapon_item.ammo_clip)
1446 return; // Clip full, return without reloading.
1447
1448 int ammo_type = get_item_type_by_id(ItemMap[Me.weapon_item.type].weapon_ammo_type);
1449
1450 int count = CountItemtypeInInventory(ammo_type);
1451 if (count > ItemMap[Me.weapon_item.type].weapon_ammo_clip_size - Me.weapon_item.ammo_clip)
1452 count = ItemMap[Me.weapon_item.type].weapon_ammo_clip_size - Me.weapon_item.ammo_clip;
1453
1454 if (!count) //no ammo found, tell the player that he "has it in the baba"
1455 {
1456 No_Ammo_Sound();
1457 No_Ammo_Sound();
1458 // TRANSLATORS: Out of <ammo type>
1459 append_new_game_message(_("Out of [s]%s[v]!"), _(item_specs_get_name(ammo_type)));
1460 return;
1461 }
1462 int i;
1463 for (i = 0; i < count; i++)
1464 DeleteOneInventoryItemsOfType(ammo_type);
1465 Me.weapon_item.ammo_clip += count;
1466 play_sound(ItemMap[Me.weapon_item.type].weapon_reloading_sound);
1467 Me.busy_time = ItemMap[Me.weapon_item.type].weapon_reloading_time;
1468 Me.busy_time *= RangedRechargeMultiplierTable[Me.ranged_weapon_skill];
1469 Me.busy_type = WEAPON_RELOAD;
1470 }
1471
1472 /**
1473 * When the player has left-clicked into the game area (i.e. the isometric
1474 * display of the game world), we need to check if maybe the click was
1475 * targeted on a droid.
1476 * In case that was so, we need to start a dialog or maybe launch an
1477 * attack movement.
1478 */
check_for_droids_to_attack_or_talk_with()1479 void check_for_droids_to_attack_or_talk_with()
1480 {
1481 /* NOTA : the call to GetLivingDroidBelowMouseCursor() does set the virt_pos attribute
1482 * of the found droid to be the bot's position relatively to Tux current level
1483 */
1484 enemy *droid_below_mouse_cursor = GetLivingDroidBelowMouseCursor();
1485
1486 // Set a move action unless there's a droid below the cursor,
1487 // 'A' is pressed,
1488 // or the player has clicked to pickup an item and hasn't released the LMB
1489 if (droid_below_mouse_cursor == NULL && (!APressed()) &&
1490 (no_left_button_press_in_previous_analyze_mouse_click ||
1491 Me.mouse_move_target_combo_action_type != COMBO_ACTION_PICK_UP_ITEM)) {
1492
1493 Me.mouse_move_target.x = translate_pixel_to_map_location(input_axis.x, input_axis.y, TRUE);
1494 Me.mouse_move_target.y = translate_pixel_to_map_location(input_axis.x, input_axis.y, FALSE);
1495 Me.mouse_move_target.z = Me.pos.z;
1496 if (!ShiftPressed()) {
1497 enemy_set_reference(&Me.current_enemy_target_n, &Me.current_enemy_target_addr, NULL);
1498 }
1499
1500 return;
1501 } else if (APressed()) {
1502 tux_wants_to_attack_now(TRUE);
1503 return;
1504 }
1505
1506 if (droid_below_mouse_cursor != NULL &&
1507 DirectLineColldet(Me.pos.x, Me.pos.y, droid_below_mouse_cursor->virt_pos.x, droid_below_mouse_cursor->virt_pos.y, Me.pos.z,
1508 &VisiblePassFilter)) {
1509
1510 enemy_set_reference(&Me.current_enemy_target_n, &Me.current_enemy_target_addr, droid_below_mouse_cursor);
1511
1512 enemy *e = enemy_resolve_address(Me.current_enemy_target_n, &Me.current_enemy_target_addr);
1513 if (is_friendly(e->faction, FACTION_SELF)) {
1514 if (no_left_button_press_in_previous_analyze_mouse_click) {
1515 chat_with_droid(enemy_resolve_address(Me.current_enemy_target_n, &Me.current_enemy_target_addr));
1516 enemy_set_reference(&Me.current_enemy_target_n, &Me.current_enemy_target_addr, NULL);
1517 }
1518
1519 return;
1520 }
1521
1522 if (!ShiftPressed()) {
1523 Me.mouse_move_target.x = -1;
1524 Me.mouse_move_target.y = -1;
1525 }
1526
1527 if (Me.weapon_item.type >= 0) {
1528 if ((ItemMap[Me.weapon_item.type].weapon_is_melee) &&
1529 (calc_distance(Me.pos.x, Me.pos.y, droid_below_mouse_cursor->virt_pos.x, droid_below_mouse_cursor->virt_pos.y)
1530 > BEST_MELEE_DISTANCE + 0.1)) {
1531
1532 return;
1533 }
1534 } else if (calc_distance(Me.pos.x, Me.pos.y, droid_below_mouse_cursor->virt_pos.x, droid_below_mouse_cursor->virt_pos.y)
1535 > BEST_MELEE_DISTANCE + 0.1) {
1536
1537 return;
1538 }
1539 // But if we're close enough or there is a ranged weapon in Tux hands,
1540 // then we can finally start the attack motion right away...
1541 //
1542 tux_wants_to_attack_now(TRUE);
1543 }
1544 }; // void check_for_droids_to_attack ( )
1545
1546 /**
1547 * If the user clicked his mouse, this might have several reasons. It
1548 * might happen to open some windows, pick up some stuff, smash a box,
1549 * move somewhere or fire a shot or make a weapon swing.
1550 *
1551 * Therefore it is not so easy to decide what to do upon a users mouse
1552 * click and so this function analyzes the situation and decides what to
1553 * do.
1554 */
AnalyzePlayersMouseClick()1555 static void AnalyzePlayersMouseClick()
1556 {
1557 int tmp;
1558
1559 // This flag avoids the mouse_move_target to change while the user presses
1560 // LMB to start a combo action.
1561 static int wait_mouseleft_release = FALSE;
1562
1563 // No action is associated to MouseLeftRelease event or state.
1564 //
1565
1566 if (!MouseLeftPressed()) {
1567 wait_mouseleft_release = FALSE;
1568 return;
1569 }
1570
1571 if (ButtonPressWasNotMeantAsFire())
1572 return;
1573 if (no_left_button_press_in_previous_analyze_mouse_click) {
1574 level *obj_lvl = NULL;
1575
1576 Me.mouse_move_target_combo_action_type = NO_COMBO_ACTION_SET;
1577
1578 if ((tmp = clickable_obstacle_below_mouse_cursor(&obj_lvl, TRUE)) != -1) {
1579 get_obstacle_spec(obj_lvl->obstacle_list[tmp].type)->action_fn(obj_lvl, tmp);
1580 if (Me.mouse_move_target_combo_action_type != NO_COMBO_ACTION_SET)
1581 wait_mouseleft_release = TRUE;
1582 return;
1583 }
1584
1585 // If the inventory screen is open, let it manage any possibly picked item.
1586 // Else, if the player left-clicked on an item, check if the item can be
1587 // picked up. If so, get it and give it to the player.
1588 // Note: if the item is too far away from Tux, check_for_items_to_pickup()
1589 // creates a combo action to reach the item.
1590 if (GameConfig.Inventory_Visible == FALSE) {
1591 if ((tmp = get_floor_item_index_under_mouse_cursor(&obj_lvl)) != -1) {
1592 if (check_for_items_to_pickup(obj_lvl, tmp)) {
1593 // The item can be picked up immediately , so give it to the player
1594 give_item(&obj_lvl->ItemList[tmp]);
1595 wait_mouseleft_release = TRUE;
1596 }
1597 return;
1598 }
1599 }
1600 }
1601 // Just after the beginning of a combo action, and while LMB is
1602 // always pressed, mouse_move_target must not be changed (so that
1603 // the player's character will actually move to the combo action's target)
1604
1605 if (!wait_mouseleft_release)
1606 check_for_droids_to_attack_or_talk_with();
1607 }
1608
1609
1610 /**
1611 * Handles arrow key based movements
1612 */
set_movement_with_keys(int move_x,int move_y)1613 void set_movement_with_keys(int move_x, int move_y)
1614 {
1615 float floor_center = 0.5;
1616 int move_amplitude = 1 + GameConfig.autorun_activated;
1617
1618 // Center the target waypoint, or establish a new target waypoint if there isn't one already
1619 if (Me.mouse_move_target.z != -1) {
1620 Me.mouse_move_target.x = floor(Me.mouse_move_target.x) + floor_center;
1621 Me.mouse_move_target.y = floor(Me.mouse_move_target.y) + floor_center;
1622 } else {
1623 Me.mouse_move_target.z = Me.pos.z;
1624 Me.mouse_move_target.x = floor(Me.pos.x) + floor_center;
1625 Me.mouse_move_target.y = floor(Me.pos.y) + floor_center;
1626 }
1627
1628 //Restricts moving target to within 2 units from current position
1629 if (fabs(Me.pos.x - (Me.mouse_move_target.x + move_x * move_amplitude)) <= 2 &&
1630 fabs(Me.pos.y - (Me.mouse_move_target.y + move_y * move_amplitude)) <= 2) {
1631 // Determine move amount
1632 Me.mouse_move_target.x += move_x * move_amplitude;
1633 Me.mouse_move_target.y += move_y * move_amplitude;
1634 }
1635 }
1636
free_tux()1637 void free_tux()
1638 {
1639 int i;
1640
1641 free(Me.character_name);
1642 Me.character_name = NULL;
1643
1644 clear_tux_mission_info();
1645
1646 // We mark all the big screen messages for this character
1647 // as out of date, so they can be overwritten with new
1648 // messages...
1649 //
1650 Me.BigScreenMessageIndex = 0;
1651 for (i = 0; i < MAX_BIG_SCREEN_MESSAGES; i++) {
1652 if (Me.BigScreenMessage[i]) {
1653 free(Me.BigScreenMessage[i]);
1654 Me.BigScreenMessage[i] = NULL;
1655 }
1656 }
1657
1658 for (i = 0; i < MAX_ITEMS_IN_INVENTORY; i++) {
1659 if (Me.Inventory[i].type != -1) {
1660 delete_upgrade_sockets(&Me.Inventory[i]);
1661 }
1662 }
1663 delete_upgrade_sockets(&Me.weapon_item);
1664 delete_upgrade_sockets(&Me.drive_item);
1665 delete_upgrade_sockets(&Me.armour_item);
1666 delete_upgrade_sockets(&Me.shield_item);
1667 delete_upgrade_sockets(&Me.special_item);
1668 }
1669
1670 /**
1671 * Reset the data in struct tux, in order to start a new game/load a game.
1672 */
init_tux()1673 void init_tux()
1674 {
1675 int i;
1676
1677 free_tux();
1678
1679 memset(&Me, 0, sizeof(struct tux));
1680
1681 Me.current_game_date = 0.0;
1682 Me.power_bonus_end_date = -1; // negative dates are always in the past...
1683 Me.dexterity_bonus_end_date = -1;
1684
1685 Me.speed.x = 0;
1686 Me.speed.y = 0;
1687
1688 Me.pos.x = 0;
1689 Me.pos.y = 0;
1690 Me.pos.z = -1;
1691
1692 Me.mouse_move_target.x = -1;
1693 Me.mouse_move_target.y = -1;
1694 Me.mouse_move_target.z = -1;
1695
1696 Me.teleport_anchor.x = 0;
1697 Me.teleport_anchor.y = 0;
1698 Me.teleport_anchor.z = -1;
1699
1700 enemy_set_reference(&Me.current_enemy_target_n, &Me.current_enemy_target_addr, NULL);
1701
1702 Me.god_mode = FALSE;
1703
1704 Me.mouse_move_target_combo_action_type = NO_COMBO_ACTION_SET;
1705 Me.mouse_move_target_combo_action_parameter = -1;
1706
1707 Me.map_maker_is_present = FALSE;
1708
1709 Me.temperature = 0.0;
1710
1711 Me.health_recovery_rate = 0.2;
1712 Me.cooling_rate = 0.2;
1713
1714 Me.busy_time = 0;
1715 Me.busy_type = NONE;
1716
1717 Me.phase = 0;
1718 Me.angle = 180;
1719 Me.walk_cycle_phase = 0;
1720 Me.weapon_swing_time = -1;
1721 Me.MissionTimeElapsed = 0;
1722 Me.got_hit_time = -1;
1723
1724 Me.points_to_distribute = 0;
1725
1726 // reset statistics
1727 Me.meters_traveled = 0;
1728 for (i = 0; i < Number_Of_Droid_Types + 1; i++) {
1729 Me.destroyed_bots[i] = 0;
1730 Me.damage_dealt[i] = 0;
1731 Me.TakeoverSuccesses[i] = 0;
1732 Me.TakeoverFailures[i] = 0;
1733 }
1734
1735 for (i = 0; i < MAX_LEVELS; i++) {
1736 Me.HaveBeenToLevel[i] = FALSE;
1737 Me.time_since_last_visit_or_respawn[i] = -1;
1738 }
1739
1740 Me.Experience = 0;
1741 Me.exp_level = 0;
1742 Me.Gold = 0;
1743
1744 Me.readied_skill = 0;
1745 for (i = 0; i < number_of_skills; i++) {
1746 Me.skill_level[i] = SpellSkillMap[i].present_at_startup;
1747 }
1748
1749 GameConfig.spell_level_visible = 0;
1750
1751 Me.melee_weapon_skill = 0;
1752 Me.ranged_weapon_skill = 0;
1753 Me.spellcasting_skill = 0;
1754
1755 Me.running_power_bonus = 0;
1756
1757 for (i = 0; i < 10; i++) {
1758 Me.program_shortcuts[i] = -1;
1759 }
1760
1761 Me.paralyze_duration = 0;
1762 Me.invisible_duration = 0;
1763 Me.nmap_duration = 0;
1764
1765 Me.readied_skill = 0;
1766
1767 Me.quest_browser_changed = 0;
1768
1769 for (i = 0; i < MAX_ITEMS_IN_INVENTORY; i++) {
1770 init_item(&Me.Inventory[i]);
1771 }
1772 init_item(&Me.weapon_item);
1773 init_item(&Me.armour_item);
1774 init_item(&Me.shield_item);
1775 init_item(&Me.special_item);
1776 init_item(&Me.drive_item);
1777 item_held_in_hand = NULL;
1778
1779 clear_out_intermediate_points(&Me.pos, Me.next_intermediate_point, MAX_INTERMEDIATE_WAYPOINTS_FOR_TUX);
1780 Me.next_intermediate_point[0].x = -1;
1781 Me.next_intermediate_point[0].y = -1;
1782
1783 Me.TextToBeDisplayed = "";
1784 Me.TextVisibleTime = 0;
1785
1786 Me.base_physique = 20;
1787 Me.base_strength = 10;
1788 Me.base_dexterity = 15;
1789 Me.base_cooling = 25;
1790
1791 UpdateAllCharacterStats();
1792
1793 Me.energy = Me.maxenergy;
1794 Me.running_power = Me.max_running_power;
1795 }
1796 #undef _influ_c
1797