1 /*------------------------------------------------------------------.
2 | Copyright 1997, 1998, 2000, 2001  Alexandre Duret-Lutz            |
3 |                                    <duret_g@epita.fr>             |
4 |                                                                   |
5 | This file is part of Heroes.                                      |
6 |                                                                   |
7 | Heroes is free software; you can redistribute it and/or modify it |
8 | under the terms of the GNU General Public License version 2 as    |
9 | published by the Free Software Foundation.                        |
10 |                                                                   |
11 | Heroes is distributed in the hope that it will be useful, but     |
12 | WITHOUT ANY WARRANTY; without even the implied warranty of        |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU |
14 | General Public License for more details.                          |
15 |                                                                   |
16 | You should have received a copy of the GNU General Public License |
17 | along with this program; if not, write to the Free Software       |
18 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA          |
19 | 02111-1307 USA                                                    |
20 `------------------------------------------------------------------*/
21 
22 #include "system.h"
23 #include "structs.h"
24 #include "scores.h"
25 #include "misc.h"
26 #include "userdir.h"
27 #include "debugmsg.h"
28 #include "rsc_files.h"
29 #include "getshline.h"
30 #include "errors.h"
31 #include "persona.h"
32 #include "filelock.h"
33 
34 top_score highs[5][10];
35 
36 static char *name = 0;
37 static FILE *fscores = 0;
38 
39 void
init_scores(void)40 init_scores (void)
41 {
42   name = sys_persona_if_needed ("score-file", "a+t");
43   dmsg (D_SYSTEM | D_FILE, "opening %s", name);
44   fscores = fopen (name, "a+t");
45   if (!fscores)
46     dperror ("fopen");
47   user_persona ();
48 }
49 
50 void
reinit_scores_if_needed(void)51 reinit_scores_if_needed (void)
52 {
53   char *newname = get_non_null_rsc_file ("score-file");
54   if (strcmp (newname, name)) {
55     free_scores ();
56     init_scores ();
57   }
58   free (newname);
59 }
60 
61 static int
cmp_scores(const void * r1,const void * r2)62 cmp_scores (const void *r1, const void *r2)
63 {
64   return (((const top_score *) r2)->points - ((const top_score *) r1)->points);
65 }
66 
67 void
sort_scores(void)68 sort_scores (void)
69 {
70   int i;
71   for (i = 0; i < 5; i++)
72     qsort (highs[i], 10, sizeof (top_score), cmp_scores);
73 }
74 
75 int
find_score_by_gameid(gameid_ptr gid)76 find_score_by_gameid (gameid_ptr gid)
77 {
78   int i;
79   for (i = 0; i < 10; i++)
80     if (equal_gameid (highs[0][i].gid, gid))
81       return (i);
82   return (-1);
83 }
84 
85 void
clear_scores(void)86 clear_scores (void)
87 {
88   int i, j;
89   for (i = 0; i < 5; i++)
90     for (j = 0; j < 10; j++) {
91       strncpy (highs[i][j].name, "\0", 8);
92       empty_gameid (highs[i][j].gid);
93       highs[i][j].points = (10 - j) * 250;
94     }
95 }
96 
97 void
write_scores_locked(void)98 write_scores_locked (void)
99 {
100   unsigned int i, j;
101 
102   if (fscores == 0) {
103     wmsg (_("cannot write %s"), name);
104   } else {
105     fflush (fscores);
106     if (ftruncate (fileno (fscores), 0) != 0)
107       emsg (_("%s: truncate error"), name);
108   }
109 
110   if (fscores) {
111     dmsg (D_FILE, "writing scores to %s", name);
112 
113     /* Write down scores to disk.  */
114     for (i = 0; i < 5; ++i)
115       for (j = 0; j < 10; ++j) {
116 	char *gidtxt = gameid_to_text (highs[i][j].gid);
117 	fprintf (fscores, "%u %u %u\n %s\n  %s\n",
118 		 i, j, highs[i][j].points, gidtxt, highs[i][j].name);
119 	free (gidtxt);
120       }
121     /* We need to seek to the beginning of file before
122        calling file_unlock on some systems.  */
123     fseek (fscores, 0L, SEEK_SET);
124     dmsg (D_FILE | D_SYSTEM, "unlocking %s", name);
125     file_unlock (fileno (fscores));
126   }
127 }
128 
129 static void
load_scores_seek(bool exclusive)130 load_scores_seek (bool exclusive)
131 {
132   dmsg (D_FILE, "reading scores from %s", name);
133   if (fscores) {
134     fseek (fscores, 0L, SEEK_SET);
135     dmsg (D_FILE | D_SYSTEM, "locking %s", name);
136     file_lock (fileno (fscores), exclusive);
137   }
138 }
139 
140 void
write_scores(void)141 write_scores (void)
142 {
143   load_scores_seek (true);
144   write_scores_locked ();
145 }
146 
147 static void
load_scores_error(int line)148 load_scores_error (int line)
149 {
150   wmsg (_("%s:%d: parse error.  Clearing score table."), name, line);
151   clear_scores ();
152 }
153 
154 static void
load_scores_read(void)155 load_scores_read (void)
156 {
157   unsigned int i, j;
158   a_u32 points;
159   int endline = 0;
160   char* buf = 0;
161   size_t bufsize = 0;
162   int firstline = 0;
163 
164   clear_scores ();
165 
166   if (fscores == 0)
167     return;
168 
169   /* Read the scores from disk.  */
170   while (getshline_numbered
171          (&firstline, &endline, &buf, &bufsize, fscores) != -1) {
172     if (*buf != ' ') {
173       if (sscanf (buf, "%u %u %u", &i, &j, &points) != 3
174 	  || i >= 5
175 	  || j >= 10) {
176 	load_scores_error (firstline);
177 	return;
178       }
179       highs[i][j].points = points;
180     } else if (buf[1] != ' ') {
181       if (text_to_gameid (buf + 1, highs[i][j].gid)) {
182 	load_scores_error (firstline);
183 	return;
184       }
185     } else {
186       strncpy (highs[i][j].name, buf + 2, PLAYER_NAME_SIZE + 1);
187       chomp (highs[i][j].name);
188     }
189   }
190   free (buf);
191 }
192 
193 void
load_scores(void)194 load_scores (void)
195 {
196   load_scores_seek (false);
197   load_scores_read ();
198   if (fscores) {
199     /* We need to seek to the beginning of file before
200        calling file_unlock on some systems.  */
201     fseek (fscores, 0L, SEEK_SET);
202     dmsg (D_FILE | D_SYSTEM, "unlocking %s", name);
203     file_unlock (fileno (fscores));
204   }
205 }
206 
207 void
load_scores_and_keep_locked(void)208 load_scores_and_keep_locked (void)
209 {
210   load_scores_seek (true);
211   load_scores_read ();
212   if (fscores)
213     fseek (fscores, 0L, SEEK_SET);
214 }
215 
216 void
free_scores(void)217 free_scores (void)
218 {
219   dmsg (D_MISC, "free scores");
220 
221   if (name)
222     free (name);
223   name = 0;
224 
225   if (fscores) {
226     fclose (fscores);
227     fscores = 0;
228   }
229 }
230 
231 /* Insert an score entry in the score table.
232    If NAME is NULL, we just return true or false, whether
233    the score would have been inserted or not (if it's too low).  */
234 bool
insert_scores(int gamemode,const char * player_name,a_gameid gid,a_u32 points)235 insert_scores (int gamemode, const char *player_name,
236 	       a_gameid gid, a_u32 points)
237 {
238   int mag = find_score_by_gameid (gid);
239   /* If we are not goiing to override the score for an existing game,
240      override the last entry of the game...  */
241   if (mag == -1)
242     mag = 9;
243   /* ... unless the score is too low, of course.  */
244   if (highs[gamemode][mag].points >= points)
245     mag = -1;
246 
247   if (mag == -1)
248     return false;
249 
250   if (player_name) {
251     copy_gameid (highs[gamemode][mag].gid, gid);
252     highs[gamemode][mag].points = points;
253     strcpy (highs[gamemode][mag].name, player_name);
254     sort_scores ();
255   }
256   return true;
257 }
258