view urogue/sticks.c @ 302:fa70bba6bb3f rel2021.03

Update the README.
author John "Elwin" Edwards
date Thu, 18 Mar 2021 20:53:49 -0400
parents c495a4f288c6
children e52a8a7ad4c5
line wrap: on
line source

/*
    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);