1 /*
2  *
3  *   Copyright (c) 1994, 2002, 2003, 2004 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 the functions managing the things one gets to see.
29  * That includes assembling of enemies, assembling the currently
30  * relevant porting of the map (the bricks I mean), drawing all visible
31  * elements like bullets, blasts, enemies or influencer in a not visible
32  * place in memory at first, and finally drawing them to the visible
33  * screen for the user.
34  */
35 
36 #define _view_c
37 
38 #include "system.h"
39 
40 #include "defs.h"
41 #include "struct.h"
42 #include "global.h"
43 #include "map.h"
44 #include "proto.h"
45 
46 #include "widgets/widgets.h"
47 
48 #include "lvledit/lvledit.h"
49 #include "lvledit/lvledit_display.h"
50 #include "lvledit/lvledit_tools.h"
51 
52 #include <zlib.h>
53 #include <math.h>
54 
55 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
56 int rmask = 0x00FF0000;
57 int gmask = 0x0000FF00;
58 int bmask = 0x000000FF;
59 int amask = 0xFF000000;
60 #else
61 int rmask = 0x0000FF00;
62 int gmask = 0x00FF0000;
63 int bmask = 0xFF000000;
64 int amask = 0x000000FF;
65 #endif
66 
67 static int old_current_level = -1;
68 
69 void PutRadialBlueSparks(float PosX, float PosY, float Radius, int SparkType, uint8_t active_directions[RADIAL_SPELL_DIRECTIONS], float age);
70 
71 struct blitting_list_element {
72 	int element_type;
73 	void *element_pointer;
74 	float norm;
75 	int list_position;
76 	int code_number;
77 };
78 
79 struct dynarray *blitting_list;
80 
81 enum {
82 	BLITTING_TYPE_NONE              = 0,
83 	BLITTING_TYPE_OBSTACLE          = 1,
84 	BLITTING_TYPE_VOLATILE_OBSTACLE = 2,
85 	BLITTING_TYPE_ENEMY             = 3,
86 	BLITTING_TYPE_TUX               = 4,
87 	BLITTING_TYPE_BULLET            = 5,
88 	BLITTING_TYPE_BLAST             = 6,
89 	BLITTING_TYPE_THROWN_ITEM       = 7,
90 	BLITTING_TYPE_MOVE_CURSOR       = 8
91 };
92 
93 LIST_HEAD(visible_level_list);
94 
95 /**
96  * This function displays an item at the current mouse cursor position.
97  * The typical crosshair cursor is assumed.  The item is centered around
98  * this crosshair cursor, depending on item size.
99  */
DisplayItemImageAtMouseCursor(int ItemImageCode)100 static void DisplayItemImageAtMouseCursor(int ItemImageCode)
101 {
102 	SDL_Rect TargetRect;
103 
104 	if (ItemImageCode == (-1)) {
105 		return;
106 	}
107 	// We define the target location for the item.  This will be the current
108 	// mouse cursor position of course, but -16 for the crosshair center,
109 	// which is somewhat (16) to the lower right of the cursor top left
110 	// corner.
111 	//
112 	// And then of course we also have to take into account the size of the
113 	// item, which is also not always the same.
114 	//
115 	TargetRect.x = GetMousePos_x() - ItemMap[ItemImageCode].inv_size.x * 16;
116 	TargetRect.y = GetMousePos_y() - ItemMap[ItemImageCode].inv_size.y * 16;
117 
118 	// Do not move an item out of the screen
119 	if (TargetRect.x < 0)
120 		TargetRect.x = 0;
121 
122 	if (TargetRect.y < 0)
123 		TargetRect.y = 0;
124 
125 	struct image *img = get_item_inventory_image(ItemImageCode);
126 	display_image_on_screen(img, TargetRect.x, TargetRect.y, IMAGE_NO_TRANSFO);
127 }
128 
129 /**
130  * Now it's time to blit all the spell effects.
131  */
PutMiscellaneousSpellEffects(void)132 void PutMiscellaneousSpellEffects(void)
133 {
134 	int i;
135 
136 	// Now we put all the spells in the list of active spells
137 	//
138 	for (i = 0; i < MAX_ACTIVE_SPELLS; i++) {
139 		if (AllActiveSpells[i].img_type == (-1))
140 			continue;
141 		PutRadialBlueSparks(AllActiveSpells[i].spell_center.x,
142 				    AllActiveSpells[i].spell_center.y,
143 				    AllActiveSpells[i].spell_radius, AllActiveSpells[i].img_type,
144 				    AllActiveSpells[i].active_directions, AllActiveSpells[i].spell_age);
145 	}
146 
147 };				// void PutMiscellaneousSpellEffects ( void )
148 
get_floor_boundaries(int mask,int * LineStart,int * LineEnd,int * ColStart,int * ColEnd)149 void get_floor_boundaries(int mask, int *LineStart, int *LineEnd, int *ColStart, int *ColEnd)
150 {
151 	float zf = lvledit_zoomfact();
152 	if (mask & ZOOM_OUT) {
153 		*LineStart = floor(Me.pos.y - (float)(FLOOR_TILES_VISIBLE_AROUND_TUX * zf));
154 		*LineEnd = floor(Me.pos.y + (float)(FLOOR_TILES_VISIBLE_AROUND_TUX * zf));
155 		*ColStart = floor(Me.pos.x - (float)(FLOOR_TILES_VISIBLE_AROUND_TUX * zf));
156 		*ColEnd = floor(Me.pos.x + (float)(FLOOR_TILES_VISIBLE_AROUND_TUX * zf));
157 	} else {
158 		*LineStart = floor(translate_pixel_to_map_location(UserCenter_x, -UserCenter_y, FALSE));
159 		*LineEnd =
160 		    floor(translate_pixel_to_map_location
161 			  (-UserCenter_x - iso_floor_tile_width + 1, UserCenter_y + iso_floor_tile_height - 1, FALSE));
162 		*ColStart = floor(translate_pixel_to_map_location(-UserCenter_x, -UserCenter_y, TRUE));
163 		*ColEnd =
164 		    floor(translate_pixel_to_map_location
165 			  (UserCenter_x + iso_floor_tile_width - 1, UserCenter_y + iso_floor_tile_height - 1, TRUE));
166 	}
167 }
168 
object_vtx_color(void * data,float * r,float * g,float * b)169 void object_vtx_color(void *data, float *r, float *g, float *b)
170 {
171 	if (element_in_selection(data)) {
172 		*r = ((SDL_GetTicks() >> 7) % 3) / 2.0;
173 		*g = (((SDL_GetTicks() >> 7) + 1) % 3) / 2.0;
174 		*b = (((SDL_GetTicks() >> 7) + 2) % 3) / 2.0;
175 	} else {
176 		*r = 1.0;
177 		*g = 1.0;
178 		*b = 1.0;
179 	}
180 }
181 
182 /**
183  * This function displays floor on the screen.
184  */
show_floor(int mask)185 static void show_floor(int mask)
186 {
187 	int LineStart, LineEnd, ColStart, ColEnd, line, col, MapBrick;
188 	int layer_start, layer_end, layer;
189 	float r, g, b;
190 	float zf = ((mask & ZOOM_OUT) ? lvledit_zoomfact_inv() : 1.0);
191 	level *lvl = curShip.AllLevels[Me.pos.z];
192 
193 	get_floor_boundaries(mask, &LineStart, &LineEnd, &ColStart, &ColEnd);
194 
195 	layer_start = 0;
196 	layer_end = MAX_FLOOR_LAYERS;
197 	// Draw only the current floor layer?
198 	if (game_status == INSIDE_LVLEDITOR) {
199 		if (!GameConfig.show_all_floor_layers) {
200 			layer_start = current_floor_layer;
201 			layer_end = layer_start + 1;
202 		}
203 	}
204 
205 	start_image_batch();
206 
207 	for (line = LineStart; line < LineEnd; line++) {
208 		for (col = ColStart; col < ColEnd; col++) {
209 			for (layer = layer_start; layer < layer_end; layer++) {
210 				// Retrieve floor tile
211 				MapBrick = get_map_brick(lvl, col, line, layer);
212 				if (MapBrick == ISO_FLOOR_EMPTY)
213 					continue;
214 
215 				// Compute colorization (in case the floor tile is currently selected in the leveleditor)
216 				if (pos_inside_level(col, line, lvl)) {
217 					object_vtx_color(&lvl->map[line][col], &r, &g, &b);
218 				} else {
219 					r = g = b = 1.0;
220 				}
221 
222 				struct image *img = get_floor_tile_image(MapBrick);
223 				display_image_on_map(img, (float)col + 0.5, (float)line + 0.5, IMAGE_SCALE_RGB_TRANSFO(zf, r, g, b));
224 			}
225 		}
226 	}
227 
228 	end_image_batch();
229 }
230 
blit_leveleditor_point(int x,int y)231 void blit_leveleditor_point(int x, int y)
232 {
233 	if (use_open_gl) {
234 #ifdef HAVE_LIBGL
235 		glDisable(GL_TEXTURE_2D);
236 		glEnable(GL_POINT_SMOOTH);
237 		glPointSize(5.0);
238 		glBegin(GL_POINTS);
239 		glColor3f(1.0, 0.0, 0.0);
240 		glVertex2i(x, y);
241 		glColor3f(1.0, 1.0, 1.0);
242 		glEnd();
243 		glDisable(GL_POINT_SMOOTH);
244 		glEnable(GL_TEXTURE_2D);
245 		glPointSize(1.0);
246 #endif
247 	} else {
248 		SDL_Rect rect = { .x = x, .y = y, .w = 4, .h = 4 };
249 		sdl_draw_rectangle(&rect, 255, 0, 0, 255);
250 	}
251 }
252 
253 /**
254  * More for debugging purposes than for real gameplay, we add some
255  * function to illustrate the collision rectangle of a certain obstacle
256  * on the floor via a bright ugly distorted rectangular shape.
257  */
blit_obstacle_collision_rectangle(obstacle * our_obstacle)258 void blit_obstacle_collision_rectangle(obstacle * our_obstacle)
259 {
260 	int r1, r2, r3, r4, c1, c2, c3, c4;
261 	float x1, y1, x2, y2;
262 	gps vpos;
263 
264 	if (!draw_collision_rectangles)
265 		return;
266 
267 	update_virtual_position(&vpos, &our_obstacle->pos, Me.pos.z);
268 
269 	// If there is no collision rectangle to draw, we are done
270 	obstacle_spec *spec = get_obstacle_spec(our_obstacle->type);
271 	if (spec->block_area_type == COLLISION_TYPE_NONE)
272 		return;
273 
274 	x1 = vpos.x + spec->left_border;
275 	y1 = vpos.y + spec->upper_border;
276 	x2 = vpos.x + spec->right_border;
277 	y2 = vpos.y + spec->lower_border;
278 
279 	translate_map_point_to_screen_pixel(x1, y1, &r1, &c1);
280 	translate_map_point_to_screen_pixel(x1, y2, &r2, &c2);
281 	translate_map_point_to_screen_pixel(x2, y2, &r3, &c3);
282 	translate_map_point_to_screen_pixel(x2, y1, &r4, &c4);
283 
284 	short x[4] = { r1, r2, r3, r4 };
285 	short y[4] = { c1, c2, c3, c4 };
286 
287 	// Now we draw the collision rectangle.  We use the same parameters
288 	// of the obstacle spec, that are also used for the collision checks.
289 	draw_quad(x, y, 15, 238, 170, 255);
290 }
291 
blit_one_obstacle(obstacle * o,int highlight,int zoom,int opacity)292 void blit_one_obstacle(obstacle *o, int highlight, int zoom, int opacity)
293 {
294 #define HIGHLIGHT 1
295 #define NOHIGHLIGHT 0
296 
297 	if (opacity <= 0)
298 		return;
299 
300 	float zf = zoom ? lvledit_zoomfact_inv() : 1.0;
301 	level *lvl = curShip.AllLevels[Me.pos.z];
302 	float r = 1.0;
303 	float g = 1.0;
304 	float b = 1.0;
305 	float a = min((float)opacity / 255.0, 1.0);
306 	gps pos;
307 
308 	if ((o->type <= -1) || (o->type >= obstacle_map.size)) {
309 		error_message(__FUNCTION__, "The obstacle type %d that was given is incorrect. Resetting type to 1.", PLEASE_INFORM, o->type);
310 		o->type = 1;
311 	}
312 
313 	// Maybe the children friendly version is desired.  Then the blood on the floor
314 	// will not be blitted to the screen.
315 	if ((!GameConfig.show_blood) && (
316 			((o->type >= ISO_BLOOD_1) && (o->type <= ISO_BLOOD_8)) ||
317 			((o->type >= ISO_OIL_STAINS_1) && (o->type <= ISO_OIL_STAINS_8)) ))
318 		return;
319 
320 	update_virtual_position(&pos, &o->pos, lvl->levelnum);
321 
322 	// Compute colorization (in case the obstacle is currently selected in the leveleditor)
323 	if (pos_inside_level(pos.x, pos.y, lvl)) {
324 		object_vtx_color(o, &r, &g, &b);
325 	}
326 
327 	if (GameConfig.transparency) {
328 		if ((pos.x > Me.pos.x - 1.0) && (pos.y > Me.pos.y - 1.0)
329 			&& (pos.x < Me.pos.x + 1.5) && (pos.y < Me.pos.y + 1.5)) {
330 				if (game_status == INSIDE_LVLEDITOR) {
331 					a *= 0.5;
332 				} else if (get_obstacle_spec(o->type)->transparent == TRANSPARENCY_FOR_WALLS) {
333 					a *= 0.5;
334 				}
335 		}
336 	}
337 
338 	struct image *img = get_obstacle_image(o->type, o->frame_index);
339 	display_image_on_map(img, pos.x, pos.y, set_image_transformation(zf, zf, r, g, b, a, highlight));
340 }
341 
342 /**
343  * Several different things must be inserted into the blitting list.
344  * Therefore this function is an abstraction, that will insert a generic
345  * object into the blitting list.
346  */
insert_new_element_into_blitting_list(float new_element_norm,int new_element_type,void * new_element_pointer,int code_number)347 static void insert_new_element_into_blitting_list(float new_element_norm, int new_element_type, void *new_element_pointer, int code_number)
348 {
349 	struct blitting_list_element elt;
350 
351 	elt.norm = new_element_norm;
352 	elt.list_position = blitting_list->size;
353 	elt.element_type = new_element_type;
354 	elt.element_pointer = new_element_pointer;
355 	elt.code_number = code_number;
356 
357 	dynarray_add(blitting_list, &elt, sizeof(struct blitting_list_element));
358 }
359 
insert_one_obstacle_into_blitting_list(int col,int line,int z,struct obstacle * the_obstacle,int kind,int idx,int tstamp)360 static void insert_one_obstacle_into_blitting_list(int col, int line, int z, struct obstacle *the_obstacle, int kind, int idx, int tstamp)
361 {
362 	gps virtpos, reference;
363 
364 	if (the_obstacle->timestamp == tstamp) {
365 		return;
366 	}
367 
368 	reference.x = the_obstacle->pos.x;
369 	reference.y = the_obstacle->pos.y;
370 	reference.z = z;
371 
372 	update_virtual_position(&virtpos, &reference, Me.pos.z);
373 
374 	// Could not find virtual position? Give up drawing.
375 	if (virtpos.z == -1)
376 		return;
377 
378 	// Check that the virpos is actually on the tile
379 	if (floorf(virtpos.x) != col)
380 		return;
381 
382 	if (floorf(virtpos.y) != line)
383 		return;
384 
385 	the_obstacle->timestamp = tstamp;
386 
387 	insert_new_element_into_blitting_list(virtpos.x + virtpos.y, kind, the_obstacle, idx);
388 }
389 
390 /**
391  * In order for the obstacles to be blitted, they must first be inserted
392  * into the correctly ordered list of objects to be blitted this frame.
393  */
insert_obstacles_into_blitting_list(int mask)394 void insert_obstacles_into_blitting_list(int mask)
395 {
396 	int i;
397 	level *obstacle_level;
398 	int LineStart, LineEnd, ColStart, ColEnd, line, col;
399 	int px, py;
400 	int tstamp = next_glue_timestamp();
401 
402 	obstacle *OurObstacle;
403 	gps tile_vpos, tile_rpos;
404 
405 	get_floor_boundaries(mask, &LineStart, &LineEnd, &ColStart, &ColEnd);
406 
407 	tile_vpos.z = Me.pos.z;
408 
409 	for (line = LineStart; line < LineEnd; line++) {
410 		tile_vpos.y = line;
411 
412 		for (col = ColStart; col < ColEnd; col++) {
413 			tile_vpos.x = col;
414 			if (!resolve_virtual_position(&tile_rpos, &tile_vpos)) {
415 				continue;
416 			}
417 			px = (int)rintf(tile_rpos.x);
418 			py = (int)rintf(tile_rpos.y);
419 			obstacle_level = curShip.AllLevels[tile_rpos.z];
420 
421 			for (i = 0; i < obstacle_level->map[py][px].glued_obstacles.size; i++) {
422 					// Now we have to insert this obstacle.  We do this of course respecting
423 					// the blitting order, as always...
424 					int idx = ((int *)obstacle_level->map[py][px].glued_obstacles.arr)[i];
425 					OurObstacle = &obstacle_level->obstacle_list[idx];
426 
427 					insert_one_obstacle_into_blitting_list(col, line, tile_rpos.z, OurObstacle, BLITTING_TYPE_OBSTACLE, idx, tstamp);
428 			}
429 
430 			struct volatile_obstacle *volatile_obs, *next;
431 			list_for_each_entry_safe(volatile_obs, next, &obstacle_level->map[py][px].volatile_obstacles, volatile_list) {
432 				if (volatile_obs->obstacle.timestamp == tstamp)
433 					continue;
434 				int opacity = 255;
435 				if (game_status == INSIDE_GAME) {
436 					volatile_obs->vanish_timeout -= Frame_Time();
437 					if (volatile_obs->vanish_timeout <= 0) {
438 						list_del(&volatile_obs->volatile_list);
439 						free(volatile_obs);
440 						continue;
441 					}
442 					float vanish_duration = get_obstacle_spec(volatile_obs->obstacle.type)->vanish_duration;
443 					if (volatile_obs->vanish_timeout < vanish_duration) {
444 						opacity = (int)((volatile_obs->vanish_timeout / vanish_duration) * 255.0);
445 					}
446 				}
447 				insert_one_obstacle_into_blitting_list(col, line, tile_rpos.z, &volatile_obs->obstacle, BLITTING_TYPE_VOLATILE_OBSTACLE, opacity, tstamp);
448 			}
449 		}
450 	}
451 }
452 
453 /**
454  *
455  *
456  */
insert_tux_into_blitting_list(void)457 static void insert_tux_into_blitting_list(void)
458 {
459 	float tux_norm = Me.pos.x + Me.pos.y;
460 
461 	insert_new_element_into_blitting_list(tux_norm, BLITTING_TYPE_TUX, NULL, -1);
462 
463 }
464 
465 /**
466  *
467  *
468  */
insert_one_enemy_into_blitting_list(enemy * erot)469 void insert_one_enemy_into_blitting_list(enemy * erot)
470 {
471 	float enemy_norm;
472 
473 	enemy_norm = erot->virt_pos.x + erot->virt_pos.y;
474 
475 	insert_new_element_into_blitting_list(enemy_norm, BLITTING_TYPE_ENEMY, erot, 0);
476 
477 };				// void insert_one_enemy_into_blitting_list ( int enemy_num )
478 
479 /**
480  *
481  *
482  */
insert_one_thrown_item_into_blitting_list(item * it,int item_num)483 static void insert_one_thrown_item_into_blitting_list(item *it, int item_num)
484 {
485 	float norm = it->virt_pos.x + it->virt_pos.y;
486 
487 	insert_new_element_into_blitting_list(norm, BLITTING_TYPE_THROWN_ITEM, it, item_num);
488 }
489 
490 /**
491  *
492  *
493  */
insert_one_bullet_into_blitting_list(float norm,int bullet_num)494 static void insert_one_bullet_into_blitting_list(float norm, int bullet_num)
495 {
496 	insert_new_element_into_blitting_list(norm, BLITTING_TYPE_BULLET, &AllBullets[bullet_num], bullet_num);
497 }
498 
499 /**
500  *
501  *
502  */
insert_one_blast_into_blitting_list(int blast_num)503 void insert_one_blast_into_blitting_list(int blast_num)
504 {
505 	gps virtpos;
506 
507 	// Due to the use of a painter algorithm, we need to sort the objects depending of their
508 	// isometric distance on the current level.
509 	// We thus have to get the blast's position on the current level.
510 	update_virtual_position(&virtpos, &AllBlasts[blast_num].pos, Me.pos.z);
511 
512 	// Could not find virtual position? Give up drawing.
513 	if (virtpos.z == -1)
514 		return;
515 
516 	insert_new_element_into_blitting_list(virtpos.x + virtpos.y, BLITTING_TYPE_BLAST, &(AllBlasts[blast_num]), blast_num);
517 }				// void insert_one_blast_into_blitting_list ( int enemy_num )
518 
519 /**
520  *
521  *
522  */
insert_move_cursor_into_blitting_list()523 void insert_move_cursor_into_blitting_list()
524 {
525 	float norm;
526 
527 	if (game_status == INSIDE_LVLEDITOR) {
528 		// Do not show move cursors inside the editor.
529 		return;
530 	}
531 
532 	norm = Me.mouse_move_target.x + Me.mouse_move_target.y;
533 
534 	insert_new_element_into_blitting_list(norm, BLITTING_TYPE_MOVE_CURSOR, NULL, 0);
535 };
536 
537 /**
538  * We need to display bots, objects, bullets... that are on the current level or on one of the
539  * levels glued to this one.
540  */
level_is_visible(int level_num)541 int level_is_visible(int level_num)
542 {
543 	// Current level is for sure visible
544 
545 	if (level_num == Me.pos.z)
546 		return TRUE;
547 
548 	struct visible_level *l, *n;
549 	BROWSE_VISIBLE_LEVELS(l, n) {
550 		if (l->lvl_pointer->levelnum == level_num)
551 			return TRUE;
552 	}
553 
554 	return FALSE;
555 
556 }				// int level_is_visible ( int level_num )
557 
558 /**
559  * Construct a linked list of visible levels.
560  * Also compute the distance between Tux and each level boundaries, and fill
561  * the lists of animated obstacles.
562  */
get_visible_levels()563 void get_visible_levels()
564 {
565 	struct visible_level *e, *n;
566 
567 	// Invalidate all entries in the visible_level list
568 
569 	list_for_each_entry(e, &visible_level_list, node) {
570 		e->valid = FALSE;
571 	}
572 
573 	// Find the 4 visible levels
574 	//
575 	// Those 4 levels form a square (eventually a degenerated one), one corner
576 	// of the square being the current level.
577 	// The 2 corners are initialized to be on the current level (idx = 1), and
578 	// we will extend one of them, depending on Tux's position.
579 	// (see gps_transform_map_init() main comment for an explanation about neighbor index)
580 
581 	int left_idx = 1, right_idx = 1;
582 	int top_idx = 1, bottom_idx = 1;
583 	float left_or_right_distance = 0.0;	// distance to the left or right neighbor
584 	float top_or_bottom_distance = 0.0;	// distance to the top or bottom neighbor
585 
586 	if (Me.pos.x < FLOOR_TILES_VISIBLE_AROUND_TUX) {
587 		// left neighbors are potentially visible
588 		left_idx = 0;
589 		left_or_right_distance = Me.pos.x;
590 	} else if (Me.pos.x >= CURLEVEL()->xlen - FLOOR_TILES_VISIBLE_AROUND_TUX) {
591 		// right neighbors are potentially visible
592 		right_idx = 2;
593 		left_or_right_distance = CURLEVEL()->xlen - Me.pos.x;
594 	}
595 
596 	if (Me.pos.y < FLOOR_TILES_VISIBLE_AROUND_TUX) {
597 		// top neighbors are potentially visible
598 		top_idx = 0;
599 		top_or_bottom_distance = Me.pos.y;
600 	} else if (Me.pos.y >= CURLEVEL()->ylen - FLOOR_TILES_VISIBLE_AROUND_TUX) {
601 		// bottom neighbors are potentially visible
602 		bottom_idx = 2;
603 		top_or_bottom_distance = CURLEVEL()->ylen - Me.pos.y;
604 	}
605 
606 	// Add or re-validate entries in the visible_level list
607 
608 	int i, j;
609 	float latitude;		// distance, along Y axis, between Tux and the current neighbor
610 	float longitude;	// distance, along X axis, between Tux and the current neighbor
611 
612 	for (j = top_idx; j <= bottom_idx; j++) {
613 		// if j==1, then current neighbor is at the same 'latitude' than Tux's level,
614 		// so latitude = 0.0
615 		latitude = (j == 1) ? 0.0 : top_or_bottom_distance;
616 
617 		for (i = left_idx; i <= right_idx; i++) {
618 			// if i==1, then current neighbor is at the same 'longitude' than Tux's level,
619 			// so longitude = 0.0
620 			longitude = (i == 1) ? 0.0 : left_or_right_distance;
621 
622 			if (level_neighbors_map[Me.pos.z][j][i]) {
623 				// if there is already an entry in the visible_level list for
624 				// this level, update the entry and re-validate it
625 				int in_list = FALSE;
626 				list_for_each_entry(e, &visible_level_list, node) {
627 					if (e->lvl_pointer->levelnum == level_neighbors_map[Me.pos.z][j][i]->lvl_idx) {
628 						e->valid = TRUE;
629 						e->boundary_squared_dist = longitude * longitude + latitude * latitude;
630 						in_list = TRUE;
631 						break;
632 					}
633 				}
634 				// if there was no corresponding entry, create a new one
635 				if (!in_list) {
636 					e = MyMalloc(sizeof(struct visible_level));
637 					e->valid = TRUE;
638 					e->lvl_pointer = curShip.AllLevels[level_neighbors_map[Me.pos.z][j][i]->lvl_idx];
639 					e->boundary_squared_dist = longitude * longitude + latitude * latitude;
640 					e->animated_obstacles_dirty_flag = TRUE;
641 					INIT_LIST_HEAD(&e->animated_obstacles_list);
642 					list_add_tail(&e->node, &visible_level_list);
643 				}
644 			}
645 		}
646 	}
647 
648 	// If the current level changed, remove useless invalid entries.
649 	// An entry is useless if the associated level is not a neighbor of the
650 	// current level. To find if two levels are connected, we look at the content
651 	// of 'gps_transform_matrix' (there is no gps transformation between two
652 	// unconnected levels).
653 
654 	struct neighbor_data_cell *transform_data;
655 
656 	if (old_current_level != Me.pos.z) {
657 		old_current_level = Me.pos.z;
658 
659 		list_for_each_entry_safe(e, n, &visible_level_list, node) {
660 			if (e->valid)
661 				continue;
662 			transform_data = &gps_transform_matrix[Me.pos.z][e->lvl_pointer->levelnum];
663 			if (!transform_data->valid) {
664 				// useless entry: removing it from the linked list
665 				clear_animated_obstacle_list(e);
666 				list_del(&e->node);
667 				free(e);
668 			}
669 		}
670 	}
671 }
672 
673 /*
674  * This function resets the visible levels list, as well as all animated
675  * obstacle lists.
676  */
reset_visible_levels()677 void reset_visible_levels()
678 {
679 	struct visible_level *e, *n;
680 
681 	// Clear current list
682 	list_for_each_entry_safe(e, n, &visible_level_list, node) {
683 		clear_animated_obstacle_list(e);
684 		list_del(&e->node);
685 		free(e);
686 	}
687 
688 	old_current_level = -1;
689 }
690 
691 /*
692  * Initialization of the 2 arrays used to accelerate gps transformations.
693  *
694  * gps_transform_matrix[lvl1][lvl2] is a matrix used in update_virtual_position().
695  * It contains the data needed to transform a gps position defined relatively to one level
696  * into a gps position defined relatively to an other level.
697  * gps_transform_matrix[lvl1][lvl2] is used for a transformation from 'lvl1' to 'lvl2'.
698  *
699  * level_neighbors_map[lvl][idY][idX] is used to retrieve all the neighbors (and the corresponding
700  * transformation data) of one given level. It is used in resolve_virtual_position().
701  * The (idY, idX) pair defines one of the nine neighbors, using the following schema:
702  *              N
703  *    (0,0) | (0,1) | (0,2)
704  *   -------+-------+-------
705  *  W (1,0) |       | (1,2) E
706  *   -------+-------+-------
707  *    (2,0) | (2,1) | (2,2)
708  *              S
709  */
710 
711 // Some helper macros
712 #define NEIGHBOR_TRANSFORM_NW(lvl)   level_neighbors_map[lvl][0][0]
713 #define NEIGHBOR_TRANSFORM_N(lvl)    level_neighbors_map[lvl][0][1]
714 #define NEIGHBOR_TRANSFORM_NE(lvl)   level_neighbors_map[lvl][0][2]
715 #define NEIGHBOR_TRANSFORM_W(lvl)    level_neighbors_map[lvl][1][0]
716 #define NEIGHBOR_TRANSFORM_SELF(lvl) level_neighbors_map[lvl][1][1]
717 #define NEIGHBOR_TRANSFORM_E(lvl)    level_neighbors_map[lvl][1][2]
718 #define NEIGHBOR_TRANSFORM_SW(lvl)   level_neighbors_map[lvl][2][0]
719 #define NEIGHBOR_TRANSFORM_S(lvl)    level_neighbors_map[lvl][2][1]
720 #define NEIGHBOR_TRANSFORM_SE(lvl)   level_neighbors_map[lvl][2][2]
721 
gps_transform_map_init()722 void gps_transform_map_init()
723 {
724 	int x, y;
725 	int lvl_idx, ngb_idx, diag_idx;
726 	struct level *lvl;
727 
728 	if (!gps_transform_map_dirty_flag)
729 		return;
730 
731 	// Reset maps
732 
733 	for (lvl_idx = 0; lvl_idx < MAX_LEVELS; lvl_idx++) {
734 		for (ngb_idx = 0; ngb_idx < MAX_LEVELS; ngb_idx++) {
735 			gps_transform_matrix[lvl_idx][ngb_idx].delta_x = 0;
736 			gps_transform_matrix[lvl_idx][ngb_idx].delta_y = 0;
737 			gps_transform_matrix[lvl_idx][ngb_idx].lvl_idx = -1;
738 			gps_transform_matrix[lvl_idx][ngb_idx].valid = FALSE;
739 		}
740 
741 		for (y = 0; y < 3; y++) {
742 			for (x = 0; x < 3; x++) {
743 				level_neighbors_map[lvl_idx][y][x] = NULL;
744 			}
745 		}
746 	}
747 
748 	// Scan direct neighbors and fill maps
749 
750 	BROWSE_LEVELS(lvl) {
751 		int lvlnum = lvl->levelnum;
752 		// Self
753 		gps_transform_matrix[lvlnum][lvlnum].delta_x = 0;
754 		gps_transform_matrix[lvlnum][lvlnum].delta_y = 0;
755 		gps_transform_matrix[lvlnum][lvlnum].lvl_idx = lvlnum;
756 		gps_transform_matrix[lvlnum][lvlnum].valid = TRUE;
757 
758 		NEIGHBOR_TRANSFORM_SELF(lvlnum) = &gps_transform_matrix[lvlnum][lvlnum];
759 
760 		// North
761 		ngb_idx = lvl->jump_target_north;
762 		if (ngb_idx != -1) {
763 			gps_transform_matrix[lvlnum][ngb_idx].delta_x = 0;
764 			gps_transform_matrix[lvlnum][ngb_idx].delta_y = +curShip.AllLevels[ngb_idx]->ylen;
765 			gps_transform_matrix[lvlnum][ngb_idx].lvl_idx = ngb_idx;
766 			gps_transform_matrix[lvlnum][ngb_idx].valid = TRUE;
767 
768 			NEIGHBOR_TRANSFORM_N(lvlnum) = &gps_transform_matrix[lvlnum][ngb_idx];
769 		}
770 		// South
771 		ngb_idx = lvl->jump_target_south;
772 		if (ngb_idx != -1) {
773 			gps_transform_matrix[lvlnum][ngb_idx].delta_x = 0;
774 			gps_transform_matrix[lvlnum][ngb_idx].delta_y = -lvl->ylen;
775 			gps_transform_matrix[lvlnum][ngb_idx].lvl_idx = ngb_idx;
776 			gps_transform_matrix[lvlnum][ngb_idx].valid = TRUE;
777 
778 			NEIGHBOR_TRANSFORM_S(lvlnum) = &gps_transform_matrix[lvlnum][ngb_idx];
779 		}
780 		// East
781 		ngb_idx = lvl->jump_target_east;
782 		if (ngb_idx != -1) {
783 			gps_transform_matrix[lvlnum][ngb_idx].delta_x = -lvl->xlen;
784 			gps_transform_matrix[lvlnum][ngb_idx].delta_y = 0;
785 			gps_transform_matrix[lvlnum][ngb_idx].lvl_idx = ngb_idx;
786 			gps_transform_matrix[lvlnum][ngb_idx].valid = TRUE;
787 
788 			NEIGHBOR_TRANSFORM_E(lvlnum) = &gps_transform_matrix[lvlnum][ngb_idx];
789 		}
790 		// West
791 		ngb_idx = lvl->jump_target_west;
792 		if (ngb_idx != -1) {
793 			gps_transform_matrix[lvlnum][ngb_idx].delta_x = +curShip.AllLevels[ngb_idx]->xlen;
794 			gps_transform_matrix[lvlnum][ngb_idx].delta_y = 0;
795 			gps_transform_matrix[lvlnum][ngb_idx].lvl_idx = ngb_idx;
796 			gps_transform_matrix[lvlnum][ngb_idx].valid = TRUE;
797 
798 			NEIGHBOR_TRANSFORM_W(lvlnum) = &gps_transform_matrix[lvlnum][ngb_idx];
799 		}
800 	}
801 
802 	// Scan diagonal levels and fill maps
803 	//
804 	// Now that we know the direct neighbors, we can use that knowledge to ease
805 	// the finding of the diagonal levels.
806 	// The main difficulty is that the north-west neighbor, for example, can be
807 	// the west neighbor of the north neighbor or the north neighbor of the west neighbor.
808 	// On a 4-connected corner (4 levels around a corner), the two cases are equivalent.
809 	// However, on a 3-connected corner, one of the two cases is invalid. We thus have to
810 	// try the 2 ways to reach each diagonal levels.
811 	//
812 
813 	for (lvl_idx = 0; lvl_idx < MAX_LEVELS; lvl_idx++) {
814 		// North-West neighbor.
815 		if (NEIGHBOR_TRANSFORM_N(lvl_idx) && NEIGHBOR_TRANSFORM_W(NEIGHBOR_ID_N(lvl_idx))) {
816 			diag_idx = NEIGHBOR_ID_W(NEIGHBOR_ID_N(lvl_idx));
817 			gps_transform_matrix[lvl_idx][diag_idx].delta_x =
818 			    NEIGHBOR_TRANSFORM_N(lvl_idx)->delta_x + NEIGHBOR_TRANSFORM_W(NEIGHBOR_ID_N(lvl_idx))->delta_x;
819 			gps_transform_matrix[lvl_idx][diag_idx].delta_y =
820 			    NEIGHBOR_TRANSFORM_N(lvl_idx)->delta_y + NEIGHBOR_TRANSFORM_W(NEIGHBOR_ID_N(lvl_idx))->delta_y;
821 			gps_transform_matrix[lvl_idx][diag_idx].lvl_idx = diag_idx;
822 			gps_transform_matrix[lvl_idx][diag_idx].valid = TRUE;
823 
824 			NEIGHBOR_TRANSFORM_NW(lvl_idx) = &gps_transform_matrix[lvl_idx][diag_idx];
825 		}
826 		// North-East neighbor.
827 		if (NEIGHBOR_TRANSFORM_N(lvl_idx) && NEIGHBOR_TRANSFORM_E(NEIGHBOR_ID_N(lvl_idx))) {
828 			diag_idx = NEIGHBOR_ID_E(NEIGHBOR_ID_N(lvl_idx));
829 			gps_transform_matrix[lvl_idx][diag_idx].delta_x =
830 			    NEIGHBOR_TRANSFORM_N(lvl_idx)->delta_x + NEIGHBOR_TRANSFORM_E(NEIGHBOR_ID_N(lvl_idx))->delta_x;
831 			gps_transform_matrix[lvl_idx][diag_idx].delta_y =
832 			    NEIGHBOR_TRANSFORM_N(lvl_idx)->delta_y + NEIGHBOR_TRANSFORM_E(NEIGHBOR_ID_N(lvl_idx))->delta_y;
833 			gps_transform_matrix[lvl_idx][diag_idx].lvl_idx = diag_idx;
834 			gps_transform_matrix[lvl_idx][diag_idx].valid = TRUE;
835 
836 			NEIGHBOR_TRANSFORM_NE(lvl_idx) = &gps_transform_matrix[lvl_idx][diag_idx];
837 		}
838 		// South-West neighbor.
839 		if (NEIGHBOR_TRANSFORM_S(lvl_idx) && NEIGHBOR_TRANSFORM_W(NEIGHBOR_ID_S(lvl_idx))) {
840 			diag_idx = NEIGHBOR_ID_W(NEIGHBOR_ID_S(lvl_idx));
841 			gps_transform_matrix[lvl_idx][diag_idx].delta_x =
842 			    NEIGHBOR_TRANSFORM_S(lvl_idx)->delta_x + NEIGHBOR_TRANSFORM_W(NEIGHBOR_ID_S(lvl_idx))->delta_x;
843 			gps_transform_matrix[lvl_idx][diag_idx].delta_y =
844 			    NEIGHBOR_TRANSFORM_S(lvl_idx)->delta_y + NEIGHBOR_TRANSFORM_W(NEIGHBOR_ID_S(lvl_idx))->delta_y;
845 			gps_transform_matrix[lvl_idx][diag_idx].lvl_idx = diag_idx;
846 			gps_transform_matrix[lvl_idx][diag_idx].valid = TRUE;
847 
848 			NEIGHBOR_TRANSFORM_SW(lvl_idx) = &gps_transform_matrix[lvl_idx][diag_idx];
849 		}
850 		// South-East neighbor.
851 		if (NEIGHBOR_TRANSFORM_S(lvl_idx) && NEIGHBOR_TRANSFORM_E(NEIGHBOR_ID_S(lvl_idx))) {
852 			diag_idx = NEIGHBOR_ID_E(NEIGHBOR_ID_S(lvl_idx));
853 			gps_transform_matrix[lvl_idx][diag_idx].delta_x =
854 			    NEIGHBOR_TRANSFORM_S(lvl_idx)->delta_x + NEIGHBOR_TRANSFORM_E(NEIGHBOR_ID_S(lvl_idx))->delta_x;
855 			gps_transform_matrix[lvl_idx][diag_idx].delta_y =
856 			    NEIGHBOR_TRANSFORM_S(lvl_idx)->delta_y + NEIGHBOR_TRANSFORM_E(NEIGHBOR_ID_S(lvl_idx))->delta_y;
857 			gps_transform_matrix[lvl_idx][diag_idx].lvl_idx = diag_idx;
858 			gps_transform_matrix[lvl_idx][diag_idx].valid = TRUE;
859 
860 			NEIGHBOR_TRANSFORM_SE(lvl_idx) = &gps_transform_matrix[lvl_idx][diag_idx];
861 		}
862 		// West-North neighbor, if needed (i.e. if north-west neighbor was not found).
863 		if (!NEIGHBOR_TRANSFORM_NW(lvl_idx)) {
864 			if (NEIGHBOR_TRANSFORM_W(lvl_idx) && NEIGHBOR_TRANSFORM_N(NEIGHBOR_ID_W(lvl_idx))) {
865 				diag_idx = NEIGHBOR_ID_N(NEIGHBOR_ID_W(lvl_idx));
866 				gps_transform_matrix[lvl_idx][diag_idx].delta_x =
867 				    NEIGHBOR_TRANSFORM_W(lvl_idx)->delta_x + NEIGHBOR_TRANSFORM_N(NEIGHBOR_ID_W(lvl_idx))->delta_x;
868 				gps_transform_matrix[lvl_idx][diag_idx].delta_y =
869 				    NEIGHBOR_TRANSFORM_W(lvl_idx)->delta_y + NEIGHBOR_TRANSFORM_N(NEIGHBOR_ID_W(lvl_idx))->delta_y;
870 				gps_transform_matrix[lvl_idx][diag_idx].lvl_idx = diag_idx;
871 				gps_transform_matrix[lvl_idx][diag_idx].valid = TRUE;
872 
873 				NEIGHBOR_TRANSFORM_NW(lvl_idx) = &gps_transform_matrix[lvl_idx][diag_idx];
874 			}
875 		}
876 		// West-South neighbor, if needed (i.e. if south-west neighbor was not found).
877 		if (!NEIGHBOR_TRANSFORM_SW(lvl_idx)) {
878 			if (NEIGHBOR_TRANSFORM_W(lvl_idx) && NEIGHBOR_TRANSFORM_S(NEIGHBOR_ID_W(lvl_idx))) {
879 				diag_idx = NEIGHBOR_ID_S(NEIGHBOR_ID_W(lvl_idx));
880 				gps_transform_matrix[lvl_idx][diag_idx].delta_x =
881 				    NEIGHBOR_TRANSFORM_W(lvl_idx)->delta_x + NEIGHBOR_TRANSFORM_S(NEIGHBOR_ID_W(lvl_idx))->delta_x;
882 				gps_transform_matrix[lvl_idx][diag_idx].delta_y =
883 				    NEIGHBOR_TRANSFORM_W(lvl_idx)->delta_y + NEIGHBOR_TRANSFORM_S(NEIGHBOR_ID_W(lvl_idx))->delta_y;
884 				gps_transform_matrix[lvl_idx][diag_idx].lvl_idx = diag_idx;
885 				gps_transform_matrix[lvl_idx][diag_idx].valid = TRUE;
886 
887 				NEIGHBOR_TRANSFORM_SW(lvl_idx) = &gps_transform_matrix[lvl_idx][diag_idx];
888 			}
889 		}
890 		// East-North neighbor, if needed (i.e. if north-east neighbor was not found).
891 		if (!NEIGHBOR_TRANSFORM_NE(lvl_idx)) {
892 			if (NEIGHBOR_TRANSFORM_E(lvl_idx) && NEIGHBOR_TRANSFORM_N(NEIGHBOR_ID_E(lvl_idx))) {
893 				diag_idx = NEIGHBOR_ID_N(NEIGHBOR_ID_E(lvl_idx));
894 				gps_transform_matrix[lvl_idx][diag_idx].delta_x =
895 				    NEIGHBOR_TRANSFORM_E(lvl_idx)->delta_x + NEIGHBOR_TRANSFORM_N(NEIGHBOR_ID_E(lvl_idx))->delta_x;
896 				gps_transform_matrix[lvl_idx][diag_idx].delta_y =
897 				    NEIGHBOR_TRANSFORM_E(lvl_idx)->delta_y + NEIGHBOR_TRANSFORM_N(NEIGHBOR_ID_E(lvl_idx))->delta_y;
898 				gps_transform_matrix[lvl_idx][diag_idx].lvl_idx = diag_idx;
899 				gps_transform_matrix[lvl_idx][diag_idx].valid = TRUE;
900 
901 				NEIGHBOR_TRANSFORM_NE(lvl_idx) = &gps_transform_matrix[lvl_idx][diag_idx];
902 			}
903 		}
904 		// East-South neighbor, if needed (i.e. if south-east neighbor was not found).
905 		if (!NEIGHBOR_TRANSFORM_SE(lvl_idx)) {
906 			if (NEIGHBOR_TRANSFORM_E(lvl_idx) && NEIGHBOR_TRANSFORM_S(NEIGHBOR_ID_E(lvl_idx))) {
907 				diag_idx = NEIGHBOR_ID_S(NEIGHBOR_ID_E(lvl_idx));
908 				gps_transform_matrix[lvl_idx][diag_idx].delta_x =
909 				    NEIGHBOR_TRANSFORM_E(lvl_idx)->delta_x + NEIGHBOR_TRANSFORM_S(NEIGHBOR_ID_E(lvl_idx))->delta_x;
910 				gps_transform_matrix[lvl_idx][diag_idx].delta_y =
911 				    NEIGHBOR_TRANSFORM_E(lvl_idx)->delta_y + NEIGHBOR_TRANSFORM_S(NEIGHBOR_ID_E(lvl_idx))->delta_y;
912 				gps_transform_matrix[lvl_idx][diag_idx].lvl_idx = diag_idx;
913 				gps_transform_matrix[lvl_idx][diag_idx].valid = TRUE;
914 
915 				NEIGHBOR_TRANSFORM_SE(lvl_idx) = &gps_transform_matrix[lvl_idx][diag_idx];
916 			}
917 		}
918 	}
919 
920 	gps_transform_map_dirty_flag = FALSE;
921 }
922 
923 /**
924  * There are several cases where an object or a character (Tux or a bot)
925  * could become visible or active when they are technically still not on the
926  * current level.
927  *
928  * Therefore we introduce 'virtual' positions, i.e. the position the object
929  * would have, if the object were in fact counted as part of a neighboring level,
930  * mostly the level of the Tux.  Using this concept, we can more easily compute
931  * distances and compare positions.
932  *
933  * This function is an abstract approach to this problem, working with
934  * the 'gps' notion.
935  *
936  */
update_virtual_position(gps * target_pos,gps * source_pos,int level_num)937 void update_virtual_position(gps * target_pos, gps * source_pos, int level_num)
938 {
939 	// The case where the position in question is already directly on
940 	// the virtual level, things are really simple and we can quit
941 	// almost immediately...
942 	//
943 	if (source_pos->z == level_num) {
944 		target_pos->x = source_pos->x;
945 		target_pos->y = source_pos->y;
946 		target_pos->z = source_pos->z;
947 		return;
948 	}
949 	// Transform the gps position
950 	//
951 	if (source_pos->z < 0 || source_pos->z >= sizeof(gps_transform_matrix)/sizeof(gps_transform_matrix[0]) ||
952 		level_num < 0 || level_num >= sizeof(gps_transform_matrix[0])/sizeof(gps_transform_matrix[0][0])) {
953 		error_message(__FUNCTION__, "Virtual position update was required for level %d relative to level %d - one of those are incorrect level numbers.", PLEASE_INFORM, source_pos->z, level_num);
954 		target_pos->x = (-1);
955 		target_pos->y = (-1);
956 		target_pos->z = (-1);
957 		return;
958 	}
959 
960 	struct neighbor_data_cell *ngb_data = &gps_transform_matrix[source_pos->z][level_num];
961 
962 	if (ngb_data->valid) {
963 		target_pos->x = source_pos->x + ngb_data->delta_x;
964 		target_pos->y = source_pos->y + ngb_data->delta_y;
965 		target_pos->z = level_num;
966 		return;
967 	}
968 	// The gps position cannot be expressed in terms of the virtual level.
969 	// That means we'll best 'erase' the virtual positions, so that
970 	// no 'phantoms' will occur...
971 	//
972 	target_pos->x = (-1);
973 	target_pos->y = (-1);
974 	target_pos->z = (-1);
975 
976 }				// void update_virtual_position ( gps* target_pos , gps* source_pos , int level_num )
977 
978 /*
979  * Transform a virtual position, defined in the 'lvl' coordinate system, into
980  * its real level and position
981  *
982  * The function is safe to be called with the same actual parameter, i.e.
983  * resolve_virtual_pos(&my_pos, &my_pos)
984  *
985  * Note: this function only works if the real position is one of the 8 neighbors of 'lvl'.
986  * If not, the function returns FALSE.
987  * (a recursive call could be used to remove this limitation)
988  */
resolve_virtual_position(gps * rpos,gps * vpos)989 int resolve_virtual_position(gps *rpos, gps *vpos)
990 {
991 	// By default, rpos is set to vpos
992 
993 	rpos->x = vpos->x;
994 	rpos->y = vpos->y;
995 	rpos->z = vpos->z;
996 
997 	// Check pre-conditions
998 
999 	if (vpos->z == -1) {
1000 		error_message(__FUNCTION__, "Resolve virtual position was called with an invalid virtual position (%f:%f:%d).", PLEASE_INFORM, vpos->x, vpos->y, vpos->z);
1001 		print_trace(0);
1002 		return FALSE;
1003 	}
1004 
1005 	// Get the gps transformation data cell, according to virtual position value
1006 
1007 	struct level *lvl = curShip.AllLevels[vpos->z];
1008 	int idX = NEIGHBOR_IDX(vpos->x, lvl->xlen);
1009 	int idY = NEIGHBOR_IDX(vpos->y, lvl->ylen);
1010 
1011 	// If we don't have to transform the position, return immediately
1012 
1013 	if (idX == 1 && idY == 1) {
1014 		return TRUE;
1015 	}
1016 
1017 	// Do the transformation
1018 
1019 	struct neighbor_data_cell *ngb_data = level_neighbors_map[vpos->z][idY][idX];
1020 
1021 	if (ngb_data && ngb_data->valid) {
1022 		struct gps tmp_pos = {
1023 				vpos->x + ngb_data->delta_x,
1024 				vpos->y + ngb_data->delta_y,
1025 				ngb_data->lvl_idx
1026 		};
1027 
1028 		// Check that the transformed position is valid (i.e. inside level boundaries)
1029 		level *rlvl = curShip.AllLevels[tmp_pos.z];
1030 		if (pos_inside_level(tmp_pos.x, tmp_pos.y, rlvl)) {
1031 			rpos->x = tmp_pos.x;
1032 			rpos->y = tmp_pos.y;
1033 			rpos->z = tmp_pos.z;
1034 			return TRUE;
1035 		}
1036 	}
1037 
1038 	return FALSE;
1039 }
1040 
1041 /**
1042  * Check if a position is inside a level's boundaries
1043  *
1044  * return TRUE if '(x,y)' is inside 'lvl'
1045  */
pos_inside_level(float x,float y,level * lvl)1046 int pos_inside_level(float x, float y, level * lvl)
1047 {
1048 	return ((x >= 0) && (x < (float)lvl->xlen) && (y >= 0) && (y < (float)lvl->ylen));
1049 }				// pos_inside_level()
1050 
1051 /**
1052  * Check if a position is inside or near a level's boundaries
1053  *
1054  * return TRUE if (x,y) is inside 'lvl' or at less than 'dist' from
1055  * 'lvl' borders.
1056  */
pos_near_level(float x,float y,level * lvl,float dist)1057 int pos_near_level(float x, float y, level * lvl, float dist)
1058 {
1059 	return ((x >= -dist) && (x < (float)lvl->xlen + dist) && (y >= -dist) && (y < (float)lvl->ylen + dist));
1060 }				// pos_near_level()
1061 
1062 /**
1063  * The blitting list must contain the enemies too.  This function is
1064  * responsible for inserting the enemies at the right positions.
1065  */
insert_enemies_into_blitting_list(int mask)1066 static void insert_enemies_into_blitting_list(int mask)
1067 {
1068 	int i;
1069 	int xmin, xmax, ymin, ymax;
1070 	enemy *ThisRobot;
1071 
1072 	get_floor_boundaries(mask, &ymin, &ymax, &xmin, &xmax);
1073 
1074 	// Now that we plan to also show bots on other levels, we must be
1075 	// a bit more general and proceed through all the levels...
1076 	//
1077 	// Those levels not in question will be filtered out anyway inside
1078 	// the loop...
1079 	//
1080 
1081 	for (i = 0; i < 2; i++) {
1082 		list_for_each_entry(ThisRobot, (i) ? &dead_bots_head : &alive_bots_head, global_list) {
1083 			if (!level_is_visible(ThisRobot->pos.z))
1084 				continue;
1085 
1086 			// We update the virtual position of this bot, such that we can handle it
1087 			// with easier expressions later...
1088 			//
1089 			update_virtual_position(&(ThisRobot->virt_pos), &(ThisRobot->pos), Me.pos.z);
1090 
1091 			if (ThisRobot->virt_pos.x < xmin || ThisRobot->virt_pos.x > xmax ||
1092 				ThisRobot->virt_pos.y < ymin || ThisRobot->virt_pos.y > ymax)
1093 				continue;
1094 
1095 			insert_one_enemy_into_blitting_list(ThisRobot);
1096 		}
1097 	}
1098 
1099 }
1100 
1101 /**
1102  *
1103  *
1104  */
insert_bullets_into_blitting_list(int mask)1105 static void insert_bullets_into_blitting_list(int mask)
1106 {
1107 	int i;
1108 	bullet *b;
1109 	int xmin, xmax, ymin, ymax;
1110 	get_floor_boundaries(mask, &ymin, &ymax, &xmin, &xmax);
1111 
1112 	for (i = 0; i < MAXBULLETS; i++) {
1113 		b = &AllBullets[i];
1114 		if (b->type == INFOUT)
1115 			continue;
1116 
1117 		gps vpos;
1118 		update_virtual_position(&vpos, &b->pos, Me.pos.z);
1119 
1120 		if (vpos.z == -1)
1121 			continue;
1122 
1123 		if (vpos.x < xmin || vpos.x > xmax || vpos.y < ymin || vpos.y > ymax)
1124 			continue;
1125 
1126 		insert_one_bullet_into_blitting_list(vpos.x + vpos.y, i);
1127 	}
1128 }
1129 
1130 /**
1131  *
1132  *
1133  */
insert_blasts_into_blitting_list(int mask)1134 static void insert_blasts_into_blitting_list(int mask)
1135 {
1136 	int i;
1137 	blast *b;
1138 	int xmin, xmax, ymin, ymax;
1139 	get_floor_boundaries(mask, &ymin, &ymax, &xmin, &xmax);
1140 
1141 	for (i = 0; i < MAXBLASTS; i++) {
1142 		b = &AllBlasts[i];
1143 		if (b->type == INFOUT)
1144 			continue;
1145 
1146 		gps vpos;
1147 		update_virtual_position(&vpos, &b->pos, Me.pos.z);
1148 
1149 		if (vpos.z == -1)
1150 			continue;
1151 
1152 		if (vpos.x < xmin || vpos.x > xmax || vpos.y < ymin || vpos.y > ymax)
1153 			continue;
1154 
1155 		insert_one_blast_into_blitting_list(i);
1156 	}
1157 
1158 }
1159 
1160 /**
1161  *
1162  *
1163  */
insert_thrown_items_into_blitting_list(int mask)1164 static void insert_thrown_items_into_blitting_list(int mask)
1165 {
1166 	int i;
1167 	struct visible_level *vis_lvl, *n;
1168 	item *it;
1169 
1170 	int xmin, xmax, ymin, ymax;
1171 	get_floor_boundaries(mask, &ymin, &ymax, &xmin, &xmax);
1172 
1173 	BROWSE_VISIBLE_LEVELS(vis_lvl, n) {
1174 		level *lvl = vis_lvl->lvl_pointer;
1175 		for (i = 0; i < MAX_ITEMS_PER_LEVEL; i++) {
1176 			it = &lvl->ItemList[i];
1177 
1178 			if (it->type == -1)
1179 				continue;
1180 
1181 			update_virtual_position(&it->virt_pos, &it->pos, Me.pos.z);
1182 
1183 			if (it->virt_pos.z == -1)
1184 				continue;
1185 
1186 			if (it->virt_pos.x < xmin || it->virt_pos.x > xmax || it->virt_pos.y < ymin || it->virt_pos.y > ymax)
1187 				continue;
1188 
1189 			insert_one_thrown_item_into_blitting_list(it, i);
1190 		}
1191 	}
1192 }
1193 
blitting_list_compare(const void * elt1,const void * elt2)1194 static int blitting_list_compare(const void *elt1, const void *elt2)
1195 {
1196 	const struct blitting_list_element *e1 = elt1, *e2 = elt2;
1197 
1198 	if (e1->norm < e2->norm)
1199 		return -1;
1200 	else if (e1->norm > e2->norm)
1201 		return 1;
1202 	else if (e1->list_position < e2->list_position)
1203 		return -1;
1204 	else return 1;
1205 }
1206 
sort_blitting_list(void)1207 static void sort_blitting_list(void)
1208 {
1209 	qsort(blitting_list->arr, blitting_list->size, sizeof(struct blitting_list_element),
1210 			blitting_list_compare);
1211 }
1212 
1213 /**
1214  * In isometric viewpoint setting, we need to respect visibility when
1215  * considering the order of things to blit.  Therefore we will first set
1216  * up a list of the things to be blitted for this frame.  Then we can
1217  * later use this list to fill in objects into the picture, automatically
1218  * having the right order.
1219  */
set_up_ordered_blitting_list(int mask)1220 void set_up_ordered_blitting_list(int mask)
1221 {
1222 	if (!blitting_list) {
1223 		blitting_list = dynarray_alloc(100, sizeof(struct blitting_list_element));
1224 	} else {
1225 		dynarray_free(blitting_list);
1226 		dynarray_init(blitting_list, 100, sizeof(struct blitting_list_element));
1227 	}
1228 
1229 	// Now we can start to fill in the obstacles around the
1230 	// tux...
1231 	insert_obstacles_into_blitting_list(mask);
1232 
1233 	insert_tux_into_blitting_list();
1234 
1235 	insert_enemies_into_blitting_list(mask);
1236 
1237 	insert_bullets_into_blitting_list(mask);
1238 
1239 	insert_blasts_into_blitting_list(mask);
1240 
1241 	insert_move_cursor_into_blitting_list();
1242 
1243 	insert_thrown_items_into_blitting_list(mask);
1244 
1245 	sort_blitting_list();
1246 }
1247 
show_obstacle(int mask,obstacle * o,int code_number,int opacity)1248 static void show_obstacle(int mask, obstacle * o, int code_number, int opacity)
1249 {
1250 	// Safety checks
1251 	if ((o->type <= -1) || (o->type >= obstacle_map.size)) {
1252 		error_message(__FUNCTION__, "The blitting list contained an illegal obstacle type %d.", PLEASE_INFORM | IS_FATAL, o->type);
1253 	}
1254 
1255 	if (!(mask & OMIT_OBSTACLES)) {
1256 		if (mask & ZOOM_OUT) {
1257 			blit_one_obstacle(o, NOHIGHLIGHT, ZOOM_OUT, opacity);
1258 		} else {
1259 			if (code_number == clickable_obstacle_under_cursor) {
1260 				blit_one_obstacle(o, HIGHLIGHT, !ZOOM_OUT, opacity);
1261 			} else {
1262 				// Do not blit "transp for water" obstacle when not in leveleditor mode
1263 				if (game_status != INSIDE_LVLEDITOR && o->type == ISO_TRANSP_FOR_WATER)
1264 					return;
1265 
1266 				// Normal display
1267 				blit_one_obstacle(o, NOHIGHLIGHT, !ZOOM_OUT, opacity);
1268 			}
1269 		}
1270 	}
1271 }
1272 
1273 /**
1274  * Now that the blitting list has finally been assembled, we can start to
1275  * blit all the objects according to the blitting list set up.
1276  */
blit_preput_objects_according_to_blitting_list(int mask)1277 void blit_preput_objects_according_to_blitting_list(int mask)
1278 {
1279 	obstacle *our_obstacle = NULL;
1280 	obstacle_spec *obstacle_spec = NULL;
1281 	int item_under_cursor = -1;
1282 	level *item_under_cursor_lvl = NULL;
1283 
1284 	start_image_batch();
1285 
1286 	int i;
1287 	for (i = 0; i < blitting_list->size; i++) {
1288 		struct blitting_list_element *e = &((struct blitting_list_element *)(blitting_list->arr))[i];
1289 
1290 		switch (e->element_type) {
1291 		case BLITTING_TYPE_OBSTACLE:
1292 		case BLITTING_TYPE_VOLATILE_OBSTACLE:
1293 			// We do some sanity checking for illegal obstacle types.
1294 			// Can't hurt to do that so as to be on the safe side.
1295 			//
1296 			if ((((obstacle *) e->element_pointer)->type <= -1) ||
1297 			    ((obstacle *) e->element_pointer)->type >= obstacle_map.size) {
1298 				error_message(__FUNCTION__,
1299 					     "The blitting list contained an illegal obstacle type %d, for obstacle at coordinates %f %f. Doing nothing.", PLEASE_INFORM,
1300 						 ((obstacle *) e->element_pointer)->type, ((obstacle *) e->element_pointer)->pos.x, ((obstacle *) e->element_pointer)->pos.y);
1301 				break;
1302 
1303 			}
1304 
1305 			our_obstacle = e->element_pointer;
1306 			obstacle_spec = get_obstacle_spec(our_obstacle->type);
1307 
1308 			// If the obstacle has a shadow, it seems like now would be a good time
1309 			// to blit it.
1310 			// Do not display obstacle shadow when obstacles are omitted
1311 			//
1312 			if (!GameConfig.skip_shadow_blitting && !(mask &OMIT_OBSTACLES)) {
1313 				gps vpos;
1314 				update_virtual_position(&vpos, &our_obstacle->pos, Me.pos.z);
1315 				struct image *shadow_img = get_obstacle_shadow_image(our_obstacle->type, our_obstacle->frame_index);
1316 				display_image_on_map(shadow_img, vpos.x, vpos.y, IMAGE_SCALE_TRANSFO((mask & ZOOM_OUT) ? lvledit_zoomfact_inv() : 1.0));
1317 			}
1318 
1319 			// If the obstacle in question does have a collision rectangle, then we
1320 			// draw that on the floor now.
1321 			//
1322 			blit_obstacle_collision_rectangle(our_obstacle);
1323 
1324 			// Draw the obstacle by itself if it is a preput obstacle
1325 			//
1326 			if (obstacle_spec->flags & NEEDS_PRE_PUT) {
1327 				if (e->element_type == BLITTING_TYPE_VOLATILE_OBSTACLE)
1328 					show_obstacle(mask, ((obstacle *) e->element_pointer), -2, e->code_number);
1329 				else
1330 					show_obstacle(mask, ((obstacle *) e->element_pointer), e->code_number, 255);
1331 			}
1332 			break;
1333 
1334 		case BLITTING_TYPE_ENEMY:
1335 			// Enemies, which are dead already become like decoration on the floor.
1336 			// They should never hide the Tux, so we blit them beforehand and not
1337 			// again later from the list.
1338 			//
1339 			if (((enemy *)e->element_pointer)->animation_type == DEATH_ANIMATION
1340 			    || ((enemy *)e->element_pointer)->animation_type == DEAD_ANIMATION) {
1341 				if (!(mask & OMIT_ENEMIES)) {
1342 					PutEnemy((enemy *) (e->element_pointer), -1, -1, mask, FALSE);
1343 				}
1344 			}
1345 			break;
1346 
1347 		case BLITTING_TYPE_THROWN_ITEM:
1348 			if (mask & SHOW_ITEMS) {
1349 				// Preput thrown items when they are on the floor (i.e. not during the
1350 				// throwing animation)
1351 				item *the_item = (item*)e->element_pointer;
1352 				item_under_cursor_lvl = NULL;
1353 				if (the_item->throw_time <= 0) {
1354 					item_under_cursor = get_floor_item_index_under_mouse_cursor(&item_under_cursor_lvl);
1355 					if (item_under_cursor != -1 && item_under_cursor_lvl != NULL &&
1356 						the_item->pos.z == item_under_cursor_lvl->levelnum && item_under_cursor == e->code_number)
1357 						PutItem(the_item, mask, PUT_NO_THROWN_ITEMS, TRUE);
1358 					else
1359 						PutItem(the_item, mask, PUT_NO_THROWN_ITEMS, FALSE);
1360 				}
1361 			}
1362 			break;
1363 
1364 		case BLITTING_TYPE_MOVE_CURSOR:
1365 			PutMouseMoveCursor();
1366 			break;
1367 		}
1368 	}
1369 
1370 	end_image_batch();
1371 
1372 }				// void blit_preput_objects_according_to_blitting_list ( ... )
1373 
1374 /**
1375  * Now that the blitting list has finally been assembled, we can start to
1376  * blit all the objects according to the blitting list set up.
1377  */
blit_nonpreput_objects_according_to_blitting_list(int mask)1378 void blit_nonpreput_objects_according_to_blitting_list(int mask)
1379 {
1380 	enemy *enemy_under_cursor = NULL;
1381 	level *item_under_cursor_lvl = NULL;
1382 
1383 	// We memorize which 'enemy' is currently under the mouse target, so that we
1384 	// can properly highlight this enemy...
1385 	//
1386 	enemy_under_cursor = GetLivingDroidBelowMouseCursor();
1387 	int item_under_cursor = get_floor_item_index_under_mouse_cursor(&item_under_cursor_lvl);
1388 
1389 	// Now it's time to blit all the elements from the list...
1390 	//
1391 	int i;
1392 	for (i = 0; i < blitting_list->size; i++) {
1393 		start_image_batch();
1394 		struct blitting_list_element *e = &((struct blitting_list_element *)(blitting_list->arr))[i];
1395 
1396 		if (e->element_type == BLITTING_TYPE_NONE)
1397 			break;
1398 		switch (e->element_type) {
1399 		case BLITTING_TYPE_OBSTACLE:
1400 		case BLITTING_TYPE_VOLATILE_OBSTACLE:
1401 			// Skip preput obstacles
1402 			if (get_obstacle_spec(((obstacle*)(e->element_pointer))->type)->flags & NEEDS_PRE_PUT)
1403 				continue;
1404 			if (e->element_type == BLITTING_TYPE_VOLATILE_OBSTACLE)
1405 				show_obstacle(mask, ((obstacle *) e->element_pointer), -2, e->code_number);
1406 			else
1407 				show_obstacle(mask, ((obstacle *) e->element_pointer), e->code_number, 255);
1408 			break;
1409 		case BLITTING_TYPE_TUX:
1410 			if (!(mask & OMIT_TUX)) {
1411 				if (Me.energy > 0)
1412 					blit_tux(-1, -1);
1413 			}
1414 			break;
1415 		case BLITTING_TYPE_ENEMY:
1416 			if (!(mask & OMIT_ENEMIES)) {
1417 				if (((enemy *) e->element_pointer)->energy < 0)
1418 					continue;
1419 				if (((enemy *) e->element_pointer)->animation_type == DEATH_ANIMATION)
1420 					continue;
1421 				if (((enemy *) e->element_pointer)->animation_type == DEAD_ANIMATION)
1422 					continue;
1423 
1424 				// A droid can either be rendered in normal mode or in highlighted
1425 				// mode, depending in whether the mouse cursor is right over it or not.
1426 				//
1427 				if (e->element_pointer == enemy_under_cursor)
1428 					PutEnemy((enemy *) e->element_pointer, -1, -1, mask, TRUE);
1429 				else
1430 					PutEnemy((enemy *) e->element_pointer, -1, -1, mask, FALSE);
1431 			}
1432 			break;
1433 		case BLITTING_TYPE_BULLET:
1434 			// DebugPrintf ( -1000 , "Bullet code_number: %d. " , blitting_list [ i ] . code_number );
1435 			PutBullet(e->code_number, mask);
1436 			break;
1437 		case BLITTING_TYPE_BLAST:
1438 			if (!(mask & OMIT_BLASTS))
1439 				PutBlast(e->code_number);
1440 			break;
1441 		case BLITTING_TYPE_THROWN_ITEM:
1442 			{
1443 				item *the_item = (item*)e->element_pointer;
1444 				if (the_item->throw_time > 0) {
1445 					if (item_under_cursor != -1 && item_under_cursor == e->code_number
1446 						&& item_under_cursor_lvl->levelnum == the_item->pos.z)
1447 						PutItem(the_item, mask, PUT_ONLY_THROWN_ITEMS, TRUE);
1448 					else
1449 						PutItem(the_item, mask, PUT_ONLY_THROWN_ITEMS, FALSE);
1450 				}
1451 			}
1452 			// DebugPrintf ( -1 , "\nThrown item now blitted..." );
1453 			break;
1454 		case BLITTING_TYPE_MOVE_CURSOR:
1455 			break;
1456 		default:
1457 			error_message(__FUNCTION__, "\
1458 						The blitting list contained an illegal blitting object type.", PLEASE_INFORM | IS_FATAL);
1459 			break;
1460 		}
1461 	}
1462 	end_image_batch();
1463 }
1464 
show_obstacle_labels(int mask)1465 static void show_obstacle_labels(int mask)
1466 {
1467 	int i;
1468 	level *l = CURLEVEL();
1469 
1470 	if (game_status != INSIDE_LVLEDITOR) {
1471 		// Don't show obstacles labels when we are not in the editor
1472 		return;
1473 	}
1474 
1475 	if (mask & OMIT_OBSTACLES) {
1476 		// Don't show obstacles labels when obstacles are not displayed on the map
1477 		return;
1478 	}
1479 
1480 	for (i = 0; i < l->obstacle_extensions.size; i++) {
1481 		struct obstacle_extension *ext = &ACCESS_OBSTACLE_EXTENSION(l->obstacle_extensions, i);
1482 
1483 		if (ext->type == OBSTACLE_EXTENSION_LABEL) {
1484 			show_backgrounded_label_at_map_position(ext->data,
1485 					0, ext->obs->pos.x,
1486 					ext->obs->pos.y, mask & ZOOM_OUT);
1487 		}
1488 	}
1489 }
1490 
1491 /**
1492  * Each item is lying on the floor.  But that means some of the items,
1493  * especially the smaller but not necessary less valuable items will not
1494  * be easy to make out under all the bushed, trees, rubble and stuff.
1495  * So the solution is to offer a special key that when pressed will make
1496  * all item names flash up, so that you can't possibly miss an item that
1497  * you're standing close to.
1498  *
1499  * This function blits all the item names to the screen on the exact
1500  * positions that have been computed before (hopefully!) in other
1501  * functions like update_item_text_slot_positions ( ... ) or so.
1502  */
blit_all_item_slots(int mask)1503 static void blit_all_item_slots(int mask)
1504 {
1505 	int i;
1506 	struct visible_level *vis_lvl, *n;
1507 
1508 	if (mask & OMIT_ITEMS_LABEL) {
1509 		// Do not show item slots
1510 		return;
1511 	}
1512 
1513 	BROWSE_VISIBLE_LEVELS(vis_lvl, n) {
1514 
1515 		level *item_level = vis_lvl->lvl_pointer;
1516 
1517 		for (i = 0; i < MAX_ITEMS_PER_LEVEL; i++) {
1518 			// We don't work with unused item slots...
1519 			//
1520 			if (item_level->ItemList[i].type == (-1))
1521 				continue;
1522 
1523 			// Now we check if the cursor is on that slot, because then the
1524 			// background of the slot will be highlighted...
1525 			//
1526 			if (MouseCursorIsInRect(&(item_level->ItemList[i].text_slot_rectangle), GetMousePos_x(), GetMousePos_y()))
1527 				draw_rectangle(&item_level->ItemList[i].text_slot_rectangle, 0, 0, 153, 100);
1528 			else {
1529 				if ((item_level->ItemList[i].text_slot_rectangle.x + item_level->ItemList[i].text_slot_rectangle.w <= 0) ||
1530 					(item_level->ItemList[i].text_slot_rectangle.y + item_level->ItemList[i].text_slot_rectangle.h <= 0) ||
1531 					(item_level->ItemList[i].text_slot_rectangle.x >= GameConfig.screen_width) ||
1532 					(item_level->ItemList[i].text_slot_rectangle.y >= GameConfig.screen_height))
1533 					continue;
1534 
1535 				draw_rectangle(&item_level->ItemList[i].text_slot_rectangle, 0, 0, 0, BACKGROUND_TEXT_RECT_ALPHA);
1536 			}
1537 
1538 			// Finally it's time to insert the font into the item slot.  We
1539 			// use the item name, but currently font color is not adapted for
1540 			// special item properties...
1541 			//
1542 			put_string(FPS_Display_Font, item_level->ItemList[i].text_slot_rectangle.x,
1543 					  item_level->ItemList[i].text_slot_rectangle.y, D_(item_specs_get_name(item_level->ItemList[i].type)));
1544 
1545 		}
1546 	}
1547 };				// void blit_all_item_slots ( void )
1548 
1549 /**
1550  *
1551  *
1552  */
item_slot_position_blocked(item * given_item,int item_slot)1553 int item_slot_position_blocked(item * given_item, int item_slot)
1554 {
1555 	int i;
1556 	item *cur_item;
1557 	struct visible_level *vis_lvl, *n;
1558 	int item_level_reached = FALSE;
1559 	int last_slot_to_check;
1560 
1561 	// We will browse all visible levels, until we reach the item's level.
1562 	// For each browsed level, we check against all items.
1563 	// But, on the item's level, we stop when the current item is reached.
1564 
1565 	BROWSE_VISIBLE_LEVELS(vis_lvl, n) {
1566 
1567 		level *item_level = vis_lvl->lvl_pointer;
1568 
1569 		if (item_level->levelnum == given_item->pos.z) {
1570 			item_level_reached = TRUE;
1571 			last_slot_to_check = item_slot;
1572 		} else {
1573 			last_slot_to_check = MAX_ITEMS_PER_LEVEL;
1574 		}
1575 
1576 		for (i = 0; i < last_slot_to_check + 1; i++) {
1577 			cur_item = &(item_level->ItemList[i]);
1578 
1579 			if (cur_item->type == (-1))
1580 				continue;
1581 
1582 			if (MouseCursorIsInRect(&(cur_item->text_slot_rectangle),
1583 						given_item->text_slot_rectangle.x, given_item->text_slot_rectangle.y)) {
1584 				return (TRUE);
1585 			}
1586 			if (MouseCursorIsInRect(&(cur_item->text_slot_rectangle),
1587 						given_item->text_slot_rectangle.x,
1588 						given_item->text_slot_rectangle.y + get_font_height(FPS_Display_Font))) {
1589 				return (TRUE);
1590 			}
1591 			if (MouseCursorIsInRect(&(cur_item->text_slot_rectangle),
1592 						given_item->text_slot_rectangle.x +
1593 						given_item->text_slot_rectangle.w, given_item->text_slot_rectangle.y)) {
1594 				return (TRUE);
1595 			}
1596 			if (MouseCursorIsInRect(&(cur_item->text_slot_rectangle),
1597 						given_item->text_slot_rectangle.x +
1598 						given_item->text_slot_rectangle.w,
1599 						given_item->text_slot_rectangle.y + get_font_height(FPS_Display_Font))) {
1600 				return (TRUE);
1601 			}
1602 			if (MouseCursorIsInRect(&(cur_item->text_slot_rectangle),
1603 						given_item->text_slot_rectangle.x +
1604 						given_item->text_slot_rectangle.w / 2, given_item->text_slot_rectangle.y)) {
1605 				return (TRUE);
1606 			}
1607 			if (MouseCursorIsInRect(&(cur_item->text_slot_rectangle),
1608 						given_item->text_slot_rectangle.x +
1609 						given_item->text_slot_rectangle.w / 2,
1610 						given_item->text_slot_rectangle.y + get_font_height(FPS_Display_Font))) {
1611 				return (TRUE);
1612 			}
1613 		}
1614 
1615 		if (item_level_reached)
1616 			break;
1617 	}
1618 
1619 	return (FALSE);
1620 };				// void item_slot_position_blocked ( int x , int y , int last_slot_to_check )
1621 
1622 /**
1623  * Each item is lying on the floor.  But that means some of the items,
1624  * especially the smaller but not necessarily less valuable items will not
1625  * be easy to make out under all the bushed, trees, rubble and stuff.
1626  * So the solution is to offer a special key that when pressed will make
1627  * all item names flash up, so that you can't possibly miss an item that
1628  * you're standing close to.
1629  *
1630  * This function computes the best rectangles and positions for such
1631  * item names to flash up.
1632  */
update_item_text_slot_positions(void)1633 void update_item_text_slot_positions(void)
1634 {
1635 	int i;
1636 	struct font *BFont_to_use = FPS_Display_Font;
1637 	item *cur_item;
1638 	struct visible_level *vis_lvl, *n;
1639 
1640 	BROWSE_VISIBLE_LEVELS(vis_lvl, n) {
1641 
1642 		level *item_level = vis_lvl->lvl_pointer;
1643 
1644 		for (i = 0; i < MAX_ITEMS_PER_LEVEL; i++) {
1645 			cur_item = &(item_level->ItemList[i]);
1646 
1647 			if (cur_item->type == (-1))
1648 				continue;
1649 
1650 			// We try to use a text rectangle that is close to the
1651 			// actual item...
1652 			//
1653 			update_virtual_position(&cur_item->virt_pos, &cur_item->pos, Me.pos.z);
1654 			cur_item->text_slot_rectangle.h = get_font_height(BFont_to_use);
1655 			cur_item->text_slot_rectangle.w = text_width(BFont_to_use, D_(item_specs_get_name(cur_item->type)));
1656 			cur_item->text_slot_rectangle.x =
1657 				translate_map_point_to_screen_pixel_x(cur_item->virt_pos.x, cur_item->virt_pos.y) - cur_item->text_slot_rectangle.w / 2;
1658 			cur_item->text_slot_rectangle.y =
1659 				translate_map_point_to_screen_pixel_y(cur_item->virt_pos.x, cur_item->virt_pos.y) - cur_item->text_slot_rectangle.h / 2;
1660 
1661 			// But maybe the situation is already very crowded, i.e. maybe there are
1662 			// already (a lot of) items there with slot positions conflicting...
1663 			// Well, what to do?  If there is already an item there, we try to escape,
1664 			// that's it.
1665 			// We will however only try a given amount of times, to protect against
1666 			// an "infinite" loop.
1667 			if ((item_slot_position_blocked(cur_item, i - 1))) {
1668 				int max_tries = 10;
1669 				while (max_tries >= 0) {
1670 					if (i % 2)
1671 						cur_item->text_slot_rectangle.y += cur_item->text_slot_rectangle.h + 2;
1672 					else
1673 						cur_item->text_slot_rectangle.y -= cur_item->text_slot_rectangle.h + 2;
1674 
1675 					if (!item_slot_position_blocked(cur_item, i - 1))
1676 						break;
1677 
1678 					// Maybe just a hundred left or right would also do...  but if it
1679 					// doesn't, we'll undo the changes made.
1680 					//
1681 					Sint16 tmp = cur_item->text_slot_rectangle.x;
1682 
1683 					cur_item->text_slot_rectangle.x += 100;
1684 					if (!item_slot_position_blocked(cur_item, i - 1))
1685 						break;
1686 
1687 					cur_item->text_slot_rectangle.x = tmp - 100;
1688 					if (!item_slot_position_blocked(cur_item, i - 1))
1689 						break;
1690 
1691 					cur_item->text_slot_rectangle.x = tmp;
1692 					max_tries--;
1693 				}
1694 			}
1695 		}
1696 	}
1697 };				// void update_item_text_slot_positions ( void )
1698 
draw_grid_on_the_floor(int mask)1699 void draw_grid_on_the_floor(int mask)
1700 {
1701 	if (game_status != INSIDE_LVLEDITOR)
1702 		return;
1703 
1704 	if (!GameConfig.show_grid)
1705 		return;
1706 
1707 	int LineStart, LineEnd, ColStart, ColEnd;
1708 	float x, y;
1709 	Level our_level = curShip.AllLevels[Me.pos.z];
1710 
1711 	get_floor_boundaries(mask, &LineStart, &LineEnd, &ColStart, &ColEnd);
1712 
1713 	x = rintf(Me.pos.x + 0.5);
1714 	y = rintf(Me.pos.y + 0.5);
1715 
1716 	if (x < 1)
1717 		x = 1;
1718 	if (x > our_level->xlen)
1719 		x = our_level->xlen;
1720 	if (y < 1)
1721 		y = 1;
1722 	if (y > our_level->ylen)
1723 		y = our_level->ylen;
1724 
1725 	float dd;
1726 
1727 	if (GameConfig.grid_mode == 1) {	// large grid
1728 
1729 		if (LineStart < 0)
1730 			LineStart = 0;
1731 		if (LineEnd > our_level->ylen)
1732 			LineEnd = our_level->ylen;
1733 		if (ColStart < 0)
1734 			ColStart = 0;
1735 		if (ColEnd > our_level->xlen)
1736 			ColEnd = our_level->xlen;
1737 
1738 		// Draw horizontal lines.
1739 		for (dd = LineStart; dd <= LineEnd; dd++) {
1740 			draw_line_on_map(ColStart, dd, ColEnd, dd, 0x99, 0xFF, 0xFF, 1);	// light cyan
1741 		}
1742 
1743 		// Draw vertical lines.
1744 		for (dd = ColStart; dd <= ColEnd; dd++) {
1745 			draw_line_on_map(dd, LineStart, dd, LineEnd, 0x99, 0xFF, 0xFF, 1);	// light cyan
1746 		}
1747 	}
1748 
1749 	for (dd = 0; dd <= 1; dd += .5)	// quick-placement grid
1750 	{
1751 		draw_line_on_map(x - 1.5, y - dd, x + 0.5, y - dd, 0xFF, 0x00, 0xFF, 1);	// magenta
1752 		draw_line_on_map(x - dd, y - 1.5, x - dd, y + 0.5, 0xFF, 0x00, 0xFF, 1);	// magenta
1753 	}
1754 
1755 	// Draw the level borders.
1756 	draw_line_on_map(0, 0, 0, our_level->ylen, 0xFF, 0x00, 0x00, 3);
1757 	draw_line_on_map(our_level->xlen, 0, our_level->xlen, our_level->ylen, 0xFF, 0x00, 0x00, 3);
1758 	draw_line_on_map(0, 0, our_level->xlen, 0, 0xFF, 0x00, 0x00, 3);
1759 	draw_line_on_map(0, our_level->ylen, our_level->xlen, our_level->ylen, 0xFF, 0x00, 0x00, 3);
1760 
1761 	// display numbers, corresponding to the numpad keys for quick placing
1762 	struct font *PreviousFont;
1763 	PreviousFont = get_current_font();
1764 	set_current_font(Messagevar_Font);
1765 	char *numbers[2][2] = { {"3", "9"}, {"1", "7"} };
1766 	int ii, jj;
1767 	for (ii = 0; ii <= 1; ii++)
1768 		for (jj = 0; jj <= 1; jj++) {
1769 			float xx, yy;
1770 			int r, c;
1771 			xx = x - ii;
1772 			yy = y - jj;
1773 			translate_map_point_to_screen_pixel(xx, yy, &r, &c);
1774 			SDL_Rect tr;
1775 			tr.x = r - 7;
1776 			tr.y = c - 7;
1777 			tr.w = 12;
1778 			tr.h = 14;
1779 
1780 			draw_rectangle(&tr, 0, 0, 0, 255);
1781 			display_text(numbers[ii][jj], r - 5, c - 5, &tr, 1.0);
1782 		}
1783 	set_current_font(PreviousFont);
1784 }
1785 
1786 /* -----------------------------------------------------------------
1787  * This function assembles the contents of the combat window
1788  * in Screen.
1789  *
1790  * Several FLAGS can be used to control its behavior:
1791  *
1792  * (*) ONLY_SHOW_MAP = 1:  This flag indicates not do draw any
1793  *     game elements but the map blocks
1794  *
1795  * (*) DO_SCREEN_UPDATE = 2: This flag indicates for the function
1796  *     to also cause an SDL_Update of the portion of the screen
1797  *     that has been modified
1798  *
1799  * (*) ONLY_SHOW_MAP_AND_TEXT = 4: This flag indicates, that only
1800  *     the map and also info like the current coordinate position
1801  *     should be entered into the Screen.  This flag is mainly
1802  *     used for the level editor.
1803  *
1804  * ----------------------------------------------------------------- */
AssembleCombatPicture(int mask)1805 void AssembleCombatPicture(int mask)
1806 {
1807 	clear_screen();
1808 	clickable_obstacle_under_cursor =  clickable_obstacle_below_mouse_cursor(NULL, TRUE);
1809 	if ((!GameConfig.skip_light_radius) && (!(mask & SKIP_LIGHT_RADIUS))) {
1810 		// We generate a list of obstacles (and other stuff) that might
1811 		// emit some light.  It should be sufficient to establish this
1812 		// list once in the code and the to use it for all light computations
1813 		// of this frame.
1814 		update_light_list();
1815 	}
1816 
1817 	show_floor(mask);
1818 
1819 	draw_grid_on_the_floor(mask);
1820 
1821 	set_up_ordered_blitting_list(mask);
1822 
1823 	blit_preput_objects_according_to_blitting_list(mask);
1824 
1825 	blit_nonpreput_objects_according_to_blitting_list(mask);
1826 
1827 	if ((!GameConfig.skip_light_radius) && (!(mask & SKIP_LIGHT_RADIUS)))
1828 		blit_light_radius();
1829 
1830 	PutMiscellaneousSpellEffects();
1831 
1832 	if (mask & ONLY_SHOW_MAP) {
1833 		// in case we only draw the map, we are done here.  But
1834 		// of course we must check if we should update the screen too.
1835 		if (mask & DO_SCREEN_UPDATE)
1836 			our_SDL_update_rect_wrapper(Screen, 0, 0, Screen->w, Screen->h);
1837 
1838 		return;
1839 	}
1840 
1841 	show_obstacle_labels(mask);
1842 
1843 	display_automap();
1844 
1845 	if (XPressed() || GameConfig.show_item_labels) {
1846 		update_item_text_slot_positions();
1847 		blit_all_item_slots(mask);
1848 	}
1849 
1850 	// Here are some more things, that are not needed in the level editor
1851 	// view...
1852 	if (!(mask & ONLY_SHOW_MAP_AND_TEXT)) {
1853 		display_widgets();
1854 		if (!GameOver && !world_frozen())
1855 			show_texts_and_banner();
1856 	}
1857 
1858 	if (GameConfig.Inventory_Visible || GameConfig.skill_explanation_screen_visible || addon_crafting_ui_visible()) {
1859 		User_Rect.x = 320;
1860 	} else
1861 		User_Rect.x = 0;
1862 
1863 	if (GameConfig.CharacterScreen_Visible || GameConfig.SkillScreen_Visible) {
1864 		User_Rect.w = GameConfig.screen_width - 320 - User_Rect.x;
1865 	} else {
1866 		User_Rect.w = GameConfig.screen_width - User_Rect.x;
1867 	}
1868 
1869 #ifdef WITH_RTPROF
1870 	rtprof_display();
1871 #endif
1872 
1873 	if (!(mask & NO_CURSOR))
1874 		blit_mouse_cursor();
1875 
1876 #if 0
1877 	/* This code displays the player tracks with red dots. */
1878 	glDisable(GL_TEXTURE_2D);
1879 	glPointSize(2.0);
1880 	glBegin(GL_POINTS);
1881 	int i = 0;
1882 	for (; i < MAX_INFLU_POSITION_HISTORY; i++) {
1883 		int x, y;
1884 		translate_map_point_to_screen_pixel(Me.Position_History_Ring_Buffer[i].x, Me.Position_History_Ring_Buffer[i].y, &x, &y);
1885 		glColor3f(1.0, 0.0, 0.0);
1886 		glVertex2i(x, y);
1887 	}
1888 
1889 	glEnd();
1890 	glEnable(GL_TEXTURE_2D);
1891 #endif
1892 
1893 #if 0
1894 	/* This code displays tux "waypoints" */
1895 	glDisable(GL_TEXTURE_2D);
1896 	glLineWidth(2.0);
1897 	glBegin(GL_LINE_STRIP);
1898 	i = 0;
1899 	int x, y;
1900 	translate_map_point_to_screen_pixel(Me.pos.x, Me.pos.y, &x, &y);
1901 	glColor3f(0.0, 1.0, 0.0);
1902 	glVertex2i(x, y);
1903 	while (Me.next_intermediate_point[i].x != -1) {
1904 		translate_map_point_to_screen_pixel(Me.next_intermediate_point[i].x, Me.next_intermediate_point[i].y, &x, &y);
1905 		glColor3f(0.0, 1.0, 0.0);
1906 		glVertex2i(x, y);
1907 		i++;
1908 	}
1909 
1910 	glEnd();
1911 	glEnable(GL_TEXTURE_2D);
1912 #endif
1913 
1914 	// At this point we are done with the drawing procedure
1915 	// and all that remains to be done is updating the screen.
1916 	//
1917 	if (mask & DO_SCREEN_UPDATE) {
1918 		our_SDL_update_rect_wrapper(Screen, 0, 0, Screen->w, Screen->h);
1919 	}
1920 
1921 	if (GameConfig.limit_framerate)
1922 		SDL_framerateDelay(&SDL_FPSmanager);
1923 }
1924 
1925 /* -----------------------------------------------------------------
1926  * This function draws the mouse move cursor.
1927  * ----------------------------------------------------------------- */
PutMouseMoveCursor(void)1928 void PutMouseMoveCursor(void)
1929 {
1930 	SDL_Rect TargetRectangle;
1931 
1932 	if ((Me.mouse_move_target.x == (-1)) && (enemy_resolve_address(Me.current_enemy_target_n, &Me.current_enemy_target_addr) == NULL)) {
1933 		return;
1934 	}
1935 
1936 	if (Me.mouse_move_target.x != (-1)) {
1937 		TargetRectangle.x = translate_map_point_to_screen_pixel_x(Me.mouse_move_target.x, Me.mouse_move_target.y);
1938 		TargetRectangle.y = translate_map_point_to_screen_pixel_y(Me.mouse_move_target.x, Me.mouse_move_target.y);
1939 		TargetRectangle.x -= MouseCursorImageList[0].w / 2;
1940 		TargetRectangle.y -= MouseCursorImageList[0].h / 2;
1941 		display_image_on_screen(&MouseCursorImageList[0], TargetRectangle.x, TargetRectangle.y, IMAGE_NO_TRANSFO);
1942 	}
1943 
1944 	enemy *t = enemy_resolve_address(Me.current_enemy_target_n, &Me.current_enemy_target_addr);
1945 	if (t != NULL) {
1946 		// translate_map_point_to_screen_pixel ( float x_map_pos , float y_map_pos , int give_x )
1947 		update_virtual_position(&t->virt_pos, &t->pos, Me.pos.z);
1948 
1949 		TargetRectangle.x = translate_map_point_to_screen_pixel_x(t->virt_pos.x, t->virt_pos.y);
1950 		TargetRectangle.y = translate_map_point_to_screen_pixel_y(t->virt_pos.x, t->virt_pos.y);
1951 		TargetRectangle.x -= MouseCursorImageList[1].w / 2;
1952 		TargetRectangle.y -= MouseCursorImageList[1].h / 2;
1953 		display_image_on_screen(&MouseCursorImageList[1], TargetRectangle.x, TargetRectangle.y, IMAGE_NO_TRANSFO);
1954 	}
1955 }
1956 
1957 /**
1958  *
1959  *
1960  */
free_one_loaded_tux_image_series(int motion_class,int tux_part_group)1961 static void free_one_loaded_tux_image_series(int motion_class, int tux_part_group)
1962 {
1963 	int i, j;
1964 
1965 	tux_images[motion_class].part_names[tux_part_group][0] = '\0';
1966 
1967 	for (i = 0; i < TUX_TOTAL_PHASES; i++) {
1968 		for (j = 0; j < MAX_TUX_DIRECTIONS; j++) {
1969 			delete_image(&tux_images[motion_class].part_images[tux_part_group][i][j]);
1970 		}
1971 	}
1972 }
1973 
1974 /**
1975  * This function should blit the isometric version of the Tux to the
1976  * screen.
1977  */
iso_put_tux_part(struct tux_part_render_data * render_data,int x,int y,int motion_class,int our_phase,int rotation_index)1978 static void iso_put_tux_part(struct tux_part_render_data *render_data, int x, int y, int motion_class, int our_phase, int rotation_index)
1979 {
1980 	char *part_string = *(render_data->default_part_instance);
1981 	int tux_part_group = render_data->part_group;
1982 
1983 	// If there is an equipped item, get its animation prefix name
1984 	if (render_data->wearable_item && render_data->wearable_item->type != -1 &&
1985 			render_data->wearable_item != item_held_in_hand &&
1986 			ItemMap[render_data->wearable_item->type].tux_part_instance) {
1987 		part_string = ItemMap[render_data->wearable_item->type].tux_part_instance;
1988 	}
1989 
1990 	// Some parts could be 'empty' (weapon, currently)
1991 	if (!part_string || !strlen(part_string))
1992 		return;
1993 
1994 	// If some part string given is unlike the part string we were using so
1995 	// far, then we'll need to free that old part and (later) load the new
1996 	// part.
1997 	//
1998 	if (strcmp(tux_images[motion_class].part_names[tux_part_group], part_string)) {
1999 		free_one_loaded_tux_image_series(motion_class, tux_part_group);
2000 		load_tux_graphics(motion_class, tux_part_group, part_string);
2001 		strcpy(tux_images[motion_class].part_names[tux_part_group], part_string);
2002 		// It can be expected, that this operation HAS TAKEN CONSIDERABLE TIME!
2003 		// Therefore we must activate the conservative frame time compution now,
2004 		// so as to prevent any unwanted jumps right now...
2005 		//
2006 		Activate_Conservative_Frame_Computation();
2007 	}
2008 
2009 	float r = 1.0, g = 1.0, b = 1.0, a = 1.0;
2010 
2011 	if (Me.paralyze_duration) {	/* Paralyzed ? tux turns red */
2012 		g = 0.2;
2013 		b = 0.2;
2014 	} else if (Me.slowdown_duration) {	/* Slowed down ? tux turns blue */
2015 		r = 0.2;
2016 		g = 0.2;
2017 	} else if (Me.energy < Me.maxenergy * 0.25) {	/* Low energy ? blink red */
2018 		g = b = ((SDL_GetTicks() >> 5) & 31) * 0.03;
2019 	}
2020 
2021 	if (Me.invisible_duration) {	/* Invisible? Become transparent */
2022 		a = 0.5;
2023 	}
2024 
2025 	struct image *img = &tux_images[motion_class].part_images[tux_part_group][our_phase][rotation_index];
2026 
2027 	if (x == -1) {
2028 		display_image_on_map(img, Me.pos.x, Me.pos.y, set_image_transformation(1.0, 1.0, r, g, b, a, 0));
2029 	} else {
2030 		display_image_on_screen(img, x, y, IMAGE_NO_TRANSFO);
2031 	}
2032 }
2033 
2034 /**
2035  * Given the name of a Tux's part, returns the data needed to render it.
2036  *
2037  * \param part_name A Tux's part name
2038  * \return A pointer the static tux_part_render_data which contains the data needed to render the part
2039  */
tux_get_part_render_data(char * part_name)2040 struct tux_part_render_data *tux_get_part_render_data(char *part_name)
2041 {
2042 	int i;
2043 	static struct tux_part_render_data render_data[] = {
2044 		{  "head",      &tux_rendering.default_instances.head,      &Me.special_item, PART_GROUP_HEAD },
2045 		{  "torso",     &tux_rendering.default_instances.torso,     &Me.armour_item,  PART_GROUP_TORSO },
2046 		{  "weaponarm", &tux_rendering.default_instances.weaponarm, NULL,             PART_GROUP_WEAPONARM },
2047 		{  "weapon",    &tux_rendering.default_instances.weapon,    &Me.weapon_item,  PART_GROUP_WEAPON },
2048 		{  "shieldarm", &tux_rendering.default_instances.shieldarm, &Me.shield_item,  PART_GROUP_SHIELD },
2049 		{  "feet",      &tux_rendering.default_instances.feet,      &Me.drive_item,   PART_GROUP_FEET }
2050 	};
2051 
2052 	if (part_name == NULL)
2053 		return NULL;
2054 
2055 	for (i = 0; i < sizeof(render_data)/sizeof(render_data[0]); i++) {
2056 		if (!strcmp(part_name, render_data[i].name)) {
2057 			return &render_data[i];
2058 		}
2059 	}
2060 
2061 	return NULL;
2062 }
2063 
2064 /**
2065   * Initialize Tux's part rendering specification's data structures
2066   */
tux_rendering_init()2067 static void tux_rendering_init()
2068 {
2069 	dynarray_init(&tux_rendering.motion_class_names, 0, 0);
2070 	memset(&tux_rendering.default_instances, 0, sizeof(struct tux_part_instances));
2071 	tux_rendering.render_order = NULL;
2072 }
2073 
2074 /**
2075  * Check mandatory specifications, needed to ensure that Tux can be rendered.
2076  */
tux_rendering_validate()2077 static void tux_rendering_validate()
2078 {
2079 	// At least one motion_class is needed
2080 	if (tux_rendering.motion_class_names.size < 1) {
2081 		error_message(__FUNCTION__,
2082 			"Tux rendering specification is invalid: at least one motion_class is needed",
2083 			PLEASE_INFORM | IS_FATAL);
2084 	}
2085 
2086 	// There must be a rendering order defined for each motion class, each rotation
2087 	// and each animation phase
2088 	int i, j;
2089 	for (i = 0; i < tux_rendering.motion_class_names.size; i++) {
2090 		for (j = 0; j < MAX_TUX_DIRECTIONS; j++) {
2091 			if (tux_rendering.render_order[i][j] == NULL) {
2092 				error_message(__FUNCTION__,
2093 					"Tux rendering specification is invalid: no rendering order defined for motion_class \"%s\""
2094 					" and rotation index %d",
2095 					PLEASE_INFORM | IS_FATAL,
2096 					get_motion_class_name_by_id(i), j);
2097 			} else {
2098 				// Check that there is a rendering order for every animation phases
2099 				// Set a flag for each phase found, and then check that all flags are set
2100 				int phase;
2101 				int check_phase[TUX_TOTAL_PHASES];
2102 				memset(check_phase, 0, TUX_TOTAL_PHASES*sizeof(int));
2103 
2104 				struct tux_part_render_set *render_order_ptr = tux_rendering.render_order[i][j];
2105 				while (render_order_ptr != NULL) {
2106 					for (phase = render_order_ptr->phase_start; phase <= render_order_ptr->phase_end; phase++) {
2107 						check_phase[phase] = 1;
2108 					}
2109 					render_order_ptr = render_order_ptr->next;
2110 				}
2111 
2112 				for (phase = 0; phase < TUX_TOTAL_PHASES; phase++) {
2113 					if (check_phase[phase] == 0) {
2114 						error_message(__FUNCTION__,
2115 							"Tux rendering specification is invalid: no rendering order defined for motion_class \"%s\","
2116 							" rotation index %d and animation phase %d",
2117 							PLEASE_INFORM | IS_FATAL,
2118 							get_motion_class_name_by_id(i), j, phase);
2119 					}
2120 				}
2121 			}
2122 		}
2123 	}
2124 }
2125 
2126 /**
2127  * Load Tux animation and rendering specifications.
2128  */
tux_rendering_load_specs(const char * config_filename)2129 void tux_rendering_load_specs(const char *config_filename)
2130 {
2131 	char fpath[PATH_MAX];
2132 
2133 	tux_rendering_init();
2134 
2135 	find_file(config_filename, MAP_DIR, fpath, PLEASE_INFORM | IS_FATAL);
2136 	run_lua_file(LUA_CONFIG, fpath);
2137 	tux_rendering_validate(); // check mandatory specifications/configurations
2138 
2139 	tux_images = MyMalloc(tux_rendering.motion_class_names.size * sizeof(struct tux_motion_class_images));
2140 }
2141 
2142 /**
2143  * Returns the id (index number) of a motion_class, given its name
2144  *
2145  * \param name A motion_class name
2146  * \return Id associated to the motion_class name or -1 if 'name' is invalid or unknown
2147  */
get_motion_class_id_by_name(char * name)2148 int get_motion_class_id_by_name(char *name)
2149 {
2150 	// Check pre-condition
2151 	if (name == NULL)
2152 		return -1;
2153 
2154 	// Search 'name' in the motion_class_names list
2155 	int i;
2156 	for (i = 0; i < tux_rendering.motion_class_names.size; i++) {
2157 		if (!strcmp(name, ((char **)tux_rendering.motion_class_names.arr)[i]))
2158 			return i;
2159 	}
2160 
2161 	// Fall-back if 'name' is not found
2162 	return -1;
2163 }
2164 
2165 /**
2166  * Returns the name of a motion_class, given its id (index number).
2167  *
2168  * \param id A motion_class id
2169  * \return The name of the motion_class associated to 'id', or the first available motion_class if 'id' is invalid (not in range)
2170  */
get_motion_class_name_by_id(int id)2171 char *get_motion_class_name_by_id(int id)
2172 {
2173 	// Check pre-condition. Returns first name if id is invalid
2174 	if ((id < 0) || (id >= tux_rendering.motion_class_names.size))
2175 		return ((char **)tux_rendering.motion_class_names.arr)[0];
2176 
2177 	// Return the motion_class name
2178 	return ((char **)tux_rendering.motion_class_names.arr)[id];
2179 }
2180 
2181 /**
2182  * Returns Tux's current motion_class id, which depends on its weapon.
2183  *
2184  * \return The current motion_class's id
2185  */
get_motion_class_id()2186 int get_motion_class_id()
2187 {
2188 	int motion_class = 0;
2189 
2190 	// It Tux has a weapon in hand, return its motion class.
2191 	// Otherwise, return the first motion_class.
2192 	if (Me.weapon_item.type != -1)
2193 		motion_class = ItemMap[Me.weapon_item.type].weapon_motion_class;
2194 
2195 	return motion_class;
2196 }
2197 
2198 /*
2199  * This function blits the isometric version of the Tux to the screen
2200  */
iso_put_tux(int x,int y)2201 void iso_put_tux(int x, int y)
2202 {
2203 	// Compute the 3 parameters defining how to render the animated Tux:
2204 	// - motion_class: type of animation (sword_motion/gun_motion)
2205 	// - our_phase: phase of animation (i.e. "animation keyframe number")
2206 	// - rotation_index: Tux's rotation index
2207 	//
2208 	// When Tux is not animated (in the takeover minigame, for instance), that
2209 	// is when x != -1, then we set those parameters to default values (apart
2210 	// from motion_class.
2211 
2212 	int motion_class = get_motion_class_id();
2213 	int our_phase = tux_anim.standing_keyframe;
2214 	int rotation_index = 0; // Facing front
2215 
2216 	if (x == -1) {
2217 
2218 		our_phase = (int)Me.phase;
2219 
2220 		// Compute the rotation angle
2221 		// If Tux is walking/running, compute its angle from the direction of movement,
2222 		// else reuse the last computed angle.
2223 		float angle = Me.angle;
2224 
2225 		if ((Me.phase < tux_anim.attack.first_keyframe) || (Me.phase > tux_anim.attack.last_keyframe)) {
2226 			if (fabs(Me.speed.x) + fabs(Me.speed.y) > 0.1) {
2227 				angle = -(atan2(Me.speed.y, Me.speed.x) * 180 / M_PI - 45 - 180);
2228 				angle += 360 / (2 * MAX_TUX_DIRECTIONS);
2229 				while (angle < 0)
2230 					angle += 360;
2231 				Me.angle = angle; // Store computed angle for later use
2232 			}
2233 		}
2234 
2235 		// Compute the rotation index from the rotation angle
2236 		rotation_index = (angle * MAX_TUX_DIRECTIONS) / 360.0 + (MAX_TUX_DIRECTIONS / 2);
2237 		while (rotation_index >= MAX_TUX_DIRECTIONS)
2238 			rotation_index -= MAX_TUX_DIRECTIONS;
2239 		while (rotation_index < 0)
2240 			rotation_index += MAX_TUX_DIRECTIONS;
2241 	}
2242 
2243 	// Depending on the rendering parameters, get the right rendering order
2244 	// Loop on all rendering orders associated to the current motion_class and
2245 	// rotation, until one set containing the current animation phase is found
2246 	struct tux_part_render_set *one_render_set = tux_rendering.render_order[motion_class][rotation_index];
2247 
2248 	while (one_render_set != NULL) {
2249 		if (our_phase >= one_render_set->phase_start && our_phase <= one_render_set->phase_end) {
2250 			break;
2251 		}
2252 		one_render_set = one_render_set->next;
2253 	}
2254 
2255 	// Render all Tux's parts in order
2256 	int i;
2257 	for (i = 0; i < 6; i++) {
2258 		if (one_render_set && one_render_set->part_render_data[i]) {
2259 			iso_put_tux_part(one_render_set->part_render_data[i], x, y, motion_class, our_phase, rotation_index);
2260 		}
2261 	}
2262 }
2263 
2264 /* -----------------------------------------------------------------
2265  * This function draws the influencer to the screen, either
2266  * to the center of the combat window if (-1,-1) was specified, or
2267  * to the specified coordinates anywhere on the screen, useful e.g.
2268  * for drawing the influencer in the takeover minigame.
2269  *
2270  * The given coordinates then indicate the UPPER LEFT CORNER for
2271  * the blit.
2272  * ----------------------------------------------------------------- */
blit_tux(int x,int y)2273 void blit_tux(int x, int y)
2274 {
2275 	SDL_Rect Text_Rect;
2276 
2277 	Text_Rect.x = UserCenter_x + 21;
2278 	Text_Rect.y = UserCenter_y - 32;
2279 	Text_Rect.w = (User_Rect.w / 2) - 21;
2280 	Text_Rect.h = (User_Rect.h / 2);
2281 
2282 	// Draw Tux
2283 	iso_put_tux(x, y);
2284 
2285 	// Maybe the influencer has something to say :)
2286 	// so let him say it..
2287 	if ((x == -1) && Me.TextToBeDisplayed && (Me.TextVisibleTime < GameConfig.WantedTextVisibleTime) &&
2288 		GameConfig.All_Texts_Switch) {
2289 		set_current_font(FPS_Display_Font);
2290 		display_text(Me.TextToBeDisplayed, Text_Rect.x, Text_Rect.y, &Text_Rect, 1.0);
2291 	}
2292 }
2293 
2294 /**
2295  * If the corresponding configuration flag is enabled, enemies might 'say'
2296  * some text comment on the screen, like 'ouch' or 'i'll getch' or
2297  * something else more sensible.  This function is here to blit these
2298  * comments, that must have been set before, to the screen.
2299  */
PrintCommentOfThisEnemy(enemy * e)2300 void PrintCommentOfThisEnemy(enemy * e)
2301 {
2302 	int x_pos, y_pos;
2303 
2304 	if (game_status != INSIDE_GAME) {
2305 		// Do not print bot states when we are not inside the game.
2306 		return;
2307 	}
2308 
2309 	// At this point we can assume, that the enemys has been blittet to the
2310 	// screen, whether it's a friendly enemy or not.
2311 	//
2312 	// So now we can add some text the enemys says.  That might be fun.
2313 	//
2314 	if (!(e->TextToBeDisplayed))
2315 		return;
2316 	if ((e->TextVisibleTime < GameConfig.WantedTextVisibleTime)
2317 	    && GameConfig.All_Texts_Switch) {
2318 		x_pos = translate_map_point_to_screen_pixel_x(e->virt_pos.x, e->virt_pos.y);
2319 		y_pos = translate_map_point_to_screen_pixel_y(e->virt_pos.x, e->virt_pos.y)
2320 		    - 100;
2321 
2322 		// First we display the normal text to be displayed...
2323 		//
2324 #	if 0
2325 		char txt[256];
2326 		sprintf(txt, "%d - %s", e->id, e->TextToBeDisplayed);
2327 		put_string(FPS_Display_Font, x_pos, y_pos, txt);
2328 #	else
2329 		put_string(FPS_Display_Font, x_pos, y_pos, e->TextToBeDisplayed);
2330 #	endif
2331 	}
2332 
2333 }
2334 
2335 /**
2336  * Not every enemy has to be blitted onto the combat screen every time.
2337  * This function is here to find out whether this enemy has to be blitted
2338  * or whether we can skip it.
2339  */
must_blit_enemy(enemy * e)2340 static int must_blit_enemy(enemy *e)
2341 {
2342 	// if enemy is on other level, return
2343 	if (e->virt_pos.z != Me.pos.z) {
2344 		return FALSE;
2345 	}
2346 	// if enemy is of type (-1), return
2347 	if (e->type == (-1)) {
2348 		return FALSE;
2349 	}
2350 	// if the enemy is dead, show him anyways
2351 	if (e->animation_type == DEAD_ANIMATION)
2352 		return TRUE;
2353 	// if the enemy is out of sight, we need not do anything more here
2354 	if (game_status != INSIDE_LVLEDITOR && (!show_all_droids) && (!IsVisible(&e->virt_pos))) {
2355 		return FALSE;
2356 	}
2357 
2358 	return TRUE;
2359 }
2360 
2361 /**
2362  *
2363  *
2364  */
PutEnemyEnergyBar(enemy * e,SDL_Rect TargetRectangle)2365 void PutEnemyEnergyBar(enemy *e, SDL_Rect TargetRectangle)
2366 {
2367 	float Percentage;
2368 	SDL_Rect FillRect;
2369 
2370 #define ENEMY_ENERGY_BAR_WIDTH 7
2371 
2372 	// If the enemy is dead already, there's nothing to do here...
2373 	//
2374 	if (e->energy <= 0)
2375 		return;
2376 
2377 	// work out the percentage health
2378 	//
2379 	Percentage = (e->energy) / Droidmap[e->type].maxenergy;
2380 	if (use_open_gl) {
2381 
2382 #ifdef HAVE_LIBGL
2383 		int x, y, w;
2384 		myColor c1 = { 0, 0, 0, 255 };
2385 		myColor c2 = { 0, 0, 0, 255 };
2386 		float PercentageDone = 0;
2387 		int barnum = 0;
2388 		// If Percentage > 100%, several bars are drawn (one bar == 100% of maxenergy)
2389 		for (; Percentage > 0; Percentage -= PercentageDone, barnum++) {
2390 			if (Percentage >= 1)
2391 				PercentageDone = 1;
2392 			else
2393 				PercentageDone = Percentage;
2394 			// draw cool bars here
2395 			x = TargetRectangle.x;
2396 			y = TargetRectangle.y - 10 * barnum;
2397 			w = TargetRectangle.w;
2398 
2399 			if (is_friendly(e->faction, FACTION_SELF))
2400 				c1.g = 255;
2401 			else
2402 				c1.r = 255;
2403 
2404 			// tweak as needed, this alters the transparency
2405 			c1.a = 140;
2406 			drawIsoEnergyBar(x, y, 1, 5, 5, w, PercentageDone, &c1, &c2);
2407 		}
2408 
2409 #endif
2410 
2411 	} else {
2412 		//sdl stuff here
2413 
2414 		// Calculates the width of the remaining health bar. Rounds the
2415 		// width up to the nearest integer to ensure that at least one
2416 		// pixel of health is always shown.
2417 		//
2418 		int health_pixels = (int) ceil(Percentage * TargetRectangle.w);
2419 
2420 		FillRect.x = TargetRectangle.x;
2421 		FillRect.y = TargetRectangle.y - ENEMY_ENERGY_BAR_WIDTH;
2422 		FillRect.h = ENEMY_ENERGY_BAR_WIDTH;
2423 		FillRect.w = health_pixels;
2424 
2425 		// The color of the bar depends on the friendly/hostile status
2426 		if (is_friendly(e->faction, FACTION_SELF))
2427 			sdl_draw_rectangle(&FillRect, 0, 255, 0, 140);
2428 		else
2429 			sdl_draw_rectangle(&FillRect, 255, 0, 0, 140);
2430 
2431 		// Now after the energy bar has been drawn, we can start to draw the
2432 		// empty part of the energy bar (but only of course, if there is some
2433 		// empty part at all!
2434 		FillRect.x = TargetRectangle.x + health_pixels;
2435 		FillRect.w = TargetRectangle.w - health_pixels;
2436 
2437 		if (Percentage < 1.0)
2438 			sdl_draw_rectangle(&FillRect, 0, 0, 0, 255);
2439 	}
2440 }
2441 
2442 /**
2443  * The direction this robot should be facing right now is determined and
2444  * properly set in this function.
2445  */
set_rotation_index_for_this_robot(enemy * ThisRobot)2446 int set_rotation_index_for_this_robot(enemy * ThisRobot)
2447 {
2448 	int RotationIndex;
2449 
2450 	// By now the angle the robot is facing is determined, so we just need to
2451 	// translate this angle into an index within the image series, i.e. into
2452 	// a 'phase' of rotation.
2453 	//
2454 	RotationIndex = ((ThisRobot->current_angle - 45.0 + 360.0 + 360 /
2455 			  (2 * ROTATION_ANGLES_PER_ROTATION_MODEL)) * ROTATION_ANGLES_PER_ROTATION_MODEL / 360);
2456 
2457 	// But it might happen, that the angle of rotation is 'out of scale' i.e.
2458 	// it's more than 360 degree or less than 0 degree.  Therefore, we need to
2459 	// be especially careful to generate only proper indices for our arrays.
2460 	// Some would say, we identify the remainder classes with integers in the
2461 	// range [ 0 - (rotation_angles-1) ], which is what's happening here.
2462 	//
2463 	while (RotationIndex < 0)
2464 		RotationIndex += ROTATION_ANGLES_PER_ROTATION_MODEL;
2465 	while (RotationIndex >= ROTATION_ANGLES_PER_ROTATION_MODEL)
2466 		RotationIndex -= ROTATION_ANGLES_PER_ROTATION_MODEL;
2467 
2468 	// Now to prevent some jittering in some cases, where the droid uses an angle that is
2469 	// right at the borderline between two possible 8-way directions, we introduce some
2470 	// enforced consistency onto the droid...
2471 	//
2472 	if (RotationIndex == ThisRobot->previous_phase) {
2473 		ThisRobot->last_phase_change += Frame_Time();
2474 	} else {
2475 		if (ThisRobot->last_phase_change >= WAIT_BEFORE_ROTATE) {
2476 			ThisRobot->last_phase_change = 0.0;
2477 			ThisRobot->previous_phase = RotationIndex;
2478 		} else {
2479 			// In this case we don't permit to use a new 8-way direction now...
2480 			//
2481 			RotationIndex = ThisRobot->previous_phase;
2482 			ThisRobot->last_phase_change += Frame_Time();
2483 		}
2484 	}
2485 
2486 	// DebugPrintf ( 0 , "\nCurrent angle: %f Current RotationIndex: %d. " , angle, RotationIndex );
2487 
2488 	return (RotationIndex);
2489 
2490 };				// int set_rotation_index_for_this_robot ( enemy* ThisRobot )
2491 
2492 /**
2493  *
2494  *
2495  */
set_rotation_model_for_this_robot(enemy * ThisRobot)2496 int set_rotation_model_for_this_robot(enemy * ThisRobot)
2497 {
2498 	int RotationModel = Droidmap[ThisRobot->type].individual_shape_nr;
2499 
2500 	// A sanity check for roation model to use can never hurt...
2501 	//
2502 	if ((RotationModel < 0) || (RotationModel >= ENEMY_ROTATION_MODELS_AVAILABLE)) {
2503 		error_message(__FUNCTION__, "\
2504 There was a rotation model type given, that exceeds the number of rotation models allowed and loaded in FreedroidRPG.", PLEASE_INFORM | IS_FATAL);
2505 	}
2506 
2507 	return (RotationModel);
2508 
2509 };				// int set_rotation_model_for_this_robot ( enemy* ThisRobot )
2510 
2511 /**
2512  * This function is here to blit the 'body' of a droid to the screen.
2513  */
PutIndividuallyShapedDroidBody(enemy * ThisRobot,SDL_Rect TargetRectangle,int mask,int highlight)2514 void PutIndividuallyShapedDroidBody(enemy * ThisRobot, SDL_Rect TargetRectangle, int mask, int highlight)
2515 {
2516 	int RotationModel;
2517 	int RotationIndex;
2518 	moderately_finepoint bot_pos;
2519 	float zf = 1.0;
2520 	if (mask & ZOOM_OUT)
2521 		zf = lvledit_zoomfact_inv();
2522 
2523 	// if ( ThisRobot -> pos . z != Me . pos . z )
2524 	// DebugPrintf ( -4 , "\n%s(): Now attempting to blit bot on truly virtual position..." , __FUNCTION__ );
2525 
2526 	// We properly set the direction this robot is facing.
2527 	//
2528 	RotationIndex = set_rotation_index_for_this_robot(ThisRobot);
2529 
2530 	// We properly set the rotation model number for this robot, i.e.
2531 	// which shape (like 302, 247 or proffa) to use for drawing this bot.
2532 	//
2533 	RotationModel = set_rotation_model_for_this_robot(ThisRobot);
2534 
2535 	// Maybe the rotation model we're going to use now isn't yet loaded.
2536 	// Now in this case, we must load it immediately, or a segfault may
2537 	// result...
2538 	//
2539 	LoadAndPrepareEnemyRotationModelNr(RotationModel);
2540 
2541 	// Maybe we don't have an enemy here that would really stick to the
2542 	// exact size of a block but be somewhat bigger or smaller instead.
2543 	// In this case, we'll just adapt the given target rectangle a little
2544 	// bit, cause this rectangle assumes exactly the same size as a map
2545 	// block and has the origin shifted accordingly.
2546 	//
2547 	if ((TargetRectangle.x != 0) && (TargetRectangle.y != 0)) {
2548 		if (use_open_gl) {
2549 			TargetRectangle.x -= (enemy_images[RotationModel][RotationIndex][0].w) / 2;
2550 			TargetRectangle.y -= (enemy_images[RotationModel][RotationIndex][0].h) / 2;
2551 			TargetRectangle.w = enemy_images[RotationModel][RotationIndex][0].w;
2552 			TargetRectangle.h = enemy_images[RotationModel][RotationIndex][0].h;
2553 		} else {
2554 			TargetRectangle.x -= (enemy_images[RotationModel][RotationIndex][0].surface->w) / 2;
2555 			TargetRectangle.y -= (enemy_images[RotationModel][RotationIndex][0].surface->h) / 2;
2556 			TargetRectangle.w = enemy_images[RotationModel][RotationIndex][0].surface->w;
2557 			TargetRectangle.h = enemy_images[RotationModel][RotationIndex][0].surface->h;
2558 		}
2559 	}
2560 	// Maybe the enemy is desired e.g. for the takeover game, so a pixel position on
2561 	// the screen is given and we blit the enemy to that position, not taking into
2562 	// account any map coordinates or stuff like that...
2563 	//
2564 	if ((TargetRectangle.x != 0) && (TargetRectangle.y != 0)) {
2565 		RotationIndex = 0;
2566 		display_image_on_screen(&enemy_images[RotationModel][RotationIndex][0],
2567 										  TargetRectangle.x, TargetRectangle.y, IMAGE_NO_TRANSFO);
2568 		return;
2569 	}
2570 
2571 	float r, g, b;
2572 
2573 	object_vtx_color(ThisRobot, &r, &g, &b);
2574 
2575 	if (ThisRobot->paralysation_duration_left != 0) {
2576 		g = 0.2;
2577 		b = 0.2;
2578 	} else if (ThisRobot->poison_duration_left != 0) {
2579 		r = 0.2;
2580 		b = 0.2;
2581 	} else if (ThisRobot->frozen != 0) {
2582 		r = 0.2;
2583 		g = 0.2;
2584 	}
2585 
2586 	bot_pos.x = ThisRobot->virt_pos.x;
2587 	bot_pos.y = ThisRobot->virt_pos.y;
2588 
2589 	struct image *img = &enemy_images[RotationModel][RotationIndex][(int)ThisRobot->animation_phase];
2590 	display_image_on_map(img, bot_pos.x, bot_pos.y, set_image_transformation(zf, zf, r, g, b, 1.0, highlight));
2591 
2592 	if (GameConfig.enemy_energy_bars_visible) {
2593 		int screen_x, screen_y;
2594 		translate_map_point_to_screen_pixel(ThisRobot->virt_pos.x, ThisRobot->virt_pos.y, &screen_x, &screen_y);
2595 
2596 		int bar_width = min(enemy_images[RotationModel][RotationIndex][0].w, ENERGYBAR_MAXWIDTH);
2597 		TargetRectangle.x = screen_x - (bar_width * zf) / 2;
2598 		TargetRectangle.y = screen_y - (enemy_images[RotationModel][RotationIndex][0].h * zf) / 1;
2599 		TargetRectangle.w = bar_width * zf;
2600 		TargetRectangle.h = enemy_images[RotationModel][RotationIndex][0].h * zf;
2601 		PutEnemyEnergyBar(ThisRobot, TargetRectangle);
2602 	}
2603 }
2604 
2605 /**
2606  * This function draws an enemy into the combat window.
2607  * The only parameter given is the number of the enemy within the
2608  * AllEnemys array. Everything else is computed in here.
2609  */
PutEnemy(enemy * e,int x,int y,int mask,int highlight)2610 void PutEnemy(enemy * e, int x, int y, int mask, int highlight)
2611 {
2612 	SDL_Rect TargetRectangle = { 0, 0, 0, 0 };
2613 
2614 	// We check for things like visibility and distance and the like,
2615 	// so that we know whether to consider this enemy for blitting to
2616 	// the screen or not.  Since there are many things to consider, we
2617 	// got a special function for this job.
2618 	//
2619 	if ((!must_blit_enemy(e)) && (!GameConfig.xray_vision_for_tux))
2620 		return;
2621 
2622 	// We check for incorrect droid types, which sometimes might occur, especially after
2623 	// heavy editing of the crew initialization functions ;)
2624 	//
2625 	if (e->type >= Number_Of_Droid_Types) {
2626 		error_message(__FUNCTION__, "\
2627 There was a droid type on this level, that does not really exist.", PLEASE_INFORM | IS_FATAL);
2628 		e->type = 0;
2629 	}
2630 	// Since we will need that several times in the sequel, we find out the correct
2631 	// target location on the screen for our surface blit once and remember it for
2632 	// later.  ( THE TARGET RECTANGLE GETS MODIFIED IN THE SDL BLIT!!! )
2633 	//
2634 	if (x == (-1)) {
2635 		TargetRectangle.x = 0;
2636 		TargetRectangle.y = 0;
2637 	} else {
2638 		TargetRectangle.x = x;
2639 		TargetRectangle.y = y;
2640 	}
2641 
2642 	PutIndividuallyShapedDroidBody(e, TargetRectangle, mask, highlight);
2643 
2644 #if 0
2645 	/* This code displays the pathway of the bots as well as their next waypoint */
2646 	if (e->energy > 0) {
2647 		glDisable(GL_TEXTURE_2D);
2648 		glLineWidth(2.0);
2649 		int a, b;
2650 		glBegin(GL_LINE_STRIP);
2651 		glColor3f(0.0, 1.0, 1.0);
2652 		translate_map_point_to_screen_pixel(e->pos.x, e->pos.y, &a, &b, 1.0);
2653 		glVertex2i(a, b);
2654 		translate_map_point_to_screen_pixel(curShip.AllLevels[e->pos.z]->AllWaypoints[e->nextwaypoint].x + 0.5,
2655 						    curShip.AllLevels[e->pos.z]->AllWaypoints[e->nextwaypoint].y + 0.5, &a, &b, 1.0);
2656 		glVertex2i(a, b);
2657 		glEnd();
2658 		int aue = 0;
2659 		glBegin(GL_LINE_STRIP);
2660 		translate_map_point_to_screen_pixel(e->pos.x, e->pos.y, &a, &b, 1.0);
2661 		glColor3f(0.0, 0.0, 1.0);
2662 		glVertex2i(a, b);
2663 		for (; aue < 5 && e->PrivatePathway[aue].x != -1; aue++) {
2664 			translate_map_point_to_screen_pixel(e->PrivatePathway[aue].x, e->PrivatePathway[aue].y, &a, &b, 1.0);
2665 			glVertex2i(a, b);
2666 		}
2667 		glEnd();
2668 		glEnable(GL_TEXTURE_2D);
2669 	}
2670 #endif
2671 
2672 	// Only if this robot is not dead, we consider printing the comments
2673 	// this robot might have to make on the current situation.
2674 	//
2675 	if (e->energy > 0)
2676 		PrintCommentOfThisEnemy(e);
2677 
2678 };				// void PutEnemy(int Enum , int x , int y)
2679 
2680 /**
2681  * This function draws a Bullet into the combat window.  The only
2682  * parameter given is the number of the bullet in the AllBullets
2683  * array. Everything else is computed in here.
2684  */
PutBullet(int bullet_index,int mask)2685 void PutBullet(int bullet_index, int mask)
2686 {
2687 	bullet *CurBullet = &(AllBullets[bullet_index]);
2688 	int PhaseOfBullet;
2689 	int direction_index;
2690 
2691 	// DebugPrintf( 0 , "\nBulletType before calculating phase : %d." , CurBullet->type );
2692 	if ((CurBullet->type >= bullet_specs.size) || (CurBullet->type < 0)) {
2693 		fprintf(stderr, "\nPutBullet:  bullet type received: %d.", CurBullet->type);
2694 		fflush(stderr);
2695 		error_message(__FUNCTION__, "\
2696 There was a bullet to be blitted of a type that does not really exist.", PLEASE_INFORM | IS_FATAL);
2697 	}
2698 
2699 	struct bulletspec *bullet_spec = dynarray_member(&bullet_specs, CurBullet->type, sizeof(struct bulletspec));
2700 
2701 	PhaseOfBullet = CurBullet->time_in_seconds * bullet_spec->phase_changes_per_second;
2702 
2703 	PhaseOfBullet = PhaseOfBullet % bullet_spec->phases;
2704 	// DebugPrintf( 0 , "\nPhaseOfBullet: %d.", PhaseOfBullet );
2705 
2706 	direction_index = ((CurBullet->angle + 360.0 + 360 / (2 * BULLET_DIRECTIONS)) * BULLET_DIRECTIONS / 360);
2707 	while (direction_index < 0)
2708 		direction_index += BULLET_DIRECTIONS;	// just to make sure... a modulo ROTATION_ANGLES_PER_ROTATION_MODEL operation can't hurt
2709 	while (direction_index >= BULLET_DIRECTIONS)
2710 		direction_index -= BULLET_DIRECTIONS;	// just to make sure... a modulo ROTATION_ANGLES_PER_ROTATION_MODEL operation can't hurt
2711 
2712 	// draw position is relative to current level, so compute the appropriate virtual position
2713 	gps vpos;
2714 	update_virtual_position(&vpos, &CurBullet->pos, Me.pos.z);
2715 	if (vpos.x == -1)
2716 		return;
2717 
2718 	float scale = 1.0;
2719 	if (mask & ZOOM_OUT)
2720 		scale = lvledit_zoomfact_inv();
2721 	if (IsVisible(&vpos)) {
2722 		struct image *bullet_image = &bullet_spec->image[direction_index][PhaseOfBullet];
2723 		bullet_image->offset_y -= CurBullet->height;
2724 		display_image_on_map(bullet_image, vpos.x, vpos.y, IMAGE_SCALE_TRANSFO(scale));
2725 		bullet_image->offset_y += CurBullet->height;
2726 	}
2727 }
2728 
2729 /**
2730  * This function draws an item into the combat window.
2731  * The only given parameter is the number of the item within
2732  * the AllItems array.
2733  */
PutItem(item * CurItem,int mask,int put_thrown_items_flag,int highlight_item)2734 void PutItem(item *CurItem, int mask, int put_thrown_items_flag, int highlight_item)
2735 {
2736 	float r, g, b;
2737 	float zf = 1.0;
2738 
2739 	if (mask & ZOOM_OUT)
2740 		zf = lvledit_zoomfact_inv();
2741 
2742 	// The unwanted cases MUST be handled first...
2743 	//
2744 	if (CurItem->type == (-1)) {
2745 		error_message(__FUNCTION__, "Tried to draw an item of type -1 (item %p on level %d). Ignoring.", PLEASE_INFORM, CurItem, CurItem->pos.z);
2746 		return;
2747 	}
2748 
2749 	// We don't blit any item, that we're currently holding in our hand, do we?
2750 	if (item_held_in_hand == CurItem)
2751 		return;
2752 
2753 	// In case the flag filters this item, we don't blit it
2754 	//
2755 	if ((put_thrown_items_flag == PUT_ONLY_THROWN_ITEMS) && (CurItem->throw_time <= 0))
2756 		return;
2757 	if ((put_thrown_items_flag == PUT_NO_THROWN_ITEMS) && (CurItem->throw_time > 0))
2758 		return;
2759 
2760 	struct image *img = get_item_ingame_image(CurItem->type);
2761 
2762 	// Apply disco mode when current item is selected
2763 	object_vtx_color(CurItem, &r, &g, &b);
2764 
2765 	float anim_throw = (CurItem->throw_time <= 0) ? 0.0 : (3.0 * sinf(CurItem->throw_time * 3.0));
2766 
2767 	display_image_on_map(img, CurItem->virt_pos.x - anim_throw, CurItem->virt_pos.y - anim_throw, set_image_transformation(zf, zf, r, g, b, 1.0, highlight_item));
2768 }
2769 
PutRadialBlueSparks(float PosX,float PosY,float Radius,int SparkType,uint8_t active_direction[RADIAL_SPELL_DIRECTIONS],float age)2770 void PutRadialBlueSparks(float PosX, float PosY, float Radius, int SparkType, uint8_t active_direction[RADIAL_SPELL_DIRECTIONS], float age)
2771 {
2772 #define FIXED_NUMBER_OF_SPARK_ANGLES 12
2773 #define FIXED_NUMBER_OF_PROTOTYPES 4
2774 #define NUMBER_OF_SPARK_TYPES 3
2775 
2776 	SDL_Rect TargetRectangle;
2777 	static SDL_Surface *SparkPrototypeSurface[NUMBER_OF_SPARK_TYPES][FIXED_NUMBER_OF_PROTOTYPES] =
2778 	    { {NULL, NULL, NULL, NULL}, {NULL, NULL, NULL, NULL} };
2779 	static struct image PrerotatedSparkSurfaces[NUMBER_OF_SPARK_TYPES][FIXED_NUMBER_OF_PROTOTYPES][FIXED_NUMBER_OF_SPARK_ANGLES];
2780 	SDL_Surface *tmp_surf;
2781 	char fpath[PATH_MAX];
2782 	int NumberOfPicturesToUse;
2783 	int i, k;
2784 	float Angle;
2785 	int PrerotationIndex;
2786 	moderately_finepoint Displacement;
2787 	int PictureType;
2788 	char ConstructedFilename[5000];
2789 	int current_active_direction;
2790 
2791 	// We do some sanity check against too small a radius
2792 	// given as parameter.  This can be loosened later.
2793 	//
2794 	if (Radius <= 1.0)
2795 		return;
2796 
2797 	PictureType = (int)(4 * age) % 4;
2798 
2799 	// Now if we do not yet have all the prototype images in memory,
2800 	// we need to load them now and for once...
2801 	//
2802 	if (SparkPrototypeSurface[SparkType][0] == NULL) {
2803 		for (k = 0; k < FIXED_NUMBER_OF_PROTOTYPES; k++) {
2804 			if (SparkType >= NUMBER_OF_SPARK_TYPES) {
2805 				error_message(__FUNCTION__, "FreedroidRPG encountered a radial wave type that exceeds the CONSTANT for wave types (%d).",
2806 						PLEASE_INFORM | IS_FATAL, SparkType);
2807 			}
2808 
2809 			switch (SparkType) {
2810 			case 0:
2811 				sprintf(ConstructedFilename, "radial_spells/blue_sparks_%d.png", k);
2812 				break;
2813 			case 1:
2814 				sprintf(ConstructedFilename, "radial_spells/green_mist_%d.png", k);
2815 				break;
2816 			case 2:
2817 				sprintf(ConstructedFilename, "radial_spells/red_fire_%d.png", k);
2818 				break;
2819 			default:
2820 				error_message(__FUNCTION__, "FreedroidRPG encountered a radial wave type that does not exist in FreedroidRPG (%d).",
2821 						PLEASE_INFORM | IS_FATAL, SparkType);
2822 			}
2823 
2824 			find_file(ConstructedFilename, GRAPHICS_DIR, fpath, PLEASE_INFORM | IS_FATAL);
2825 
2826 			tmp_surf = our_IMG_load_wrapper(fpath);
2827 			if (tmp_surf == NULL) {
2828 				error_message(__FUNCTION__, "FreedroidRPG wanted to load a certain image file into memory, but the SDL\n"
2829 				                            "function used for this did not succeed (%s).",
2830 				              PLEASE_INFORM | IS_FATAL, fpath);
2831 			}
2832 			// SDL_SetColorKey( tmp_surf , 0 , 0 );
2833 			SparkPrototypeSurface[SparkType][k] = SDL_DisplayFormatAlpha(tmp_surf);
2834 			SDL_FreeSurface(tmp_surf);
2835 
2836 			// Now that the loading is successfully done, we can do the
2837 			// prerotation of the images...using a constant for simplicity...
2838 			//
2839 			for (i = 0; i < FIXED_NUMBER_OF_SPARK_ANGLES; i++) {
2840 				Angle = +45 - 360.0 * (float)i / (float)FIXED_NUMBER_OF_SPARK_ANGLES;
2841 
2842 				tmp_surf = rotozoomSurface(SparkPrototypeSurface[SparkType][k], Angle, 1.0, FALSE);
2843 
2844 				PrerotatedSparkSurfaces[SparkType][k][i].surface = SDL_DisplayFormatAlpha(tmp_surf);
2845 
2846 				// Maybe opengl is in use.  Then we need to prepare some textures too...
2847 				//
2848 				if (use_open_gl) {
2849 					make_texture_out_of_surface(&(PrerotatedSparkSurfaces[SparkType][k][i]));
2850 				}
2851 
2852 				SDL_FreeSurface(tmp_surf);
2853 			}
2854 		}
2855 
2856 	}
2857 
2858 	NumberOfPicturesToUse = 2 * (2 * Radius * 64 * 3.14) / (float)SparkPrototypeSurface[SparkType][PictureType]->w;
2859 	NumberOfPicturesToUse += 3;	// we want some overlap
2860 
2861 	// Now we blit all the pictures we like to use...in this case using
2862 	// multiple dynamic rotations (oh god!)...
2863 	//
2864 	for (i = 0; i < NumberOfPicturesToUse; i++) {
2865 		Angle = 360.0 * (float)i / (float)NumberOfPicturesToUse;
2866 		Displacement.x = Radius;
2867 		Displacement.y = 0;
2868 		RotateVectorByAngle(&Displacement, Angle);
2869 
2870 		PrerotationIndex = rintf((Angle) * (float)FIXED_NUMBER_OF_SPARK_ANGLES / 360.0);
2871 		if (PrerotationIndex >= FIXED_NUMBER_OF_SPARK_ANGLES)
2872 			PrerotationIndex = 0;
2873 
2874 		current_active_direction = rintf((Angle) * (float)RADIAL_SPELL_DIRECTIONS / 360.0);
2875 		if (!active_direction[current_active_direction])
2876 			continue;
2877 
2878 		if (use_open_gl) {
2879 			TargetRectangle.x =
2880 			    translate_map_point_to_screen_pixel_x(PosX + Displacement.x,
2881 								  PosY + Displacement.y) -
2882 			    ((PrerotatedSparkSurfaces[SparkType][PictureType][PrerotationIndex].w) / 2);
2883 			TargetRectangle.y =
2884 			    translate_map_point_to_screen_pixel_y(PosX + Displacement.x,
2885 								  PosY + Displacement.y) -
2886 			    ((PrerotatedSparkSurfaces[SparkType][PictureType][PrerotationIndex].h) / 2);
2887 		} else {
2888 			TargetRectangle.x =
2889 			    translate_map_point_to_screen_pixel_x(PosX + Displacement.x,
2890 								  PosY + Displacement.y) -
2891 			    ((PrerotatedSparkSurfaces[SparkType][PictureType][PrerotationIndex].surface->w) / 2);
2892 			TargetRectangle.y =
2893 			    translate_map_point_to_screen_pixel_y(PosX + Displacement.x,
2894 								  PosY + Displacement.y) -
2895 			    ((PrerotatedSparkSurfaces[SparkType][PictureType][PrerotationIndex].surface->h) / 2);
2896 		}
2897 
2898 		display_image_on_screen(&PrerotatedSparkSurfaces[SparkType][PictureType][PrerotationIndex],
2899 								 TargetRectangle.x, TargetRectangle.y, IMAGE_NO_TRANSFO);
2900 	}
2901 }
2902 
2903 /**
2904  * This function draws a blast into the combat window.
2905  * The only given parameter is the number of the blast within
2906  * the AllBlasts array.
2907  */
PutBlast(int Blast_number)2908 void PutBlast(int Blast_number)
2909 {
2910 	blast *CurBlast = &AllBlasts[Blast_number];
2911 
2912 	// If the blast is already long dead, we need not do anything else here
2913 	if (CurBlast->type == INFOUT)
2914 		return;
2915 
2916 	int phase = (int)floorf(CurBlast->phase);
2917 	if (phase >= 20) {
2918 		DeleteBlast(Blast_number);
2919 		return;
2920 	}
2921 	// DebugPrintf( 0 , "\nBulletType before calculating phase : %d." , CurBullet->type );
2922 	if (CurBlast->type >= ALLBLASTTYPES) {
2923 		error_message(__FUNCTION__, "\
2924 The PutBlast function should blit a blast of a type that does not\n\
2925 exist at all.", PLEASE_INFORM | IS_FATAL);
2926 	}
2927 	// draw position is relative to current level, so compute the appropriate virtual position
2928 	gps vpos;
2929 	update_virtual_position(&vpos, &CurBlast->pos, Me.pos.z);
2930 	if (vpos.x == -1)
2931 		return;
2932 	if (IsVisible(&vpos))
2933 		display_image_on_map(&Blastmap[CurBlast->type].images[phase], vpos.x, vpos.y, IMAGE_NO_TRANSFO);
2934 }
2935 
2936 /**
2937  * When the inventory screen is visible, we do not only show the items
2938  * present in inventory, but we also show the inventory squares, that each
2939  * item in the item pool takes away for storage.  This function blits a
2940  * part-transparent colored shadow under the item, such that the inventory
2941  * dimensions become apparent to the player immediately.
2942  */
draw_inventory_occupied_rectangle(SDL_Rect TargetRect,int bgcolor)2943 void draw_inventory_occupied_rectangle(SDL_Rect TargetRect, int bgcolor)
2944 {
2945 #define REQUIREMENTS_NOT_MET 1
2946 #define IS_MAGICAL 2
2947 
2948 	if (!bgcolor)
2949 		draw_rectangle(&TargetRect, 127, 127, 127, 100);
2950 	if (bgcolor & IS_MAGICAL)
2951 		draw_rectangle(&TargetRect, 0, 0, 255, 100);
2952 	if (bgcolor & REQUIREMENTS_NOT_MET)
2953 		draw_rectangle(&TargetRect, 255, 0, 0, 100);
2954 }
2955 
2956 /**
2957  * This function displays the inventory screen and also fills in all the
2958  * items the influencer is carrying in his inventory and also all the
2959  * items the influencer is fitted with.
2960  */
show_inventory_screen(void)2961 void show_inventory_screen(void)
2962 {
2963 	SDL_Rect TargetRect;
2964 	int SlotNum;
2965 	int i, j;
2966 
2967 	// We define the left side of the user screen as the rectangle
2968 	// for our inventory screen.
2969 	//
2970 	InventoryRect.x = 0;
2971 	InventoryRect.y = User_Rect.y;
2972 	InventoryRect.w = 320;
2973 	InventoryRect.h = 480;
2974 
2975 	if (GameConfig.Inventory_Visible == FALSE)
2976 		return;
2977 
2978 	// At this point we know, that the inventory screen is desired and must be
2979 	// displayed in-game:
2980 	//
2981 	blit_background("inventory.png");
2982 
2983 	// Now we display the item in the influencer drive slot
2984 	//
2985 	TargetRect.x = InventoryRect.x + DRIVE_RECT_X;
2986 	TargetRect.y = InventoryRect.y + DRIVE_RECT_Y;
2987 	if (item_held_in_hand != &Me.drive_item && (Me.drive_item.type != (-1))) {
2988 		struct image *img = get_item_inventory_image(Me.drive_item.type);
2989 		display_image_on_screen(img, TargetRect.x, TargetRect.y, IMAGE_NO_TRANSFO);
2990 	}
2991 
2992 	// Now we display the item in the influencer weapon slot
2993 	// At this point we have to pay extra care, cause the weapons in FreedroidRPG
2994 	// really come in many different sizes.
2995 	//
2996 	TargetRect.x = InventoryRect.x + WEAPON_RECT_X;
2997 	TargetRect.y = InventoryRect.y + WEAPON_RECT_Y;
2998 	if (item_held_in_hand != &Me.weapon_item && (Me.weapon_item.type != (-1))) {
2999 		TargetRect.x += INV_SUBSQUARE_WIDTH * 0.5 * (2 - ItemMap[Me.weapon_item.type].inv_size.x);
3000 		TargetRect.y += INV_SUBSQUARE_HEIGHT * 0.5 * (3 - ItemMap[Me.weapon_item.type].inv_size.y);
3001 		struct image *img = get_item_inventory_image(Me.weapon_item.type);
3002 		display_image_on_screen(img, TargetRect.x, TargetRect.y, IMAGE_NO_TRANSFO);
3003 
3004 		// Maybe this is also a 2-handed weapon.  In this case we need to blit the
3005 		// weapon a second time, this time in the center of the shield rectangle to
3006 		// visibly reflect the fact, that the shield hand is required too for this
3007 		// weapon.
3008 		//
3009 		if (ItemMap[Me.weapon_item.type].weapon_needs_two_hands) {
3010 			// Display the weapon again
3011 			TargetRect.x = InventoryRect.x + SHIELD_RECT_X;
3012 			TargetRect.y = InventoryRect.y + SHIELD_RECT_Y;
3013 			TargetRect.x += INV_SUBSQUARE_WIDTH * 0.5 * (2 - ItemMap[Me.weapon_item.type].inv_size.x);
3014 			TargetRect.y += INV_SUBSQUARE_HEIGHT * 0.5 * (3 - ItemMap[Me.weapon_item.type].inv_size.y);
3015 			display_image_on_screen(img, TargetRect.x, TargetRect.y, IMAGE_NO_TRANSFO);
3016 		}
3017 	}
3018 	// Now we display the item in the influencer armour slot
3019 	//
3020 	TargetRect.x = InventoryRect.x + ARMOUR_RECT_X;
3021 	TargetRect.y = InventoryRect.y + ARMOUR_RECT_Y;
3022 	if (item_held_in_hand != &Me.armour_item && (Me.armour_item.type != (-1))) {
3023 		struct image *img = get_item_inventory_image(Me.armour_item.type);
3024 		display_image_on_screen(img, TargetRect.x, TargetRect.y, IMAGE_NO_TRANSFO);
3025 	}
3026 	// Now we display the item in the influencer shield slot
3027 	//
3028 	TargetRect.x = InventoryRect.x + SHIELD_RECT_X;
3029 	TargetRect.y = InventoryRect.y + SHIELD_RECT_Y;
3030 	if (item_held_in_hand != &Me.shield_item && (Me.shield_item.type != (-1))) {
3031 		// Not all shield have the same height, therefore we do a little safety
3032 		// correction here, so that the shield will always appear in the center
3033 		// of the shield slot
3034 		//
3035 		TargetRect.y += INV_SUBSQUARE_HEIGHT * 0.5 * (3 - ItemMap[Me.shield_item.type].inv_size.y);
3036 		struct image *img = get_item_inventory_image(Me.shield_item.type);
3037 		display_image_on_screen(img, TargetRect.x, TargetRect.y, IMAGE_NO_TRANSFO);
3038 	}
3039 	// Now we display the item in the influencer special slot
3040 	//
3041 	TargetRect.x = InventoryRect.x + HELMET_RECT_X;
3042 	TargetRect.y = InventoryRect.y + HELMET_RECT_Y;
3043 	if (item_held_in_hand != &Me.special_item && (Me.special_item.type != (-1))) {
3044 		struct image *img = get_item_inventory_image(Me.special_item.type);
3045 		display_image_on_screen(img, TargetRect.x, TargetRect.y, IMAGE_NO_TRANSFO);
3046 	}
3047 	// Now we display all the items the influencer is carrying with him
3048 	//
3049 	for (SlotNum = 0; SlotNum < MAX_ITEMS_IN_INVENTORY - 1; SlotNum++) {
3050 		// In case the item does not exist at all, we need not do anything more...
3051 		if (Me.Inventory[SlotNum].type < 0) {
3052 			// The < 0 test is to handle a case where the item type field gets corrupted and is -256. We could not reproduce the problem so at least try to hide it to players.
3053 			continue;
3054 		}
3055 		// In case the item is currently held in hand, we need not do anything more HERE ...
3056 		if (item_held_in_hand == &Me.Inventory[SlotNum] ) {
3057 			continue;
3058 		}
3059 
3060 		for (i = 0; i < ItemMap[Me.Inventory[SlotNum].type].inv_size.y; i++) {
3061 			for (j = 0; j < ItemMap[Me.Inventory[SlotNum].type].inv_size.x; j++) {
3062 				TargetRect.x =
3063 				    INVENTORY_RECT_X - 1 + INV_SUBSQUARE_WIDTH * (Me.Inventory[SlotNum].inventory_position.x + j);
3064 				TargetRect.y =
3065 				    User_Rect.y + INVENTORY_RECT_Y + INV_SUBSQUARE_HEIGHT * (Me.Inventory[SlotNum].inventory_position.y +
3066 											     i);
3067 				TargetRect.w = INV_SUBSQUARE_WIDTH;
3068 				TargetRect.h = INV_SUBSQUARE_HEIGHT;
3069 				if (ItemUsageRequirementsMet(&(Me.Inventory[SlotNum]), FALSE)) {
3070 					// Draw a blue background for items with installed add-ons.
3071 					int socketnum;
3072 					int has_addons = FALSE;
3073 					for (socketnum = 0; socketnum < Me.Inventory[SlotNum].upgrade_sockets.size; socketnum++) {
3074 						if (Me.Inventory[SlotNum].upgrade_sockets.arr[socketnum].addon) {
3075 							has_addons = TRUE;
3076 							break;
3077 						}
3078 					}
3079 					draw_inventory_occupied_rectangle(TargetRect, has_addons? 2 : 0);
3080 				} else {
3081 					draw_inventory_occupied_rectangle(TargetRect, 1);
3082 				}
3083 			}
3084 		}
3085 
3086 		TargetRect.x = INVENTORY_RECT_X - 1 + INV_SUBSQUARE_WIDTH * Me.Inventory[SlotNum].inventory_position.x;
3087 		TargetRect.y = User_Rect.y + INVENTORY_RECT_Y + INV_SUBSQUARE_HEIGHT * Me.Inventory[SlotNum].inventory_position.y;
3088 
3089 		struct image *img = get_item_inventory_image(Me.Inventory[SlotNum].type);
3090 		display_image_on_screen(img, TargetRect.x, TargetRect.y, IMAGE_NO_TRANSFO);
3091 
3092 		// Show amount
3093 		if (ItemMap[Me.Inventory[SlotNum].type].item_group_together_in_inventory) {
3094 			set_current_font(Messagevar_Font);
3095 			// Only 3 characters fit in one inventory square.
3096 			char amount[4];
3097 			if (Me.Inventory[SlotNum].multiplicity < 999)
3098 				sprintf(amount, "%d", Me.Inventory[SlotNum].multiplicity);
3099 			else
3100 				strcpy(amount, "+++");
3101 			TargetRect.w = INV_SUBSQUARE_WIDTH * ItemMap[Me.Inventory[SlotNum].type].inv_size.x;
3102 			TargetRect.h = INV_SUBSQUARE_HEIGHT * ItemMap[Me.Inventory[SlotNum].type].inv_size.y;
3103 			int xpos = TargetRect.x + TargetRect.w - text_width(get_current_font(), amount) - 2;
3104 			int ypos = TargetRect.y + TargetRect.h - get_font_height(Messagevar_Font);
3105 			display_text(amount, xpos, ypos, &TargetRect, 1.0);
3106 		}
3107 	}
3108 
3109 	if (item_held_in_hand != NULL) {
3110 		DisplayItemImageAtMouseCursor(item_held_in_hand->type);
3111 	} else {
3112 		// In case the player does not have anything in his hand, then of course we need to
3113 		// unset everything as 'not in his hand'.
3114 		//
3115 		// printf("\n Mouse button should cause no image now.");
3116 	}
3117 }
3118 
3119 #undef _view_c
3120