/*
* This file is part of Deliantra, the Roguelike Realtime MMORPG.
*
* Copyright (©) 2005,2006,2007,2008,2009,2010 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
* Copyright (©) 2002 Mark Wedel & Crossfire Development Team
* Copyright (©) 1992 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 ()
{
recipelist *tl = new recipelist;
tl->total_chance = 0;
tl->number = 0;
tl->items = 0;
tl->next = 0;
return tl;
}
static recipe *
get_empty_formula ()
{
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 = 0;
t->arch_names = 0;
t->arch_name = 0;
t->skill = 0;
t->cauldron = 0;
t->ingred = 0;
t->next = 0;
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 ()
{
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 ()
{
static int has_been_done = 0;
FILE *fp;
char filename[MAX_BUF], buf[MAX_BUF], *cp, *next;
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);
object_thawer thawer (filename);
if (!thawer)
{
LOG (llevError, "Can't open %s.\n", filename);
return;
}
while (thawer.kw)
{
if (thawer.kw != KW_object)
if (!thawer.parse_error ("formulae file"))
break;
recipe *formula = get_empty_formula ();
thawer.get (formula->title);
for (;;)
{
thawer.next ();
switch (thawer.kw)
{
case KW_keycode: thawer.get (formula->keycode ); break;
case KW_trans: thawer.get (formula->transmute); break;
case KW_yield: thawer.get (formula->yield ); break;
case KW_chance: thawer.get (formula->chance ); break;
case KW_exp: thawer.get (formula->exp ); break;
case KW_diff: thawer.get (formula->diff ); break;
case KW_skill: thawer.get (formula->skill ); break;
case KW_cauldron: thawer.get (formula->cauldron ); break;
case KW_arch:
{
build_stringlist (thawer.value_nn, &formula->arch_name, &formula->arch_names);
check_recipe (formula);
}
break;
case KW_ingred:
if (thawer.value)
{
int numb_ingred = 1;
char *cp = thawer.value;
do
{
if ((next = strchr (cp, ',')))
{
*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));
/* 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;
}
break;
default:
delete formula;
case KW_EOF:
case KW_object:
goto next_object;
}
}
next_object: ;
}
LOG (llevDebug, "done.\n");
/* 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). */
static 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;
}
static const char *
ingred_name (const char *name)
{
const char *cp = name;
if (atoi (cp))
cp = strchr (cp, ' ') + 1;
return cp;
}
static 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;
}
static recipelist *
get_random_recipelist ()
{
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;
}
/**
* 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;
}
}