Crossfire Server, Trunk  R22047
alchemy.c
Go to the documentation of this file.
1 /*
2  * Crossfire -- cooperative multi-player graphical RPG and adventure game
3  *
4  * Copyright (c) 1999-2014 Mark Wedel and the Crossfire Development Team
5  * Copyright (c) 1992 Frank Tore Johansen
6  *
7  * Crossfire is free software and comes with ABSOLUTELY NO WARRANTY. You are
8  * welcome to redistribute it under certain conditions. For details, please
9  * see COPYING and LICENSE.
10  *
11  * The authors can be reached via e-mail at <crossfire@metalforge.org>.
12  */
13 
14 /* March 96 - Laid down original code. -b.t. thomas@astro.psu.edu */
15 
21 #include "global.h"
22 
23 #include <assert.h>
24 #include <ctype.h>
25 #include <stdlib.h>
26 #include <string.h>
27 
28 #include "object.h"
29 #include "shop.h"
30 #include "skills.h"
31 #include "spells.h"
32 #include "sproto.h"
33 
35 #if 0
36 #define ALCHEMY_DEBUG
37 #endif
38 
40 #if 0
41 #define EXTREME_ALCHEMY_DEBUG
42 #endif
43 
45 static const char *const cauldron_effect [] = {
46  "vibrates briefly",
47  "produces a cloud of steam",
48  "emits bright flames",
49  "pours forth heavy black smoke",
50  "emits sparks",
51  "shoots out small flames",
52  "whines painfully",
53  "hiccups loudly",
54  "wheezes",
55  "burps",
56  "shakes",
57  "rattles",
58  "makes chugging sounds",
59  "smokes heavily for a while"
60 };
61 
62 
63 static int is_defined_recipe(const recipe *rp, const object *cauldron, object *caster);
64 static const recipe *find_recipe(const recipelist *fl, int formula, object *ingredients);
65 static int content_recipe_value(object *op);
66 static int numb_ob_inside(const object *op);
67 static void alchemy_failure_effect(object *op, object *cauldron, const recipe *rp, int danger);
68 static object *attempt_recipe(object *caster, object *cauldron, int ability, const recipe *rp, int nbatches, int ignore_cauldron);
69 static int calc_alch_danger(object *caster, object *cauldron, const recipe *rp);
70 static object *make_item_from_recipe(object *cauldron, const recipe *rp);
71 static void remove_contents(object *first_ob, object *save_item);
72 static void adjust_product(object *item, int lvl, int yield);
73 static object *find_transmution_ob(object *first_ingred, const recipe *rp, size_t *rp_arch_index, int create_item);
74 static void attempt_do_alchemy(object *caster, object *cauldron);
75 
77 static const char *cauldron_sound(void) {
78  int size = sizeof(cauldron_effect)/sizeof(char *);
79 
80  return cauldron_effect[rndm(0, size-1)];
81 }
82 
89 static float chance_fn(int diff) {
90  if (diff > 10)
91  return MAX(.01, .3 - (diff - 10) * .03);
92 
93  if (diff > -10)
94  return .5 + .02 * (float)(-diff);
95 
96  return MIN(.95, .70 + (-diff - 10) * .01);
97 }
98 
121 static float recipe_chance(const recipe *rp, const object *skill, const object *cauldron) {
122  assert(rp);
123  assert(skill);
124  assert(cauldron);
125 
126  const int cauldron_add_skill = (cauldron->magic + 1) / 2;
127  const int eff_skill = skill->level + cauldron_add_skill;
128  return chance_fn(rp->diff - eff_skill);
129 }
130 
159 static void attempt_do_alchemy(object *caster, object *cauldron) {
160  const recipelist *fl;
161  const recipe *rp = NULL;
162  float success_chance;
163  int numb, ability = 1;
164  int formula = 0;
165  object *item, *skop;
166 
167  if (caster->type != PLAYER)
168  return; /* only players for now */
169 
170  /* if no ingredients, no formula! lets forget it */
171  if (!(formula = content_recipe_value(cauldron))) {
173  "The %s is empty.",
174  cauldron->name);
175  return;
176  }
177 
178  numb = numb_ob_inside(cauldron);
179  if ((fl = get_formulalist(numb))) {
180  if (QUERY_FLAG(caster, FLAG_WIZ)) {
181  rp = find_recipe(fl, formula, cauldron->inv);
182  if (rp != NULL) {
183 #ifdef ALCHEMY_DEBUG
184  if (strcmp(rp->title, "NONE"))
185  LOG(llevDebug, "WIZ got formula: %s of %s\n", rp->arch_name[0], rp->title);
186  else
187  LOG(llevDebug, "WIZ got formula: %s (nbatches:%d)\n", rp->arch_name[0], formula/rp->index);
188 #endif
189  attempt_recipe(caster, cauldron, ability, rp, formula/rp->index, !is_defined_recipe(rp, cauldron, caster));
190  } else
191  LOG(llevDebug, "WIZ couldn't find formula for ingredients.\n");
192  return;
193  } /* End of WIZ alchemy */
194 
195  /* find the recipe */
196  rp = find_recipe(fl, formula, cauldron->inv);
197  if (rp != NULL) {
198  uint64_t value_ingredients;
199  uint64_t value_item;
200  int attempt_shadow_alchemy;
201 
202  /* the caster gets an increase in ability based on thier skill lvl */
203  if (rp->skill != NULL) {
204  skop = find_skill_by_name(caster, rp->skill);
205  if (!skop) {
207  "You do not have the proper skill for this recipe");
208  } else {
209  ability += skop->level*((4.0+cauldron->magic)/4.0);
210  }
211  } else {
212  LOG(llevDebug, "Recipe %s has NULL skill!\n", rp->title);
213  return;
214  }
215 
216  if (rp->cauldron == NULL) {
217  LOG(llevDebug, "Recipe %s has NULL cauldron!\n", rp->title);
218  return;
219  }
220 
221  if (rp->min_level != 0 && skop->level < rp->min_level) {
223  "You aren't skilled enough to try this recipe.");
224  return;
225  }
226 
227  /* determine value of ingredients */
228  value_ingredients = 0;
229  FOR_INV_PREPARE(cauldron, tmp)
230  value_ingredients += price_base(tmp);
231  FOR_INV_FINISH();
232 
233  attempt_shadow_alchemy = !is_defined_recipe(rp, cauldron, caster);
234 
235  /* create the object **FIRST**, then decide whether to keep it. */
236  if ((item = attempt_recipe(caster, cauldron, ability, rp, formula/rp->index, attempt_shadow_alchemy)) != NULL) {
237  /* compute base chance of recipe success */
238  success_chance = recipe_chance(rp, skop, cauldron);
239 
240 #ifdef ALCHEMY_DEBUG
241  LOG(llevDebug, "percent success chance = %f ab%d / diff%d*lev%d\n", success_chance, ability, rp->diff, item->level);
242 #endif
243 
244  value_item = price_base(item);
245  if (attempt_shadow_alchemy && value_item > value_ingredients) {
246 #ifdef ALCHEMY_DEBUG
247 #ifndef WIN32
248  LOG(llevDebug, "Forcing failure for shadow alchemy recipe because price of ingredients (%llu) is less than price of result (%llu).\n", value_ingredients, value_item);
249 #else
250  LOG(llevDebug, "Forcing failure for shadow alchemy recipe because price of ingredients (%I64d) is less than price of result (%I64d).\n", value_ingredients, value_item);
251 #endif
252 #endif
253  /* roll the dice */
254  } else if (random_roll(0, 101, caster, PREFER_LOW) <= 100.0*success_chance) {
255  change_exp(caster, rp->exp, rp->skill, SK_EXP_NONE);
256  return;
257  }
258  }
259  }
260  }
261  /* if we get here, we failed!! */
262  alchemy_failure_effect(caster, cauldron, rp, calc_alch_danger(caster, cauldron, rp));
263 }
264 
275 static int content_recipe_value(object *op) {
276  char name[MAX_BUF];
277  int tval = 0, formula = 0;
278 
279  FOR_INV_PREPARE(op, tmp) {
280  tval = 0;
281  safe_strncpy(name, tmp->name, sizeof(name));
282  if (tmp->title)
283  snprintf(name, sizeof(name), "%s %s", tmp->name, tmp->title);
284  tval = (strtoint(name)*NROF(tmp));
285 #ifdef ALCHEMY_DEBUG
286  LOG(llevDebug, "Got ingredient %d %s(%d)\n", NROF(tmp), name, tval);
287 #endif
288  formula += tval;
289  } FOR_INV_FINISH();
290 #ifdef ALCHEMY_DEBUG
291  LOG(llevDebug, " Formula value=%d\n", formula);
292 #endif
293  return formula;
294 }
295 
303 static int numb_ob_inside(const object *op) {
304  int o_number = 0;
305 
306  FOR_INV_PREPARE(op, tmp)
307  o_number++;
308  FOR_INV_FINISH();
309 #ifdef ALCHEMY_DEBUG
310  LOG(llevDebug, "numb_ob_inside(%s): found %d ingredients\n", op->name, o_number);
311 #endif
312  return o_number;
313 }
314 
340 static object *attempt_recipe(object *caster, object *cauldron, int ability, const recipe *rp, int nbatches, int ignore_cauldron) {
341  object *item = NULL, *skop;
342  /* this should be passed to this fctn, not effiecent cpu use this way */
343  int batches = abs(nbatches);
344 
345  /* is the cauldron the right type? */
346  if (!ignore_cauldron && (strcmp(rp->cauldron, cauldron->arch->name) != 0)) {
348  "You are not using the proper facilities for this formula.");
349  return NULL;
350  }
351 
352  skop = find_skill_by_name(caster, rp->skill);
353  /* does the caster have the skill? */
354  if (!skop)
355  return NULL;
356 
357  /* code required for this recipe, search the caster */
358  if (rp->keycode) {
359  object *tmp;
360 
361  tmp = object_find_by_type_and_slaying(caster, FORCE, rp->keycode);
362  if (tmp == NULL) { /* failure--no code found */
364  "You know the ingredients, but not the technique. Go learn how to do this recipe.");
365  return NULL;
366  }
367  }
368 
369 #ifdef EXTREME_ALCHEMY_DEBUG
370  LOG(llevDebug, "attempt_recipe(): got %d nbatches\n", nbatches);
371  LOG(llevDebug, "attempt_recipe(): using recipe %s\n", rp->title ? rp->title : "unknown");
372 #endif
373 
374  if ((item = make_item_from_recipe(cauldron, rp)) != NULL) {
375  remove_contents(cauldron->inv, item);
376  /* adj lvl, nrof on caster level */
377  adjust_product(item, ability, rp->yield ? (rp->yield*batches) : batches);
378 
379  if (item->type == POTION) {
380  item->level = MAX(item->level, skop->level);
381  }
382 
383  if (!item->env && (item = object_insert_in_ob(item, cauldron)) == NULL) {
385  "Nothing happened.");
386  } else {
387  draw_ext_info_format(NDI_UNIQUE, 0, caster,
389  "The %s %s.",
390  cauldron->name, cauldron_sound());
391  }
392  }
393  return item;
394 }
395 
406 static void adjust_product(object *item, int adjust, int yield) {
407  int nrof = 1;
408 
409  if (!yield)
410  yield = 1;
411  if (adjust <= 0)
412  adjust = 1; /* lets avoid div by zero! */
413  if (item->nrof) {
414  nrof = (1.0-1.0/(adjust/10.0+1.0))*(rndm(0, yield-1)+rndm(0, yield-1)+rndm(0, yield-1))+1;
415  if (nrof > yield)
416  nrof = yield;
417  item->nrof = nrof;
418  }
419 }
420 
431 static object *make_item_from_recipe(object *cauldron, const recipe *rp) {
432  const artifact *art = NULL;
433  object *item = NULL;
434  size_t rp_arch_index;
435 
436  if (rp == NULL)
437  return (object *)NULL;
438 
439  /* Find the appropriate object to transform...*/
440  if ((item = find_transmution_ob(cauldron->inv, rp, &rp_arch_index, 1)) == NULL) {
441  LOG(llevDebug, "make_alchemy_item(): failed to create alchemical object.\n");
442  return (object *)NULL;
443  }
444 
445  /* If item is already in container, we need to remove its weight, since it can change later on. */
446  if (item->env != NULL)
447  object_sub_weight(cauldron, item->weight*NROF(item));
448 
449  /* Find the appropriate artifact template...*/
450  if (strcmp(rp->title, "NONE")) {
451  int item_power_delta = 0;
452  if ((art = locate_recipe_artifact(rp, rp_arch_index)) == NULL) {
453  LOG(llevError, "make_alchemy_item(): failed to locate recipe artifact.\n");
454  LOG(llevDebug, " --requested recipe: %s of %s.\n", rp->arch_name[0], rp->title);
455  return (object *)NULL;
456  }
457  transmute_materialname(item, art->item);
458  if (rp->transmute) {
459  /* If we change an item, then we must keep the item power.
460  * Else this allows for instance using a helmet+5 (item power 5) for a
461  * helmet of Xebinon, giving a helmet of Xebinon+5 with item power 0. */
462  item_power_delta = item->item_power - item->arch->clone.item_power;
463  }
464  give_artifact_abilities(item, art->item);
465  item->item_power += item_power_delta;
466  if (is_identified(item)) {
467  /* artifacts are generated unidentified, so if the item is identified we must give it its real properties. */
469  }
470  }
471  if (item->env != NULL)
472  object_add_weight(cauldron, item->weight*NROF(item));
473 
474  if (QUERY_FLAG(cauldron, FLAG_CURSED))
475  SET_FLAG(item, FLAG_CURSED);
476  if (QUERY_FLAG(cauldron, FLAG_DAMNED))
477  SET_FLAG(item, FLAG_DAMNED);
478 
479  return item;
480 }
481 
495 static object *find_transmution_ob(object *first_ingred, const recipe *rp, size_t *rp_arch_index, int create_item) {
496  object *item = NULL;
497 
498  *rp_arch_index = 0;
499 
500  if (rp->transmute) { /* look for matching ingredient/prod archs */
501  item = first_ingred;
503  size_t i;
504 
505  for (i = 0; i < rp->arch_names; i++) {
506  /* Items with titles are skipped to avoid shadow alchemy ability stacking. */
507  if (strcmp(item->arch->name, rp->arch_name[i]) == 0 && !(item->title)) {
508  *rp_arch_index = i;
509  break;
510  }
511  }
512  if (i < rp->arch_names)
513  break;
515  }
516 
517  /* failed, create a fresh object. Note no nrof>1 because that would
518  * allow players to create massive amounts of artifacts easily */
519  if (create_item && (!item || item->nrof > 1)) {
520  *rp_arch_index = RANDOM()%rp->arch_names;
521  item = create_archetype(rp->arch_name[*rp_arch_index]);
522  }
523 
524 #ifdef ALCHEMY_DEBUG
525  LOG(llevDebug, "recipe calls for%stransmution.\n", rp->transmute ? " " : " no ");
526  if (item != NULL) {
527  LOG(llevDebug, " find_transmutable_ob(): returns arch %s(sp:%d)\n", item->arch->name, item->stats.sp);
528  }
529 #endif
530 
531  return item;
532 }
533 
550 static void alchemy_failure_effect(object *op, object *cauldron, const recipe *rp, int danger) {
551  int level = 0;
552 
553  if (!op || !cauldron)
554  return;
555 
557  if (rp && rp->failure_arch) {
558  object *failure = create_archetype(rp->failure_arch);
559  if (!failure) {
560  LOG(llevError, "invalid failure_arch %s for recipe %s\n", rp->failure_arch, rp->title);
561  return;
562  }
563 
564  remove_contents(cauldron->inv, NULL);
565 
566  object_insert_in_ob(failure, cauldron);
567  if (rp->failure_message) {
569  }
570  return;
571  }
572 
573  if (danger > 1)
574  level = random_roll(1, danger, op, PREFER_LOW);
575 
576 #ifdef ALCHEMY_DEBUG
577  LOG(llevDebug, "Alchemy_failure_effect(): using level=%d\n", level);
578 #endif
579 
580  /* possible outcomes based on level */
581  if (level < 25) { /* INGREDIENTS USED/SLAGGED */
582  object *item = NULL;
583 
584  if (rndm(0, 2)) { /* slag created */
585  object *tmp;
586  int weight = 0;
587  uint16_t material = M_STONE;
588 
589  FOR_INV_PREPARE(cauldron, tmp) { /* slag has coadded ingredient properties */
590  weight += tmp->weight;
591  if (!(material&tmp->material))
592  material |= tmp->material;
593  } FOR_INV_FINISH();
594  tmp = create_archetype("rock");
595  tmp->weight = weight;
596  tmp->value = 0;
597  tmp->material = material;
598  tmp->materialname = add_string("stone");
599  free_string(tmp->name);
600  tmp->name = add_string("slag");
601  if (tmp->name_pl)
602  free_string(tmp->name_pl);
603  tmp->name_pl = add_string("slags");
604  item = object_insert_in_ob(tmp, cauldron);
606  CLEAR_FLAG(tmp, FLAG_NO_PICK);
607  tmp->move_block = 0;
608  }
609  remove_contents(cauldron->inv, item);
611  "The %s %s.",
612  cauldron->name, cauldron_sound());
613  return;
614  } else if (level < 40) { /* MAKE TAINTED ITEM */
615  object *tmp = NULL;
616 
617  /*
618  * Note by Nicolas Weeger 2010-09-26
619  * This is an incorrect part.
620  * Calling again attempt_recipe in case of failure will apply again the artifact
621  * combination to the item.
622  * This leads to items with eg 100% resist, or more.
623  * So use the actual item in the cauldron, don't retry the recipe.
624  * This should fix bug #2020224: buggy(?) crafting yields.
625  *
626  if (!rp)
627  if ((rp = get_random_recipe((recipelist *)NULL)) == NULL)
628  return;
629  */
630 
631  if ((tmp = cauldron->inv)) /*attempt_recipe(op, cauldron, 1, rp, -1, 0)))*/ {
632  if (!QUERY_FLAG(tmp, FLAG_CURSED)) { /* curse it */
633  SET_FLAG(tmp, FLAG_CURSED);
636  }
637 
638  /* the apply code for potions already deals with cursed
639  * potions, so any code here is basically ignored.
640  */
641  if (tmp->type == FOOD) {
642  tmp->stats.hp = random_roll(0, 149, op, PREFER_LOW);
643  }
644  tmp->value = 0; /* unsaleable item */
645 
646  /* change stats downward */
647  do {
648  change_attr_value(&tmp->stats, rndm(0, 6), -1*(rndm(1, 3)));
649  } while (rndm(0, 2));
650  }
651  return;
652  }
653 #if 0
654  /*
655  Note: this does not work as expected...
656  At this point there is only one item in the cauldron, and get_formulalist(0) will return
657  the first formula list for recipes with 1 ingredient.
658  So disable this, and just use the next case.
659  */
660 
661  if (level == 40) { /* MAKE RANDOM RECIPE */
662  recipelist *fl;
663  int numb = numb_ob_inside(cauldron);
664 
665  fl = get_formulalist(numb-1); /* take a lower recipe list */
666  if (fl && (rp = get_random_recipe(fl)))
667  /* even though random, don't grant user any EXP for it */
668  (void)attempt_recipe(op, cauldron, 1, rp, -1, 0);
669  else
670  alchemy_failure_effect(op, cauldron, rp, level-1);
671  return;
672 
673  } else
674 #endif
675  if (level < 45) { /* INFURIATE NPC's */
676  /* this is kind of kludgy I know...*/
677  object_set_enemy(cauldron, op);
678  monster_npc_call_help(cauldron);
679  object_set_enemy(cauldron, NULL);
680 
681  alchemy_failure_effect(op, cauldron, rp, level-5);
682  return;
683  } else if (level < 50) { /* MINOR EXPLOSION/FIREBALL */
684  object *tmp;
685 
686  remove_contents(cauldron->inv, NULL);
687  switch (rndm(0, 2)) {
688  case 0:
689  tmp = create_archetype("bomb");
690  tmp->stats.dam = random_roll(1, level, op, PREFER_LOW);
691  tmp->stats.hp = random_roll(1, level, op, PREFER_LOW);
693  "The %s creates a bomb!",
694  cauldron->name);
695  break;
696 
697  default:
698  tmp = create_archetype("fireball");
699  tmp->stats.dam = random_roll(1, level, op, PREFER_LOW)/5+1;
700  tmp->stats.hp = random_roll(1, level, op, PREFER_LOW)/10+2;
702  "The %s erupts in flame!",
703  cauldron->name);
704  break;
705  }
706  object_insert_in_map_at(tmp, op->map, NULL, 0, cauldron->x, cauldron->y);
707  return;
708  } else if (level < 60) { /* CREATE MONSTER */
710  "The %s %s.", cauldron->name, cauldron_sound());
711  remove_contents(cauldron->inv, NULL);
712  return;
713  } else if (level < 80) { /* MAJOR FIRE */
714  object *fb = create_archetype(SP_MED_FIREBALL);
715 
716  remove_contents(cauldron->inv, NULL);
717  fire_arch_from_position(cauldron, cauldron, cauldron->x, cauldron->y, 0, fb);
720  "The %s erupts in flame!",
721  cauldron->name);
722  return;
723  } else if (level < 100) { /* WHAMMY the CAULDRON */
724  if (!QUERY_FLAG(cauldron, FLAG_CURSED)) {
725  SET_FLAG(cauldron, FLAG_CURSED);
726  CLEAR_FLAG(cauldron, FLAG_KNOWN_CURSED);
727  CLEAR_FLAG(cauldron, FLAG_IDENTIFIED);
728  } else
729  cauldron->magic--;
730  cauldron->magic -= random_roll(0, 4, op, PREFER_LOW);
731  if (rndm(0, 1)) {
732  remove_contents(cauldron->inv, NULL);
734  "Your %s turns darker then makes a gulping sound!",
735  cauldron->name);
736  } else
738  "Your %s becomes darker.",
739  cauldron->name);
740  return;
741  } else if (level < 110) { /* SUMMON EVIL MONSTERS */
742  object *tmp = get_random_mon(level/5);
743 
744  remove_contents(cauldron->inv, NULL);
745  if (!tmp)
746  alchemy_failure_effect(op, cauldron, rp, level);
747  else if (summon_hostile_monsters(cauldron, random_roll(1, 10, op, PREFER_LOW), tmp->arch->name))
749  "The %s %s and then pours forth monsters!",
750  cauldron->name, cauldron_sound());
751  return;
752  } else if (level < 150) { /* COMBO EFFECT */
753  int roll = rndm(1, 3);
754  while (roll) {
755  alchemy_failure_effect(op, cauldron, rp, level-39);
756  roll--;
757  }
758  return;
759  } else if (level == 151) { /* CREATE RANDOM ARTIFACT */
760  object *tmp;
761 
762  /* this is meant to be better than prior possiblity,
763  * in this one, we allow *any *valid alchemy artifact
764  * to be made (rather than only those on the given
765  * formulalist) */
766  if (!rp)
767  rp = get_random_recipe((recipelist *)NULL);
768  if (rp && (tmp = create_archetype(rp->arch_name[RANDOM()%rp->arch_names]))) {
769  generate_artifact(tmp, random_roll(1, op->level/2+1, op, PREFER_HIGH)+1);
770  if ((tmp = object_insert_in_ob(tmp, cauldron))) {
771  remove_contents(cauldron->inv, tmp);
774  "The %s %s.",
775  cauldron->name, cauldron_sound());
776  }
777  }
778  return;
779  } else { /* MANA STORM - watch out!! */
780  object *tmp = create_archetype(LOOSE_MANA);
782  "You unwisely release potent forces!");
783  remove_contents(cauldron->inv, NULL);
784  cast_magic_storm(op, tmp, level);
785  return;
786  }
787 }
788 
798 static void remove_contents(object *first_ob, object *save_item) {
799  object *tmp;
800 
801  tmp = first_ob;
803  if (tmp != save_item) {
804  if (tmp->inv)
805  remove_contents(tmp->inv, NULL);
806  object_remove(tmp);
808  }
810 }
811 
831 static int calc_alch_danger(object *caster, object *cauldron, const recipe *rp) {
832  int danger = 0;
833 
834  /* Knowing alchemy skill reduces yer risk */
835  danger -= caster->chosen_skill ? caster->chosen_skill->level : caster->level;
836 
837  /* better cauldrons reduce risk */
838  danger -= cauldron->magic;
839 
840  /* Higher Int, lower the risk */
841  danger -= 3*(caster->stats.Int-15);
842 
843  /* Ingredients. */
844  FOR_INV_PREPARE(cauldron, item) {
845  /* Nicolas Weeger: no reason why this is the case.
846  danger += item->weight / 100;
847  */
848  danger++;
849  if (QUERY_FLAG(item, FLAG_CURSED) || QUERY_FLAG(item, FLAG_DAMNED))
850  danger += 5;
851  } FOR_INV_FINISH();
852 
853  if (rp == NULL)
854  danger += 110;
855  else
856  danger += rp->diff*3;
857 
858  /* Using a bad device is *majorly *stupid */
859  if (QUERY_FLAG(cauldron, FLAG_CURSED))
860  danger += 80;
861  if (QUERY_FLAG(cauldron, FLAG_DAMNED))
862  danger += 200;
863 
864 #ifdef ALCHEMY_DEBUG
865  LOG(llevDebug, "calc_alch_danger() returned danger=%d\n", danger);
866 #endif
867 
868  return danger;
869 }
870 
890 static int is_defined_recipe(const recipe *rp, const object *cauldron, object *caster) {
891  uint32_t batches_in_cauldron;
892  const linked_char *ingredient;
893  int number;
894 
895  /* check for matching number of ingredients */
896  number = 0;
897  for (ingredient = rp->ingred; ingredient != NULL; ingredient = ingredient->next)
898  number++;
899  FOR_INV_PREPARE(cauldron, ob)
900  number--;
901  FOR_INV_FINISH();
902  if (number != 0)
903  return 0;
904 
905  /* check for matching ingredients */
906  batches_in_cauldron = 0;
907  for (ingredient = rp->ingred; ingredient != NULL; ingredient = ingredient->next) {
908  uint32_t nrof;
909  const char *name;
910  int ok;
911 
912  /* determine and remove nrof from name */
913  name = ingredient->name;
914  nrof = 0;
915  while (isdigit(*name)) {
916  nrof = 10*nrof+(*name-'0');
917  name++;
918  }
919  if (nrof == 0)
920  nrof = 1;
921  while (*name == ' ')
922  name++;
923 
924  /* find the current ingredient in the cauldron */
925  ok = 0;
926  FOR_INV_PREPARE(cauldron, ob) {
927  char name_ob[MAX_BUF];
928  const char *name2;
929 
930  if (ob->title == NULL)
931  name2 = ob->name;
932  else {
933  snprintf(name_ob, sizeof(name_ob), "%s %s", ob->name, ob->title);
934  name2 = name_ob;
935  }
936 
937  if (strcmp(name2, name) == 0) {
938  if (ob->nrof%nrof == 0) {
939  uint32_t batches;
940 
941  batches = ob->nrof/nrof;
942  if (batches_in_cauldron == 0) {
943  batches_in_cauldron = batches;
944  ok = 1;
945  } else if (batches_in_cauldron == batches)
946  ok = 1;
947  }
948  break;
949  }
950  } FOR_INV_FINISH();
951  if (!ok)
952  return(0);
953  }
954 
955  return(1);
956 }
957 
972 static const recipe *find_recipe(const recipelist *fl, int formula, object *ingredients) {
973  const recipe *rp;
974  const recipe *result; /* winning recipe, or NULL if no recipe found */
975  int recipes_matching; /* total number of matching recipes so far */
976  int transmute_found; /* records whether a transmuting recipe was found so far */
977  size_t rp_arch_index;
978 
979 #ifdef EXTREME_ALCHEMY_DEBUG
980  LOG(llevDebug, "looking for formula %d:\n", formula);
981 #endif
982  result = NULL;
983  recipes_matching = 0;
984  transmute_found = 0;
985  for (rp = fl->items; rp != NULL; rp = rp->next) {
986  /* check if recipe matches at all */
987  if (formula%rp->index != 0) {
988 #ifdef EXTREME_ALCHEMY_DEBUG
989  LOG(llevDebug, " formula %s of %s (%d) does not match\n", rp->arch_name[0], rp->title, rp->index);
990 #endif
991  continue;
992  }
993 
994  if (rp->transmute && find_transmution_ob(ingredients, rp, &rp_arch_index, 0) != NULL) {
995 #ifdef EXTREME_ALCHEMY_DEBUG
996  LOG(llevDebug, " formula %s of %s (%d) is a matching transmuting formula\n", rp->arch_name[rp_arch_index], rp->title, rp->index);
997 #endif
998  /* transmution recipe with matching base ingredient */
999  if (!transmute_found) {
1000  transmute_found = 1;
1001  recipes_matching = 0;
1002  }
1003  } else if (transmute_found) {
1004 #ifdef EXTREME_ALCHEMY_DEBUG
1005  LOG(llevDebug, " formula %s of %s (%d) matches but is not a matching transmuting formula\n", rp->arch_name[0], rp->title, rp->index);
1006 #endif
1007  /* "normal" recipe found after previous transmution recipe => ignore this recipe */
1008  continue;
1009  }
1010 #ifdef EXTREME_ALCHEMY_DEBUG
1011  else {
1012  LOG(llevDebug, " formula %s of %s (%d) matches\n", rp->arch_name[0], rp->title, rp->index);
1013  }
1014 #endif
1015 
1016  if (rndm(0, recipes_matching) == 0)
1017  result = rp;
1018 
1019  recipes_matching++;
1020  }
1021 
1022  if (result == NULL) {
1023 #ifdef ALCHEMY_DEBUG
1024  LOG(llevDebug, "couldn't find formula for ingredients.\n");
1025 #endif
1026  return NULL;
1027  }
1028 
1029 #ifdef ALCHEMY_DEBUG
1030  if (strcmp(result->title, "NONE") != 0)
1031  LOG(llevDebug, "got formula: %s of %s (nbatches:%d)\n", result->arch_name[0], result->title, formula/result->index);
1032  else
1033  LOG(llevDebug, "got formula: %s (nbatches:%d)\n", result->arch_name[0], formula/result->index);
1034 #endif
1035  return result;
1036 }
1037 
1049 int use_alchemy(object *op) {
1050  object *unpaid_cauldron = NULL;
1051  object *unpaid_item = NULL;
1052  int did_alchemy = 0;
1053  char name[MAX_BUF];
1054 
1055  if (QUERY_FLAG(op, FLAG_WIZ))
1056  draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_DM, "Note: alchemy in wizard-mode.\n");
1057 
1058  FOR_MAP_PREPARE(op->map, op->x, op->y, tmp) {
1059  if (QUERY_FLAG(tmp, FLAG_IS_CAULDRON)) {
1060  if (QUERY_FLAG(tmp, FLAG_UNPAID)) {
1061  unpaid_cauldron = tmp;
1062  continue;
1063  }
1064  unpaid_item = object_find_by_flag(tmp, FLAG_UNPAID);
1065  if (unpaid_item != NULL)
1066  continue;
1067 
1068  attempt_do_alchemy(op, tmp);
1069  if (QUERY_FLAG(tmp, FLAG_APPLIED))
1070  esrv_send_inventory(op, tmp);
1071  did_alchemy = 1;
1072  }
1073  } FOR_MAP_FINISH();
1074  if (unpaid_cauldron) {
1075  query_base_name(unpaid_cauldron, 0, name, MAX_BUF);
1077  "You must pay for your %s first!",
1078  name);
1079  } else if (unpaid_item) {
1080  query_base_name(unpaid_item, 0, name, MAX_BUF);
1082  "You must pay for your %s first!",
1083  name);
1084  }
1085 
1086  return did_alchemy;
1087 }
void draw_ext_info_format(int flags, int pri, const object *pl, uint8_t type, uint8_t subtype, const char *format,...)
Definition: main.c:313
#define M_STONE
Definition: material.h:20
#define FLAG_DAMNED
Definition: define.h:318
static void alchemy_failure_effect(object *op, object *cauldron, const recipe *rp, int danger)
Definition: alchemy.c:550
#define FLAG_UNPAID
Definition: define.h:236
int diff
Definition: recipe.h:16
static void attempt_do_alchemy(object *caster, object *cauldron)
Definition: alchemy.c:159
void object_give_identified_properties(object *op)
Definition: item.c:1324
#define SET_FLAG(xyz, p)
Definition: define.h:223
void object_sub_weight(object *op, signed long weight)
Definition: object.c:1562
recipe * get_random_recipe(recipelist *rpl)
Definition: recipe.c:622
static void adjust_product(object *item, int lvl, int yield)
Definition: alchemy.c:406
object * get_random_mon(int level)
Definition: readable.c:1251
static int numb_ob_inside(const object *op)
Definition: alchemy.c:303
int yield
Definition: recipe.h:21
void free_string(sstring str)
Definition: shstr.c:280
void query_base_name(const object *op, int plural, char *buf, size_t size)
Definition: item.c:680
linked_char * ingred
Definition: recipe.h:22
#define PREFER_LOW
Definition: define.h:601
void esrv_send_inventory(object *pl, object *op)
Definition: item.c:307
object * object_find_by_flag(const object *who, int flag)
Definition: object.c:3996
#define MSG_TYPE_SKILL_MISSING
Definition: newclient.h:582
object * object_find_by_type_and_slaying(const object *who, int type, const char *slaying)
Definition: object.c:3948
#define MSG_TYPE_SKILL_ERROR
Definition: newclient.h:583
int transmute
Definition: recipe.h:19
#define MAX(x, y)
Definition: compat.h:20
static const char *const cauldron_effect[]
Definition: alchemy.c:45
#define safe_strncpy
Definition: compat.h:23
void draw_ext_info(int flags, int pri, const object *pl, uint8_t type, uint8_t subtype, const char *message)
Definition: main.c:308
void transmute_materialname(object *op, const object *change)
Definition: utils.c:267
#define FOR_OB_AND_BELOW_FINISH()
Definition: define.h:791
#define MIN(x, y)
Definition: compat.h:17
#define FLAG_CAN_ROLL
Definition: define.h:254
#define LOOSE_MANA
Definition: spells.h:162
int rndm(int min, int max)
Definition: utils.c:162
void object_free_drop_inventory(object *ob)
Definition: object.c:1316
object * object_insert_in_map_at(object *op, mapstruct *m, object *originator, int flag, int x, int y)
Definition: object.c:1849
#define MSG_TYPE_COMMAND
Definition: newclient.h:379
static float chance_fn(int diff)
Definition: alchemy.c:89
int is_identified(const object *op)
Definition: item.c:1316
sstring title
Definition: recipe.h:11
char ** arch_name
Definition: recipe.h:13
object * object_insert_in_ob(object *op, object *where)
Definition: object.c:2602
static const recipe * find_recipe(const recipelist *fl, int formula, object *ingredients)
Definition: alchemy.c:972
static int content_recipe_value(object *op)
Definition: alchemy.c:275
object * create_archetype(const char *name)
Definition: arch.cpp:281
void monster_npc_call_help(object *op)
Definition: monster.c:1840
const artifact * locate_recipe_artifact(const recipe *rp, size_t idx)
Definition: recipe.c:562
#define snprintf
Definition: win32.h:46
static object * attempt_recipe(object *caster, object *cauldron, int ability, const recipe *rp, int nbatches, int ignore_cauldron)
Definition: alchemy.c:340
int summon_hostile_monsters(object *op, int n, const char *monstername)
Definition: spell_util.c:1043
struct linked_char * next
Definition: global.h:88
#define FLAG_IDENTIFIED
Definition: define.h:261
#define FOR_INV_FINISH()
Definition: define.h:714
int strtoint(const char *buf)
Definition: recipe.c:541
void change_attr_value(living *stats, int attr, int8_t value)
Definition: living.c:265
uint64_t price_base(const object *obj)
Definition: shop.c:75
#define FLAG_IS_CAULDRON
Definition: define.h:339
#define MSG_TYPE_SKILL_FAILURE
Definition: newclient.h:585
void cast_magic_storm(object *op, object *tmp, int lvl)
Definition: spell_effect.c:46
sstring skill
Definition: recipe.h:26
static int calc_alch_danger(object *caster, object *cauldron, const recipe *rp)
Definition: alchemy.c:831
unsigned __int64 uint64_t
Definition: win32.h:167
#define QUERY_FLAG(xyz, p)
Definition: define.h:225
#define CLEAR_FLAG(xyz, p)
Definition: define.h:224
#define FLAG_WIZ
Definition: define.h:231
void change_exp(object *op, int64_t exp, const char *skill_name, int flag)
Definition: living.c:2166
int index
Definition: recipe.h:18
#define MAX_BUF
Definition: define.h:35
void generate_artifact(object *op, int difficulty)
Definition: artifact.c:155
void give_artifact_abilities(object *op, const object *artifact)
Definition: artifact.c:203
unsigned short uint16_t
Definition: win32.h:163
#define FOR_MAP_FINISH()
Definition: define.h:767
#define FLAG_KNOWN_CURSED
Definition: define.h:321
object * ob
Definition: player.h:158
int exp
Definition: recipe.h:17
#define FLAG_CURSED
Definition: define.h:317
unsigned int uint32_t
Definition: win32.h:162
static int is_defined_recipe(const recipe *rp, const object *cauldron, object *caster)
Definition: alchemy.c:890
const char * name
Definition: global.h:87
#define PREFER_HIGH
Definition: define.h:600
static object * make_item_from_recipe(object *cauldron, const recipe *rp)
Definition: alchemy.c:431
#define SP_MED_FIREBALL
Definition: spells.h:161
#define RANDOM()
Definition: define.h:681
size_t arch_names
Definition: recipe.h:12
sstring failure_message
Definition: recipe.h:29
void object_set_enemy(object *op, object *enemy)
Definition: object.c:679
#define SK_EXP_NONE
Definition: skills.h:80
#define FLAG_APPLIED
Definition: define.h:235
struct recipestruct * next
Definition: recipe.h:24
static float recipe_chance(const recipe *rp, const object *skill, const object *cauldron)
Definition: alchemy.c:121
int fire_arch_from_position(object *op, object *caster, int16_t x, int16_t y, int dir, object *spell)
Definition: spell_util.c:668
#define FOR_OB_AND_BELOW_PREPARE(op_)
Definition: define.h:787
sstring add_string(const char *str)
Definition: shstr.c:124
void object_add_weight(object *op, signed long weight)
Definition: object.c:2578
#define MSG_TYPE_SKILL
Definition: newclient.h:383
#define NDI_UNIQUE
Definition: newclient.h:245
sstring failure_arch
Definition: recipe.h:28
void LOG(LogLevel logLevel, const char *format,...)
Definition: logger.c:51
#define FOR_MAP_PREPARE(map_, mx_, my_, it_)
Definition: define.h:760
static object * find_transmution_ob(object *first_ingred, const recipe *rp, size_t *rp_arch_index, int create_item)
Definition: alchemy.c:495
#define MSG_TYPE_SKILL_SUCCESS
Definition: newclient.h:584
object * item
Definition: artifact.h:15
static const char * cauldron_sound(void)
Definition: alchemy.c:77
int random_roll(int min, int max, const object *op, int goodbad)
Definition: utils.c:42
#define FLAG_NO_PICK
Definition: define.h:239
int use_alchemy(object *op)
Definition: alchemy.c:1049
object * find_skill_by_name(object *who, const char *name)
Definition: skill_util.c:202
static void remove_contents(object *first_ob, object *save_item)
Definition: alchemy.c:798
sstring cauldron
Definition: recipe.h:27
int level
Definition: readable.c:1589
sstring keycode
Definition: recipe.h:25
struct recipestruct * items
Definition: recipe.h:40
recipelist * get_formulalist(int i)
Definition: recipe.c:96
int min_level
Definition: recipe.h:30
#define FOR_INV_PREPARE(op_, it_)
Definition: define.h:707
void object_remove(object *op)
Definition: object.c:1588
#define MSG_TYPE_COMMAND_DM
Definition: newclient.h:515