788 lines
25 KiB
C
788 lines
25 KiB
C
/*
|
|
CrossFire, A Multiplayer game for X-windows
|
|
|
|
Copyright (C) 2001-2006 Crossfire Development Team
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 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 GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
The authors can be reached via e-mail at crossfire-devel@real-time.com
|
|
*/
|
|
|
|
/**
|
|
* @file recipe.c
|
|
* 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
|
|
*
|
|
* An ingredient is a name, which can contain an initial number for how many are needed.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <global.h>
|
|
#include <object.h>
|
|
#include <ctype.h>
|
|
|
|
static void build_stringlist(const char *str, char ***result_list, size_t *result_size);
|
|
static void check_formulae(void);
|
|
|
|
/** Pointer to first recipelist. */
|
|
static recipelist *formulalist;
|
|
|
|
/**
|
|
* Allocates a new recipelist.
|
|
*
|
|
* Will call fatal() if memory allocation error.
|
|
*
|
|
* @return
|
|
* new structure initialized. Never NULL.
|
|
*/
|
|
static recipelist *init_recipelist(void) {
|
|
recipelist *tl = (recipelist *)malloc(sizeof(recipelist));
|
|
if (tl == NULL)
|
|
fatal(OUT_OF_MEMORY);
|
|
tl->total_chance = 0;
|
|
tl->number = 0;
|
|
tl->items = NULL;
|
|
tl->next = NULL;
|
|
return tl;
|
|
}
|
|
|
|
/**
|
|
* Allocates a new recipe.
|
|
*
|
|
* Will call fatal() if memory allocation error.
|
|
*
|
|
* @return
|
|
* new structure initialized. Never NULL.
|
|
*/
|
|
static recipe *get_empty_formula(void) {
|
|
recipe *t = (recipe *)malloc(sizeof(recipe));
|
|
if (t == NULL)
|
|
fatal(OUT_OF_MEMORY);
|
|
t->chance = 0;
|
|
t->index = 0;
|
|
t->transmute = 0;
|
|
t->yield = 0;
|
|
t->diff = 0;
|
|
t->exp = 0;
|
|
t->keycode = NULL;
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Gets a formula list by ingredients count.
|
|
*
|
|
* @param i
|
|
* number of ingredients.
|
|
*
|
|
* @return
|
|
* pointer to the formula list, or NULL if it doesn't exist.
|
|
*/
|
|
recipelist *get_formulalist(int i) {
|
|
recipelist *fl = formulalist;
|
|
int number = i;
|
|
|
|
while (fl && number > 1) {
|
|
if (!(fl = fl->next))
|
|
break;
|
|
number--;
|
|
}
|
|
return fl;
|
|
}
|
|
|
|
/**
|
|
* Makes sure we actually have the requested artifact
|
|
* and archetype.
|
|
*
|
|
* @param rp
|
|
* recipe we want to check.
|
|
*
|
|
* @return
|
|
* 1 if recipe is ok, 0 if missing something (and LOG() to error).
|
|
*/
|
|
static int check_recipe(const recipe *rp) {
|
|
size_t i;
|
|
int result;
|
|
|
|
result = 1;
|
|
for (i = 0; i < rp->arch_names; i++) {
|
|
if (find_archetype(rp->arch_name[i]) != NULL) {
|
|
artifact *art = locate_recipe_artifact(rp, i);
|
|
|
|
if (!art && strcmp(rp->title, "NONE") != 0) {
|
|
LOG(llevError, "\nWARNING: Formula %s of %s has no artifact.\n", rp->arch_name[i], rp->title);
|
|
result = 0;
|
|
}
|
|
} else {
|
|
LOG(llevError, "\nWARNING: Can't find archetype %s for formula %s\n", rp->arch_name[i], rp->title);
|
|
result = 0;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
snprintf(filename, sizeof(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 = add_string(strchr(cp, ' ')+1);
|
|
} else if (!strncmp(cp, "keycode", 7)) {
|
|
formula->keycode = add_string(strchr(cp, ' ')+1);
|
|
} else if (sscanf(cp, "trans %d", &value)) {
|
|
formula->transmute = value;
|
|
} else if (sscanf(cp, "yield %d", &value)) {
|
|
formula->yield = value;
|
|
} else if (sscanf(cp, "chance %d", &value)) {
|
|
formula->chance = value;
|
|
} else if (sscanf(cp, "exp %d", &value)) {
|
|
formula->exp = value;
|
|
} else if (sscanf(cp, "diff %d", &value)) {
|
|
formula->diff = 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 = (linked_char *)malloc(sizeof(linked_char));
|
|
tmp->name = add_string(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 = add_string(strchr(cp, ' ')+1);
|
|
} else if (!strncmp(cp, "cauldron", 8)) {
|
|
formula->cauldron = add_string(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();
|
|
}
|
|
|
|
/**
|
|
* Check if formula don't have the same index.
|
|
*
|
|
* 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.
|
|
*
|
|
* LOG() to error level.
|
|
*/
|
|
static void check_formulae(void) {
|
|
recipelist *fl;
|
|
recipe *check, *formula;
|
|
int numb = 1;
|
|
|
|
LOG(llevDebug, "Checking formulae lists...\n");
|
|
|
|
for (fl = formulalist; fl != NULL; fl = fl->next) {
|
|
for (formula = fl->items; formula != NULL; formula = formula->next)
|
|
for (check = formula->next; check != NULL; check = check->next)
|
|
if (check->index == formula->index && strcmp(check->cauldron, formula->cauldron) == 0) {
|
|
/* if the recipes don't have the same facility, then no issue anyway. */
|
|
LOG(llevError, " ERROR: On %d ingred list:\n", 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 checking.\n");
|
|
}
|
|
|
|
/**
|
|
* Dumps alchemy recipes to output.
|
|
* Borrowed (again) from the artifacts code for this.
|
|
*
|
|
* @todo
|
|
* use LOG() instead of fprintf?
|
|
*/
|
|
void dump_alchemy(void) {
|
|
recipelist *fl = formulalist;
|
|
recipe *formula = NULL;
|
|
linked_char *next;
|
|
int num_ingred = 1;
|
|
|
|
fprintf(logfile, "\n");
|
|
while (fl) {
|
|
fprintf(logfile, "\n Formulae with %d ingredient%s %d Formulae with total_chance=%d\n", num_ingred, num_ingred > 1 ? "s." : ".", fl->number, fl->total_chance);
|
|
for (formula = fl->items; formula != NULL; formula = formula->next) {
|
|
artifact *art = NULL;
|
|
char buf[MAX_BUF];
|
|
size_t i;
|
|
|
|
for (i = 0; i < formula->arch_names; i++) {
|
|
const char *string = formula->arch_name[i];
|
|
|
|
if (find_archetype(string) != NULL) {
|
|
art = locate_recipe_artifact(formula, i);
|
|
if (!art && strcmp(formula->title, "NONE"))
|
|
LOG(llevError, "Formula %s has no artifact\n", formula->title);
|
|
else {
|
|
if (strcmp(formula->title, "NONE"))
|
|
snprintf(buf, sizeof(buf), "%s of %s", string, formula->title);
|
|
else
|
|
snprintf(buf, sizeof(buf), "%s", string);
|
|
fprintf(logfile, "%-30s(%d) bookchance %3d ", buf, formula->index, formula->chance);
|
|
fprintf(logfile, "skill %s", formula->skill);
|
|
fprintf(logfile, "\n");
|
|
if (formula->ingred != NULL) {
|
|
int nval = 0, tval = 0;
|
|
fprintf(logfile, "\tIngred: ");
|
|
for (next = formula->ingred; next != NULL; next = next->next) {
|
|
if (nval != 0)
|
|
fprintf(logfile, ",");
|
|
fprintf(logfile, "%s(%d)", next->name, (nval = strtoint(next->name)));
|
|
tval += nval;
|
|
}
|
|
fprintf(logfile, "\n");
|
|
if (tval != formula->index)
|
|
fprintf(logfile, "WARNING:ingredient list and formula values not equal.\n");
|
|
}
|
|
if (formula->skill != NULL)
|
|
fprintf(logfile, "\tSkill Required: %s", formula->skill);
|
|
if (formula->cauldron != NULL)
|
|
fprintf(logfile, "\tCauldron: %s\n", formula->cauldron);
|
|
fprintf(logfile, "\tDifficulty: %d\t Exp: %d\n", formula->diff, formula->exp);
|
|
}
|
|
} else
|
|
LOG(llevError, "Can't find archetype:%s for formula %s\n", string, formula->title);
|
|
}
|
|
}
|
|
fprintf(logfile, "\n");
|
|
fl = fl->next;
|
|
num_ingred++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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).
|
|
*
|
|
* @param t
|
|
* item of treasure list to search from
|
|
* @param name
|
|
* name we're trying to find. Doesn't need to be a shared string.
|
|
* @param depth
|
|
* current depth. Code will exit if greater than 10.
|
|
* @return
|
|
* archetype with name, or NULL if nothing found.
|
|
*/
|
|
archetype *find_treasure_by_name(const treasure *t, const char *name, int depth) {
|
|
treasurelist *tl;
|
|
archetype *at;
|
|
|
|
if (depth > 10)
|
|
return NULL;
|
|
|
|
while (t != NULL) {
|
|
if (t->name != NULL) {
|
|
tl = find_treasurelist(t->name);
|
|
at = find_treasure_by_name(tl->items, name, depth+1);
|
|
if (at != NULL)
|
|
return at;
|
|
} else {
|
|
if (!strcasecmp(t->item->clone.name, name))
|
|
return t->item;
|
|
}
|
|
if (t->next_yes != NULL) {
|
|
at = find_treasure_by_name(t->next_yes, name, depth);
|
|
if (at != NULL)
|
|
return at;
|
|
}
|
|
if (t->next_no != NULL) {
|
|
at = find_treasure_by_name(t->next_no, name, depth);
|
|
if (at != NULL)
|
|
return at;
|
|
}
|
|
t = t->next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Try to find an ingredient with specified name.
|
|
*
|
|
* 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).
|
|
*
|
|
* Will also search in artifacts.
|
|
*
|
|
* @param name
|
|
* ingredient we're searching for. Can start with a number.
|
|
* @return
|
|
* cost of ingredient, -1 if wasn't found.
|
|
*/
|
|
static long find_ingred_cost(const char *name) {
|
|
archetype *at;
|
|
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 (at = first_archetype; at != NULL; at = at->next) {
|
|
if (at->clone.title != NULL) {
|
|
/* inefficient, but who cares? */
|
|
snprintf(part1, sizeof(part1), "%s %s", at->clone.name, at->clone.title);
|
|
if (!strcasecmp(part1, name))
|
|
return mult*at->clone.value;
|
|
}
|
|
if (!strcasecmp(at->clone.name, name))
|
|
return mult*at->clone.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 (at = first_archetype; at != NULL; at = at->next)
|
|
if (!strcasecmp(at->clone.name, part1) && at->clone.title == NULL)
|
|
break;
|
|
if (at != NULL) {
|
|
/* find the first artifact derived from that archetype (same type) */
|
|
for (al = first_artifactlist; al != NULL; al = al->next)
|
|
if (al->type == at->clone.type) {
|
|
for (art = al->items; art != NULL; art = art->next)
|
|
if (!strcasecmp(art->item->name, part2))
|
|
return mult*at->clone.value*art->item->value;
|
|
}
|
|
}
|
|
}
|
|
/* third, try to match a body part ("arch's something") */
|
|
cp = strstr(name, "'s ");
|
|
if (cp != NULL) {
|
|
strcpy(part1, name);
|
|
part1[cp-name] = '\0';
|
|
strcpy(part2, cp+3);
|
|
/* examine all archetypes matching the first part of the name */
|
|
for (at = first_archetype; at != NULL; at = at->next)
|
|
if (!strcasecmp(at->clone.name, part1) && at->clone.title == NULL) {
|
|
if (at->clone.randomitems != NULL) {
|
|
at2 = find_treasure_by_name(at->clone.randomitems->items, part2, 0);
|
|
if (at2 != NULL)
|
|
return mult*at2->clone.value*isqrt(at->clone.level*2);
|
|
}
|
|
}
|
|
}
|
|
/* failed to find any matching items -- formula should be checked */
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Dumps to output all costs of recipes.
|
|
*
|
|
* Code copied from dump_alchemy() and modified by Raphael Quinet
|
|
*
|
|
* @todo
|
|
* should use LOG()
|
|
*/
|
|
void dump_alchemy_costs(void) {
|
|
recipelist *fl = formulalist;
|
|
recipe *formula = NULL;
|
|
linked_char *next;
|
|
int num_ingred = 1;
|
|
int num_errors = 0;
|
|
long cost;
|
|
long tcost;
|
|
|
|
fprintf(logfile, "\n");
|
|
while (fl) {
|
|
fprintf(logfile, "\n Formulae with %d ingredient%s %d Formulae with total_chance=%d\n", num_ingred, num_ingred > 1 ? "s." : ".", fl->number, fl->total_chance);
|
|
for (formula = fl->items; formula != NULL; formula = formula->next) {
|
|
artifact *art = NULL;
|
|
archetype *at = NULL;
|
|
char buf[MAX_BUF];
|
|
size_t i;
|
|
|
|
for (i = 0; i < formula->arch_names; i++) {
|
|
const char *string = formula->arch_name[i];
|
|
|
|
if ((at = find_archetype(string)) != NULL) {
|
|
art = locate_recipe_artifact(formula, i);
|
|
if (!art && strcmp(formula->title, "NONE"))
|
|
LOG(llevError, "Formula %s has no artifact\n", formula->title);
|
|
else {
|
|
if (!strcmp(formula->title, "NONE"))
|
|
snprintf(buf, sizeof(buf), "%s", string);
|
|
else
|
|
snprintf(buf, sizeof(buf), "%s of %s", string, formula->title);
|
|
fprintf(logfile, "\n%-40s bookchance %3d skill %s\n", buf, formula->chance, formula->skill);
|
|
if (formula->ingred != NULL) {
|
|
tcost = 0;
|
|
for (next = formula->ingred; next != NULL; next = next->next) {
|
|
cost = find_ingred_cost(next->name);
|
|
if (cost < 0)
|
|
num_errors++;
|
|
fprintf(logfile, "\t%-33s%5ld\n", next->name, cost);
|
|
if (cost < 0 || tcost < 0)
|
|
tcost = -1;
|
|
else
|
|
tcost += cost;
|
|
}
|
|
if (art != NULL && art->item != NULL)
|
|
cost = at->clone.value*art->item->value;
|
|
else
|
|
cost = at->clone.value;
|
|
fprintf(logfile, "\t\tBuying result costs: %5ld", cost);
|
|
if (formula->yield > 1) {
|
|
fprintf(logfile, " to %ld (max %d items)\n", cost*formula->yield, formula->yield);
|
|
cost = cost*(formula->yield+1L)/2L;
|
|
} else
|
|
fprintf(logfile, "\n");
|
|
fprintf(logfile, "\t\tIngredients cost: %5ld\n\t\tComment: ", tcost);
|
|
if (tcost < 0)
|
|
fprintf(logfile, "Could not find some ingredients. Check the formula!\n");
|
|
else if (tcost > cost)
|
|
fprintf(logfile, "Ingredients are much too expensive. Useless formula.\n");
|
|
else if (tcost*2L > cost)
|
|
fprintf(logfile, "Ingredients are too expensive.\n");
|
|
else if (tcost*10L < cost)
|
|
fprintf(logfile, "Ingredients are too cheap.\n");
|
|
else
|
|
fprintf(logfile, "OK.\n");
|
|
}
|
|
}
|
|
} else
|
|
LOG(llevError, "Can't find archetype:%s for formula %s\n", string, formula->title);
|
|
}
|
|
}
|
|
fprintf(logfile, "\n");
|
|
fl = fl->next;
|
|
num_ingred++;
|
|
}
|
|
if (num_errors > 0)
|
|
fprintf(logfile, "WARNING: %d objects required by the formulae do not exist in the game.\n", num_errors);
|
|
}
|
|
|
|
/**
|
|
* Extracts the name from an ingredient.
|
|
*
|
|
* @param name
|
|
* ingredient to extract from. Can contain a number at start.
|
|
* @return
|
|
* pointer in name to the first character of the ingredient's name.
|
|
*/
|
|
static const char *ingred_name(const char *name) {
|
|
const char *cp = name;
|
|
|
|
if (atoi(cp))
|
|
cp = strchr(cp, ' ')+1;
|
|
return cp;
|
|
}
|
|
|
|
/**
|
|
* Extracts the number part of an ingredient.
|
|
*
|
|
* @param buf
|
|
* ingredient.
|
|
* @return
|
|
* number part of an ingredient.
|
|
*/
|
|
static int numb_ingred(const char *buf) {
|
|
int numb;
|
|
|
|
if ((numb = atoi(buf)))
|
|
return numb;
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Convert buf into an integer equal to the coadded sum of the (lowercase) character
|
|
*
|
|
* ASCII values in buf (times prepended integers).
|
|
*
|
|
* @param buf
|
|
* buffer we want to convert. Can contain an initial number.
|
|
* @return
|
|
* sum of lowercase characters of the ingredient's name.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Finds an artifact for a recipe.
|
|
*
|
|
* @param rp
|
|
* recipe
|
|
* @param idx
|
|
* index of ingredient in recipe.
|
|
* @return
|
|
* artifact, or NULL if not found.
|
|
*/
|
|
artifact *locate_recipe_artifact(const recipe *rp, size_t idx) {
|
|
object *item = create_archetype(rp->arch_name[idx]);
|
|
artifactlist *at = NULL;
|
|
artifact *art = NULL;
|
|
|
|
if (!item)
|
|
return (artifact *)NULL;
|
|
|
|
if ((at = find_artifactlist(item->type)))
|
|
for (art = at->items; art; art = art->next)
|
|
if (!strcmp(art->item->name, rp->title) && legal_artifact_combination(item, art))
|
|
break;
|
|
|
|
free_object(item);
|
|
|
|
return art;
|
|
}
|
|
|
|
/**
|
|
* Gets a random recipe list.
|
|
*
|
|
* @return
|
|
* random recipe list.
|
|
*/
|
|
static 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 = RANDOM()%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;
|
|
}
|
|
|
|
/**
|
|
* Gets a random recipe from a list, based on chance.
|
|
*
|
|
* @param rpl
|
|
* recipelist we want a recipe from. Can be NULL in which case a random one is selected.
|
|
* @return
|
|
* random recipe. Can be NULL if recipelist has a total_chance of 0.
|
|
*/
|
|
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 = RANDOM()%fl->total_chance;
|
|
for (rp = fl->items; rp; rp = rp->next) {
|
|
r -= rp->chance;
|
|
if (r < 0)
|
|
break;
|
|
}
|
|
}
|
|
return rp;
|
|
}
|
|
|
|
/**
|
|
* Frees all memory allocated to recipes and recipes lists.
|
|
*/
|
|
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);
|
|
if (formula->title)
|
|
free_string(formula->title);
|
|
if (formula->skill)
|
|
free_string(formula->skill);
|
|
if (formula->cauldron)
|
|
free_string(formula->cauldron);
|
|
for (lchar = formula->ingred; lchar; lchar = charnext) {
|
|
charnext = lchar->next;
|
|
free_string(lchar->name);
|
|
free(lchar);
|
|
}
|
|
free(formula);
|
|
}
|
|
free(fl);
|
|
}
|
|
formulalist = NULL;
|
|
}
|
|
|
|
/**
|
|
* Split a comma separated string list into words.
|
|
*
|
|
* @param str
|
|
* the string to split
|
|
* @param[out] result_list
|
|
* pointer to return value for the newly created list; the
|
|
* caller is responsible for freeing both *result_list and **result_list.
|
|
* @param[out] 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_local(str);
|
|
if (dup == NULL)
|
|
fatal(OUT_OF_MEMORY);
|
|
|
|
size = 0;
|
|
for (p = strtok(dup, ","); p != NULL; p = strtok(NULL, ","))
|
|
size++;
|
|
|
|
*result_list = malloc(size*sizeof(*result_list));
|
|
if (*result_list == NULL)
|
|
fatal(OUT_OF_MEMORY);
|
|
*result_size = size;
|
|
|
|
for (i = 0; i < size; i++) {
|
|
(*result_list)[i] = dup;
|
|
dup = dup+strlen(dup)+1;
|
|
}
|
|
}
|