1 #include <stdlib.h>
2 #include <string.h>
3 #include <SDL_timer.h>
4 
5 #include "asc.h"
6 #include "chat.h"
7 #include "context_menu.h"
8 #include "font.h"
9 #include "elconfig.h"
10 #include "gl_init.h"
11 #include "gamewin.h"
12 #include "hud.h"
13 #include "hud_statsbar_window.h"
14 #include "icon_window.h"
15 #include "sound.h"
16 
17 int stats_bar_win= -1;
18 int show_action_bar = 0;
19 int show_last_health_change_always = 0;
20 int max_food_level = 45;
21 int lock_skills_selection = 0;
22 
23 static int actual_num_disp_stats=1;
24 static int exp_bar_text_len = 0;
25 static int stats_bar_text_len = 0;
26 static int statbar_cursor_x;
27 static size_t cm_id = CM_INIT_VALUE;
28 static int scaled_line = 0;
29 static int exp_bar_start_x;
30 static int mana_bar_start_x;
31 static int food_bar_start_x;
32 static int load_bar_start_x;
33 static int action_bar_start_x;
34 static int player_statsbar_bar_height = 0;
35 static int stats_bar_len;
36 static int health_bar_start_x;
37 static struct { int d; int h; Uint32 dt; Uint32 ht; } my_last_health = { 0, 0, 0, 0 };
38 static int watch_this_stats[MAX_WATCH_STATS]={NUM_WATCH_STAT -1, 0, 0, 0, 0};  // default to only watching overall
39 
40 
41 // return the number of watched stat bars, the number displayed in the botton HUD
get_num_statsbar_exp(void)42 static int get_num_statsbar_exp(void)
43 {
44 	size_t i;
45 	int num_skills_bar = 0;
46 	for (i=0; i<MAX_WATCH_STATS; i++)
47 		if ((watch_this_stats[i] > 0) && statsinfo[watch_this_stats[i]-1].is_selected)
48 			num_skills_bar++;
49 	return num_skills_bar;
50 }
51 
52 
53 // return the optimal length for stat bars for the botton HUD
calc_stats_bar_len(window_info * win,int num_exp)54 static int calc_stats_bar_len(window_info *win, int num_exp)
55 {
56 	// calculate the maximum length for stats bars given the current number of both bar types
57 	int max_len = (int)(0.5 + win->current_scale * 100);
58 	int num_stat = (show_action_bar) ?5: 4;
59 	int prosed_len = (window_width-HUD_MARGIN_X-1) - (num_stat * stats_bar_text_len) - (num_exp * exp_bar_text_len);
60 	prosed_len /= num_stat + num_exp;
61 
62 	// constrain the maximum and minimum length of the skills bars to reasonable size
63 	if (prosed_len < 50)
64 		prosed_len = 50;
65 	else if (prosed_len > max_len)
66 		prosed_len = max_len;
67 
68 	return prosed_len;
69 }
70 
71 
72 // calculate the maximum number of exp bars
calc_max_disp_stats(int suggested_stats_bar_len)73 static int calc_max_disp_stats(int suggested_stats_bar_len)
74 {
75 	int exp_offset = ((show_action_bar)?5:4) * (suggested_stats_bar_len + stats_bar_text_len);
76 	int preposed_max_disp_stats = (window_width - HUD_MARGIN_X - exp_offset) / (suggested_stats_bar_len + exp_bar_text_len);
77 	if (preposed_max_disp_stats > MAX_WATCH_STATS)
78 		preposed_max_disp_stats = MAX_WATCH_STATS;
79 	if (preposed_max_disp_stats < 0)
80 		preposed_max_disp_stats = 0;
81 	return preposed_max_disp_stats;
82 }
83 
84 
85 /* draws damage and heal above the health bar */
draw_last_health_change(window_info * win)86 static void draw_last_health_change(window_info *win)
87 {
88 	unsigned char str[20];
89 	static const Uint32 timeoutms = 2*60*1000;
90 	const int yoff = -(HUD_MARGIN_Y + win->default_font_len_y + 1 - (window_height - win->cur_y));
91 	/* damage in red */
92 	if (my_last_health.d != 0)
93 	{
94 		if ((SDL_GetTicks() - my_last_health.dt) > timeoutms)
95 			my_last_health.d = 0;
96 		else
97 		{
98 			safe_snprintf((char*)str, sizeof(str), " %d ", my_last_health.d);
99 			draw_text(health_bar_start_x+stats_bar_len/2-2, yoff, str, strlen((const char*)str),
100 				win->font_category, TDO_MAX_WIDTH, window_width - 80, TDO_HELP, 1, TDO_FOREGROUND,
101 				1.0f, 0.0f, 0.0f, TDO_ZOOM, win->current_scale, TDO_ALIGNMENT, RIGHT, TDO_END);
102 		}
103 	}
104 	/* heal in green */
105 	if (my_last_health.h != 0)
106 	{
107 		if ((SDL_GetTicks() - my_last_health.ht) > timeoutms)
108 			my_last_health.h = 0;
109 		else
110 		{
111 			safe_snprintf((char*)str, sizeof(str), " %d ", my_last_health.h);
112 			draw_text(health_bar_start_x+stats_bar_len/2+2, yoff, str, strlen((const char*)str),
113 				win->font_category, TDO_HELP, 1, TDO_FOREGROUND, 0.0, 1.0, 0.0,
114 				TDO_ZOOM, win->current_scale, TDO_END);
115 		}
116 	}
117 }
118 
119 
get_player_statsbar_active_height(void)120 static int get_player_statsbar_active_height(void)
121 {
122 	return HUD_MARGIN_Y - get_icons_win_active_height() - scaled_line;
123 }
124 
125 
126 // clear the context menu regions for all stats bars and set up again
reset_statsbar_exp_cm_regions(void)127 static void reset_statsbar_exp_cm_regions(void)
128 {
129 	size_t i;
130 	cm_remove_regions(stats_bar_win);
131 	for (i=0; i<actual_num_disp_stats; i++)
132 		if (watch_this_stats[i] > 0)
133 			cm_add_region(cm_id, stats_bar_win, exp_bar_start_x+i*(stats_bar_len+exp_bar_text_len), 0, stats_bar_len, get_player_statsbar_active_height());
134 }
135 
136 
137 // remove the specific stat bar
remove_watched_stat(size_t watched_stat_index)138 static void remove_watched_stat(size_t watched_stat_index)
139 {
140 	if (watched_stat_index >= MAX_WATCH_STATS)
141 		return;
142 	statsinfo[watch_this_stats[watched_stat_index]-1].is_selected = 0;
143 	if (watched_stat_index < MAX_WATCH_STATS - 1)
144 		memmove(&(watch_this_stats[watched_stat_index]), &(watch_this_stats[watched_stat_index + 1]), (MAX_WATCH_STATS-watched_stat_index - 1) * sizeof(int));
145 	watch_this_stats[MAX_WATCH_STATS-1] = 0;
146 }
147 
148 
cm_statsbar_handler(window_info * win,int widget_id,int mx,int my,int option)149 static int cm_statsbar_handler(window_info *win, int widget_id, int mx, int my, int option)
150 {
151 	int i;
152 	int add_bar = 0;
153 
154 	// selecting the same stat more than once, removing the last bar
155 	// or adding too many is not possible as options are greyed out.
156 
157 	for (i=0; i<actual_num_disp_stats;i++)
158 	{
159 		if ((mx >= exp_bar_start_x+i*(stats_bar_len+exp_bar_text_len)) && (mx <= exp_bar_start_x+i*(stats_bar_len+exp_bar_text_len)+stats_bar_len))
160 		{
161 			// if deleting the bar, close any gap
162 			if (option == NUM_WATCH_STAT+1)
163 			{
164 				remove_watched_stat(i);
165 				init_stats_display();
166 			}
167 			else if (option == NUM_WATCH_STAT)
168 				add_bar = 1;
169 			else if (option < NUM_WATCH_STAT)
170 			{
171 				statsinfo[watch_this_stats[i]-1].is_selected=0;
172 				watch_this_stats[i] = option+1;
173 				statsinfo[option].is_selected=1;
174 			}
175 			break;
176 		}
177 	}
178 
179 	// if we want another bar, assigning it to the first unwatched stat
180 	if (add_bar)
181 	{
182 		int proposed_max_disp_stats = calc_max_disp_stats(calc_stats_bar_len(win, get_num_statsbar_exp()+1));
183 		int next_free = -1;
184 		for (i=0; i<NUM_WATCH_STAT-1; i++)
185 			if (statsinfo[i].is_selected == 0)
186 			{
187 				next_free = i;
188 				break;
189 			}
190 		for (i=0; next_free>=0 && i<proposed_max_disp_stats; i++)
191 			if (watch_this_stats[i] == 0)
192 			{
193 				watch_this_stats[i] = next_free + 1;
194 				statsinfo[next_free].is_selected =1 ;
195 				break;
196 			}
197 		init_stats_display();
198 	}
199 
200 	reset_statsbar_exp_cm_regions();
201 
202 	return 1;
203 }
204 
205 
206 // calculate the number of digits for the specified Uint32
Uint32_digits(Uint32 number)207 static Uint32 Uint32_digits(Uint32 number)
208 {
209 	Uint32 digits = 1;
210 	long step = 10;
211 	while ((step <= number) && (digits<11))
212 	{
213 		digits++;
214 		step *= 10;
215 	}
216 	return digits;
217 }
218 
219 
cm_statsbar_pre_show_handler(window_info * win,int widget_id,int mx,int my,window_info * cm_win)220 static void cm_statsbar_pre_show_handler(window_info *win, int widget_id, int mx, int my, window_info *cm_win)
221 {
222 	size_t i;
223 	int proposed_max_disp_stats = calc_max_disp_stats(calc_stats_bar_len(win, get_num_statsbar_exp()+1));
224 	for (i=0; i<NUM_WATCH_STAT-1; i++)
225 		cm_grey_line(cm_id, i, 0);
226 	for (i=0; i<MAX_WATCH_STATS; i++)
227 		if (watch_this_stats[i] > 0)
228 			cm_grey_line(cm_id, watch_this_stats[i]-1, 1);
229 	cm_grey_line(cm_id, NUM_WATCH_STAT, ((get_num_statsbar_exp() < proposed_max_disp_stats) ?0 :1));
230 	cm_grey_line(cm_id, NUM_WATCH_STAT+1, ((watch_this_stats[1]==0)?1:0));
231 }
232 
233 
draw_stats_bar(window_info * win,int x,int val,int len,float r,float g,float b,float r2,float g2,float b2)234 static void draw_stats_bar(window_info *win, int x, int val, int len, float r, float g, float b, float r2, float g2, float b2)
235 {
236 	char buf[32];
237 	int i; // i deals with massive bars by trimming at 110%
238 	int bar_height = player_statsbar_bar_height;
239 	int text_offset = (int)(0.5 + win->current_scale * 3);
240 	int y; // both bars and numbers are drawn centred vertically in the window
241 
242 	if(len>stats_bar_len*1.1)
243 		i=stats_bar_len*1.1;
244 	else
245 		i=len;
246 	glDisable(GL_TEXTURE_2D);
247 
248 	y = (int)(0.5 + (float)(win->len_y - bar_height) / 2.0);
249 	if(i >= 0){
250 		glBegin(GL_QUADS);
251 		//draw the colored section
252  		glColor3f(r2, g2, b2);
253 		glVertex3i(x, y+bar_height, 0);
254 		glColor3f(r, g, b);
255 		glVertex3i(x, y, 0);
256 		glColor3f(r, g, b);
257 		glVertex3i(x+i, y, 0);
258 		glColor3f(r2, g2, b2);
259 		glVertex3i(x+i, y+bar_height, 0);
260 		glEnd();
261 	}
262 	// draw the bar frame
263 	glColor3fv(gui_color);
264 	glBegin(GL_LINE_LOOP);
265 	glVertex3i(x, y, 0);
266 	glVertex3i(x+stats_bar_len, y, 0);
267 	glVertex3i(x+stats_bar_len, y+bar_height, 0);
268 	glVertex3i(x, y+bar_height, 0);
269 	glEnd();
270 	glEnable(GL_TEXTURE_2D);
271 
272 	// handle the text
273 	safe_snprintf(buf, sizeof(buf), "%d", val);
274 	y = (int)(0.5 + (float)win->len_y / 2.0);
275 	draw_text(x - text_offset, y, (const unsigned char*)buf, strlen(buf), UI_FONT, TDO_MAX_LINES, 1,
276 		TDO_SHADOW, 1, TDO_FOREGROUND, 0.8f, 0.8f, 0.8f, TDO_BACKGROUND, 0.0f, 0.0f, 0.0f,
277 		TDO_ZOOM, win->current_scale_small, TDO_VERTICAL_ALIGNMENT, CENTER_DIGITS, TDO_ALIGNMENT, RIGHT, TDO_END);
278 #ifdef OPENGL_TRACE
279 CHECK_GL_ERRORS();
280 #endif //OPENGL_TRACE
281 }
282 
283 
284 // check if we need to adjust exp_bar_text_len due to an exp change
recalc_exp_bar_text_len(window_info * win,int force)285 static int recalc_exp_bar_text_len(window_info *win, int force)
286 {
287 	static int init_flag = 1;
288 	static Uint32 last_exp[NUM_WATCH_STAT-1];
289 	static Uint32 last_to_go_len[NUM_WATCH_STAT-1];
290 	static Uint32 last_selected[NUM_WATCH_STAT-1];
291 	int recalc = init_flag || force;
292 	int i;
293 
294 	if (init_flag)
295 	{
296 		for (i=0; i<NUM_WATCH_STAT-1; i++)
297 		{
298 			last_exp[i] = *statsinfo[i].exp;
299 			last_to_go_len[i] = Uint32_digits(*statsinfo[i].next_lev - *statsinfo[i].exp);
300 			last_selected[i] = 0;
301 		}
302 		init_flag = 0;
303 	}
304 
305 	for (i=0; i<NUM_WATCH_STAT-1; i++)
306 	{
307 		/* if any exp changes, recalculate the number of digits for next level value */
308 		if (last_exp[i] != *statsinfo[i].exp)
309 		{
310 			unsigned int curr = Uint32_digits(*statsinfo[i].next_lev - *statsinfo[i].exp);
311 			/* if the number of digit changes, we need to recalulate exp_bar_text_len */
312 			if (last_to_go_len[i] != curr)
313 			{
314 				last_to_go_len[i] = curr;
315 				recalc = 1;
316 			}
317 			last_exp[i] = *statsinfo[i].exp;
318 		}
319 		/* if the selected status of any skill changed, we need to recalulate exp_bar_text_len */
320 		if (last_selected[i] != statsinfo[i].is_selected)
321 		{
322 			last_selected[i] = statsinfo[i].is_selected;
323 			recalc = 1;
324 		}
325 	}
326 
327 	/* recalc based only the skills being watched */
328 	if (recalc)
329 	{
330 		int max_len = 0;
331 		int max_digit_width = get_max_digit_width_zoom(win->font_category,
332 			win->current_scale_small);
333 		for (i=0; i<MAX_WATCH_STATS; i++)
334 			if ((watch_this_stats[i] > 0) && statsinfo[watch_this_stats[i]-1].is_selected &&
335 					(last_to_go_len[watch_this_stats[i]-1] > max_len))
336 				max_len = last_to_go_len[watch_this_stats[i]-1];
337 		return max_digit_width*(max_len+1.5);
338 	}
339 	else
340 		return exp_bar_text_len;
341 }
342 
343 
draw_exp_display(window_info * win)344 static void draw_exp_display(window_info *win)
345 {
346 	size_t i;
347 	int my_exp_bar_start_x = exp_bar_start_x;
348 
349 	// default to overall if no valid first skill is set
350 	if(watch_this_stats[0]<1 || watch_this_stats[0]>=NUM_WATCH_STAT)
351 	{
352 		watch_this_stats[0]=NUM_WATCH_STAT-1;
353 		statsinfo[watch_this_stats[0]-1].is_selected=1;
354 		reset_statsbar_exp_cm_regions();
355 	}
356 
357 	for (i=0; i<actual_num_disp_stats; i++)
358 	{
359 		if (watch_this_stats[i] > 0)
360 		{
361 			int icon_x = get_icons_win_active_len();
362 			int cur_exp = *statsinfo[watch_this_stats[i]-1].exp;
363 			int nl_exp = *statsinfo[watch_this_stats[i]-1].next_lev;
364 			int baselev = statsinfo[watch_this_stats[i]-1].skillattr->base;
365 			const unsigned char* name = statsinfo[watch_this_stats[i]-1].skillnames->name;
366 			int name_y = (int)(0.5 + (win->len_y + player_statsbar_bar_height) / 2.0) + scaled_line;
367 			int exp_adjusted_x_len;
368 			int delta_exp;
369 			float prev_exp;
370 			int name_width;
371 
372 			if(!baselev)
373 				prev_exp= 0;
374 			else
375 				prev_exp= exp_lev[baselev];
376 
377 			delta_exp= nl_exp-prev_exp;
378 
379 			if(!cur_exp || !nl_exp || delta_exp <=0)
380 				exp_adjusted_x_len= 0;
381 			else
382 				exp_adjusted_x_len= stats_bar_len-(float)stats_bar_len/(float)((float)delta_exp/(float)(nl_exp-cur_exp));
383 
384 			name_width = get_string_width_zoom(name, win->font_category, win->current_scale_small);
385 			// the the name would overlap with the icons...
386 			if (my_exp_bar_start_x + stats_bar_len - name_width < icon_x)
387 			{
388 				name = statsinfo[watch_this_stats[i]-1].skillnames->shortname;
389 				name_y = (int)(0.5 + (float)(win->len_y - get_line_height(UI_FONT, win->current_scale_small)) / 2.0) -
390 					get_center_offset(name, strlen((char *)name), UI_FONT, win->current_scale_small);
391 			}
392 
393 			draw_stats_bar(win, my_exp_bar_start_x, nl_exp - cur_exp, exp_adjusted_x_len, 0.1f, 0.8f, 0.1f, 0.1f, 0.4f, 0.1f);
394 			draw_string_small_shadowed_zoomed_right(my_exp_bar_start_x + stats_bar_len,
395 				name_y, name, 1,1.0f,1.0f,1.0f,0.0f,0.0f,0.0f, win->current_scale);
396 
397 			my_exp_bar_start_x += stats_bar_len+exp_bar_text_len;
398 		}
399 		else
400 			break;
401 	}
402 
403 }
404 
check_text_widths(window_info * win,int force)405 static void check_text_widths(window_info *win, int force)
406 {
407 	int proposed_len = 0;
408 	stats_bar_text_len = 4.5 * win->small_font_max_len_x;
409 	if ((proposed_len = recalc_exp_bar_text_len(win, force)) != exp_bar_text_len) // it will very rarely change
410 	{
411 		exp_bar_text_len = proposed_len;
412 		init_stats_display();
413 	}
414 }
415 
display_stats_bar_handler(window_info * win)416 static int	display_stats_bar_handler(window_info *win)
417 {
418 	static Uint32 last_time = 0;
419 	float health_adjusted_x_len;
420 	float food_adjusted_x_len;
421 	float mana_adjusted_x_len;
422 	float load_adjusted_x_len;
423 	float action_adjusted_x_len;
424 	int over_health_bar;
425 
426 	// the space taken up by the exp bar text is minimised, but may change
427 	// don't have to check often but this is an easy place to do it and its quick anyway
428 	if ((SDL_GetTicks()-last_time) > 250)
429 	{
430 		check_text_widths(win, 0);
431 		last_time = SDL_GetTicks();
432 	}
433 
434 	over_health_bar = statbar_cursor_x>health_bar_start_x && statbar_cursor_x < health_bar_start_x+stats_bar_len;
435 
436 	//get the adjusted length
437 
438 	if(!your_info.material_points.cur || !your_info.material_points.base)
439 		health_adjusted_x_len=0;//we don't want a div by 0
440 	else
441 		health_adjusted_x_len=stats_bar_len/((float)your_info.material_points.base/(float)your_info.material_points.cur);
442 
443 	if(your_info.food_level<=0)
444 		food_adjusted_x_len=0;//we don't want a div by 0
445 	else
446 		food_adjusted_x_len=stats_bar_len/((float)max_food_level/(float)your_info.food_level);
447 	if(food_adjusted_x_len>stats_bar_len) food_adjusted_x_len=stats_bar_len;
448 
449 	if(!your_info.ethereal_points.cur || !your_info.ethereal_points.base)
450 		mana_adjusted_x_len=0;//we don't want a div by 0
451 	else
452 		mana_adjusted_x_len=stats_bar_len/((float)your_info.ethereal_points.base/(float)your_info.ethereal_points.cur);
453 
454 	if(!your_info.carry_capacity.cur || !your_info.carry_capacity.base)
455 		load_adjusted_x_len=0;//we don't want a div by 0
456 	else
457 		load_adjusted_x_len=stats_bar_len/((float)your_info.carry_capacity.base/(float)your_info.carry_capacity.cur);
458 
459 	if(!your_info.action_points.cur || !your_info.action_points.base)
460 		action_adjusted_x_len=0;//we don't want a div by 0
461 	else
462 		action_adjusted_x_len=stats_bar_len/((float)your_info.action_points.base/(float)your_info.action_points.cur);
463 
464 	draw_stats_bar(win, health_bar_start_x, your_info.material_points.cur, health_adjusted_x_len, 1.0f, 0.2f, 0.2f, 0.5f, 0.2f, 0.2f);
465 
466 	if (your_info.food_level<=max_food_level) //yellow
467 		draw_stats_bar(win, food_bar_start_x, your_info.food_level, food_adjusted_x_len, 1.0f, 1.0f, 0.2f, 0.5f, 0.5f, 0.2f);
468 	else draw_stats_bar(win, food_bar_start_x, your_info.food_level, food_adjusted_x_len, 1.0f, 0.5f, 0.0f, 0.7f, 0.3f, 0.0f); //orange
469 
470 	draw_stats_bar(win, mana_bar_start_x, your_info.ethereal_points.cur, mana_adjusted_x_len, 0.2f, 0.2f, 1.0f, 0.2f, 0.2f, 0.5f);
471 	draw_stats_bar(win, load_bar_start_x, your_info.carry_capacity.base-your_info.carry_capacity.cur, load_adjusted_x_len, 0.6f, 0.4f, 0.4f, 0.4f, 0.2f, 0.2f);
472 	if (show_action_bar)
473 		draw_stats_bar(win, action_bar_start_x, your_info.action_points.cur, action_adjusted_x_len, 0.8f, 0.3f, 0.8f, 0.5f, 0.1f, 0.5f);
474 
475 	draw_exp_display(win);
476 
477 	if(show_help_text && statbar_cursor_x>=0)
478 	{
479 		int y_pos = -1.1 * get_line_height(UI_FONT, win->current_scale_small);
480 		if (over_health_bar)
481 			show_help((char*)attributes.material_points.name, health_bar_start_x, y_pos, win->current_scale);
482 		else if(statbar_cursor_x>food_bar_start_x && statbar_cursor_x < food_bar_start_x+stats_bar_len)
483 			show_help((char*)attributes.food.name, food_bar_start_x, y_pos, win->current_scale);
484 		else if(statbar_cursor_x>mana_bar_start_x && statbar_cursor_x < mana_bar_start_x+stats_bar_len)
485 			show_help((char*)attributes.ethereal_points.name, mana_bar_start_x, y_pos, win->current_scale);
486 		else if(statbar_cursor_x>load_bar_start_x && statbar_cursor_x < load_bar_start_x+stats_bar_len)
487 			show_help((char*)attributes.carry_capacity.name, load_bar_start_x, y_pos, win->current_scale);
488 		else if(show_action_bar && statbar_cursor_x>action_bar_start_x && statbar_cursor_x < action_bar_start_x+stats_bar_len)
489 			show_help((char*)attributes.action_points.name, action_bar_start_x, y_pos, win->current_scale);
490 	}
491 
492 	if ((over_health_bar) || (show_last_health_change_always && get_show_window (game_root_win) && ((use_windowed_chat == 2) || !input_text_line.len)))
493 		draw_last_health_change(win);
494 
495 	statbar_cursor_x = -1;
496 
497 	return 1;
498 }
499 
500 
mouseover_stats_bar_handler(window_info * win,int mx,int my)501 static int mouseover_stats_bar_handler(window_info *win, int mx, int my)
502 {
503 	statbar_cursor_x=mx;
504 	return 0;
505 }
506 
507 
508 // the stats display
ui_scale_stats_bar_handler(window_info * win)509 static int ui_scale_stats_bar_handler(window_info *win)
510 {
511 	int i;
512 	int num_exp = get_num_statsbar_exp();
513 	int proposed_max_disp_stats = 0;
514 
515 	scaled_line = (int)(0.5 + win->current_scale);
516 	player_statsbar_bar_height = (int)(0.5 + win->current_scale * 8);
517 
518 	init_window(stats_bar_win, -1, 0, 0, window_height - HUD_MARGIN_Y + scaled_line,
519 		window_width - HUD_MARGIN_X, get_player_statsbar_active_height());
520 
521 	/* use a fixed width for user attrib stat bar text */
522 	stats_bar_text_len = 4.5 * win->small_font_max_len_x;
523 
524 	// calculate the statsbar len given curent config
525 	stats_bar_len = calc_stats_bar_len(win, num_exp);
526 
527 	// calculate the maximum number of exp bars we can have
528 	proposed_max_disp_stats = calc_max_disp_stats(stats_bar_len);
529 
530 	// if we need to reduce the number of bars, recalculate the optimum stats bar len
531 	if (num_exp > proposed_max_disp_stats)
532 	{
533 		stats_bar_len = calc_stats_bar_len(win, proposed_max_disp_stats);
534 		actual_num_disp_stats = proposed_max_disp_stats;
535 	}
536 	else
537 		actual_num_disp_stats = num_exp;
538 
539 	// calculate the stats bar x position
540 	mana_bar_start_x = stats_bar_text_len;
541 	food_bar_start_x = stats_bar_len + 2 * stats_bar_text_len;
542 	health_bar_start_x = 2 * stats_bar_len + 3 * stats_bar_text_len;
543 	load_bar_start_x = 3 * stats_bar_len + 4 * stats_bar_text_len;
544 	if (show_action_bar)
545 		action_bar_start_x = 4 * stats_bar_len + 5 * stats_bar_text_len;
546 
547 	// the x position of the first exp bar, keep right aligned
548 	exp_bar_start_x = window_width + exp_bar_text_len - HUD_MARGIN_X - 2
549 		- actual_num_disp_stats * (exp_bar_text_len + stats_bar_len);
550 
551 	// create the exp bars context menu, used by all active exp bars
552 	if (!cm_valid(cm_id))
553 	{
554 		int thestat;
555 		cm_id = cm_create(NULL, cm_statsbar_handler);
556 		for (thestat=0; thestat<NUM_WATCH_STAT-1; thestat++)
557 			cm_add(cm_id, (char *)statsinfo[thestat].skillnames->name, NULL);
558 		cm_add(cm_id, cm_stats_bar_base_str, NULL);
559 		cm_bool_line(cm_id, NUM_WATCH_STAT+2, &lock_skills_selection, NULL);
560 		cm_set_pre_show_handler(cm_id,cm_statsbar_pre_show_handler);
561 	}
562 	reset_statsbar_exp_cm_regions();
563 
564 	for (i=0; i<MAX_WATCH_STATS; i++)
565 	{
566 		if (watch_this_stats[i] > 0)
567 			statsinfo[watch_this_stats[i]-1].is_selected = 1;
568 	}
569 
570 	check_text_widths(win, 1);
571 
572 	return 1;
573 }
574 
change_stats_bar_font_handler(window_info * win,font_cat cat)575 static int change_stats_bar_font_handler(window_info* win, font_cat cat)
576 {
577 	if (cat != UI_FONT)
578 		return 0;
579 	check_text_widths(win, 1);
580 	return 1;
581 }
582 
583 //create the stats bar window
init_stats_display(void)584 void init_stats_display(void)
585 {
586 	if(stats_bar_win < 0)
587 	{
588 		static size_t cm_id_ap = CM_INIT_VALUE;
589 		stats_bar_win= create_window("Stats Bar", -1, 0, 0, 0, 0, 0, ELW_USE_UISCALE|ELW_TITLE_NONE|ELW_SHOW_LAST);
590 		set_window_handler(stats_bar_win, ELW_HANDLER_DISPLAY, &display_stats_bar_handler);
591 		set_window_handler(stats_bar_win, ELW_HANDLER_MOUSEOVER, &mouseover_stats_bar_handler);
592 		set_window_handler(stats_bar_win, ELW_HANDLER_UI_SCALE, &ui_scale_stats_bar_handler);
593 		set_window_handler(stats_bar_win, ELW_HANDLER_FONT_CHANGE, &change_stats_bar_font_handler);
594 
595 		// context menu to enable/disable the action points bar
596 		cm_id_ap = cm_create(cm_statsbar_str, NULL);
597 		cm_add_window(cm_id_ap, stats_bar_win);
598 		cm_bool_line(cm_id_ap, 0, &show_action_bar, "show_action_bar");
599 		cm_bool_line(cm_id_ap, 1, &show_last_health_change_always, "show_last_health_change_always");
600 	}
601 	if (stats_bar_win >= 0 && stats_bar_win < windows_list.num_windows)
602 		ui_scale_stats_bar_handler(&windows_list.window[stats_bar_win]);
603 }
604 
605 
handle_stats_selection(int stat,Uint32 flags)606 void handle_stats_selection(int stat, Uint32 flags)
607 {
608 	int i;
609 
610 	if (lock_skills_selection || stats_bar_win < 0 || stats_bar_win >= windows_list.num_windows)
611 	{
612 		do_alert1_sound();
613 		return;
614 	}
615 
616 	if ((flags & KMOD_ALT) || (flags & KMOD_SHIFT))
617 	{
618 		for (i=0;i<MAX_WATCH_STATS;i++)
619 		{
620 			// if already selected, unselect and remove bar, closing any gap
621 			if (watch_this_stats[i]==stat)
622 			{
623 				remove_watched_stat(i);
624 				break;
625 			}
626 			// if the bar is not in use, set it to the new stat
627 			if (watch_this_stats[i]==0)
628 			{
629 				watch_this_stats[i]=stat;
630 				statsinfo[stat-1].is_selected=1;
631 				break;
632 			}
633 		}
634 	}
635 	else
636 	{
637 		// if not already selected, select the stat and replace the first bar
638 		if (statsinfo[stat-1].is_selected==0)
639 		{
640 			statsinfo[watch_this_stats[0]-1].is_selected=0;
641 			watch_this_stats[0] = stat;
642 			statsinfo[stat-1].is_selected=1;
643 		}
644 		// else unselect the stat and remove the bar, closing any gap
645 		else
646 		{
647 			for (i=0;i<MAX_WATCH_STATS;i++)
648 				if (watch_this_stats[i] == stat)
649 				{
650 					remove_watched_stat(i);
651 					break;
652 				}
653 		}
654 	}
655 
656 	// default to overall if no valid first skill is set
657 	if(watch_this_stats[0]<1 || watch_this_stats[0]>=NUM_WATCH_STAT)
658 	{
659 		watch_this_stats[0]=NUM_WATCH_STAT-1;
660 		statsinfo[watch_this_stats[0]-1].is_selected=1;
661 	}
662 
663 	init_stats_display();
664 	do_click_sound();
665 }
666 
667 
668 /* called when our actor receives damage, displayed as hover over health bar */
set_last_damage(int quantity)669 void set_last_damage(int quantity)
670 {
671 	my_last_health.d = quantity;
672 	my_last_health.dt = SDL_GetTicks();
673 }
674 
675 
676 /* called when our actor heals, displayed as hover over health bar */
set_last_heal(int quantity)677 void set_last_heal(int quantity)
678 {
679 	my_last_health.h = quantity;
680 	my_last_health.ht = SDL_GetTicks();
681 }
682 
683 
set_statsbar_watched_stats(int * cfg_watch_this_stats)684 void set_statsbar_watched_stats(int *cfg_watch_this_stats)
685 {
686 	int i;
687 #if MAX_WATCH_STATS != 5
688 #error You cannot just go around changing MAX_WATCH_STATS as its used by the cfg file so change init.h too.
689 #endif
690 	for(i=0;i<MAX_WATCH_STATS;i++)
691 	{
692 		watch_this_stats[i] = cfg_watch_this_stats[i];
693 		if (watch_this_stats[i]<0 || watch_this_stats[i]>=NUM_WATCH_STAT)
694 			watch_this_stats[i]=0;
695 	}
696 	if(watch_this_stats[0]<1 || watch_this_stats[0]>=NUM_WATCH_STAT)
697 		watch_this_stats[0]=NUM_WATCH_STAT-1;
698 }
699 
get_statsbar_watched_stats(int * cfg_watch_this_stats)700 void get_statsbar_watched_stats(int *cfg_watch_this_stats)
701 {
702 	int i;
703 	for(i=0;i<MAX_WATCH_STATS;i++)
704 		cfg_watch_this_stats[i]=watch_this_stats[i];
705 }
706