Crossfire Client, Branch  R11627
stats.c
Go to the documentation of this file.
00001 const char * const rcsid_gtk2_stats_c =
00002     "$Id: stats.c 9201 2008-06-01 17:32:45Z anmaster $";
00003 /*
00004     Crossfire client, a client program for the crossfire program.
00005 
00006     Copyright (C) 2005-2007 Mark Wedel & Crossfire Development Team
00007 
00008     This program is free software; you can redistribute it and/or modify
00009     it under the terms of the GNU General Public License as published by
00010     the Free Software Foundation; either version 2 of the License, or
00011     (at your option) any later version.
00012 
00013     This program is distributed in the hope that it will be useful,
00014     but WITHOUT ANY WARRANTY; without even the implied warranty of
00015     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00016     GNU General Public License for more details.
00017 
00018     You should have received a copy of the GNU General Public License
00019     along with this program; if not, write to the Free Software
00020     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
00021 
00022     The author can be reached via e-mail to crossfire@metalforge.org
00023 */
00024 
00030 #ifdef HAVE_CONFIG_H
00031 #  include <config.h>
00032 #endif
00033 
00034 #include <assert.h>
00035 #include <gtk/gtk.h>
00036 #include <glade/glade.h>
00037 
00038 #include "client.h"
00039 
00040 #include "main.h"
00041 
00042 #define STAT_BAR_HP     0
00043 #define STAT_BAR_SP     1
00044 #define STAT_BAR_GRACE  2
00045 #define STAT_BAR_FOOD   3
00046 #define STAT_BAR_EXP    4
00047 #define MAX_STAT_BARS   5
00048 static const char * const stat_bar_names[MAX_STAT_BARS] = {
00049     "hp", "sp", "grace", "food", "exp"
00050 };
00051 
00052 GtkWidget *stat_label[MAX_STAT_BARS], *stat_bar[MAX_STAT_BARS];
00053 
00054 #define STYLE_NORMAL    0
00055 #define STYLE_LOW       1
00056 #define STYLE_SUPER     2
00057 #define STYLE_GRAD_NORMAL   3
00058 #define STYLE_GRAD_LOW      4
00059 #define STYLE_GRAD_SUPER    5
00060 #define NUM_STYLES  6
00061 
00062 /* The name of the symbolic widget names we try to look up
00063  * the styles of (these will be prefixed with hp_, sp_, etc).
00064  * This should always match NUM_STYLES.
00065  */
00066 static const char * const stat_style_names[NUM_STYLES] = {
00067     "bar_normal", "bar_low", "bar_super",
00068     "gradual_bar_normal", "gradual_bar_low", "gradual_bar_super"
00069 };
00070 
00071 /* We really only care about the colors, as there isn't
00072  * anything else we can change about the progressbar widget
00073  * itself.
00074  */
00075 GdkColor    *bar_colors[MAX_STAT_BARS][NUM_STYLES];
00076 
00077 
00078 /* The table for showing skill exp is an x & y grid.  Note
00079  * for proper formatting, SKILL_BOXES_X must be even.
00080  * Hmmm - perhaps these should instead be dynamically
00081  * generated?
00082  */
00083 #define SKILL_BOXES_X   6
00084 #define SKILL_BOXES_Y   17
00085 
00086 #define PROTECTION_BOXES_X      6
00087 #define PROTECTION_BOXES_Y      6
00088 
00089 typedef struct {
00090     GtkWidget *playername;
00091     GtkWidget *Str;
00092     GtkWidget *Dex;
00093     GtkWidget *Con;
00094     GtkWidget *Int;
00095     GtkWidget *Wis;
00096     GtkWidget *Cha;
00097     GtkWidget *Pow;
00098     GtkWidget *wc;
00099     GtkWidget *dam;
00100     GtkWidget *ac;
00101     GtkWidget *armor;
00102     GtkWidget *speed;
00103     GtkWidget *weapon_speed;
00104     GtkWidget *range;
00105     GtkWidget *exp;
00106     GtkWidget *level;
00107     GtkWidget *table_skills_exp;
00108     GtkWidget *table_protections;
00109     GtkWidget *skill_exp[SKILL_BOXES_X * SKILL_BOXES_Y];
00110     GtkWidget *resists[PROTECTION_BOXES_X * PROTECTION_BOXES_Y];
00111 
00112 } StatWindow;
00113 
00114 static StatWindow statwindow;
00115 
00116 /* The basic idea of this structure is there are some lists of
00117  * names we get from the server (like say skill names) -
00118  * internally, these are all referred to number, and the numbers
00119  * are not in any order.  The idea here is we can store away the
00120  * names, and then can display the items in the window in
00121  * nice alphabetical order instead of the random order they
00122  * normally show up in.
00123  */
00124 typedef struct {
00125     const char *name;
00126     int     value;
00127 } NameMapping;
00128 
00129 NameMapping skill_mapping[MAX_SKILL], resist_mapping[NUM_RESISTS];
00130 
00131 int need_mapping_update=1;
00132 
00133 static int lastval[MAX_STAT_BARS], lastmax[MAX_STAT_BARS];
00134 
00139 void stats_get_styles(void)
00140 {
00141     static int has_init=0;
00142     int stat_bar, sub_style;
00143     char    buf[MAX_BUF];
00144     GtkStyle *tmp_style;
00145 
00146     if (!has_init) {
00147         memset(bar_colors, 0, sizeof(GdkColor*) * MAX_STAT_BARS * NUM_STYLES);
00148     }
00149 
00150     for (stat_bar=0; stat_bar < MAX_STAT_BARS; stat_bar++) {
00151         for (sub_style=0; sub_style < NUM_STYLES; sub_style++) {
00152             snprintf(buf, sizeof(buf), "%s_%s", stat_bar_names[stat_bar], stat_style_names[sub_style]);
00153 
00154             tmp_style = gtk_rc_get_style_by_paths(gtk_settings_get_default(), NULL, buf, G_TYPE_NONE);
00155 
00156             if (!tmp_style) {
00157                 if (bar_colors[stat_bar][sub_style]) {
00158                     free(bar_colors[stat_bar][sub_style]);
00159                     bar_colors[stat_bar][sub_style] = NULL;
00160                 }
00161                 LOG(LOG_INFO, "stats.c::stats_get_styles()", "Unable to find style '%s'", buf);
00162             } else {
00163                 if (!bar_colors[stat_bar][sub_style])
00164                     bar_colors[stat_bar][sub_style] = calloc(1, sizeof(GdkColor));
00165                 memcpy(bar_colors[stat_bar][sub_style],
00166                        &tmp_style->base[GTK_STATE_SELECTED], sizeof(GdkColor));
00167             }
00168         }
00169     }
00170 }
00171 
00176 void stats_init(GtkWidget *window_root)
00177 {
00178     int i, x, y;
00179     char buf[MAX_BUF];
00180     GladeXML *xml_tree;
00181 
00182     xml_tree = glade_get_widget_tree(GTK_WIDGET(window_root));
00183     for (i=0; i<MAX_STAT_BARS; i++) {
00184         snprintf(buf, sizeof(buf), "label_stats_%s", stat_bar_names[i]);
00185         stat_label[i] = glade_xml_get_widget(xml_tree, buf);
00186 
00187         snprintf(buf, sizeof(buf), "progressbar_%s", stat_bar_names[i]);
00188         stat_bar[i] = glade_xml_get_widget(xml_tree, buf);
00189 
00190         lastval[i] = -1;
00191         lastmax[i] = -1;
00192     }
00193 
00194     statwindow.playername =
00195         glade_xml_get_widget(xml_tree, "label_playername");
00196     statwindow.Str =
00197         glade_xml_get_widget(xml_tree, "label_str");
00198     statwindow.Dex =
00199         glade_xml_get_widget(xml_tree, "label_dex");
00200     statwindow.Con =
00201         glade_xml_get_widget(xml_tree, "label_con");
00202     statwindow.Int =
00203         glade_xml_get_widget(xml_tree, "label_int");
00204     statwindow.Wis =
00205         glade_xml_get_widget(xml_tree, "label_wis");
00206     statwindow.Pow =
00207         glade_xml_get_widget(xml_tree, "label_pow");
00208     statwindow.Cha =
00209         glade_xml_get_widget(xml_tree, "label_cha");
00210     statwindow.wc =
00211         glade_xml_get_widget(xml_tree, "label_wc");
00212     statwindow.dam =
00213         glade_xml_get_widget(xml_tree, "label_dam");
00214     statwindow.ac =
00215         glade_xml_get_widget(xml_tree, "label_ac");
00216     statwindow.armor =
00217         glade_xml_get_widget(xml_tree, "label_armor");
00218     statwindow.speed =
00219         glade_xml_get_widget(xml_tree, "label_speed");
00220     statwindow.weapon_speed =
00221         glade_xml_get_widget(xml_tree, "label_weapon_speed");
00222     statwindow.range =
00223         glade_xml_get_widget(xml_tree, "label_range");
00224     statwindow.exp =
00225         glade_xml_get_widget(xml_tree, "label_exp");
00226     statwindow.level =
00227         glade_xml_get_widget(xml_tree, "label_level");
00228 
00229     /* Note that the order the labels are attached to the tables determines
00230      * the order of display.  The order as right now is left to right,
00231      * then top to bottom, which means that is the order if displaying
00232      * skills & protections.
00233      */
00234 
00235     statwindow.table_skills_exp =
00236         glade_xml_get_widget(xml_tree,"table_skills_exp");
00237 
00238     for (i=0, x=0, y=0; i < SKILL_BOXES_X * SKILL_BOXES_Y; i++) {
00239         statwindow.skill_exp[i] = gtk_label_new("");
00240         gtk_table_attach(GTK_TABLE(statwindow.table_skills_exp), statwindow.skill_exp[i],
00241                   x, x+1, y, y+1, GTK_EXPAND, 0, 0, 0);
00242         gtk_widget_show(statwindow.skill_exp[i]);
00243         x++;
00244         if (x == SKILL_BOXES_X) {
00245             x=0;
00246             y++;
00247         }
00248     }
00249 
00250     statwindow.table_protections =
00251         glade_xml_get_widget(xml_tree,"table_protections");
00252 
00253     for (i=0, x=0, y=0; i < PROTECTION_BOXES_X * PROTECTION_BOXES_Y; i++) {
00254         statwindow.resists[i] = gtk_label_new("");
00255         gtk_table_attach(GTK_TABLE(statwindow.table_protections), statwindow.resists[i],
00256                   x, x+1, y, y+1, GTK_EXPAND, 0, 0, 0);
00257         gtk_widget_show(statwindow.resists[i]);
00258         x++;
00259         if (x == PROTECTION_BOXES_X) {
00260             x=0;
00261             y++;
00262         }
00263     }
00264     stats_get_styles();
00265 }
00266 
00267 
00292 void update_stat(int stat_no, sint64 max_stat, sint64 current_stat,
00293          sint64 statbar_max, sint64 statbar_stat, const char *name, int can_alert)
00294 {
00295     float bar;
00296     int is_alert;
00297     char buf[256];
00298     GdkColor ncolor, *set_color=NULL;
00299 
00300     /* If nothing changed, don't need to do anything */
00301     if (lastval[stat_no] == current_stat && lastmax[stat_no] == max_stat)
00302         return;
00303 
00304     lastval[stat_no] = current_stat;
00305     lastmax[stat_no] = max_stat;
00306 
00307     if (statbar_max > 0) bar = (float) statbar_stat / (float) statbar_max;
00308     else bar = 0.0;
00309 
00310     /* Simple check to see if current stat is less than 25% */
00311     if (can_alert && current_stat * 4 < max_stat) is_alert=1;
00312     else is_alert = 0;
00313 
00314     if (use_config[CONFIG_GRAD_COLOR]) {
00315         /* In this mode, the color of the stat bar were go between red and green
00316          * in a gradual style. Color is blended from low to high
00317          */
00318 
00319         GdkColor        *hcolor, *lcolor;
00320         float       diff;
00321 
00322         /* First thing we do is figure out current values, and thus
00323          * what color bases we use (based on super charged or
00324          * normal value).  We also set up diff as where we are between
00325          * those two points.  In this way, the blending logic
00326          * below is the same regardless of actual value.
00327          */
00328         if (bar > 1.0) {
00329             if (bar>2.0) bar=2.0;   /* Doesn't affect display, just our calculations */
00330             hcolor = bar_colors[stat_no][STYLE_GRAD_SUPER];
00331             lcolor = bar_colors[stat_no][STYLE_GRAD_NORMAL];
00332             diff = bar - 1.0;
00333         } else {
00334             if (bar < 0.0) bar=0.0;  /* Like above, doesn't affect display */
00335             hcolor = bar_colors[stat_no][STYLE_GRAD_NORMAL];
00336             lcolor = bar_colors[stat_no][STYLE_GRAD_LOW];
00337             diff = bar;
00338         }
00339         /* Now time to blend.  First, make sure colors are set.
00340          * then, we use the lcolor as the base, making adjustments
00341          * based on hcolor.  Values in hcolor may be lower than
00342          * lcolor, but that just means we substract from lcolor, not
00343          * add.
00344          */
00345 
00346         if (lcolor && hcolor) {
00347 #if 1
00348             memcpy(&ncolor, lcolor, sizeof(GdkColor));
00349 
00350             ncolor.red += (hcolor->red - lcolor->red) * diff;
00351             ncolor.green += (hcolor->green - lcolor->green) * diff;
00352             ncolor.blue += (hcolor->blue - lcolor->blue) * diff;
00353 #else
00354             /* This is an alternate coloring method that works
00355              * when using saturated colors for the base points.
00356              * This mimics the old code, and works good
00357              * when using such saturated colors (eg, one
00358              * of the RGB triplets being 255, others 0, like
00359              * red, green, or blue).  However, this doesn't produce
00360              * very good results when not using those colors - if
00361              * say magenta and yellow are chosen as the two colors,
00362              * this code results in the colors basically getting near
00363              * white in the middle values.  For saturated colors, the
00364              * code below would produce nice bright yellow for the middle
00365              * values, where as the code above produces more a dark yellow,
00366              * since it only takes half the red and half the green.
00367              * However, the code above still produces useful results
00368              * even with that limitation, and it works for all colors,
00369              * so it is the code enabled.  It perhaps be
00370              * interesting to have some detection logic on how the colors
00371              * are actually set - if only a single r/g/b value is set for
00372              * the two colors, then use this logic here, otherwise
00373              * the above logic or something.
00374              * MSW 2007-01-24
00375              */
00376             if (diff > 0.5) {
00377                 memcpy(&ncolor, hcolor, sizeof(GdkColor));
00378 
00379                 if (lcolor->red > hcolor->red)
00380                     ncolor.red = 2 * lcolor->red * (1.0 - diff);
00381 
00382                 if (lcolor->green > hcolor->green)
00383                     ncolor.green = 2 * lcolor->green * (1.0 - diff);
00384 
00385                 if (lcolor->blue > hcolor->blue)
00386                     ncolor.blue = 2 * lcolor->blue * (1.0 - diff);
00387 
00388             } else {
00389                 memcpy(&ncolor, lcolor, sizeof(GdkColor));
00390 
00391                 if (hcolor->red > lcolor->red)
00392                     ncolor.red = 2 * hcolor->red * diff;
00393 
00394                 if (hcolor->green > lcolor->green)
00395                     ncolor.green = 2 * hcolor->green * diff;
00396 
00397                 if (hcolor->blue > lcolor->blue)
00398                     ncolor.blue = 2 * hcolor->blue * diff;
00399             }
00400 #endif
00401 #if 0
00402             fprintf(stderr,"stat %d, val %d, r/g/b=%d/%d/%d\n",
00403                     stat_no, current_stat, ncolor.red, ncolor.green, ncolor.blue);
00404 #endif
00405             set_color = &ncolor;
00406         }
00407     } else {
00408         if (statbar_stat * 4 < statbar_max)
00409             set_color = bar_colors[stat_no][STYLE_LOW];
00410         else if (statbar_stat > statbar_max)
00411             set_color = bar_colors[stat_no][STYLE_SUPER];
00412         else
00413             set_color = bar_colors[stat_no][STYLE_NORMAL];
00414     }
00415     if (bar > 1.0) bar = 1.0;
00416     if (bar < 0.0) bar = 0.0;
00417 
00418     /* It may be a waste, but we set the color everytime here - it
00419      * isn't very costly, and keeps us from tracing the last
00420      * color we set.
00421      * Note that set_color could be null, which means it reverts
00422      * back to normal color.
00423      */
00424     gtk_widget_modify_base(stat_bar[stat_no], GTK_STATE_SELECTED, set_color);
00425 
00426     gtk_progress_set_percentage(GTK_PROGRESS(stat_bar[stat_no]), bar);
00427     snprintf(buf, sizeof(buf), "%s %"FMT64"/%"FMT64, name, current_stat, max_stat);
00428     gtk_label_set(GTK_LABEL(stat_label[stat_no]), buf);
00429 
00430 }
00431 
00437 void draw_message_window(int redraw) {
00438     static int lastbeep=0;
00439     static sint64 level_diff;
00440 
00441     update_stat(0, cpl.stats.maxhp, cpl.stats.hp,
00442                 cpl.stats.maxhp, cpl.stats.hp, "HP:", TRUE);
00443     update_stat(1, cpl.stats.maxsp, cpl.stats.sp,
00444                 cpl.stats.maxsp, cpl.stats.sp, "Spell Points:", TRUE);
00445     update_stat(2, cpl.stats.maxgrace, cpl.stats.grace,
00446                 cpl.stats.maxgrace, cpl.stats.grace, "Grace:", TRUE);
00447     update_stat(3, 999, cpl.stats.food, 999, cpl.stats.food, "Food:", TRUE);
00448 
00449     /* We may or may not have an exp table from the server.  If we don't, just
00450      * use current exp value so it will always appear maxed out.
00451      */
00452     /* We calculate level_diff here just so it makes the update_stat()
00453      * call below less messy.
00454      */
00455     if ((cpl.stats.level+1) < exp_table_max)
00456         level_diff = exp_table[cpl.stats.level+1] - exp_table[cpl.stats.level];
00457     else
00458         level_diff=cpl.stats.exp;
00459 
00460     update_stat(4,
00461         (cpl.stats.level+1) < exp_table_max ? exp_table[cpl.stats.level+1]:cpl.stats.exp,
00462         cpl.stats.exp,
00463         level_diff,
00464         (cpl.stats.level+1) < exp_table_max ?
00465                 (cpl.stats.exp - exp_table[cpl.stats.level]):cpl.stats.exp,
00466         "Exp:", FALSE);
00467 
00468     if (use_config[CONFIG_FOODBEEP] && (cpl.stats.food%4==3) && (cpl.stats.food < 200)) {
00469         gdk_beep( );
00470     } else if (use_config[CONFIG_FOODBEEP] && cpl.stats.food == 0 && ++lastbeep == 5) {
00471         lastbeep = 0;
00472         gdk_beep( );
00473     }
00474 }
00475 
00480 static int mapping_sort(NameMapping *a, NameMapping *b)
00481 {
00482     if (!a->name && !b->name) return 0;
00483     if (!a->name) return 1;
00484     if (!b->name) return -1;
00485     else return strcasecmp(a->name, b->name);
00486 }
00487 
00491 static void update_stat_mapping(void)
00492 {
00493     int i;
00494 
00495     for (i=0; i < MAX_SKILL; i++) {
00496         skill_mapping[i].value=i;
00497         if (skill_names[i])
00498             skill_mapping[i].name = skill_names[i];
00499         else
00500             skill_mapping[i].name = NULL;
00501     }
00502     qsort(skill_mapping, MAX_SKILL, sizeof(NameMapping),
00503           (int (*)(const void*, const void*))mapping_sort);
00504 
00505     for (i=0; i < NUM_RESISTS; i++) {
00506         resist_mapping[i].value=i;
00507         if (resists_name[i])
00508             resist_mapping[i].name = resists_name[i];
00509         else
00510             resist_mapping[i].name = NULL;
00511     }
00512     qsort(resist_mapping, NUM_RESISTS, sizeof(NameMapping),
00513           (int (*)(const void*, const void*))mapping_sort);
00514 
00515     need_mapping_update=0;
00516 }
00517 
00522 void draw_stats(int redraw) {
00523     static Stats last_stats;
00524     static char last_name[MAX_BUF]="", last_range[MAX_BUF]="";
00525     static int init_before=0, max_drawn_skill=0, max_drawn_resists=0;
00526 
00527     float weap_sp;
00528     char buff[MAX_BUF];
00529     int i, on_skill, sk;
00530 
00531     if (!init_before) {
00532         init_before=1;
00533         memset(&last_stats, 0, sizeof(Stats));
00534     }
00535 
00536     /* skill_names gets set as part of the initialization with the
00537      * client - however, right now, there is no callback when
00538      * it is set, so instead, just track that wee need to update
00539      * and see if it changes.
00540      */
00541     if (need_mapping_update && skill_names[1] != NULL) {
00542         update_stat_mapping();
00543     }
00544 
00545     if (strcmp(cpl.title, last_name) || redraw) {
00546         strcpy(last_name,cpl.title);
00547         gtk_label_set (GTK_LABEL(statwindow.playername), cpl.title);
00548     }
00549 
00550     if(redraw || cpl.stats.exp!=last_stats.exp) {
00551         last_stats.exp = cpl.stats.exp;
00552         snprintf(buff, sizeof(buff), "Experience: %5" FMT64 ,cpl.stats.exp);
00553         gtk_label_set (GTK_LABEL(statwindow.exp), buff);
00554     }
00555 
00556     if(redraw || cpl.stats.level!=last_stats.level) {
00557         last_stats.level = cpl.stats.level;
00558         snprintf(buff, sizeof(buff), "Level: %d",cpl.stats.level);
00559         gtk_label_set (GTK_LABEL(statwindow.level), buff);
00560     }
00561 
00562     if(redraw || cpl.stats.Str!=last_stats.Str) {
00563         last_stats.Str=cpl.stats.Str;
00564         snprintf(buff, sizeof(buff), "%2d",cpl.stats.Str);
00565         gtk_label_set (GTK_LABEL(statwindow.Str), buff);
00566     }
00567 
00568     if(redraw || cpl.stats.Dex!=last_stats.Dex) {
00569         last_stats.Dex=cpl.stats.Dex;
00570         snprintf(buff, sizeof(buff), "%2d",cpl.stats.Dex);
00571         gtk_label_set (GTK_LABEL(statwindow.Dex), buff);
00572     }
00573 
00574     if(redraw || cpl.stats.Con!=last_stats.Con) {
00575         last_stats.Con=cpl.stats.Con;
00576         snprintf(buff, sizeof(buff), "%2d",cpl.stats.Con);
00577         gtk_label_set (GTK_LABEL(statwindow.Con), buff);
00578     }
00579 
00580     if(redraw || cpl.stats.Int!=last_stats.Int) {
00581         last_stats.Int=cpl.stats.Int;
00582         snprintf(buff, sizeof(buff), "%2d",cpl.stats.Int);
00583         gtk_label_set (GTK_LABEL(statwindow.Int), buff);
00584     }
00585 
00586     if(redraw || cpl.stats.Wis!=last_stats.Wis) {
00587         last_stats.Wis=cpl.stats.Wis;
00588         snprintf(buff, sizeof(buff), "%2d",cpl.stats.Wis);
00589         gtk_label_set (GTK_LABEL(statwindow.Wis), buff);
00590     }
00591 
00592     if(redraw || cpl.stats.Pow!=last_stats.Pow) {
00593         last_stats.Pow=cpl.stats.Pow;
00594         snprintf(buff, sizeof(buff), "%2d",cpl.stats.Pow);
00595         gtk_label_set (GTK_LABEL(statwindow.Pow), buff);
00596     }
00597 
00598     if(redraw || cpl.stats.Cha!=last_stats.Cha) {
00599         last_stats.Cha=cpl.stats.Cha;
00600         snprintf(buff, sizeof(buff), "%2d",cpl.stats.Cha);
00601         gtk_label_set (GTK_LABEL(statwindow.Cha), buff);
00602     }
00603 
00604     if(redraw || cpl.stats.wc!=last_stats.wc) {
00605         last_stats.wc=cpl.stats.wc;
00606         snprintf(buff, sizeof(buff), "%3d",cpl.stats.wc);
00607         gtk_label_set (GTK_LABEL(statwindow.wc), buff);
00608     }
00609 
00610     if(redraw || cpl.stats.dam!=last_stats.dam) {
00611         last_stats.dam=cpl.stats.dam;
00612         snprintf(buff, sizeof(buff), "%d",cpl.stats.dam);
00613         gtk_label_set (GTK_LABEL(statwindow.dam), buff);
00614     }
00615 
00616     if(redraw || cpl.stats.ac!=last_stats.ac) {
00617         last_stats.ac=cpl.stats.ac;
00618         snprintf(buff, sizeof(buff), "%d",cpl.stats.ac);
00619         gtk_label_set (GTK_LABEL(statwindow.ac), buff);
00620     }
00621 
00622     if(redraw || cpl.stats.resists[0]!=last_stats.resists[0]) {
00623         last_stats.resists[0]=cpl.stats.resists[0];
00624         snprintf(buff, sizeof(buff), "%d",cpl.stats.resists[0]);
00625         gtk_label_set (GTK_LABEL(statwindow.armor), buff);
00626     }
00627 
00628     if (redraw || cpl.stats.speed!=last_stats.speed) {
00629         last_stats.speed=cpl.stats.speed;
00630         snprintf(buff, sizeof(buff), "%3.2f",(float)cpl.stats.speed/FLOAT_MULTF);
00631         gtk_label_set (GTK_LABEL(statwindow.speed), buff);
00632     }
00633     /* sc_version >= 1029 reports real value of weapon speed -
00634      * not as a factor of player speed.  Handle accordingly.
00635      */
00636     if (csocket.sc_version >= 1029)
00637         weap_sp = (float) cpl.stats.weapon_sp / FLOAT_MULTF;
00638     else
00639         weap_sp = (float) cpl.stats.speed/((float)cpl.stats.weapon_sp);
00640 
00641     if (redraw || weap_sp !=last_stats.weapon_sp) {
00642         last_stats.weapon_sp=weap_sp;
00643         snprintf(buff, sizeof(buff), "%3.2f",weap_sp);
00644         gtk_label_set (GTK_LABEL(statwindow.weapon_speed), buff);
00645     }
00646 
00647     if(redraw || strcmp(cpl.range, last_range)) {
00648         strcpy(last_range, cpl.range);
00649         snprintf(buff, sizeof(buff), "Range: %s",cpl.range);
00650         gtk_label_set (GTK_LABEL(statwindow.range), cpl.range);
00651     }
00652 
00653     on_skill=0;
00654     assert(sizeof(statwindow.skill_exp)/sizeof(*statwindow.skill_exp) >= 2*MAX_SKILL);
00655     for (i=0; i<MAX_SKILL; i++) {
00656         /* Drawing a particular skill entry is tricky - only draw if
00657          * different, and only draw if we have a name for the skill
00658          * and the player has some exp in the skill - don't draw
00659          * all 30 skills for no reason.
00660          */
00661         sk = skill_mapping[i].value;
00662 
00663         if ((redraw || cpl.stats.skill_exp[sk] != last_stats.skill_exp[sk]) &&
00664             skill_mapping[i].name && cpl.stats.skill_exp[sk]){
00665             gtk_label_set(GTK_LABEL(statwindow.skill_exp[on_skill++]), skill_mapping[i].name);
00666             snprintf(buff, sizeof(buff), "%" FMT64 " (%d)", cpl.stats.skill_exp[sk], cpl.stats.skill_level[sk]);
00667             gtk_label_set(GTK_LABEL(statwindow.skill_exp[on_skill++]), buff);
00668             last_stats.skill_level[sk] = cpl.stats.skill_level[sk];
00669             last_stats.skill_exp[sk] = cpl.stats.skill_exp[sk];
00670         } else if (cpl.stats.skill_exp[sk]) {
00671             /* don't need to draw the skill, but need to update the position
00672              * of where to draw the next one.
00673              */
00674             on_skill+=2;
00675         }
00676     }
00677 
00678     /* Since the number of skills we draw come and go, basically we want
00679      * to erase any extra.  This shows up when switching characters, eg, character
00680      * #1 knows 10 skills, #2 knows 5 - need to erase those 5 extra.
00681      */
00682     if (on_skill < max_drawn_skill) {
00683         int k;
00684 
00685         for (k = on_skill; k <= max_drawn_skill; k++)
00686             gtk_label_set(GTK_LABEL(statwindow.skill_exp[k]), "");
00687     }
00688     max_drawn_skill = on_skill;
00689 
00690     /* Now do the resistance table */
00691     if (redraw || cpl.stats.resist_change) {
00692         int i,j=0;
00693 
00694         cpl.stats.resist_change=0;
00695         for (i=0; i<NUM_RESISTS; i++) {
00696             sk = resist_mapping[i].value;
00697             if (cpl.stats.resists[sk]) {
00698                 gtk_label_set(GTK_LABEL(statwindow.resists[j]), resist_mapping[i].name);
00699                 j++;
00700                 snprintf(buff, sizeof(buff), "%+4d", cpl.stats.resists[sk]);
00701                 gtk_label_set(GTK_LABEL(statwindow.resists[j]), buff);
00702                 j++;
00703                 if (j >= PROTECTION_BOXES_X * PROTECTION_BOXES_Y) break;
00704             }
00705         }
00706         /* Erase old/unused resistances */
00707         if (j < max_drawn_resists) {
00708             for (i=j; i <= max_drawn_resists; i++)  {
00709                 gtk_label_set(GTK_LABEL(statwindow.resists[i]), "");
00710             }
00711         }
00712         max_drawn_resists = j;
00713     } /* if we draw the resists */
00714 
00715 
00716     /* Don't need to worry about hp, sp, grace, food - update_stat()
00717      * deals with that as part of the stat bar logic.
00718      */
00719 
00720 }
00721 
00725 void clear_stat_mapping(void)
00726 {
00727     need_mapping_update=1;
00728 }