diff -Naur nethack-3.4.3/src/objects.c nethack-alchemy/src/objects.c --- nethack-3.4.3/src/objects.c 2003-12-08 10:39:13.000000000 +1100 +++ nethack-alchemy/src/objects.c 2003-12-20 14:43:35.265625000 +1100 @@ -729,11 +729,11 @@ POTION("blindness", "yellow", 1, BLINDED, 40, 150, CLR_YELLOW), POTION("paralysis", "emerald", 1, 0, 42, 300, CLR_BRIGHT_GREEN), POTION("speed", "dark green", 1, FAST, 42, 200, CLR_GREEN), -POTION("levitation", "cyan", 1, LEVITATION, 42, 200, CLR_CYAN), +POTION("levitation", "viscous", 1, LEVITATION, 42, 200, CLR_GRAY), POTION("hallucination", "sky blue", 1, HALLUC, 40, 100, CLR_CYAN), POTION("invisibility", "brilliant blue",1, INVIS, 40, 150, CLR_BRIGHT_BLUE), POTION("see invisible", "magenta", 1, SEE_INVIS, 42, 50, CLR_MAGENTA), -POTION("healing", "purple-red", 1, 0, 57, 100, CLR_MAGENTA), +POTION("healing", "amber", 1, 0, 57, 100, CLR_ORANGE), POTION("extra healing", "puce", 1, 0, 47, 100, CLR_RED), POTION("gain level", "milky", 1, 0, 20, 300, CLR_WHITE), POTION("enlightenment", "swirly", 1, 0, 20, 200, CLR_BROWN), diff -Naur nethack-3.4.3/src/potion.c nethack-alchemy/src/potion.c --- nethack-3.4.3/src/potion.c 2003-12-08 10:39:13.000000000 +1100 +++ nethack-alchemy/src/potion.c 2003-12-20 14:49:52.171875000 +1100 @@ -13,6 +13,7 @@ STATIC_DCL long FDECL(itimeout, (long)); STATIC_DCL long FDECL(itimeout_incr, (long,int)); STATIC_DCL void NDECL(ghost_from_bottle); +STATIC_OVL void NDECL(alchemy_init); STATIC_DCL short FDECL(mixtype, (struct obj *,struct obj *)); /* force `val' to be within valid range for intrinsic timeout value */ @@ -1318,100 +1319,299 @@ } } +/* new alchemy scheme based on color mixing + * YANI by Graham Cox + * Implemented by Nephi Allred on 15 Apr 2003 + * + * Alchemical tables are based on 4 bits describing dark/light level, yellow, blue and red + * + * DYBR + * 0000 white + * 0001 pink + * 0010 sky-blue + * 0011 puce + * 0100 yellow + * 0101 orange + * 0110 emerald + * 0111 brown + * 1000 black + * 1001 ruby + * 1010 brilliant blue + * 1011 magenta + * 1100 golden + * 1101 amber + * 1110 dark green + * 1111 brown + */ + +/* Assumes gain ability is first potion and water is last */ +char alchemy_table1[POT_WATER - POT_GAIN_ABILITY]; +short alchemy_table2[17]; + +#define ALCHEMY_WHITE 0 +#define ALCHEMY_BLACK 8 +#define ALCHEMY_GRAY (alchemy_table2[16]) +#define IS_PRIMARY_COLOR(x) (((x)&7)==1 || ((x)&7)==2 || ((x)&7)==4) +#define IS_SECONDARY_COLOR(x) (((x)&7)==3 || ((x)&7)==5 || ((x)&7)==6) +#define IS_LIGHT_COLOR(x) (((x)&8)==0) +#define IS_DARK_COLOR(x) ((x)&8) + +/* do a one-time set up of alchemical tables */ +STATIC_OVL void +alchemy_init() +{ + static boolean init = FALSE; + + if(init==FALSE) { + short i; + const char* potion_desc; + + for(i=POT_GAIN_ABILITY;i<=POT_WATER;i++) { + potion_desc = OBJ_DESCR(objects[i]); + if(0==strcmp(potion_desc,"white")) { + alchemy_table1[i-POT_GAIN_ABILITY]=0; + alchemy_table2[0]=i; + } else if (0==strcmp(potion_desc,"pink")) { + alchemy_table1[i-POT_GAIN_ABILITY]=1; + alchemy_table2[1]=i; + } else if (0==strcmp(potion_desc,"sky blue")) { + alchemy_table1[i-POT_GAIN_ABILITY]=2; + alchemy_table2[2]=i; + } else if (0==strcmp(potion_desc,"puce")) { + alchemy_table1[i-POT_GAIN_ABILITY]=3; + alchemy_table2[3]=i; + } else if (0==strcmp(potion_desc,"yellow")) { + alchemy_table1[i-POT_GAIN_ABILITY]=4; + alchemy_table2[4]=i; + } else if (0==strcmp(potion_desc,"orange")) { + alchemy_table1[i-POT_GAIN_ABILITY]=5; + alchemy_table2[5]=i; + } else if (0==strcmp(potion_desc,"emerald")) { + alchemy_table1[i-POT_GAIN_ABILITY]=6; + alchemy_table2[6]=i; + } else if (0==strcmp(potion_desc,"brown")) { + alchemy_table1[i-POT_GAIN_ABILITY]=7; + alchemy_table2[7]=i; + alchemy_table2[15]=i; + } else if (0==strcmp(potion_desc,"black")) { + alchemy_table1[i-POT_GAIN_ABILITY]=8; + alchemy_table2[8]=i; + } else if (0==strcmp(potion_desc,"ruby")) { + alchemy_table1[i-POT_GAIN_ABILITY]=9; + alchemy_table2[9]=i; + } else if (0==strcmp(potion_desc,"brilliant blue")) { + alchemy_table1[i-POT_GAIN_ABILITY]=10; + alchemy_table2[10]=i; + } else if (0==strcmp(potion_desc,"magenta")) { + alchemy_table1[i-POT_GAIN_ABILITY]=11; + alchemy_table2[11]=i; + } else if (0==strcmp(potion_desc,"golden")) { + alchemy_table1[i-POT_GAIN_ABILITY]=12; + alchemy_table2[12]=i; + } else if (0==strcmp(potion_desc,"amber")) { + alchemy_table1[i-POT_GAIN_ABILITY]=13; + alchemy_table2[13]=i; + } else if (0==strcmp(potion_desc,"dark green")) { + alchemy_table1[i-POT_GAIN_ABILITY]=14; + alchemy_table2[14]=i; + } else if (0==strcmp(potion_desc,"cloudy")) { + alchemy_table1[i-POT_GAIN_ABILITY]=-1; + alchemy_table2[16]=i; + } else { + alchemy_table1[i-POT_GAIN_ABILITY]=-1; + } + } + init = TRUE; + } +} + STATIC_OVL short mixtype(o1, o2) register struct obj *o1, *o2; /* returns the potion type when o1 is dipped in o2 */ { - /* cut down on the number of cases below */ - if (o1->oclass == POTION_CLASS && - (o2->otyp == POT_GAIN_LEVEL || - o2->otyp == POT_GAIN_ENERGY || - o2->otyp == POT_HEALING || - o2->otyp == POT_EXTRA_HEALING || - o2->otyp == POT_FULL_HEALING || - o2->otyp == POT_ENLIGHTENMENT || - o2->otyp == POT_FRUIT_JUICE)) { - struct obj *swp; - - swp = o1; o1 = o2; o2 = swp; - } - - switch (o1->otyp) { - case POT_HEALING: - switch (o2->otyp) { - case POT_SPEED: - case POT_GAIN_LEVEL: - case POT_GAIN_ENERGY: - return POT_EXTRA_HEALING; - } - case POT_EXTRA_HEALING: - switch (o2->otyp) { - case POT_GAIN_LEVEL: - case POT_GAIN_ENERGY: - return POT_FULL_HEALING; - } - case POT_FULL_HEALING: - switch (o2->otyp) { - case POT_GAIN_LEVEL: - case POT_GAIN_ENERGY: - return POT_GAIN_ABILITY; - } - case UNICORN_HORN: - switch (o2->otyp) { - case POT_SICKNESS: - return POT_FRUIT_JUICE; - case POT_HALLUCINATION: - case POT_BLINDNESS: - case POT_CONFUSION: - return POT_WATER; - } - break; - case AMETHYST: /* "a-methyst" == "not intoxicated" */ - if (o2->otyp == POT_BOOZE) - return POT_FRUIT_JUICE; - break; - case POT_GAIN_LEVEL: - case POT_GAIN_ENERGY: - switch (o2->otyp) { - case POT_CONFUSION: - return (rn2(3) ? POT_BOOZE : POT_ENLIGHTENMENT); - case POT_HEALING: - return POT_EXTRA_HEALING; - case POT_EXTRA_HEALING: - return POT_FULL_HEALING; - case POT_FULL_HEALING: - return POT_GAIN_ABILITY; - case POT_FRUIT_JUICE: - return POT_SEE_INVISIBLE; - case POT_BOOZE: - return POT_HALLUCINATION; - } - break; - case POT_FRUIT_JUICE: - switch (o2->otyp) { - case POT_SICKNESS: - return POT_SICKNESS; - case POT_SPEED: - return POT_BOOZE; - case POT_GAIN_LEVEL: - case POT_GAIN_ENERGY: - return POT_SEE_INVISIBLE; - } - break; - case POT_ENLIGHTENMENT: - switch (o2->otyp) { - case POT_LEVITATION: - if (rn2(3)) return POT_GAIN_LEVEL; + if(o1->oclass == POTION_CLASS) { + char i1,i2,result; + + alchemy_init(); + i1 = alchemy_table1[o1->otyp-POT_GAIN_ABILITY]; + i2 = alchemy_table1[o2->otyp-POT_GAIN_ABILITY]; + + /* check that both potions are of mixable types */ + if(i1<0 || i2<0) + return 0; + + /* swap for simplified checks */ + if(i2==ALCHEMY_WHITE || (i2==ALCHEMY_BLACK && i1!=ALCHEMY_WHITE)) { + result = i1; + i1 = i2; + i2 = result; + } + + if(i1==ALCHEMY_WHITE && i2==ALCHEMY_BLACK) { + return ALCHEMY_GRAY; + } else if( (IS_PRIMARY_COLOR(i1) && IS_PRIMARY_COLOR(i2)) + || (IS_SECONDARY_COLOR(i1) && IS_SECONDARY_COLOR(i2)) ) { + /* bitwise OR simulates pigment addition */ + result = i1 | i2; + /* adjust light/dark level if necessary */ + if((i1^i2)&8) { + if(o1->odiluted==o2->odiluted) { + /* same dilution level, randomly toggle */ + result ^= (rn2(2)<<3); + } else { + /* use dark/light level of undiluted potion */ + result ^= (o1->odiluted ? i1:i2)&8; + } + } + } else if ( (i1==ALCHEMY_WHITE && IS_DARK_COLOR(i2)) + || (i1==ALCHEMY_BLACK && IS_LIGHT_COLOR(i2))) { + /* toggle light/dark bit */ + result = i2 ^ 8; + } else { + return 0; + } + return alchemy_table2[result]; + } else { + switch (o1->otyp) { + case UNICORN_HORN: + switch (o2->otyp) { + case POT_SICKNESS: + return POT_FRUIT_JUICE; + case POT_HALLUCINATION: + case POT_BLINDNESS: + case POT_CONFUSION: + return POT_WATER; + } + break; + case AMETHYST: /* "a-methyst" == "not intoxicated" */ + if (o2->otyp == POT_BOOZE) + return POT_FRUIT_JUICE; break; - case POT_FRUIT_JUICE: - return POT_BOOZE; - case POT_BOOZE: - return POT_CONFUSION; - } - break; - } + } + } + /* MRKR: Extra alchemical effects. */ + + if (o2->otyp == POT_ACID && o1->oclass == GEM_CLASS) { + char *potion_descr = NULL; + + /* Note: you can't create smoky, milky or clear potions */ + + switch (o1->otyp) { + + /* white */ + + case DILITHIUM_CRYSTAL: + /* explodes - special treatment in dodip */ + /* here we just want to return something non-zero */ + return POT_WATER; + break; + case DIAMOND: + /* won't dissolve */ + break; + case OPAL: + potion_descr = "cloudy"; + break; + + /* red */ + + case RUBY: + potion_descr = "ruby"; + break; + case GARNET: + potion_descr = "pink"; + break; + case JASPER: + potion_descr = "murky"; + break; + + /* orange */ + + case JACINTH: + potion_descr = "orange"; + break; + case AGATE: + potion_descr = "swirly"; + break; + + /* yellow */ + + case CITRINE: + potion_descr = "yellow"; + break; + case CHRYSOBERYL: + potion_descr = "golden"; + break; + + /* yellowish brown */ + + case AMBER: + potion_descr = "amber"; + break; + case TOPAZ: + potion_descr = "brown"; + break; + + /* green */ + + case EMERALD: + potion_descr = "emerald"; + break; + case TURQUOISE: + potion_descr = "sky blue"; + break; + case AQUAMARINE: + potion_descr = "viscous"; + break; + case JADE: + potion_descr = "dark green"; + break; + + /* blue */ + + case SAPPHIRE: + potion_descr = "brilliant blue"; + break; + + /* violet */ + + case AMETHYST: + potion_descr = "magenta"; + break; + case FLUORITE: + potion_descr = "white"; + break; + + /* black */ + + case BLACK_OPAL: + potion_descr = "black"; + break; + case JET: + potion_descr = "dark"; + break; + case OBSIDIAN: + potion_descr = "effervescent"; + break; + } + + if (potion_descr) { + int typ; + + /* find a potion that matches the description */ + + for (typ = bases[POTION_CLASS]; + objects[typ].oc_class == POTION_CLASS; + typ++) { + + if (strcmp(potion_descr, OBJ_DESCR(objects[typ])) == 0) { + return typ; + } + } + } + } + return 0; } @@ -1438,8 +1638,10 @@ if (obj->otyp == POT_ACID) { pline("It boils vigorously!"); You("are caught in the explosion!"); - losehp(rnd(10), "elementary chemistry", KILLED_BY); + losehp(Acid_resistance ? rnd(5) : rnd(10), + "elementary chemistry", KILLED_BY); makeknown(obj->otyp); + useup(obj); update_inventory(); return (TRUE); } @@ -1524,7 +1726,7 @@ int dodip() { - register struct obj *potion, *obj; + struct obj *potion, *obj; struct obj *singlepotion; const char *tmp; uchar here; @@ -1556,7 +1758,6 @@ #endif } else { (void) get_wet(obj); - if (obj->otyp == POT_ACID) useup(obj); } return 1; } @@ -1568,6 +1769,13 @@ pline("That is a potion bottle, not a Klein bottle!"); return 0; } + + if(potion->otyp != POT_WATER && obj->otyp == POT_WATER) { + /* swap roles, to ensure symmetry */ + struct obj *otmp = potion; + potion = obj; + obj = otmp; + } potion->in_use = TRUE; /* assume it will be used up */ if(potion->otyp == POT_WATER) { boolean useeit = !Blind; @@ -1664,16 +1872,26 @@ return(1); } else if(obj->oclass == POTION_CLASS && obj->otyp != potion->otyp) { /* Mixing potions is dangerous... */ - pline_The("potions mix..."); + /* Give a clue to what's going on ... */ + if(potion->dknown && obj->dknown) { + You("mix the %s potion with the %s one ...", + OBJ_DESCR(objects[potion->otyp]), + OBJ_DESCR(objects[obj->otyp])); + } else + pline_The("potions mix..."); /* KMH, balance patch -- acid is particularly unstable */ - if (obj->cursed || obj->otyp == POT_ACID || !rn2(10)) { + if (obj->cursed || obj->otyp == POT_ACID || + potion->cursed || potion->otyp == POT_ACID || !rn2(10)) { pline("BOOM! They explode!"); exercise(A_STR, FALSE); if (!breathless(youmonst.data) || haseyes(youmonst.data)) potionbreathe(obj); useup(obj); useup(potion); - losehp(rnd(10), "alchemic blast", KILLED_BY_AN); + /* MRKR: an alchemy smock ought to be */ + /* some protection against this: */ + losehp(Acid_resistance ? rnd(5) : rnd(10), + "alchemic blast", KILLED_BY_AN); return(1); } @@ -1861,7 +2079,7 @@ } potion->in_use = FALSE; /* didn't go poof */ - if ((obj->otyp == UNICORN_HORN || obj->otyp == AMETHYST) && + if ((obj->otyp == UNICORN_HORN || obj->oclass == GEM_CLASS) && (mixture = mixtype(obj, potion)) != 0) { char oldbuf[BUFSZ], newbuf[BUFSZ]; short old_otyp = potion->otyp; @@ -1880,10 +2098,55 @@ singlepotion = splitobj(potion, 1L); } else singlepotion = potion; + /* MRKR: Gems dissolve in acid to produce new potions */ + + if (obj->oclass == GEM_CLASS && potion->otyp == POT_ACID) { + + struct obj *singlegem = (obj->quan > 1L ? + splitobj(obj, 1L) : obj); + + if (potion->otyp == POT_ACID && + (obj->otyp == DILITHIUM_CRYSTAL || + potion->cursed || !rn2(10))) { + + /* Just to keep them on their toes */ + + if (Hallucination && obj->otyp == DILITHIUM_CRYSTAL) { + /* Thanks to Robin Johnson */ + pline("Warning, Captain! The warp core has been breached!"); + } + pline("BOOM! %s explodes!", The(xname(singlegem))); + exercise(A_STR, FALSE); + if (!breathless(youmonst.data) || haseyes(youmonst.data)) + potionbreathe(singlepotion); + useup(singlegem); + useup(singlepotion); + /* MRKR: an alchemy smock ought to be */ + /* some protection against this: */ + losehp(Acid_resistance ? rnd(5) : rnd(10), + "alchemic blast", KILLED_BY_AN); + return(1); + } + + pline("%s dissolves in %s.", The(xname(singlegem)), + the(xname(singlepotion))); + makeknown(POT_ACID); + useup(singlegem); + } + if(singlepotion->unpaid && costly_spot(u.ux, u.uy)) { You("use it, you pay for it."); bill_dummy_object(singlepotion); } + + if (singlepotion->otyp == mixture) { + /* no change - merge it back in */ + if (more_than_one && !merged(&potion, &singlepotion)) { + /* should never happen */ + impossible("singlepotion won't merge with parent potion."); + } + } + else { singlepotion->otyp = mixture; singlepotion->blessed = 0; if (mixture == POT_WATER) @@ -1918,6 +2181,8 @@ "You juggle and drop %s!", doname(singlepotion), (const char *)0); update_inventory(); + } + return(1); }