view xrogue/misc.c @ 314:2f0eb38da609

Advanced Rogue 7: fix a crash when casting magic missile. do_zap() dereferenced a struct object pointer while ignoring the result of a NULL check. XRogue had the same problem, though triggering it was unlikely, since XRogue does not include a magic missile spell. Reported by John Harris of @Play.
author John "Elwin" Edwards
date Thu, 21 Oct 2021 21:00:15 -0400
parents e1cd27c5464f
children
line wrap: on
line source

/*
    misc.c - routines dealing specifically with miscellaneous magic
    
    XRogue: Expeditions into the Dungeons of Doom
    Copyright (C) 1991 Robert Pietkivitch
    All rights reserved.
    
    Based on "Advanced Rogue"
    Copyright (C) 1984, 1985 Michael Morgan, Ken Dalka and AT&T
    All rights reserved.

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

#include <stdlib.h>
#include <curses.h>
#include <ctype.h>
#include <string.h>
#include "rogue.h"

/*
 * changeclass:
 *      Change the player's class to the specified one.
 */

void
changeclass(int newclass)
{
    if (newclass == player.t_ctype) {
        msg("You feel more skillful.");
        raise_level();
    }
    else {
        /*
         * reset his class and then use check_level to reset hit
         * points and the right level for his exp pts
         * drop exp pts by 10%
         */
        long save;

        msg("You are transformed into a %s! ", char_class[newclass].name);

        /*
         * if he becomes a thief or an assassin give him studded leather armor
         */
        if ((newclass == C_THIEF || newclass == C_ASSASSIN) &&
            cur_armor != NULL && cur_armor->o_which != STUDDED_LEATHER)
                cur_armor->o_which = STUDDED_LEATHER;
        /*
         * if he becomes a monk he can't wear any armor
     * so give him a cloak of protection
         */
        if (newclass == C_MONK && cur_armor != NULL) {
                cur_armor->o_ac = armors[cur_armor->o_which].a_class - 
                                  cur_armor->o_ac;
                cur_armor->o_type = MM;
                cur_armor->o_which = MM_PROTECT;
                cur_armor->o_flags &= ~(ISPROT | ISKNOW);
                cur_misc[WEAR_CLOAK] = cur_armor;
                cur_armor = NULL;
        }
    /*
     * otherwise give him plate armor
     */
        if ((newclass != C_THIEF ||
         newclass != C_ASSASSIN || newclass != C_MONK) &&
         cur_armor != NULL && cur_armor->o_which != PLATE_ARMOR) 
                cur_armor->o_which = PLATE_ARMOR;

        /*
         * if he used to be a spell caster of some sort, kill the fuse
         */
        if (player.t_ctype == C_MAGICIAN || player.t_ctype == C_RANGER)
                extinguish(spell_recovery);
        if (player.t_ctype == C_DRUID || player.t_ctype == C_MONK)
                extinguish(chant_recovery);
        if ((player.t_ctype == C_CLERIC || player.t_ctype == C_PALADIN) &&
             !cur_relic[HEIL_ANKH])
                extinguish(prayer_recovery);

        /*
         * if he becomes a spell caster of some kind, give him a fuse
         */
        if (newclass == C_MAGICIAN || newclass == C_RANGER)
                fuse(spell_recovery, NULL, SPELLTIME, AFTER);
        if (newclass == C_DRUID || newclass == C_MONK)
                fuse(chant_recovery, NULL, SPELLTIME, AFTER);
        if ((newclass==C_CLERIC || newclass==C_PALADIN) && !cur_misc[HEIL_ANKH])
                fuse(prayer_recovery, NULL, SPELLTIME, AFTER);
        /*
         * if he's changing from a fighter, ranger, or paladin then we
     * may have to change his sword since only these types can wield
     * the two-handed sword.
         */
        if ((player.t_ctype == C_FIGHTER ||
        player.t_ctype == C_RANGER   ||
            player.t_ctype == C_PALADIN) &&
        cur_weapon != NULL && cur_weapon->o_type == WEAPON &&
            (cur_weapon->o_which == BASWORD  ||
        cur_weapon->o_which == TWOSWORD) &&
            !(newclass == C_FIGHTER || newclass == C_RANGER ||
            newclass == C_PALADIN)  &&
        cur_weapon->o_which == TWOSWORD)
                cur_weapon->o_which = SWORD;

        /*
         * if he's changing from a thief, assassin, fighter, or monk
     * then we may have to change his sword again since only these
     * types can wield the bastard sword.
         */
        if ((player.t_ctype == C_THIEF  || player.t_ctype == C_ASSASSIN ||
            player.t_ctype == C_FIGHTER || player.t_ctype == C_MONK)    &&
        cur_weapon != NULL && cur_weapon->o_type == WEAPON &&
            (cur_weapon->o_which == BASWORD  ||
            cur_weapon->o_which == TWOSWORD) &&
            !(newclass == C_THIEF || newclass == C_ASSASSIN ||
            newclass == C_MONK)   &&
        cur_weapon->o_which == BASWORD)
                cur_weapon->o_which = SWORD;

        /*
         * if he was a thief, assassin, or monk then take out
     * the trap_look() daemon
         */
        if (player.t_ctype == C_THIEF || player.t_ctype == C_MONK ||
            player.t_ctype == C_ASSASSIN)
                kill_daemon(trap_look);

        /*
         * if he becomes a thief, assassin, or monk then add 
     * the trap_look() daemon
         */
        if (newclass == C_THIEF || newclass == C_ASSASSIN ||
        newclass == C_MONK)
                start_daemon(trap_look, NULL, AFTER);

    /* adjust stats */
        char_type = player.t_ctype = newclass;
        save = pstats.s_hpt;
        max_stats.s_hpt = pstats.s_hpt = 0;
        max_stats.s_lvl = pstats.s_lvl = 0; 
        max_stats.s_lvladj = pstats.s_lvladj = 0; 
        max_stats.s_exp = pstats.s_exp + rnd(4);
        check_level();
        if (pstats.s_hpt > save) /* don't add to current hits */
            pstats.s_hpt = save;
    }
    dsrpt_player(); /* this should disrupt whatever we were doing */
}

/*
 * Use the relic that our monster is wielding.
 */

void
m_use_relic(struct thing *monster)
{
    register struct object *obj;

    /* Make sure we really have it */
    if (monster->t_using) obj = OBJPTR(monster->t_using);
    else {
        debug("Relic not set!");
        monster->t_action = A_NIL;
        return;
    }

    /* Now let's see what we're using */
    if (obj->o_type == RELIC) switch (obj->o_which) {
        case MING_STAFF: {
            static struct object missile = {
              MISSILE, {0,0}, 0, "", "0d4 " , NULL, 0, WS_MISSILE, 100, 1
            };

            debug("Firing Ming's staff");
            sprintf(missile.o_hurldmg, "%dd4", monster->t_stats.s_lvl);
            do_motion(&missile,
                       monster->t_newpos.y, monster->t_newpos.x, monster);
            hit_monster(unc(missile.o_pos), &missile, monster);
            monster->t_artifact = monster->t_artifact * 4 / 5;
        }
        when EMORI_CLOAK:
            debug("stunning with Emori's cloak");
            do_zap(monster, obj, &monster->t_newpos, WS_PARALYZE, 0);
            obj->o_charges = 0;

        when ASMO_ROD: {
            char *name;

            switch (rnd(3)) { /* Select a function */
                case 0:    name = "lightning bolt";
                when 1:    name = "flame";
                otherwise: name = "ice";
            }
            shoot_bolt( monster, 
                        monster->t_pos, 
                        monster->t_newpos, 
                        FALSE, 
                        monster->t_index, 
                        name, 
                        roll(monster->t_stats.s_lvl,6));
            monster->t_artifact /= 2;
        }
        when BRIAN_MANDOLIN:
            /* Make sure the defendant is still around */
            if (DISTANCE(monster->t_pos.y, monster->t_pos.x,
                         hero.y, hero.x) < 25) {
                if (!save(VS_MAGIC, &player, -4) &&
                    !ISWEARING(R_ALERT)) {
                    msg("Some beautiful music enthralls you.");
                    player.t_no_move += movement(&player) * FREEZETIME;
                    player.t_action = A_FREEZE;
                    monster->t_artifact = monster->t_artifact * 2 / 3;
                }
                else {
                    msg("You wince at a sour note.");
                    monster->t_artifact /= 3;
                }
            }
        when GERYON_HORN:
            /* Make sure the defendant is still around */
            if (DISTANCE(monster->t_pos.y, monster->t_pos.x,
                         hero.y, hero.x) < 25) {
                if (!ISWEARING(R_HEROISM) &&
                    !save(VS_MAGIC, &player, -4)) {
                        turn_on(player, ISFLEE);
                        player.t_dest = &monster->t_pos;
                        msg("A shrill blast terrifies you.");
                        monster->t_artifact = monster->t_artifact * 3 / 4;
                }
                else  {
                    msg("A shrill blast sends chills up your spine! ");
                    monster->t_artifact /= 3;
                }
            }

        otherwise:
            /* Unknown RELIC! */
            debug("Unknown wielded relic %d", obj->o_which);
    }
    else debug("Declared relic is %d", obj->o_type);

    turn_off(*monster, CANSURPRISE);
    /* Reset the monsters actions */
    monster->t_action = A_NIL;
    monster->t_using = NULL;
}
 
/*
 * add something to the contents of something else
 * bag: the holder of the items
 * item: the item to put inside
 */

void
put_contents(struct object *bag, struct linked_list *item)
{
    register struct linked_list *titem;
    register struct object *tobj;

    bag->o_ac++;
    tobj = OBJPTR(item);
    for (titem = bag->contents; titem != NULL; titem = next(titem)) {
        if ((OBJPTR(titem))->o_which == tobj->o_which)
            break;
    }
    if (titem == NULL) {        /* if not a duplicate put at beginning */
        attach(bag->contents, item);
    }
    else {
        item->l_prev = titem;
        item->l_next = titem->l_next;
        if (next(titem) != NULL) 
            (titem->l_next)->l_prev = item;
        titem->l_next = item;
    }
}

/*
 * remove something from something else
 * bag: the holder of the items
 */

void
take_contents(struct object *bag, struct linked_list *item)
{

    if (bag->o_ac <= 0) {
        msg("Nothing to take out");
        return;
    }
    bag->o_ac--;
    detach(bag->contents, item);
    if (!add_pack(item, FALSE))
        put_contents(bag, item);
}


void
do_bag(struct linked_list *item)
{

    register struct linked_list *titem = NULL;
    register struct object *obj, *tobj;
    bool doit = TRUE;

    obj = OBJPTR(item);
    while (doit) {
        msg("What do you want to do? (* for a list): ");
        mpos = 0;
        switch (wgetch(cw)) {
            case EOF:
            case ESC:
                msg ("");
                doit = FALSE;
            when '1':
                inventory(obj->contents, ALL);

            when '2':
                if (obj->o_ac >= MAXCONTENTS) {
                    msg("the %s is full", m_magic[obj->o_which].mi_name);
                    break;
                }
                switch (obj->o_which) {
                case MM_BEAKER:
                    titem = get_item(pack, "put in", POTION, FALSE, FALSE);
                when MM_BOOK:
                    titem = get_item(pack, "put in", SCROLL, FALSE, FALSE);
                }
                if (titem == NULL)
                    break;
                detach(pack, titem);
                inpack--;
                put_contents(obj, titem);
            
            when '3':
                titem = get_item(obj->contents,"take out",ALL,FALSE,FALSE);
                if (titem == NULL)
                    break;
                take_contents(obj, titem);
                
            when '4': 
                switch (obj->o_which) {
                case MM_BEAKER:
                    titem = get_item(obj->contents,"quaff",ALL,FALSE,FALSE);
                    if (titem == NULL)
                        break;
                    tobj = OBJPTR(titem);
                    obj->o_ac--;
                    detach(obj->contents, titem);
                    quaff(tobj->o_which, 
                          tobj->o_kind,
                          tobj->o_flags,
                          TRUE);
                    if (p_know[tobj->o_which] && p_guess[tobj->o_which])
                    {
                        free(p_guess[tobj->o_which]);
                        p_guess[tobj->o_which] = NULL;
                    }
                    else if (!p_know[tobj->o_which]             && 
                             askme                              &&
                             (tobj->o_flags & ISKNOW) == 0      &&
                             (tobj->o_flags & ISPOST) == 0      &&
                             p_guess[tobj->o_which] == NULL) {
                        nameitem(titem, FALSE);
                    }
                    o_discard(titem);
                when MM_BOOK:   
                    if (on(player, ISBLIND)) {
                        msg("You can't see to read anything! ");
                        break;
                    }
                    titem = get_item(obj->contents,"read",ALL,FALSE,FALSE);
                    if (titem == NULL)
                        break;
                    tobj = OBJPTR(titem);
                    obj->o_ac--;
                    detach(obj->contents, titem);
                    read_scroll(tobj->o_which, 
                                tobj->o_flags & (ISCURSED|ISBLESSED),
                                TRUE);
                    if (s_know[tobj->o_which] && s_guess[tobj->o_which])
                    {
                        free(s_guess[tobj->o_which]);
                        s_guess[tobj->o_which] = NULL;
                    }
                    else if (!s_know[tobj->o_which]             && 
                             askme                              &&
                             (tobj->o_flags & ISKNOW) == 0      &&
                             (tobj->o_flags & ISPOST) == 0      &&
                             s_guess[tobj->o_which] == NULL) {
                        nameitem(titem, FALSE);
                    }
                    o_discard(titem);
                }
                doit = FALSE;

            otherwise:
                wclear(hw);
                touchwin(hw);
                mvwaddstr(hw,0,0,"The following operations are available:");
                mvwaddstr(hw,2,0,"[1]\tInventory\n");
                wprintw(hw,"[2]\tPut something in the %s\n",
                        m_magic[obj->o_which].mi_name);
                wprintw(hw,"[3]\tTake something out of the %s\n",
                        m_magic[obj->o_which].mi_name);
                switch(obj->o_which) {
                    case MM_BEAKER: waddstr(hw,"[4]\tQuaff a potion\n");
                    when MM_BOOK:   waddstr(hw,"[4]\tRead a scroll\n");
                }
        /* this is confusing! <press space to continue> */
                /* waddstr(hw,"[ESC]\tLeave this menu\n"); */
                mvwaddstr(hw, lines-1, 0, spacemsg);
                draw(hw);
                wait_for (' ');
                restscr(cw);
        }
    }
}

void
do_panic(int who)
{
    /* who: kind of monster to panic (all if who is 0) */
    register int x,y;
    register struct linked_list *mon, *item;
    register struct thing *th;

    for (x = hero.x-2; x <= hero.x+2; x++) {
        for (y = hero.y-2; y <= hero.y+2; y++) {
            if (y < 1 || x < 0 || y > lines - 3  || x > cols - 1) 
                continue;
            if (isalpha(mvwinch(mw, y, x))) {

                if ((mon = find_mons(y, x)) != NULL) {
                    th = THINGPTR(mon);

                    /* Is this the right kind of monster to panic? */
                    if (who && th->t_index != who) continue;

                    if ((who && th->t_stats.s_intel < 14) || 
                        (!on(*th, ISUNDEAD) && !save(VS_MAGIC, th, 0) &&
              off(*th, WASTURNED))) {
                          msg("%s %s.", prname(monster_name(th), TRUE),
                            terse ? "panics" : "turns to run in panic");

                        turn_on(*th, ISFLEE);
                        turn_on(*th, WASTURNED);
                        turn_off(*th, CANSURPRISE);

                        /* Disrupt what it was doing */
                        dsrpt_monster(th, TRUE, TRUE);

                        /* If monster was suffocating, stop it */
                        if (on(*th, DIDSUFFOCATE)) {
                            turn_off(*th, DIDSUFFOCATE);
                            extinguish(suffocate);
                        }

                        /* If monster held us, stop it */
                        if (on(*th, DIDHOLD) && (--hold_count == 0))
                                turn_off(player, ISHELD);
                        turn_off(*th, DIDHOLD);

                        /*
                         * if he has something he might drop it
                         */
                        if ((item = th->t_pack) != NULL         && 
                            (OBJPTR(item))->o_type != RELIC     && 
                            rnd(100) < 67) {
                                detach(th->t_pack, item);
                                fall(item, FALSE);
                        }

                        /* It is okay to turn tail */
                        th->t_oldpos = th->t_pos;
                    }
                    runto(th, &hero);
                }
            }
        }
    }
}

/*
 * print miscellaneous magic bonuses
 */

int
misc_name(char *str, struct object *obj)
{
    char buf1[LINELEN];

    *str = 0;
    buf1[0] = 0;

    if (!(obj->o_flags & ISKNOW))
    {
        strcat(str,m_magic[obj->o_which].mi_name);
        return(0);
    }

    switch (obj->o_which) 
    {
        case MM_BRACERS:
        case MM_PROTECT:
            strcat(str, num(obj->o_ac, 0));
            strcat(str, " ");
    }
    switch (obj->o_which) {
    case MM_CRYSTAL:
            if (obj->o_flags & ISBLESSED)
                strcat(str, "glowing ");
    }
    switch (obj->o_which) {
        case MM_G_OGRE:
        case MM_G_DEXTERITY:
        case MM_JEWEL:
        case MM_STRANGLE:
        case MM_R_POWERLESS:
        case MM_DANCE:
            if (obj->o_flags & ISCURSED)
                strcat(str, "cursed ");
        when MM_CRYSTAL:
            if (obj->o_flags & ISCURSED)
                strcat(str, "opaque ");
    }
    strcat(str, m_magic[obj->o_which].mi_name);

    switch (obj->o_which) 
    {
        case MM_JUG:
            if (obj->o_ac == JUG_EMPTY)
                strcat(buf1, " [empty]");
            else if (p_know[obj->o_ac])
                sprintf(buf1, " [containing a potion of %s (%s)]",
                        p_magic[obj->o_ac].mi_name,
                        p_colors[obj->o_ac]);
            else sprintf(buf1, " [containing a%s %s liquid]", 
                        vowelstr(p_colors[obj->o_ac]),
                        p_colors[obj->o_ac]);
        when MM_BEAKER:         
        case MM_BOOK: {
            sprintf(buf1, " [containing %d]", obj->o_ac);
        }
        when MM_OPEN:
        case MM_HUNGER:
            sprintf(buf1, " [%d ring%s]", obj->o_charges, 
                          obj->o_charges == 1 ? "" : "s");
        when MM_DRUMS:
            sprintf(buf1, " [%d beat%s]", obj->o_charges, 
                          obj->o_charges == 1 ? "" : "s");
        when MM_DISAPPEAR:
        case MM_CHOKE:
            sprintf(buf1, " [%d pinch%s]", obj->o_charges, 
                          obj->o_charges == 1 ? "" : "es");
        when MM_KEOGHTOM:
            sprintf(buf1, " [%d application%s]", obj->o_charges, 
                          obj->o_charges == 1 ? "" : "s");
        when MM_SKILLS:
            sprintf(buf1, " [%s]", char_class[obj->o_ac].name);
    }
    strcat(str, buf1);
    
    return(0);
}

void
use_emori(void)
{
    char selection;     /* Cloak function */
    int state = 0;      /* Menu state */

    msg("What do you want to do? (* for a list): ");
    do {
        selection = wgetch(cw);
        switch (selection) {
            case '*':
              if (state != 1) {
                wclear(hw);
                touchwin(hw);
                mvwaddstr(hw, 2, 0,  "[1] Fly\n[2] Stop flying\n");
                waddstr(hw,          "[3] Turn invisible\n[4] Turn Visible\n");
                mvwaddstr(hw, 0, 0, "What do you want to do? ");
                draw(hw);
                state = 1;      /* Now in prompt window */
              }
              break;

            case ESC:
                if (state == 1) {
                    restscr(cw);
                }
                msg("");

                after = FALSE;
                return;

            when '1':
            case '2':
            case '3':
            case '4':
                if (state == 1) {       /* In prompt window */
                    restscr(cw);
                }

                msg("");

                state = 2;      /* Finished */
                break;

            default:
                if (state == 1) {       /* In the prompt window */
                    mvwaddstr(hw, 0, 0,
                                "Please enter a selection between 1 and 4:  ");
                    draw(hw);
                }
                else {  /* Normal window */
                    mpos = 0;
                    msg("Please enter a selection between 1 and 4:  ");
                }
        }
    } while (state != 2);

    /* We now must have a selection between 1 and 4 */
    switch (selection) {
        case '1':       /* Fly */
            if (on(player, ISFLY)) {
                extinguish(land);       /* Extinguish in case of potion */
                msg("%slready flying.", terse ? "A" : "You are a");
            }
            else {
                msg("You feel lighter than air!");
                turn_on(player, ISFLY);
            }
        when '2':       /* Stop flying */
            if (off(player, ISFLY))
                msg("%sot flying.", terse ? "N" : "You are n");
            else {
                if (find_slot(land))
                    msg("%sot flying by the cloak.",
                        terse ? "N" : "You are n");
                else land();
            }
        when '3':       /* Turn invisible */
            if (off(player, ISINVIS)) {
                turn_on(player, ISINVIS);
                msg("You have a tingling feeling all over your body. ");
                PLAYER = IPLAYER;
                light(&hero);
            }
            else {
                extinguish(appear);     /* Extinguish in case of potion */
                extinguish(dust_appear);/* dust of disappearance        */
                msg("%slready invisible.", terse ? "A" : "You are a");
            }
        when '4':       /* Turn visible */
            if (off(player, ISINVIS))
                msg("%sot invisible.", terse ? "N" : "You are n");
            else {
                if (find_slot(appear) || find_slot(dust_appear))
                    msg("%sot invisible by the cloak.",
                        terse ? "N" : "You are n");
                else appear();
            }
    }
}

/*
 * try to write a scroll with the quill of Nagrom
 */

void
use_quill(struct object *obj)
{
    struct linked_list  *item;
    register int        i,
                        scroll_ability;
    int                 which_scroll,
                        curlen,
                        maxlen = 0,
                        dummy = 0;
    bool                nohw = FALSE;

    i = which_scroll = 0;
    scroll_ability = obj->o_charges;

    /* Prompt for scrolls */
    msg("Which scroll are you writing? (* for list): ");

    which_scroll = (int) (wgetch(cw) - 'a');
    msg("");    /* Get rid of the prompt */
    if (which_scroll == (int) ESC - (int) 'a') {
        after = FALSE;
        return;
    }
    if (which_scroll >= 0 && which_scroll < MAXQUILL) nohw = TRUE;

    else if (slow_invent) {
        register char c;

        nohw = TRUE;
        do {
            for (i=0; i<MAXQUILL; i++) {
                msg("");
                mvwaddch(msgw, 0, 0, '[');
                waddch(msgw, (char) ((int) 'a' + i));
                waddstr(msgw, "] A scroll of ");
                waddstr(msgw, s_magic[quill_scrolls[i].s_which].mi_name);
                waddstr(msgw, morestr);
                clearok(msgw, FALSE);
                draw(msgw);
                do {
                    c = wgetch(cw);
                } while (c != ' ' && c != ESC);
                if (c == ESC)
                    break;
            }
            msg("");
            mvwaddstr(msgw, 0, 0, "Which scroll are you writing? ");
            clearok(msgw, FALSE);
            draw(msgw);

            which_scroll = (int) (wgetch(cw) - 'a');
        } while (which_scroll != (int) (ESC - 'a') &&
                 (which_scroll < 0 || which_scroll >= MAXQUILL));

        if (which_scroll == (int) (ESC - 'a')) {
            mpos = 0;
            msg("");
            after = FALSE;
            return;
        }
    }
    else {
        /* Now display the possible scrolls */
        wclear(hw);
        touchwin(hw);
        mvwaddstr(hw, 2, 0, "   Cost            Scroll");
        mvwaddstr(hw, 3, 0,
                "-----------------------------------------------");
        maxlen = 47;    /* Maximum width of header */

        for (i=0; i<MAXQUILL; i++) {
            wmove(hw, i+4, 0);
            sprintf(prbuf, "[%c]        %3d     A scroll of %s",
                    (char) ((int) 'a' + i),
                    quill_scrolls[i].s_cost,
                    s_magic[quill_scrolls[i].s_which].mi_name);
            waddstr(hw, prbuf);

            /* Get the length of the line */
            getyx(hw, dummy, curlen);
            if (maxlen < curlen) maxlen = curlen;
        }

        sprintf(prbuf, "[Current scroll power = %d]", scroll_ability);
        mvwaddstr(hw, 0, 0, prbuf);
        waddstr(hw, " Which scroll are you writing? ");
        getyx(hw, dummy, curlen);
        if (maxlen < curlen) maxlen = curlen;

        /* Should we overlay? */
        if (menu_overlay && MAXQUILL + 3 < lines - 3) {
            over_win(cw, hw, MAXQUILL + 5, maxlen + 3, 0, curlen, '\0');
        }
        else draw(hw);
    }

    if (!nohw) {
        which_scroll = (int) (wgetch(cw) - 'a');
        while (which_scroll < 0 || which_scroll >= MAXQUILL) {