view urogue/options.c @ 298:5a94c9b3181e

UltraRogue: clear the next_obj field when removing items from the floor. The next_obj field is a pointer which the top item in a stack uses to keep a list of the other items. When removing an item from the stack, rem_obj() failed to set next_obj to NULL, which can cause items in monster inventory to point to items earlier in the inventory list. That causes infinite co-recursion when saving or restoring.
author John "Elwin" Edwards
date Thu, 08 Feb 2018 20:54:34 -0500
parents 9dcf3344d3fd
children 0250220d8cdd
line wrap: on
line source

/*
    options.c - This file has all the code for the option command
        
    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 <string.h>
#include <ctype.h>
#include "rogue.h"

#define NUM_OPTS    (sizeof optlist / sizeof (OPTION))
#define EQSTR(a, b, c)  (strncmp(a, b, c) == 0)

int get_restr(opt_arg *opt, WINDOW *win);
int get_score(opt_arg *opt, WINDOW *win);

/* description of an option and what to do with it */
static OPTION optlist[] =
{
{"jump","Show position only at end of run (jump): ", &jump,put_bool,get_bool},
{"inven","Style of inventories (inven): ", &inv_type, put_inv, get_inv},
{"askme","Ask me about unidentified things (askme): ",&askme,put_bool,get_bool},
{"doorstop","Stop running when adjacent (doorstop): ",&doorstop,put_bool,get_bool},
{"name", "Name (name): ",               &whoami, put_str, get_restr},
{"fruit", "Fruit (fruit): ",            &fruit, put_str, get_str},
{"file", "Save file (file): ",          &file_name, put_str, get_restr},
{"score", "Score file (score): ",       &score_file, put_str, get_score},
{"class", "Character class (class): ",&char_type, put_abil, get_abil}
};

/*
    option()
        print and then set options from the terminal
*/

void
option(void)
{
    OPTION  *op;
    int retval;

    wclear(hw);
    touchwin(hw);

    /* Display current values of options */

    for (op = optlist; op < &optlist[NUM_OPTS]; op++)
    {
        waddstr(hw, op->o_prompt);
        (*op->o_putfunc)(&op->o_opt, hw);
        waddch(hw, '\n');
    }

    /* Set values */

    wmove(hw, 0, 0);

    for (op = optlist; op < &optlist[NUM_OPTS]; op++)
    {
        waddstr(hw, op->o_prompt);

	retval = (*op->o_getfunc)(&op->o_opt, hw);

        if (retval)
            if (retval == QUIT)
                break;
            else if (op > optlist)      /* MINUS */
            {
                wmove(hw, (int)(op - optlist) - 1, 0);
                op -= 2;
            }
            else    /* trying to back up beyond the top */
            {
                putchar('\007');
                wmove(hw, 0, 0);
                op--;
            }
    }

    /* Switch back to original screen */

    mvwaddstr(hw, LINES - 1, 0, spacemsg);
    wrefresh(hw);
    wait_for(' ');
    clearok(cw, TRUE);
    touchwin(cw);
}

/*
    put_bool()
        put out a boolean
*/

void
put_bool(opt_arg *opt, WINDOW *win)
{
    waddstr(win, *opt->iarg ? "True" : "False");
}

/*
    put_str()
        put out a string
*/

void
put_str(opt_arg *opt, WINDOW *win)
{
    waddstr(win, opt->str);
}

/*
    put_abil()
        print the character type
*/

void
put_abil(opt_arg *opt, WINDOW *win)
{
    char    *abil;

    switch(*opt->iarg)
    {
        case C_FIGHTER:
            abil = "Fighter";
            break;
        case C_MAGICIAN:
            abil = "Magic User";
            break;
        case C_CLERIC:
            abil = "Cleric";
            break;
        case C_THIEF:
            abil = "Thief";
            break;
        case C_PALADIN:
            abil = "Paladin";
            break;
        case C_RANGER:
            abil = "Ranger";
            break;
        case C_ILLUSION:
            abil = "Illusionist";
            break;
        case C_ASSASIN:
            abil = "Assasin";
            break;
        case C_NINJA:
            abil = "Ninja";
            break;
        case C_DRUID:
            abil = "Druid";
            break;
        default:
            abil = "(unknown)";
    }
    waddstr(win, abil);
}


/*
    get_bool()
        allow changing a boolean option and print it out
*/

int
get_bool(opt_arg *opt, WINDOW *win)
{
    int oy, ox;
    int op_bad;

    op_bad = TRUE;
    getyx(win, oy, ox);
    waddstr(win, *opt->iarg ? "True" : "False");

    while(op_bad)
    {
        wmove(win, oy, ox);
        wrefresh(win);

        switch (readcharw(win))
        {
            case 't':
            case 'T':
                *opt->iarg = TRUE;
                op_bad = FALSE;
                break;

            case 'f':
            case 'F':
                *opt->iarg = FALSE;
                op_bad = FALSE;
                break;

            case '\n':
            case '\r':
                op_bad = FALSE;
                break;

            case '\033':
            case '\007':
                return QUIT;

            case '-':
                return MINUS;

            default:
                mvwaddstr(win, oy, ox + 10, "(T or F)");
        }
    }

    wmove(win, oy, ox);
    wclrtoeol(win);
    waddstr(win, *opt->iarg ? "True" : "False");
    waddch(win, '\n');

    return(NORM);
}

/*
    get_str()
        set a string option
*/

int
get_str(opt_arg *opt, WINDOW *win)
{
    return( get_string(opt->str, win) );
}

/*
    get_abil()
        The ability field is read-only
*/

int
get_abil(opt_arg *opt, WINDOW *win)
{
    int oy, ox, ny, nx;
    int op_bad;

    op_bad = TRUE;
    getyx(win, oy, ox);
    put_abil(opt, win);
    getyx(win, ny, nx);

    while(op_bad)
    {
        wmove(win, oy, ox);
        wrefresh(win);

        switch(readcharw(win))
        {
            case '\n':
            case '\r':
                op_bad = FALSE;
                break;

            case '\033':
            case '\007':
                return(QUIT);

            case '-':
                return(MINUS);

            default:
                mvwaddstr(win, ny, nx + 5, "(no change allowed)");
        }
    }

    wmove(win, ny, nx + 5);
    wclrtoeol(win);
    wmove(win, ny, nx);
    waddch(win, '\n');

    return(NORM);
}


/*
    parse_opts()
        parse options from string, usually taken from the environment. the
        string is a series of comma seperated values, with booleans being
        stated as "name" (true) or "noname" (false), and strings being
        "name=....", with the string being defined up to a comma or the
        end of the entire option string.
 */

void
parse_opts(char *str)
{
    char    *sp;
    const OPTION  *op;
    size_t len;

    while (*str)
    {
        for (sp = str; isalpha(*sp); sp++)
            continue;

        len = sp - str;

        /* Look it up and deal with it */

        for (op = optlist; op < &optlist[NUM_OPTS]; op++)
	{
            if (EQSTR(str, op->o_name, len))
            {
                if (op->o_putfunc == put_bool)
                    *op->o_opt.iarg = TRUE;
                else    /* string option */
                {
                    char    *start;
                    char    value[80];

                    /* Skip to start of string value */

                    for (str = sp + 1; *str == '='; str++)
                        continue;

                    start = (char *) value;

                    /* Skip to end of string value */

                    for (sp = str + 1; *sp && *sp != ','; sp++)
                        continue;

                    strncpy(start, str, sp - str);

		    /* Some options can't be changed. */
		    if (use_savedir && 
				    (op->o_opt.str == whoami || 
				     op->o_opt.str == file_name || 
				     op->o_opt.str == score_file))
			break;

                    /* Put the value into the option field */

                    if (op->o_putfunc != put_abil &&
                        op->o_putfunc != put_inv)
                        strcpy(op->o_opt.str, value);

                    if (op->o_putfunc == put_inv)
                    {
                        int *opt = op->o_opt.iarg;

                        len = strlen(value);

                        if (isupper(value[0]))
                            value[0] = (char) tolower(value[0]);
                        if (EQSTR(value, "overwrite",len))
                            *opt = INV_OVER;
                        if (EQSTR(value, "slow", len))
                            *opt = INV_SLOW;
                        if (EQSTR(value, "clear", len))
                            *opt = INV_CLEAR;
                    }
                    else if (*op->o_opt.iarg == -1)
                    {
                        int *opt = op->o_opt.iarg;

                        len = strlen(value);

                        if (isupper(value[0]))
                            value[0] = (char) tolower(value[0]);
                        if (EQSTR(value, "fighter", len))
                            *opt = C_FIGHTER;
                        else if (EQSTR(value, "magic", min(len, 5)))
                            *opt = C_MAGICIAN;
                        else if (EQSTR(value, "illus", min(len, 5)))
                            *opt = C_ILLUSION;
                        else if (EQSTR(value, "cleric", len))
                            *opt = C_CLERIC;
                        else if (EQSTR(value, "thief",  len))
                            *opt = C_THIEF;
                        else if (EQSTR(value, "paladin", len))
                            *opt = C_PALADIN;
                        else if (EQSTR(value, "ranger",  len))
                            *opt = C_RANGER;
                        else if (EQSTR(value, "assasin", len))
                            *opt = C_ASSASIN;
                        else if (EQSTR(value, "druid",   len))
                            *opt = C_DRUID;
                        else if (EQSTR(value, "ninja",   len))
                            *opt = C_NINJA;
                    }
                }
                break;
            }
            else if (op->o_putfunc == put_bool
                 && EQSTR(str, "no", 2) &&
                EQSTR(str + 2, op->o_name, len - 2))
            {
                *op->o_opt.iarg = FALSE;
                break;
            }
	}

        /* skip to start of next option name  */

        while (*sp && !isalpha(*sp))
            sp++;

        str = sp;
    }
}

/*
    put_inv()
        print the inventory type
*/

void
put_inv(opt_arg *opt, WINDOW *win)
{
    char    *style;

    switch(*opt->iarg)
    {
        case INV_OVER:
            style = "Overwrite";
            break;

        case INV_SLOW:
            style = "Slow";
            break;

        case INV_CLEAR:
            style = "Clear Screen";
            break;

        default:
            style = "(unknown)";
    }

    waddstr(win, style);
}

/*
    get_inv()
        The inventory field.
*/

int
get_inv(opt_arg *opt, WINDOW *win)
{
    int oy, ox, ny, nx;
    int op_bad;

    op_bad = TRUE;
    getyx(win, oy, ox);
    put_inv(opt, win);
    getyx(win, ny, nx);

    while(op_bad)
    {
        wmove(win, oy, ox);
        wrefresh(win);

        switch(readcharw(win))
        {
            case '\n':
            case '\r':
                op_bad = FALSE;
                break;

            case '\033':
            case '\007':
                return(QUIT);

            case '-':
                return(MINUS);

            case 'O':
            case 'o':
                *opt->iarg = INV_OVER;
                op_bad = FALSE;
                break;

            case 'S':
            case 's':
                *opt->iarg = INV_SLOW;
                op_bad = FALSE;
                break;

            case 'C':
            case 'c':
                *opt->iarg = INV_CLEAR;
                op_bad = FALSE;
                break;

            default:
                mvwaddstr(win, ny, nx + 5, "(Use: o, s, or c)");
        }
    }

    wmove(win, oy, ox);
    wclrtoeol(win);

    switch(*opt->iarg)
    {
        case INV_SLOW:
            waddstr(win, "Slow\n");
            break;

        case INV_CLEAR:
            waddstr(win, "Clear Screen\n");
            break;

        case INV_OVER:
            waddstr(win, "Overwrite\n");
            break;

        default:
            waddstr(win, "Unknown\n");
            break;
    }

    return(NORM);
}

/* 
 * get_restr()
 *
 * Gets strings that cannot be changed when use_savedir is set.
 * get_abil() can't be repurposed to do this without ugliness.
 *
 */
int
get_restr(opt_arg *opt, WINDOW *win)
{
    int oy, ox, ny, nx;
    int keep_up;

    keep_up = TRUE;
    getyx(win, oy, ox);
    put_str(opt, win);

    if (!use_savedir) {
        wmove(win, oy, ox);
        return get_str(opt, win);
    }

    getyx(win, ny, nx);
    while(keep_up)
    {
        wmove(win, oy, ox);
        wrefresh(win);

        switch(readcharw(win))
        {
            case '\n':
            case '\r':
                keep_up = FALSE;
                break;

            case '\033':
            case '\007':
                return(QUIT);

            case '-':
                return(MINUS);

            default:
                mvwaddstr(win, ny, nx + 5, "(no change allowed)");
        }
    }

    wmove(win, ny, nx + 5);
    wclrtoeol(win);
    wmove(win, ny, nx);
    waddch(win, '\n');

    return(NORM);
}

int
get_score(opt_arg *opt, WINDOW *win)
{
    char old_score_file[2*LINELEN];
    int status;

    if (use_savedir)
        return get_restr(opt, win);

    strncpy(old_score_file, opt->str, 2*LINELEN);
    old_score_file[2*LINELEN - 1] = '\0';
    status = get_str(opt, win);
    if (status == NORM && strcmp(old_score_file, opt->str)) {
        fclose(fd_score);
        fd_score = fopen(score_file, "r+");
        if (fd_score == NULL)
            fd_score = fopen(score_file, "a+");
    }
    return status;
}