Crossfire Client, Trunk  R20612
stats.c
Go to the documentation of this file.
1 /*
2  * Crossfire -- cooperative multi-player graphical RPG and adventure game
3  *
4  * Copyright (c) 1999-2013 Mark Wedel and the Crossfire Development Team
5  * Copyright (c) 1992 Frank Tore Johansen
6  *
7  * Crossfire is free software and comes with ABSOLUTELY NO WARRANTY. You are
8  * welcome to redistribute it under certain conditions. For details, please
9  * see COPYING and LICENSE.
10  *
11  * The authors can be reached via e-mail at <crossfire@metalforge.org>.
12  */
13 
19 #include "client.h"
20 
21 #include <assert.h>
22 #include <gtk/gtk.h>
23 
24 #include "main.h"
25 #include "image.h"
26 #include "gtk2proto.h"
27 
28 #define STAT_BAR_HP 0
29 #define STAT_BAR_SP 1
30 #define STAT_BAR_GRACE 2
31 #define STAT_BAR_FOOD 3
32 #define STAT_BAR_EXP 4
33 #define MAX_STAT_BARS 5
34 
35 static const char * const stat_bar_names[MAX_STAT_BARS] = {
36  "hp", "sp", "grace", "food", "exp"
37 };
38 
39 static GtkWidget *stat_bar[MAX_STAT_BARS];
40 
41 #define STYLE_NORMAL 0
42 #define STYLE_LOW 1
43 #define STYLE_SUPER 2
44 #define STYLE_GRAD_NORMAL 3
45 #define STYLE_GRAD_LOW 4
46 #define STYLE_GRAD_SUPER 5
47 #define NUM_STYLES 6
48 
49 /* The name of the symbolic widget names we try to look up the styles of
50  * (these will be prefixed with hp_, sp_, etc). This should always match
51  * NUM_STYLES.
52  */
53 static const char * const stat_style_names[NUM_STYLES] = {
54  "bar_normal", "bar_low", "bar_super",
55  "gradual_bar_normal", "gradual_bar_low", "gradual_bar_super"
56 };
57 
58 /* We really only care about the colors, as there isn't anything else we can
59  * change about the progressbar widget itself.
60  */
62 
63 
64 /* The table for showing skill exp is an x & y grid. Note for proper
65  * formatting, SKILL_BOXES_X must be even. Hmmm - perhaps these should
66  * instead be dynamically generated?
67  */
68 #define SKILL_BOXES_X 6
69 #define SKILL_BOXES_Y 17
70 
71 #define PROTECTION_BOXES_X 6
72 #define PROTECTION_BOXES_Y 6
73 
74 typedef struct {
75  GtkWidget *playername;
76  GtkWidget *Str;
77  GtkWidget *Dex;
78  GtkWidget *Con;
79  GtkWidget *Int;
80  GtkWidget *Wis;
81  GtkWidget *Cha;
82  GtkWidget *Pow;
83  GtkWidget *wc;
84  GtkWidget *dam;
85  GtkWidget *ac;
86  GtkWidget *armor;
87  GtkWidget *speed;
88  GtkWidget *weapon_speed;
89  GtkWidget *range;
90  GtkWidget *exp;
91  GtkWidget *level;
92  GtkWidget *table_skills_exp;
93  GtkWidget *table_protections;
94  GtkWidget *skill_exp[SKILL_BOXES_X * SKILL_BOXES_Y];
95  GtkWidget *resists[PROTECTION_BOXES_X * PROTECTION_BOXES_Y];
96 
97 } StatWindow;
98 
100 
101 static gboolean need_mapping_update;
102 
104 
110 {
111  static int has_init=0;
112  int stat_bar, sub_style;
113  char buf[MAX_BUF];
114  GtkStyle *tmp_style;
115 
116  if (!has_init) {
117  memset(bar_colors, 0, sizeof(GdkColor*) * MAX_STAT_BARS * NUM_STYLES);
118  }
119 
120  for (stat_bar=0; stat_bar < MAX_STAT_BARS; stat_bar++) {
121  for (sub_style=0; sub_style < NUM_STYLES; sub_style++) {
122  snprintf(buf, sizeof(buf), "%s_%s", stat_bar_names[stat_bar], stat_style_names[sub_style]);
123 
124  tmp_style = gtk_rc_get_style_by_paths(gtk_settings_get_default(), NULL, buf, G_TYPE_NONE);
125 
126  if (!tmp_style) {
127  if (bar_colors[stat_bar][sub_style]) {
128  free(bar_colors[stat_bar][sub_style]);
129  bar_colors[stat_bar][sub_style] = NULL;
130  }
131  LOG(LOG_INFO, "stats.c::stats_get_styles()", "Unable to find style '%s'", buf);
132  } else {
133  if (!bar_colors[stat_bar][sub_style]) {
134  bar_colors[stat_bar][sub_style] = calloc(1, sizeof(GdkColor));
135  }
136  memcpy(bar_colors[stat_bar][sub_style],
137  &tmp_style->base[GTK_STATE_SELECTED], sizeof(GdkColor));
138  }
139  }
140  }
141 }
142 
147 void stats_init(GtkWidget *window_root) {
148  int i, x, y;
149  char buf[MAX_BUF];
150 
151  for (i=0; i<MAX_STAT_BARS; i++) {
152  snprintf(buf, sizeof(buf), "progressbar_%s", stat_bar_names[i]);
153  stat_bar[i] = GTK_WIDGET(gtk_builder_get_object(window_xml, buf));
154 
155  lastval[i] = -1;
156  lastmax[i] = -1;
157  }
158 
159  statwindow.playername =
160  GTK_WIDGET(gtk_builder_get_object(window_xml, "label_playername"));
161  statwindow.Str =
162  GTK_WIDGET(gtk_builder_get_object(window_xml, "label_str"));
163  statwindow.Dex =
164  GTK_WIDGET(gtk_builder_get_object(window_xml, "label_dex"));
165  statwindow.Con =
166  GTK_WIDGET(gtk_builder_get_object(window_xml, "label_con"));
167  statwindow.Int =
168  GTK_WIDGET(gtk_builder_get_object(window_xml, "label_int"));
169  statwindow.Wis =
170  GTK_WIDGET(gtk_builder_get_object(window_xml, "label_wis"));
171  statwindow.Pow =
172  GTK_WIDGET(gtk_builder_get_object(window_xml, "label_pow"));
173  statwindow.Cha =
174  GTK_WIDGET(gtk_builder_get_object(window_xml, "label_cha"));
175  statwindow.wc =
176  GTK_WIDGET(gtk_builder_get_object(window_xml, "label_wc"));
177  statwindow.dam =
178  GTK_WIDGET(gtk_builder_get_object(window_xml, "label_dam"));
179  statwindow.ac =
180  GTK_WIDGET(gtk_builder_get_object(window_xml, "label_ac"));
181  statwindow.armor =
182  GTK_WIDGET(gtk_builder_get_object(window_xml, "label_armor"));
183  statwindow.speed =
184  GTK_WIDGET(gtk_builder_get_object(window_xml, "label_speed"));
185  statwindow.weapon_speed =
186  GTK_WIDGET(gtk_builder_get_object(window_xml, "label_weapon_speed"));
187  statwindow.range =
188  GTK_WIDGET(gtk_builder_get_object(window_xml, "label_range"));
189  statwindow.exp =
190  GTK_WIDGET(gtk_builder_get_object(window_xml, "label_exp"));
191  statwindow.level =
192  GTK_WIDGET(gtk_builder_get_object(window_xml, "label_level"));
193 
194  /* Note that the order the labels are attached to the tables determines
195  * the order of display. The order as right now is left to right,
196  * then top to bottom, which means that is the order if displaying
197  * skills & protections.
198  */
199 
200  statwindow.table_skills_exp =
201  GTK_WIDGET(gtk_builder_get_object(window_xml,"table_skills_exp"));
202 
203  if (statwindow.table_skills_exp) {
204  /* Do not attempt to set up the table_skills_exp widget if it was not
205  * defined in the layout.
206  */
207  for (i=0, x=0, y=0; i < SKILL_BOXES_X * SKILL_BOXES_Y; i++) {
208  statwindow.skill_exp[i] = gtk_label_new("");
209  gtk_table_attach(GTK_TABLE(statwindow.table_skills_exp), statwindow.skill_exp[i],
210  x, x+1, y, y+1, GTK_EXPAND, 0, 0, 0);
211  gtk_widget_show(statwindow.skill_exp[i]);
212  x++;
213  if (x == SKILL_BOXES_X) {
214  x=0;
215  y++;
216  }
217  }
218  }
219 
220  statwindow.table_protections =
221  GTK_WIDGET(gtk_builder_get_object(window_xml, "table_protections"));
222 
223  for (i=0, x=0, y=0; i < PROTECTION_BOXES_X * PROTECTION_BOXES_Y; i++) {
224  statwindow.resists[i] = gtk_label_new("");
225  gtk_table_attach(GTK_TABLE(statwindow.table_protections), statwindow.resists[i],
226  x, x+1, y, y+1, GTK_EXPAND, 0, 0, 0);
227  gtk_widget_show(statwindow.resists[i]);
228  x++;
229  if (x == PROTECTION_BOXES_X) {
230  x=0;
231  y++;
232  }
233  }
234  /*stats_get_styles();*/
235 }
236 
243 static char *format_si_number(gint64 number) {
244  /* List of SI prefixes and corresponding values, least to greatest. */
245  const char SI_SUFFIX[] = {'\0', 'k', 'M', 'G'};
246  const float SI_VALUE[] = {1, 1000, 1000000, 1000000000};
247 
248  int suffix = 0, i;
249  float value;
250 
251  /* Determine the most appropriate SI prefix to use. */
252  for (i = sizeof(SI_SUFFIX) / sizeof(char) - 1; i > 0; i--) {
253  if (number / SI_VALUE[i] >= 10) {
254  suffix = i;
255  break;
256  }
257  }
258 
259  value = number / SI_VALUE[suffix];
260 
261  /* If possible, trim the trailing zero decimal. */
262  if (value - (int)value == 0) {
263  return g_strdup_printf("%.0f%c", value, SI_SUFFIX[suffix]);
264  } else {
265  return g_strdup_printf("%.1f%c", value, SI_SUFFIX[suffix]);
266  }
267 }
268 
291 void update_stat(int stat_no, gint64 max_stat, gint64 current_stat,
292  gint64 statbar_max, gint64 statbar_stat, int can_alert)
293 {
294  float bar;
295  GdkColor ncolor, *set_color=NULL;
296 
297  /* If nothing changed, don't need to do anything */
298  if (lastval[stat_no] == current_stat && lastmax[stat_no] == max_stat) {
299  return;
300  }
301 
302  lastval[stat_no] = current_stat;
303  lastmax[stat_no] = max_stat;
304 
305  if (statbar_max > 0) {
306  bar = (float) statbar_stat / (float) statbar_max;
307  } else {
308  bar = 0.0;
309  }
310 
312  /* In this mode, the color of the stat bar were go between red and green
313  * in a gradual style. Color is blended from low to high
314  */
315 
316  GdkColor *hcolor, *lcolor;
317  float diff;
318 
319  /* First thing we do is figure out current values, and thus what color
320  * bases we use (based on super charged or normal value). We also set
321  * up diff as where we are between those two points. In this way, the
322  * blending logic below is the same regardless of actual value.
323  */
324  if (bar > 1.0) {
325  if (bar>2.0) {
326  bar=2.0; /* Display unaffected; just calculations */
327  }
328  hcolor = bar_colors[stat_no][STYLE_GRAD_SUPER];
329  lcolor = bar_colors[stat_no][STYLE_GRAD_NORMAL];
330  diff = bar - 1.0;
331  } else {
332  if (bar < 0.0) {
333  bar=0.0; /* Like above, does not affect display */
334  }
335  hcolor = bar_colors[stat_no][STYLE_GRAD_NORMAL];
336  lcolor = bar_colors[stat_no][STYLE_GRAD_LOW];
337  diff = bar;
338  }
339  /* Now time to blend. First, make sure colors are set. then, we use
340  * the lcolor as the base, making adjustments based on hcolor. Values
341  * in hcolor may be lower than lcolor, but that just means we
342  * substract from lcolor, not add.
343  */
344 
345  if (lcolor && hcolor) {
346 #if 1
347  memcpy(&ncolor, lcolor, sizeof(GdkColor));
348 
349  ncolor.red += (hcolor->red - lcolor->red) * diff;
350  ncolor.green += (hcolor->green - lcolor->green) * diff;
351  ncolor.blue += (hcolor->blue - lcolor->blue) * diff;
352 #else
353  /* This is an alternate coloring method that works when using
354  * saturated colors for the base points. This mimics the old
355  * code, and works good when using such saturated colors (eg, one
356  * of the RGB triplets being 255, others 0, like red, green, or
357  * blue). However, this doesn't produce very good results when
358  * not using those colors - if say magenta and yellow are chosen
359  * as the two colors, this code results in the colors basically
360  * getting near white in the middle values. For saturated colors,
361  * the code below would produce nice bright yellow for the middle
362  * values, where as the code above produces more a dark yellow,
363  * since it only takes half the red and half the green. However,
364  * the code above still produces useful results even with that
365  * limitation, and it works for all colors, so it is the code
366  * enabled. It perhaps be interesting to have some detection
367  * logic on how the colors are actually set - if only a single
368  * r/g/b value is set for the two colors, then use this logic
369  * here, otherwise the above logic or something.
370  * MSW 2007-01-24
371  */
372  if (diff > 0.5) {
373  memcpy(&ncolor, hcolor, sizeof(GdkColor));
374 
375  if (lcolor->red > hcolor->red) {
376  ncolor.red = 2 * lcolor->red * (1.0 - diff);
377  }
378 
379  if (lcolor->green > hcolor->green) {
380  ncolor.green = 2 * lcolor->green * (1.0 - diff);
381  }
382 
383  if (lcolor->blue > hcolor->blue) {
384  ncolor.blue = 2 * lcolor->blue * (1.0 - diff);
385  }
386 
387  } else {
388  memcpy(&ncolor, lcolor, sizeof(GdkColor));
389 
390  if (hcolor->red > lcolor->red) {
391  ncolor.red = 2 * hcolor->red * diff;
392  }
393 
394  if (hcolor->green > lcolor->green) {
395  ncolor.green = 2 * hcolor->green * diff;
396  }
397 
398  if (hcolor->blue > lcolor->blue) {
399  ncolor.blue = 2 * hcolor->blue * diff;
400  }
401  }
402 #endif
403 #if 0
404  fprintf(stderr,"stat %d, val %d, r/g/b=%d/%d/%d\n",
405  stat_no, current_stat, ncolor.red, ncolor.green, ncolor.blue);
406 #endif
407  set_color = &ncolor;
408  }
409  } else {
410  if (statbar_stat * 4 < statbar_max) {
411  set_color = bar_colors[stat_no][STYLE_LOW];
412  } else if (statbar_stat > statbar_max) {
413  set_color = bar_colors[stat_no][STYLE_SUPER];
414  } else {
415  set_color = bar_colors[stat_no][STYLE_NORMAL];
416  }
417  }
418  if (bar > 1.0) {
419  bar = 1.0;
420  }
421  if (bar < 0.0) {
422  bar = 0.0;
423  }
424 
425  GtkProgressBar *curr_bar = GTK_PROGRESS_BAR(stat_bar[stat_no]);
426 
427  /* It may be a waste, but we set the color everytime here - it isn't very
428  * costly, and keeps us from tracing the last color we set. Note that
429  * set_color could be null, which means it reverts back to normal color.
430  */
431  gtk_widget_modify_base(stat_bar[stat_no], GTK_STATE_SELECTED, set_color);
432  /* The line above doesn't work sometimes, but then, the next one does. */
433  gtk_widget_modify_bg(stat_bar[stat_no], GTK_STATE_PRELIGHT, set_color);
434 
435  char *label;
436 
437  // Display experience using SI prefixes; everything else normally.
438  if (stat_no == STAT_BAR_EXP) {
439  char *exp_curr = format_si_number(current_stat);
440  char *exp_max = format_si_number(max_stat);
441  label = g_strdup_printf("%s/%s", exp_curr, exp_max);
442  g_free(exp_curr);
443  g_free(exp_max);
444  } else {
445  label = g_strdup_printf("%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT,
446  current_stat, max_stat);
447  }
448 
449  gtk_progress_bar_set_fraction(curr_bar, bar);
450  gtk_progress_bar_set_text(curr_bar, label);
451  g_free(label);
452 }
453 
460 {
461  static int lastbeep=0;
462  static gint64 level_diff;
463 
465  cpl.stats.maxhp, cpl.stats.hp, TRUE);
467  cpl.stats.maxsp, cpl.stats.sp, TRUE);
469  cpl.stats.maxgrace, cpl.stats.grace, TRUE);
470  update_stat(STAT_BAR_FOOD, 999, cpl.stats.food, 999, cpl.stats.food, TRUE);
471 
472  /* We may or may not have an exp table from the server. If we don't, just
473  * use current exp value so it will always appear maxed out.
474  */
475  /* We calculate level_diff here just so it makes the update_stat()
476  * call below less messy.
477  */
478  if ((cpl.stats.level+1) < exp_table_max) {
479  level_diff = exp_table[cpl.stats.level+1] - exp_table[cpl.stats.level];
480  } else {
481  level_diff=cpl.stats.exp;
482  }
483 
486  cpl.stats.exp,
487  level_diff,
488  (cpl.stats.level+1) < exp_table_max ?
489  (cpl.stats.exp - exp_table[cpl.stats.level]):cpl.stats.exp, FALSE);
490 
491  if (use_config[CONFIG_FOODBEEP] && (cpl.stats.food%4==3) && (cpl.stats.food < 200)) {
492  gdk_beep( );
493  } else if (use_config[CONFIG_FOODBEEP] && cpl.stats.food == 0 && ++lastbeep == 5) {
494  lastbeep = 0;
495  gdk_beep( );
496  }
497 }
498 
504 {
505  if (!a->name && !b->name) {
506  return 0;
507  }
508  if (!a->name) {
509  return 1;
510  }
511  if (!b->name) {
512  return -1;
513  } else {
514  return g_ascii_strcasecmp(a->name, b->name);
515  }
516 }
517 
521 static void update_stat_mapping(void)
522 {
523  int i;
524 
525  for (i=0; i < MAX_SKILL; i++) {
526  skill_mapping[i].value=i;
527  if (skill_names[i]) {
529  } else {
530  skill_mapping[i].name = NULL;
531  }
532  }
533  qsort(skill_mapping, MAX_SKILL, sizeof(NameMapping),
534  (int (*)(const void*, const void*))mapping_sort);
535 
536  for (i=0; i < NUM_RESISTS; i++) {
537  resist_mapping[i].value=i;
538  if (resists_name[i]) {
540  } else {
541  resist_mapping[i].name = NULL;
542  }
543  }
544  qsort(resist_mapping, NUM_RESISTS, sizeof(NameMapping),
545  (int (*)(const void*, const void*))mapping_sort);
546 
547  need_mapping_update = FALSE;
548 }
549 
555 {
556  static Stats last_stats;
557  static char last_name[MAX_BUF]="", last_range[MAX_BUF]="";
558  static int init_before=0, max_drawn_skill=0, max_drawn_resists=0;
559 
560  float weap_sp;
561  char buff[MAX_BUF];
562  int i, on_skill, sk;
563 
564  if (!init_before) {
565  init_before=1;
566  memset(&last_stats, 0, sizeof(Stats));
567  }
568 
569  /* skill_names gets set as part of the initialization with the
570  * client - however, right now, there is no callback when
571  * it is set, so instead, just track that wee need to update
572  * and see if it changes.
573  */
574  if (need_mapping_update && skill_names[1] != NULL) {
576  }
577 
578  if (strcmp(cpl.title, last_name) || redraw) {
579  strcpy(last_name, cpl.title);
580  gtk_label_set_text(GTK_LABEL(statwindow.playername), cpl.title);
581  }
582 
583  if (redraw || cpl.stats.exp != last_stats.exp) {
584  last_stats.exp = cpl.stats.exp;
585  snprintf(buff, sizeof(buff), "Experience: %5" G_GINT64_FORMAT,
586  cpl.stats.exp);
587  gtk_label_set_text(GTK_LABEL(statwindow.exp), buff);
588  }
589 
590  if (redraw || cpl.stats.level != last_stats.level) {
591  last_stats.level = cpl.stats.level;
592  snprintf(buff, sizeof(buff), "Level: %d", cpl.stats.level);
593  gtk_label_set_text(GTK_LABEL(statwindow.level), buff);
594  }
595 
596  if (redraw || cpl.stats.Str != last_stats.Str) {
597  last_stats.Str = cpl.stats.Str;
598  snprintf(buff, sizeof(buff), "%2d", cpl.stats.Str);
599  gtk_label_set_text(GTK_LABEL(statwindow.Str), buff);
600  }
601 
602  if (redraw || cpl.stats.Dex != last_stats.Dex) {
603  last_stats.Dex = cpl.stats.Dex;
604  snprintf(buff, sizeof(buff), "%2d", cpl.stats.Dex);
605  gtk_label_set_text(GTK_LABEL(statwindow.Dex), buff);
606  }
607 
608  if (redraw || cpl.stats.Con != last_stats.Con) {
609  last_stats.Con = cpl.stats.Con;
610  snprintf(buff, sizeof(buff), "%2d", cpl.stats.Con);
611  gtk_label_set_text(GTK_LABEL(statwindow.Con), buff);
612  }
613 
614  if (redraw || cpl.stats.Int != last_stats.Int) {
615  last_stats.Int = cpl.stats.Int;
616  snprintf(buff, sizeof(buff), "%2d", cpl.stats.Int);
617  gtk_label_set_text(GTK_LABEL(statwindow.Int), buff);
618  }
619 
620  if (redraw || cpl.stats.Wis != last_stats.Wis) {
621  last_stats.Wis = cpl.stats.Wis;
622  snprintf(buff, sizeof(buff), "%2d", cpl.stats.Wis);
623  gtk_label_set_text(GTK_LABEL(statwindow.Wis), buff);
624  }
625 
626  if (redraw || cpl.stats.Pow != last_stats.Pow) {
627  last_stats.Pow = cpl.stats.Pow;
628  snprintf(buff, sizeof(buff), "%2d", cpl.stats.Pow);
629  gtk_label_set_text(GTK_LABEL(statwindow.Pow), buff);
630  }
631 
632  if (redraw || cpl.stats.Cha != last_stats.Cha) {
633  last_stats.Cha = cpl.stats.Cha;
634  snprintf(buff, sizeof(buff), "%2d", cpl.stats.Cha);
635  gtk_label_set_text(GTK_LABEL(statwindow.Cha), buff);
636  }
637 
638  if (redraw || cpl.stats.wc != last_stats.wc) {
639  last_stats.wc = cpl.stats.wc;
640  snprintf(buff, sizeof(buff), "%2d", cpl.stats.wc);
641  gtk_label_set_text(GTK_LABEL(statwindow.wc), buff);
642  }
643 
644  if (redraw || cpl.stats.dam != last_stats.dam) {
645  last_stats.dam = cpl.stats.dam;
646  snprintf(buff, sizeof(buff), "%d", cpl.stats.dam);
647  gtk_label_set_text(GTK_LABEL(statwindow.dam), buff);
648  }
649 
650  if (redraw || cpl.stats.ac != last_stats.ac) {
651  last_stats.ac = cpl.stats.ac;
652  snprintf(buff, sizeof(buff), "%d", cpl.stats.ac);
653  gtk_label_set_text(GTK_LABEL(statwindow.ac), buff);
654  }
655 
656  if (redraw || cpl.stats.resists[0] != last_stats.resists[0]) {
657  last_stats.resists[0] = cpl.stats.resists[0];
658  snprintf(buff, sizeof(buff), "%d", cpl.stats.resists[0]);
659  gtk_label_set_text(GTK_LABEL(statwindow.armor), buff);
660  }
661 
662  if (redraw || cpl.stats.speed != last_stats.speed) {
663  last_stats.speed = cpl.stats.speed;
664  snprintf(buff, sizeof(buff), "%3.2f",
665  (float)cpl.stats.speed / FLOAT_MULTF);
666  gtk_label_set_text(GTK_LABEL(statwindow.speed), buff);
667  }
668  /* sc_version >= 1029 reports real value of weapon speed -
669  * not as a factor of player speed. Handle accordingly.
670  */
671  if (csocket.sc_version >= 1029) {
672  weap_sp = (float)cpl.stats.weapon_sp / FLOAT_MULTF;
673  } else {
674  weap_sp = (float)cpl.stats.speed / ((float)cpl.stats.weapon_sp);
675  }
676 
677  if (redraw || weap_sp != last_stats.weapon_sp) {
678  last_stats.weapon_sp = weap_sp;
679  snprintf(buff, sizeof(buff), "%3.2f", weap_sp);
680  gtk_label_set_text(GTK_LABEL(statwindow.weapon_speed), buff);
681  }
682 
683  if (redraw || strcmp(cpl.range, last_range)) {
684  strcpy(last_range, cpl.range);
685  snprintf(buff, sizeof(buff), "Range: %s", cpl.range);
686  gtk_label_set_text(GTK_LABEL(statwindow.range), cpl.range);
687  }
688 
690 
691  if (statwindow.table_skills_exp) {
692  /* Do not attempt to set up the table_skills_exp widget if it was not
693  * defined in the layout.
694  */
695  on_skill=0;
696  assert(sizeof(statwindow.skill_exp)/sizeof(*statwindow.skill_exp) >= 2*MAX_SKILL);
697  for (i=0; i<MAX_SKILL; i++) {
698  /* Drawing a particular skill entry is tricky - only draw if
699  * different, and only draw if we have a name for the skill and
700  * the player has some exp in the skill - don't draw all 30 skills
701  * for no reason.
702  */
703  sk = skill_mapping[i].value;
704 
705  if ((redraw || cpl.stats.skill_exp[sk] != last_stats.skill_exp[sk])
706  && skill_mapping[i].name && cpl.stats.skill_exp[sk]) {
707  gtk_label_set_text(GTK_LABEL(statwindow.skill_exp[on_skill++]),
708  skill_mapping[i].name);
709  snprintf(buff, sizeof(buff), "%" G_GINT64_FORMAT " (%d)",
711  gtk_label_set_text(
712  GTK_LABEL(statwindow.skill_exp[on_skill++]), buff);
713  last_stats.skill_level[sk] = cpl.stats.skill_level[sk];
714  last_stats.skill_exp[sk] = cpl.stats.skill_exp[sk];
715  } else if (cpl.stats.skill_exp[sk]) {
716  /* Don't need to draw the skill, but need to update the
717  * position of where to draw the next one.
718  */
719  on_skill+=2;
720  }
721  }
722 
723  /* Since the number of skills we draw come and go, basically we want
724  * to erase any extra. This shows up when switching characters, eg,
725  * character #1 knows 10 skills, #2 knows 5 - need to erase those 5
726  * extra.
727  */
728  if (on_skill < max_drawn_skill) {
729  for (int k = on_skill; k <= max_drawn_skill; k++) {
730  gtk_label_set_text(GTK_LABEL(statwindow.skill_exp[k]), "");
731  }
732  }
733  max_drawn_skill = on_skill;
734  }
735 
736  /* Now do the resistance table */
737  if (redraw || cpl.stats.resist_change) {
738  int j = 0;
739 
741  for (int i = 0; i < NUM_RESISTS; i++) {
742  sk = resist_mapping[i].value;
743  if (cpl.stats.resists[sk]) {
744  gtk_label_set_text(GTK_LABEL(statwindow.resists[j]),
745  resist_mapping[i].name);
746  j++;
747  snprintf(buff, sizeof(buff), "%+4d", cpl.stats.resists[sk]);
748  gtk_label_set_text(GTK_LABEL(statwindow.resists[j]), buff);
749  j++;
751  break;
752  }
753  }
754  }
755  /* Erase old/unused resistances */
756  if (j < max_drawn_resists) {
757  for (int i = j; i <= max_drawn_resists; i++) {
758  gtk_label_set_text(GTK_LABEL(statwindow.resists[i]), "");
759  }
760  }
761  max_drawn_resists = j;
762  } /* if we draw the resists */
763 
764 
765  /* Don't need to worry about hp, sp, grace, food - update_stat()
766  * deals with that as part of the stat bar logic.
767  */
768 
769 }
770 
772  need_mapping_update = TRUE;
773 }
void draw_stats(int redraw)
Definition: stats.c:554
void draw_message_window(int redraw)
Definition: stats.c:459
gint16 grace
Definition: client.h:266
gint8 wc
Definition: client.h:259
void clear_stat_mapping()
Definition: stats.c:771
#define STYLE_LOW
Definition: stats.c:42
GtkBuilder * window_xml
Definition: main.c:86
static char * format_si_number(gint64 number)
Definition: stats.c:243
gint32 weapon_sp
Definition: client.h:276
int value
Definition: client.h:463
static const char *const stat_style_names[NUM_STYLES]
Definition: stats.c:53
gint16 use_config[CONFIG_NUMS]
Definition: init.c:40
static GtkWidget * stat_bar[MAX_STAT_BARS]
Definition: stats.c:39
GtkWidget * Con
Definition: stats.c:78
GtkWidget * weapon_speed
Definition: stats.c:88
gint64 skill_exp[MAX_SKILL]
Definition: client.h:290
int sc_version
Definition: client.h:121
#define MAX_SKILL
Definition: client.h:84
#define NUM_STYLES
Definition: stats.c:47
gint16 maxsp
Definition: client.h:265
GtkWidget * level
Definition: stats.c:91
GtkWidget * Int
Definition: stats.c:79
#define STYLE_NORMAL
Definition: stats.c:41
gint32 speed
Definition: client.h:275
ClientSocket csocket
Definition: client.c:69
#define CONFIG_FOODBEEP
Definition: client.h:205
#define STAT_BAR_FOOD
Definition: stats.c:31
Stats stats
Definition: client.h:349
const char *const resists_name[NUM_RESISTS]
Definition: client.c:72
NameMapping resist_mapping[NUM_RESISTS]
Definition: client.c:66
#define FLOAT_MULTF
Definition: newclient.h:80
#define SKILL_BOXES_X
Definition: stats.c:68
gint8 Con
Definition: client.h:254
#define PROTECTION_BOXES_Y
Definition: stats.c:72
char * skill_names[MAX_SKILL]
Definition: client.c:50
#define STAT_BAR_EXP
Definition: stats.c:32
#define STAT_BAR_HP
Definition: stats.c:28
GtkWidget * wc
Definition: stats.c:83
GtkWidget * playername
Definition: stats.c:75
guint64 * exp_table
Definition: client.c:64
static int lastmax[MAX_STAT_BARS]
Definition: stats.c:103
GtkWidget * table_protections
Definition: stats.c:93
void stats_get_styles(void)
Definition: stats.c:109
#define STYLE_SUPER
Definition: stats.c:43
void stats_init(GtkWidget *window_root)
Definition: stats.c:147
#define STYLE_GRAD_LOW
Definition: stats.c:45
void LOG(LogLevel level, const char *origin, const char *format,...)
Definition: misc.c:109
static int mapping_sort(NameMapping *a, NameMapping *b)
Definition: stats.c:503
guint32 resist_change
Definition: client.h:288
gint8 ac
Definition: client.h:260
#define NUM_RESISTS
Definition: client.h:451
GdkColor * bar_colors[MAX_STAT_BARS][NUM_STYLES]
Definition: stats.c:61
static gboolean need_mapping_update
Definition: stats.c:101
static gboolean redraw(gpointer data)
Definition: main.c:128
GtkWidget * window_root
Definition: main.c:87
gint8 Str
Definition: client.h:252
#define STYLE_GRAD_NORMAL
Definition: stats.c:44
void update_stat(int stat_no, gint64 max_stat, gint64 current_stat, gint64 statbar_max, gint64 statbar_stat, int can_alert)
Definition: stats.c:291
NameMapping skill_mapping[MAX_SKILL]
Definition: client.c:66
GtkWidget * Str
Definition: stats.c:76
#define SKILL_BOXES_Y
Definition: stats.c:69
gint8 Wis
Definition: client.h:255
#define STAT_BAR_GRACE
Definition: stats.c:30
gint8 level
Definition: client.h:261
#define PROTECTION_BOXES_X
Definition: stats.c:71
Client_Player cpl
Definition: client.c:68
#define CONFIG_GRAD_COLOR
Definition: client.h:208
GtkWidget * Dex
Definition: stats.c:77
gint8 Pow
Definition: client.h:258
#define MAX_BUF
Definition: client.h:40
gint16 food
Definition: client.h:269
gint8 Dex
Definition: client.h:253
#define STYLE_GRAD_SUPER
Definition: stats.c:46
#define STAT_BAR_SP
Definition: stats.c:29
GtkWidget * dam
Definition: stats.c:84
gint16 resists[30]
Definition: client.h:287
GtkWidget * exp
Definition: stats.c:90
GtkWidget * range
Definition: stats.c:89
gint64 exp
Definition: client.h:268
const char * name
Definition: client.h:462
gint16 skill_level[MAX_SKILL]
Definition: client.h:289
gint16 dam
Definition: client.h:272
static int has_init
Definition: account.c:94
GtkWidget * skill_exp[SKILL_BOXES_X *SKILL_BOXES_Y]
Definition: stats.c:94
static const char *const stat_bar_names[MAX_STAT_BARS]
Definition: stats.c:35
void update_skill_information(void)
Definition: skills.c:62
gint8 Int
Definition: client.h:257
static int lastval[MAX_STAT_BARS]
Definition: stats.c:103
gint16 sp
Definition: client.h:264
GtkWidget * ac
Definition: stats.c:85
char range[MAX_BUF]
Definition: client.h:352
GtkWidget * resists[PROTECTION_BOXES_X *PROTECTION_BOXES_Y]
Definition: stats.c:95
gint16 maxhp
Definition: client.h:263
char title[MAX_BUF]
Definition: client.h:351
GtkWidget * Wis
Definition: stats.c:80
gint16 hp
Definition: client.h:262
static StatWindow statwindow
Definition: stats.c:99
static void update_stat_mapping(void)
Definition: stats.c:521
GtkWidget * Cha
Definition: stats.c:81
#define MAX_STAT_BARS
Definition: stats.c:33
gint8 Cha
Definition: client.h:256
GtkWidget * Pow
Definition: stats.c:82
guint16 exp_table_max
Definition: client.c:63
Minor, non-harmful issues.
Definition: client.h:442
GtkWidget * armor
Definition: stats.c:86
GtkWidget * table_skills_exp
Definition: stats.c:92
gint16 maxgrace
Definition: client.h:267
GtkWidget * speed
Definition: stats.c:87