/*
    sticks.c - Functions to implement the various sticks one might find
            
    UltraRogue: The Ultimate Adventure in the Dungeons of Doom
    Copyright (C) 1985, 1986, 1992, 1993, 1995 Herb Chong
    All rights reserved.

    Based on "Advanced Rogue"
    Copyright (C) 1984, 1985 Michael Morgan, Ken Dalka
    All rights reserved.

    Based on "Rogue: Exploring the Dungeons of Doom"
    Copyright (C) 1980, 1981 Michael Toy, Ken Arnold and Glenn Wichman
    All rights reserved.

    See the file LICENSE.TXT for full copyright and licensing information.
*/

#include <limits.h>
#include <string.h>
#include <ctype.h>
#include "rogue.h"

/* for WS_HIT, WS_WEB, etc */

static struct object null_stick =
{
    {0,0},NULL,NULL,"",0,"0d0",0,0,'X',0,0,0,0,0,0,0,0,0,{0}
};

/*
 * Mask for cancelling special abilities The flags listed here will be the
 * ones left on after the cancellation takes place
 */

#define CANC0MASK ( ISBLIND     | ISINWALL  | ISRUN     | \
            ISFLEE      | ISMEAN    | ISGREED   | \
            CANSHOOT    | ISHELD    | ISHUH     | \
            ISSLOW      | ISHASTE   | ISCLEAR   | \
            ISUNIQUE)

#define CANC1MASK ( HASDISEASE  | DIDSUFFOCATE  | CARRYGOLD     | \
            HASITCH     | CANSELL   | CANBBURN  | \
            CANSPEAK    | CANFLY    | ISFRIENDLY)

#define CANC2MASK ( HASINFEST   | NOMOVE    | ISSCAVENGE    | \
            DOROT       | HASSTINK  | DIDHOLD)

#define CANC3MASK ( ISUNDEAD    | CANBREATHE    | CANCAST   | \
            HASOXYGEN)

#define CANC4MASK ( CANTRAMPLE  | CANSWIM   | CANWIELD  | \
            ISFAST      | CANBARGAIN    | CANSPORE  | \
            ISLARGE     | ISSMALL   | ISFLOCK   | \
            ISSWARM     | CANSTICK  | CANTANGLE | \
            SHOOTNEEDLE | CANZAP    | HASARMOR  | \
            CANTELEPORT | ISBERSERK | ISFAMILIAR    | \
            HASFAMILIAR | SUMMONING)

#define CANC5MASK ( CANREFLECT  | MAGICATTRACT  | HASSHIELD | HASMSHIELD)

#define CANC6MASK ( 0 )
#define CANC7MASK ( 0 )
#define CANC8MASK ( 0 )
#define CANC9MASK ( 0 )
#define CANCAMASK ( 0 )
#define CANCBMASK ( 0 )
#define CANCCMASK ( 0 )
#define CANCDMASK ( 0 )
#define CANCEMASK ( 0 )
#define CANCFMASK ( 0 )

void
fix_stick(struct object *cur)
{
    if (strcmp(ws_type[cur->o_which], "staff") == 0)
    {
        cur->o_weight = 100;
        cur->o_charges = 5 + rnd(10);
        cur->o_damage = "2d3";

        switch (cur->o_which)
        {
            case WS_HIT:
                cur->o_hplus = 3;
                cur->o_dplus = 3;
                cur->o_damage = "2d8";
                break;

            case WS_LIGHT:
                cur->o_charges = 20 + rnd(10);
                break;
        }
    }
    else            /* A wand */
    {
        cur->o_damage = "1d3";
        cur->o_weight = 60;
        cur->o_charges = 3 + rnd(5);

        switch (cur->o_which)
        {
            case WS_HIT:
                cur->o_hplus = 3;
                cur->o_dplus = 3;
                cur->o_damage = "1d8";
                break;

            case WS_LIGHT:
                cur->o_charges = 10 + rnd(10);
                break;
        }
    }

    cur->o_hurldmg = "1d1";
}

/*
    do_zap()
        zap a stick (or effect a stick-like spell)
        zapper:  who does it
        got_dir: need to ask for direction?
        which:   which WS_STICK (-1 means ask from pack)
        flags:   ISBLESSED, ISCURSED
*/

void
do_zap(struct thing *zapper, int which, unsigned long flags)
{
    struct linked_list  *item = NULL, *nitem;
    struct object   *obj, *nobj;
    struct room *rp;
    struct thing    *tp = NULL;
    char *mname = NULL;
    int y, x;
    int blessed = flags & ISBLESSED;
    int cursed = flags & ISCURSED;
    int is_stick = (which < 0 ? TRUE : FALSE);
    int got_one = TRUE;

    if (zapper != &player)
    {
        monster_do_zap(zapper, which, flags);
        return;
    }

    if (is_stick)
    {
        if ((obj = get_object(pack, "zap with", 0, bff_zappable)) == NULL)
            return;

        if (obj->o_type != STICK && !(obj->o_flags & ISZAPPED))
        {
            msg("You can't zap with that!");
            return;
        }

        if (obj->o_type != STICK)   /* an electrified weapon */
            which = WS_ELECT;
        else
            which = obj->o_which;

        if (obj->o_charges < 1)
        {
            nothing_message(flags);
            return;
        }

        obj->o_charges--;

        if (delta.y == 0 && delta.x == 0)
            do
            {
                delta.y = rnd(3) - 1;
                delta.x = rnd(3) - 1;
            }
            while (delta.y == 0 && delta.x == 0);

        flags = obj->o_flags;
        cursed = obj->o_flags & ISCURSED;
        blessed = obj->o_flags & ISBLESSED;
    }
    else
        obj = &null_stick;

    /* Find out who the target is */

    y = hero.y;
    x = hero.x;

    while(shoot_ok(CCHAR(winat(y, x))))
    {
        y += delta.y;
        x += delta.x;
    }

    if (x >= 0 && x < COLS && y >= 1 && y < LINES - 2 &&
            isalpha(mvwinch(mw, y, x)))
    {
        item = find_mons(y, x);
        tp = THINGPTR(item);
        mname = on(player, ISBLIND) ? "monster" :
            monsters[tp->t_index].m_name;

        if (on(*tp, CANSELL))
        {
            luck++;
            aggravate();
        }
    }
    else
        got_one = FALSE;

    debug("Zapping with %d", which);

    switch(which)
    {
        case WS_LIGHT:
            /* Reddy Kilowat wand.  Light up the room */
            if (blue_light(flags) && is_stick)
                if (is_stick)
                    know_items[TYP_STICK][WS_LIGHT] = TRUE;
            break;

        case WS_DRAIN:

            /*
             * Take away 1/2 of hero's hit points, then take it away
             * evenly from the monsters in the room or next to hero if he
             * is in a passage. Leave the monsters alone if the stick is
             * cursed. Drain 1/3rd of hero's hit points if blessed.
             */

            if (pstats.s_hpt < 2)
            {
                death(D_DRAINLIFE);
                return;
            }

            if (cursed)
                pstats.s_hpt /= 2;
            else if ((rp = roomin(hero)) == NULL)
                drain(hero.y - 1, hero.y + 1, hero.x - 1, hero.x + 1);
            else
                drain(rp->r_pos.y, rp->r_pos.y + rp->r_max.y,
                      rp->r_pos.x, rp->r_pos.x + rp->r_max.x);

            if (blessed)
                pstats.s_hpt = (int) (pstats.s_hpt * 2.0 / 3.0);

            break;

        case WS_POLYMORPH:
        case WS_MONSTELEP:
        case WS_CANCEL:
        {
            char    oldch;
            int rm;
            int save_adj = 0;

            if (got_one)
            {
                /* if the monster gets the saving throw, leave the case */

                if (blessed)
                    save_adj = -5;

                if (cursed)
                    if (which == WS_POLYMORPH)
                        save_adj = -5;  /* not save vs becoming tougher */
                    else
                        save_adj = 5;

                if (save_throw(VS_MAGIC - save_adj, tp))
                {
                    nothing_message(flags);
                    break;
                }
                else if (is_stick)
                    know_items[TYP_STICK][which] = TRUE;

                /* Unhold player */

                if (on(*tp, DIDHOLD))
                {
                    turn_off(*tp, DIDHOLD);

                    if (--hold_count == 0)
                        turn_off(player, ISHELD);
                }

                /* unsuffocate player */

                if (on(*tp, DIDSUFFOCATE))
                {
                    turn_off(*tp, DIDSUFFOCATE);
                    extinguish_fuse(FUSE_SUFFOCATE);
                }

                if (which == WS_POLYMORPH)
                {
                    int which_new;
                    int charmed;

                    detach(mlist, item);
                    charmed = on(*tp, ISCHARMED);
                    oldch = tp->t_oldch;
                    delta.y = y;
                    delta.x = x;

                    if (!blessed && !cursed)
                        which_new = randmonster(WANDER, GRAB);
                    else
                    {
                        /* duplicate randmonster() for now */
                        /* Eventually fix to take level     */

                        int cur_level = 0, range, i;

                        if (blessed)
                            cur_level = level / 2;

                        if (cursed)
                            cur_level = level * 2;

                        range = 4 * NLEVMONS;
                        i = 0;

                        do
                        {
                            if (i++ > range * 10)   /* just in case all have */
                            {                       /* been genocided */
                                i = 0;

                                if (--cur_level <= 0)
                                    fatal("Rogue could not find a monster to make");
                            }

                            which_new = NLEVMONS * (cur_level - 1) + (rnd(range) - (range - 1 - NLEVMONS));

                            if (which_new < 1)
                                which_new = rnd(NLEVMONS) + 1;

                            if (which_new > nummonst - NUMSUMMON - 1)
                            {
                                if (blessed)
                                    which_new = rnd(range) + (nummonst - NUMSUMMON - 1) - (range - 1);
                                else if (which_new > nummonst - 1)
                                    which_new = rnd(range + NUMSUMMON) + (nummonst - 1) - (range + NUMSUMMON - 1);
                            }
                        }
                        while (!monsters[which_new].m_normal);
                    }

                    new_monster(item, which_new, &delta, NOMAXSTATS);
                    mname = on(player, ISBLIND) ? "monster" : monsters[tp->t_index].m_name;

                    if (!cursed && charmed)
                        turn_on(*tp, ISCHARMED);

                    if (off(*tp, ISRUN))
                        chase_it(&delta, &player);

                    if (isalpha(mvwinch(cw, y, x)))
                        mvwaddch(cw, y, x, tp->t_type);

                    tp->t_oldch = oldch;
                    seemsg("You have created a new %s!", mname);
                }
                else if (which == WS_CANCEL)
                {
                    tp->t_flags[0] &= CANC0MASK;
                    tp->t_flags[1] &= CANC1MASK;
                    tp->t_flags[2] &= CANC2MASK;
                    tp->t_flags[3] &= CANC3MASK;
                    tp->t_flags[4] &= CANC4MASK;
                    tp->t_flags[5] &= CANC5MASK;
                    tp->t_flags[6] &= CANC5MASK;
                    tp->t_flags[7] &= CANC7MASK;
                    tp->t_flags[8] &= CANC8MASK;
                    tp->t_flags[9] &= CANC9MASK;
                    tp->t_flags[10] &= CANCAMASK;
                    tp->t_flags[11] &= CANCBMASK;
                    tp->t_flags[12] &= CANCCMASK;
                    tp->t_flags[13] &= CANCDMASK;
                    tp->t_flags[14] &= CANCEMASK;
                    tp->t_flags[15] &= CANCFMASK;
                }
                else    /* A teleport stick */
                {
                    if (cursed)     /* Teleport monster to */
                    {               /*  player */
                        if ((y == (hero.y + delta.y)) &&
                          (x == (hero.x + delta.x)))
                            nothing_message(flags);
                        else
                        {
                            tp->t_pos.y = hero.y + delta.y;
                            tp->t_pos.x = hero.x + delta.x;
                        }
                    }
                    else if (blessed)   /* Get rid of monster */
                    {
                        killed(NULL, item, NOMESSAGE, NOPOINTS);
                        return;
                    }
                    else
                    {
                        int i = 0;

                        do      /* Move monster to */
                        {       /* another room */
                            rm = rnd_room();
                            rnd_pos(&rooms[rm], &tp->t_pos);
                        }
                        while (winat(tp->t_pos.y, tp->t_pos.x) !=
                             FLOOR && i++ < 500);
                    }

                    /* Now move the monster */

                    if (isalpha(mvwinch(cw, y, x)))
                        mvwaddch(cw, y, x, tp->t_oldch);

                    chase_it(&tp->t_pos, &player);
                    mvwaddch(mw, y, x, ' ');
                    mvwaddch(mw, tp->t_pos.y, tp->t_pos.x, tp->t_oldch);

                    if (tp->t_pos.y != y || tp->t_pos.x != x)
                        tp->t_oldch = CCHAR(mvwinch(cw, tp->t_pos.y, tp->t_pos.x));
                }
            }
        }
        break;
		
        case WS_MISSILE:
        {
            int damage;
            int nsides = 4;
            int  ch;
            coord pos;

            if (is_stick)
                know_items[TYP_STICK][which] = TRUE;

            /* Magic Missiles *always* hit, no saving throw */

            pos = do_motion('*', delta.y, delta.x, &player);
            ch  = winat(pos.y, pos.x);

            if (cursed)
                nsides /= 2;
            else if (blessed)
                nsides *= 2;

            damage = roll(pstats.s_lvl, nsides);

            if (isalpha(ch))
            {
                debug("Missiled %s for %d (%d)",
                 mname, damage, tp->t_stats.s_hpt - damage);

                if ((tp->t_stats.s_hpt -= damage) <= 0)
                {
                    seemsg("The missile kills the %s.", mname);
                    killed(&player, item, NOMESSAGE, POINTS);
                }
                else
                {
                    seemsg("The missile hits the %s", mname);
                    chase_it(&pos, &player);
                    summon_help(tp, NOFORCE);
                }
            }

            if (pos.y >= 0 && pos.x >= 0 &&
                pos.y < LINES && pos.x < COLS)
                mvwaddch(cw, pos.y, pos.x, show(pos.y, pos.x));
        }
        break;

        case WS_HIT:
        {
            coord pos;
            int ch;
            struct object   strike; /* don't want to change sticks attributes */

            if (is_stick)
                know_items[TYP_STICK][which] = TRUE;

            pos = do_motion('@', delta.y, delta.x, &player);
            ch = winat(pos.y, pos.x);

            if (isalpha(ch))
            {
                static char buf[20];
                int nsides, ndice;

                strike = *obj;

                if (blessed)
                    strike.o_hplus = 12;
                else if (cursed)
                    strike.o_hplus = 3;
                else
                    strike.o_hplus = 6;

                if (!is_stick || strcmp(ws_type[which], "staff") == 0)
                    ndice = 3;
                else
                    ndice = 2;

                if (blessed)
                    nsides = 16;
                else if (cursed)
                    nsides = 4;
                else
                    nsides = 8;

                sprintf(buf, "%dd%d", ndice, nsides);

                strike.o_damage = buf;
                fight(&tp->t_pos, &strike, NOTHROWN);
            }
        }
        break;

        case  WS_SLOW_M:
            if (got_one)
            {
                if (cursed)
                {
                    if (off(*tp, ISSLOW))
                        turn_on(*tp, ISHASTE);
                    else
                        turn_off(*tp, ISSLOW);
                }
                else if (blessed)
                {
                    if (is_stick)
                        know_items[TYP_STICK][which] = TRUE;

                    turn_off(*tp, ISRUN);
                    turn_on(*tp, ISHELD);
                    return;
                }
                else
                {
                    if (is_stick)
                        know_items[TYP_STICK][which] = TRUE;

                    if (off(*tp, ISHASTE))
                        turn_on(*tp, ISSLOW);
                    else
                        turn_off(*tp, ISHASTE);

                    tp->t_turn = TRUE;
                }

                delta.y = y;
                delta.x = x;
                chase_it(&delta, &player);
            }
            break;

        case WS_CHARGE:
            if (know_items[TYP_STICK][which] != TRUE && is_stick)
            {
                msg("This is a wand of charging.");
                know_items[TYP_STICK][which] = TRUE;
            }

            if ((nitem = get_item("charge", STICK)) != NULL)
            {
                nobj = OBJPTR(nitem);

                if ((nobj->o_charges == 0) && (nobj->o_which != WS_CHARGE))
                {
                    fix_stick(nobj);

                    if (blessed)
                        nobj->o_charges *= 2;
                    else if (cursed)
                        nobj->o_charges /= 2;
                }
                else
                {
                    if (blessed)
                        nobj->o_charges += 2;
                    else if (cursed)
                        nobj->o_charges -= 1;
                    else
                        nobj->o_charges += 1;
                }
            }
            break;

        case WS_ELECT:
        case WS_FIRE:
        case WS_COLD:
        {
            char    *name = NULL;
            int damage;
            int ndice = 3 + pstats.s_lvl;
            int nsides;

            if (is_stick)
            {
                know_items[TYP_STICK][which] = TRUE;

                if (strcmp(ws_type[which], "staff") == 0)
                    nsides = 8;
                else
                    nsides = 4;
            }
            else
                nsides = 6;

            if (cursed)
                nsides /= 2;
            else if (blessed)
                nsides *= 2;

            switch (which)
            {
                case WS_ELECT:
                    name = "lightning bolt";

                    if (rnd(2))
                        ndice += rnd(obj->o_charges / 10);
                    else
                        nsides += rnd(obj->o_charges / 10);

                    break;

                case WS_FIRE:
                    name = "flame";
                    break;

                case WS_COLD:
                    name = "ice";
                    break;
            }

            damage = roll(ndice, nsides);
            shoot_bolt(&player, hero, delta, POINTS, D_BOLT, name, damage);
        }
        break;

        case WS_ANTIMATTER:
        {
            int m1, m2, x1, y1;
            char    ch;
            struct linked_list  *ll;
            struct thing    *lt;

            if (is_stick)
                know_items[TYP_STICK][which] = TRUE;

            y1 = hero.y;
            x1 = hero.x;

            do
            {
                y1 += delta.y;
                x1 += delta.x;
                ch = winat(y1, x1);
            }
            while (ch == PASSAGE || ch == FLOOR);

            for (m1 = x1 - 1; m1 <= x1 + 1; m1++)
            {
                for (m2 = y1 - 1; m2 <= y1 + 1; m2++)
                {
                    ch = winat(m2, m1);

                    if (m1 == hero.x && m2 == hero.y)
                        continue;

                    if (ch != ' ')
                    {
                        ll = find_obj(m2, m1);

                        while (ll != NULL)
                        {
                            rem_obj(ll, TRUE);
                            ll = find_obj(m2, m1);
                        }

                        ll = find_mons(m2, m1);

                        if (ll != NULL)
                        {
                            lt = THINGPTR(ll);
                            if (on(*lt, CANSELL))
                            {
                                luck++;
                                aggravate();
                            }

                            if (off(*lt, CANINWALL))
                            {
                                check_residue(lt);
                                detach(mlist, ll);
                                discard(ll);
                                mvwaddch(mw, m2, m1, ' ');
                            }
                        }

                        mvaddch(m2, m1, ' ');
                        mvwaddch(cw, m2, m1, ' ');
                    }
                }
            }

            touchwin(cw);
            touchwin(mw);
        }
        break;

        case WS_CONFMON:
        case WS_PARALYZE:
            if (got_one)
            {
                if (save_throw(VS_MAGIC - (blessed ? 5 : (cursed ? -5 : 0)), tp))
                    nothing_message(flags);
                else
                {
                    if (is_stick)
                        know_items[TYP_STICK][which] = TRUE;

                    switch(which)
                    {
                        case WS_CONFMON:
                            seemsg("The %s looks bewildered.", mname);
                            turn_on(*tp, ISHUH);
                            break;

                        case WS_PARALYZE:
                            seemsg("The %s looks stunned.", mname);
                            tp->t_no_move = FREEZETIME;
                            break;
                    }
                }

                delta.y = y;
                delta.x = x;
                chase_it(&delta, &player);
            }
            else
                nothing_message(flags);

            break;

        case WS_XENOHEALING:
        {
            int hpt_gain = roll(zapper->t_stats.s_lvl, (blessed ? 8 : 4));
            int mons_hpt = tp->t_stats.s_hpt;

            if (got_one)
            {
                if (cursed)
                {
                    if (!save_throw(VS_MAGIC, tp))
                    {
                        if ((mons_hpt -= hpt_gain) <= 0) {
                            seemsg("The %s shrivels up and dies!", mname);
                            killed(&player, item, NOMESSAGE, POINTS);
                        }
                        else
                            seemsg("Wounds appear all over the %s.", mname);
                    }
                    else
                        nothing_message(flags);

                    delta.y = y;
                    delta.x = x;
                    chase_it(&delta, &player);
                }
                else
                {
                    if (is_stick)
                        know_items[TYP_STICK][which] = TRUE;

                    mons_hpt = min(mons_hpt + hpt_gain, tp->maxstats.s_hpt);
                    seemsg("The %s appears less wounded!", mname);
                }
            }
            else
                nothing_message(flags);
        }
        break;

        case WS_DISINTEGRATE:
            if (got_one)
            {
                if (cursed)
                {
                    if (on(*tp, ISUNIQUE))
                    {
                        if (on(*tp, CANSUMMON))
                            summon_help(tp, FORCE);
                        else
                            msg("The %s is very upset.", mname);

                        turn_on(*tp, ISHASTE);
                    }
                    else
                    {
                        int m1, m2;
                        coord   mp;
                        struct linked_list  *titem;
                        int ch;
                        struct thing    *th;

                        for (m1 = tp->t_pos.x - 1; m1 <= tp->t_pos.x + 1; m1++)
                        {
                            for (m2 = tp->t_pos.y - 1; m2 <= tp->t_pos.y + 1; m2++)
                            {
                                ch = winat(m2, m1);

                                if (shoot_ok(ch) && ch != PLAYER)
                                {
                                    mp.x = m1;  /* create it */
                                    mp.y = m2;
                                    titem = new_item(sizeof(struct thing));
                                    new_monster(titem, (short) tp->t_index, &mp, NOMAXSTATS);
                                    th = THINGPTR(titem);
                                    turn_on(*th, ISMEAN);
                                    chase_it(&mp, &player);
                                }
                            }
                        }
                    }

                    delta.y = y;
                    delta.x = x;
                    turn_on(*tp, ISMEAN);
                    chase_it(&delta, &player);
                }
                else    /* if its a UNIQUE it might still live */
                {
                    if (is_stick)
                        know_items[TYP_STICK][which] = TRUE;

                    if (on(*tp, ISUNIQUE) &&
                        save_throw(VS_MAGIC - (blessed ? -5 : 0), tp))
                    {
                        tp->t_stats.s_hpt = tp->t_stats.s_hpt / 2 - 1;

                        if (tp->t_stats.s_hpt < 1)
                        {
                            killed(&player, item, NOMESSAGE, POINTS);
                            seemsg("The %s fades away.", mname);
                        }
                        else
                        {
                            delta.y = y;
                            delta.x = x;

                            chase_it(&delta, &player);

                            if (on(*tp, CANSUMMON))
                                summon_help(tp, FORCE);
                            else
                                seemsg("The %s is very upset.", mname);
                        }
                    }
                    else
                    {
                        if (on(*tp, CANSELL))
                        {
                            luck++;
                            aggravate();
                        }
                        seemsg("The %s turns to dust and blows away.", mname);
                        killed(&player, item, NOMESSAGE, POINTS);
                    }
                }
            }
            break;

        case WS_NOTHING:
            nothing_message(flags);
            break;

        case WS_INVIS:
        {
            if (cursed)
            {
                int x1, y1, x2, y2;
                int  zapped = FALSE;

                if ((rp = roomin(hero)) == NULL)
                {
                    x1 = max(hero.x - 1, 0);
                    y1 = max(hero.y - 1, 0);
                    x2 = min(hero.x + 1, COLS - 1);
                    y2 = min(hero.y + 1, LINES - 3);
                }
                else
                {
                    x1 = rp->r_pos.x;
                    y1 = rp->r_pos.y;
                    x2 = rp->r_pos.x + rp->r_max.x;
                    y2 = rp->r_pos.y + rp->r_max.y;
                }

                for (item = mlist; item != NULL; item = next(item))
                {
                    tp = THINGPTR(item);

                    if (tp->t_pos.x >= x1 && tp->t_pos.x <= x2 &&
                        tp->t_pos.y >= y1 && tp->t_pos.y <= y2)
                    {
                        turn_on(*tp, ISINVIS);
                        turn_on(*tp, ISRUN);
                        turn_off(*tp, ISDISGUISE);
                        chase_it(&tp->t_pos, &player);
                        zapped = TRUE;
                    }
                }

                if (zapped)
                    seemsg("The monsters seem to have all disappeared.");
                else
                    nothing_message(flags);
            }
            else
            {
                if (got_one)
                {
                    if (is_stick)
                        know_items[TYP_STICK][which] = TRUE;

                    if (blessed)
                    {
                        turn_off(*tp, ISINVIS);
                        turn_off(*tp, ISSHADOW);
                        seemsg("The %s appears.", mname);
                    }
                    else
                    {
                        turn_on(*tp, ISINVIS);
                        seemsg("The %s disappears.", mname);
                    }
                }
                else
                    nothing_message(flags);
            }
            light(&hero);
        }
        break;

        case WS_BLAST:
        {
            int                  ch;
            struct linked_list  *itm, *ip;
            struct object       *ob;
            struct trap         *trp;

            if (is_stick)
                know_items[TYP_STICK][which] = TRUE;

            itm = spec_item(WEAPON, GRENADE, 0, 0);
            ob = OBJPTR(itm);
            ob->o_count = 1;
            ob->o_flags |= ISKNOW;
            hearmsg("BOOOM!");
            aggravate();
            ob->o_pos.x = hero.x;
            ob->o_pos.y = hero.y;

            for (;;)
            {
                ob->o_pos.y += delta.y;
                ob->o_pos.x += delta.x;

                if (!ce(ob->o_pos, hero) &&
                    cansee(ob->o_pos.y, ob->o_pos.x) &&
                    mvwinch(cw, ob->o_pos.y, ob->o_pos.x) != ' ')
                {
                    mvwaddch(cw, ob->o_pos.y, ob->o_pos.x,
                      show(ob->o_pos.y, ob->o_pos.x));
                }

                if (shoot_ok(ch = winat(ob->o_pos.y, ob->o_pos.x))
                    && ch != DOOR && !ce(ob->o_pos, hero))
                {
                    if (cansee(ob->o_pos.y, ob->o_pos.x) &&
                        ntraps + 1 < 2 * MAXTRAPS &&
                        mvwinch(cw, ob->o_pos.y, ob->o_pos.x) != ' ')
                    {
                        mvwaddch(cw, ob->o_pos.y, ob->o_pos.x,TRAPDOOR);
                        wrefresh(cw);
                    }

                    if (isatrap(ch))
                    {
                        trp = trap_at(ob->o_pos.y, ob->o_pos.x);

                        if (trp != NULL)
                        {
                            trp->tr_type = TRAPDOOR;
                            trp->tr_flags |= ISFOUND;
                            trp->tr_show = TRAPDOOR;
                        }
                    }
                    else if (isalpha(ch))
                        hit_monster(ob->o_pos.y, ob->o_pos.x, ob, &player);
                    else if ((ch == FLOOR || ch == PASSAGE)
                         && ntraps + 1 < 2 * MAXTRAPS)
                    {
                        mvaddch(ob->o_pos.y, ob->o_pos.x, TRAPDOOR);
                        traps[ntraps].tr_type = TRAPDOOR;
                        traps[ntraps].tr_flags = ISFOUND;
                        traps[ntraps].tr_show = TRAPDOOR;
                        traps[ntraps].tr_pos.y = ob->o_pos.y;
                        traps[ntraps++].tr_pos.x = ob->o_pos.x;
                    }
                    else if (ch == POTION || ch == SCROLL || ch == FOOD
                         || ch == WEAPON || ch == RING || ch == ARMOR
                         || ch == STICK || ch == ARTIFACT || ch == GOLD)
                    {
                        ip = find_obj(ob->o_pos.y, ob->o_pos.x);

                        while (ip)     /* BOOM destroys all stacked objects */
                        {
                            rem_obj(ip, TRUE);
                            ip = find_obj(ob->o_pos.y, ob->o_pos.x);
                        }

                        mvaddch(ob->o_pos.y, ob->o_pos.x,
                            (roomin(ob->o_pos) == NULL ? PASSAGE : FLOOR) );
                    }
                    continue;
                }
                break;
            }
            discard(itm);
        }
        break;

        case WS_WEB:
        {
            int ch;
            coord pos;

            if (is_stick)
                know_items[TYP_STICK][which] = TRUE;

            pos = do_motion('#', delta.y, delta.x, &player);
            ch  = winat(pos.y, pos.x);

            if (isalpha(ch))
            {
                if (save_throw(VS_MAGIC, tp))
                    seemsg("The %s evades your web.", mname);
                else
                {
                    seemsg("The %s is webbed.", mname);
                    turn_off(*tp, ISRUN);
                    turn_on(*tp, ISHELD);
                }
            }

            if (pos.y >= 0 && pos.x >= 0 &&
                pos.y < LINES && pos.x < COLS)
                mvwaddch(cw, pos.y, pos.x, show(pos.y, pos.x));
        }
        break;

        case WS_KNOCK:
        case WS_CLOSE:
            if (blessed)    /* Do all doors in room */
            {
                char    new_door = (which == WS_KNOCK ? DOOR : SECRETDOOR);
                struct room *rmp = roomin(hero);

                if (rmp != NULL)
                    for (x = 0; x < rmp->r_nexits; x++)
                    {
                        move(rmp->r_exit[x].y, rmp->r_exit[x].x);
                        addch(new_door);
                    }
                else    /* Do all adjacent doors */
                    for (x = hero.x - 1; x <= hero.x + 1; x++)
                        for (y = hero.y - 1; y <= hero.y + 1; y++)
                            switch(CCHAR( winat(y, x) ))
                            {
                                case DOOR:
                                case SECRETDOOR:
                                    addch(new_door);
                                    break;
                            }
                light(&hero);
            }
            else if (cursed)/* do opposite spell */
                do_zap(zapper, (which == WS_KNOCK ? WS_CLOSE : WS_KNOCK), ISBLESSED);
            else
            {
                coord   zap_loc;
                char    loc;

                zap_loc.y = hero.y + delta.y;
                zap_loc.x = hero.x + delta.x;
                loc = winat(zap_loc.y, zap_loc.x);

                switch (loc)
                {
                    case SECRETDOOR:
                        if (which == WS_KNOCK)
                        {
                            mvaddch(zap_loc.y, zap_loc.x, DOOR);
                            seemsg("A secret door stands revealed before you!");

                            if (is_stick)
                                know_items[TYP_STICK][which] = TRUE;
                        }
                        else
                            nothing_message(flags);
                        break;

                    case DOOR:

                        if (which == WS_CLOSE)
                        {
                            mvaddch(zap_loc.y, zap_loc.x, SECRETDOOR);
                            msg("You sucessfully block off the door.")
;
                            if (is_stick)
                                know_items[TYP_STICK][which] = TRUE;
                        }
                        else
                            nothing_message(flags);

                        break;

                    default:
                        nothing_message(flags);
                }
            }

        break;
        default:
            msg("What a bizarre schtick!");
    }
}

/*
    drain()
        Do drain hit points from player shtick
*/

void
drain(int ymin, int ymax, int xmin, int xmax)
{
    int i, j, cnt;
    struct thing    *ick;
    struct linked_list  *item;

    /* First count how many things we need to spread the hit points among */

    for (cnt = 0, i = ymin; i <= ymax; i++)
        for (j = xmin; j <= xmax; j++)
            if (isalpha(mvwinch(mw, i, j)))
                cnt++;

    if (cnt == 0)
    {
        msg("You have a tingling feeling.");
        return;
    }
    else
        msg("You feel weaker.");

    cnt = pstats.s_hpt / cnt;
    pstats.s_hpt /= 2;

    /* Now zot all of the monsters */

    for (i = ymin; i <= ymax; i++)
        for (j = xmin; j <= xmax; j++)
            if (isalpha(mvwinch(mw, i, j)) && ((item = find_mons(i, j)) != NULL))
            {
                ick = THINGPTR(item);

                if (on(*ick, CANSELL))
                {
                    luck++;
                    aggravate();
                }

                if ((ick->t_stats.s_hpt -= cnt) < 1)
                    killed(&player, item,
                           cansee(i, j) && !on(*ick, ISINVIS), POINTS);
            }
}

/*
    charge_str()
        charge a wand for wizards.
*/

char *
charge_str(struct object *obj, char *buf)
{
    if (buf == NULL)
        return( "[UltraRogue Bug #103]" );

    if (!(obj->o_flags & ISKNOW))
        buf[0] = '\0';
    else if (obj->o_charges == 1)
        sprintf(buf, " [%d charge]", obj->o_charges);
    else
        sprintf(buf, " [%d charges]", obj->o_charges);

    return(buf);
}


/*
    shoot_bolt()
        fires a bolt from the given starting point in the given direction
        struct thing    *shooter;
        coord   start, dir;
        int    get_points;      should player get exp points?
        short   reason;         reason for dying
        char    *name;          fire, nerve, cold, etc
        int damage;             make zapee suffer
*/

void
shoot_bolt(struct thing *shooter, coord start, coord dir, int get_points, int reason, char *name, int damage)
{
    int     ch;
    char    dirch;
    int     change;
    short   y, x;
    coord   pos;
    coord   spotpos[BOLT_LENGTH + 1];
    int     ret_val = FALSE;/* True if breathing monster gets killed */
    struct linked_list  *item;
    struct thing    *tp;
    char    *mname;
    int bounced;    /* where along BOLT_LENGTH it hit a wall */
    int player_damage;  /* damage if player saved */
    int  no_effect;  /* zap does not effect zappee */
    int take_that[BOLT_LENGTH + 1]; /* damage to each monster */

    /* last spot for player */

    debug("%s does %d damage", name, damage);

    switch (dir.y + dir.x)
    {
        case 0:
            dirch = '/';
            break;

        case 1:
        case -1:
            dirch = (dir.y == 0 ? '-' : '|');
            break;

        case 2:
        case -2:
            dirch = '\\';
            break;
    }

    pos.y = start.y + dir.y;
    pos.x = start.x + dir.x;
    change = FALSE;

    for (y = 0; y < BOLT_LENGTH + 1; y++)
        take_that[y] = 0;

    bounced = 0;

    for (y = 0; y < BOLT_LENGTH; y++)
    {
        no_effect = FALSE;
        ch = winat(pos.y, pos.x);
        spotpos[y] = pos;

        /* Bolt damage dimishes over space */

        damage = max(1, damage - (y / 3));

        /* Are we at hero? */

        if (ce(pos, hero))
            goto at_hero;

        switch(ch)
        {
            case SECRETDOOR:
            case '|':
            case '-':
            case ' ':
                bounced = y;
                if (dirch == '-' || dirch == '|')
                {
                    dir.y = -dir.y;
                    dir.x = -dir.x;
                }
                else
                    switch (ch)
                    {
                        case '|':
                        case '-':
                        case SECRETDOOR:
                        {
                            struct room *rp;

                            rp = roomin(pos);

                            if (pos.y == rp->r_pos.y ||
                                pos.y == rp->r_pos.y + rp->r_max.y - 1)
                            {
                                dir.y = -dir.y;
                                change ^= TRUE;
                            }

                            if (pos.x == rp->r_pos.x ||
                                pos.x == rp->r_pos.x + rp->r_max.x - 1)
                            {
                                dir.x = -dir.x;
                                change ^= TRUE;
                            }
                        }
                        default:     /* A wall */
                        {
                            char    chy = CCHAR( mvinch(pos.y - dir.y, pos.x + dir.x)), chx = CCHAR( mvinch(pos.y + dir.y, pos.x - dir.x) );

                            if (chy != WALL && chy != SECRETDOOR &&
                             chy != '-' && chy != '|')
                            {
                                dir.y = -dir.y;
                                change = TRUE;
                            }
                            else if (chx != WALL && chx != SECRETDOOR &&
                                 chx != '-' && chx != '|')
                            {
                                dir.x = -dir.x;
                                change = TRUE;
                            }
                            else
                            {
                                dir.y = -dir.y;
                                dir.x = -dir.x;
                            }
                        }
                    }

                /* Do we change how the bolt looks? */

                if (change)
                {
                    change = FALSE;

                    if (dirch == '\\')
                        dirch = '/';
                    else if (dirch == '/')
                        dirch = '\\';
                }

            break;

            default:
                if (isalpha(ch))    /* hit a monster */
                {
                    item = find_mons(pos.y, pos.x);

                    if (item == NULL)
                    {
                        debug("Can't find monster %c @ %d %d.",
                              ch, pos.y, pos.x);

                        continue;
                    }

                    tp = THINGPTR(item);
                    mname = on(player, ISBLIND) ? "monster" : monsters[tp->t_index].m_name;

                    /* Disguised monsters stay hidden if they save */

                    if (on(*tp, ISDISGUISE) && save_throw(VS_MAGIC, tp) &&
                        (tp->t_type != tp->t_disguise) && off(player, ISBLIND))
                    {
                        msg("Wait! That's a %s!", mname);
                        turn_off(*tp, ISDISGUISE);
                    }

                    tp->t_wasshot = TRUE;

                    if (on(*tp, CANSELL))
                    {
                        luck++;
                        aggravate();
                    }

                    /* Hit the monster -- does it do damage? */

                    if (strcmp(name, "ice") == 0)
                    {
                        if (on(*tp, NOCOLD) || on(*tp, ISUNDEAD))
                            no_effect = TRUE;
                    }
                    else if (strcmp(name, "flame") == 0)
                    {
                        if (on(*tp, NOFIRE))
                            no_effect = TRUE;

                        if (on(*tp, CANBBURN))
                        {
                            seemsg("The %s is burned to death by the flame.",
                                   mname);

                            take_that[y] += tp->t_stats.s_hpt + 1;
                            ret_val = TRUE;
                        }
                    }
                    else if (strcmp(name, "lightning bolt") == 0)
                    {
                        if (on(*tp, NOBOLT))
                            no_effect = TRUE;

                        if (on(*tp, BOLTDIVIDE))
                        {
                            no_effect = TRUE;

                            if (creat_mons(tp, tp->t_index, NOMESSAGE))
                                seemsg("The %s divides the %s.", name, mname);
                        }
                    }

                    if (no_effect == TRUE)
                    {
                        msg("The %s has no effect on the %s.", name,
                            on(player, ISBLIND) ? "monster" : mname);
                    }
                    else
                    {
                        take_that[(bounced ? bounced-- : y)] +=
                            save_throw(VS_MAGIC, tp) ? damage / 2 : damage;
                    }
                }
                else if (pos.y == hero.y && pos.x == hero.x)
                {
        at_hero:
                    player_damage = damage;
                    running = fighting = FALSE;
                    bounced = 0;

                    if (cur_armor != NULL &&
                        cur_armor->o_which == CRYSTAL_ARMOR &&
                        (strcmp(name, "acid") == 0))
                    {
                        player_damage = 0;
                        msg("The acid splashes harmlessly against your armor!");
                    }
                    else if (((cur_armor != NULL &&
                           cur_armor->o_which == CRYSTAL_ARMOR) ||
                          (on(player, ISELECTRIC)) ||
                          is_wearing(R_ELECTRESIST)) &&
                        (strcmp(name, "lightning bolt") == 0))
                    {
                        player_damage = 0;

                        if (rnd(100) < 75
                            && cur_weapon != NULL
                            && shooter != &player)
                        {
                            cur_weapon->o_flags |= ISZAPPED;
                            cur_weapon->o_charges += (10 + rnd(15));
                        }

                        if (cur_weapon != NULL && cur_armor != NULL)
                            msg("Your armor and %s are covered with dancing blue lights!", weaps[cur_weapon->o_which].w_name);
                        else if (cur_armor != NULL)
                            msg("Your armor is covered with dancing blue lights!");
                        else if (cur_weapon != NULL)
                            msg("Your %s is covered with dancing blue lights!", weaps[cur_weapon->o_which].w_name);
                        else
                            msg("You are momentarily covered with dancing blue lights.");
                    }
                    else if ((is_wearing(R_FIRERESIST) || on(player, NOFIRE)) &&
                         (strcmp(name, "flame") == 0))
                    {
                        player_damage = 0;
                        msg("The flame bathes you harmlessly.");
                    }
                    else if ((is_wearing(R_COLDRESIST) || on(player, NOCOLD)) &&
                         (strcmp(name, "ice") == 0))
                    {
                        player_damage = 0;
                        msg("The ice cracks and quickly melts around you.");
                    }
                    else if (save(VS_MAGIC))
                    {
                        take_that[BOLT_LENGTH] += player_damage / 2;
                        debug("Player dodges %s for %d.", name, player_damage / 2);
                    }
                    else
                    {
                        debug("Player zapped by %s for %d.", name, player_damage);
                        take_that[BOLT_LENGTH] += player_damage;
                    }

                    /* Check for gas with special effects */

                    if (off(player, HASOXYGEN) && !is_wearing(R_BREATHE) && !save(VS_BREATH))
                    {
                        if (strcmp(name, "nerve gas") == 0)
                        {
                            if (no_command == 0)
                            {
                                msg("The nerve gas paralyzes you.");
                                no_command = FREEZETIME;
                            }
                        }
                        else if (strcmp(name, "sleeping gas") == 0)
                        {
                            if (no_command == 0)
                            {
                                msg("The sleeping gas puts you to sleep.");
                                no_command = SLEEPTIME;
                            }
                        }
                        else if (strcmp(name, "slow gas") == 0
                             && !is_wearing(R_FREEDOM))
                        {
                            msg("You feel yourself moving %sslower.",
                                on(player, ISSLOW) ? "even " : "");

                            if (on(player, ISSLOW))
                                lengthen_fuse(FUSE_NOSLOW, rnd(10) + 4);
                            else
                            {
                                turn_on(player, ISSLOW);
                                player.t_turn = TRUE;
                                light_fuse(FUSE_NOSLOW, 0, rnd(10) + 4, AFTER);
                            }
                        }
                        else if (strcmp(name, "fear gas") == 0)
                        {
                            if (!(on(player, ISFLEE) &&
                                  (player.t_chasee==shooter)) &&
                                (shooter != &player))
                            {
                                if (off(player, SUPERHERO)
                                    && player.t_ctype != C_PALADIN)
                                {
                                    turn_on(player, ISFLEE);
                                    player.t_chasee = shooter;
                                    player.t_ischasing = FALSE;
                                    msg("The fear gas terrifies you.");
                                }
                                else
                                    msg("The fear gas has no effect.");
                            }
                        }
                    }
                }
                mvwaddch(cw, pos.y, pos.x, dirch);
                wrefresh(cw);
            }
            pos.y += dir.y;
            pos.x += dir.x;
        }

        /*
         * Now that we have determined the damage for the length of the bolt,
         * apply it to each monster (and then the player) in turn
         */

        for (x = 0; x < BOLT_LENGTH; x++)
        {
            ch = winat(spotpos[x].y, spotpos[x].x);

            if (take_that[x] != 0 && isalpha(ch))
            {
                if ((item = find_mons(spotpos[x].y, spotpos[x].x)) == NULL)
                {
                    debug("Can't find monster %c @ %d %d.",
                          ch, spotpos[x].y, spotpos[x].x);

                    continue;
                }
                else
                    tp = THINGPTR(item);

                mname = on(player, ISBLIND) ? "monster" : monsters[tp->t_index].m_name;

                debug("Zapped %s for %d (%d)",
                      mname, take_that[x], tp->t_stats.s_hpt - take_that[x]);

                if ((tp->t_stats.s_hpt -= take_that[x]) <= 0)
                {
                    if (cansee(tp->t_pos.y, tp->t_pos.x))
                        msg("The %s kills the %s.", name,
                            on(player, ISBLIND) ? "monster" : mname);

                    killed(shooter, item, NOMESSAGE, get_points);
                    ret_val = TRUE;
                }
                else if (get_points)
                {
                    chase_it(&spotpos[x], &player);
                    summon_help(tp, NOFORCE);
                }
            }

            if (spotpos[x].y >= 0 && spotpos[x].x >= 0 &&
                spotpos[x].y < LINES && spotpos[x].x < COLS)
                mvwaddch(cw, spotpos[x].y, spotpos[x].x,
                     show(spotpos[x].y, spotpos[x].x));
        }

        if (take_that[BOLT_LENGTH] != 0)
        {
            if (tp != THINGPTR(fam_ptr))
            {
                msg("You are hit by the %s.", name);

                if ((pstats.s_hpt -= take_that[BOLT_LENGTH]) <= 0)
                {
                    death(reason);
                    return;
                }
            }
        }

    return;
}

/*
    monster_do_zap()
        monster gets the effect
*/

void
monster_do_zap(struct thing *zapper, int which, int flags)
{
    struct stats    *curp = &(zapper->t_stats);
    int blessed = flags & ISBLESSED;
    int cursed = flags & ISCURSED;
    int shoot = FALSE;
    int damage;
    int ndice, nsides;
    int ch;
    char *bolt_name = NULL;
    coord pos;

    switch(which)
    {
        case WS_MISSILE:
            bolt_name = "magic missile";
            pos = do_motion('*', delta.y, delta.x, zapper);
            ch = winat(pos.y, pos.x);
            ndice = curp->s_lvl;
            nsides = 4;

            if (cursed)
                nsides /= 2;
            else if (blessed)
                nsides *= 2;

            damage = roll(ndice, nsides);

            if (ch == PLAYER)
            {
                if (save(VS_MAGIC)) /* help the player */
                    msg("You evade the %s.", bolt_name);
                else
                {
                    msg("You are hit by the %s.", bolt_name);

                    if ((pstats.s_hpt -= damage) <= 0)
                        death(D_BOLT);
                }
            }
            else if (isalpha(ch))
            {
                struct linked_list  *item = find_mons(pos.y, pos.x);

                if (item != NULL)
                {
                    struct thing    *tp = THINGPTR(item);
                    int see_it = cansee(pos.y, pos.x);

                    if ((tp->t_stats.s_hpt -= damage) <= 0)
                        killed(zapper, item, see_it, (zapper == THINGPTR(fam_ptr)));
                    else if (see_it)
                        msg("The %s hits the monster.", bolt_name);
                }
            }

            if (pos.y >= 0 && pos.x >= 0 &&
                pos.y < LINES && pos.x < COLS)
                mvwaddch(cw, pos.y, pos.x, show(pos.y, pos.x));

            break;

        case WS_CANCEL:
            cancel_player(off(*zapper, ISUNIQUE));
            break;

        case WS_COLD:
            shoot = TRUE;
            bolt_name = "cold";
            break;

        case WS_FIRE:
            shoot = TRUE;
            bolt_name = "fire";
            break;

        case WS_ELECT:
            shoot = TRUE;
            bolt_name = "lightning";
            break;

        default:
            debug("'%s' is a strange stick for a monster to zap!",
                  ws_magic[which].mi_name);
            break;
    }

    if (shoot)
    {
        ndice = curp->s_lvl / 2 + 1;
        nsides = 6;

        if (cursed)
            nsides /= 2;
        else if (blessed)
            nsides *= 2;

        damage = roll(ndice, nsides);
        shoot_bolt(zapper, zapper->t_pos, delta,
            (zapper == THINGPTR(fam_ptr)), D_BOLT, bolt_name, damage);
    }
}

struct powers
{
    unsigned long p_flag;
    int fuse_id;
};

/* Order in which powers will attempt to be cancelled */

struct powers player_powers[] =
{
    { ISHASTE,    FUSE_NOHASTE     },
    { ISELECTRIC, FUSE_UNELECTRIFY },
    { CANINWALL,  FUSE_UNPHASE     },
    { CANFLY,     FUSE_UNFLY       },
    { ISINVIS,    FUSE_APPEAR      },
    { CANREFLECT, FUSE_UNGAZE      },
    { ISDISGUISE, FUSE_UNDISGUISE  },
    { HASMSHIELD, FUSE_UNMSHIELD   },
    { ISREGEN,    FUSE_UNREGEN     },
    { CANSEE,     FUSE_UNSEE       },
    { NOCOLD,     FUSE_UNCOLD      },
    { NOFIRE,     FUSE_UNHOT       },
    { HASOXYGEN,  FUSE_UNBREATHE   },
    { HASSHIELD,  FUSE_UNSHIELD    },
    { ULONG_MAX,         FUSE_NULL        }
};

void
cancel_player(int not_unique)
{
    struct powers   *pp;
    int no_effect = TRUE;

    for(pp = player_powers; pp->p_flag != ULONG_MAX; pp++)
    {
        if (on(player, pp->p_flag) && !save(VS_MAGIC))
        {
            fuses[pp->fuse_id].func(NULL);
            extinguish_fuse(pp->fuse_id);
            no_effect = FALSE;

            if (not_unique) /* Gods might cancel everything */
                break;
        }
    }

    if (no_effect)
        nothing_message(ISNORMAL);
}
