/*
* This file is part of Deliantra, the Roguelike Realtime MMORPG.
*
* Copyright (©) 2005,2006,2007,2008 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
* Copyright (©) 2002,2007 Mark Wedel & Crossfire Development Team
* Copyright (©) 1992,2007 Frank Tore Johansen
*
* Deliantra is free software: you can redistribute it and/or modify it under
* the terms of the Affero GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the Affero GNU General Public License
* and the GNU General Public License along with this program. If not, see
* .
*
* The authors can be reached via e-mail to
*/
#include
#include
#include
#include
//TODO: make this a constructor
static materialtype_t *
get_empty_mat (void)
{
materialtype_t *mt;
int i;
mt = new materialtype_t;
mt->name = shstr_unknown;
mt->description = 0;
for (i = 0; i < NROFATTACKS; i++)
{
mt->save[i] = 0;
mt->mod[i] = 0;
}
mt->chance = 0;
mt->difficulty = 0;
mt->magic = 0;
mt->damage = 0;
mt->wc = 0;
mt->ac = 0;
mt->sp = 0;
mt->weight = 100;
mt->value = 100;
mt->density = 1;
mt->next = 0;
return mt;
}
void
load_materials (void)
{
char filename[MAX_BUF];
sprintf (filename, "%s/materials", settings.datadir);
LOG (llevDebug, "Reading material type data from %s...\n", filename);
//TODO: somehow free old materials, or update them in-place
materialt = 0;
object_thawer thawer (filename);
if (!thawer)
{
LOG (llevError, "Cannot open %s for reading\n", filename);
goto done;
}
while (thawer.kw != KW_name)
{
thawer.next ();
if (thawer.kw == KW_EOF)
goto done;
}
materialtype_t *mt;
for (;;)
{
switch (thawer.kw)
{
case KW_name:
mt = get_empty_mat ();
mt->next = materialt;
materialt = mt;
thawer.get (mt->name);
mt->description = mt->name;
break;
case KW_description:
thawer.get (mt->description);
break;
case KW_material:
thawer.get (mt->material);
break;
case KW_saves:
{
const char *cp = thawer.get_str () - 1;
for (int i = 0; i < NROFATTACKS; i++)
{
if (!cp)
{
mt->save[i] = 0;
continue;
}
int value;
++cp;
sscanf (cp, "%d", &value);
mt->save[i] = (sint8) value;
cp = strchr (cp, ',');
}
}
break;
case KW_mods:
{
const char *cp = thawer.get_str () - 1;
for (int i = 0; i < NROFATTACKS; i++)
{
if (!cp)
{
mt->save[i] = 0;
continue;
}
++cp;
int value;
sscanf (cp, "%d", &value);
mt->mod[i] = (sint8) value;
cp = strchr (cp, ',');
}
}
break;
case KW_chance: thawer.get (mt->chance); break;
case KW_difficulty: // cf+ alias, not original cf
case KW_diff: thawer.get (mt->difficulty); break;
case KW_magic: thawer.get (mt->magic); break;
case KW_dam: // cf+ alias, not original cf
case KW_damage: thawer.get (mt->damage); break;
case KW_wc: thawer.get (mt->wc); break;
case KW_ac: thawer.get (mt->ac); break;
case KW_sp: thawer.get (mt->sp); break;
case KW_weight: thawer.get (mt->weight); break;
case KW_value: thawer.get (mt->value); break;
case KW_density: thawer.get (mt->density); break;
case KW_EOF:
goto done;
default:
if (!thawer.parse_error ("materials file", "materials"))
goto done;
break;
}
thawer.next ();
}
done:
if (!materialt)
materialt = get_empty_mat ();
LOG (llevDebug, "Done.\n");
}
/* This loads the settings file. There could be debate whether this should
* be here or in the common directory - but since only the server needs this
* information, having it here probably makes more sense.
*/
void
load_settings (void)
{
char buf[MAX_BUF], *cp;
int has_val, comp;
FILE *fp;
sprintf (buf, "%s/settings", settings.confdir);
/* We don't require a settings file at current time, but down the road,
* there will probably be so many values that not having a settings file
* will not be a good thing.
*/
if (!(fp = open_and_uncompress (buf, 0, &comp)))
{
LOG (llevError, "Error: No settings file found\n");
exit (1);
}
while (fgets (buf, MAX_BUF - 1, fp) != NULL)
{
if (buf[0] == '#')
continue;
/* eliminate newline */
if ((cp = strrchr (buf, '\n')) != NULL)
*cp = '\0';
/* Skip over empty lines */
if (buf[0] == 0)
continue;
/* Skip all the spaces and set them to nulls. If not space,
* set cp to "" to make strcpy's and the like easier down below.
*/
if ((cp = strchr (buf, ' ')) != NULL)
{
while (*cp == ' ')
*cp++ = 0;
has_val = 1;
}
else
{
cp = (char *)"";
has_val = 0;
}
if (!strcasecmp (buf, "not_permadeth"))
{
if (!strcasecmp (cp, "on") || !strcasecmp (cp, "true"))
{
settings.not_permadeth = TRUE;
}
else if (!strcasecmp (cp, "off") || !strcasecmp (cp, "false"))
{
settings.not_permadeth = FALSE;
}
else
{
LOG (llevError, "load_settings: Unknown value for not_permadeth" ": %s\n", cp);
}
}
else if (!strcasecmp (buf, "resurrection"))
{
if (!strcasecmp (cp, "on") || !strcasecmp (cp, "true"))
{
settings.resurrection = TRUE;
}
else if (!strcasecmp (cp, "off") || !strcasecmp (cp, "false"))
{
settings.resurrection = FALSE;
}
else
{
LOG (llevError, "load_settings: Unknown value for resurrection" ": %s\n", cp);
}
}
else if (!strcasecmp (buf, "set_title"))
{
if (!strcasecmp (cp, "on") || !strcasecmp (cp, "true"))
{
settings.set_title = TRUE;
}
else if (!strcasecmp (cp, "off") || !strcasecmp (cp, "false"))
{
settings.set_title = FALSE;
}
else
{
LOG (llevError, "load_settings: Unknown value for set_title" ": %s\n", cp);
}
}
else if (!strcasecmp (buf, "search_items"))
{
if (!strcasecmp (cp, "on") || !strcasecmp (cp, "true"))
{
settings.search_items = TRUE;
}
else if (!strcasecmp (cp, "off") || !strcasecmp (cp, "false"))
{
settings.search_items = FALSE;
}
else
{
LOG (llevError, "load_settings: Unknown value for search_items" ": %s\n", cp);
}
}
else if (!strcasecmp (buf, "spell_encumbrance"))
{
if (!strcasecmp (cp, "on") || !strcasecmp (cp, "true"))
{
settings.spell_encumbrance = TRUE;
}
else if (!strcasecmp (cp, "off") || !strcasecmp (cp, "false"))
{
settings.spell_encumbrance = FALSE;
}
else
{
LOG (llevError, "load_settings: Unknown value for " "spell_encumbrance: %s\n", cp);
}
}
else if (!strcasecmp (buf, "spell_failure_effects"))
{
if (!strcasecmp (cp, "on") || !strcasecmp (cp, "true"))
{
settings.spell_failure_effects = TRUE;
}
else if (!strcasecmp (cp, "off") || !strcasecmp (cp, "false"))
{
settings.spell_failure_effects = FALSE;
}
else
{
LOG (llevError, "load_settings: Unknown value for " "spell_failure_effects: %s\n", cp);
}
}
else if (!strcasecmp (buf, "spellpoint_level_depend"))
{
if (!strcasecmp (cp, "on") || !strcasecmp (cp, "true"))
{
settings.spellpoint_level_depend = TRUE;
}
else if (!strcasecmp (cp, "off") || !strcasecmp (cp, "false"))
{
settings.spellpoint_level_depend = FALSE;
}
else
{
LOG (llevError, "load_settings: Unknown value for " "spellpoint_level_depend: %s\n", cp);
}
}
else if (!strcasecmp (buf, "stat_loss_on_death"))
{
if (!strcasecmp (cp, "on") || !strcasecmp (cp, "true"))
{
settings.stat_loss_on_death = TRUE;
}
else if (!strcasecmp (cp, "off") || !strcasecmp (cp, "false"))
{
settings.stat_loss_on_death = FALSE;
}
else
{
LOG (llevError, "load_settings: Unknown value for " "stat_loss_on_death: %s\n", cp);
}
}
else if (!strcasecmp (buf, "use_permanent_experience"))
{
LOG (llevError, "use_permanent_experience is deprecated, use" "permenent_experience_percentage instead\n");
}
else if (!strcasecmp (buf, "permanent_experience_percentage"))
{
int val = atoi (cp);
if (val < 0 || val > 100)
LOG (llevError, "load_settings: permenent_experience_percentage" "must be between 0 and 100, %d is invalid\n", val);
else
settings.permanent_exp_ratio = val;
}
else if (!strcasecmp (buf, "death_penalty_percentage"))
{
int val = atoi (cp);
if (val < 0 || val > 100)
LOG (llevError, "load_settings: death_penalty_percentage" "must be between 0 and 100, %d is invalid\n", val);
else
settings.death_penalty_ratio = val;
}
else if (!strcasecmp (buf, "death_penalty_levels"))
{
int val = atoi (cp);
if (val < 0 || val > 255)
LOG (llevError, "load_settings: death_penalty_levels" "can not be negative, %d is invalid\n", val);
else
settings.death_penalty_level = val;
}
else if (!strcasecmp (buf, "balanced_stat_loss"))
{
if (!strcasecmp (cp, "on") || !strcasecmp (cp, "true"))
{
settings.balanced_stat_loss = TRUE;
}
else if (!strcasecmp (cp, "off") || !strcasecmp (cp, "false"))
{
settings.balanced_stat_loss = FALSE;
}
else
{
LOG (llevError, "load_settings: Unknown value for " "balanced_stat_loss: %s\n", cp);
}
}
else if (!strcasecmp (buf, "simple_exp"))
{
if (!strcasecmp (cp, "on") || !strcasecmp (cp, "true"))
{
settings.simple_exp = TRUE;
}
else if (!strcasecmp (cp, "off") || !strcasecmp (cp, "false"))
{
settings.simple_exp = FALSE;
}
else
{
LOG (llevError, "load_settings: Unknown value for simple_exp: %s\n", cp);
}
}
else if (!strcasecmp (buf, "item_power_factor"))
{
float tmp = atof (cp);
if (tmp < 0)
LOG (llevError, "load_settings: item_power_factor must be a positive number (%f < 0)\n", tmp);
else
settings.item_power_factor = tmp;
}
else if (!strcasecmp (buf, "pk_luck_penalty"))
{
sint16 val = atoi (cp);
if (val < -100 || val > 100)
LOG (llevError, "load_settings: pk_luck_penalty must be between -100 and 100" ", %d is invalid\n", val);
else
settings.pk_luck_penalty = val;
}
else if (!strcasecmp (buf, "set_friendly_fire"))
{
int val = atoi (cp);
if (val < 0 || val > 100)
LOG (llevError, "load_settings: set_friendly_fire must be between 0 an 100" ", %d is invalid\n", val);
else
settings.set_friendly_fire = val;
}
else if (!strcasecmp (buf, "armor_max_enchant"))
{
int max_e = atoi (cp);
if (max_e <= 0)
LOG (llevError, "load_settings: armor_max_enchant is %d\n", max_e);
else
settings.armor_max_enchant = max_e;
}
else if (!strcasecmp (buf, "armor_weight_reduction"))
{
int wr = atoi (cp);
if (wr < 0)
LOG (llevError, "load_settings: armor_weight_reduction is %d\n", wr);
else
settings.armor_weight_reduction = wr;
}
else if (!strcasecmp (buf, "armor_weight_linear"))
{
if (!strcasecmp (cp, "on") || !strcasecmp (cp, "true"))
{
settings.armor_weight_linear = TRUE;
}
else if (!strcasecmp (cp, "off") || !strcasecmp (cp, "false"))
{
settings.armor_weight_linear = FALSE;
}
else
{
LOG (llevError, "load_settings: unknown value for armor_weight_linear: %s\n", cp);
}
}
else if (!strcasecmp (buf, "armor_speed_improvement"))
{
int wr = atoi (cp);
if (wr < 0)
LOG (llevError, "load_settings: armor_speed_improvement is %d\n", wr);
else
settings.armor_speed_improvement = wr;
}
else if (!strcasecmp (buf, "armor_speed_linear"))
{
if (!strcasecmp (cp, "on") || !strcasecmp (cp, "true"))
{
settings.armor_speed_linear = TRUE;
}
else if (!strcasecmp (cp, "off") || !strcasecmp (cp, "false"))
{
settings.armor_speed_linear = FALSE;
}
else
{
LOG (llevError, "load_settings: unknown value for armor_speed_linear: %s\n", cp);
}
}
else
LOG (llevError, "Unknown value in settings file: %s\n", buf);
}
close_and_delete (fp, comp);
}
/*
* init() is called only once, when starting the program.
*/
void
init (int argc, char **argv)
{
init_done = 0; /* Must be done before init_signal() */
init_environ ();
cfperl_init ();
init_done = 1;
}
void
usage (void)
{
fprintf (stderr, "Usage: deliantra-server [-h] [-]...\n");
}
void
help (void)
{
/* The information in usage is redundant with what is given below, so why call it? */
/* usage();*/
printf ("Flags:\n");
printf (" -csport Specifies the port to use for the new client/server code.\n");
printf (" -d Turns on some debugging.\n");
printf (" +d Turns off debugging (useful if server compiled with debugging\n");
printf (" as default).\n");
printf (" -detach The server will go in the background, closing all\n");
printf (" connections to the tty.\n");
printf (" -h Display this information.\n");
printf (" -log Specifies which file to send output to.\n");
printf (" Only has meaning if -detach is specified.\n");
printf (" -mon Turns on monster debugging.\n");
printf (" -o Prints out info on what was defined at compile time.\n");
printf (" -s Display the high-score list.\n");
printf (" -score Displays all high scores with matching name/class.\n");
printf (" -v Print version and contributors.\n");
printf (" -data Sets the lib dir (archetypes, treasures, etc.)\n");
printf (" -local Read/write local data (hiscore, unique items, etc.)\n");
printf (" -maps Sets the directory for maps.\n");
printf (" -arch Sets the archetype file to use.\n");
printf (" -regions Sets the regions file to use.\n");
printf (" -playerdir Sets the directory for the player files.\n");
printf (" -templatedir Sets the directory for template generate maps.\n");
printf (" -treasures Sets the treasures file to use.\n");
printf (" -uniquedir Sets the unique items/maps directory.\n");
printf (" -tmpdir Sets the directory for temporary files (mostly maps.)\n");
printf (" -m Lists out suggested experience for all monsters.\n");
printf (" -m2 Dumps out abilities.\n");
printf (" -m3 Dumps out artifact information.\n");
printf (" -m4 Dumps out spell information.\n");
printf (" -m5 Dumps out skill information.\n");
printf (" -m6 Dumps out race information.\n");
printf (" -m7 Dumps out alchemy information.\n");
printf (" -m8 Dumps out gods information.\n");
printf (" -m9 Dumps out more alchemy information (formula checking).\n");
printf (" -mt Dumps out list of treasures for a monster.\n");
exit (0);
}
void
init_beforeplay (void)
{
init_artifacts (); /* If not called before, reads all artifacts from file */
init_races (); /* overwrite race designations using entries in lib/races file */
init_gods (); /* init linked list of gods from archs */
init_readable (); /* inits useful arrays for readable texts */
init_formulae (); /* If not called before, reads formulae from file */
}
/* Signal handlers: */
static void
rec_sigabrt (int i)
{
signal (SIGABRT, SIG_DFL);
LOG (llevError, "SIGABRT received.\n");
cleanup ("SIGABRT received", 1);
}
static void
rec_sigsegv (int i)
{
signal (SIGSEGV, SIG_DFL);
LOG (llevError, "SIGSEGV received.\n");
cleanup ("SIGSEGV received", 1);
}
static void
rec_sigquit (int i)
{
signal (SIGQUIT, SIG_IGN);
LOG (llevInfo, "SIGQUIT received\n");
cleanup ("SIGQUIT received", 1);
}
static void
rec_sigbus (int i)
{
signal (SIGBUS, SIG_DFL);
LOG (llevError, "SIGBUS received\n");
cleanup ("SIGBUS received", 1);
}
void
reset_signals ()
{
signal (SIGABRT, SIG_DFL);
signal (SIGQUIT, SIG_DFL);
signal (SIGSEGV, SIG_DFL);
signal (SIGBUS , SIG_DFL);
signal (SIGINT , SIG_DFL);
signal (SIGTERM, SIG_DFL);
}
void
init_signals (void)
{
// large stack, but it's important data we want to save, and it is not usually
// being physically allocated anyways
const size_t stacksize = 8 * 1024 * 1024 + SIGSTKSZ;
stack_t ss;
ss.ss_sp = malloc (stacksize);
ss.ss_flags = 0;
ss.ss_size = stacksize;
sigaltstack (&ss, 0);
struct sigaction sa;
sigfillset (&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = SIG_IGN; sigaction (SIGPIPE, &sa, 0);
sa.sa_handler = rec_sigabrt; sigaction (SIGABRT, &sa, 0);
sa.sa_handler = rec_sigquit; sigaction (SIGQUIT, &sa, 0);
sa.sa_handler = rec_sigbus; sigaction (SIGBUS, &sa, 0);
sa.sa_flags |= SA_ONSTACK;
sa.sa_handler = rec_sigsegv; sigaction (SIGSEGV, &sa, 0);
}
/* init_races() - reads the races file in the lib/ directory, then
* overwrites old 'race' entries. This routine allow us to quickly
* re-configure the 'alignment' of monsters, objects. Useful for
* putting together lists of creatures, etc that belong to gods.
*/
void
init_races (void)
{
FILE *file;
char race[MAX_BUF], fname[MAX_BUF], buf[MAX_BUF], *cp, variable[MAX_BUF];
archetype *mon = NULL;
static int init_done = 0;
if (init_done)
return;
init_done = 1;
first_race = 0;
sprintf (fname, "%s/races", settings.datadir);
LOG (llevDebug, "Reading races from %s...\n", fname);
if (!(file = fopen (fname, "r")))
{
LOG (llevError, "Cannot open races file %s: %s\n", fname, strerror (errno));
return;
}
while (fgets (buf, MAX_BUF, file) != NULL)
{
int set_race = 1, set_list = 1;
if (*buf == '#')
continue;
if ((cp = strchr (buf, '\n')) != NULL)
*cp = '\0';
cp = buf;
while (*cp == ' ' || *cp == '!' || *cp == '@')
{
if (*cp == '!') set_race = 0;
if (*cp == '@') set_list = 0;
cp++;
}
if (sscanf (cp, "RACE %s", variable))
/* set new race value */
strcpy (race, variable);
else
{
char *cp1;
/* Take out beginning spaces */
for (cp1 = cp; *cp1 == ' '; cp1++)
;
/* Remove newline and trailing spaces */
for (cp1 = cp + strlen (cp) - 1; *cp1 == '\n' || *cp1 == ' '; cp1--)
{
*cp1 = '\0';
if (cp == cp1)
break;
}
if (cp[strlen (cp) - 1] == '\n')
cp[strlen (cp) - 1] = '\0';
/* set creature race to race value */
if ((mon = archetype::find (cp)) == NULL)
LOG (llevError, "Creature %s in race file lacks archetype\n", cp);
else
{
if (set_race && (!mon->race || strcmp (&mon->race, race)))
{
if (mon->race)
LOG (llevDebug, "Resetting race to %s from %s for archetype %s\n", race, &mon->race, &mon->archname);
mon->race = race;
}
/* if the arch is a monster, add it to the race list */
if (set_list && QUERY_FLAG (mon, FLAG_MONSTER))
add_to_racelist (race, mon);
}
}
}
fclose (file);
LOG (llevDebug, "done.\n");
}
void
dump_races (void)
{
racelink *list;
objectlink *tmp;
for (list = first_race; list; list = list->next)
{
fprintf (stderr, "\nRACE %s:\t", &list->name);
for (tmp = list->member; tmp; tmp = tmp->next)
fprintf (stderr, "%s(%d), ", &tmp->ob->arch->archname, tmp->ob->level);
}
fprintf (stderr, "\n");
}
void
add_to_racelist (const char *race_name, object *op)
{
racelink *race;
if (!op || !race_name)
return;
race = find_racelink (race_name);
if (!race)
{ /* add in a new race list */
race = get_racelist ();
race->next = first_race;
first_race = race;
race->name = race_name;
}
if (race->member->ob)
{
objectlink *tmp = get_objectlink ();
tmp->next = race->member;
race->member = tmp;
}
race->nrof++;
race->member->ob = op;
}
racelink *
get_racelist ()
{
racelink *list = new racelink;
list->name = 0;
list->nrof = 0;
list->next = 0;
list->member = get_objectlink ();
return list;
}
racelink *
find_racelink (const char *name)
{
if (name)
for (racelink *link = first_race; link; link = link->next)
if (!link->name || !strcmp (name, link->name))
return link;
return 0;
}