ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/init.C
Revision: 1.73
Committed: Wed Nov 4 00:08:44 2009 UTC (14 years, 7 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.72: +0 -157 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 elmex 1.1 /*
2 root 1.59 * This file is part of Deliantra, the Roguelike Realtime MMORPG.
3 pippijn 1.34 *
4 root 1.64 * Copyright (©) 2005,2006,2007,2008 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
5 root 1.52 * Copyright (©) 2002,2007 Mark Wedel & Crossfire Development Team
6     * Copyright (©) 1992,2007 Frank Tore Johansen
7 pippijn 1.34 *
8 root 1.71 * Deliantra is free software: you can redistribute it and/or modify it under
9     * the terms of the Affero GNU General Public License as published by the
10     * Free Software Foundation, either version 3 of the License, or (at your
11     * option) any later version.
12 pippijn 1.34 *
13 root 1.56 * This program is distributed in the hope that it will be useful,
14     * but WITHOUT ANY WARRANTY; without even the implied warranty of
15     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16     * GNU General Public License for more details.
17 pippijn 1.34 *
18 root 1.71 * You should have received a copy of the Affero GNU General Public License
19     * and the GNU General Public License along with this program. If not, see
20     * <http://www.gnu.org/licenses/>.
21 root 1.52 *
22 root 1.59 * The authors can be reached via e-mail to <support@deliantra.net>
23 pippijn 1.34 */
24 elmex 1.1
25     #include <global.h>
26     #include <material.h>
27     #include <loader.h>
28 root 1.22 #include <sproto.h>
29 elmex 1.1
30     /* This loads the settings file. There could be debate whether this should
31     * be here or in the common directory - but since only the server needs this
32     * information, having it here probably makes more sense.
33     */
34 root 1.66 void
35 root 1.9 load_settings (void)
36 elmex 1.1 {
37 root 1.9 char buf[MAX_BUF], *cp;
38     int has_val, comp;
39     FILE *fp;
40    
41     sprintf (buf, "%s/settings", settings.confdir);
42    
43     /* We don't require a settings file at current time, but down the road,
44     * there will probably be so many values that not having a settings file
45     * will not be a good thing.
46     */
47 root 1.43 if (!(fp = open_and_uncompress (buf, 0, &comp)))
48 root 1.9 {
49 root 1.43 LOG (llevError, "Error: No settings file found\n");
50     exit (1);
51 elmex 1.1 }
52 root 1.43
53 root 1.9 while (fgets (buf, MAX_BUF - 1, fp) != NULL)
54     {
55     if (buf[0] == '#')
56     continue;
57     /* eliminate newline */
58     if ((cp = strrchr (buf, '\n')) != NULL)
59     *cp = '\0';
60    
61     /* Skip over empty lines */
62     if (buf[0] == 0)
63     continue;
64    
65     /* Skip all the spaces and set them to nulls. If not space,
66     * set cp to "" to make strcpy's and the like easier down below.
67     */
68     if ((cp = strchr (buf, ' ')) != NULL)
69     {
70     while (*cp == ' ')
71     *cp++ = 0;
72     has_val = 1;
73     }
74     else
75     {
76 root 1.44 cp = (char *)"";
77 root 1.9 has_val = 0;
78     }
79    
80 root 1.72 if (!strcasecmp (buf, "not_permadeth"))
81 root 1.9 {
82     if (!strcasecmp (cp, "on") || !strcasecmp (cp, "true"))
83     {
84     settings.not_permadeth = TRUE;
85     }
86     else if (!strcasecmp (cp, "off") || !strcasecmp (cp, "false"))
87     {
88     settings.not_permadeth = FALSE;
89     }
90     else
91     {
92     LOG (llevError, "load_settings: Unknown value for not_permadeth" ": %s\n", cp);
93     }
94     }
95     else if (!strcasecmp (buf, "resurrection"))
96     {
97     if (!strcasecmp (cp, "on") || !strcasecmp (cp, "true"))
98     {
99     settings.resurrection = TRUE;
100     }
101     else if (!strcasecmp (cp, "off") || !strcasecmp (cp, "false"))
102     {
103     settings.resurrection = FALSE;
104     }
105     else
106     {
107     LOG (llevError, "load_settings: Unknown value for resurrection" ": %s\n", cp);
108     }
109     }
110     else if (!strcasecmp (buf, "set_title"))
111     {
112     if (!strcasecmp (cp, "on") || !strcasecmp (cp, "true"))
113     {
114     settings.set_title = TRUE;
115     }
116     else if (!strcasecmp (cp, "off") || !strcasecmp (cp, "false"))
117     {
118     settings.set_title = FALSE;
119     }
120     else
121     {
122     LOG (llevError, "load_settings: Unknown value for set_title" ": %s\n", cp);
123     }
124     }
125     else if (!strcasecmp (buf, "search_items"))
126     {
127     if (!strcasecmp (cp, "on") || !strcasecmp (cp, "true"))
128     {
129     settings.search_items = TRUE;
130     }
131     else if (!strcasecmp (cp, "off") || !strcasecmp (cp, "false"))
132     {
133     settings.search_items = FALSE;
134     }
135     else
136     {
137     LOG (llevError, "load_settings: Unknown value for search_items" ": %s\n", cp);
138     }
139     }
140     else if (!strcasecmp (buf, "spell_encumbrance"))
141     {
142     if (!strcasecmp (cp, "on") || !strcasecmp (cp, "true"))
143     {
144     settings.spell_encumbrance = TRUE;
145     }
146     else if (!strcasecmp (cp, "off") || !strcasecmp (cp, "false"))
147     {
148     settings.spell_encumbrance = FALSE;
149     }
150     else
151     {
152     LOG (llevError, "load_settings: Unknown value for " "spell_encumbrance: %s\n", cp);
153     }
154     }
155     else if (!strcasecmp (buf, "spell_failure_effects"))
156     {
157     if (!strcasecmp (cp, "on") || !strcasecmp (cp, "true"))
158     {
159     settings.spell_failure_effects = TRUE;
160     }
161     else if (!strcasecmp (cp, "off") || !strcasecmp (cp, "false"))
162     {
163     settings.spell_failure_effects = FALSE;
164     }
165     else
166     {
167     LOG (llevError, "load_settings: Unknown value for " "spell_failure_effects: %s\n", cp);
168     }
169     }
170     else if (!strcasecmp (buf, "spellpoint_level_depend"))
171     {
172     if (!strcasecmp (cp, "on") || !strcasecmp (cp, "true"))
173     {
174     settings.spellpoint_level_depend = TRUE;
175     }
176     else if (!strcasecmp (cp, "off") || !strcasecmp (cp, "false"))
177     {
178     settings.spellpoint_level_depend = FALSE;
179     }
180     else
181     {
182     LOG (llevError, "load_settings: Unknown value for " "spellpoint_level_depend: %s\n", cp);
183     }
184     }
185     else if (!strcasecmp (buf, "stat_loss_on_death"))
186     {
187     if (!strcasecmp (cp, "on") || !strcasecmp (cp, "true"))
188     {
189     settings.stat_loss_on_death = TRUE;
190     }
191     else if (!strcasecmp (cp, "off") || !strcasecmp (cp, "false"))
192     {
193     settings.stat_loss_on_death = FALSE;
194     }
195     else
196     {
197     LOG (llevError, "load_settings: Unknown value for " "stat_loss_on_death: %s\n", cp);
198     }
199     }
200     else if (!strcasecmp (buf, "use_permanent_experience"))
201     {
202     LOG (llevError, "use_permanent_experience is deprecated, use" "permenent_experience_percentage instead\n");
203     }
204     else if (!strcasecmp (buf, "permanent_experience_percentage"))
205     {
206     int val = atoi (cp);
207    
208     if (val < 0 || val > 100)
209     LOG (llevError, "load_settings: permenent_experience_percentage" "must be between 0 and 100, %d is invalid\n", val);
210     else
211     settings.permanent_exp_ratio = val;
212     }
213     else if (!strcasecmp (buf, "death_penalty_percentage"))
214     {
215     int val = atoi (cp);
216    
217     if (val < 0 || val > 100)
218     LOG (llevError, "load_settings: death_penalty_percentage" "must be between 0 and 100, %d is invalid\n", val);
219     else
220     settings.death_penalty_ratio = val;
221     }
222     else if (!strcasecmp (buf, "death_penalty_levels"))
223     {
224     int val = atoi (cp);
225    
226     if (val < 0 || val > 255)
227     LOG (llevError, "load_settings: death_penalty_levels" "can not be negative, %d is invalid\n", val);
228     else
229     settings.death_penalty_level = val;
230     }
231     else if (!strcasecmp (buf, "balanced_stat_loss"))
232     {
233     if (!strcasecmp (cp, "on") || !strcasecmp (cp, "true"))
234     {
235     settings.balanced_stat_loss = TRUE;
236     }
237     else if (!strcasecmp (cp, "off") || !strcasecmp (cp, "false"))
238     {
239     settings.balanced_stat_loss = FALSE;
240     }
241     else
242     {
243     LOG (llevError, "load_settings: Unknown value for " "balanced_stat_loss: %s\n", cp);
244     }
245     }
246     else if (!strcasecmp (buf, "simple_exp"))
247     {
248     if (!strcasecmp (cp, "on") || !strcasecmp (cp, "true"))
249     {
250     settings.simple_exp = TRUE;
251     }
252     else if (!strcasecmp (cp, "off") || !strcasecmp (cp, "false"))
253     {
254     settings.simple_exp = FALSE;
255     }
256     else
257     {
258     LOG (llevError, "load_settings: Unknown value for simple_exp: %s\n", cp);
259     }
260     }
261     else if (!strcasecmp (buf, "item_power_factor"))
262     {
263     float tmp = atof (cp);
264    
265     if (tmp < 0)
266     LOG (llevError, "load_settings: item_power_factor must be a positive number (%f < 0)\n", tmp);
267     else
268     settings.item_power_factor = tmp;
269     }
270     else if (!strcasecmp (buf, "pk_luck_penalty"))
271     {
272     sint16 val = atoi (cp);
273    
274     if (val < -100 || val > 100)
275     LOG (llevError, "load_settings: pk_luck_penalty must be between -100 and 100" ", %d is invalid\n", val);
276     else
277     settings.pk_luck_penalty = val;
278     }
279     else if (!strcasecmp (buf, "set_friendly_fire"))
280     {
281     int val = atoi (cp);
282 elmex 1.1
283 root 1.9 if (val < 0 || val > 100)
284     LOG (llevError, "load_settings: set_friendly_fire must be between 0 an 100" ", %d is invalid\n", val);
285     else
286     settings.set_friendly_fire = val;
287     }
288     else if (!strcasecmp (buf, "armor_max_enchant"))
289     {
290     int max_e = atoi (cp);
291    
292     if (max_e <= 0)
293     LOG (llevError, "load_settings: armor_max_enchant is %d\n", max_e);
294     else
295 elmex 1.1 settings.armor_max_enchant = max_e;
296 root 1.9 }
297     else if (!strcasecmp (buf, "armor_weight_reduction"))
298     {
299     int wr = atoi (cp);
300    
301     if (wr < 0)
302     LOG (llevError, "load_settings: armor_weight_reduction is %d\n", wr);
303     else
304 elmex 1.1 settings.armor_weight_reduction = wr;
305     }
306 root 1.9 else if (!strcasecmp (buf, "armor_weight_linear"))
307     {
308     if (!strcasecmp (cp, "on") || !strcasecmp (cp, "true"))
309     {
310     settings.armor_weight_linear = TRUE;
311     }
312     else if (!strcasecmp (cp, "off") || !strcasecmp (cp, "false"))
313     {
314     settings.armor_weight_linear = FALSE;
315     }
316     else
317     {
318     LOG (llevError, "load_settings: unknown value for armor_weight_linear: %s\n", cp);
319     }
320    
321     }
322     else if (!strcasecmp (buf, "armor_speed_improvement"))
323     {
324     int wr = atoi (cp);
325 elmex 1.1
326 root 1.9 if (wr < 0)
327     LOG (llevError, "load_settings: armor_speed_improvement is %d\n", wr);
328     else
329 elmex 1.1 settings.armor_speed_improvement = wr;
330     }
331 root 1.9 else if (!strcasecmp (buf, "armor_speed_linear"))
332     {
333     if (!strcasecmp (cp, "on") || !strcasecmp (cp, "true"))
334     {
335     settings.armor_speed_linear = TRUE;
336     }
337     else if (!strcasecmp (cp, "off") || !strcasecmp (cp, "false"))
338     {
339     settings.armor_speed_linear = FALSE;
340     }
341     else
342     {
343     LOG (llevError, "load_settings: unknown value for armor_speed_linear: %s\n", cp);
344     }
345    
346     }
347     else
348 root 1.50 LOG (llevError, "Unknown value in settings file: %s\n", buf);
349 elmex 1.1 }
350 root 1.20
351 root 1.9 close_and_delete (fp, comp);
352 elmex 1.1 }
353    
354     /*
355     * init() is called only once, when starting the program.
356     */
357 root 1.9 void
358     init (int argc, char **argv)
359     {
360 root 1.37 init_done = 0; /* Must be done before init_signal() */
361 root 1.36
362 root 1.42 init_environ ();
363 root 1.14 cfperl_init ();
364 root 1.9 init_done = 1;
365 elmex 1.1 }
366    
367 root 1.9 void
368     usage (void)
369     {
370 root 1.62 fprintf (stderr, "Usage: deliantra-server [-h] [-<flags>]...\n");
371 elmex 1.1 }
372    
373 root 1.9 void
374     help (void)
375     {
376    
377 elmex 1.1 /* The information in usage is redundant with what is given below, so why call it? */
378 root 1.9
379 elmex 1.1 /* usage();*/
380 root 1.9 printf ("Flags:\n");
381     printf (" -csport <port> Specifies the port to use for the new client/server code.\n");
382     printf (" -d Turns on some debugging.\n");
383     printf (" +d Turns off debugging (useful if server compiled with debugging\n");
384     printf (" as default).\n");
385     printf (" -detach The server will go in the background, closing all\n");
386     printf (" connections to the tty.\n");
387     printf (" -h Display this information.\n");
388     printf (" -log <file> Specifies which file to send output to.\n");
389     printf (" Only has meaning if -detach is specified.\n");
390     printf (" -mon Turns on monster debugging.\n");
391     printf (" -o Prints out info on what was defined at compile time.\n");
392     printf (" -s Display the high-score list.\n");
393     printf (" -score <name or class> Displays all high scores with matching name/class.\n");
394     printf (" -v Print version and contributors.\n");
395     printf (" -data Sets the lib dir (archetypes, treasures, etc.)\n");
396     printf (" -local Read/write local data (hiscore, unique items, etc.)\n");
397     printf (" -maps Sets the directory for maps.\n");
398     printf (" -arch Sets the archetype file to use.\n");
399     printf (" -regions Sets the regions file to use.\n");
400     printf (" -playerdir Sets the directory for the player files.\n");
401     printf (" -templatedir Sets the directory for template generate maps.\n");
402     printf (" -treasures Sets the treasures file to use.\n");
403     printf (" -uniquedir Sets the unique items/maps directory.\n");
404     printf (" -tmpdir Sets the directory for temporary files (mostly maps.)\n");
405     printf (" -m Lists out suggested experience for all monsters.\n");
406     printf (" -m2 Dumps out abilities.\n");
407     printf (" -m3 Dumps out artifact information.\n");
408     printf (" -m4 Dumps out spell information.\n");
409     printf (" -m5 Dumps out skill information.\n");
410     printf (" -m6 Dumps out race information.\n");
411     printf (" -m7 Dumps out alchemy information.\n");
412     printf (" -m8 Dumps out gods information.\n");
413     printf (" -m9 Dumps out more alchemy information (formula checking).\n");
414     printf (" -mt <name> Dumps out list of treasures for a monster.\n");
415     exit (0);
416     }
417    
418     void
419     init_beforeplay (void)
420     {
421     init_artifacts (); /* If not called before, reads all artifacts from file */
422     init_races (); /* overwrite race designations using entries in lib/races file */
423     init_gods (); /* init linked list of gods from archs */
424     init_readable (); /* inits useful arrays for readable texts */
425     init_formulae (); /* If not called before, reads formulae from file */
426 elmex 1.1 }
427    
428     /* Signal handlers: */
429    
430 root 1.48 static void
431 root 1.33 rec_sigabrt (int i)
432     {
433     signal (SIGABRT, SIG_DFL);
434    
435     LOG (llevError, "SIGABRT received.\n");
436     cleanup ("SIGABRT received", 1);
437     }
438    
439 root 1.48 static void
440 root 1.9 rec_sigsegv (int i)
441     {
442 root 1.33 signal (SIGSEGV, SIG_DFL);
443    
444 root 1.21 LOG (llevError, "SIGSEGV received.\n");
445 root 1.29 cleanup ("SIGSEGV received", 1);
446 elmex 1.1 }
447    
448 root 1.48 static void
449 root 1.9 rec_sigquit (int i)
450     {
451 root 1.33 signal (SIGQUIT, SIG_IGN);
452    
453 root 1.21 LOG (llevInfo, "SIGQUIT received\n");
454 root 1.29 cleanup ("SIGQUIT received", 1);
455 elmex 1.1 }
456    
457 root 1.48 static void
458 root 1.9 rec_sigbus (int i)
459     {
460 root 1.33 signal (SIGBUS, SIG_DFL);
461    
462 root 1.21 LOG (llevError, "SIGBUS received\n");
463 root 1.29 cleanup ("SIGBUS received", 1);
464 elmex 1.1 }
465    
466 root 1.9 void
467 root 1.48 reset_signals ()
468     {
469     signal (SIGABRT, SIG_DFL);
470     signal (SIGQUIT, SIG_DFL);
471     signal (SIGSEGV, SIG_DFL);
472     signal (SIGBUS , SIG_DFL);
473     signal (SIGINT , SIG_DFL);
474     signal (SIGTERM, SIG_DFL);
475     }
476    
477     void
478 root 1.9 init_signals (void)
479     {
480 root 1.37 // large stack, but it's important data we want to save, and it is not usually
481     // being physically allocated anyways
482     const size_t stacksize = 8 * 1024 * 1024 + SIGSTKSZ;
483    
484     stack_t ss;
485     ss.ss_sp = malloc (stacksize);
486     ss.ss_flags = 0;
487     ss.ss_size = stacksize;
488     sigaltstack (&ss, 0);
489    
490     struct sigaction sa;
491    
492     sigfillset (&sa.sa_mask);
493 root 1.49 sa.sa_flags = SA_RESTART;
494 root 1.37
495     sa.sa_handler = SIG_IGN; sigaction (SIGPIPE, &sa, 0);
496     sa.sa_handler = rec_sigabrt; sigaction (SIGABRT, &sa, 0);
497     sa.sa_handler = rec_sigquit; sigaction (SIGQUIT, &sa, 0);
498 root 1.49 sa.sa_handler = rec_sigbus; sigaction (SIGBUS, &sa, 0);
499    
500     sa.sa_flags |= SA_ONSTACK;
501 root 1.37 sa.sa_handler = rec_sigsegv; sigaction (SIGSEGV, &sa, 0);
502 elmex 1.1 }
503    
504     /* init_races() - reads the races file in the lib/ directory, then
505     * overwrites old 'race' entries. This routine allow us to quickly
506     * re-configure the 'alignment' of monsters, objects. Useful for
507     * putting together lists of creatures, etc that belong to gods.
508     */
509 root 1.9 void
510     init_races (void)
511 elmex 1.8 {
512 elmex 1.1 FILE *file;
513     char race[MAX_BUF], fname[MAX_BUF], buf[MAX_BUF], *cp, variable[MAX_BUF];
514 elmex 1.8 archetype *mon = NULL;
515     static int init_done = 0;
516 elmex 1.1
517 elmex 1.8 if (init_done)
518 elmex 1.1 return;
519 elmex 1.8 init_done = 1;
520 root 1.10 first_race = 0;
521 elmex 1.1
522 elmex 1.8 sprintf (fname, "%s/races", settings.datadir);
523 pippijn 1.25 LOG (llevDebug, "Reading races from %s...\n", fname);
524 elmex 1.8 if (!(file = fopen (fname, "r")))
525     {
526 root 1.9 LOG (llevError, "Cannot open races file %s: %s\n", fname, strerror (errno));
527 elmex 1.8 return;
528 elmex 1.1 }
529 elmex 1.8
530     while (fgets (buf, MAX_BUF, file) != NULL)
531     {
532     int set_race = 1, set_list = 1;
533 root 1.9
534 elmex 1.8 if (*buf == '#')
535     continue;
536 root 1.10
537 elmex 1.8 if ((cp = strchr (buf, '\n')) != NULL)
538     *cp = '\0';
539 root 1.10
540 elmex 1.8 cp = buf;
541     while (*cp == ' ' || *cp == '!' || *cp == '@')
542     {
543 root 1.65 if (*cp == '!') set_race = 0;
544     if (*cp == '@') set_list = 0;
545    
546 elmex 1.8 cp++;
547     }
548 root 1.10
549 elmex 1.8 if (sscanf (cp, "RACE %s", variable))
550 root 1.40 /* set new race value */
551     strcpy (race, variable);
552 elmex 1.8 else
553     {
554     char *cp1;
555 root 1.9
556 elmex 1.8 /* Take out beginning spaces */
557     for (cp1 = cp; *cp1 == ' '; cp1++)
558     ;
559     /* Remove newline and trailing spaces */
560     for (cp1 = cp + strlen (cp) - 1; *cp1 == '\n' || *cp1 == ' '; cp1--)
561     {
562     *cp1 = '\0';
563     if (cp == cp1)
564     break;
565     }
566    
567     if (cp[strlen (cp) - 1] == '\n')
568     cp[strlen (cp) - 1] = '\0';
569 root 1.10
570 elmex 1.8 /* set creature race to race value */
571 root 1.12 if ((mon = archetype::find (cp)) == NULL)
572 pippijn 1.25 LOG (llevError, "Creature %s in race file lacks archetype\n", cp);
573 elmex 1.8 else
574     {
575 root 1.70 if (set_race && (!mon->race || strcmp (&mon->race, race)))
576 elmex 1.8 {
577 root 1.54 if (mon->race)
578     LOG (llevDebug, "Resetting race to %s from %s for archetype %s\n", race, &mon->race, &mon->archname);
579 root 1.10
580 root 1.54 mon->race = race;
581 elmex 1.1 }
582 root 1.10
583 elmex 1.8 /* if the arch is a monster, add it to the race list */
584 root 1.54 if (set_list && QUERY_FLAG (mon, FLAG_MONSTER))
585     add_to_racelist (race, mon);
586 elmex 1.8 }
587 elmex 1.1 }
588     }
589 root 1.10
590 elmex 1.8 fclose (file);
591     LOG (llevDebug, "done.\n");
592 elmex 1.1 }
593    
594 root 1.9 void
595     dump_races (void)
596     {
597     racelink *list;
598     objectlink *tmp;
599    
600     for (list = first_race; list; list = list->next)
601     {
602 root 1.10 fprintf (stderr, "\nRACE %s:\t", &list->name);
603 root 1.9 for (tmp = list->member; tmp; tmp = tmp->next)
604 root 1.53 fprintf (stderr, "%s(%d), ", &tmp->ob->arch->archname, tmp->ob->level);
605 elmex 1.1 }
606 root 1.10
607 root 1.9 fprintf (stderr, "\n");
608 elmex 1.1 }
609    
610 root 1.9 void
611     add_to_racelist (const char *race_name, object *op)
612     {
613 elmex 1.1 racelink *race;
614 root 1.9
615     if (!op || !race_name)
616     return;
617 root 1.10
618 root 1.9 race = find_racelink (race_name);
619    
620     if (!race)
621     { /* add in a new race list */
622     race = get_racelist ();
623     race->next = first_race;
624     first_race = race;
625     race->name = race_name;
626     }
627    
628     if (race->member->ob)
629     {
630     objectlink *tmp = get_objectlink ();
631    
632     tmp->next = race->member;
633     race->member = tmp;
634     }
635 root 1.10
636 elmex 1.1 race->nrof++;
637     race->member->ob = op;
638     }
639    
640 root 1.9 racelink *
641     get_racelist ()
642     {
643 root 1.10 racelink *list = new racelink;
644 root 1.9
645 root 1.10 list->name = 0;
646 root 1.9 list->nrof = 0;
647 root 1.10 list->next = 0;
648 root 1.9 list->member = get_objectlink ();
649 elmex 1.1
650     return list;
651     }
652 root 1.9
653     racelink *
654     find_racelink (const char *name)
655     {
656 root 1.10 if (name)
657     for (racelink *link = first_race; link; link = link->next)
658     if (!link->name || !strcmp (name, link->name))
659     return link;
660 root 1.9
661 root 1.10 return 0;
662 elmex 1.1 }