Create new files.
In the header, put your structures, defines, function prototypes, etc. In the source file, put your globals etc, and actual functions.
Then, when the menu is opened, you could read the file (or better yet, when cgame it loaded to avoid unessary overhead of always loading the file) and save the file when the menu is closed (or better yet, only write the new entry if there is a new entry when the menu is closed, or even better yet keep it in a variable array until cgame is shutdown to prevent uneccesary overhead).
EDIT IN PROGRESS:
Going to do a quick example, still typing it out
EDIT:
Actually, I’m not going to do an example. Just take a look at this xpsave system and it has everything you need; it can read/write files, etc. If you still can’t quite grasp it, I may help you out with an example of what you want, but it’s best to learn on your own. BTW, everyone here (due to lack of documentation I figure, unless I’m really missing something) has spent many hours going though the source to figure out what does what, etc. It’s not an easy task. ANyhow, here’s the files… (igone the numbers on the side, I copied and pasted from svn and those are just the line numbers they are on)
g_xpsave.h
1 #ifndef G_XPSAVE_H
2 #define G_XPSAVE_H
3
4 //defines
5 #define XPSAVE_FILENAME "xpsave.dat" //name of xpsave file
6 #define XPSAVE_MAX_RECORDS 16384 //maximum xpsave records
7 #define XPSAVE_ENABLED 1 //determines if xpsave is enabled
8
9 //xpsave structure
10 typedef struct {
11 char guid[33]; //client guid
12 float skill[SK_NUM_SKILLS]; //client skills
13 int time; //xpsave expiration
14 } g_XPSave_t;
15
16 //function prototypes
17 void G_XPSave_ReadConfig(); //read xpsave records from file
18 void G_XPSave_WriteConfig(); //write xpsave records to file
19 qboolean G_XPSave_Load(gentity_t* ent); //load client xpsave record
20 qboolean G_XPSave_Store(gentity_t* ent); //store client xpsave record
21 void G_XPSave_Cleanup(); //cleanup xpsave data
22 void G_XPSave_Duration(int secs, char *duration, int dursize); //get duration of time
23 void G_XPSave_ReadConfig_String(char **cnf, char *s, int size); //read strings from config file
24 void G_XPSave_ReadConfig_Int(char **cnf, int *v); //read integers from config file
25 void G_XPSave_ReadConfig_Float(char **cnf, float *v); //read floats from config file
26 void G_XPSave_WriteConfig_String(char *s, fileHandle_t f); //write strings to config file
27 void G_XPSave_WriteConfig_Int(int v, fileHandle_t f); //write integers to config file
28 void G_XPSave_WriteConfig_Float(float v, fileHandle_t f); //write floats to config file
29
30 #endif
g_xpsave.c
1 //includes
2 #include "g_local.h"
3
4 //globals
5 g_XPSave_t *g_XPSaves[XPSAVE_MAX_RECORDS];
6
7 //read xpsave records from file
8 void G_XPSave_ReadConfig() {
9 //declare variables
10 int len, i;
11 fileHandle_t f;
12 char *cnf, *cnf2, *t;
13 qboolean xpsave_open = qfalse;
14 int xc = 0;
15 g_XPSave_t *x = g_XPSaves[0];
16 float skill;
17
18 //make sure xpsave is enabled
19 if(!(g_xpsave.integer & XPSAVE_ENABLED)) {
20 return;
21 }
22
23 //open xpsave file
24 len = trap_FS_FOpenFile(XPSAVE_FILENAME, &f, FS_READ) ;
25 if(len < 0) {
26 G_Printf("XPSave: could not open xpsave file (%s)
", XPSAVE_FILENAME);
27 return;
28 }
29
30 //read file
31 cnf = malloc(len+1);
32 cnf2 = cnf;
33 trap_FS_Read(cnf, len, f);
34 *(cnf + len) = '\0';
35
36 //close file
37 trap_FS_FCloseFile(f);
38
39 //cleanup old xpsave data
40 G_XPSave_Cleanup();
41
42 //parse file
43 t = COM_Parse(&cnf);
44 while(*t) {
45 if(!Q_stricmp(t, "[xpsave]")) {
46 if(xpsave_open) {
47 g_XPSaves[xc++] = x;
48 }
49 xpsave_open = qfalse;
50 }
51
52 if(xpsave_open) {
53 if(!Q_stricmp(t, "guid")) {
54 G_XPSave_ReadConfig_String(&cnf, x->guid, sizeof(x->guid));
55 } else if(!Q_stricmpn(t, "skill[", 6)) {
56 for(i=0; i<SK_NUM_SKILLS; i++) {
57 if(Q_stricmp(t, va("skill[%i]", i))) {
58 continue;
59 }
60 G_XPSave_ReadConfig_Float(&cnf, &skill);
61 x->skill[i] = skill;
62 break;
63 }
64 } else if(!Q_stricmp(t, "time")) {
65 G_XPSave_ReadConfig_Int(&cnf, &x->time);
66 } else {
67 G_Printf("XPSave: readconfig error (parse error near %s on line %d)
", t, COM_GetCurrentParseLine());
68 }
69 }
70
71 if(!Q_stricmp(t, "[xpsave]")) {
72 if(xc >= XPSAVE_MAX_RECORDS) {
73 G_Printf("XPSave: XPSAVE_MAX_RECORDS exceeded (%i)", XPSAVE_MAX_RECORDS);
74 return;
75 }
76 x = malloc(sizeof(g_XPSave_t));
77 x->guid[0] = '\0';
78 for(i=0; i<SK_NUM_SKILLS; i++) {
79 x->skill[i] = 0.0f;
80 }
81 x->time = 0;
82 xpsave_open = qtrue;
83 }
84 t = COM_Parse(&cnf);
85 }
86
87 //make sure last xpsave record is used also
88 if(xpsave_open) {
89 g_XPSaves[xc++] = x;
90 }
91
92 //release resources
93 free(cnf2);
94
95 //display message
96 G_Printf("XPSave: loaded %d xpsave records
", xc);
97 }
98
99 //write xpsave records to file
100 void G_XPSave_WriteConfig() {
101 //declare variables
102 time_t t;
103 int len, i, j, age;
104 fileHandle_t f;
105
106 //make sure xpsave is enabled
107 if(!(g_xpsave.integer & XPSAVE_ENABLED)) {
108 return;
109 }
110
111 time(&t);
112
113 //open xpsave file
114 len = trap_FS_FOpenFile(XPSAVE_FILENAME, &f, FS_WRITE);
115 if(len < 0) {
116 G_Printf("XPSave: could not open xpsave file (%s)
", XPSAVE_FILENAME);
117 }
118
119 //write xpsave records to file
120 for(i=0; g_XPSaves[i]; i++) {
121 if(!g_XPSaves[i]->time) {
122 continue;
123 }
124 age = t - g_XPSaves[i]->time;
125 if((age > g_xpsave_duration.integer) && (g_xpsave_duration.integer > 0)) {
126 continue;
127 }
128 trap_FS_Write("[xpsave]
", 9, f);
129 trap_FS_Write("guid = ", 19, f);
130 G_XPSave_WriteConfig_String(g_XPSaves[i]->guid, f);
131 for(j=0; j<SK_NUM_SKILLS; j++) {
132 if(g_XPSaves[i]->skill[j] == 0.0f) {
133 continue;
134 }
135 trap_FS_Write(va("skill[%i] = ", j), 19, f);
136 G_XPSave_WriteConfig_Float(g_XPSaves[i]->skill[j], f);
137 }
138 trap_FS_Write("time = ", 19, f);
139 G_XPSave_WriteConfig_Int(g_XPSaves[i]->time, f);
140 trap_FS_Write("
", 1, f);
141 }
142
143 //close file
144 trap_FS_FCloseFile(f);
145
146 //display message
147 G_Printf("XPSave: wrote %d xpsave records
", i);
148 }
149
150 //load client xpsave record
151 qboolean G_XPSave_Load(gentity_t* ent) {
152 //declare variables
153 time_t t;
154 char userinfo[MAX_INFO_STRING], *guid;
155 int clientNum, i, age;
156 qboolean found = qfalse;
157 g_XPSave_t *x = g_XPSaves[0];
158 char agestr[MAX_STRING_CHARS];
159
160 //make sure this is a client
161 if(!ent || !ent->client) {
162 return qfalse;
163 }
164
165 //make sure xpsave is enabled
166 if(!(g_xpsave.integer & XPSAVE_ENABLED)) {
167 return qfalse;
168 }
169
170 //make sure we can get a valid time
171 if(!time(&t)) {
172 return qfalse;
173 }
174
175 //get client guid
176 clientNum = ent - g_entities;
177 trap_GetUserinfo(clientNum, userinfo, sizeof(userinfo));
178 guid = Info_ValueForKey(userinfo, "cl_guid");
179
180 //make sure client has a valid guid
181 if(!guid[0] || strlen(guid) != 32) {
182 return qfalse;
183 }
184
185 //determine if the client has an xpsave record
186 for(i=0; g_XPSaves[i]; i++) {
187 if(!Q_stricmp(g_XPSaves[i]->guid, guid)) {
188 found = qtrue;
189 x = g_XPSaves[i];
190 break;
191 }
192 }
193 if(!found) {
194 return qfalse;
195 }
196
197 //determine if xpsave record is expired
198 age = t - x->time;
199 if(age > g_xpsave_duration.integer) {
200 return qfalse;
201 }
202
203 //give client his xp
204 for(i=0; i<SK_NUM_SKILLS; i++) {
205 ent->client->sess.skillpoints[i] = x->skill[i];
206 ent->client->sess.startxptotal += x->skill[i];
207 }
208 ent->client->ps.stats[STAT_XP] = (int)ent->client->sess.startxptotal;
209
210 //recalculate client rank
211 G_CalcRank(ent->client);
212 BG_PlayerStateToEntityState(&ent->client->ps, &ent->s, qtrue);
213
214 //display message to client
215 G_XPSave_Duration(age, agestr, sizeof(agestr));
216 CP(va("print \"^3XPSave: loaded stored xpsave state from %s ago
\"", agestr));
217
218 //return success
219 return qtrue;
220 }
221
222 //store client xpsave record
223 qboolean G_XPSave_Store(gentity_t* ent) {
224 //declare variables
225 time_t t;
226 char userinfo[MAX_INFO_STRING], *guid;
227 int clientNum, i, j;
228 g_XPSave_t *x = g_XPSaves[0];
229 qboolean found = qfalse;
230
231 //make sure this is a client
232 if(!ent || !ent->client) {
233 return qfalse;
234 }
235
236 //make sure xpsave is enabled
237 if(!(g_xpsave.integer & XPSAVE_ENABLED)) {
238 return qfalse;
239 }
240
241 //make sure we can get a valid time
242 if(!time(&t)) {
243 return qfalse;
244 }
245
246 //make sure client is still connected
247 if(ent->client->pers.connected != CON_CONNECTED) {
248 return qfalse;
249 }
250
251 //get client guid
252 clientNum = ent - g_entities;
253 trap_GetUserinfo(clientNum, userinfo, sizeof(userinfo));
254 guid = Info_ValueForKey(userinfo, "cl_guid");
255
256 //make sure client has a valid guid
257 if(!guid[0] || strlen(guid) != 32) {
258 return qfalse;
259 }
260
261 //determine if client allready has an xpsave record
262 for(i=0; g_XPSaves[i]; i++) {
263 if(!Q_stricmp(g_XPSaves[i]->guid, guid)) {
264 x = g_XPSaves[i];
265 found = qtrue;
266 break;
267 }
268 }
269
270 //create new xpsave record for client
271 if(!found) {
272 if(i == XPSAVE_MAX_RECORDS) {
273 G_Printf("XPSave: cannot save record, XPSAVE_MAX_RECORDS exceeded (%i)", XPSAVE_MAX_RECORDS);
274 return qfalse;
275 }
276 x = malloc(sizeof(g_XPSave_t));
277 x->guid[0] = '\0';
278 for(j=0; j<SK_NUM_SKILLS; j++) {
279 x->skill[j] = 0.0f;
280 }
281 x->time = 0;
282 g_XPSaves[i] = x;
283 }
284
285 //store xpsave record
286 Q_strncpyz(x->guid, guid, sizeof(x->guid));
287 x->time = t;
288 for(i=0; i<SK_NUM_SKILLS; i++) {
289 x->skill[i] = ent->client->sess.skillpoints[i];
290 }
291
292 //return success
293 return qtrue;
294 }
295
296 //cleanup xpsave data
297 void G_XPSave_Cleanup() {
298 //declare variables
299 int i = 0;
300
301 //cleanup xpsave data
302 for(i=0; g_XPSaves[i]; i++) {
303 free(g_XPSaves[i]);
304 g_XPSaves[i] = NULL;
305 }
306 }
307
308 //get duration of time
309 void G_XPSave_Duration(int secs, char *duration, int dursize) {
310 if(secs > 1576800000 || secs < 0) {
311 Q_strncpyz(duration, "PERMANENT", dursize);
312 } else if(secs > 63072000) {
313 Com_sprintf(duration, dursize, "%d years", (secs / 31536000));
314 } else if(secs > 31536000) {
315 Q_strncpyz(duration, "1 year", dursize);
316 } else if(secs > 5184000) {
317 Com_sprintf(duration, dursize, "%i months", (secs / 2592000));
318 } else if(secs > 2592000) {
319 Q_strncpyz(duration, "1 month", dursize);
320 } else if(secs > 172800) {
321 Com_sprintf(duration, dursize, "%i days", (secs / 86400));
322 } else if(secs > 86400) {
323 Q_strncpyz(duration, "1 day", dursize);
324 } else if(secs > 7200) {
325 Com_sprintf(duration, dursize, "%i hours", (secs / 3600));
326 } else if(secs > 3600) {
327 Q_strncpyz(duration, "1 hour", dursize);
328 } else if(secs > 120) {
329 Com_sprintf(duration, dursize, "%i mins", (secs / 60));
330 } else if(secs > 60) {
331 Q_strncpyz(duration, "1 minute", dursize);
332 } else {
333 Com_sprintf(duration, dursize, "%i secs", secs);
334 }
335 }
336
337 //read strings from config file
338 void G_XPSave_ReadConfig_String(char **cnf, char *s, int size) {
339 char *t;
340
341 t = COM_ParseExt(cnf, qfalse);
342 if(!strcmp(t, "=")) {
343 t = COM_ParseExt(cnf, qfalse);
344 } else {
345 G_Printf("XPSave: readconfig error (missing = before %s on line %d)
", t, COM_GetCurrentParseLine());
346 }
347 s[0] = '\0';
348 while(t[0]) {
349 if((s[0] == '\0' && strlen(t) <= size) || (strlen(t)+strlen(s) < size)) {
350 Q_strcat(s, size, t);
351 Q_strcat(s, size, " ");
352 }
353 t = COM_ParseExt(cnf, qfalse);
354 }
355 // trim the trailing space
356 if(strlen(s) > 0 && s[strlen(s)-1] == ' ') {
357 s[strlen(s)-1] = '\0';
358 }
359 }
360
361 //read integers from config file
362 void G_XPSave_ReadConfig_Int(char **cnf, int *v) {
363 char *t;
364
365 t = COM_ParseExt(cnf, qfalse);
366 if(!strcmp(t, "=")) {
367 t = COM_ParseExt(cnf, qfalse);
368 } else {
369 G_Printf("XPSave: readconfig error (missing = before %s on line %d)
", t, COM_GetCurrentParseLine());
370 }
371 *v = atoi(t);
372 }
373
374 //read floats from config file
375 void G_XPSave_ReadConfig_Float(char **cnf, float *v) {
376 char *t;
377
378 t = COM_ParseExt(cnf, qfalse);
379 if(!strcmp(t, "=")) {
380 t = COM_ParseExt(cnf, qfalse);
381 } else {
382 G_Printf("XPSave: readconfig error (missing = before %s on line %d)
", t, COM_GetCurrentParseLine());
383 }
384 *v = atof(t);
385 }
386
387 //write strings to config file
388 void G_XPSave_WriteConfig_String(char *s, fileHandle_t f) {
389 char buf[MAX_STRING_CHARS];
390
391 buf[0] = '\0';
392 if(s[0]) {
393 Q_strncpyz(buf, s, sizeof(buf));
394 trap_FS_Write(buf, strlen(buf), f);
395 }
396 trap_FS_Write("
", 1, f);
397 }
398
399 //write integers to config file
400 void G_XPSave_WriteConfig_Int(int v, fileHandle_t f) {
401 char buf[32];
402
403 sprintf(buf, "%d", v);
404 if(buf[0]) trap_FS_Write(buf, strlen(buf), f);
405 trap_FS_Write("
", 1, f);
406 }
407
408 //write floats to config file
409 void G_XPSave_WriteConfig_Float(float v, fileHandle_t f) {
410 char buf[32];
411
412 sprintf(buf, "%f", v);
413 if(buf[0]) trap_FS_Write(buf, strlen(buf), f);
414 trap_FS_Write("
", 1, f);
415 }
EDIT:
If you can’t read it with the code blocks the way they are, here are links to the files on my svn (use a tab size of 3 to get proper alignment):
g_xpsave.h
g_xpsave.c