/*
* 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
*/
/* Basic stuff for use with the alchemy code. Clearly some of this stuff
* could go into server/alchemy, but I left it here just in case it proves
* more generally useful.
*
* Nov 1995 - file created by b.t. thomas@astro.psu.edu
*/
/* Our definition of 'formula' is any product of an alchemical process.
* Ingredients are just comma delimited list of archetype (or object)
* names.
*/
/* Example 'formula' entry in libdir/formulae:
* object transparency
* chance 10
* ingred dust of beholdereye,gem
* arch potion_generic
*/
#include
#include
#include
static void build_stringlist (const char *str, char ***result_list, size_t * result_size);
static recipelist *formulalist;
static recipelist *
init_recipelist (void)
{
recipelist *tl = new recipelist;
tl->total_chance = 0;
tl->number = 0;
tl->items = 0;
tl->next = 0;
return tl;
}
static recipe *
get_empty_formula (void)
{
recipe *t = new recipe;
t->chance = 0;
t->index = 0;
t->transmute = 0;
t->yield = 0;
t->diff = 0;
t->exp = 0;
t->keycode = 0;
t->title = NULL;
t->arch_names = 0;
t->arch_name = NULL;
t->skill = NULL;
t->cauldron = NULL;
t->ingred = NULL;
t->next = NULL;
return t;
}
/* get_formulalist() - returns pointer to the formula list */
recipelist *
get_formulalist (int i)
{
recipelist *fl = formulalist;
int number = i;
while (fl && number > 1)
{
if (!(fl = fl->next))
break;
number--;
}
return fl;
}
/* check_recipe() - makes sure we actually have the requested artifact
* and archetype. */
static int
check_recipe (const recipe *rp)
{
size_t i;
int result = 1;
for (i = 0; i < rp->arch_names; i++)
{
if (archetype::find (rp->arch_name[i]))
{
artifact *art = locate_recipe_artifact (rp, i);
if (!art && rp->title != shstr_NONE)
{
LOG (llevError, "WARNING: Formula %s of %s has no artifact.\n", rp->arch_name[i], &rp->title);
result = 0;
}
}
else
{
LOG (llevError, "WARNING: Can't find archetype %s for formula %s\n", rp->arch_name[i], &rp->title);
result = 0;
}
}
return result;
}
/* check_formulae()- since we are doing a squential search on the
* formulae lists now, we have to be carefull that we dont have 2
* formula with the exact same index value. Under the new nbatches
* code, it is possible to have multiples of ingredients in a cauldron
* which could result in an index formula mismatch. We *don't* check for
* that possibility here. -b.t.
*/
static void
check_formulae (void)
{
recipelist *fl;
recipe *check, *formula;
int numb = 1;
LOG (llevDebug, "Checking formulae lists...\n");
for (fl = formulalist; fl; fl = fl->next)
{
for (formula = fl->items; formula; formula = formula->next)
for (check = formula->next; check; check = check->next)
if (check->index == formula->index)
{
LOG (llevError, " ERROR: On %d ingred list: ", numb);
LOG (llevError, "Formulae [%s] of %s and [%s] of %s have matching index id (%d)\n",
formula->arch_name[0], &formula->title, check->arch_name[0], &check->title, formula->index);
}
numb++;
}
LOG (llevDebug, "done.\n");
}
/*
* init_formulae() - Builds up the lists of formula from the file in
* the libdir. -b.t.
*/
void
init_formulae (void)
{
static int has_been_done = 0;
FILE *fp;
char filename[MAX_BUF], buf[MAX_BUF], *cp, *next;
recipe *formula = NULL;
recipelist *fl = init_recipelist ();
linked_char *tmp;
int value, comp;
if (!formulalist)
formulalist = fl;
if (has_been_done)
return;
else
has_been_done = 1;
sprintf (filename, "%s/formulae", settings.datadir);
LOG (llevDebug, "Reading alchemical formulae from %s...\n", filename);
if ((fp = open_and_uncompress (filename, 0, &comp)) == NULL)
{
LOG (llevError, "Can't open %s.\n", filename);
return;
}
while (fgets (buf, MAX_BUF, fp) != NULL)
{
if (*buf == '#')
continue;
if ((cp = strchr (buf, '\n')) != NULL)
*cp = '\0';
cp = buf;
while (*cp == ' ') /* Skip blanks */
cp++;
if (!strncmp (cp, "object", 6))
{
formula = get_empty_formula ();
formula->title = strchr (cp, ' ') + 1;
}
else if (!strncmp (cp, "keycode", 7))
formula->keycode = strchr (cp, ' ') + 1;
else if (sscanf (cp, "trans %d", &value))
formula->transmute = (uint16) value;
else if (sscanf (cp, "yield %d", &value))
formula->yield = (uint16) value;
else if (sscanf (cp, "chance %d", &value))
formula->chance = (uint16) value;
else if (sscanf (cp, "exp %d", &value))
formula->exp = (uint16) value;
else if (sscanf (cp, "diff %d", &value))
formula->diff = (uint16) value;
else if (!strncmp (cp, "ingred", 6))
{
int numb_ingred = 1;
cp = strchr (cp, ' ') + 1;
do
{
if ((next = strchr (cp, ',')) != NULL)
{
*(next++) = '\0';
numb_ingred++;
}
tmp = new linked_char;
tmp->name = cp;
tmp->next = formula->ingred;
formula->ingred = tmp;
/* each ingredient's ASCII value is coadded. Later on this
* value will be used allow us to search the formula lists
* quickly for the right recipe.
*/
formula->index += strtoint (cp);
}
while ((cp = next) != NULL);
/* now find the correct (# of ingred ordered) formulalist */
fl = formulalist;
while (numb_ingred != 1)
{
if (!fl->next)
fl->next = init_recipelist ();
fl = fl->next;
numb_ingred--;
}
fl->total_chance += formula->chance;
fl->number++;
formula->next = fl->items;
fl->items = formula;
}
else if (!strncmp (cp, "arch", 4))
{
build_stringlist (strchr (cp, ' ') + 1, &formula->arch_name, &formula->arch_names);
check_recipe (formula);
}
else if (!strncmp (cp, "skill", 5))
formula->skill = strchr (cp, ' ') + 1;
else if (!strncmp (cp, "cauldron", 8))
formula->cauldron = strchr (cp, ' ') + 1;
else
LOG (llevError, "Unknown input in file %s: %s\n", filename, buf);
}
LOG (llevDebug, "done.\n");
close_and_delete (fp, comp);
/* Lastly, lets check for problems in formula we got */
check_formulae ();
}
/* Find a treasure with a matching name. The 'depth' parameter is
* only there to prevent infinite loops in treasure lists (a list
* referencing another list pointing back to the first one). */
archetype *
find_treasure_by_name (const treasure *t, const char *name, int depth)
{
if (depth > 10)
return 0;
while (t)
{
if (t->name)
{
if (treasurelist *tl = treasurelist::find (t->name))
if (tl->items)
if (archetype *at = find_treasure_by_name (tl->items, name, depth + 1))
return at;
}
else
{
if (t->item && !strcasecmp (t->item->object::name, name))
return t->item;
}
if (t->next_yes)
if (archetype *at = find_treasure_by_name (t->next_yes, name, depth))
return at;
if (t->next_no)
if (archetype *at = find_treasure_by_name (t->next_no, name, depth))
return at;
t = t->next;
}
return 0;
}
/* If several archetypes have the same name, the value of the first
* one with that name will be returned. This happens for the
* mushrooms (mushroom_1, mushroom_2 and mushroom_3). For the
* monsters' body parts, there may be several monsters with the same
* name. This is not a problem if these monsters have the same level
* (e.g. sage & c_sage) or if only one of the monsters generates the
* body parts that we are looking for (e.g. big_dragon and
* big_dragon_worthless). */
long
find_ingred_cost (const char *name)
{
archetype *at2;
artifactlist *al;
artifact *art;
long mult;
char *cp;
char part1[100];
char part2[100];
/* same as atoi(), but skip number */
mult = 0;
while (isdigit (*name))
{
mult = 10 * mult + (*name - '0');
name++;
}
if (mult > 0)
name++;
else
mult = 1;
/* first, try to match the name of an archetype */
for_all_archetypes (at)
{
if (at->title != NULL)
{
/* inefficient, but who cares? */
sprintf (part1, "%s %s", &at->object::name, &at->title);
if (!strcasecmp (part1, name))
return mult * at->value;
}
if (!strcasecmp (at->object::name, name))
return mult * at->value;
}
/* second, try to match an artifact ("arch of something") */
cp = strstr (name, " of ");
if (cp != NULL)
{
strcpy (part1, name);
part1[cp - name] = '\0';
strcpy (part2, cp + 4);
/* find the first archetype matching the first part of the name */
for_all_archetypes (at)
if (at->object::name.eq_nc (part1) && !at->title)
{
/* find the first artifact derived from that archetype (same type) */
for (al = first_artifactlist; al; al = al->next)
if (al->type == at->type)
{
for (art = al->items; art; art = art->next)
if (!strcasecmp (art->item->name, part2))
return mult * at->value * art->item->value;
}
}
}
/* third, try to match a body part ("arch's something") */
cp = strstr (name, "'s ");
if (cp)
{
strcpy (part1, name);
part1[cp - name] = '\0';
strcpy (part2, cp + 3);
/* examine all archetypes matching the first part of the name */
for_all_archetypes (at)
if (at->object::name.eq_nc (part1) && !at->title)
{
if (at->randomitems)
{
at2 = find_treasure_by_name (at->randomitems->items, part2, 0);
if (at2)
return mult * at2->value * isqrt (at->level * 2);
}
}
}
/* failed to find any matching items -- formula should be checked */
return -1;
}
const char *
ingred_name (const char *name)
{
const char *cp = name;
if (atoi (cp))
cp = strchr (cp, ' ') + 1;
return cp;
}
int
numb_ingred (const char *buf)
{
int numb;
if ((numb = atoi (buf)))
return numb;
else
return 1;
}
/* strtoint() - we use this to convert buf into an integer
* equal to the coadded sum of the (lowercase) character
* ASCII values in buf (times prepended integers).
* Some kind of hashing.
*/
int
strtoint (const char *buf)
{
const char *cp = ingred_name (buf);
int val = 0, len = strlen (cp), mult = numb_ingred (buf);
while (len)
{
val += tolower (*cp);
cp++;
len--;
}
return val * mult;
}
artifact *
locate_recipe_artifact (const recipe *rp, size_t idx)
{
archetype *at = archetype::find (rp->arch_name [idx]);
if (at)
if (artifactlist *al = find_artifactlist (at->type))
for (artifact *art = al->items; art; art = art->next)
if (art->item->name == rp->title)
return art;
return 0;
}
recipelist *
get_random_recipelist (void)
{
recipelist *fl = NULL;
int number = 0, roll = 0;
/* first, determine # of recipelist we have */
for (fl = get_formulalist (1); fl; fl = fl->next)
number++;
/* now, randomly choose one */
if (number > 0)
roll = rndm (number);
fl = get_formulalist (1);
while (roll && fl)
{
if (fl->next)
fl = fl->next;
else
break;
roll--;
}
if (!fl) /* failed! */
LOG (llevError, "get_random_recipelist(): no recipelists found!\n");
else if (fl->total_chance == 0)
fl = get_random_recipelist ();
return fl;
}
recipe *
get_random_recipe (recipelist * rpl)
{
recipelist *fl = rpl;
recipe *rp = NULL;
int r = 0;
/* looks like we have to choose a random one */
if (fl == NULL)
if ((fl = get_random_recipelist ()) == NULL)
return rp;
if (fl->total_chance > 0)
{
r = rndm (fl->total_chance);
for (rp = fl->items; rp; rp = rp->next)
{
r -= rp->chance;
if (r < 0)
break;
}
}
return rp;
}
void
free_all_recipes (void)
{
recipelist *fl = formulalist, *flnext;
recipe *formula = NULL, *next;
linked_char *lchar, *charnext;
LOG (llevDebug, "Freeing all the recipes\n");
for (fl = formulalist; fl != NULL; fl = flnext)
{
flnext = fl->next;
for (formula = fl->items; formula != NULL; formula = next)
{
next = formula->next;
free (formula->arch_name[0]);
free (formula->arch_name);
for (lchar = formula->ingred; lchar; lchar = charnext)
{
charnext = lchar->next;
delete lchar;
}
delete formula;
}
delete fl;
}
}
/**
* Split a comma separated string list into words.
*
* @param str the string to split
*
* @param result_list pointer to return value for the newly created list; the
* caller is responsible for freeing both *result_list and **result_list.
*
* @param result_size pointer to return value for the size of the newly
* created list
*/
static void
build_stringlist (const char *str, char ***result_list, size_t * result_size)
{
char *dup;
char *p;
size_t size;
size_t i;
dup = strdup (str);
if (dup == NULL)
fatal (OUT_OF_MEMORY);
size = 0;
for (p = strtok (dup, ","); p != NULL; p = strtok (NULL, ","))
size++;
*result_list = (char **) malloc (size * sizeof (*result_list));
*result_size = size;
for (i = 0; i < size; i++)
{
(*result_list)[i] = dup;
dup = dup + strlen (dup) + 1;
}
}