[CF-Devel] Alchemy exploit

crossfire-devel at archives.real-time.com crossfire-devel at archives.real-time.com
Fri Aug 13 13:41:07 CDT 2004


Hello,

I'd like to come back to alternate recipes for alchemy and related
skills. I think they can be (and currently on metalforge are) abused to
make money. (I found 1 million(!) potions of sunfire sold to a shop.)

The common precondition for any valid recipe should be that at least one
ingredient is hard to get. This precondition is not valid for many
alternate recipes: after writing a small perl script, I recognized that
alternate recipes are *very* common, even when using only common
ingredients (for example coins or food). Without much effort, I found a
recipe that made items with a selling price of 40.000pp while using only
400pp of coins as ingredients.

To prevent this particular abuse, I'd like to propose the following fix
(see attached diff): do not allow *alternate* recipes with a result
value higher than that of the ingredients.

I limited the check for increased value to alternate recipes only
because there are some normal recipes that also match. But this should
not be a problem because you have to get the uncommon items first.
-------------- next part --------------
Index: server/alchemy.c
===================================================================
RCS file: /cvsroot/crossfire/crossfire/server/alchemy.c,v
retrieving revision 1.20
diff -u -w -r1.20 alchemy.c
--- server/alchemy.c	13 Sep 2003 05:02:07 -0000	1.20
+++ server/alchemy.c	13 Aug 2004 17:32:39 -0000
@@ -60,6 +60,9 @@
 }; 
 
 
+static int is_defined_recipe(const recipe *rp, const object *cauldron, object *caster);
+
+
 /* cauldron_sound() - returns a random selection from cauldron_effect[] */
 
 char * cauldron_sound ( void ) {
@@ -136,6 +139,11 @@
 	    ;
 
 	if (rp) { /* if we found a recipe */
+	    uint64 value_ingredients;
+	    uint64 value_item;
+	    object *tmp;
+	    int attempt_shadow_alchemy;
+
 	    ave_chance = fl->total_chance/(float)fl->number;
 	    /* the caster gets an increase in ability based on thier skill lvl */
 	    if (rp->skill != NULL) {
@@ -155,6 +163,13 @@
 		return;
 	    }
 
+	    /* determine value of ingredients */
+	    value_ingredients = 0;
+	    for(tmp = cauldron->inv; tmp != NULL; tmp = tmp->below)
+		value_ingredients += query_cost(tmp, NULL, F_TRUE);
+
+	    attempt_shadow_alchemy = !is_defined_recipe(rp, cauldron, caster);
+
 	    /* create the object **FIRST**, then decide whether to keep it.	*/
 	    if ((item=attempt_recipe(caster, cauldron, ability, rp, formula/rp->index)) != NULL) {
 		/*  compute base chance of recipe success */
@@ -168,8 +183,14 @@
 		    success_chance, ability, rp->diff, item->level);
 #endif
 
+		value_item = query_cost(item, NULL, F_TRUE);
+		new_draw_info_format(NDI_UNIQUE, 0, caster, "result value=%llu", value_item);
+		if(attempt_shadow_alchemy && value_item > value_ingredients)
+#ifdef ALCHEMY_DEBUG
+		    LOG(llevDebug, "Forcing failure for shadow alchemy recipe.\n");
+#endif
 		/* roll the dice */
-		if ((float)(random_roll(0, 101, caster, PREFER_LOW)) <= 100.0 * success_chance) {
+		else if ((float)(random_roll(0, 101, caster, PREFER_LOW)) <= 100.0 * success_chance) {
 		    change_exp(caster, rp->exp, rp->skill, SK_EXP_NONE);
 		    return;
 		}
@@ -640,4 +661,84 @@
    return danger;
 }
 
+/* is_defined_recipe() - determines if ingredients in a container match the
+ * proper ingredients for a recipe.
+ *
+ * rp is the recipe to check
+ * cauldron is the container that holds the ingredients
+ * returns 1 if the ingredients match the recipe, 0 if not
+ *
+ * This functions tries to find each defined ingredient in the container. It is
+ * the defined recipe iff
+ *  - the number of ingredients of the recipe and in the container is equal
+ *  - all ingredients of the recipe are found in the container
+ *  - the number of batches is the same for all ingredients
+ */
+static int is_defined_recipe(const recipe *rp, const object *cauldron, object *caster)
+{
+    unsigned long batches_in_cauldron;
+    const linked_char *ingredient;
+    int number;
+    const object *ob;
+
+    /* check for matching number of ingredients */
+    number = 0;
+    for(ingredient = rp->ingred; ingredient != NULL; ingredient = ingredient->next)
+	number++;
+    for(ob = cauldron->inv; ob != NULL; ob = ob->below)
+	number--;
+    if(number != 0)
+	return 0;
 
+    /* check for matching ingredients */
+    batches_in_cauldron = 0;
+    for(ingredient = rp->ingred; ingredient != NULL; ingredient = ingredient->next) {
+	unsigned long nrof;
+	const char *name;
+	int ok;
+
+	/* determine and remove nrof from name */
+	name = ingredient->name;
+	nrof = 0;
+	while(isdigit(*name)) {
+	    nrof = 10*nrof+(*name-'0');
+	    name++;
+	}
+	if(nrof == 0)
+	    nrof = 1;
+	while(*name == ' ')
+	    name++;
+
+	/* find the current ingredient in the cauldron */
+	ok = 0;
+	for(ob = cauldron->inv; ob != NULL; ob = ob->below) {
+	    char name_ob[MAX_BUF];
+	    const char *name2;
+
+	    if(ob->title == NULL)
+		name2 = ob->name;
+	    else {
+		snprintf(name_ob, sizeof(name_ob), "%s %s", ob->name, ob->title);
+		name2 = name_ob;
+	    }
+
+	    if(strcmp(name2, name) == 0) {
+		if(ob->nrof%nrof == 0) {
+		    unsigned long batches;
+
+		    batches = ob->nrof/nrof;
+		    if(batches_in_cauldron == 0) {
+			batches_in_cauldron = batches;
+			ok = 1;
+		    } else if(batches_in_cauldron == batches)
+			ok = 1;
+		}
+		break;
+	    }
+	}
+	if(!ok)
+	    return(0);
+    }
+
+    return(1);
+}
-------------- next part --------------
_______________________________________________
crossfire-devel mailing list
     
     crossfire-devel at lists.real-time.com
     
     
     https://mailman.real-time.com/mailman/listinfo/crossfire-devel
     
     
    


More information about the crossfire mailing list