From 4aec98ce9ca8b5b2e09f5b653a910b48c51a7ed2 Mon Sep 17 00:00:00 2001 From: nhmall Date: Mon, 7 Mar 2016 08:36:24 -0500 Subject: [PATCH 001/151] missed a file MSDOS --- Files | 2 +- sys/msdos/vidvesa.c | 1613 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1614 insertions(+), 1 deletion(-) create mode 100644 sys/msdos/vidvesa.c diff --git a/Files b/Files index 959393d8e..322450292 100644 --- a/Files +++ b/Files @@ -120,7 +120,7 @@ Install.dos Makefile.BC Makefile.GCC Makefile.MSC moveinit.pat msdos.c msdoshlp.txt ovlinit.c pckeys.c pctiles.c pctiles.h pcvideo.h portio.h schema1.BC schema2.BC schema3.MSC SCHEMA35.MSC setup.bat sound.c tile2bin.c -video.c vidtxt.c vidvga.c +video.c vidtxt.c vidvesa.c vidvga.c (files for running MSDOS binary under Windows) nhico.uu nhpif.uu diff --git a/sys/msdos/vidvesa.c b/sys/msdos/vidvesa.c new file mode 100644 index 000000000..4f497ef9b --- /dev/null +++ b/sys/msdos/vidvesa.c @@ -0,0 +1,1613 @@ +/* Copyright (c) NetHack PC Development Team 1995 */ +/* VESA BIOS functions copyright (c) Ray Chason 2016 */ +/* NetHack may be freely redistributed. See license for details. */ +/* + * vidvesa.c - VGA Hardware video support with VESA BIOS Extensions + */ + +#include "hack.h" + +#ifdef SCREEN_VESA /* this file is for SCREEN_VESA only */ +#include + +#include "pcvideo.h" +#include "tile.h" +#include "pctiles.h" +#include "vesa.h" +#include "wintty.h" +#include "tileset.h" + +#define BACKGROUND_VESA_COLOR 1 +#define FIRST_TEXT_COLOR 240 + +static unsigned long FDECL(vesa_SetWindow, (int window, unsigned long offset)); +static unsigned long FDECL(vesa_ReadPixel32, (unsigned x, unsigned y)); +static void FDECL(vesa_WritePixel32, (unsigned x, unsigned y, + unsigned long color)); +static void FDECL(vesa_WritePixel, (unsigned x, unsigned y, unsigned color)); +static unsigned long FDECL(vesa_MakeColor, (unsigned r, unsigned g, unsigned b)); +static void FDECL(vesa_GetRGB, ( + unsigned long color, + unsigned char *rp, unsigned char *gp, unsigned char *bp)); +static void FDECL(vesa_FillRect, ( + unsigned left, unsigned top, + unsigned width, unsigned height, + unsigned color)); + +static void NDECL(vesa_redrawmap); +static void FDECL(vesa_cliparound, (int, int)); +static void FDECL(decal_packed, (const struct TileImage *tile, unsigned special)); +static void FDECL(vesa_SwitchMode, (unsigned mode)); +static boolean FDECL(vesa_SetPalette, (const struct Pixel *)); +static boolean FDECL(vesa_SetHardPalette, (const struct Pixel *)); +static boolean FDECL(vesa_SetSoftPalette, (const struct Pixel *)); +static void FDECL(vesa_DisplayCell, (const struct TileImage *tile, int, int)); +static void FDECL(vesa_DisplayCellInMemory, (const struct TileImage *tile, + int, char buf[TILE_Y][640*2])); +static unsigned FDECL(vesa_FindMode, (unsigned long mode_addr, unsigned bits)); +static void FDECL(vesa_WriteChar, (int, int, int, int, BOOLEAN_P)); +static void FDECL(vesa_WriteCharInMemory, (int, int, char buf[TILE_Y][640*2], + int)); +static void FDECL(vesa_WriteStr, (const char *, int, int, int, int)); +static char __far *NDECL(vesa_FontPtrs); + +#ifdef POSITIONBAR +static void NDECL(positionbar); +#endif + +extern int clipx, clipxmax; /* current clipping column from wintty.c */ +extern int curcol, currow; /* current column and row */ +extern int g_attribute; +extern int attrib_text_normal; /* text mode normal attribute */ +extern int attrib_gr_normal; /* graphics mode normal attribute */ +extern int attrib_gr_intense; /* graphics mode intense attribute */ +extern boolean inmap; /* in the map window */ +extern boolean restoring; + +/* + * Global Variables + */ + +static unsigned char __far *font; + +static struct map_struct { + int glyph; + int ch; + int attr; + unsigned special; +} map[ROWNO][COLNO]; /* track the glyphs */ + +#define vesa_clearmap() \ + { \ + int x, y; \ + for (y = 0; y < ROWNO; ++y) \ + for (x = 0; x < COLNO; ++x) { \ + map[y][x].glyph = cmap_to_glyph(S_stone); \ + map[y][x].ch = S_stone; \ + map[y][x].attr = 0; \ + map[y][x].special = 0; \ + } \ + } +#define TOP_MAP_ROW 1 + +static int viewport_size = 40; + +static const struct Pixel defpalette[] = { /* Colors for text and the position bar */ + { 0x18, 0x18, 0x18, 0xff }, /* CLR_BLACK */ + { 0xaa, 0x00, 0x00, 0xff }, /* CLR_RED */ + { 0x00, 0xaa, 0x00, 0xff }, /* CLR_GREEN */ + { 0x99, 0x40, 0x00, 0xff }, /* CLR_BROWN */ + { 0x00, 0x00, 0xaa, 0xff }, /* CLR_BLUE */ + { 0xaa, 0x00, 0xaa, 0xff }, /* CLR_MAGENTA */ + { 0x00, 0xaa, 0xaa, 0xff }, /* CLR_CYAN */ + { 0xaa, 0xaa, 0xaa, 0xff }, /* CLR_GRAY */ + { 0x55, 0x55, 0x55, 0xff }, /* NO_COLOR */ + { 0xff, 0x90, 0x00, 0xff }, /* CLR_ORANGE */ + { 0x00, 0xff, 0x00, 0xff }, /* CLR_BRIGHT_GREEN */ + { 0xff, 0xff, 0x00, 0xff }, /* CLR_YELLOW */ + { 0x00, 0x00, 0xff, 0xff }, /* CLR_BRIGHT_BLUE */ + { 0xff, 0x00, 0xff, 0xff }, /* CLR_BRIGHT_MAGENTA */ + { 0x00, 0xff, 0xff, 0xff }, /* CLR_BRIGHT_CYAN */ + { 0xff, 0xff, 0xff, 0xff } /* CLR_WHITE */ +}; + +/* Information about the selected VESA mode */ +static unsigned short vesa_mode = 0xFFFF; /* Mode number */ +static unsigned short vesa_x_res; /* X resolution */ +static unsigned short vesa_y_res; /* Y resolution */ +static unsigned short vesa_x_center; /* X centering offset */ +static unsigned short vesa_y_center; /* Y centering offset */ +static unsigned short vesa_scan_line; /* Bytes per scan line */ +static int vesa_read_win; /* Select the read window */ +static int vesa_write_win; /* Select the write window */ +static unsigned long vesa_win_pos[2]; /* Window position */ +static unsigned long vesa_win_addr[2]; /* Window physical address */ +static unsigned long vesa_win_size; /* Window size */ +static unsigned long vesa_win_gran; /* Window granularity */ +static unsigned char vesa_pixel_size; +static unsigned char vesa_pixel_bytes; +static unsigned char vesa_red_pos; +static unsigned char vesa_red_size; +static unsigned char vesa_green_pos; +static unsigned char vesa_green_size; +static unsigned char vesa_blue_pos; +static unsigned char vesa_blue_size; +static unsigned long vesa_palette[256]; + +struct OldModeInfo { + unsigned mode; + + unsigned short XResolution; /* horizontal resolution in pixels or characters */ + unsigned short YResolution; /* vertical resolution in pixels or characters */ + unsigned char BitsPerPixel; /* bits per pixel */ + unsigned char MemoryModel; /* memory model type */ +}; + +static const struct OldModeInfo old_mode_table[] = { + { 0x0101, 640, 480, 8, 4 }, + { 0x0103, 800, 600, 8, 4 }, + { 0x0105, 1024, 768, 8, 4 }, + { 0x0107, 1280, 1024, 8, 4 }, + { 0x0110, 640, 480, 15, 6 }, + { 0x0111, 640, 480, 16, 6 }, + { 0x0112, 640, 480, 24, 6 }, + { 0x0113, 800, 600, 15, 6 }, + { 0x0114, 800, 600, 16, 6 }, + { 0x0115, 800, 600, 24, 6 }, + { 0x0116, 1024, 768, 15, 6 }, + { 0x0117, 1024, 768, 16, 6 }, + { 0x0118, 1024, 768, 24, 6 }, + { 0x0119, 1280, 1024, 15, 6 }, + { 0x011A, 1280, 1024, 16, 6 }, + { 0x011B, 1280, 1024, 24, 6 }, +}; + +/* Retrieve the mode info block */ +static boolean +vesa_GetModeInfo(mode, info) +unsigned mode; +struct ModeInfoBlock *info; +{ + int mode_info_sel = -1; /* custodial */ + int mode_info_seg; + __dpmi_regs regs; + + mode_info_seg = __dpmi_allocate_dos_memory( + (sizeof(*info) + 15) / 16, + &mode_info_sel); + if (mode_info_seg < 0) goto error; + + memset(info, 0, sizeof(*info)); + dosmemput(info, sizeof(*info), mode_info_seg * 16L); + + memset(®s, 0, sizeof(regs)); + regs.x.ax = 0x4F01; + regs.x.cx = mode; + regs.x.di = 0; + regs.x.es = mode_info_seg; + (void) __dpmi_int(VIDEO_BIOS, ®s); + + if (regs.x.ax != 0x004F) goto error; + dosmemget(mode_info_seg * 16L, sizeof(*info), info); + if (!(info->ModeAttributes & 0x0001)) goto error; + + if (!(info->ModeAttributes & 0x0002)) { + /* Older VESA BIOS that did not return certain mode properties, but + that has fixed mode numbers; search the table to find the right + mode properties */ + + unsigned i; + + for (i = 0; i < SIZE(old_mode_table); ++i) { + if (mode == old_mode_table[i].mode) { + break; + } + } + if (i >= SIZE(old_mode_table)) goto error; + + info->XResolution = old_mode_table[i].XResolution; + info->YResolution = old_mode_table[i].YResolution; + info->NumberOfPlanes = 1; + info->BitsPerPixel = old_mode_table[i].BitsPerPixel; + info->NumberOfBanks = 1; + info->MemoryModel = old_mode_table[i].MemoryModel; + } + + __dpmi_free_dos_memory(mode_info_sel); + return TRUE; + +error: + if (mode_info_sel != -1) __dpmi_free_dos_memory(mode_info_sel); + return FALSE; +} + +/* Set the memory window and return the offset */ +static unsigned long +vesa_SetWindow(window, offset) +int window; +unsigned long offset; +{ + /* If the desired offset is already within the window, leave the window + as it is and return the address based on the current window position. + This minimizes the use of the window switch function. + + On the first call to the function, vesa_win_pos[window] == 0xFFFFFFFF, + the offset will always be less than this, and the BIOS will always be + called. */ + + unsigned long pos = vesa_win_pos[window]; + if (offset < pos || pos + vesa_win_size <= offset) { + __dpmi_regs regs; + + memset(®s, 0, sizeof(regs)); + regs.x.ax = 0x4F05; + regs.h.bh = 0x00; + regs.h.bl = window; + regs.x.dx = offset / vesa_win_gran; + pos = regs.x.dx * vesa_win_gran; + (void) __dpmi_int(VIDEO_BIOS, ®s); + vesa_win_pos[window] = pos; + } + + offset = offset - vesa_win_pos[window] + vesa_win_addr[window]; + /* Keep from crashing the system if some malfunction gives us a bad + offset */ + if (offset < 0xA0000 || offset > 0xBFFFF) { + vesa_SwitchMode(MODETEXT); + fprintf(stderr, "Abort: offset=%08lX\n", offset); + exit(1); + } + return offset; +} + +static unsigned long +vesa_ReadPixel32(x, y) +unsigned x, y; +{ + unsigned long offset = y * vesa_scan_line + x * vesa_pixel_bytes; + unsigned long addr, color; + unsigned i; + + switch (vesa_pixel_size) { + case 8: + addr = vesa_SetWindow(vesa_read_win, offset); + color = _farpeekb(_dos_ds, addr); + break; + + case 15: + case 16: + addr = vesa_SetWindow(vesa_read_win, offset); + color = _farpeekw(_dos_ds, addr); + break; + + case 24: + /* Pixel may cross a window boundary */ + color = 0; + for (i = 0; i < 3; ++i) { + addr = vesa_SetWindow(vesa_read_win, offset + i); + color |= (unsigned long) _farpeekb(_dos_ds, addr) << (i * 8); + } + break; + + case 32: + addr = vesa_SetWindow(vesa_read_win, offset); + color = _farpeekl(_dos_ds, addr); + break; + } + return color; +} + +static void +vesa_WritePixel32(x, y, color) +unsigned x, y; +unsigned long color; +{ + unsigned long offset = y * vesa_scan_line + x * vesa_pixel_bytes; + unsigned long addr; + unsigned i; + + switch (vesa_pixel_size) { + case 8: + addr = vesa_SetWindow(vesa_write_win, offset); + _farpokeb(_dos_ds, addr, color); + break; + + case 15: + case 16: + addr = vesa_SetWindow(vesa_write_win, offset); + _farpokew(_dos_ds, addr, color); + break; + + case 24: + /* Pixel may cross a window boundary */ + for (i = 0; i < 3; ++i) { + addr = vesa_SetWindow(vesa_read_win, offset + i); + _farpokeb(_dos_ds, addr, (unsigned char) (color >> (i * 8))); + } + break; + + case 32: + addr = vesa_SetWindow(vesa_write_win, offset); + _farpokel(_dos_ds, addr, color); + break; + } +} + +static void +vesa_WritePixel(x, y, color) +unsigned x, y; +unsigned color; +{ + if (vesa_pixel_size == 8) { + vesa_WritePixel32(x, y, color); + } else { + vesa_WritePixel32(x, y, vesa_palette[color & 0xFF]); + } +} + +static unsigned long +vesa_MakeColor(r, g, b) +unsigned r, g, b; +{ + r = (r & 0xFF) >> (8 - vesa_red_size); + g = (g & 0xFF) >> (8 - vesa_green_size); + b = (b & 0xFF) >> (8 - vesa_blue_size); + return ((unsigned long) r << vesa_red_pos) + | ((unsigned long) g << vesa_green_pos) + | ((unsigned long) b << vesa_blue_pos); +} + +static void +vesa_GetRGB(color, rp, gp, bp) +unsigned long color; +unsigned char *rp, *gp, *bp; +{ + unsigned r, g, b; + + r = color >> vesa_red_pos; + g = color >> vesa_green_pos; + b = color >> vesa_blue_pos; + r <<= 8 - vesa_red_size; + g <<= 8 - vesa_green_size; + b <<= 8 - vesa_blue_size; + *rp = (unsigned char) r; + *gp = (unsigned char) g; + *bp = (unsigned char) b; +} + +static void +vesa_FillRect(left, top, width, height, color) +unsigned left, top, width, height, color; +{ + unsigned x, y; + + for (y = 0; y < height; ++y) { + for (x = 0; x < width; ++x) { + vesa_WritePixel(left + x, top + y, color); + } + } +} + +void +vesa_get_scr_size() +{ + CO = 80; + LI = 29; +} + +void +vesa_backsp() +{ + int col, row; + + col = curcol; /* Character cell row and column */ + row = currow; + + if (col > 0) + col = col - 1; + vesa_gotoloc(col, row); +} + +void +vesa_clear_screen(colour) +int colour; +{ + vesa_FillRect(0, 0, vesa_x_res, vesa_y_res, colour); + if (iflags.tile_view) + vesa_clearmap(); + vesa_gotoloc(0, 0); /* is this needed? */ +} + +/* clear to end of line */ +void +vesa_cl_end(col, row) +int col, row; +{ + unsigned left = vesa_x_center + col * 8; + unsigned top = vesa_y_center + row * 16; + unsigned width = (CO - 1 - col) * 8; + unsigned height = 16; + + vesa_FillRect(left, top, width, height, BACKGROUND_VESA_COLOR); +} + +/* clear to end of screen */ +void +vesa_cl_eos(cy) +int cy; +{ + int count; + + cl_end(); + if (cy < LI - 1) { + unsigned left = vesa_x_center; + unsigned top = vesa_y_center + cy * 16; + unsigned width = 640; + unsigned height = (LI - 1 - cy) * 16; + + vesa_FillRect(left, top, width, height, BACKGROUND_VESA_COLOR); + } +} + +void +vesa_tty_end_screen() +{ + vesa_clear_screen(BACKGROUND_VESA_COLOR); + vesa_SwitchMode(MODETEXT); +} + +void +vesa_tty_startup(wid, hgt) +int *wid, *hgt; +{ + /* code to sense display adapter is required here - MJA */ + + vesa_get_scr_size(); + if (CO && LI) { + *wid = CO; + *hgt = LI; + } + + attrib_gr_normal = ATTRIB_VGA_NORMAL; + attrib_gr_intense = ATTRIB_VGA_INTENSE; + g_attribute = attrib_gr_normal; /* Give it a starting value */ +} + +/* + * Screen output routines (these are heavily used). + * + * These are the 3 routines used to place information on the screen + * in the VGA PC tty port of NetHack. These are the routines + * that get called by the general interface routines in video.c. + * + * vesa_xputs -Writes a c null terminated string at the current location. + * + * vesa_xputc -Writes a single character at the current location. Since + * various places in the code assume that control characters + * can be used to control, we are forced to interpret some of + * the more common ones, in order to keep things looking correct. + * + * vesa_xputg -This routine is used to display a graphical representation of a + * NetHack glyph (a tile) at the current location. For more + * information on NetHack glyphs refer to the comments in + * include/display.h. + * + */ + +void +vesa_xputs(s, col, row) +const char *s; +int col, row; +{ + if (s != NULL) { + vesa_WriteStr(s, strlen(s), col, row, g_attribute); + } +} + +/* write out character (and attribute) */ +void +vesa_xputc(ch, attr) +char ch; +int attr; +{ + int col, row; + + col = curcol; + row = currow; + + switch (ch) { + case '\n': + col = 0; + ++row; + break; + default: + vesa_WriteChar((unsigned char) ch, col, row, attr, FALSE); + if (col < (CO - 1)) + ++col; + break; + } /* end switch */ + vesa_gotoloc(col, row); +} + +#if defined(USE_TILES) +/* Place tile represent. a glyph at current location */ +void +vesa_xputg(glyphnum, ch, + special) +int glyphnum; +int ch; +unsigned special; /* special feature: corpse, invis, detected, pet, ridden - + hack.h */ +{ + int col, row; + int attr; + int ry; + const struct TileImage *packcell; + + row = currow; + col = curcol; + if ((col < 0 || col >= COLNO) + || (row < TOP_MAP_ROW || row >= (ROWNO + TOP_MAP_ROW))) + return; + ry = row - TOP_MAP_ROW; + map[ry][col].glyph = glyphnum; + map[ry][col].ch = ch; + map[ry][col].special = special; + attr = (g_attribute == 0) ? attrib_gr_normal : g_attribute; + map[ry][col].attr = attr; + if (iflags.traditional_view) { + vesa_WriteChar((unsigned char) ch, col, row, attr, FALSE); + } else { + if ((col >= clipx) && (col <= clipxmax)) { + packcell = get_tile(glyph2tile[glyphnum]); + if (!iflags.over_view && map[ry][col].special) + decal_packed(packcell, special); + vesa_DisplayCell(packcell, col - clipx, row); + } + } + if (col < (CO - 1)) + ++col; + vesa_gotoloc(col, row); +} +#endif /* USE_TILES */ + +/* + * Cursor location manipulation, and location information fetching + * routines. + * These include: + * + * vesa_gotoloc(x,y) - Moves the "cursor" on screen to the specified x + * and y character cell location. This routine + * determines the location where screen writes + * will occur next, it does not change the location + * of the player on the NetHack level. + */ + +void +vesa_gotoloc(col, row) +int col, row; +{ + curcol = min(col, CO - 1); /* protection from callers */ + currow = min(row, LI - 1); +} + +#if defined(USE_TILES) && defined(CLIPPING) +static void +vesa_cliparound(x, y) +int x, y; +{ + int oldx = clipx; + + if (!iflags.tile_view || iflags.over_view || iflags.traditional_view) + return; + + if (x < clipx + 5) { + clipx = max(0, x - (viewport_size / 2)); + clipxmax = clipx + (viewport_size - 1); + } else if (x > clipxmax - 5) { + clipxmax = min(COLNO - 1, x + (viewport_size / 2)); + clipx = clipxmax - (viewport_size - 1); + } + if (clipx != oldx) { + if (on_level(&u.uz0, &u.uz) && !restoring) + /* (void) doredraw(); */ + vesa_redrawmap(); + } +} + +static void +vesa_redrawmap() +{ + int x, y, t; + const struct TileImage *packcell; + + /* y here is in screen rows*/ + /* Build each row in local memory, then write, to minimize use of the + window switch function */ + for (y = 0; y < ROWNO; ++y) { + char buf[TILE_Y][640*2]; + + for (x = clipx; x <= clipxmax; ++x) { + if (iflags.traditional_view) { + vesa_WriteCharInMemory((unsigned char) map[y][x].ch, x, + buf, map[y][x].attr); + } else { + t = map[y][x].glyph; + packcell = get_tile(glyph2tile[t]); + if (!iflags.over_view && map[y][x].special) + decal_packed(packcell, map[y][x].special); + vesa_DisplayCellInMemory(packcell, x - clipx, buf); + } + } + if (iflags.over_view && vesa_pixel_size != 8) { + for (t = 0; t < TILE_Y; ++t) { + for (x = 0; x < 640; ++x) { + unsigned long c1 = vesa_palette[buf[t][x * 2 + 0]]; + unsigned long c2 = vesa_palette[buf[t][x * 2 + 1]]; + unsigned char r1, r2, g1, g2, b1, b2; + + vesa_GetRGB(c1, &r1, &g1, &b1); + vesa_GetRGB(c2, &r2, &g2, &b2); + r1 = (r1 + r2) / 2; + g1 = (g1 + g2) / 2; + b1 = (b1 + b2) / 2; + vesa_WritePixel32(x, (y + TOP_MAP_ROW) * TILE_Y + t, + vesa_MakeColor(r1, g1, b1)); + } + } + } else { + for (t = 0; t < TILE_Y; ++t) { + for (x = 0; x < 640; ++x) { + vesa_WritePixel(x, (y + TOP_MAP_ROW) * TILE_Y + t, buf[t][x]); + } + } + } + } +} +#endif /* USE_TILES && CLIPPING */ + +void +vesa_userpan(left) +boolean left; +{ + int x; + + /* pline("Into userpan"); */ + if (iflags.over_view || iflags.traditional_view) + return; + if (left) + x = min(COLNO - 1, clipxmax + 10); + else + x = max(0, clipx - 10); + vesa_cliparound(x, 10); /* y value is irrelevant on VGA clipping */ + positionbar(); + vesa_DrawCursor(); +} + +void +vesa_overview(on) +boolean on; +{ + /* vesa_HideCursor(); */ + if (on) { + iflags.over_view = TRUE; + clipx = 0; + clipxmax = CO - 1; + } else { + iflags.over_view = FALSE; + clipx = max(0, (curcol - viewport_size / 2)); + if (clipx > ((CO - 1) - viewport_size)) + clipx = (CO - 1) - viewport_size; + clipxmax = clipx + (viewport_size - 1); + } +} + +void +vesa_traditional(on) +boolean on; +{ + /* vesa_HideCursor(); */ + if (on) { + /* switch_symbols(FALSE); */ + iflags.traditional_view = TRUE; + clipx = 0; + clipxmax = CO - 1; + } else { + iflags.traditional_view = FALSE; + if (!iflags.over_view) { + clipx = max(0, (curcol - viewport_size / 2)); + if (clipx > ((CO - 1) - viewport_size)) + clipx = (CO - 1) - viewport_size; + clipxmax = clipx + (viewport_size - 1); + } + } +} + +void +vesa_refresh() +{ + positionbar(); + vesa_redrawmap(); + vesa_DrawCursor(); +} + +static void +decal_packed(gp, special) +const struct TileImage *gp; +unsigned special; +{ + /* FIXME: the tile array is fixed in memory and should not be changed; + if we ever implement this, we'll have to copy the pixels */ + if (special & MG_CORPSE) { + } else if (special & MG_INVIS) { + } else if (special & MG_DETECT) { + } else if (special & MG_PET) { + } else if (special & MG_RIDDEN) { + } +} + +/* + * Open tile files, + * initialize the SCREEN, switch it to graphics mode, + * initialize the pointers to the fonts, clear + * the screen. + * + */ +void +vesa_Init(void) +{ + const struct Pixel *paletteptr; +#ifdef USE_TILES + const char *tile_file; + int tilefailure = 0; + /* + * Attempt to open the required tile files. If we can't + * don't perform the video mode switch, use TTY code instead. + * + */ + tile_file = iflags.wc_tile_file; + if (tile_file == NULL || tile_file == '\0') { + tile_file = "nhtiles.bmp"; + } + if (!read_tiles(tile_file, FALSE)) + tilefailure |= 1; + if (get_palette() == NULL) + tilefailure |= 4; + + if (tilefailure) { + raw_printf("Reverting to TTY mode, tile initialization failure (%d).", + tilefailure); + wait_synch(); + iflags.usevga = 0; + iflags.tile_view = FALSE; + iflags.over_view = FALSE; + CO = 80; + LI = 25; + /* clear_screen() */ /* not vesa_clear_screen() */ + return; + } +#endif + + if (vesa_mode == 0xFFFF) { + vesa_detect(); + } + vesa_SwitchMode(vesa_mode); + windowprocs.win_cliparound = vesa_cliparound; +#ifdef USE_TILES + paletteptr = get_palette(); + iflags.tile_view = TRUE; + iflags.over_view = FALSE; +#else + paletteptr = defpalette; +#endif + vesa_SetPalette(paletteptr); + g_attribute = attrib_gr_normal; + font = vesa_FontPtrs(); + clear_screen(); + clipx = 0; + clipxmax = clipx + (viewport_size - 1); +} + +/* + * Switches modes of the video card. + * + * If mode == MODETEXT (0x03), then the card is placed into text + * mode. Otherwise, the card is placed in the mode selected by + * vesa_detect. Supported modes are those with packed 8 bit pixels. + * + */ +static void +vesa_SwitchMode(mode) +unsigned mode; +{ + __dpmi_regs regs; + + if (mode == MODETEXT) { + iflags.grmode = 0; + regs.x.ax = mode; + (void) __dpmi_int(VIDEO_BIOS, ®s); + } else if (mode >= 0x100) { + iflags.grmode = 1; + regs.x.ax = 0x4F02; + regs.x.bx = mode & 0x81FF; + (void) __dpmi_int(VIDEO_BIOS, ®s); + /* Record that the window position is unknown */ + vesa_win_pos[0] = 0xFFFFFFFF; + vesa_win_pos[1] = 0xFFFFFFFF; + } else { + iflags.grmode = 0; /* force text mode for error msg */ + regs.x.ax = MODETEXT; + (void) __dpmi_int(VIDEO_BIOS, ®s); + g_attribute = attrib_text_normal; + impossible("vesa_SwitchMode: Bad video mode requested 0x%X", mode); + } +} + +/* + * This allows grouping of several tasks to be done when + * switching back to text mode. This is a public (extern) function. + * + */ +void +vesa_Finish(void) +{ + free_tiles(); + vesa_SwitchMode(MODETEXT); + windowprocs.win_cliparound = tty_cliparound; + g_attribute = attrib_text_normal; + iflags.tile_view = FALSE; +} + +/* + * + * Returns a far pointer (or flat 32 bit pointer under djgpp) to the + * location of the appropriate ROM font for the _current_ video mode + * (so you must place the card into the desired video mode before + * calling this function). + * + * This function takes advantage of the video BIOS loading the + * address of the appropriate character definition table for + * the current graphics mode into interrupt vector 0x43 (0000:010C). + */ +static char __far * +vesa_FontPtrs(void) +{ + USHORT __far *tmp; + char __far *retval; + USHORT fseg, foff; + tmp = (USHORT __far *) MK_PTR(((USHORT) FONT_PTR_SEGMENT), + ((USHORT) FONT_PTR_OFFSET)); + foff = READ_ABSOLUTE_WORD(tmp); + ++tmp; + fseg = READ_ABSOLUTE_WORD(tmp); + retval = (char __far *) MK_PTR(fseg, foff); + return retval; +} + +/* + * This will verify the existance of a VGA adapter on the machine. + * Video function call 0x4F00 returns 0x004F in AX if successful, and + * returns a VbeInfoBlock describing the features of the VESA BIOS. + */ +int +vesa_detect() +{ + int vbe_info_sel = -1; /* custodial */ + int vbe_info_seg; + struct VbeInfoBlock vbe_info; + __dpmi_regs regs; + unsigned long mode_addr; + struct ModeInfoBlock mode_info; + + vbe_info_seg = __dpmi_allocate_dos_memory( + (sizeof(vbe_info) + 15) / 16, + &vbe_info_sel); + if (vbe_info_seg < 0) goto error; + + /* Request VBE 2.0 information if it is available */ + memset(&vbe_info, 0, sizeof(vbe_info)); + memcpy(vbe_info.VbeSignature, "VBE2", 4); + dosmemput(&vbe_info, sizeof(vbe_info), vbe_info_seg * 16L); + + /* Request VESA BIOS information */ + regs.x.ax = 0x4F00; + regs.x.di = 0; + regs.x.es = vbe_info_seg; + (void) __dpmi_int(VIDEO_BIOS, ®s); + + /* Check for successful completion of function: is VESA BIOS present? */ + if (regs.x.ax != 0x004F) goto error; + dosmemget(vbe_info_seg * 16L, sizeof(vbe_info), &vbe_info); + if (memcmp(vbe_info.VbeSignature, "VESA", 4) != 0) goto error; + + /* Get the address of the mode list */ + /* The mode list may be within the DOS memory area allocated above. + That area must remain allocated and must not be rewritten until + we're done here. */ + mode_addr = (vbe_info.VideoModePtr >> 16) * 16L + + (vbe_info.VideoModePtr & 0xFFFF); + + /* Scan the mode list for an acceptable mode */ + vesa_mode = vesa_FindMode(mode_addr, 32); + if (vesa_mode == 0xFFFF) + vesa_mode = vesa_FindMode(mode_addr, 24); + if (vesa_mode == 0xFFFF) + vesa_mode = vesa_FindMode(mode_addr, 16); + if (vesa_mode == 0xFFFF) + vesa_mode = vesa_FindMode(mode_addr, 15); + if (vesa_mode == 0xFFFF) + vesa_mode = vesa_FindMode(mode_addr, 8); + if (vesa_mode == 0xFFFF) + goto error; + + /* Set up the variables for the pixel functions */ + vesa_GetModeInfo(vesa_mode, &mode_info); + vesa_x_res = mode_info.XResolution; + vesa_y_res = mode_info.YResolution; + vesa_x_center = (vesa_x_res - 640) / 2; + vesa_y_center = (vesa_y_res - 480) / 2; + vesa_scan_line = mode_info.BytesPerScanLine; + vesa_win_size = mode_info.WinSize * 1024L; + vesa_win_gran = mode_info.WinGranularity * 1024L; + vesa_pixel_size = mode_info.BitsPerPixel; + vesa_pixel_bytes = (vesa_pixel_size + 7) / 8; + if (vbe_info.VbeVersion >= 0x0300) { + vesa_red_pos = mode_info.RedFieldPosition; + vesa_red_size = mode_info.RedMaskSize; + vesa_green_pos = mode_info.GreenFieldPosition; + vesa_green_size = mode_info.GreenMaskSize; + vesa_blue_pos = mode_info.BlueFieldPosition; + vesa_blue_size = mode_info.BlueMaskSize; + } else { + switch (vesa_pixel_size) { + case 15: + vesa_blue_pos = 0; + vesa_blue_size = 5; + vesa_green_pos = 5; + vesa_green_size = 5; + vesa_red_pos = 10; + vesa_red_size = 5; + break; + + case 16: + vesa_blue_pos = 0; + vesa_blue_size = 5; + vesa_green_pos = 5; + vesa_green_size = 6; + vesa_red_pos = 11; + vesa_red_size = 5; + break; + + case 24: + case 32: + vesa_blue_pos = 0; + vesa_blue_size = 8; + vesa_green_pos = 8; + vesa_green_size = 8; + vesa_red_pos = 16; + vesa_red_size = 8; + break; + } + } + vesa_win_addr[0] = mode_info.WinASegment * 16L; + vesa_win_addr[1] = mode_info.WinBSegment * 16L; + vesa_win_pos[0] = 0xFFFFFFFF; /* position unknown */ + vesa_win_pos[1] = 0xFFFFFFFF; + /* Read window */ + if (mode_info.WinAAttributes & 0x2) { + vesa_read_win = 0; + } else if (mode_info.WinBAttributes & 0x2) { + vesa_read_win = 1; + } else { + goto error; /* Shouldn't happen */ + } + /* Write window */ + if (mode_info.WinAAttributes & 0x4) { + vesa_write_win = 0; + } else if (mode_info.WinBAttributes & 0x4) { + vesa_write_win = 1; + } else { + goto error; /* Shouldn't happen */ + } + + __dpmi_free_dos_memory(vbe_info_sel); + return TRUE; + +error: + if (vbe_info_sel != -1) __dpmi_free_dos_memory(vbe_info_sel); + return FALSE; +} + +static unsigned +vesa_FindMode(mode_addr, bits) +unsigned long mode_addr; +unsigned bits; +{ + unsigned selected_mode; + struct ModeInfoBlock mode_info0, mode_info; + unsigned model = (bits == 8) ? 4 : 6; + + memset(&mode_info, 0, sizeof(mode_info)); + selected_mode = 0xFFFF; + while (1) { + unsigned mode = _farpeekw(_dos_ds, mode_addr); + if (mode == 0xFFFF) break; + mode_addr += 2; + + /* Query the mode info; skip to next if not in fact supported */ + if (!vesa_GetModeInfo(mode, &mode_info0)) continue; + + /* Check that the mode is acceptable */ + if (mode_info0.XResolution < 640) continue; + if (mode_info0.YResolution < 480) continue; + if (mode_info0.NumberOfPlanes != 1) continue; + if (mode_info0.BitsPerPixel != bits) continue; + if (mode_info0.NumberOfBanks != 1) continue; + if (mode_info0.MemoryModel != model) continue; + if (mode_info0.ModeAttributes & 0x40) continue; + + /* The mode is OK. Accept it if it is smaller than any previous mode + or if no previous mode is accepted. */ + if (selected_mode == 0xFFFF + || mode_info0.XResolution * mode_info0.YResolution + < mode_info.XResolution * mode_info.YResolution) { + selected_mode = mode; + mode_info = mode_info0; + } + } + + return selected_mode; +} + +/* + * Write character 'ch', at (x,y) and + * do it using the colour 'colour'. + * + */ +static void +vesa_WriteChar(chr, col, row, colour, transparent) +int chr, col, row, colour; +boolean transparent; +{ + int i, j; + int pixx, pixy; + + unsigned char __far *fp = font; + unsigned char fnt; + + pixx = min(col, (CO - 1)) * 8; /* min() protects from callers */ + pixy = min(row, (LI - 1)) * 16; /* assumes 8 x 16 char set */ + pixx += vesa_x_center; + pixy += vesa_y_center; + + for (i = 0; i < MAX_ROWS_PER_CELL; ++i) { + fnt = READ_ABSOLUTE((fp + chr * 16 + i)); + for (j = 0; j < 8; ++j) { + if (fnt & (0x80 >> j)) { + vesa_WritePixel(pixx + j, pixy + i, colour + FIRST_TEXT_COLOR); + } else if (!transparent) { + vesa_WritePixel(pixx + j, pixy + i, BACKGROUND_VESA_COLOR); + } + } + } +} + +/* + * Like vesa_WriteChar, but draw the character in local memory rather than in + * the VGA frame buffer. + * + * vesa_redrawmap uses this to gather a row of cells in local memory and then + * draw them in strict row-major order, minimizing the use of the VESA + * windowing function. + * + */ +static void +vesa_WriteCharInMemory(chr, col, buf, colour) +int chr, col; +char buf[TILE_Y][640*2]; +int colour; +{ + int i, j; + int pixx; + + unsigned char __far *fp = font; + unsigned char fnt; + + pixx = min(col, (CO - 1)) * 8; /* min() protects from callers */ + + for (i = 0; i < MAX_ROWS_PER_CELL; ++i) { + fnt = READ_ABSOLUTE((fp + chr * 16 + i)); + for (j = 0; j < 8; ++j) { + if (fnt & (0x80 >> j)) { + buf[i][pixx + j] = colour + FIRST_TEXT_COLOR; + } else { + buf[i][pixx + j] = BACKGROUND_VESA_COLOR; + } + } + } +} + +/* + * This is the routine that displays a high-res "cell" pointed to by 'gp' + * at the desired location (col,row). + * + * Note: (col,row) in this case refer to the coordinate location in + * NetHack character grid terms, (ie. the 40 x 25 character grid), + * not the x,y pixel location. + * + */ +static void +vesa_DisplayCell(tile, col, row) +const struct TileImage *tile; +int col, row; +{ + int i, j, pixx, pixy; + + pixx = col * TILE_X; + pixy = row * TILE_Y; + if (iflags.over_view) { + pixx /= 2; + pixx += vesa_x_center; + pixy += vesa_y_center; + if (vesa_pixel_size != 8) { + for (i = 0; i < TILE_Y; ++i) { + for (j = 0; j < TILE_X; j += 2) { + unsigned index = i * tile->width + j; + unsigned long c1 = vesa_palette[tile->indexes[index + 0]]; + unsigned long c2 = vesa_palette[tile->indexes[index + 1]]; + unsigned char r1, r2, g1, g2, b1, b2; + + vesa_GetRGB(c1, &r1, &g1, &b1); + vesa_GetRGB(c2, &r2, &g2, &b2); + r1 = (r1 + r2) / 2; + g1 = (g1 + g2) / 2; + b1 = (b1 + b2) / 2; + vesa_WritePixel32(pixx + j / 2, pixy + i, + vesa_MakeColor(r1, g1, b1)); + } + } + } else { + for (i = 0; i < TILE_Y; ++i) { + for (j = 0; j < TILE_X; j += 2) { + unsigned index = i * tile->width + j; + vesa_WritePixel(pixx + j / 2, pixy + i, tile->indexes[index]); + } + } + } + } else { + pixx += vesa_x_center; + pixy += vesa_y_center; + for (i = 0; i < TILE_Y; ++i) { + for (j = 0; j < TILE_X; ++j) { + unsigned index = i * tile->width + j; + vesa_WritePixel(pixx + j, pixy + i, tile->indexes[index]); + } + } + } +} + +/* + * Like vesa_DisplayCell, but draw the tile in local memory rather than in + * the VGA frame buffer. + * + * vesa_redrawmap uses this to gather a row of cells in local memory and then + * draw them in strict row-major order, minimizing the use of the VESA + * windowing function. + * + */ +static void +vesa_DisplayCellInMemory(tile, col, buf) +const struct TileImage *tile; +int col; +char buf[TILE_Y][640*2]; +{ + int i, j, pixx; + + pixx = col * TILE_X; + if (iflags.over_view && vesa_pixel_size == 8) { + pixx /= 2; + for (i = 0; i < TILE_Y; ++i) { + for (j = 0; j < TILE_X; j += 2) { + unsigned index = i * tile->width + j; + buf[i][pixx + j / 2] = tile->indexes[index]; + } + } + } else { + for (i = 0; i < TILE_Y; ++i) { + for (j = 0; j < TILE_X; ++j) { + unsigned index = i * tile->width + j; + buf[i][pixx + j] = tile->indexes[index]; + } + } + } +} + +/* + * Write the character string pointed to by 's', whose maximum length + * is 'len' at location (x,y) using the 'colour' colour. + * + */ +static void +vesa_WriteStr(s, len, col, row, colour) +const char *s; +int len, col, row, colour; +{ + const unsigned char *us; + int i = 0; + + /* protection from callers */ + if (row > (LI - 1)) + return; + + i = 0; + us = (const unsigned char *) s; + while ((*us != 0) && (i < len) && (col < (CO - 1))) { + vesa_WriteChar(*us, col, row, colour, FALSE); + ++us; + ++i; + ++col; + } +} + +/* + * Initialize the VGA palette with the desired colours. This + * must be a series of 720 bytes for use with a card in 256 + * colour mode at 640 x 480. The first 240 palette entries are + * used by the tile set; the last 16 are reserved for text. + * + */ +static boolean +vesa_SetPalette(palette) +const struct Pixel *palette; +{ + if (vesa_pixel_size == 8) { + vesa_SetHardPalette(palette); + } else { + vesa_SetSoftPalette(palette); + } +} + +static boolean +vesa_SetHardPalette(palette) +const struct Pixel *palette; +{ + const struct Pixel *p = palette; + int palette_sel = -1; /* custodial */ + int palette_seg; + unsigned long palette_ptr; + unsigned i, shift; + unsigned char r, g, b; + unsigned long color; + __dpmi_regs regs; + + palette_seg = __dpmi_allocate_dos_memory( 1024 / 16, &palette_sel); + if (palette_seg < 0) goto error; + + /* Use 8 bit DACs if we have them */ + memset(®s, 0, sizeof(regs)); + regs.x.ax = 0x4F08; + regs.h.bl = 0; + regs.h.bh = 8; + (void) __dpmi_int(VIDEO_BIOS, ®s); + if (regs.x.ax != 0x004F) { + shift = 2; + } else if (regs.h.bh > 8) { + shift = 0; + } else { + shift = 8 - regs.h.bh; + } + + /* Set the tile set and text colors */ + palette_ptr = palette_seg * 16L; +#ifdef USE_TILES + for (i = 0; i < FIRST_TEXT_COLOR; ++i) { + r = p->r >> shift; + g = p->g >> shift; + b = p->b >> shift; + color = ((unsigned long) r << 16) + | ((unsigned long) g << 8) + | ((unsigned long) b << 0); + _farpokel(_dos_ds, palette_ptr, color); + palette_ptr += 4; + ++p; + } +#else + palette_ptr += FIRST_TEXT_COLOR * 4; +#endif + p = defpalette; + for (i = FIRST_TEXT_COLOR; i < 256; ++i) { + r = p->r >> shift; + g = p->g >> shift; + b = p->b >> shift; + color = ((unsigned long) r << 16) + | ((unsigned long) g << 8) + | ((unsigned long) b << 0); + _farpokel(_dos_ds, palette_ptr, color); + palette_ptr += 4; + ++p; + } + + memset(®s, 0, sizeof(regs)); + regs.x.ax = 0x4F09; + regs.h.bl = 0; + regs.x.cx = 256; + regs.x.dx = 0; + regs.x.di = 0; + regs.x.es = palette_seg; + (void) __dpmi_int(VIDEO_BIOS, ®s); + + __dpmi_free_dos_memory(palette_sel); + return TRUE; + +error: + if (palette_sel != -1) __dpmi_free_dos_memory(palette_sel); + return FALSE; +} + +static boolean +vesa_SetSoftPalette(palette) +const struct Pixel *palette; +{ + const struct Pixel *p; + unsigned i; + unsigned char r, g, b; + + /* Set the tile set and text colors */ +#ifdef USE_TILES + p = palette; + for (i = 0; i < FIRST_TEXT_COLOR; ++i) { + r = p->r; + g = p->g; + b = p->b; + vesa_palette[i] = vesa_MakeColor(r, g, b); + ++p; + } +#endif + p = defpalette; + for (i = FIRST_TEXT_COLOR; i < 256; ++i) { + r = p->r; + g = p->g; + b = p->b; + vesa_palette[i] = vesa_MakeColor(r, g, b); + ++p; + } +} + +#ifdef POSITIONBAR + +#define PBAR_ROW (LI - 4) +#define PBAR_COLOR_ON 16 /* slate grey background colour of tiles */ +#define PBAR_COLOR_OFF 0 /* bluish grey, used in old style only */ +#define PBAR_COLOR_STAIRS CLR_BROWN /* brown */ +#define PBAR_COLOR_HERO CLR_WHITE /* creamy white */ + +static unsigned char pbar[COLNO]; + +void +vesa_update_positionbar(posbar) +char *posbar; +{ + char *p = pbar; + if (posbar) + while (*posbar) + *p++ = *posbar++; + *p = 0; +} + +static void +positionbar() +{ + char *posbar = pbar; + int feature, ucol; + int k, x, y, colour, row; + + int startk, stopk; + boolean nowhere = FALSE; + int pixy = (PBAR_ROW * MAX_ROWS_PER_CELL); + int tmp; + + if (!iflags.grmode || !iflags.tile_view) + return; + if ((clipx < 0) || (clipxmax <= 0) || (clipx >= clipxmax)) + nowhere = TRUE; + if (nowhere) { +#ifdef DEBUG + pline("Would have put bar using %d - %d.", clipx, clipxmax); +#endif + return; + } +#ifdef OLD_STYLE + for (y = pixy; y < (pixy + MAX_ROWS_PER_CELL); ++y) { + for (x = 0; x < 640; ++x) { + k = x / 8; + if ((k < clipx) || (k > clipxmax)) { + colour = PBAR_COLOR_OFF; + } else + colour = PBAR_COLOR_ON; + vesa_WritePixel(x + vesa_x_center, y + vesa_y_center, colour); + } + } +#else + for (y = pixy, row = 0; y < (pixy + MAX_ROWS_PER_CELL); ++y, ++row) { + if ((!row) || (row == (ROWS_PER_CELL - 1))) { + startk = 0; + stopk = SCREENBYTES; + } else { + startk = clipx; + stopk = clipxmax; + } + for (x = 0; x < 640; ++x) { + k = x / 8; + if ((k < startk) || (k > stopk)) + colour = BACKGROUND_VGA_COLOR; + else + colour = PBAR_COLOR_ON; + vesa_WritePixel(x + vesa_x_center, y + vesa_y_center, colour); + } + } +#endif + ucol = 0; + if (posbar) { + while (*posbar != 0) { + feature = *posbar++; + switch (feature) { + case '>': + vesa_WriteChar(feature, (int) *posbar++, PBAR_ROW, PBAR_COLOR_STAIRS, TRUE); + break; + case '<': + vesa_WriteChar(feature, (int) *posbar++, PBAR_ROW, PBAR_COLOR_STAIRS, TRUE); + break; + case '@': + ucol = (int) *posbar++; + vesa_WriteChar(feature, ucol, PBAR_ROW, PBAR_COLOR_HERO, TRUE); + break; + default: /* unanticipated symbols */ + vesa_WriteChar(feature, (int) *posbar++, PBAR_ROW, PBAR_COLOR_STAIRS, TRUE); + break; + } + } + } +#ifdef SIMULATE_CURSOR + if (inmap) { + tmp = curcol + 1; + if ((tmp != ucol) && (curcol >= 0)) + vesa_WriteChar('_', tmp, PBAR_ROW, PBAR_COLOR_HERO, TRUE); + } +#endif +} + +#endif /*POSITIONBAR*/ + +#ifdef SIMULATE_CURSOR + +static unsigned long undercursor[TILE_Y][TILE_X]; + +void +vesa_DrawCursor() +{ + unsigned x, y, left, top, right, bottom, width; + boolean isrogue = Is_rogue_level(&u.uz); + boolean halfwidth = + (isrogue || iflags.over_view || iflags.traditional_view || !inmap); + int curtyp; + + if (!cursor_type && inmap) + return; /* CURSOR_INVIS - nothing to do */ + + x = min(curcol, (CO - 1)); /* protection from callers */ + y = min(currow, (LI - 1)); /* protection from callers */ + if (!halfwidth && ((x < clipx) || (x > clipxmax))) + return; + if (inmap) + x -= clipx; + left = x * TILE_X; /* convert to pixels */ + top = y * TILE_Y; + if (halfwidth) { + left /= 2; + width = TILE_X / 2; + } else { + width = TILE_X; + } + left += vesa_x_center; + top += vesa_y_center; + right = left + width - 1; + bottom = top + TILE_Y - 1; + + for (y = 0; y < ROWS_PER_CELL; ++y) { + for (x = 0; x < width; ++x) { + undercursor[y][x] = vesa_ReadPixel32(left + x, top + y); + } + } + + /* + * Now we have a snapshot of the current cell. + * Write the cursor on top of the display. + */ + + if (inmap) + curtyp = cursor_type; + else + curtyp = CURSOR_UNDERLINE; + + switch (curtyp) { + case CURSOR_CORNER: + vesa_WritePixel(left , top , FIRST_TEXT_COLOR + 15); + vesa_WritePixel(left + 1, top , FIRST_TEXT_COLOR + 15); + vesa_WritePixel(right - 1, top , FIRST_TEXT_COLOR + 15); + vesa_WritePixel(right , top , FIRST_TEXT_COLOR + 15); + vesa_WritePixel(left , top + 1, FIRST_TEXT_COLOR + 15); + vesa_WritePixel(right , top + 1, FIRST_TEXT_COLOR + 15); + vesa_WritePixel(left , bottom - 1, FIRST_TEXT_COLOR + 15); + vesa_WritePixel(right , bottom - 1, FIRST_TEXT_COLOR + 15); + vesa_WritePixel(left , bottom , FIRST_TEXT_COLOR + 15); + vesa_WritePixel(left + 1, bottom , FIRST_TEXT_COLOR + 15); + vesa_WritePixel(right - 1, bottom , FIRST_TEXT_COLOR + 15); + vesa_WritePixel(right , bottom , FIRST_TEXT_COLOR + 15); + break; + + case CURSOR_UNDERLINE: + for (x = left; x <= right; ++x) { + vesa_WritePixel(x, bottom, FIRST_TEXT_COLOR + 15); + } + break; + + case CURSOR_FRAME: + + /* fall through */ + + default: + for (x = left; x <= right; ++x) { + vesa_WritePixel(x, top, FIRST_TEXT_COLOR + 15); + } + for (y = top + 1; y <= bottom - 1; ++y) { + vesa_WritePixel(left , y, FIRST_TEXT_COLOR + 15); + vesa_WritePixel(right, y, FIRST_TEXT_COLOR + 15); + } + for (x = left; x <= right; ++x) { + vesa_WritePixel(x, bottom, FIRST_TEXT_COLOR + 15); + } + break; + } +#ifdef POSITIONBAR + if (inmap) + positionbar(); +#endif +} + +void +vesa_HideCursor() +{ + unsigned x, y, left, top, width; + boolean isrogue = Is_rogue_level(&u.uz); + boolean halfwidth = + (isrogue || iflags.over_view || iflags.traditional_view || !inmap); + int curtyp; + + if (!cursor_type && inmap) + return; /* CURSOR_INVIS - nothing to do */ + + x = min(curcol, (CO - 1)); /* protection from callers */ + y = min(currow, (LI - 1)); /* protection from callers */ + if (!halfwidth && ((x < clipx) || (x > clipxmax))) + return; + if (inmap) + x -= clipx; + left = x * TILE_X; /* convert to pixels */ + top = y * TILE_Y; + if (halfwidth) { + left /= 2; + width = TILE_X / 2; + } else { + width = TILE_X; + } + left += vesa_x_center; + top += vesa_y_center; + + for (y = 0; y < ROWS_PER_CELL; ++y) { + for (x = 0; x < width; ++x) { + vesa_WritePixel32(left + x, top + y, undercursor[y][x]); + } + } +} +#endif /* SIMULATE_CURSOR */ +#endif /* SCREEN_VESA */ From 40f2994ba776df1145c947d04645d35b1609aa1f Mon Sep 17 00:00:00 2001 From: nhmall Date: Mon, 7 Mar 2016 08:40:03 -0500 Subject: [PATCH 002/151] giftiles.c --- win/share/giftiles.c | 512 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 512 insertions(+) create mode 100644 win/share/giftiles.c diff --git a/win/share/giftiles.c b/win/share/giftiles.c new file mode 100644 index 000000000..e4e44ba8f --- /dev/null +++ b/win/share/giftiles.c @@ -0,0 +1,512 @@ +/* NetHack 3.6 tileset.h $NHDT-Date: 1457207052 2016/03/05 19:44:12 $ $NHDT-Branch: chasonr $:$NHDT-Revision: 1.0 $ */ +/* Copyright (c) Ray Chason, 2016. */ +/* NetHack may be freely redistributed. See license for details. */ + +/* giftiles.c -- read a tile map in GIF format */ +/* Reference: GIF specification,, retrieved from + * http://www.w3.org/Graphics/GIF/spec-gif89a.txt */ + +#include "config.h" +#include "tileset.h" + +#define MIN_LZW_BITS 3 +#define MAX_LZW_BITS 12 +#define END_OF_DATA 0x7FFF + +/* For use when reading the GIF as a bitstream */ +struct Bitstream { + /* The file */ + FILE *fp; + + /* For unpacking LZW codes */ + unsigned long bits; + unsigned char num_bits; + unsigned char bit_width; + unsigned char initial_bit_width; + unsigned char block_size; + + /* The dictionary */ + struct { + unsigned char byte; + unsigned short next; + } dictionary[1 << MAX_LZW_BITS]; + unsigned short dict_size; + + /* The string currently being decoded */ + unsigned char string[1 << MAX_LZW_BITS]; + unsigned short str_size; + + unsigned short last_code; +}; + +struct DataBlock { + size_t size; + size_t index; + unsigned char *data; +}; + +static boolean FDECL(read_data_block, (struct Bitstream *gif, struct DataBlock *block)); +static void FDECL(free_data_block, (struct DataBlock *block)); +static unsigned short FDECL(read_u16, (const unsigned char buf[2])); +static void FDECL(init_decoder, (struct Bitstream *gif, unsigned bit_width)); +static void FDECL(reset_decoder, (struct Bitstream *gif)); +static int FDECL(decode, (struct Bitstream *gif, struct DataBlock *block)); +static int FDECL(get_code, (struct Bitstream *gif, struct DataBlock *block)); +static unsigned FDECL(interlace_incr, (unsigned y, unsigned height)); + +/* + * GIF specifies a canvas, which may have a palette (the "global color table") + * of up to 256 colors, of which one is a background color, and zero or more + * images, each of which may have its own palette (a "local color table") + * independently of the canvas. We will join all palettes found into a single + * palette, and indicate that the image uses a palette if, and only if, the + * various palettes do not total more than 256 colors. + */ + +boolean +read_gif_tiles(filename, image) +const char *filename; +struct TileSetImage *image; +{ + struct Bitstream gif; + struct DataBlock block; + + unsigned char buf[1024]; + size_t size, num_pixels, i; + + /* Image data not returned to the caller */ + boolean have_gct; /* global color table is present */ + unsigned gct_size; /* global color table size */ + unsigned back_color; /* index for background color */ + unsigned trans_color = 0xFFFF; /* index for transparent palette entry */ + + block.data = NULL; /* custodial */ + gif.fp = NULL; /* custodial */ + + image->width = 0; + image->height = 0; + image->pixels = NULL; /* custodial, returned */ + image->indexes = NULL; /* custodial, returned */ + image->image_desc = NULL; /* custodial, returned */ + image->tile_width = 0; + image->tile_height = 0; + + gif.fp = fopen(filename, "rb"); + if (gif.fp == NULL) goto error; + + /* 17. Header */ + size = fread(buf, 1, 6, gif.fp); + if (size < 6) goto error; + if (memcmp(buf, "GIF87a", 6) != 0 && memcmp(buf, "GIF89a", 6) != 0) + goto error; + + /* 18. Logical screen descriptor */ + size = fread(buf, 1, 7, gif.fp); + if (size < 7) goto error; + image->width = read_u16(buf + 0); + image->height = read_u16(buf + 2); + have_gct = (buf[4] & 0x80) != 0; + gct_size = 1 << ((buf[4] & 0x07) + 1); + back_color = buf[5]; + if (image->width == 0 || image->height == 0) goto error; + + /* 19. Global Color Table */ + for (i = 0; i < SIZE(image->palette); ++i) { + image->palette[i].r = 0; + image->palette[i].g = 0; + image->palette[i].b = 0; + image->palette[i].a = 255; + } + if (have_gct) { + size = fread(buf, 3, gct_size, gif.fp); + if (size < gct_size) goto error; + for (i = 0; i < gct_size; ++i) { + image->palette[i].r = buf[i * 3 + 0]; + image->palette[i].g = buf[i * 3 + 1]; + image->palette[i].b = buf[i * 3 + 2]; + image->palette[i].a = 255; + } + } + + /* Allocate pixel area; watch out for overflow */ + num_pixels = (size_t) image->width * (size_t) image->height; + if (num_pixels / image->width != image->height) goto error; /* overflow */ + size = num_pixels * sizeof(image->pixels[0]); + if (size / sizeof(image->pixels[0]) != num_pixels) goto error; /* overflow */ + image->pixels = (struct Pixel *) alloc(size); + image->indexes = (unsigned char *) alloc(num_pixels); + + /* Fill with the background color */ + for (i = 0; i < num_pixels; ++i) { + image->pixels[i] = image->palette[back_color]; + image->indexes[i] = back_color; + } + + /* Read the image data */ + while (TRUE) { + int b = fgetc(gif.fp); + if (b == EOF) goto error; + + /* 27. Trailer (0x3B) */ + if (b == 0x3B) break; + + switch (b) { + case 0x2C: + /* 20. Image descriptor (0x2C) */ + { + unsigned img_left, img_top, img_width, img_height; + boolean have_lct, interlace; + unsigned lct_start, lct_size; + struct Pixel lct[256]; + int b; + unsigned x, y, x2, y2; + + size = fread(buf, 1, 9, gif.fp); + if (size < 9) goto error; + img_left = read_u16(buf + 0); + img_top = read_u16(buf + 2); + img_width = read_u16(buf + 4); + img_height = read_u16(buf + 6); + have_lct = (buf[8] & 0x80) != 0; + interlace = (buf[8] & 0x40) != 0; + lct_size = 1 << ((buf[8] & 0x07) + 1); + + /* 21. Local color table */ + lct_start = 0; + memcpy(lct, image->palette, sizeof(lct)); + if (have_lct) { + size = fread(buf, 3, lct_size, gif.fp); + if (size < lct_size) goto error; + for (i = 0; i < lct_size; ++i) { + lct[i].r = buf[i * 3 + 0]; + lct[i].g = buf[i * 3 + 1]; + lct[i].b = buf[i * 3 + 2]; + lct[i].a = 255; + } + /* + * The combined palette may exceed 256 colors, in which + * case the indexes array will be discarded, indicating a + * full-color image. + */ + if (lct_size + gct_size <= 256) { + memcpy(image->palette, lct, sizeof(lct[0]) * lct_size); + } + lct_start = gct_size; + gct_size += lct_start; + } + if (trans_color != 0xFFFF) { + lct[trans_color].a = 0; + if (!have_lct) { + /* FIXME: this will affect all images using the global + * color table, not just the current one */ + image->palette[trans_color].a = 0; + } + } + /* 22. Table based image data */ + b = fgetc(gif.fp); + if (b == EOF) goto error; + if (b < MIN_LZW_BITS - 1 || MAX_LZW_BITS - 1 < b) goto error; + init_decoder(&gif, b); + x = 0; + y = 0; + if (!read_data_block(&gif, &block)) goto error; + while (TRUE) { + b = decode(&gif, &block); + if (b == EOF) goto error; + if (b == END_OF_DATA) break; + if (y >= img_height) goto error; + x2 = img_left + x; + y2 = img_top + y; + if (x2 < image->width && y2 < image->height) { + image->pixels[y2 * image->width + x2] = lct[b]; + image->indexes[y2 * image->width + x2] = b + lct_start; + } + ++x; + if (x >= img_width) { + x = 0; + if (interlace) { + y = interlace_incr(y, img_height); + } else { + ++y; + } + } + } + free_data_block(&block); + trans_color = 0xFFFF; + } + break; + + case 0x21: + /* Extension blocks */ + { + int label; + + label = fgetc(gif.fp); + if (label == EOF) goto error; + if (!read_data_block(&gif, &block)) goto error; + switch (label) { + case 0xF9: + /* 23. Graphic control extension (0xF9) */ + if (block.size >= 4 && (block.data[0] & 0x01) != 0) { + /* image has a transparent index */ + trans_color = block.data[3]; + } + break; + +#if 0 + case 0xFE: + /* 24. Comment extension (0xFE) */ + break; + + case 0x01: + /* 25. Plain text extension (0x01) */ + break; +#endif + + case 0xFF: + /* 26. Application extension (0xFF) */ + if (block.size > 11 + && memcmp(block.data, "NETHACK3GIF", 11) == 0 + && image->image_desc == NULL) { + memmove(block.data, block.data + 11, block.size - 11); + block.data[block.size - 11] = '\0'; + image->image_desc = (char *) block.data; + block.data = NULL; + } + break; + + default: + /* Unknown extension type */ + break; + } + free_data_block(&block); + } + break; + + default: + goto error; + } + } + + fclose(gif.fp); + free_data_block(&block); + if (gct_size > 256) { + /* Max palette size exceeded; indexes array is not meaningful */ + free(image->indexes); + image->indexes = NULL; + } + return TRUE; + +error: + if (gif.fp) fclose(gif.fp); + free_data_block(&block); + free(image->pixels); + image->pixels = NULL; + free(image->indexes); + image->indexes = NULL; + free(image->image_desc); + image->image_desc = NULL; + return FALSE; +} + +static void +init_decoder(gif, bit_width) +struct Bitstream *gif; +unsigned bit_width; +{ + unsigned i; + unsigned clear; + + gif->bits = 0; + gif->num_bits = 0; + gif->initial_bit_width = bit_width; + gif->block_size = 0; + + clear = 1 << bit_width; + gif->dict_size = clear + 2; + for (i = 0; i < clear; ++i) { + gif->dictionary[i].byte = i; + gif->dictionary[i].next = 0xFFFF; + } + + gif->str_size = 0; + + reset_decoder(gif); +} + +static void +reset_decoder(gif) +struct Bitstream *gif; +{ + /* Set the bit width */ + gif->bit_width = gif->initial_bit_width + 1; + + /* Reset the dictionary */ + gif->dict_size = (1 << gif->initial_bit_width) + 2; + + /* No last code */ + gif->last_code = 0xFFFF; +} + +static int +decode(gif, block) +struct Bitstream *gif; +struct DataBlock *block; +{ + int code; + unsigned clear = 1 << gif->initial_bit_width; + + /* If a string is being decoded, return the next byte */ + if (gif->str_size != 0) { + return gif->string[--gif->str_size]; + } + + /* Get the next code, until code other than clear */ + while (TRUE) { + code = get_code(gif, block); + if (code != clear) break; + reset_decoder(gif); + } + + if (code == EOF) return EOF; + if (code == clear + 1) return END_OF_DATA; + if (code > gif->dict_size) return EOF; + + /* Add a new string to the dictionary */ + if (gif->last_code != 0xFFFF && gif->dict_size < SIZE(gif->dictionary)) { + unsigned next_code; + if (code < gif->dict_size) { + next_code = code; + } else { + next_code = gif->last_code; + } + while (next_code >= clear) { + next_code = gif->dictionary[next_code].next; + } + gif->dictionary[gif->dict_size].next = gif->last_code; + gif->dictionary[gif->dict_size].byte = next_code; + ++gif->dict_size; + if (gif->dict_size >= 1 << gif->bit_width + && gif->bit_width < MAX_LZW_BITS) { + ++gif->bit_width; + } + } + gif->last_code = code; + + /* code is less than gif->dict_size and not equal to clear or clear + 1 */ + /* Prepare the decoded string for return; note that it is stored in + * reverse order */ + while (code >= clear) { + gif->string[gif->str_size++] = gif->dictionary[code].byte; + code = gif->dictionary[code].next; + } + + return code; +} + +static int +get_code(gif, block) +struct Bitstream *gif; +struct DataBlock *block; +{ + int code; + + while (gif->num_bits < gif->bit_width) { + unsigned char b; + if (block->index >= block->size) return EOF; + b = block->data[block->index++]; + gif->bits |= (unsigned long)b << gif->num_bits; + gif->num_bits += 8; + } + + code = (int) (gif->bits & ((1UL << gif->bit_width) - 1)); + gif->bits >>= gif->bit_width; + gif->num_bits -= gif->bit_width; + return code; +} + +static unsigned +interlace_incr(y, height) +unsigned y; +unsigned height; +{ + static const unsigned char incr[] = { 8, 2, 4, 2 }; + + /* The lower three bits indicate the current pass */ + + /* Advance to the next row of the current pass */ + y += incr[y & 0x3]; + + /* Go to the next pass if y exceeds height */ + /* Might not be the immediately following pass if height is small */ + if (y >= height && (y & 0x7) == 0) { + /* Pass 1 -> Pass 2 */ + y = 4; + } + if (y >= height && (y & 0x7) == 4) { + /* Pass 2 -> Pass 3 */ + y = 2; + } + if (y >= height && (y & 0x3) == 2) { + /* Pass 3 -> Pass 4 */ + y = 1; + } + + return y; +} + +/* Decode an unsigned 16 bit quantity */ +static unsigned short +read_u16(buf) +const unsigned char buf[2]; +{ + return ((unsigned short)buf[0] << 0) + | ((unsigned short)buf[1] << 8); +} + +static boolean +read_data_block(gif, block) +struct Bitstream *gif; +struct DataBlock *block; +{ + long pos = ftell(gif->fp); + int b; + size_t i; + + free_data_block(block); + + /* Get the length of the data block */ + while (TRUE) { + b = fgetc(gif->fp); + if (b == EOF) return FALSE; + if (b == 0) break; + block->size += b; + fseek(gif->fp, b, SEEK_CUR); + } + fseek(gif->fp, pos, SEEK_SET); + + /* Allocate memory */ + block->data = (unsigned char *) alloc(block->size); + + /* Read the data from the file */ + i = 0; + while (TRUE) { + b = fgetc(gif->fp); + if (b == EOF) return FALSE; + if (b == 0) break; + if (fread(block->data + i, 1, b, gif->fp) != b) return FALSE; + i += b; + } + + block->index = 0; + return TRUE; +} + +static void +free_data_block(block) +struct DataBlock *block; +{ + free(block->data); + block->size = 0; + block->data = NULL; +} From 87f4aca12ec40b32a8933993906306a9c4fecf78 Mon Sep 17 00:00:00 2001 From: nhmall Date: Mon, 7 Mar 2016 08:41:39 -0500 Subject: [PATCH 003/151] add giftiles.c to Files --- Files | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Files b/Files index 322450292..a70ae72ad 100644 --- a/Files +++ b/Files @@ -264,9 +264,9 @@ NetHackTerm.applescript recover.pl win/share: (files for versions using optional tiles) -gifread.c monsters.txt objects.txt other.txt ppmwrite.c -renumtiles.pl thintile.c tile.doc tile.h tile2bmp.c -tilemap.c tiletext.c +gifread.c giftiles.c monsters.txt objects.txt other.txt +ppmwrite.c renumtiles.pl thintile.c tile.doc tile.h +tile2bmp.c tilemap.c tiletext.c win/tty: (files for tty versions) From 730adeebefd692c4afb022292404b0989e34dae2 Mon Sep 17 00:00:00 2001 From: nhmall Date: Mon, 7 Mar 2016 08:46:57 -0500 Subject: [PATCH 004/151] cut and paste error --- win/share/giftiles.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/win/share/giftiles.c b/win/share/giftiles.c index e4e44ba8f..911ea3c21 100644 --- a/win/share/giftiles.c +++ b/win/share/giftiles.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 tileset.h $NHDT-Date: 1457207052 2016/03/05 19:44:12 $ $NHDT-Branch: chasonr $:$NHDT-Revision: 1.0 $ */ +/* NetHack 3.6 giftiles.c $NHDT-Date: 1457358406 2016/03/07 13:46:46 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.0 $ */ /* Copyright (c) Ray Chason, 2016. */ /* NetHack may be freely redistributed. See license for details. */ From e3d2dfc7fba6b8abfeb0cd204d7e2e4096fb3d62 Mon Sep 17 00:00:00 2001 From: nhmall Date: Mon, 7 Mar 2016 18:21:25 -0500 Subject: [PATCH 005/151] fix post-3.6.0 shopkeeper message when deaf --- src/shk.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/shk.c b/src/shk.c index bb593f1fc..6cf62f2c0 100644 --- a/src/shk.c +++ b/src/shk.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 shk.c $NHDT-Date: 1455403704 2016/02/13 22:48:24 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.124 $ */ +/* NetHack 3.6 shk.c $NHDT-Date: 1457392872 2016/03/07 23:21:12 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.125 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -575,7 +575,8 @@ char *enterstring; if (!Deaf && !muteshk(shkp)) verbalize("Invisible customers are not welcome!"); else - pline("%s stands firm as if he knows you are there.", Shknam(shkp)); + pline("%s stands firm as if %s knows you are there.", + Shknam(shkp), mhe(shkp)); return; } From 6106a7240fdf9dd3cbdfda54469ddb30442b7c66 Mon Sep 17 00:00:00 2001 From: PatR Date: Mon, 7 Mar 2016 16:38:05 -0800 Subject: [PATCH 006/151] fix bz238 - looting many containers "Looting many containers via menu cannot be stopped". When the player uses #loot command at a location with multiple containers, a menu of which ones to loot is presented and player can pick any or all of them. But if you terminate the looting of a particular container with ESC, it goes on to the next selected one rather than stopping the loot action because that's what the 'q' choice does. The simplest fix would be to allow choosing only one container from the "loot which?" menu, but this retains the ability to loot multiple containers on a pile in one turn. It makes looting stoppable by extending the ":iobrsq or ?" prompt, adding 'n' for "next container" and changing 'q' from "done with this container" to "done looting" (with ESC still a synonym for 'q'). When just one container is being looted, or when on the last of N containers, 'n' is not shown but is still accepted (and treated as 'q'). Also, use_container() was using a menu for ":iobrsq" if player had menustyle set to Full when it was intended to be for Partial (name confusion...). This switches Partial to use menu for loot action, and leaves Full with that since that's how 3.6.0 has been behaving. Traditional and Combination use the prompt string and single char response. --- doc/fixes36.1 | 3 ++ include/extern.h | 4 +- src/apply.c | 4 +- src/pickup.c | 120 +++++++++++++++++++++++++++++++---------------- 4 files changed, 87 insertions(+), 44 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index d382342ee..c2f614219 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -177,6 +177,9 @@ a few types of monster (barrow wight, Nazgul, erinys) have weapon attacks that cockatrice corpse wouldn't be inflicted non-item-using monsters who happened to be carrying scroll or wand of fire or a fire horn could use it to cure themselves of being turned into slime +when looting multiple containers, add 'n' for "loot next container", change + 'q' and ESC from "done with this container" to "done looting" +change looting to choose ":iobrs nq" action from a menu for menustyle:Partial Platform- and/or Interface-Specific Fixes diff --git a/include/extern.h b/include/extern.h index 4277d3b63..a0b47449a 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1,4 +1,4 @@ -/* NetHack 3.6 extern.h $NHDT-Date: 1456992437 2016/03/03 08:07:17 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.550 $ */ +/* NetHack 3.6 extern.h $NHDT-Date: 1457397476 2016/03/08 00:37:56 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.551 $ */ /* Copyright (c) Steve Creps, 1988. */ /* NetHack may be freely redistributed. See license for details. */ @@ -1763,7 +1763,7 @@ E int NDECL(encumber_msg); E int NDECL(doloot); E boolean FDECL(container_gone, (int (*)(OBJ_P))); E boolean NDECL(u_handsy); -E int FDECL(use_container, (struct obj **, int)); +E int FDECL(use_container, (struct obj **, int, BOOLEAN_P)); E int FDECL(loot_mon, (struct monst *, int *, boolean *)); E int NDECL(dotip); E boolean FDECL(is_autopickup_exception, (struct obj *, BOOLEAN_P)); diff --git a/src/apply.c b/src/apply.c index e30542dd2..ddd253aee 100644 --- a/src/apply.c +++ b/src/apply.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 apply.c $NHDT-Date: 1457207021 2016/03/05 19:43:41 $ $NHDT-Branch: chasonr $:$NHDT-Revision: 1.223 $ */ +/* NetHack 3.6 apply.c $NHDT-Date: 1457397477 2016/03/08 00:37:57 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.224 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -3430,7 +3430,7 @@ doapply() case SACK: case BAG_OF_HOLDING: case OILSKIN_SACK: - res = use_container(&obj, 1); + res = use_container(&obj, 1, FALSE); break; case BAG_OF_TRICKS: (void) bagotricks(obj, FALSE, (int *) 0); diff --git a/src/pickup.c b/src/pickup.c index fe738fa26..7a5deef0d 100644 --- a/src/pickup.c +++ b/src/pickup.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 pickup.c $NHDT-Date: 1453591408 2016/01/23 23:23:28 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.169 $ */ +/* NetHack 3.6 pickup.c $NHDT-Date: 1457397478 2016/03/08 00:37:58 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.170 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -33,16 +33,16 @@ STATIC_PTR int FDECL(out_container, (struct obj *)); STATIC_DCL void FDECL(removed_from_icebox, (struct obj *)); STATIC_DCL long FDECL(mbag_item_gone, (int, struct obj *)); STATIC_DCL void FDECL(observe_quantum_cat, (struct obj *)); -STATIC_DCL void NDECL(explain_container_prompt); +STATIC_DCL void FDECL(explain_container_prompt, (BOOLEAN_P)); STATIC_DCL int FDECL(traditional_loot, (BOOLEAN_P)); STATIC_DCL int FDECL(menu_loot, (int, BOOLEAN_P)); STATIC_DCL char FDECL(in_or_out_menu, (const char *, struct obj *, BOOLEAN_P, - BOOLEAN_P, BOOLEAN_P)); + BOOLEAN_P, BOOLEAN_P, BOOLEAN_P)); STATIC_DCL int FDECL(container_at, (int, int, BOOLEAN_P)); STATIC_DCL boolean FDECL(able_to_loot, (int, int, BOOLEAN_P)); STATIC_DCL boolean NDECL(reverse_loot); STATIC_DCL boolean FDECL(mon_beside, (int, int)); -STATIC_DCL int FDECL(do_loot_cont, (struct obj **)); +STATIC_DCL int FDECL(do_loot_cont, (struct obj **, int, int)); STATIC_DCL void FDECL(tipcontainer, (struct obj *)); /* define for query_objlist() and autopickup() */ @@ -66,6 +66,7 @@ STATIC_DCL void FDECL(tipcontainer, (struct obj *)); /* in_container() and out_container() from askchain() and use_container(). */ /* Also used by menu_loot() and container_gone(). */ static NEARDATA struct obj *current_container; +static NEARDATA boolean abort_looting; #define Icebox (current_container->otyp == ICE_BOX) static const char @@ -1579,16 +1580,22 @@ int x, y; } int -do_loot_cont(cobjp) +do_loot_cont(cobjp, cindex, ccount) struct obj **cobjp; +int cindex, ccount; /* index of this container (1..N), number of them (N) */ { struct obj *cobj = *cobjp; if (!cobj) return 0; if (cobj->olocked) { - pline("%s locked.", - cobj->lknown ? "It is" : "Hmmm, it turns out to be"); + if (ccount < 2) + pline("%s locked.", + cobj->lknown ? "It is" : "Hmmm, it turns out to be"); + else if (cobj->lknown) + pline("%s is locked.", The(xname(cobj))); + else + pline("Hmmm, %s turns out to be locked.", the(xname(cobj))); cobj->lknown = 1; return 0; } @@ -1597,17 +1604,18 @@ struct obj **cobjp; if (cobj->otyp == BAG_OF_TRICKS) { int tmp; - You("carefully open the bag..."); + You("carefully open %s...", the(xname(cobj))); pline("It develops a huge set of teeth and bites you!"); tmp = rnd(10); losehp(Maybe_Half_Phys(tmp), "carnivorous bag", KILLED_BY_AN); makeknown(BAG_OF_TRICKS); + abort_looting = TRUE; return 1; } You("%sopen %s...", (!cobj->cknown || !cobj->lknown) ? "carefully " : "", the(xname(cobj))); - return use_container(cobjp, 0); + return use_container(cobjp, 0, (boolean) (cindex < ccount)); } /* loot a container on the floor or loot saddle from mon. */ @@ -1626,6 +1634,8 @@ doloot() boolean prev_loot = FALSE; int num_conts; + abort_looting = FALSE; + if (check_capacity((char *) 0)) { /* "Can't do that while carrying so much stuff." */ return 0; @@ -1676,16 +1686,17 @@ lootcont: destroy_nhwindow(win); if (n > 0) { - for (i = 0; i < n; i++) { - timepassed |= do_loot_cont(&pick_list[i].item.a_obj); - if (multi < 0 || !pick_list[i].item.a_obj) { + for (i = 1; i <= n; i++) { + cobj = pick_list[i - 1].item.a_obj; + timepassed |= do_loot_cont(&cobj, i, n); + if (abort_looting) { + /* chest trap or magic bag explosion or */ free((genericptr_t) pick_list); - return 1; + return timepassed; } } - } - if (pick_list) free((genericptr_t) pick_list); + } if (n != 0) c = 'y'; } else { @@ -1702,11 +1713,10 @@ lootcont: continue; anyfound = TRUE; - timepassed |= do_loot_cont(&cobj); - /* might have triggered chest trap or magic bag explosion - */ - if (multi < 0 || !cobj) - return 1; + timepassed |= do_loot_cont(&cobj, 1, 1); + if (abort_looting) + /* chest trap or magic bag explosion or */ + return timepassed; } } if (anyfound) @@ -2234,14 +2244,16 @@ int FDECL((*fn), (OBJ_P)); } STATIC_OVL void -explain_container_prompt() +explain_container_prompt(more_containers) +boolean more_containers; { static const char *const explaintext[] = { "Container actions:", "", " : -- Look: examine contents", " o -- Out: take things out", " i -- In: put things in", " b -- Both: first take things out, then put things in", " r -- Reversed: put things in, then take things out", - " s -- Stash: put one item in", " q -- Quit: do nothing", + " s -- Stash: put one item in", "", + " n -- Next: loot next selected container", " q -- Quit: finished", " ? -- Help: display this text.", "", 0 }; const char *const *txtpp; @@ -2249,8 +2261,11 @@ explain_container_prompt() /* "Do what with ? [:oibrsq or ?] (q)" */ if ((win = create_nhwindow(NHW_TEXT)) != WIN_ERR) { - for (txtpp = explaintext; *txtpp; ++txtpp) + for (txtpp = explaintext; *txtpp; ++txtpp) { + if (!more_containers && !strncmp(*txtpp, " n ", 3)) + continue; putstr(win, 0, *txtpp); + } display_nhwindow(win, FALSE); destroy_nhwindow(win); } @@ -2272,9 +2287,10 @@ u_handsy() static const char stashable[] = { ALLOW_COUNT, COIN_CLASS, ALL_CLASSES, 0 }; int -use_container(objp, held) +use_container(objp, held, more_containers) struct obj **objp; int held; +boolean more_containers; /* True iff #loot multiple and this isn't last one */ { struct obj *curr, *otmp, *obj = *objp; boolean quantum_cat, cursed_mbag, loot_out, loot_in, loot_in_first, @@ -2282,6 +2298,7 @@ int held; char c, emptymsg[BUFSZ], qbuf[QBUFSZ], pbuf[QBUFSZ], xbuf[QBUFSZ]; int used = 0; + abort_looting = FALSE; emptymsg[0] = '\0'; if (!u_handsy()) @@ -2304,12 +2321,15 @@ int held; multi_reason = "opening a container"; nomovemsg = ""; } + abort_looting = TRUE; return 1; } obj->lknown = 1; current_container = obj; /* for use by in/out_container */ - /* from here on out, all early returns go through containerdone */ + /* + * From here on out, all early returns go through 'containerdone:'. + */ /* check for Schroedinger's Cat */ quantum_cat = SchroedingersBox(current_container); @@ -2375,14 +2395,15 @@ int held; (void) safe_qbuf(qbuf, "Do what with ", "?", current_container, yname, ysimple_name, "it"); /* ask player about what to do with this container */ - if (flags.menu_style == MENU_FULL) { + if (flags.menu_style == MENU_PARTIAL + || flags.menu_style == MENU_FULL) { if (!inokay && !outmaybe) { /* nothing to take out, nothing to put in; trying to do both will yield proper feedback */ c = 'b'; } else { c = in_or_out_menu(qbuf, current_container, outmaybe, inokay, - (used != 0)); + (boolean) (used != 0), more_containers); } } else { /* TRADITIONAL, COMBINATION, or PARTIAL */ xbuf[0] = '\0'; /* list of extra acceptable responses */ @@ -2391,18 +2412,22 @@ int held; Strcat(inokay ? pbuf : xbuf, "i"); /* put in */ Strcat(outmaybe ? pbuf : xbuf, "b"); /* both */ Strcat(inokay ? pbuf : xbuf, "rs"); /* reversed, stash */ + Strcat(pbuf, " "); + Strcat(more_containers ? pbuf : xbuf, "n"); Strcat(pbuf, "q"); /* quit */ if (iflags.cmdassist) + /* this unintentionally allows user to answer with 'o' or + 'r'; fortunately, those are already valid choices here */ Strcat(pbuf, " or ?"); /* help */ else Strcat(xbuf, "?"); if (*xbuf) Strcat(strcat(pbuf, "\033"), xbuf); - c = yn_function(qbuf, pbuf, 'q'); - } /* FULL vs other modes */ + c = yn_function(qbuf, pbuf, more_containers ? 'n' : 'q'); + } /* PARTIAL|FULL vs other modes */ if (c == '?') { - explain_container_prompt(); + explain_container_prompt(more_containers); } else if (c == ':') { /* note: will set obj->cknown */ if (!current_container->cknown) used = 1; /* gaining info */ @@ -2411,7 +2436,9 @@ int held; break; } /* loop until something other than '?' or ':' is picked */ - if (c == 'q') /* [not strictly needed; falling through works] */ + if (c == 'q') + abort_looting = TRUE; + if (c == 'n' || c == 'q') /* [not strictly needed; falling thru works] */ goto containerdone; loot_out = (c == 'o' || c == 'b' || c == 'r'); loot_in = (c == 'i' || c == 'b' || c == 'r'); @@ -2499,7 +2526,10 @@ containerdone: } *objp = current_container; /* might have become null */ - current_container = 0; /* avoid hanging on to stale pointer */ + if (current_container) + current_container = 0; /* avoid hanging on to stale pointer */ + else + abort_looting = TRUE; return used; } @@ -2623,13 +2653,13 @@ boolean put_in; } STATIC_OVL char -in_or_out_menu(prompt, obj, outokay, inokay, alreadyused) +in_or_out_menu(prompt, obj, outokay, inokay, alreadyused, more_containers) const char *prompt; struct obj *obj; -boolean outokay, inokay, alreadyused; +boolean outokay, inokay, alreadyused, more_containers; { /* underscore is not a choice; it's used to skip element [0] */ - static const char lootchars[] = "_:oibrsq", abc_chars[] = "_:abcdeq"; + static const char lootchars[] = "_:oibrsnq", abc_chars[] = "_:abcdenq"; winid win; anything any; menu_item *pick_list; @@ -2674,20 +2704,30 @@ boolean outokay, inokay, alreadyused; add_menu(win, NO_GLYPH, &any, menuselector[any.a_int], 0, ATR_NONE, buf, MENU_UNSELECTED); } - any.a_int = 7; /* 'q' */ + any.a_int = 0; + add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED); + if (more_containers) { + any.a_int = 7; /* 'n' */ + add_menu(win, NO_GLYPH, &any, menuselector[any.a_int], 0, ATR_NONE, + "loot next container", MENU_SELECTED); + } + any.a_int = 8; /* 'q' */ Strcpy(buf, alreadyused ? "done" : "do nothing"); add_menu(win, NO_GLYPH, &any, menuselector[any.a_int], 0, ATR_NONE, buf, - MENU_SELECTED); + more_containers ? MENU_UNSELECTED : MENU_SELECTED); end_menu(win, prompt); n = select_menu(win, PICK_ONE, &pick_list); destroy_nhwindow(win); if (n > 0) { - n = pick_list[0].item.a_int; + int k = pick_list[0].item.a_int; + + if (n > 1 && k == (more_containers ? 7 : 8)) + k = pick_list[1].item.a_int; free((genericptr_t) pick_list); - return lootchars[n]; /* :,o,i,b,r,s,q */ + return lootchars[k]; /* :,o,i,b,r,s,n,q */ } - return 'q'; /* quit */ + return (n == 0 && more_containers) ? 'n' : 'q'; /* next or quit */ } static const char tippables[] = { ALL_CLASSES, TOOL_CLASS, 0 }; From f28a498d03b5a70fd93ede4373f65087b80708a7 Mon Sep 17 00:00:00 2001 From: PatR Date: Mon, 7 Mar 2016 17:35:52 -0800 Subject: [PATCH 007/151] use_containers() comments Catch the comments up with the 'loot many containers' fix. --- src/pickup.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/pickup.c b/src/pickup.c index 7a5deef0d..38c7aeea7 100644 --- a/src/pickup.c +++ b/src/pickup.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 pickup.c $NHDT-Date: 1457397478 2016/03/08 00:37:58 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.170 $ */ +/* NetHack 3.6 pickup.c $NHDT-Date: 1457400916 2016/03/08 01:35:16 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.171 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -2375,17 +2375,20 @@ boolean more_containers; /* True iff #loot multiple and this isn't last one */ * inventory is empty--taking out could alter that; * include do-both-reversed when 'i' is available, * even if container is empty--for similar reason; - * always include the quit choice ('q'). + * include the next container choice ('n') when + * relevant, and make it the default; + * always include the quit choice ('q'), and make + * it the default if there's no next containter; * include the help choice (" or ?") if `cmdassist' * run-time option is set; - * (Player can pick any of (o,i,b,r,s,?) even when + * (Player can pick any of (o,i,b,r,n,s,?) even when * they're not listed among the available actions.) * - * Do what with ? [:oibrsq or ?] (q) + * Do what with ? [:oibrs nq or ?] (q) * or - * is empty. Do what with it? [:irsq or ?] + * is empty. Do what with it? [:irs nq or ?] */ - for (;;) { /* repeats if '?' or ":' gets chosen */ + for (;;) { /* repeats iff '?' or ":' gets chosen */ outmaybe = (outokay || !current_container->cknown); if (!outmaybe) (void) safe_qbuf(qbuf, (char *) 0, " is empty. Do what with it?", @@ -2405,7 +2408,7 @@ boolean more_containers; /* True iff #loot multiple and this isn't last one */ c = in_or_out_menu(qbuf, current_container, outmaybe, inokay, (boolean) (used != 0), more_containers); } - } else { /* TRADITIONAL, COMBINATION, or PARTIAL */ + } else { /* TRADITIONAL or COMBINATION */ xbuf[0] = '\0'; /* list of extra acceptable responses */ Strcpy(pbuf, ":"); /* look inside */ Strcat(outmaybe ? pbuf : xbuf, "o"); /* take out */ From 9a1bbbd14d23fac8a2befdbca35f4177f36fa145 Mon Sep 17 00:00:00 2001 From: PatR Date: Tue, 8 Mar 2016 16:22:05 -0800 Subject: [PATCH 008/151] fix #H4062, pt 2 - zaps at edge on Plane of Air Pt 1 was about the wrong message delivered when a high priest rejects being given a name by the player, and was fixed weeks ago. Pt 2 is about zaps on the Elemental Plane of Air which reach the edge of the map not having their temporary display effect removed after "the vanishes in the aether". There was a 'goto' in use which bypassed the tmp_at(DISP_END) call. I guess Dijkstra earns an "I told you so" here. --- doc/fixes36.1 | 2 ++ src/zap.c | 75 +++++++++++++++++++++++++++------------------------ 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index c2f614219..54445094a 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -180,6 +180,8 @@ non-item-using monsters who happened to be carrying scroll or wand of fire or when looting multiple containers, add 'n' for "loot next container", change 'q' and ESC from "done with this container" to "done looting" change looting to choose ":iobrs nq" action from a menu for menustyle:Partial +wand/spell/breath zaps that reached the edge of the level on the Plane of Air + and "vanished into the aether" left temporary display effects on screen Platform- and/or Interface-Specific Fixes diff --git a/src/zap.c b/src/zap.c index d8784d12b..dd20de275 100644 --- a/src/zap.c +++ b/src/zap.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 zap.c $NHDT-Date: 1456528600 2016/02/26 23:16:40 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.247 $ */ +/* NetHack 3.6 zap.c $NHDT-Date: 1457482920 2016/03/09 00:22:00 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.248 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -2119,6 +2119,7 @@ dozap() && !(objects[obj->otyp].oc_dir == NODIR)) { if ((damage = zapyourself(obj, TRUE)) != 0) { char buf[BUFSZ]; + Sprintf(buf, "zapped %sself with a wand", uhim()); losehp(Maybe_Half_Phys(damage), buf, NO_KILLER_PREFIX); } @@ -3100,7 +3101,7 @@ struct obj **pobj; /* object tossed/used, set to NULL } if (is_pick(obj) && inside_shop(x, y) - && (mtmp = shkcatch(obj, x, y))) { + && (mtmp = shkcatch(obj, x, y)) != 0) { tmp_at(DISP_END, 0); return mtmp; } @@ -3109,9 +3110,9 @@ struct obj **pobj; /* object tossed/used, set to NULL /* iron bars will block anything big enough */ if ((weapon == THROWN_WEAPON || weapon == KICKED_WEAPON) - && typ == IRONBARS && hits_bars(pobj, x - ddx, y - ddy, - bhitpos.x, bhitpos.y, - point_blank ? 0 : !rn2(5), 1)) { + && typ == IRONBARS + && hits_bars(pobj, x - ddx, y - ddy, bhitpos.x, bhitpos.y, + point_blank ? 0 : !rn2(5), 1)) { /* caveat: obj might now be null... */ obj = *pobj; bhitpos.x -= ddx; @@ -3286,6 +3287,7 @@ struct obj **pobj; /* object tossed/used, set to NULL && obj->otyp == HEAVY_IRON_BALL) { struct obj *bobj; struct trap *t; + if ((bobj = sobj_at(BOULDER, x, y)) != 0) { if (cansee(x, y)) pline("%s hits %s.", The(distant_name(obj, xname)), @@ -3845,7 +3847,6 @@ register xchar sx, sy; register int dx, dy; { int range, abstype = abs(type) % 10; - struct rm *lev; register xchar lsx, lsy; struct monst *mon; coord save_bhitpos; @@ -3889,23 +3890,24 @@ register int dx, dy; sx += dx; lsy = sy; sy += dy; - if (isok(sx, sy) && (lev = &levl[sx][sy])->typ) { - mon = m_at(sx, sy); - if (cansee(sx, sy)) { - /* reveal/unreveal invisible monsters before tmp_at() */ - if (mon && !canspotmon(mon)) - map_invisible(sx, sy); - else if (!mon && glyph_is_invisible(levl[sx][sy].glyph)) { - unmap_object(sx, sy); - newsym(sx, sy); - } - if (ZAP_POS(lev->typ) || (isok(lsx, lsy) && cansee(lsx, lsy))) - tmp_at(sx, sy); - delay_output(); /* wait a little */ - } - } else + if (!isok(sx, sy) || levl[sx][sy].typ == STONE) goto make_bounce; + mon = m_at(sx, sy); + if (cansee(sx, sy)) { + /* reveal/unreveal invisible monsters before tmp_at() */ + if (mon && !canspotmon(mon)) + map_invisible(sx, sy); + else if (!mon && glyph_is_invisible(levl[sx][sy].glyph)) { + unmap_object(sx, sy); + newsym(sx, sy); + } + if (ZAP_POS(levl[sx][sy].typ) + || (isok(lsx, lsy) && cansee(lsx, lsy))) + tmp_at(sx, sy); + delay_output(); /* wait a little */ + } + /* hit() and miss() need bhitpos to match the target */ bhitpos.x = sx, bhitpos.y = sy; /* Fireballs only damage when they explode */ @@ -4017,24 +4019,28 @@ register int dx, dy; nomul(0); } - if (!ZAP_POS(lev->typ) || (closed_door(sx, sy) && (range >= 0))) { + if (!ZAP_POS(levl[sx][sy].typ) + || (closed_door(sx, sy) && range >= 0)) { int bounce; uchar rmn; + boolean fireball; make_bounce: - if (type == ZT_SPELL(ZT_FIRE)) { - sx = lsx; - sy = lsy; - break; /* fireballs explode before the wall */ - } bounce = 0; - range--; - if (range && isok(lsx, lsy) && cansee(lsx, lsy)) { - pline("%s %s!", The(fltxt), - Is_airlevel(&u.uz) - ? "vanishes into the aether" - : "bounces"); - if (Is_airlevel(&u.uz)) goto get_out_buzz; + fireball = (type == ZT_SPELL(ZT_FIRE)); + if ((--range > 0 && isok(lsx, lsy) && cansee(lsx, lsy)) + || fireball) { + if (Is_airlevel(&u.uz)) { /* nothing to bounce off of */ + pline_The("%s vanishes into the aether!", fltxt); + if (fireball) + type = ZT_WAND(ZT_FIRE); /* skip pending fireball */ + break; + } else if (fireball) { + sx = lsx; + sy = lsy; + break; /* fireballs explode before the obstacle */ + } else + pline_The("%s bounces!", fltxt); } if (!dx || !dy || !rn2(20)) { dx = -dx; @@ -4069,7 +4075,6 @@ register int dx, dy; tmp_at(DISP_END, 0); if (type == ZT_SPELL(ZT_FIRE)) explode(sx, sy, type, d(12, 6), 0, EXPL_FIERY); - get_out_buzz: if (shopdamage) pay_for_damage(abstype == ZT_FIRE ? "burn away" From 7304c1de5c0ff07fd83899a2b5099a69bbb1854d Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Wed, 9 Mar 2016 10:56:26 +0100 Subject: [PATCH 009/151] Remove unused variable. --- util/makedefs.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/util/makedefs.c b/util/makedefs.c index 2969b6645..dc02b93df 100644 --- a/util/makedefs.c +++ b/util/makedefs.c @@ -1963,7 +1963,6 @@ do_oracles() void do_dungeon() { - int rcnt = 0; char *line; Sprintf(filename, DATA_IN_TEMPLATE, DGN_I_FILE); @@ -1989,7 +1988,6 @@ do_dungeon() while ((line = fgetline(ifp)) != 0) { SpinCursor(3); - rcnt++; if (line[0] == '#') { free(line); continue; /* discard comments */ From 545455b8cd8618700098b3ce89e8fa5e02d4347b Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Wed, 9 Mar 2016 11:05:28 +0100 Subject: [PATCH 010/151] Return computed result instead of constant. --- src/trap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/trap.c b/src/trap.c index 716962de1..d69b50aa2 100644 --- a/src/trap.c +++ b/src/trap.c @@ -4524,7 +4524,7 @@ boolean *noticed; /* set to true iff hero notices the effect; */ --force_mintrap; /* mon might now be on the migrating monsters list */ } - return TRUE; + return result; } /* only called when the player is doing something to the chest directly */ From 9d01178e5bff80f21e07fedbd031cfa9e6ffa467 Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Wed, 9 Mar 2016 11:38:46 +0100 Subject: [PATCH 011/151] Remove unreachable break statements. --- src/dbridge.c | 4 ---- src/mon.c | 1 - 2 files changed, 5 deletions(-) diff --git a/src/dbridge.c b/src/dbridge.c index 7d6bda397..428161de5 100644 --- a/src/dbridge.c +++ b/src/dbridge.c @@ -115,16 +115,12 @@ int mask; switch (mask & DB_UNDER) { case DB_ICE: return ICE; - break; case DB_LAVA: return LAVAPOOL; - break; case DB_MOAT: return MOAT; - break; default: return STONE; - break; } } diff --git a/src/mon.c b/src/mon.c index c97963abe..c4fac702a 100644 --- a/src/mon.c +++ b/src/mon.c @@ -370,7 +370,6 @@ unsigned corpseflags; } free_mname(mtmp); return obj; - break; default_1: default: if (mvitals[mndx].mvflags & G_NOCORPSE) { From ef863f3c9f6988d9efbda755dad26bf8b3f24f29 Mon Sep 17 00:00:00 2001 From: PatR Date: Wed, 9 Mar 2016 16:37:43 -0800 Subject: [PATCH 012/151] fix #H4057 - rusting amulets There have been two or three reports on getting feedback about amulets rusting. Object formatting doesn't display erosion for them, so being told about damage then not seeing that damage feels like a bug. Even if damage was displayed, it has no effect on them so would still feel somewhat strange. It does display erosion for wands and rings, which is strange too. This limits erosion damage--and its feedback--to items which are actually impacted by erosion: armor, weapons and weapon-tools; also heavy iron balls and iron chains since they've traditionally shown rust even though it has little effect. A side-effect of this change is that flammable items (other than armor and weapons) which don't burn up immediately will no longer become burnt, then very burnt, thorougly burnt, and finally be destroyed. Since the player couldn't see or possibly repair the erosion state, it seemed incomplete. It could be reinstated by making other flammable items be subject to erosion and displayed as such by xname() & co. Wishing now avoids applying erosion and erosion-proofing to items that aren't affected by it, regardless of material. It also now allows wishing for "rusty rustproof " which used to suppress "rusty" in that combination and triggered a couple of old bug reports. Heavy iron balls and iron chains can have rust repaired and can be made rustproof by wielding, then reading enchant weapon while confused, as if they were weapons. --- doc/fixes36.1 | 2 ++ include/extern.h | 3 ++- src/objnam.c | 63 ++++++++++++++++++++++++++++++++---------------- src/read.c | 8 ++++-- src/trap.c | 4 ++- src/zap.c | 19 ++++++++------- 6 files changed, 65 insertions(+), 34 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index 54445094a..8dd08e546 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -182,6 +182,8 @@ when looting multiple containers, add 'n' for "loot next container", change change looting to choose ":iobrs nq" action from a menu for menustyle:Partial wand/spell/breath zaps that reached the edge of the level on the Plane of Air and "vanished into the aether" left temporary display effects on screen +stop amulets and other items which aren't affected by erosion damage from + being subjected to erosion damage Platform- and/or Interface-Specific Fixes diff --git a/include/extern.h b/include/extern.h index a0b47449a..a85f5ce4e 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1,4 +1,4 @@ -/* NetHack 3.6 extern.h $NHDT-Date: 1457397476 2016/03/08 00:37:56 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.551 $ */ +/* NetHack 3.6 extern.h $NHDT-Date: 1457570257 2016/03/10 00:37:37 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.552 $ */ /* Copyright (c) Steve Creps, 1988. */ /* NetHack may be freely redistributed. See license for details. */ @@ -1591,6 +1591,7 @@ E char *FDECL(xname, (struct obj *)); E char *FDECL(mshot_xname, (struct obj *)); E boolean FDECL(the_unique_obj, (struct obj *)); E boolean FDECL(the_unique_pm, (struct permonst *)); +E boolean FDECL(erosion_matters, (struct obj *)); E char *FDECL(doname, (struct obj *)); E char *FDECL(doname_with_price, (struct obj *)); E boolean FDECL(not_fully_identified, (struct obj *)); diff --git a/src/objnam.c b/src/objnam.c index e90a2d53f..33c781744 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 objnam.c $NHDT-Date: 1455672990 2016/02/17 01:36:30 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.165 $ */ +/* NetHack 3.6 objnam.c $NHDT-Date: 1457570258 2016/03/10 00:37:38 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.166 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -730,6 +730,31 @@ char *prefix; : ""); } +/* used to prevent rust on items where rust makes no difference */ +boolean +erosion_matters(obj) +struct obj *obj; +{ + switch (obj->oclass) { + case TOOL_CLASS: + /* it's possible for a rusty weptool to be polymorphed into some + non-weptool iron tool, in which case the rust implicitly goes + away, but it's also possible for it to be polymorphed into a + non-iron tool, in which case rust also implicitly goes away, + so there's no particular reason to try to handle the first + instance differently [this comment belongs in poly_obj()...] */ + return is_weptool(obj) ? TRUE : FALSE; + case WEAPON_CLASS: + case ARMOR_CLASS: + case BALL_CLASS: + case CHAIN_CLASS: + return TRUE; + default: + break; + } + return FALSE; +} + static char * doname_base(obj, with_price) register struct obj *obj; @@ -853,31 +878,27 @@ boolean with_price; plur(itemcount)); } - switch (obj->oclass) { + switch (is_weptool(obj) ? WEAPON_CLASS : obj->oclass) { case AMULET_CLASS: if (obj->owornmask & W_AMUL) Strcat(bp, " (being worn)"); break; + case ARMOR_CLASS: + if (obj->owornmask & W_ARMOR) + Strcat(bp, (obj == uskin) ? " (embedded in your skin)" + : " (being worn)"); + /*FALLTHRU*/ case WEAPON_CLASS: if (ispoisoned) Strcat(prefix, "poisoned "); - plus: add_erosion_words(obj, prefix); if (known) { Strcat(prefix, sitoa(obj->spe)); Strcat(prefix, " "); } break; - case ARMOR_CLASS: - if (obj->owornmask & W_ARMOR) - Strcat(bp, (obj == uskin) ? " (embedded in your skin)" - : " (being worn)"); - goto plus; case TOOL_CLASS: - /* weptools already get this done when we go to the +n code */ - if (!is_weptool(obj)) - add_erosion_words(obj, prefix); - if (obj->owornmask & (W_TOOL /* blindfold */ | W_SADDLE)) { + if (obj->owornmask & (W_TOOL | W_SADDLE)) { /* blindfold */ Strcat(bp, " (being worn)"); break; } @@ -885,8 +906,6 @@ boolean with_price; Strcat(bp, " (in use)"); break; } - if (is_weptool(obj)) - goto plus; if (obj->otyp == CANDELABRUM_OF_INVOCATION) { if (!obj->spe) Strcpy(tmpbuf, "no"); @@ -908,7 +927,6 @@ boolean with_price; goto charges; break; case WAND_CLASS: - add_erosion_words(obj, prefix); charges: if (known) Sprintf(eos(bp), " (%d:%d)", (int) obj->recharged, obj->spe); @@ -918,7 +936,6 @@ boolean with_price; Strcat(bp, " (lit)"); break; case RING_CLASS: - add_erosion_words(obj, prefix); ring: if (obj->owornmask & W_RINGR) Strcat(bp, " (on right "); @@ -3443,15 +3460,19 @@ typfnd: curse(otmp); } - /* set eroded */ - if (is_damageable(otmp) || otmp->otyp == CRYSKNIFE) { + /* set eroded and erodeproof */ + if (erosion_matters(otmp)) { if (eroded && (is_flammable(otmp) || is_rustprone(otmp))) otmp->oeroded = eroded; if (eroded2 && (is_corrodeable(otmp) || is_rottable(otmp))) otmp->oeroded2 = eroded2; - - /* set erodeproof */ - if (erodeproof && !eroded && !eroded2) + /* + * 3.6.1: earlier versions included `&& !eroded && !eroded2' here, + * but damageproof combined with damaged is feasible (eroded + * armor modified by confused reading of cursed destroy armor) + * so don't prevent player from wishing for such a combination. + */ + if (erodeproof && (is_damageable(otmp) || otmp->otyp == CRYSKNIFE)) otmp->oerodeproof = (Luck >= 0 || wizard); } diff --git a/src/read.c b/src/read.c index 815dfe731..2d5b4f2a7 100644 --- a/src/read.c +++ b/src/read.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 read.c $NHDT-Date: 1450577673 2015/12/20 02:14:33 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.131 $ */ +/* NetHack 3.6 read.c $NHDT-Date: 1457570260 2016/03/10 00:37:40 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.135 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -60,6 +60,7 @@ struct obj *otmp; char *buf; { int erosion = greatest_erosion(otmp); + if (erosion) wipeout_text(buf, (int) (strlen(buf) * erosion / (2 * MAX_ERODE)), otmp->o_id ^ (unsigned) ubirthday); @@ -1312,8 +1313,11 @@ struct obj *sobj; /* scroll, or fake spellbook object for scroll-like spell */ */ break; case SCR_ENCHANT_WEAPON: + /* [What about twoweapon mode? Proofing/repairing/enchanting both + would be too powerful, but shouldn't we choose randomly between + primary and secondary instead of always acting on primary?] */ if (confused && uwep - && (uwep->oclass == WEAPON_CLASS || is_weptool(uwep))) { + && erosion_matters(uwep) && uwep->oclass != ARMOR_CLASS) { old_erodeproof = (uwep->oerodeproof != 0); new_erodeproof = !scursed; uwep->oerodeproof = 0; /* for messages */ diff --git a/src/trap.c b/src/trap.c index 716962de1..aa8a1724b 100644 --- a/src/trap.c +++ b/src/trap.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 trap.c $NHDT-Date: 1454528963 2016/02/03 19:49:23 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.261 $ */ +/* NetHack 3.6 trap.c $NHDT-Date: 1457570259 2016/03/10 00:37:39 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.262 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -197,6 +197,8 @@ int ef_flags; if (check_grease && otmp->greased) { grease_protect(otmp, ostr, victim); return ER_GREASED; + } else if (!erosion_matters(otmp)) { + return ER_NOTHING; } else if (!vulnerable || (otmp->oerodeproof && otmp->rknown)) { if (flags.verbose && print && (uvictim || vismon)) pline("%s %s %s not affected by %s.", diff --git a/src/zap.c b/src/zap.c index dd20de275..854c62fad 100644 --- a/src/zap.c +++ b/src/zap.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 zap.c $NHDT-Date: 1457482920 2016/03/09 00:22:00 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.248 $ */ +/* NetHack 3.6 zap.c $NHDT-Date: 1457570259 2016/03/10 00:37:39 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.249 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -1423,14 +1423,15 @@ int id; otmp->cursed = obj->cursed; otmp->blessed = obj->blessed; - otmp->oeroded = obj->oeroded; - otmp->oeroded2 = obj->oeroded2; - if (!is_flammable(otmp) && !is_rustprone(otmp)) - otmp->oeroded = 0; - if (!is_corrodeable(otmp) && !is_rottable(otmp)) - otmp->oeroded2 = 0; - if (is_damageable(otmp)) - otmp->oerodeproof = obj->oerodeproof; + + if (erosion_matters(otmp)) { + if (is_flammable(otmp) || is_rustprone(otmp)) + otmp->oeroded = obj->oeroded; + if (is_corrodeable(otmp) || is_rottable(otmp)) + otmp->oeroded2 = obj->oeroded2; + if (is_damageable(otmp)) + otmp->oerodeproof = obj->oerodeproof; + } /* Keep chest/box traps and poisoned ammo if we may */ if (obj->otrapped && Is_box(otmp)) From 7cf75b707a00c4aede97fe7b9220ec2a6346d147 Mon Sep 17 00:00:00 2001 From: PatR Date: Wed, 9 Mar 2016 17:15:32 -0800 Subject: [PATCH 013/151] fix #H4272 - "you turn into a Elvenking" Polyself with gender change into a creature with fixed gender would deliver a message containing "a " regardless of whether "an" was warranted. (Into any creature which supports both genders it yielded "a male " or "a female " so "an" was never needed. And when no gender change was involved, it used an() so got "a " or "an " as applicable.) --- doc/fixes36.1 | 1 + src/polyself.c | 23 +++++++++-------------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index 8dd08e546..bb863eebb 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -184,6 +184,7 @@ wand/spell/breath zaps that reached the edge of the level on the Plane of Air and "vanished into the aether" left temporary display effects on screen stop amulets and other items which aren't affected by erosion damage from being subjected to erosion damage +grammar bit: polyself w/ gender change yielded "you turn into a Elvenking" Platform- and/or Interface-Specific Fixes diff --git a/src/polyself.c b/src/polyself.c index 9c03e5b8d..b67cc133a 100644 --- a/src/polyself.c +++ b/src/polyself.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 polyself.c $NHDT-Date: 1451082254 2015/12/25 22:24:14 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.105 $ */ +/* NetHack 3.6 polyself.c $NHDT-Date: 1457572516 2016/03/10 01:15:16 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.108 $ */ /* Copyright (C) 1987, 1988, 1989 by Ken Arromdee */ /* NetHack may be freely redistributed. See license for details. */ @@ -592,6 +592,7 @@ int polymon(mntmp) int mntmp; { + char buf[BUFSZ]; boolean sticky = sticks(youmonst.data) && u.ustuck && !u.uswallow, was_blind = !!Blind, dochange = FALSE; int mlvl; @@ -643,20 +644,16 @@ int mntmp; if (sex_change_ok && !rn2(10)) dochange = TRUE; } + + Strcpy(buf, (u.umonnum != mntmp) ? "" : "new "); if (dochange) { flags.female = !flags.female; - You("%s %s%s!", - (u.umonnum != mntmp) ? "turn into a" : "feel like a new", - (is_male(&mons[mntmp]) || is_female(&mons[mntmp])) - ? "" - : flags.female ? "female " : "male ", - mons[mntmp].mname); - } else { - if (u.umonnum != mntmp) - You("turn into %s!", an(mons[mntmp].mname)); - else - You_feel("like a new %s!", mons[mntmp].mname); + Strcat(buf, (is_male(&mons[mntmp]) || is_female(&mons[mntmp])) + ? "" : flags.female ? "female " : "male "); } + Strcat(buf, mons[mntmp].mname); + You("%s %s!", (u.umonnum != mntmp) ? "turn into" : "feel like", an(buf)); + if (Stoned && poly_when_stoned(&mons[mntmp])) { /* poly_when_stoned already checked stone golem genocide */ mntmp = PM_STONE_GOLEM; @@ -746,8 +743,6 @@ int mntmp; uunstick(); if (u.usteed) { if (touch_petrifies(u.usteed->data) && !Stone_resistance && rnl(3)) { - char buf[BUFSZ]; - pline("%s touch %s.", no_longer_petrify_resistant, mon_nam(u.usteed)); Sprintf(buf, "riding %s", an(u.usteed->data->mname)); From b89e0a832f2767e441564fdc405f7eac73a1d6e5 Mon Sep 17 00:00:00 2001 From: PatR Date: Thu, 10 Mar 2016 17:50:09 -0800 Subject: [PATCH 014/151] ^G enhancement Accept "male" or "female" when specifying monster type for ^G. Groundwork for testing and hopefully eventually fixing "female gnome" grows up into "gnome lord" and becomes male. --- doc/fixes36.1 | 1 + src/read.c | 29 +++++++++++++++++++++-------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index bb863eebb..68dacfdab 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -251,6 +251,7 @@ feedback from probing of long worm now includes number of segments it has monk starts with 'shuriken' pre-discovered (despite language issue...) item-using monster on or next to a fire trap can use it to be cured of turning into slime +wizard mode ^G can now specify "male" or "female" when creating a monster Platform- and/or Interface-Specific New Features diff --git a/src/read.c b/src/read.c index 2d5b4f2a7..372f90b42 100644 --- a/src/read.c +++ b/src/read.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 read.c $NHDT-Date: 1457570260 2016/03/10 00:37:40 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.135 $ */ +/* NetHack 3.6 read.c $NHDT-Date: 1457660917 2016/03/11 01:48:37 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.136 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -2370,11 +2370,9 @@ create_particular() int which, tryct, i, firstchoice = NON_PM; struct permonst *whichpm = NULL; struct monst *mtmp; - boolean madeany = FALSE; - boolean maketame, makepeaceful, makehostile; - boolean randmonst = FALSE; - boolean saddled = FALSE; - boolean invisible = FALSE; + boolean madeany = FALSE, randmonst = FALSE, + maketame, makepeaceful, makehostile, saddled, invisible; + int fem; tryct = 5; do { @@ -2382,18 +2380,29 @@ create_particular() which = urole.malenum; /* an arbitrary index into mons[] */ maketame = makepeaceful = makehostile = FALSE; saddled = invisible = FALSE; + fem = -1; /* gender not specified */ getlin("Create what kind of monster? [type the name or symbol]", buf); bufp = mungspaces(buf); if (*bufp == '\033') return FALSE; if ((tmpp = strstri(bufp, "saddled ")) != 0) { saddled = TRUE; - memset(tmpp, ' ', sizeof("saddled ")-1); + (void) memset(tmpp, ' ', sizeof "saddled " - 1); } if ((tmpp = strstri(bufp, "invisible ")) != 0) { invisible = TRUE; - memset(tmpp, ' ', sizeof("invisible ")-1); + (void) memset(tmpp, ' ', sizeof "invisible " - 1); } + /* check "female" before "male" to avoid false hit mid-word */ + if ((tmpp = strstri(bufp, "female ")) != 0) { + fem = 1; + (void) memset(tmpp, ' ', sizeof "female " - 1); + } + if ((tmpp = strstri(bufp, "male ")) != 0) { + fem = 0; + (void) memset(tmpp, ' ', sizeof "male " - 1); + } + bufp = mungspaces(bufp); /* after potential memset(' ') */ /* allow the initial disposition to be specified */ if (!strncmpi(bufp, "tame ", 5)) { bufp += 5; @@ -2452,6 +2461,9 @@ create_particular() /* otherwise try again */ continue; } + /* 'is_FOO()' ought to be called 'always_FOO()' */ + if (fem != -1 && !is_male(mtmp->data) && !is_female(mtmp->data)) + mtmp->female = fem; /* ignored for is_neuter() */ if (maketame) { (void) tamedog(mtmp, (struct obj *) 0); } else if (makepeaceful || makehostile) { @@ -2461,6 +2473,7 @@ create_particular() } if (saddled && can_saddle(mtmp) && !which_armor(mtmp, W_SADDLE)) { struct obj *otmp = mksobj(SADDLE, TRUE, FALSE); + put_saddle_on_mon(otmp, mtmp); } if (invisible) From 6e8da58cd04ba9562f462b6bbb819e7f75c00b93 Mon Sep 17 00:00:00 2001 From: Tung Nguyen Date: Wed, 9 Mar 2016 14:20:58 +1100 Subject: [PATCH 015/151] Don't shop-donate non-empty bags dropped in sellobj_state SELL_DONTSELL For a shop to NOT charge for an object, two conditions apply: 1. The object's `no_charge` flag must be set. 2. That `no_charge` flag must be set regardless of whether or not the shop typically sells the object in question. There are two places in `sellobj()` which ignore the second condition, thus transferring object ownership from the player to the shop without the player's consent: 1. A container is dropped in a shop that typically sells such containers and `sellobj_state` is `SELL_DONTSELL`. 2. A zero-cost container holding nothing but gold is dropped in a shop that typically sells such containers. Neither occurs currently in NetHack: the latter because NetHack has no zero-cost containers, but the former is needed for an upcoming bug fix. This may be related to SC343-21: "Accounting is incorrect for containers dropped in a shop that does not sell them." Based on DynaHack commit 4e79b6a (Don't shop-donate non-empty bags dropped in sellobj_state SELL_DONTSELL) by me. --- src/shk.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shk.c b/src/shk.c index 6cf62f2c0..0699d36e3 100644 --- a/src/shk.c +++ b/src/shk.c @@ -2885,7 +2885,7 @@ xchar x, y; if (container) { dropped_container(obj, shkp, FALSE); - if (!obj->unpaid && !saleitem) + if (!obj->unpaid) obj->no_charge = 1; if (unpaid) subfrombill(obj, shkp); @@ -2960,7 +2960,7 @@ xchar x, y; if (!isgold) { if (container) dropped_container(obj, shkp, FALSE); - if (!obj->unpaid && !saleitem) + if (!obj->unpaid) obj->no_charge = 1; subfrombill(obj, shkp); } From 8ccdea6d8dade3a792c3a90a12f0f8f6a63856a3 Mon Sep 17 00:00:00 2001 From: Tung Nguyen Date: Wed, 9 Mar 2016 14:55:02 +1100 Subject: [PATCH 016/151] Credit/debit gold in containers even in sellobj_state SELL_DONTSELL There's no capacity for the shop logic to handle gold without also changing the credit/debit within it, so gold must always be handled in `sellobj()`, even when the state of it is set to `SELL_DONTSELL`. This is needed for an upcoming bug fix. Based on DynaHack commit b0784c5 (Credit/debit gold in containers even in sellobj_state SELL_DONTSELL) by me. --- src/shk.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/shk.c b/src/shk.c index 0699d36e3..5d3117563 100644 --- a/src/shk.c +++ b/src/shk.c @@ -2880,7 +2880,8 @@ xchar x, y; offer = ltmp + cltmp; /* get one case out of the way: nothing to sell, and no gold */ - if (!isgold && ((offer + gltmp) == 0L || sell_how == SELL_DONTSELL)) { + if (!(isgold || cgold) + && ((offer + gltmp) == 0L || sell_how == SELL_DONTSELL)) { boolean unpaid = is_unpaid(obj); if (container) { @@ -2956,7 +2957,7 @@ xchar x, y; currency(eshkp->credit)); } - if (!offer) { + if (!offer || sell_how == SELL_DONTSELL) { if (!isgold) { if (container) dropped_container(obj, shkp, FALSE); From 82386643a0dcf59c60869507a4e84a1690f1f1e1 Mon Sep 17 00:00:00 2001 From: Tung Nguyen Date: Wed, 9 Mar 2016 15:18:15 +1100 Subject: [PATCH 017/151] Fix billing/credit when hero nests their containers on a shop floor This fixes a bug where the hero could accidentally donate the contents of their bag to a shopkeeper if they put it in another bag on the shop floor that also belonged to the hero. To reproduce: 1. Drop a sack on the floor, but don't sell it. 2. Get another sack and put in hero-owned objects. 3. Put the sack with objects into the sack on the shop floor. 4. Take out the sack with the objects from the sack on the shop floor. The shopkeeper will claim you owe them for the objects in the sack, and view the contents of the sack will show them as belonging to the shopkeeper. This fix is what those previous fixes for `SELL_DONTSELL` were for. Based on DynaHack commit f91ce0b (Fix billing/credit when hero nests their containers on a shop floor) by me. --- src/pickup.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/pickup.c b/src/pickup.c index 38c7aeea7..a332e57f6 100644 --- a/src/pickup.c +++ b/src/pickup.c @@ -2025,15 +2025,14 @@ register struct obj *obj; (void) snuff_lit(obj); if (floor_container && costly_spot(u.ux, u.uy)) { - if (obj->oclass == COIN_CLASS) { - ; /* defer gold until after put-in message */ - } else if (current_container->no_charge && !obj->unpaid) { - /* don't sell when putting the item into your own container */ - obj->no_charge = 1; - } else { + /* defer gold until after put-in message */ + if (obj->oclass != COIN_CLASS) { /* sellobj() will take an unpaid item off the shop bill */ was_unpaid = obj->unpaid ? TRUE : FALSE; - sellobj_state(SELL_DELIBERATE); + /* don't sell when putting the item into your own container, + * but handle billing correctly */ + sellobj_state(current_container->no_charge + ? SELL_DONTSELL : SELL_DELIBERATE); sellobj(obj, u.ux, u.uy); sellobj_state(SELL_NORMAL); } From f8b0e766d85289ef0c66be9e50f93c5258929c2f Mon Sep 17 00:00:00 2001 From: Tung Nguyen Date: Wed, 9 Mar 2016 00:26:57 +1100 Subject: [PATCH 018/151] Fix & instead of && in onbill() It's obviously supposed to be the latter and not the former. Interesting note: This same bug was found and fixed in NitroHack commit 4973ce4 (static checker day: fixes for scan-build and PVS warnings). --- src/shk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shk.c b/src/shk.c index 6cf62f2c0..addf8aac7 100644 --- a/src/shk.c +++ b/src/shk.c @@ -859,7 +859,7 @@ boolean silent; } else bp++; } - if (obj->unpaid & !silent) + if (obj->unpaid && !silent) pline("onbill: unpaid obj not on bill?"); return (struct bill_x *) 0; } From e1d0faa5849c79741c29b962a51d356574ab3c06 Mon Sep 17 00:00:00 2001 From: Tung Nguyen Date: Wed, 9 Mar 2016 00:48:52 +1100 Subject: [PATCH 019/151] Fix paid object on bill when angering another shopkeeper To test: 1. Get a level layout with two shops facing each other, e.g. minetn-4. 2. Sell a fragile object to one of the shops. 3. Dig a pit in the other shop's door space so its shopkeeper stays out of the way. 4. Pick up an object in that other shop so it appears on your bill. 5. Zap a wand of striking at the first shop to break the fragile object. 6. 'p'ay for the object picked up. Expected result: Object gets the standard prompt to pay for it. Actual result: "Paid object on bill??" followed by "Program in disorder perhaps you'd better #quit." followed by the object being given to the player for free. The cause? This comment going all the way back to 2002: > /* FIXME: object handling should be limited to > items which are on this particular shk's bill */ Originally reported by PaRaD0xx in FreeNode's #NetHack IRC channel whilst playing NAO343. Based on DynaHack commit d995ed1 (Fix paid object on bill when angering another shkp) by me. --- src/shk.c | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/shk.c b/src/shk.c index addf8aac7..4d683f500 100644 --- a/src/shk.c +++ b/src/shk.c @@ -38,7 +38,8 @@ STATIC_DCL struct monst *FDECL(next_shkp, (struct monst *, BOOLEAN_P)); STATIC_DCL long FDECL(shop_debt, (struct eshk *)); STATIC_DCL char *FDECL(shk_owns, (char *, struct obj *)); STATIC_DCL char *FDECL(mon_owns, (char *, struct obj *)); -STATIC_DCL void FDECL(clear_unpaid, (struct obj *)); +STATIC_DCL void FDECL(clear_unpaid_obj, (struct monst *, struct obj *)); +STATIC_DCL void FDECL(clear_unpaid, (struct monst *, struct obj *)); STATIC_DCL long FDECL(check_credit, (long, struct monst *)); STATIC_DCL void FDECL(pay, (long, struct monst *)); STATIC_DCL long FDECL(get_cost, (struct obj *, struct monst *)); @@ -260,15 +261,26 @@ boolean ghostly; } } +/* Clear the unpaid bit on a single object and its contents. */ +STATIC_OVL void +clear_unpaid_obj(shkp, otmp) +struct monst *shkp; +struct obj *otmp; +{ + if (Has_contents(otmp)) + clear_unpaid(shkp, otmp->cobj); + if (onbill(otmp, shkp, TRUE)) + otmp->unpaid = 0; +} + /* Clear the unpaid bit on all of the objects in the list. */ STATIC_OVL void -clear_unpaid(list) -register struct obj *list; +clear_unpaid(shkp, list) +struct monst *shkp; +struct obj *list; { while (list) { - if (Has_contents(list)) - clear_unpaid(list->cobj); - list->unpaid = 0; + clear_unpaid_obj(shkp, list); list = list->nobj; } } @@ -281,20 +293,17 @@ register struct monst *shkp; register struct obj *obj; register struct monst *mtmp; - /* FIXME: object handling should be limited to - items which are on this particular shk's bill */ - - clear_unpaid(invent); - clear_unpaid(fobj); - clear_unpaid(level.buriedobjlist); + clear_unpaid(shkp, invent); + clear_unpaid(shkp, fobj); + clear_unpaid(shkp, level.buriedobjlist); if (thrownobj) - thrownobj->unpaid = 0; + clear_unpaid_obj(shkp, thrownobj); if (kickedobj) - kickedobj->unpaid = 0; + clear_unpaid_obj(shkp, kickedobj); for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) - clear_unpaid(mtmp->minvent); + clear_unpaid(shkp, mtmp->minvent); for (mtmp = migrating_mons; mtmp; mtmp = mtmp->nmon) - clear_unpaid(mtmp->minvent); + clear_unpaid(shkp, mtmp->minvent); while ((obj = billobjs) != 0) { obj_extract_self(obj); From b3b4953020d44fce51bdb5323d3517a2eee66b1f Mon Sep 17 00:00:00 2001 From: PatR Date: Sun, 13 Mar 2016 16:23:38 -0700 Subject: [PATCH 020/151] 'sortloot' revamp Change the sortloot option to use qsort() instead of naive insertion sort. After sorting, it reorders the linked list into the sorted order, so might have some subtle change(s) in behavior since that wasn't done before. pickup.c includes some formatting cleanup. modified: include/extern.h, hack.h, obj.h src/do.c, do_wear.c, end.c, invent.c, pickup.c --- include/extern.h | 11 +- include/hack.h | 9 +- include/obj.h | 13 ++- src/do.c | 4 +- src/do_wear.c | 6 +- src/end.c | 38 ++----- src/invent.c | 279 ++++++++++++++++++++++++++++++----------------- src/pickup.c | 137 +++++++++++------------ 8 files changed, 278 insertions(+), 219 deletions(-) diff --git a/include/extern.h b/include/extern.h index a85f5ce4e..bf0faf578 100644 --- a/include/extern.h +++ b/include/extern.h @@ -891,8 +891,7 @@ E int NDECL(midnight); /* ### invent.c ### */ -E struct obj **FDECL(objarr_init, (int)); -E void FDECL(objarr_set, (struct obj *, int, struct obj **, BOOLEAN_P)); +E void FDECL(sortloot, (struct obj **, unsigned, BOOLEAN_P)); E void FDECL(assigninvlet, (struct obj *)); E struct obj *FDECL(merge_choice, (struct obj *, struct obj *)); E int FDECL(merged, (struct obj **, struct obj **)); @@ -1755,10 +1754,10 @@ E int FDECL(out_container, (struct obj *)); #endif E int FDECL(pickup, (int)); E int FDECL(pickup_object, (struct obj *, long, BOOLEAN_P)); -E int FDECL(query_category, - (const char *, struct obj *, int, menu_item **, int)); -E int FDECL(query_objlist, (const char *, struct obj *, int, menu_item **, - int, boolean (*)(OBJ_P))); +E int FDECL(query_category, (const char *, struct obj *, int, + menu_item **, int)); +E int FDECL(query_objlist, (const char *, struct obj **, int, + menu_item **, int, boolean (*)(OBJ_P))); E struct obj *FDECL(pick_obj, (struct obj *)); E int NDECL(encumber_msg); E int NDECL(doloot); diff --git a/include/hack.h b/include/hack.h index 816b7fee0..9054e0cf1 100644 --- a/include/hack.h +++ b/include/hack.h @@ -318,8 +318,13 @@ NEARDATA extern coord bhitpos; /* place where throw or zap hits or stops */ #define BASICENLIGHTENMENT 1 /* show mundane stuff */ #define MAGICENLIGHTENMENT 2 /* show intrinsics and such */ #define ENL_GAMEINPROGRESS 0 -#define ENL_GAMEOVERALIVE 1 /* ascension, escape, quit, trickery */ -#define ENL_GAMEOVERDEAD 2 +#define ENL_GAMEOVERALIVE 1 /* ascension, escape, quit, trickery */ +#define ENL_GAMEOVERDEAD 2 + +/* control flags for sortloot() */ +#define SORTLOOT_PACK 0x01 +#define SORTLOOT_INVLET 0x02 +#define SORTLOOT_LOOT 0x04 /* Macros for messages referring to hands, eyes, feet, etc... */ #define ARM 0 diff --git a/include/obj.h b/include/obj.h index d03aec2ec..a511e1cf6 100644 --- a/include/obj.h +++ b/include/obj.h @@ -40,12 +40,13 @@ struct obj { unsigned owt; long quan; /* number of items */ - schar spe; /* quality of weapon, armor or ring (+ or -) - number of charges for wand ( >= -1 ) - marks your eggs, tin variety and spinach tins - royal coffers for a court ( == 2) - tells which fruit a fruit is - special for uball and amulet + schar spe; /* quality of weapon, armor or ring (+ or -); + number of charges for wand or charged tool ( >= -1 ); + marks your eggs, tin variety and spinach tins; + Schroedinger's Box (1) or royal coffers for a court (2); + tells which fruit a fruit is; + special for uball and amulet; + scroll of mail (normal==0, bones or wishing==1, written==2); historic and gender for statues */ #define STATUE_HISTORIC 0x01 #define STATUE_MALE 0x02 diff --git a/src/do.c b/src/do.c index f6058eabd..734324821 100644 --- a/src/do.c +++ b/src/do.c @@ -813,8 +813,8 @@ int retry; bypass_objlist(invent, FALSE); } else { /* should coordinate with perm invent, maybe not show worn items */ - n = query_objlist("What would you like to drop?", invent, - USE_INVLET | INVORDER_SORT, &pick_list, PICK_ANY, + n = query_objlist("What would you like to drop?", &invent, + (USE_INVLET | INVORDER_SORT), &pick_list, PICK_ANY, all_categories ? allow_all : allow_category); if (n > 0) { /* diff --git a/src/do_wear.c b/src/do_wear.c index 1f84aa6d8..9f2f1fb2d 100644 --- a/src/do_wear.c +++ b/src/do_wear.c @@ -2520,9 +2520,9 @@ int retry; all_worn_categories = TRUE; } - n = query_objlist("What do you want to take off?", invent, - SIGNAL_NOMENU | USE_INVLET | INVORDER_SORT, &pick_list, - PICK_ANY, + n = query_objlist("What do you want to take off?", &invent, + (SIGNAL_NOMENU | USE_INVLET | INVORDER_SORT), + &pick_list, PICK_ANY, all_worn_categories ? is_worn : is_worn_by_type); if (n > 0) { for (i = 0; i < n; i++) diff --git a/src/end.c b/src/end.c index 4db37b71b..09e76812a 100644 --- a/src/end.c +++ b/src/end.c @@ -1329,9 +1329,6 @@ struct obj *list; boolean identified, all_containers, reportempty; { register struct obj *box, *obj; - struct obj **oarray; - int i, n; - char *invlet; char buf[BUFSZ]; boolean cat, deadcat; @@ -1355,43 +1352,24 @@ boolean identified, all_containers, reportempty; } else if (box->cobj) { winid tmpwin = create_nhwindow(NHW_MENU); - /* count the number of items */ - for (n = 0, obj = box->cobj; obj; obj = obj->nobj) - n++; - /* Make a temporary array to store the objects sorted */ - oarray = objarr_init(n); - - /* Add objects to the array */ - i = 0; - invlet = flags.inv_order; - nextclass: - for (obj = box->cobj; obj; obj = obj->nobj) { - if (!flags.sortpack || obj->oclass == *invlet) { - objarr_set( - obj, i++, oarray, - (flags.sortloot == 'f' || flags.sortloot == 'l')); - } - } /* for loop */ - if (flags.sortpack) { - if (*++invlet) - goto nextclass; - } - + sortloot(&box->cobj, + (((flags.sortloot == 'l' || flags.sortloot == 'f') + ? SORTLOOT_LOOT : 0) + | (flags.sortpack ? SORTLOOT_PACK : 0)), + FALSE); Sprintf(buf, "Contents of %s:", the(xname(box))); putstr(tmpwin, 0, buf); putstr(tmpwin, 0, ""); - for (i = 0; i < n; i++) { - obj = oarray[i]; + for (obj = box->cobj; obj; obj = obj->nobj) { if (identified) { makeknown(obj->otyp); - obj->known = obj->bknown = obj->dknown = obj->rknown = - 1; + obj->known = obj->bknown = obj->dknown + = obj->rknown = 1; if (Is_container(obj) || obj->otyp == STATUE) obj->cknown = obj->lknown = 1; } putstr(tmpwin, 0, doname(obj)); } - free(oarray); if (cat) putstr(tmpwin, 0, "Schroedinger's cat"); else if (deadcat) diff --git a/src/invent.c b/src/invent.c index 103ee9580..37937b6cf 100644 --- a/src/invent.c +++ b/src/invent.c @@ -8,7 +8,8 @@ #define CONTAINED_SYM '>' /* designator for inside a container */ #define HANDS_SYM '-' -STATIC_DCL int FDECL(CFDECLSPEC sortloot_cmp, (struct obj *, struct obj *)); +STATIC_DCL int FDECL(CFDECLSPEC sortloot_cmp, (const genericptr, + const genericptr)); STATIC_DCL void NDECL(reorder_invent); STATIC_DCL void FDECL(noarmor, (BOOLEAN_P)); STATIC_DCL void FDECL(invdisp_nothing, (const char *, const char *)); @@ -25,8 +26,8 @@ STATIC_PTR char *FDECL(safeq_shortxprname, (struct obj *)); STATIC_DCL char FDECL(display_pickinv, (const char *, const char *, BOOLEAN_P, long *)); STATIC_DCL char FDECL(display_used_invlets, (CHAR_P)); -STATIC_DCL void FDECL(tally_BUCX, - (struct obj *, int *, int *, int *, int *, int *)); +STATIC_DCL void FDECL(tally_BUCX, (struct obj *, + int *, int *, int *, int *, int *)); STATIC_DCL boolean FDECL(this_type_only, (struct obj *)); STATIC_DCL void NDECL(dounpaid); STATIC_DCL struct obj *FDECL(find_unpaid, (struct obj *, struct obj **)); @@ -47,96 +48,187 @@ static int lastinvnr = 51; /* 0 ... 51 (never saved&restored) */ */ static char venom_inv[] = { VENOM_CLASS, 0 }; /* (constant) */ +struct sortloot_item { + struct obj *obj; + int indx; +}; +unsigned sortlootmode = 0; + +/* qsort comparison routine for sortloot() */ STATIC_OVL int CFDECLSPEC -sortloot_cmp(obj1, obj2) -struct obj *obj1; -struct obj *obj2; +sortloot_cmp(vptr1, vptr2) +const genericptr vptr1; +const genericptr vptr2; { - int val1 = 0; - int val2 = 0; + struct sortloot_item *sli1 = (struct sortloot_item *) vptr1, + *sli2 = (struct sortloot_item *) vptr2; + struct obj *obj1 = sli1->obj, + *obj2 = sli2->obj; + char *cls1, *cls2; + int val1, val2, c, namcmp; + + /* order by object class like inventory display */ + if ((sortlootmode & SORTLOOT_PACK) != 0) { + cls1 = index(flags.inv_order, obj1->oclass); + cls2 = index(flags.inv_order, obj2->oclass); + if (cls1 != cls2) + return (int) (cls1 - cls2); + + /* for armor, group by sub-category */ + if (obj1->oclass == ARMOR_CLASS) { + static int armcat[7 + 1]; + + if (!armcat[7]) { + /* one-time init; we want to control the order */ + armcat[ARM_HELM] = 1; /* [2] */ + armcat[ARM_GLOVES] = 2; /* [3] */ + armcat[ARM_BOOTS] = 3; /* [4] */ + armcat[ARM_SHIELD] = 4; /* [1] */ + armcat[ARM_CLOAK] = 5; /* [5] */ + armcat[ARM_SHIRT] = 6; /* [6] */ + armcat[ARM_SUIT] = 7; /* [0] */ + armcat[7] = 8; + } + val1 = armcat[objects[obj1->otyp].oc_armcat]; + val2 = armcat[objects[obj2->otyp].oc_armcat]; + if (val1 != val2) + return val1 - val2; + + /* for weapons, group by ammo (arrows, bolts), launcher (bows), + missile (dart, boomerang), stackable (daggers, knives, spears), + 'other' (swords, axes, &c), polearm */ + } else if (obj1->oclass == WEAPON_CLASS) { + val1 = objects[obj1->otyp].oc_skill; + val1 = (val1 < 0) + ? (val1 >= -P_CROSSBOW && val1 <= -P_BOW) ? 1 : 3 + : (val1 >= P_BOW && val1 <= P_CROSSBOW) ? 2 + : (val1 == P_SPEAR || val1 == P_DAGGER + || val1 == P_KNIFE) ? 4 : !is_pole(obj1) ? 5 : 6; + val2 = objects[obj2->otyp].oc_skill; + val2 = (val2 < 0) + ? (val2 >= -P_CROSSBOW && val2 <= -P_BOW) ? 1 : 3 + : (val2 >= P_BOW && val2 <= P_CROSSBOW) ? 2 + : (val2 == P_SPEAR || val2 == P_DAGGER + || val2 == P_KNIFE) ? 4 : !is_pole(obj2) ? 5 : 6; + if (val1 != val2) + return val1 - val2; + } + } + + /* order by assigned inventory letter */ + if ((sortlootmode & SORTLOOT_INVLET) != 0) { + c = obj1->invlet; + val1 = ('a' <= c && c <= 'z') ? (c - 'a' + 2) + : ('A' <= c && c <= 'Z') ? (c - 'Z' + 2 + 26) + : (c == '$') ? 1 + : (c == '#') ? 1 + 52 + 1 + : 1 + 52 + 1 + 1; /* none of the above */ + c = obj2->invlet; + val2 = ('a' <= c <= 'z') ? (c - 'a' + 2) + : ('A' <= c <= 'Z') ? (c - 'Z' + 2 + 26) + : (c == '$') ? 1 + : (c == '#') ? 1 + 52 + 1 + : 1 + 52 + 1 + 1; /* none of the above */ + if (val1 != val2) + return val1 - val2; + } + + if ((sortlootmode & SORTLOOT_LOOT) == 0) + goto tiebreak; /* Sort object names in lexicographical order, ignoring quantity. */ - int name_cmp = strcmpi(cxname_singular(obj1), cxname_singular(obj2)); + if ((namcmp = strcmpi(cxname_singular(obj1), cxname_singular(obj2))) != 0) + return namcmp; - if (name_cmp != 0) { - return name_cmp; - } - - /* Sort by BUC. Map blessed to 4, uncursed to 2, cursed to 1, and unknown - * to 0. */ + /* Sort by BUCX. Map blessed to 4, uncursed to 2, cursed to 1, and + unknown to 0. */ val1 = obj1->bknown - ? (obj1->blessed << 2) - + ((!obj1->blessed && !obj1->cursed) << 1) + obj1->cursed - : 0; + ? (obj1->blessed << 2) + + ((!obj1->blessed && !obj1->cursed) << 1) + obj1->cursed + : 0; val2 = obj2->bknown - ? (obj2->blessed << 2) - + ((!obj2->blessed && !obj2->cursed) << 1) + obj2->cursed - : 0; - if (val1 != val2) { - return val2 - val1; /* Because bigger is better. */ - } + ? (obj2->blessed << 2) + + ((!obj2->blessed && !obj2->cursed) << 1) + obj2->cursed + : 0; + if (val1 != val2) + return val2 - val1; /* bigger is better */ - /* Sort by greasing. This will put the objects in degreasing order. */ + /* Sort by greasing. This will put the objects in degreasing order. */ val1 = obj1->greased; val2 = obj2->greased; - if (val1 != val2) { - return val2 - val1; /* Because bigger is better. */ - } + if (val1 != val2) + return val2 - val1; /* bigger is better */ - /* Sort by erosion. The effective amount is what matters. */ + /* Sort by erosion. The effective amount is what matters. */ val1 = greatest_erosion(obj1); val2 = greatest_erosion(obj2); - if (val1 != val2) { - return val1 - val2; /* Because bigger is WORSE. */ - } + if (val1 != val2) + return val1 - val2; /* bigger is WORSE */ - /* Sort by erodeproofing. Map known-invulnerable to 1, and both - * known-vulnerable and unknown-vulnerability to 0, because that's how - * they're displayed. */ + /* Sort by erodeproofing. Map known-invulnerable to 1, and both + known-vulnerable and unknown-vulnerability to 0, because that's + how they're displayed. */ val1 = obj1->rknown && obj1->oerodeproof; val2 = obj2->rknown && obj2->oerodeproof; - if (val1 != val2) { - return val2 - val1; /* Because bigger is better. */ + if (val1 != val2) + return val2 - val1; /* bigger is better */ + + /* Sort by enchantment. Map unknown to -1000, which is comfortably + below the range of obj->spe. oc_uses_known means that obj->known + matters, which usually indirectly means that obj->spe is relevant. + Lots of objects use obj->spe for some other purpose (see obj.h). */ + if (objects[obj1->otyp].oc_uses_known + /* exclude eggs (laid by you) and tins (homemade, pureed, &c) */ + && obj1->oclass != FOOD_CLASS) { + val1 = obj1->known ? obj1->spe : -1000; + val2 = obj2->known ? obj2->spe : -1000; + if (val1 != val2) + return val2 - val1; /* bigger is better */ } - /* Sort by enchantment. Map unknown to -1000, which is comfortably below - * the range of ->spe. */ - val1 = obj1->known ? obj1->spe : -1000; - val2 = obj2->known ? obj2->spe : -1000; - if (val1 != val2) { - return val2 - val1; /* Because bigger is better. */ - } - - /* They're identical, as far as we're concerned, - but we want to force a determistic order between them. */ - return (obj1->o_id > obj2->o_id) ? 1 : -1; -} - -struct obj ** -objarr_init(n) -int n; -{ - return (struct obj **) alloc(n * sizeof(struct obj *)); +tiebreak: + /* They're identical, as far as we're concerned. We want + to force a deterministic order, and do so by producing a + stable sort: maintain the original order of equal items. */ + return (sli2->indx - sli1->indx); } void -objarr_set(otmp, idx, oarray, dosort) -struct obj *otmp; -int idx; -struct obj **oarray; -boolean dosort; +sortloot(olist, mode, by_nexthere) +struct obj **olist; +unsigned mode; /* flags for sortloot_cmp() */ +boolean by_nexthere; /* T: traverse via obj->nexthere, F: via obj->nobj */ { - if (dosort) { - int j; - for (j = idx; j; j--) { - if (sortloot_cmp(otmp, oarray[j - 1]) > 0) - break; - oarray[j] = oarray[j - 1]; - } - oarray[j] = otmp; - } else { - oarray[idx] = otmp; + struct sortloot_item *sliarray, osli, nsli; + struct obj *o, **nxt_p; + unsigned n, i; + boolean already_sorted = TRUE; + + sortlootmode = mode; /* extra input for sortloot_cmp() */ + for (n = osli.indx = 0, osli.obj = *olist; (o = osli.obj) != 0; + osli = nsli) { + nsli.obj = by_nexthere ? o->nexthere : o->nobj; + nsli.indx = (int) ++n; + if (nsli.obj && already_sorted + && sortloot_cmp((genericptr_t) &osli, (genericptr_t) &nsli) > 0) + already_sorted = FALSE; } + if (n > 1 && !already_sorted) { + sliarray = (struct sortloot_item *) alloc(n * sizeof *sliarray); + for (i = 0, o = *olist; o; + ++i, o = by_nexthere ? o->nexthere : o->nobj) + sliarray[i].obj = o, sliarray[i].indx = (int) i; + + qsort((genericptr_t) sliarray, n, sizeof *sliarray, sortloot_cmp); + for (i = 0; i < n; ++i) { + o = sliarray[i].obj; + nxt_p = by_nexthere ? &(o->nexthere) : &(o->nobj); + *nxt_p = (i < n - 1) ? sliarray[i + 1].obj : (struct obj *) 0; + } + *olist = sliarray[0].obj; + free((genericptr_t) sliarray); + } + sortlootmode = 0; } void @@ -1783,8 +1875,8 @@ int id_limit; while (id_limit) { Sprintf(buf, "What would you like to identify %s?", first ? "first" : "next"); - n = query_objlist(buf, invent, SIGNAL_NOMENU | SIGNAL_ESCAPE - | USE_INVLET | INVORDER_SORT, + n = query_objlist(buf, &invent, (SIGNAL_NOMENU | SIGNAL_ESCAPE + | USE_INVLET | INVORDER_SORT), &pick_list, PICK_ANY, not_fully_identified); if (n > 0) { @@ -2021,11 +2113,10 @@ long *out_cnt; struct obj *otmp; char ilet, ret; char *invlet = flags.inv_order; - int i, n, classcount; + int n, classcount; winid win; /* windows being used */ anything any; menu_item *selected; - struct obj **oarray; if (flags.perm_invent && ((lets && *lets) || xtra_choice)) { /* partial inventory in perm_invent setting; don't operate on @@ -2096,19 +2187,10 @@ long *out_cnt; return ret; } - /* count the number of items (preliminary count of 0,1,more was 'more' - and is now obsolete); we have at least 2 items or want to behave as - if we do (full invent and wiz_identify use this even for 1 item) */ - for (n = 0, otmp = invent; otmp; otmp = otmp->nobj) - if (!lets || !*lets || index(lets, otmp->invlet)) - n++; - oarray = objarr_init(n); - /* Add objects to the array */ - i = 0; - for (otmp = invent; otmp; otmp = otmp->nobj) - if (!lets || !*lets || index(lets, otmp->invlet)) { - objarr_set(otmp, i++, oarray, (flags.sortloot == 'f')); - } + sortloot(&invent, + (((flags.sortloot == 'f') ? SORTLOOT_LOOT : SORTLOOT_INVLET) + | (flags.sortpack ? SORTLOOT_PACK : 0)), + FALSE); start_menu(win); any = zeroany; @@ -2133,8 +2215,7 @@ long *out_cnt; } nextclass: classcount = 0; - for (i = 0; i < n; i++) { - otmp = oarray[i]; + for (otmp = invent; otmp; otmp = otmp->nobj) { if (!flags.sortpack || otmp->oclass == *invlet) { any = zeroany; /* all bits zero */ ilet = otmp->invlet; @@ -2158,7 +2239,6 @@ nextclass: goto nextclass; } } - free(oarray); end_menu(win, (char *) 0); n = select_menu(win, want_reply ? PICK_ONE : PICK_NONE, &selected); @@ -2627,9 +2707,9 @@ dotypeinv() } this_type = oclass; } - if (query_objlist((char *) 0, invent, - (flags.invlet_constant ? USE_INVLET : 0) - | INVORDER_SORT, + if (query_objlist((char *) 0, &invent, + ((flags.invlet_constant ? USE_INVLET : 0) + | INVORDER_SORT), &pick_list, PICK_NONE, this_type_only) > 0) free((genericptr_t) pick_list); return 0; @@ -3619,8 +3699,8 @@ char *title; */ youmonst.data = mon->data; - n = query_objlist(title ? title : tmp, mon->minvent, - INVORDER_SORT | (incl_hero ? INCLUDE_HERO : 0), + n = query_objlist(title ? title : tmp, &(mon->minvent), + (INVORDER_SORT | (incl_hero ? INCLUDE_HERO : 0)), &selected, (dflags & MINV_NOLET) ? PICK_NONE : PICK_ONE, do_all ? allow_all : worn_wield_only); @@ -3656,8 +3736,8 @@ register struct obj *obj; "that"); if (obj->cobj) { - n = query_objlist(qbuf, obj->cobj, INVORDER_SORT, &selected, - PICK_NONE, allow_all); + n = query_objlist(qbuf, &(obj->cobj), INVORDER_SORT, + &selected, PICK_NONE, allow_all); } else { invdisp_nothing(qbuf, "(empty)"); n = 0; @@ -3707,8 +3787,9 @@ boolean as_if_seen; if (n) { only.x = x; only.y = y; - if (query_objlist("Things that are buried here:", level.buriedobjlist, - INVORDER_SORT, &selected, PICK_NONE, only_here) > 0) + if (query_objlist("Things that are buried here:", + &level.buriedobjlist, INVORDER_SORT, + &selected, PICK_NONE, only_here) > 0) free((genericptr_t) selected); only.x = only.y = 0; } diff --git a/src/pickup.c b/src/pickup.c index 38c7aeea7..39effd261 100644 --- a/src/pickup.c +++ b/src/pickup.c @@ -11,9 +11,9 @@ #define CONTAINED_SYM '>' /* from invent.c */ STATIC_DCL void FDECL(simple_look, (struct obj *, BOOLEAN_P)); -STATIC_DCL boolean -FDECL(query_classes, (char *, boolean *, boolean *, const char *, - struct obj *, BOOLEAN_P, int *)); +STATIC_DCL boolean FDECL(query_classes, (char *, boolean *, boolean *, + const char *, struct obj *, + BOOLEAN_P, int *)); STATIC_DCL boolean FDECL(fatal_corpse_mistake, (struct obj *, BOOLEAN_P)); STATIC_DCL void FDECL(check_here, (BOOLEAN_P)); STATIC_DCL boolean FDECL(n_or_more, (struct obj *)); @@ -47,7 +47,7 @@ STATIC_DCL void FDECL(tipcontainer, (struct obj *)); /* define for query_objlist() and autopickup() */ #define FOLLOW(curr, flags) \ - (((flags) &BY_NEXTHERE) ? (curr)->nexthere : (curr)->nobj) + (((flags) & BY_NEXTHERE) ? (curr)->nexthere : (curr)->nobj) /* * How much the weight of the given container will change when the given @@ -62,9 +62,9 @@ STATIC_DCL void FDECL(tipcontainer, (struct obj *)); /* if you can figure this out, give yourself a hearty pat on the back... */ #define GOLD_CAPACITY(w, n) (((w) * -100L) - ((n) + 50L) - 1L) -/* A variable set in use_container(), to be used by the callback routines */ -/* in_container() and out_container() from askchain() and use_container(). */ -/* Also used by menu_loot() and container_gone(). */ +/* A variable set in use_container(), to be used by the callback routines + in_container() and out_container() from askchain() and use_container(). + Also used by menu_loot() and container_gone(). */ static NEARDATA struct obj *current_container; static NEARDATA boolean abort_looting; #define Icebox (current_container->otyp == ICE_BOX) @@ -221,9 +221,7 @@ int *menu_on_demand; oclasses[oclassct] = '\0'; } else { if (!where) - where = - !strcmp(action, "pick up") - ? "here" + where = !strcmp(action, "pick up") ? "here" : !strcmp(action, "take out") ? "inside" : ""; if (*where) There("are no %c's %s.", sym, where); @@ -384,6 +382,7 @@ struct obj *obj; if (Role_if(PM_PRIEST)) obj->bknown = TRUE; + /* * There are three types of filters possible and the first and * third can have more than one entry: @@ -400,6 +399,7 @@ struct obj *obj; * in accepting all scrolls and potions regardless of bless/curse * state plus all blessed non-scroll, non-potion objects.) */ + /* if class is expected but obj's class is not in the list, reject */ if (class_filter && !index(valid_menu_classes, obj->oclass)) return FALSE; @@ -481,7 +481,8 @@ int what; /* should be a long */ count = 0; if (!u.uswallow) { - struct trap *ttmp = t_at(u.ux, u.uy); + struct trap *ttmp; + /* no auto-pick if no-pick move, nothing there, or in a pool */ if (autopickup && (context.nopick || !OBJ_AT(u.ux, u.uy) || (is_pool(u.ux, u.uy) && !Underwater) @@ -489,11 +490,11 @@ int what; /* should be a long */ read_engr_at(u.ux, u.uy); return 0; } - /* no pickup if levitating & not on air or water level */ if (!can_reach_floor(TRUE)) { if ((multi && !context.run) || (autopickup && !flags.pickup) - || (ttmp && uteetering_at_seen_pit(ttmp))) + || ((ttmp = t_at(u.ux, u.uy)) != 0 + && uteetering_at_seen_pit(ttmp))) read_engr_at(u.ux, u.uy); return 0; } @@ -540,21 +541,20 @@ int what; /* should be a long */ if (flags.menu_style != MENU_TRADITIONAL || iflags.menu_requested) { /* use menus exclusively */ + traverse_how |= AUTOSELECT_SINGLE | INVORDER_SORT; if (count) { /* looking for N of something */ char qbuf[QBUFSZ]; Sprintf(qbuf, "Pick %d of what?", count); val_for_n_or_more = count; /* set up callback selector */ - n = query_objlist(qbuf, objchain, traverse_how | AUTOSELECT_SINGLE - | INVORDER_SORT, + n = query_objlist(qbuf, &objchain, traverse_how, &pick_list, PICK_ONE, n_or_more); /* correct counts, if any given */ for (i = 0; i < n; i++) pick_list[i].count = count; } else { - n = query_objlist("Pick up what?", objchain, - traverse_how | AUTOSELECT_SINGLE | INVORDER_SORT - | FEEL_COCKATRICE, + n = query_objlist("Pick up what?", &objchain, + (traverse_how | FEEL_COCKATRICE), &pick_list, PICK_ANY, all_but_uchain); } menu_pickup: @@ -582,8 +582,7 @@ int what; /* should be a long */ selective = FALSE; /* ask for each item */ /* check for more than one object */ - for (obj = objchain; obj; - obj = (traverse_how == BY_NEXTHERE) ? obj->nexthere : obj->nobj) + for (obj = objchain; obj; obj = FOLLOW(obj, traverse_how)) ct++; if (ct == 1 && count) { @@ -601,12 +600,13 @@ int what; /* should be a long */ There("are %s objects here.", (ct <= 10) ? "several" : "many"); if (!query_classes(oclasses, &selective, &all_of_a_type, "pick up", objchain, - traverse_how == BY_NEXTHERE, &via_menu)) { + (traverse_how & BY_NEXTHERE) ? TRUE : FALSE, + &via_menu)) { if (!via_menu) return 0; - n = query_objlist("Pick up what?", objchain, - traverse_how - | (selective ? 0 : INVORDER_SORT), + if (selective) + traverse_how |= INVORDER_SORT; + n = query_objlist("Pick up what?", &objchain, traverse_how, &pick_list, PICK_ANY, (via_menu == -2) ? allow_all : allow_category); @@ -615,15 +615,11 @@ int what; /* should be a long */ } for (obj = objchain; obj; obj = obj2) { - if (traverse_how == BY_NEXTHERE) - obj2 = obj->nexthere; /* perhaps obj will be picked up */ - else - obj2 = obj->nobj; - lcount = -1L; - + obj2 = FOLLOW(obj, traverse_how); if (!selective && oclasses[0] && !index(oclasses, obj->oclass)) continue; + lcount = -1L; if (!all_of_a_type) { char qbuf[BUFSZ]; @@ -648,7 +644,7 @@ int what; /* should be a long */ lcount = (long) yn_number; if (lcount > obj->quan) lcount = obj->quan; - /* fall thru */ + /*FALLTHRU*/ default: /* 'y' */ break; } @@ -779,11 +775,12 @@ menu_item **pick_list; /* list of objects and counts to pick up */ * SIGNAL_NOMENU - Return -1 rather than 0 if nothing passes "allow". * SIGNAL_ESCAPE - Return -1 rather than 0 if player uses ESC to * pick nothing. + * FEEL_COCKATRICE - touch corpse. */ int -query_objlist(qstr, olist, qflags, pick_list, how, allow) +query_objlist(qstr, olist_p, qflags, pick_list, how, allow) const char *qstr; /* query string */ -struct obj *olist; /* the list to pick from */ +struct obj **olist_p; /* the list to pick from */ int qflags; /* options to control the query */ menu_item **pick_list; /* return list of items picked */ int how; /* type of query */ @@ -791,12 +788,12 @@ boolean FDECL((*allow), (OBJ_P)); /* allow function */ { int i, n, actualn; winid win; - struct obj *curr, *last, fake_hero_object; - struct obj **oarray; + struct obj *curr, *last, fake_hero_object, *olist = *olist_p; char *pack; anything any; - boolean printed_type_name, sorted = (qflags & INVORDER_SORT) != 0, - engulfer = (qflags & INCLUDE_HERO) != 0; + boolean printed_type_name, + sorted = (qflags & INVORDER_SORT) != 0, + engulfer = (qflags & INCLUDE_HERO) != 0; *pick_list = (menu_item *) 0; if (!olist && !engulfer) @@ -819,27 +816,26 @@ boolean FDECL((*allow), (OBJ_P)); /* allow function */ return (qflags & SIGNAL_NOMENU) ? -1 : 0; if (n == 1 && (qflags & AUTOSELECT_SINGLE)) { - *pick_list = (menu_item *) alloc(sizeof(menu_item)); + *pick_list = (menu_item *) alloc(sizeof (menu_item)); (*pick_list)->item.a_obj = last; (*pick_list)->count = last->quan; return 1; } - oarray = objarr_init(actualn); - /* Add objects to the array */ - i = 0; - for (curr = olist; curr; curr = FOLLOW(curr, qflags)) { - if ((*allow)(curr)) { - objarr_set(curr, i++, oarray, (flags.sortloot == 'f' - || (flags.sortloot == 'l' - && !(qflags & USE_INVLET)))); - } + if (sorted) { + sortloot(&olist, + (((flags.sortloot == 'f' + || (flags.sortloot == 'l' && !(qflags & USE_INVLET))) + ? SORTLOOT_LOOT + : (qflags & USE_INVLET) ? SORTLOOT_INVLET : 0) + | (flags.sortpack ? SORTLOOT_PACK : 0)), + (qflags & BY_NEXTHERE) ? TRUE : FALSE); + *olist_p = olist; } win = create_nhwindow(NHW_MENU); start_menu(win); any = zeroany; - /* * Run through the list and add the objects to the menu. If * INVORDER_SORT is set, we'll run through the list once for @@ -849,22 +845,23 @@ boolean FDECL((*allow), (OBJ_P)); /* allow function */ pack = flags.inv_order; do { printed_type_name = FALSE; - for (i = 0; i < actualn; i++) { - curr = oarray[i]; + for (curr = olist; curr; curr = FOLLOW(curr, qflags)) { + if (sorted && curr->oclass != *pack) + continue; if ((qflags & FEEL_COCKATRICE) && curr->otyp == CORPSE && will_feel_cockatrice(curr, FALSE)) { destroy_nhwindow(win); /* stop the menu and revert */ (void) look_here(0, FALSE); return 0; } - if ((!sorted || curr->oclass == *pack) && (*allow)(curr)) { + if ((*allow)(curr)) { /* if sorting, print type name (once only) */ if (sorted && !printed_type_name) { any = zeroany; add_menu(win, NO_GLYPH, &any, 0, 0, iflags.menu_headings, let_to_name(*pack, FALSE, - (how != PICK_NONE) - && iflags.menu_head_objsym), + ((how != PICK_NONE) + && iflags.menu_head_objsym)), MENU_UNSELECTED); printed_type_name = TRUE; } @@ -878,7 +875,6 @@ boolean FDECL((*allow), (OBJ_P)); /* allow function */ } pack++; } while (sorted && *pack); - free(oarray); if (engulfer) { char buf[BUFSZ]; @@ -1656,7 +1652,6 @@ doloot() cc.y = u.uy; lootcont: - if ((num_conts = container_at(cc.x, cc.y, TRUE)) > 0) { boolean anyfound = FALSE; @@ -1875,11 +1870,12 @@ boolean *prev_loot; */ if (mtmp && mtmp != u.usteed && (otmp = which_armor(mtmp, W_SADDLE))) { long unwornmask; + if (passed_info) *passed_info = 1; - Sprintf( - qbuf, "Do you want to remove the saddle from %s?", - x_monnam(mtmp, ARTICLE_THE, (char *) 0, SUPPRESS_SADDLE, FALSE)); + Sprintf(qbuf, "Do you want to remove the saddle from %s?", + x_monnam(mtmp, ARTICLE_THE, (char *) 0, + SUPPRESS_SADDLE, FALSE)); if ((c = yn_function(qbuf, ynqchars, 'n')) == 'y') { if (nolimbs(youmonst.data)) { You_cant("do that without limbs."); /* not body_part(HAND) */ @@ -1887,8 +1883,8 @@ boolean *prev_loot; } if (otmp->cursed) { You("can't. The saddle seems to be stuck to %s.", - x_monnam(mtmp, ARTICLE_THE, (char *) 0, SUPPRESS_SADDLE, - FALSE)); + x_monnam(mtmp, ARTICLE_THE, (char *) 0, + SUPPRESS_SADDLE, FALSE)); /* the attempt costs you time */ return 1; } @@ -1907,8 +1903,7 @@ boolean *prev_loot; return 0; } } - /* 3.4.0 introduced the ability to pick things up from within - swallower's stomach */ + /* 3.4.0 introduced ability to pick things up from swallower's stomach */ if (u.uswallow) { int count = passed_info ? *passed_info : 0; @@ -2091,7 +2086,7 @@ int ck_bag(obj) struct obj *obj; { - return current_container && obj != current_container; + return (current_container && obj != current_container); } /* Returns: -1 to stop, 1 item was removed, 0 item was not removed. */ @@ -2218,14 +2213,14 @@ struct obj *box; pline("%s inside the box is still alive!", Monnam(livecat)); (void) christen_monst(livecat, sc); } else { - deadcat = - mk_named_object(CORPSE, &mons[PM_HOUSECAT], box->ox, box->oy, sc); + deadcat = mk_named_object(CORPSE, &mons[PM_HOUSECAT], + box->ox, box->oy, sc); if (deadcat) { obj_extract_self(deadcat); (void) add_to_container(box, deadcat); } pline_The("%s inside the box is dead!", - Hallucination ? rndmonnam(NULL) : "housecat"); + Hallucination ? rndmonnam((char *) 0) : "housecat"); } box->owt = weight(box); return; @@ -2409,14 +2404,14 @@ boolean more_containers; /* True iff #loot multiple and this isn't last one */ (boolean) (used != 0), more_containers); } } else { /* TRADITIONAL or COMBINATION */ - xbuf[0] = '\0'; /* list of extra acceptable responses */ - Strcpy(pbuf, ":"); /* look inside */ + xbuf[0] = '\0'; /* list of extra acceptable responses */ + Strcpy(pbuf, ":"); /* look inside */ Strcat(outmaybe ? pbuf : xbuf, "o"); /* take out */ Strcat(inokay ? pbuf : xbuf, "i"); /* put in */ Strcat(outmaybe ? pbuf : xbuf, "b"); /* both */ Strcat(inokay ? pbuf : xbuf, "rs"); /* reversed, stash */ - Strcat(pbuf, " "); - Strcat(more_containers ? pbuf : xbuf, "n"); + Strcat(pbuf, " "); /* separator */ + Strcat(more_containers ? pbuf : xbuf, "n"); /* next container */ Strcat(pbuf, "q"); /* quit */ if (iflags.cmdassist) /* this unintentionally allows user to answer with 'o' or @@ -2624,7 +2619,7 @@ boolean put_in; if (!put_in) current_container->cknown = 1; Sprintf(buf, "%s what?", action); - n = query_objlist(buf, put_in ? invent : current_container->cobj, + n = query_objlist(buf, put_in ? &invent : &(current_container->cobj), mflags, &pick_list, PICK_ANY, all_categories ? allow_all : allow_category); if (n) { From 529dad8ef15d255e00ba2d75dd9a92d5030d97f5 Mon Sep 17 00:00:00 2001 From: PatR Date: Sun, 13 Mar 2016 17:45:18 -0700 Subject: [PATCH 021/151] looting gold When removing items from a container via menu, list gold as '$' instead of 'a' when it is the first item. Requested during beta testing last year.... When gold isn't first ('sortpack' false, or custom 'inv_order[]'), it uses the next letter in sequence instead of '$', otherwise it would be the only item out of sequence. --- src/options.c | 20 +++++++++++--------- src/pickup.c | 13 ++++++++----- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/options.c b/src/options.c index c200ae1d2..e96252911 100644 --- a/src/options.c +++ b/src/options.c @@ -1010,17 +1010,20 @@ change_inv_order(op) char *op; { int oc_sym, num; - char *sp, buf[BUFSZ]; + char *sp, buf[QBUFSZ]; num = 0; - /* !!!! probably unnecessary with gold as normal inventory */ + if (!index(op, GOLD_SYM)) + buf[num++] = COIN_CLASS; for (sp = op; *sp; sp++) { oc_sym = def_char_to_objclass(*sp); /* reject bad or duplicate entries */ - if (oc_sym == MAXOCLASSES || oc_sym == RANDOM_CLASS - || oc_sym == ILLOBJ_CLASS || !index(flags.inv_order, oc_sym) - || index(sp + 1, *sp)) + if (oc_sym == MAXOCLASSES /* not an object class char */ + /* VENOM_CLASS, RANDOM_CLASS, and ILLOBJ_CLASS are excluded + because they aren't in def_inv_order[] so don't make it + into flags.inv_order, hence always fail this index() test */ + || !index(flags.inv_order, oc_sym) || index(sp + 1, *sp)) return 0; /* retain good ones */ buf[num++] = (char) oc_sym; @@ -1029,10 +1032,9 @@ char *op; /* fill in any omitted classes, using previous ordering */ for (sp = flags.inv_order; *sp; sp++) - if (!index(buf, *sp)) { - buf[num++] = *sp; - buf[num] = '\0'; /* explicitly terminate for next index() */ - } + if (!index(buf, *sp)) + (void) strkitten(&buf[num++], *sp); + buf[MAXOCLASSES - 1] = '\0'; Strcpy(flags.inv_order, buf); return 1; diff --git a/src/pickup.c b/src/pickup.c index 39effd261..466c1e10f 100644 --- a/src/pickup.c +++ b/src/pickup.c @@ -791,7 +791,7 @@ boolean FDECL((*allow), (OBJ_P)); /* allow function */ struct obj *curr, *last, fake_hero_object, *olist = *olist_p; char *pack; anything any; - boolean printed_type_name, + boolean printed_type_name, first, sorted = (qflags & INVORDER_SORT) != 0, engulfer = (qflags & INCLUDE_HERO) != 0; @@ -843,6 +843,7 @@ boolean FDECL((*allow), (OBJ_P)); /* allow function */ * be called once per object in the list. */ pack = flags.inv_order; + first = TRUE; do { printed_type_name = FALSE; for (curr = olist; curr; curr = FOLLOW(curr, qflags)) { @@ -868,9 +869,11 @@ boolean FDECL((*allow), (OBJ_P)); /* allow function */ any.a_obj = curr; add_menu(win, obj_to_glyph(curr), &any, - (qflags & USE_INVLET) ? curr->invlet : 0, + (qflags & USE_INVLET) ? curr->invlet + : (first && curr->oclass == COIN_CLASS) ? '$' : 0, def_oc_syms[(int) objects[curr->otyp].oc_class].sym, ATR_NONE, doname_with_price(curr), MENU_UNSELECTED); + first = FALSE; } } pack++; @@ -2586,9 +2589,9 @@ boolean put_in; } else if (flags.menu_style == MENU_FULL) { all_categories = FALSE; Sprintf(buf, "%s what type of objects?", action); - mflags = put_in - ? ALL_TYPES | BUC_ALLBKNOWN | BUC_UNKNOWN - : ALL_TYPES | CHOOSE_ALL | BUC_ALLBKNOWN | BUC_UNKNOWN; + mflags = (ALL_TYPES | BUC_ALLBKNOWN | BUC_UNKNOWN); + if (put_in) + mflags |= CHOOSE_ALL; n = query_category(buf, put_in ? invent : current_container->cobj, mflags, &pick_list, PICK_ANY); if (!n) From 1aff4f89fd4d53caf5a483323f1428668968735a Mon Sep 17 00:00:00 2001 From: PatR Date: Mon, 14 Mar 2016 15:32:17 -0700 Subject: [PATCH 022/151] sortloot fixes Fix some typos in the sort-by-invlet code and a logic error in the lately added subclass sorting for sort-by-pack. Regular inventory display only works correctly for the latter if invlet is the tie- breaker within object classes. When helmet/gloves/boots/&c and ammo/launcher/missile/&c sub-categories already break ties for armor and weapon classes, inventory ended up out of alphabetical order. --- src/invent.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/invent.c b/src/invent.c index 37937b6cf..5670476ab 100644 --- a/src/invent.c +++ b/src/invent.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 invent.c $NHDT-Date: 1457207035 2016/03/05 19:43:55 $ $NHDT-Branch: chasonr $:$NHDT-Revision: 1.197 $ */ +/* NetHack 3.6 invent.c $NHDT-Date: 1457994703 2016/03/14 22:31:43 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.199 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -74,8 +74,11 @@ const genericptr vptr2; if (cls1 != cls2) return (int) (cls1 - cls2); + if ((sortlootmode & SORTLOOT_INVLET) != 0) { + ; /* skip sub-classes when sorting by packorder+invlet */ + /* for armor, group by sub-category */ - if (obj1->oclass == ARMOR_CLASS) { + } else if (obj1->oclass == ARMOR_CLASS) { static int armcat[7 + 1]; if (!armcat[7]) { @@ -119,13 +122,13 @@ const genericptr vptr2; if ((sortlootmode & SORTLOOT_INVLET) != 0) { c = obj1->invlet; val1 = ('a' <= c && c <= 'z') ? (c - 'a' + 2) - : ('A' <= c && c <= 'Z') ? (c - 'Z' + 2 + 26) + : ('A' <= c && c <= 'Z') ? (c - 'A' + 2 + 26) : (c == '$') ? 1 : (c == '#') ? 1 + 52 + 1 : 1 + 52 + 1 + 1; /* none of the above */ c = obj2->invlet; - val2 = ('a' <= c <= 'z') ? (c - 'a' + 2) - : ('A' <= c <= 'Z') ? (c - 'Z' + 2 + 26) + val2 = ('a' <= c && c <= 'z') ? (c - 'a' + 2) + : ('A' <= c && c <= 'Z') ? (c - 'A' + 2 + 26) : (c == '$') ? 1 : (c == '#') ? 1 + 52 + 1 : 1 + 52 + 1 + 1; /* none of the above */ From 1a886241ae4620426f44517b23bdea8afa925615 Mon Sep 17 00:00:00 2001 From: PatR Date: Mon, 14 Mar 2016 15:39:18 -0700 Subject: [PATCH 023/151] monst.h formatting Clean up the formatting in monst.h. Provoked by the M_AP_NOTHING absurdity. --- include/monst.h | 139 ++++++++++++++++++++++++------------------------ 1 file changed, 69 insertions(+), 70 deletions(-) diff --git a/include/monst.h b/include/monst.h index 7196026f9..171b840a2 100644 --- a/include/monst.h +++ b/include/monst.h @@ -1,10 +1,14 @@ -/* NetHack 3.6 monst.h $NHDT-Date: 1432512777 2015/05/25 00:12:57 $ $NHDT-Branch: master $:$NHDT-Revision: 1.20 $ */ +/* NetHack 3.6 monst.h $NHDT-Date: 1457995142 2016/03/14 22:39:02 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.23 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ #ifndef MONST_H #define MONST_H +#ifndef MEXTRA_H +#include "mextra.h" +#endif + /* The weapon_check flag is used two ways: * 1) When calling mon_wield_item, is 2-6 depending on what is desired. * 2) Between calls to mon_wield_item, is 0 or 1 depending on whether or not @@ -15,13 +19,13 @@ * that there are enough situations which might make a monster change its * weapon that this is impractical. --KAA */ -#define NO_WEAPON_WANTED 0 -#define NEED_WEAPON 1 +#define NO_WEAPON_WANTED 0 +#define NEED_WEAPON 1 #define NEED_RANGED_WEAPON 2 -#define NEED_HTH_WEAPON 3 -#define NEED_PICK_AXE 4 -#define NEED_AXE 5 -#define NEED_PICK_OR_AXE 6 +#define NEED_HTH_WEAPON 3 +#define NEED_PICK_AXE 4 +#define NEED_AXE 5 +#define NEED_PICK_OR_AXE 6 /* The following flags are used for the second argument to display_minventory * in invent.c: @@ -31,36 +35,30 @@ * just display wielded weapons and worn items. */ #define MINV_NOLET 0x01 -#define MINV_ALL 0x02 - -#ifndef MEXTRA_H -#include "mextra.h" -#endif +#define MINV_ALL 0x02 struct monst { struct monst *nmon; struct permonst *data; unsigned m_id; - short mnum; /* permanent monster index number */ - short cham; /* if shapeshifter, orig mons[] idx goes here */ - short movement; /* movement points (derived from permonst definition and - added effects */ - uchar m_lev; /* adjusted difficulty level of monster */ - aligntyp malign; /* alignment of this monster, relative to the - player (positive = good to kill) */ + short mnum; /* permanent monster index number */ + short cham; /* if shapeshifter, orig mons[] idx goes here */ + short movement; /* movement points (derived from permonst definition + and added effects */ + uchar m_lev; /* adjusted difficulty level of monster */ + aligntyp malign; /* alignment of this monster, relative to the + player (positive = good to kill) */ xchar mx, my; - xchar mux, muy; /* where the monster thinks you are */ + xchar mux, muy; /* where the monster thinks you are */ #define MTSZ 4 - coord mtrack[MTSZ]; /* monster track */ + coord mtrack[MTSZ]; /* monster track */ int mhp, mhpmax; unsigned mappearance; /* for undetected mimics and the wiz */ uchar m_ap_type; /* what mappearance is describing: */ -#define M_AP_NOTHING \ - 0 /* mappearance is unused -- monster appears \ - as itself */ -#define M_AP_FURNITURE 1 /* stairs, a door, an altar, etc. */ -#define M_AP_OBJECT 2 /* an object */ -#define M_AP_MONSTER 3 /* a monster */ +#define M_AP_NOTHING 0 /* mappearance unused--monster appears as itself */ +#define M_AP_FURNITURE 1 /* stairs, a door, an altar, etc. */ +#define M_AP_OBJECT 2 /* an object */ +#define M_AP_MONSTER 3 /* a monster */ schar mtame; /* level of tameness, implies peaceful */ unsigned short mintrinsics; /* low 8 correspond to mresists */ @@ -72,12 +70,12 @@ struct monst { Bitfield(perminvis, 1); /* intrinsic minvis value */ Bitfield(mcan, 1); /* has been cancelled */ Bitfield(mburied, 1); /* has been buried */ - Bitfield(mundetected, 1); /* not seen in present hiding place */ - /* implies one of M1_CONCEAL or M1_HIDE, + Bitfield(mundetected, 1); /* not seen in present hiding place; + * implies one of M1_CONCEAL or M1_HIDE, * but not mimic (that is, snake, spider, * trapper, piercer, eel) */ - Bitfield(mcansee, 1); /* cansee 1, temp.blinded 0, blind 0 */ + Bitfield(mcansee, 1); /* cansee 1, temp.blinded 0, blind 0 */ Bitfield(mspeed, 2); /* current speed */ Bitfield(permspeed, 2); /* intrinsic mspeed value */ @@ -89,11 +87,11 @@ struct monst { Bitfield(mfleetim, 7); /* timeout for mflee */ Bitfield(msleeping, 1); /* asleep until woken */ - Bitfield(mblinded, 7); /* cansee 0, temp.blinded n, blind 0 */ - Bitfield(mstun, 1); /* stunned (off balance) */ + Bitfield(mblinded, 7); /* cansee 0, temp.blinded n, blind 0 */ + Bitfield(mstun, 1); /* stunned (off balance) */ Bitfield(mfrozen, 7); - Bitfield(mcanmove, 1); /* paralysis, similar to mblinded */ + Bitfield(mcanmove, 1); /* paralysis, similar to mblinded */ Bitfield(mconf, 1); /* confused */ Bitfield(mpeaceful, 1); /* does not attack unprovoked */ @@ -102,13 +100,13 @@ struct monst { Bitfield(isshk, 1); /* is shopkeeper */ Bitfield(isminion, 1); /* is a minion */ Bitfield(isgd, 1); /* is guard */ - Bitfield(ispriest, 1); /* is a priest */ + Bitfield(ispriest, 1); /* is an aligned priest or high priest */ - Bitfield(iswiz, 1); /* is the Wizard of Yendor */ - Bitfield(wormno, 5); /* at most 31 worms on any level */ -/* 2 free bits */ + Bitfield(iswiz, 1); /* is the Wizard of Yendor */ + Bitfield(wormno, 5); /* at most 31 worms on any level */ + /* 2 free bits */ -#define MAX_NUM_WORMS 32 /* should be 2^(wormno bitfield size) */ +#define MAX_NUM_WORMS 32 /* should be 2^(wormno bitfield size) */ unsigned long mstrategy; /* for monsters with mflag3: current strategy */ #ifdef NHSTDC @@ -116,39 +114,39 @@ struct monst { #else #define STRAT_APPEARMSG 0x80000000L #endif -#define STRAT_ARRIVE 0x40000000L /* just arrived on current level */ -#define STRAT_WAITFORU 0x20000000L -#define STRAT_CLOSE 0x10000000L -#define STRAT_WAITMASK (STRAT_CLOSE | STRAT_WAITFORU) -#define STRAT_HEAL 0x08000000L -#define STRAT_GROUND 0x04000000L -#define STRAT_MONSTR 0x02000000L -#define STRAT_PLAYER 0x01000000L -#define STRAT_NONE 0x00000000L +#define STRAT_ARRIVE 0x40000000L /* just arrived on current level */ +#define STRAT_WAITFORU 0x20000000L +#define STRAT_CLOSE 0x10000000L +#define STRAT_WAITMASK (STRAT_CLOSE | STRAT_WAITFORU) +#define STRAT_HEAL 0x08000000L +#define STRAT_GROUND 0x04000000L +#define STRAT_MONSTR 0x02000000L +#define STRAT_PLAYER 0x01000000L +#define STRAT_NONE 0x00000000L #define STRAT_STRATMASK 0x0f000000L -#define STRAT_XMASK 0x00ff0000L -#define STRAT_YMASK 0x0000ff00L -#define STRAT_GOAL 0x000000ffL -#define STRAT_GOALX(s) ((xchar)((s & STRAT_XMASK) >> 16)) -#define STRAT_GOALY(s) ((xchar)((s & STRAT_YMASK) >> 8)) +#define STRAT_XMASK 0x00ff0000L +#define STRAT_YMASK 0x0000ff00L +#define STRAT_GOAL 0x000000ffL +#define STRAT_GOALX(s) ((xchar) ((s & STRAT_XMASK) >> 16)) +#define STRAT_GOALY(s) ((xchar) ((s & STRAT_YMASK) >> 8)) - long mtrapseen; /* bitmap of traps we've been trapped in */ - long mlstmv; /* for catching up with lost time */ + long mtrapseen; /* bitmap of traps we've been trapped in */ + long mlstmv; /* for catching up with lost time */ long mspare1; - struct obj *minvent; + struct obj *minvent; /* mon's inventory */ - struct obj *mw; - long misc_worn_check; - xchar weapon_check; + struct obj *mw; /* mon's weapon */ + long misc_worn_check; /* mon's wornmask */ + xchar weapon_check; /* flag for whether to try switching weapons */ int meating; /* monster is eating timeout */ struct mextra *mextra; /* point to mextra struct */ }; -#define newmonst() (struct monst *) alloc(sizeof(struct monst)) +#define newmonst() (struct monst *) alloc(sizeof (struct monst)) /* these are in mspeed */ -#define MSLOW 1 /* slow monster */ +#define MSLOW 1 /* slowed monster */ #define MFAST 2 /* speeded monster */ #define MON_WEP(mon) ((mon)->mw) @@ -161,16 +159,17 @@ struct monst { || (mon)->cham == PM_VLAD_THE_IMPALER) /* mimic appearances that block vision/light */ -#define is_lightblocker_mappear(mon) \ - (is_obj_mappear(mon, BOULDER) || \ - ((mon)->m_ap_type == M_AP_FURNITURE \ - && ((mon)->mappearance == S_hcdoor \ - || (mon)->mappearance == S_vcdoor \ - || (mon)->mappearance < S_ndoor /* = walls */ \ - || (mon)->mappearance == S_tree))) - +#define is_lightblocker_mappear(mon) \ + (is_obj_mappear(mon, BOULDER) \ + || ((mon)->m_ap_type == M_AP_FURNITURE \ + && ((mon)->mappearance == S_hcdoor \ + || (mon)->mappearance == S_vcdoor \ + || (mon)->mappearance < S_ndoor /* = walls */ \ + || (mon)->mappearance == S_tree))) #define is_door_mappear(mon) ((mon)->m_ap_type == M_AP_FURNITURE \ - && ((mon)->mappearance == S_hcdoor || (mon)->mappearance == S_vcdoor)) + && ((mon)->mappearance == S_hcdoor \ + || (mon)->mappearance == S_vcdoor)) #define is_obj_mappear(mon,otyp) ((mon)->m_ap_type == M_AP_OBJECT \ - && (mon)->mappearance == (otyp)) + && (mon)->mappearance == (otyp)) + #endif /* MONST_H */ From 77fe8db3202bb4bf11ee9577c4b9fe7c28317a7a Mon Sep 17 00:00:00 2001 From: PatR Date: Mon, 14 Mar 2016 15:42:03 -0700 Subject: [PATCH 024/151] monster sanity checking Add some more checks for monsters when the sanity_check option is enabled. --- src/mon.c | 71 ++++++++++++++++++++++++++++++++++++++++----------- src/mondata.c | 6 +++-- 2 files changed, 60 insertions(+), 17 deletions(-) diff --git a/src/mon.c b/src/mon.c index c97963abe..b0eec652e 100644 --- a/src/mon.c +++ b/src/mon.c @@ -13,7 +13,8 @@ STATIC_VAR boolean vamp_rise_msg; -STATIC_DCL void FDECL(sanity_check_single_mon, (struct monst *, const char *)); +STATIC_DCL void FDECL(sanity_check_single_mon, (struct monst *, BOOLEAN_P, + const char *)); STATIC_DCL boolean FDECL(restrap, (struct monst *)); STATIC_DCL long FDECL(mm_aggression, (struct monst *, struct monst *)); STATIC_DCL long FDECL(mm_displacement, (struct monst *, struct monst *)); @@ -41,35 +42,75 @@ const char *warnings[] = { void -sanity_check_single_mon(mtmp, msg) +sanity_check_single_mon(mtmp, chk_geno, msg) struct monst *mtmp; +boolean chk_geno; const char *msg; { if (DEADMONSTER(mtmp)) return; - if (mtmp->data < &mons[LOW_PM] || mtmp->data >= &mons[NUMMONS]) - impossible("illegal mon data (%s)", msg); + if (mtmp->data < &mons[LOW_PM] || mtmp->data >= &mons[NUMMONS]) { + impossible("illegal mon data %s; mnum=%d (%s)", + fmt_ptr((genericptr_t) mtmp->data), mtmp->mnum, msg); + } else { + int mndx = monsndx(mtmp->data); + + if (mtmp->mnum != mndx) { + impossible("monster mnum=%d, monsndx=%d (%s)", + mtmp->mnum, mndx, msg); + mtmp->mnum = mndx; + } + if (chk_geno && (mvitals[mndx].mvflags & G_GENOD) != 0) + impossible("genocided %s in play (%s)", mons[mndx].mname, msg); + } + if (mtmp->isshk && !has_eshk(mtmp)) + impossible("shk without eshk (%s)", msg); + if (mtmp->ispriest && !has_epri(mtmp)) + impossible("priest without epri (%s)", msg); + if (mtmp->isgd && !has_egd(mtmp)) + impossible("guard without egd (%s)", msg); + if (mtmp->isminion && !has_emin(mtmp)) + impossible("minion without emin (%s)", msg); + /* guardian angel on astral level is tame but has emin rather than edog */ + if (mtmp->mtame && !has_edog(mtmp) && !mtmp->isminion) + impossible("pet without edog (%s)", msg); } void mon_sanity_check() { - int x,y; - struct monst *mtmp = fmon; + int x, y; + struct monst *mtmp, *m; - while (mtmp) { - sanity_check_single_mon(mtmp, "fmon"); - mtmp = mtmp->nmon; + for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { + sanity_check_single_mon(mtmp, TRUE, "fmon"); + x = mtmp->mx, y = mtmp->my; + if (!isok(x, y) || (x == 0 && !mtmp->isgd)) + impossible("mon (%s) claims to be at <%d,%d>?", + fmt_ptr((genericptr_t) mtmp), x, y); + else if (level.monsters[x][y] != mtmp) + impossible("mon (%s) at <%d,%d> is not there!", + fmt_ptr((genericptr_t) mtmp), x, y); } + for (x = 0; x < COLNO; x++) for (y = 0; y < ROWNO; y++) - if ((mtmp = m_at(x,y)) != 0) - sanity_check_single_mon(mtmp, "m_at"); + if ((mtmp = level.monsters[x][y]) != 0) { + for (m = fmon; m; m = m->nmon) + if (m == mtmp) + break; + if (!m) + impossible("map mon (%s) at <%d,%d> not in fmon list!", + fmt_ptr((genericptr_t) mtmp), x, y); + else if ((mtmp->mx != x || mtmp->my != y) + && mtmp->data != &mons[PM_LONG_WORM]) + impossible("map mon (%s) at <%d,%d> is found at <%d,%d>?", + fmt_ptr((genericptr_t) mtmp), + mtmp->mx, mtmp->my, x, y); + } - mtmp = migrating_mons; - while (mtmp) { - sanity_check_single_mon(mtmp, "migr"); - mtmp = mtmp->nmon; + for (mtmp = migrating_mons; mtmp; mtmp = mtmp->nmon) { + sanity_check_single_mon(mtmp, FALSE, "migr"); } } diff --git a/src/mondata.c b/src/mondata.c index 97bee8ae1..0e924f3fc 100644 --- a/src/mondata.c +++ b/src/mondata.c @@ -3,8 +3,9 @@ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" - -/* These routines provide basic data for any type of monster. */ +/* + * These routines provide basic data for any type of monster. + */ /* set up an individual monster's base type (initial creation, shapechange) */ void @@ -16,6 +17,7 @@ int flag; int new_speed, old_speed = mon->data ? mon->data->mmove : 0; mon->data = ptr; + mon->mnum = (short) monsndx(ptr); if (flag == -1) return; /* "don't care" */ From 13c40d85e7bb8aa9450e933ea9ac2c205d3b2e29 Mon Sep 17 00:00:00 2001 From: PatR Date: Tue, 15 Mar 2016 01:00:36 -0700 Subject: [PATCH 025/151] fix #H4275 - blinded, stunned, confused timers Blindness due to face covered by pie was ignored for several cases of magically curing blindness--cleaning the face seems better than adjusting timeout to account for u.ucreamed for those cases. A few instances of taking stun or confusion damage overrode existing stun or confusion rather than increasing it. Plus a copy/paste mistake for dual stun+confusion when casting an expired spell. There was also a suggestion that vomiting when already nauseated should decrement the timer instead of increasing it. But there is a negative effect for as long as it's in effect, so I left that as is. --- doc/fixes36.1 | 3 +++ src/eat.c | 7 ++++++- src/mhitu.c | 11 ++++++++--- src/potion.c | 6 +++++- src/pray.c | 9 ++++++++- src/sit.c | 1 + src/spell.c | 4 ++-- 7 files changed, 33 insertions(+), 8 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index 68dacfdab..d990e736c 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -185,6 +185,9 @@ wand/spell/breath zaps that reached the edge of the level on the Plane of Air stop amulets and other items which aren't affected by erosion damage from being subjected to erosion damage grammar bit: polyself w/ gender change yielded "you turn into a Elvenking" +some blindness cures ignored u.ucreamed +some instances of stun or confusion timers were being overridden rather than + incremented when new stun or confusion damage was suffered Platform- and/or Interface-Specific Fixes diff --git a/src/eat.c b/src/eat.c index 269fcaf28..4c69cf147 100644 --- a/src/eat.c +++ b/src/eat.c @@ -1502,7 +1502,9 @@ struct obj *obj; make_confused(HConfusion + d(2, 4), FALSE); } else if (!rn2(4) && !Blind) { pline("Everything suddenly goes dark."); - make_blinded((long) d(2, 10), FALSE); + /* hero is not Blind, but Blinded timer might be nonzero if + blindness is being overridden by the Eyes of the Overworld */ + make_blinded((Blinded & TIMEOUT) + (long) d(2, 10), FALSE); if (!Blind) Your1(vision_clears); } else if (!rn2(3)) { @@ -1785,6 +1787,9 @@ struct obj *otmp; #endif } else if (otmp->otyp == EGG && stale_egg(otmp)) { pline("Ugh. Rotten egg."); /* perhaps others like it */ + /* increasing existing nausea means that it will take longer + before eventual vomit, but also means that constitution + will be abused more times before illness completes */ make_vomiting((Vomiting & TIMEOUT) + (long) d(10, 4), TRUE); } else { give_feedback: diff --git a/src/mhitu.c b/src/mhitu.c index f7f0afde2..9f3d70b14 100644 --- a/src/mhitu.c +++ b/src/mhitu.c @@ -2147,10 +2147,15 @@ register struct attack *mattk; /* not blind at this point implies you're wearing the Eyes of the Overworld; make them block this particular stun attack too */ - if (!Blind) + if (!Blind) { Your1(vision_clears); - else - make_stunned((long) d(1, 3), TRUE); + } else { + long oldstun = (HStun & TIMEOUT), newstun = (long) rnd(3); + + /* we don't want to increment stun duration every time + or sighted hero will become incapacitated */ + make_stunned(max(oldstun, newstun), TRUE); + } } } break; diff --git a/src/potion.c b/src/potion.c index 53de9e8d9..568c48d99 100644 --- a/src/potion.c +++ b/src/potion.c @@ -1090,8 +1090,12 @@ register boolean curesick, cureblind; u.uhp = (u.uhpmax += nxtra); } } - if (cureblind) + if (cureblind) { + /* 3.6.1: it's debatible whether healing magic should clean off + mundane 'dirt', but if it doesn't, blindness isn't cured */ + u.ucreamed = 0; make_blinded(0L, TRUE); + } if (curesick) { make_vomiting(0L, TRUE); make_sick(0L, (char *) 0, TRUE, SICK_ALL); diff --git a/src/pray.c b/src/pray.c index 4c2df62a6..604e0663f 100644 --- a/src/pray.c +++ b/src/pray.c @@ -289,7 +289,7 @@ worst_cursed_item() } else if (uright && uright->cursed) { /* right ring */ otmp = uright; } else if (ublindf && ublindf->cursed) { /* eyewear */ - otmp = ublindf; /* must be non-blinding lenses */ + otmp = ublindf; /* must be non-blinding lenses */ /* if weapon wasn't handled above, do it now */ } else if (welded(uwep)) { /* weapon */ otmp = uwep; @@ -1048,8 +1048,15 @@ aligntyp g_align; ABASE(A_STR) = AMAX(A_STR); if (u.uhunger < 900) init_uhunger(); + /* luck couldn't have been negative at start of prayer because + the prayer would have failed, but might have been decremented + due to a timed event (delayed death of peaceful monster hit + by hero-created stinking cloud) during the praying interval */ if (u.uluck < 0) u.uluck = 0; + /* superfluous; if hero was blinded we'd be handling trouble + rather than issuing a pat-on-head */ + u.ucreamed = 0; make_blinded(0L, TRUE); context.botl = 1; break; diff --git a/src/sit.c b/src/sit.c index b91da1581..b39df58ea 100644 --- a/src/sit.c +++ b/src/sit.c @@ -184,6 +184,7 @@ dosit() if (u.uhp >= (u.uhpmax - 5)) u.uhpmax += 4; u.uhp = u.uhpmax; + u.ucreamed = 0; make_blinded(0L, TRUE); make_sick(0L, (char *) 0, FALSE, SICK_ALL); heal_legs(); diff --git a/src/spell.c b/src/spell.c index 6110a8472..a8adba34c 100644 --- a/src/spell.c +++ b/src/spell.c @@ -866,8 +866,8 @@ int spell; break; case 7: case 8: - make_stunned(old_conf + 2L * duration / 3L, FALSE); /* 20% */ - make_confused(old_stun + duration / 3L, FALSE); + make_stunned(old_stun + 2L * duration / 3L, FALSE); /* 20% */ + make_confused(old_conf + duration / 3L, FALSE); break; case 9: make_stunned(old_stun + duration, FALSE); /* 10% */ From 606a738c4be9ef56575c577fa04d08886fd09106 Mon Sep 17 00:00:00 2001 From: PatR Date: Wed, 16 Mar 2016 15:03:37 -0700 Subject: [PATCH 026/151] fix #H4274 - monster growing up changes gender Most of the humanoid species have Lords and several have Kings, but none of them have Ladies or Queens. When a female grows up to reach that level of monster, she changes into male. This fix gives an alternate message acknowledging that change rather than prevent taking on the stronger form. A better fix would be to add ogre ladies and dwarf queens as separate monsters, but doing so will break 3.6.0 save file compatibility. (I started out with an alternate fix, adding mons[].fname for the dozen or so creatures which warrant an alternate name for females. But that requires statues, figurines, corpses, tins, and maybe even eggs to track gender [some statues already do, and corpses and statues with attached mtraits also implicitly do] and to not stack with equivalent ones of the opposite gender. Plus glyphs to track them, and new tiles. It was becoming too complicated for such a relatively unimportant feature. Separate monsters is the way to go, deferred until save file format changes again.) --- doc/fixes36.1 | 2 ++ src/makemon.c | 25 +++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index d990e736c..31655d3ab 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -188,6 +188,8 @@ grammar bit: polyself w/ gender change yielded "you turn into a Elvenking" some blindness cures ignored u.ucreamed some instances of stun or confusion timers were being overridden rather than incremented when new stun or confusion damage was suffered +female gnome who gains level can grow up into male-only gnome lord; give an + an alternate message instead of prohibiting the promotion Platform- and/or Interface-Specific Fixes diff --git a/src/makemon.c b/src/makemon.c index 422a446c2..9e2d5b9a6 100644 --- a/src/makemon.c +++ b/src/makemon.c @@ -1668,6 +1668,7 @@ grow_up(mtmp, victim) struct monst *mtmp, *victim; { int oldtype, newtype, max_increase, cur_increase, lev_limit, hp_threshold; + unsigned fem; struct permonst *ptr = mtmp->data; /* monster died after killing enemy but before calling this function */ @@ -1728,6 +1729,9 @@ struct monst *mtmp, *victim; else if (lev_limit > 49) lev_limit = (ptr->mlevel > 49 ? 50 : 49); + /* new form might force gender change */ + fem = is_male(ptr) ? 0 : is_female(ptr) ? 1 : mtmp->female; + if ((int) ++mtmp->m_lev >= mons[newtype].mlevel && newtype != oldtype) { ptr = &mons[newtype]; if (mvitals[newtype].mvflags & G_GENOD) { /* allow G_EXTINCT */ @@ -1739,14 +1743,31 @@ struct monst *mtmp, *victim; mondied(mtmp); return (struct permonst *) 0; } else if (canspotmon(mtmp)) { + char buf[BUFSZ]; + + /* 3.6.1: + * Temporary (?) hack to fix growing into opposite gender. + */ + Sprintf(buf, "%s%s", + /* deal with female gnome becoming a gnome lord */ + (mtmp->female && !fem) ? "male " + /* or a male gnome becoming a gnome lady + (can't happen with 3.6.0 mons[], but perhaps + slightly less sexist if prepared for it...) */ + : (fem && !mtmp->female) ? "female " : "", + ptr->mname); pline("%s %s %s.", Monnam(mtmp), - humanoid(ptr) ? "becomes" : "grows up into", - an(ptr->mname)); + (fem != mtmp->female) ? "changes into" + : humanoid(ptr) ? "becomes" + : "grows up into", + an(buf)); } set_mon_data(mtmp, ptr, 1); /* preserve intrinsics */ newsym(mtmp->mx, mtmp->my); /* color may change */ lev_limit = (int) mtmp->m_lev; /* never undo increment */ } + mtmp->female = fem; /* gender might be changing */ + /* sanity checks */ if ((int) mtmp->m_lev > lev_limit) { mtmp->m_lev--; /* undo increment */ From 2ff96797a8a52d0917529f68f1a3e38dca2668e8 Mon Sep 17 00:00:00 2001 From: Tung Nguyen Date: Thu, 17 Mar 2016 14:20:17 +1100 Subject: [PATCH 027/151] Omit accel and select char from menu colors e.g. When there's "a - whatever", color the "whatever" part and leave the "a - " part unformatted. This matches the behavior of menu colors in NAO343. --- win/tty/wintty.c | 40 +++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/win/tty/wintty.c b/win/tty/wintty.c index 199474869..9c33a11b1 100644 --- a/win/tty/wintty.c +++ b/win/tty/wintty.c @@ -1777,6 +1777,15 @@ struct WinDesc *cw; page_lines++, curr = curr->next) { int color = NO_COLOR, attr = ATR_NONE; boolean menucolr = FALSE; + int select_pos = -1; /* -/+/# position for selectables */ + int format_start_pos = 0; /* menu color/attr start pos */ + + if (curr->identifier.a_void != 0) { + /* "a - whatever" */ + select_pos = 2; /* '-' */ + format_start_pos = 4; /* start of "whatever" */ + } + if (curr->selector) *rp++ = curr->selector; @@ -1793,27 +1802,31 @@ struct WinDesc *cw; * actually output the character. We're faster doing * this. */ - if (iflags.use_menu_color - && (menucolr = get_menu_coloring(curr->str, &color, - &attr))) { - term_start_attr(attr); -#ifdef TEXTCOLOR - if (color != NO_COLOR) - term_start_color(color); -#endif - } else - term_start_attr(curr->attr); for (n = 0, cp = curr->str; #ifndef WIN32CON *cp && (int) ++ttyDisplay->curx < (int) ttyDisplay->cols; - cp++, n++) + cp++, n++ #else *cp && (int) ttyDisplay->curx < (int) ttyDisplay->cols; - cp++, n++, ttyDisplay->curx++) + cp++, n++, ttyDisplay->curx++ #endif - if (n == 2 && curr->identifier.a_void != 0 + ) { + if (n == format_start_pos) { + if (iflags.use_menu_color + && (menucolr = get_menu_coloring(curr->str, + &color, + &attr))) { + term_start_attr(attr); +#ifdef TEXTCOLOR + if (color != NO_COLOR) + term_start_color(color); +#endif + } else + term_start_attr(curr->attr); + } + if (n == select_pos && curr->identifier.a_void != 0 && curr->selected) { if (curr->count == -1L) (void) putchar('+'); /* all selected */ @@ -1821,6 +1834,7 @@ struct WinDesc *cw; (void) putchar('#'); /* count selected */ } else (void) putchar(*cp); + } if (iflags.use_menu_color && menucolr) { #ifdef TEXTCOLOR if (color != NO_COLOR) From cfb0a075525148767845d7905d758e57679e3b6c Mon Sep 17 00:00:00 2001 From: Tung Nguyen Date: Thu, 17 Mar 2016 14:46:59 +1100 Subject: [PATCH 028/151] Stop `>` from closing TTY menus on the last page Pages can now be freely and safely scrolled with the `<` and `>` keys. Space still dismisses menus if it's used on the last page. --- win/tty/wintty.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/win/tty/wintty.c b/win/tty/wintty.c index 9c33a11b1..e99418d7f 100644 --- a/win/tty/wintty.c +++ b/win/tty/wintty.c @@ -360,9 +360,6 @@ char **argv UNUSED; (void) signal(SIGWINCH, (SIG_RET_TYPE) winch_handler); #endif - /* add one a space forward menu command alias */ - add_menu_cmd_alias(' ', MENU_NEXT_PAGE); - tty_clear_nhwindow(BASE_WINDOW); tty_putstr(BASE_WINDOW, 0, ""); @@ -1863,6 +1860,7 @@ struct WinDesc *cw; /* set extra chars.. */ Strcat(resp, default_menu_cmds); + Strcat(resp, " "); /* next page or end */ Strcat(resp, "0123456789\033\n\r"); /* counts, quit */ Strcat(resp, gacc); /* group accelerators */ Strcat(resp, mapped_menu_cmds); @@ -1946,12 +1944,15 @@ struct WinDesc *cw; break; } /* else fall through */ + case ' ': case MENU_NEXT_PAGE: if (cw->npages > 0 && curr_page != cw->npages - 1) { curr_page++; page_start = 0; - } else - finished = TRUE; /* questionable behavior */ + } else if (morc == ' ') { + /* ' ' finishes menus here, but stop '>' doing the same. */ + finished = TRUE; + } break; case MENU_PREVIOUS_PAGE: if (cw->npages > 0 && curr_page != 0) { From 6ae8f291acae94a7f7bf5999f35b8f00579dd3f1 Mon Sep 17 00:00:00 2001 From: PatR Date: Fri, 18 Mar 2016 15:46:11 -0700 Subject: [PATCH 029/151] tuning: thrones This was sitting around for several years. When positive luck lessens a negative effect from sitting on a throne, reduce luck so that repeated occurances will eventually get the intended result. --- src/sit.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sit.c b/src/sit.c index b39df58ea..cbc88f922 100644 --- a/src/sit.c +++ b/src/sit.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 sit.c $NHDT-Date: 1445906863 2015/10/27 00:47:43 $ $NHDT-Branch: master $:$NHDT-Revision: 1.51 $ */ +/* NetHack 3.6 sit.c $NHDT-Date: 1458341129 2016/03/18 22:45:29 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.53 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -226,6 +226,7 @@ dosit() "A curse upon thee for sitting upon this most holy throne!"); if (Luck > 0) { make_blinded(Blinded + rn1(100, 250), TRUE); + change_luck((Luck > 1) ? -rnd(2) : -1); } else rndcurse(); break; From bb68c4b4e77030e39d77009e6db869bd1a9c4816 Mon Sep 17 00:00:00 2001 From: PatR Date: Fri, 18 Mar 2016 15:50:13 -0700 Subject: [PATCH 030/151] tuning: succubi This one had been intended for longer than several years, but I hadn't gotten around to it. When consorting with succubi and incubi, very high Cha+Int no longer guarantees that a positive outcome will occur. Chance of positive outcome is still quite high and most of the negative outcomes are pretty easy to repair, so this isn't likely to make a significant impact. However, the possibility of losing spell power will matter for some players.... --- src/mhitu.c | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/mhitu.c b/src/mhitu.c index 9f3d70b14..99aea801b 100644 --- a/src/mhitu.c +++ b/src/mhitu.c @@ -2302,7 +2302,7 @@ register struct monst *mon; { register struct obj *ring, *nring; boolean fem = (mon->data == &mons[PM_SUCCUBUS]); /* otherwise incubus */ - int tried_gloves = 0; + int attr_tot, tried_gloves = 0; char qbuf[QBUFSZ]; if (mon->mcan || mon->mspec_used) { @@ -2443,7 +2443,12 @@ register struct monst *mon; /* by this point you have discovered mon's identity, blind or not... */ pline("Time stands still while you and %s lie in each other's arms...", noit_mon_nam(mon)); - if (rn2(35) > ACURR(A_CHA) + ACURR(A_INT)) { + /* 3.6.1: a combined total for charisma plus intelligence of 35-1 + used to guarantee successful outcome; now total maxes out at 32 + as far as deciding what will happen; chance for bad outcome when + Cha+Int is 32 or more is 2/35, a bit over 5.7% */ + attr_tot = ACURR(A_CHA) + ACURR(A_INT); + if (rn2(35) > min(attr_tot, 32)) { /* Don't bother with mspec_used here... it didn't get tired! */ pline("%s seems to have enjoyed it more than you...", noit_Monnam(mon)); @@ -2475,16 +2480,20 @@ register struct monst *mon; } else { You("have a curious feeling..."); } + exercise(A_CON, FALSE); + exercise(A_DEX, FALSE); + exercise(A_WIS, FALSE); break; case 4: { int tmp; + You_feel("exhausted."); exercise(A_STR, FALSE); tmp = rn1(10, 6); losehp(Maybe_Half_Phys(tmp), "exhaustion", KILLED_BY); break; - } - } + } /* case 4 */ + } /* switch */ } else { mon->mspec_used = rnd(100); /* monster is worn out */ You("seem to have enjoyed it more than %s...", noit_mon_nam(mon)); @@ -2522,14 +2531,14 @@ register struct monst *mon; } } - if (mon->mtame) /* don't charge */ + if (mon->mtame) { /* don't charge */ ; - else if (rn2(20) < ACURR(A_CHA)) { + } else if (rn2(20) < ACURR(A_CHA)) { pline("%s demands that you pay %s, but you refuse...", noit_Monnam(mon), Blind ? (fem ? "her" : "him") : mhim(mon)); - } else if (u.umonnum == PM_LEPRECHAUN) + } else if (u.umonnum == PM_LEPRECHAUN) { pline("%s tries to take your money, but fails...", noit_Monnam(mon)); - else { + } else { long cost; long umoney = money_cnt(invent); @@ -2544,9 +2553,9 @@ register struct monst *mon; } if (cost > umoney) cost = umoney; - if (!cost) + if (!cost) { verbalize("It's on the house!"); - else { + } else { pline("%s takes %ld %s for services rendered!", noit_Monnam(mon), cost, currency(cost)); money2mon(mon, cost); From 96dd8003016d1229987a684e0233dea5022750cc Mon Sep 17 00:00:00 2001 From: PatR Date: Sat, 19 Mar 2016 14:47:36 -0700 Subject: [PATCH 031/151] fix #4279 - kicked daggers are lost A kicked object which hits a monster wasn't placed back on map, removing it from play. --- doc/fixes36.1 | 1 + src/dothrow.c | 16 +++++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index 31655d3ab..6f5049a8c 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -190,6 +190,7 @@ some instances of stun or confusion timers were being overridden rather than incremented when new stun or confusion damage was suffered female gnome who gains level can grow up into male-only gnome lord; give an an alternate message instead of prohibiting the promotion +kicked weapon which successfully hits monster vanishes from play Platform- and/or Interface-Specific Fixes diff --git a/src/dothrow.c b/src/dothrow.c index bfec1303b..11e2b84bc 100644 --- a/src/dothrow.c +++ b/src/dothrow.c @@ -1364,8 +1364,9 @@ register struct obj *obj; /* thrownobj or kickedobj or uwep */ int otyp = obj->otyp, hmode; boolean guaranteed_hit = (u.uswallow && mon == u.ustuck); - hmode = (obj == uwep) ? HMON_APPLIED : (obj == kickedobj) ? HMON_KICKED - : HMON_THROWN; + hmode = (obj == uwep) ? HMON_APPLIED + : (obj == kickedobj) ? HMON_KICKED + : HMON_THROWN; /* Differences from melee weapons: * @@ -1509,18 +1510,22 @@ register struct obj *obj; /* thrownobj or kickedobj or uwep */ } if (tmp >= rnd(20)) { + boolean wasthrown = (thrownobj != 0); + + /* attack hits mon */ if (hmode == HMON_APPLIED) u.uconduct.weaphit++; if (hmon(mon, obj, hmode)) { /* mon still alive */ cutworm(mon, bhitpos.x, bhitpos.y, obj); } exercise(A_DEX, TRUE); - /* if hero is swallowed and projectile kills the engulfer, - obj gets added to engulfer's inventory and then dropped, + /* if hero was swallowed and projectile killed the engulfer, + 'obj' got added to engulfer's inventory and then dropped, so we can't safely use that pointer anymore; it escapes the chance to be used up here... */ - if (!thrownobj) + if (wasthrown && !thrownobj) return 1; + /* projectiles other than magic stones sometimes disappear when thrown */ if (objects[otyp].oc_skill < P_NONE @@ -1531,6 +1536,7 @@ register struct obj *obj; /* thrownobj or kickedobj or uwep */ * but we need ammo to stay around longer on average. */ int broken, chance; + chance = 3 + greatest_erosion(obj) - obj->spe; if (chance > 1) broken = rn2(chance); From c53565036cfd7a23ac353c504d54f49155425b2f Mon Sep 17 00:00:00 2001 From: PatR Date: Sat, 19 Mar 2016 15:46:33 -0700 Subject: [PATCH 032/151] fix scatter feedback Reported directly to devteam (for 3.4.3 but still present in 3.6.0): an unseen landmine explosion which caused scatter() to break a boulder or statue would give feedback as if the hero could see the boulder or statue being destroyed. Also, a couple of landmine explosion messages didn't take deafness into account. --- doc/fixes36.1 | 1 + src/explode.c | 14 ++++++++++---- src/trap.c | 9 +++++---- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index 6f5049a8c..815ef5f5e 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -191,6 +191,7 @@ some instances of stun or confusion timers were being overridden rather than female gnome who gains level can grow up into male-only gnome lord; give an an alternate message instead of prohibiting the promotion kicked weapon which successfully hits monster vanishes from play +unseen landmine explosion could result in "The statue crumbles." Platform- and/or Interface-Specific Fixes diff --git a/src/explode.c b/src/explode.c index 0c736a7f9..3a3b35597 100644 --- a/src/explode.c +++ b/src/explode.c @@ -576,7 +576,10 @@ struct obj *obj; /* only scatter this obj */ && ((otmp->otyp == BOULDER) || (otmp->otyp == STATUE)) && rn2(10)) { if (otmp->otyp == BOULDER) { - pline("%s apart.", Tobjnam(otmp, "break")); + if (cansee(sx, sy)) + pline("%s apart.", Tobjnam(otmp, "break")); + else + You_hear("stone breaking."); fracture_rock(otmp); place_object(otmp, sx, sy); if ((otmp = sobj_at(BOULDER, sx, sy)) != 0) { @@ -589,7 +592,10 @@ struct obj *obj; /* only scatter this obj */ if ((trap = t_at(sx, sy)) && trap->ttyp == STATUE_TRAP) deltrap(trap); - pline("%s.", Tobjnam(otmp, "crumble")); + if (cansee(sx, sy)) + pline("%s.", Tobjnam(otmp, "crumble")); + else + You_hear("stone crumbling."); (void) break_statue(otmp); place_object(otmp, sx, sy); /* put fragments on floor */ } @@ -604,8 +610,8 @@ struct obj *obj; /* only scatter this obj */ } if (!used_up) { - stmp = - (struct scatter_chain *) alloc(sizeof(struct scatter_chain)); + stmp = (struct scatter_chain *) + alloc(sizeof (struct scatter_chain)); stmp->next = (struct scatter_chain *) 0; stmp->obj = otmp; stmp->ox = sx; diff --git a/src/trap.c b/src/trap.c index aa8a1724b..740f49094 100644 --- a/src/trap.c +++ b/src/trap.c @@ -2555,18 +2555,19 @@ register struct monst *mtmp; } } else if (in_sight) { newsym(mtmp->mx, mtmp->my); - pline("KAABLAMM!!! %s triggers %s land mine!", Monnam(mtmp), + pline("%s%s triggers %s land mine!", + !Deaf ? "KAABLAMM!!! " : "", Monnam(mtmp), a_your[trap->madeby_u]); } - if (!in_sight) + if (!in_sight && !Deaf) pline("Kaablamm! You hear an explosion in the distance!"); blow_up_landmine(trap); /* explosion might have destroyed a drawbridge; don't dish out more damage if monster is already dead */ if (mtmp->mhp <= 0 - || thitm(0, mtmp, (struct obj *) 0, rnd(16), FALSE)) + || thitm(0, mtmp, (struct obj *) 0, rnd(16), FALSE)) { trapkilled = TRUE; - else { + } else { /* monsters recursively fall into new pit */ if (mintrap(mtmp) == 2) trapkilled = TRUE; From 3353ad11147045cc2351ea22aa5eb2c7357e4ac9 Mon Sep 17 00:00:00 2001 From: PatR Date: Sun, 20 Mar 2016 13:01:56 -0700 Subject: [PATCH 033/151] fix "object lost" panic after query_objlist Reported directly to devteam for recent git code, the "sortloot revamp" patch could trigger an object lost panic after calling query_objlist() when menustyle was full or partial and player picked up a subset of available items. Modified head-of-list was not being propagated to its source in pickup(). Use an extra layer of indirection. --- doc/fixes36.1 | 1 + src/pickup.c | 23 ++++++++++++----------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index 815ef5f5e..ee41c08b2 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -192,6 +192,7 @@ female gnome who gains level can grow up into male-only gnome lord; give an an alternate message instead of prohibiting the promotion kicked weapon which successfully hits monster vanishes from play unseen landmine explosion could result in "The statue crumbles." +post-3.6.0: fix "object lost" panic during pickup caused by sortloot revamp Platform- and/or Interface-Specific Fixes diff --git a/src/pickup.c b/src/pickup.c index 466c1e10f..368508b16 100644 --- a/src/pickup.c +++ b/src/pickup.c @@ -464,7 +464,7 @@ int what; /* should be a long */ int i, n, res, count, n_tried = 0, n_picked = 0; menu_item *pick_list = (menu_item *) 0; boolean autopickup = what > 0; - struct obj *objchain; + struct obj **objchain_p; int traverse_how; /* we might have arrived here while fainted or sleeping, via @@ -522,10 +522,10 @@ int what; /* should be a long */ add_valid_menu_class(0); /* reset */ if (!u.uswallow) { - objchain = level.objects[u.ux][u.uy]; + objchain_p = &level.objects[u.ux][u.uy]; traverse_how = BY_NEXTHERE; } else { - objchain = u.ustuck->minvent; + objchain_p = &u.ustuck->minvent; traverse_how = 0; /* nobj */ } /* @@ -535,7 +535,7 @@ int what; /* should be a long */ * to make things less confusing. */ if (autopickup) { - n = autopick(objchain, traverse_how, &pick_list); + n = autopick(*objchain_p, traverse_how, &pick_list); goto menu_pickup; } @@ -547,16 +547,17 @@ int what; /* should be a long */ Sprintf(qbuf, "Pick %d of what?", count); val_for_n_or_more = count; /* set up callback selector */ - n = query_objlist(qbuf, &objchain, traverse_how, + n = query_objlist(qbuf, objchain_p, traverse_how, &pick_list, PICK_ONE, n_or_more); /* correct counts, if any given */ for (i = 0; i < n; i++) pick_list[i].count = count; } else { - n = query_objlist("Pick up what?", &objchain, + n = query_objlist("Pick up what?", objchain_p, (traverse_how | FEEL_COCKATRICE), &pick_list, PICK_ANY, all_but_uchain); } + menu_pickup: n_tried = n; for (n_picked = i = 0; i < n; i++) { @@ -582,12 +583,12 @@ int what; /* should be a long */ selective = FALSE; /* ask for each item */ /* check for more than one object */ - for (obj = objchain; obj; obj = FOLLOW(obj, traverse_how)) + for (obj = *objchain_p; obj; obj = FOLLOW(obj, traverse_how)) ct++; if (ct == 1 && count) { /* if only one thing, then pick it */ - obj = objchain; + obj = *objchain_p; lcount = min(obj->quan, (long) count); n_tried++; if (pickup_object(obj, lcount, FALSE) > 0) @@ -599,14 +600,14 @@ int what; /* should be a long */ There("are %s objects here.", (ct <= 10) ? "several" : "many"); if (!query_classes(oclasses, &selective, &all_of_a_type, - "pick up", objchain, + "pick up", *objchain_p, (traverse_how & BY_NEXTHERE) ? TRUE : FALSE, &via_menu)) { if (!via_menu) return 0; if (selective) traverse_how |= INVORDER_SORT; - n = query_objlist("Pick up what?", &objchain, traverse_how, + n = query_objlist("Pick up what?", objchain_p, traverse_how, &pick_list, PICK_ANY, (via_menu == -2) ? allow_all : allow_category); @@ -614,7 +615,7 @@ int what; /* should be a long */ } } - for (obj = objchain; obj; obj = obj2) { + for (obj = *objchain_p; obj; obj = obj2) { obj2 = FOLLOW(obj, traverse_how); if (!selective && oclasses[0] && !index(oclasses, obj->oclass)) continue; From 98b5f581bb5157b80471c7208ad92baabf9788eb Mon Sep 17 00:00:00 2001 From: PatR Date: Mon, 21 Mar 2016 18:26:48 -0700 Subject: [PATCH 034/151] tty menu coloring There was a report during beta testing that menu lines which were displayed in color showed the whole line in color, unless/until an item was selected or unselected, in which case the '-', '+', or '#' was rendered in monochrome. The suggestion then was to redraw the selection character in color, but I went the other way. Menu entries will render the selector letter and selection indicator in monochrome all the time, and only the text of the menu entry will honor menucolors. --- doc/fixes36.1 | 1 + win/tty/wintty.c | 103 ++++++++++++++++++++++++++++++----------------- 2 files changed, 67 insertions(+), 37 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index ee41c08b2..1d1b68691 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -205,6 +205,7 @@ tty: responding with or during role, race, &c selection behaved same as to quit; now it will pick [random] instead tty: rename struct variable 'filter' (role.c) and function 'winch()' (wintty.c) to avoid conflicts with +tty: skip selector letter and selection indicator (-,+,#) during menu coloring unix/X11: in top level Makefile, some commented out definitions of VARDATND misspelled pilemark.xbm (as pilemark.xpm) unix: options file with CR+LF line ends and an invalid option line resulted in diff --git a/win/tty/wintty.c b/win/tty/wintty.c index 199474869..fd23b6336 100644 --- a/win/tty/wintty.c +++ b/win/tty/wintty.c @@ -152,14 +152,15 @@ STATIC_DCL void FDECL(erase_menu_or_text, STATIC_DCL void FDECL(free_window_info, (struct WinDesc *, BOOLEAN_P)); STATIC_DCL void FDECL(dmore, (struct WinDesc *, const char *)); STATIC_DCL void FDECL(set_item_state, (winid, int, tty_menu_item *)); -STATIC_DCL void FDECL(set_all_on_page, - (winid, tty_menu_item *, tty_menu_item *)); -STATIC_DCL void FDECL(unset_all_on_page, - (winid, tty_menu_item *, tty_menu_item *)); -STATIC_DCL void FDECL(invert_all_on_page, - (winid, tty_menu_item *, tty_menu_item *, CHAR_P)); -STATIC_DCL void FDECL(invert_all, - (winid, tty_menu_item *, tty_menu_item *, CHAR_P)); +STATIC_DCL void FDECL(set_all_on_page, (winid, tty_menu_item *, + tty_menu_item *)); +STATIC_DCL void FDECL(unset_all_on_page, (winid, tty_menu_item *, + tty_menu_item *)); +STATIC_DCL void FDECL(invert_all_on_page, (winid, tty_menu_item *, + tty_menu_item *, CHAR_P)); +STATIC_DCL void FDECL(invert_all, (winid, tty_menu_item *, + tty_menu_item *, CHAR_P)); +STATIC_DCL void FDECL(toggle_menu_attr, (BOOLEAN_P, int, int)); STATIC_DCL void FDECL(process_menu_window, (winid, struct WinDesc *)); STATIC_DCL void FDECL(process_text_window, (winid, struct WinDesc *)); STATIC_DCL tty_menu_item *FDECL(reverse, (tty_menu_item *)); @@ -1697,6 +1698,31 @@ char acc; /* group accelerator, 0 => all */ } } +/* support menucolor in addition to caller-supplied attribute */ +STATIC_OVL void +toggle_menu_attr(on, color, attr) +boolean on; +int color, attr; +{ + if (on) { + term_start_attr(attr); +#ifdef TEXTCOLOR + if (color != NO_COLOR) + term_start_color(color); +#endif + } else { +#ifdef TEXTCOLOR + if (color != NO_COLOR) + term_end_color(); +#endif + term_end_attr(attr); + } + +#ifndef TEXTCOLOR + nhUse(color); +#endif +} + STATIC_OVL void process_menu_window(window, cw) winid window; @@ -1704,7 +1730,7 @@ struct WinDesc *cw; { tty_menu_item *page_start, *page_end, *curr; long count; - int n, curr_page, page_lines, resp_len; + int n, attr_n, curr_page, page_lines, resp_len; boolean finished, counting, reset_count; char *cp, *rp, resp[QBUFSZ], gacc[QBUFSZ], *msave, *morestr, really_morc; #define MENU_EXPLICIT_CHOICE 0x7f /* pseudo menu manipulation char */ @@ -1775,8 +1801,8 @@ struct WinDesc *cw; page_end = cw->plist[curr_page + 1]; for (page_lines = 0, curr = page_start; curr != page_end; page_lines++, curr = curr->next) { - int color = NO_COLOR, attr = ATR_NONE; - boolean menucolr = FALSE; + int attr, color = NO_COLOR; + if (curr->selector) *rp++ = curr->selector; @@ -1786,6 +1812,21 @@ struct WinDesc *cw; (void) putchar(' '); ++ttyDisplay->curx; + + if (!iflags.use_menu_color + || !get_menu_coloring(curr->str, &color, &attr)) + attr = curr->attr; + + /* which character to start attribute highlighting; + whole line for headers and such, after the selector + character and space and selection indicator for menu + lines (including fake ones that simulate grayed-out + entries, so we don't rely on curr->identifier here) */ + attr_n = 0; /* whole line */ + if (curr->str[0] && curr->str[1] && curr->str[2] + && curr->str[1] == ' ' && index("-+#", curr->str[2])) + attr_n = 2 + 1; /* after selection indicator */ + /* * Don't use xputs() because (1) under unix it calls * tputstr() which will interpret a '*' as some kind @@ -1793,27 +1834,20 @@ struct WinDesc *cw; * actually output the character. We're faster doing * this. */ - if (iflags.use_menu_color - && (menucolr = get_menu_coloring(curr->str, &color, - &attr))) { - term_start_attr(attr); -#ifdef TEXTCOLOR - if (color != NO_COLOR) - term_start_color(color); -#endif - } else - term_start_attr(curr->attr); for (n = 0, cp = curr->str; + *cp && #ifndef WIN32CON - *cp - && (int) ++ttyDisplay->curx < (int) ttyDisplay->cols; - cp++, n++) + (int) ++ttyDisplay->curx < (int) ttyDisplay->cols; #else - *cp - && (int) ttyDisplay->curx < (int) ttyDisplay->cols; - cp++, n++, ttyDisplay->curx++) + (int) ttyDisplay->curx < (int) ttyDisplay->cols; + ttyDisplay->curx++, #endif - if (n == 2 && curr->identifier.a_void != 0 + cp++, n++) { + if (n == attr_n && (color != NO_COLOR + || attr != ATR_NONE)) + toggle_menu_attr(TRUE, color, attr); + if (n == 2 + && curr->identifier.a_void != 0 && curr->selected) { if (curr->count == -1L) (void) putchar('+'); /* all selected */ @@ -1821,15 +1855,10 @@ struct WinDesc *cw; (void) putchar('#'); /* count selected */ } else (void) putchar(*cp); - if (iflags.use_menu_color && menucolr) { -#ifdef TEXTCOLOR - if (color != NO_COLOR) - term_end_color(); -#endif - term_end_attr(attr); - } else - term_end_attr(curr->attr); - } + } /* for *cp */ + if (n > attr_n && (color != NO_COLOR || attr != ATR_NONE)) + toggle_menu_attr(FALSE, color, attr); + } /* if npages > 0 */ } else { page_start = 0; page_end = 0; From e8e8c14b345bc4cae9f54e27cf1b9929e0feff42 Mon Sep 17 00:00:00 2001 From: PatR Date: Tue, 22 Mar 2016 01:19:27 -0700 Subject: [PATCH 035/151] more fixes for revised 'sortloot' After some permutation of commands which displayed items, the 'd' command presented a prompt with the list of letters scrambled (in loot order or pack order rather than invlet order), so explicitly sort when getobj operates. Done for ggetobj too. For menustyle:Traditional, ',' followed by 'm' presented a pickup list in pile order even when sortloot was 'l' or 'f'. That was an unintentional change during the 'revamp'. --- doc/fixes36.1 | 1 + src/hack.c | 4 ++++ src/invent.c | 53 ++++++++++++++++++++++++++++++++------------------- src/pickup.c | 2 +- 4 files changed, 39 insertions(+), 21 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index 1d1b68691..c8e65af55 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -193,6 +193,7 @@ female gnome who gains level can grow up into male-only gnome lord; give an kicked weapon which successfully hits monster vanishes from play unseen landmine explosion could result in "The statue crumbles." post-3.6.0: fix "object lost" panic during pickup caused by sortloot revamp +post-3.6.0: more sortloot revisions Platform- and/or Interface-Specific Fixes diff --git a/src/hack.c b/src/hack.c index 6ce28907a..b3fd6654d 100644 --- a/src/hack.c +++ b/src/hack.c @@ -2197,6 +2197,7 @@ register boolean newlev; *ptr2 = '\0'; } +/* possibly deliver a one-time room entry message */ void check_special_room(newlev) register boolean newlev; @@ -2327,11 +2328,13 @@ register boolean newlev; return; } +/* the ',' command */ int dopickup() { int count; struct trap *traphere = t_at(u.ux, u.uy); + /* awful kludge to work around parse()'s pre-decrement */ count = (multi || (save_cm && *save_cm == ',')) ? multi + 1 : 0; multi = 0; /* always reset */ @@ -2372,6 +2375,7 @@ dopickup() } if (!OBJ_AT(u.ux, u.uy)) { register struct rm *lev = &levl[u.ux][u.uy]; + if (IS_THRONE(lev->typ)) pline("It must weigh%s a ton!", lev->looted ? " almost" : ""); else if (IS_SINK(lev->typ)) diff --git a/src/invent.c b/src/invent.c index 5670476ab..1e89d406b 100644 --- a/src/invent.c +++ b/src/invent.c @@ -1106,6 +1106,13 @@ register const char *let, *word; if (!flags.invlet_constant) reassign(); + else + /* in case invent is in packorder, force it to be in invlet + order before collecing candidate inventory letters; + if player responds with '?' or '*' it will be changed + back by display_pickinv(), but by then we'll have 'lets' + and so won't have to re-sort in the for(;;) loop below */ + sortloot(&invent, SORTLOOT_INVLET, FALSE); for (otmp = firstobj; otmp; otmp = otmp->nobj) { if (&bp[foo] == &buf[sizeof buf - 1] @@ -1119,8 +1126,8 @@ register const char *let, *word; || (usegold && otmp->invlet == GOLD_SYM) || (useboulder && otmp->otyp == BOULDER)) { register int otyp = otmp->otyp; - bp[foo++] = otmp->invlet; + bp[foo++] = otmp->invlet; /* clang-format off */ /* *INDENT-OFF* */ /* ugly check: remove inappropriate things */ @@ -1521,9 +1528,9 @@ static NEARDATA const char removeables[] = { ARMOR_CLASS, WEAPON_CLASS, RING_CLASS, AMULET_CLASS, TOOL_CLASS, 0 }; -/* interactive version of getobj - used for Drop, Identify and */ -/* Takeoff (A). Return the number of times fn was called successfully */ -/* If combo is TRUE, we just use this to get a category list */ +/* Interactive version of getobj - used for Drop, Identify, and Takeoff (A). + Return the number of times fn was called successfully. + If combo is TRUE, we just use this to get a category list. */ int ggetobj(word, fn, mx, combo, resultflags) const char *word; @@ -1700,15 +1707,15 @@ unsigned *resultflags; * Walk through the chain starting at objchn and ask for all objects * with olet in olets (if nonNULL) and satisfying ckfn (if nonnull) * whether the action in question (i.e., fn) has to be performed. - * If allflag then no questions are asked. Max gives the max nr of - * objects to be treated. Return the number of objects treated. + * If allflag then no questions are asked. Mx gives the max number + * of objects to be treated. Return the number of objects treated. */ int askchain(objchn, olets, allflag, fn, ckfn, mx, word) struct obj **objchn; -register int allflag, mx; -register const char *olets, *word; /* olets is an Obj Class char array */ -register int FDECL((*fn), (OBJ_P)), FDECL((*ckfn), (OBJ_P)); +int allflag, mx; +const char *olets, *word; /* olets is an Obj Class char array */ +int FDECL((*fn), (OBJ_P)), FDECL((*ckfn), (OBJ_P)); { struct obj *otmp, *otmpo; register char sym, ilet; @@ -1723,11 +1730,17 @@ register int FDECL((*fn), (OBJ_P)), FDECL((*ckfn), (OBJ_P)); nodot = (!strcmp(word, "nodot") || !strcmp(word, "drop") || ident || takeoff || take_out || put_in); ininv = (*objchn == invent); + + /* someday maybe we'll sort by 'olets' too (temporarily replace + flags.packorder and pass SORTLOOT_PACK), but not yet... */ + sortloot(objchn, SORTLOOT_INVLET, FALSE); + first = TRUE; -/* Changed so the askchain is interrogated in the order specified. - * For example, if a person specifies =/ then first all rings will be - * asked about followed by all wands -dgk - */ + /* + * Interrogate in the object class order specified. + * For example, if a person specifies =/ then first all rings + * will be asked about followed by all wands. -dgk + */ nextclass: ilet = 'a' - 1; if (*objchn && (*objchn)->oclass == COIN_CLASS) @@ -1770,9 +1783,10 @@ nextclass: Sprintf(qpfx, "%s: ", word), *qpfx = highc(*qpfx); first = FALSE; } - (void) safe_qbuf( - qbuf, qpfx, "?", otmp, ininv ? safeq_xprname : doname, - ininv ? safeq_shortxprname : ansimpleoname, "item"); + (void) safe_qbuf(qbuf, qpfx, "?", otmp, + ininv ? safeq_xprname : doname, + ininv ? safeq_shortxprname : ansimpleoname, + "item"); sym = (takeoff || ident || otmp->quan < 2L) ? nyaq(qbuf) : nyNaq(qbuf); } else @@ -1784,11 +1798,10 @@ nextclass: to 'none' or 'all'. 2 special cases: cursed loadstones and welded weapons (eg, multiple daggers) will remain as merged unit; done to avoid splitting an object that won't be - droppable (even if we're picking up rather than dropping). - */ - if (!yn_number) + droppable (even if we're picking up rather than dropping). */ + if (!yn_number) { sym = 'n'; - else { + } else { sym = 'y'; if (yn_number < otmp->quan && splittable(otmp)) otmp = splitobj(otmp, yn_number); diff --git a/src/pickup.c b/src/pickup.c index 368508b16..f2642a6ed 100644 --- a/src/pickup.c +++ b/src/pickup.c @@ -823,7 +823,7 @@ boolean FDECL((*allow), (OBJ_P)); /* allow function */ return 1; } - if (sorted) { + if (sorted || flags.sortloot != 'n') { sortloot(&olist, (((flags.sortloot == 'f' || (flags.sortloot == 'l' && !(qflags & USE_INVLET))) From b5d21356735d3c3be0e3ab45146ba99394a660f9 Mon Sep 17 00:00:00 2001 From: PatR Date: Tue, 22 Mar 2016 18:22:39 -0700 Subject: [PATCH 036/151] 'O' for regexp options Noticed when testing tty menucolor changes recently, using the 'O' command to interactively add a new entry would accept one and then quit, unlike remove which accepts multiple at a time and then goes back to the menu where you could choose 'remove' all over again. Adding is still done one entry at a time, but instead of finishing, it goes back to the menu where you can choose to add another. --- doc/fixes36.1 | 4 +++ src/options.c | 84 ++++++++++++++++++++++++--------------------------- 2 files changed, 43 insertions(+), 45 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index c8e65af55..a2853713b 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -192,6 +192,10 @@ female gnome who gains level can grow up into male-only gnome lord; give an an alternate message instead of prohibiting the promotion kicked weapon which successfully hits monster vanishes from play unseen landmine explosion could result in "The statue crumbles." +when using the 'O' command for regexp options (autopickup exceptions, + menucolors, message types), provide opportunity to add more than one + at a time [after 'add', return to add/list/remove menu] + post-3.6.0: fix "object lost" panic during pickup caused by sortloot revamp post-3.6.0: more sortloot revisions diff --git a/src/options.c b/src/options.c index e96252911..af854e9c8 100644 --- a/src/options.c +++ b/src/options.c @@ -4288,23 +4288,23 @@ boolean setinitial, setfromfile; msgtypes_again: nmt = msgtype_count(); opt_idx = handle_add_list_remove("message type", nmt); - if (opt_idx == 3) { - ; /* done--fall through to function exit */ + if (opt_idx == 3) { /* done */ + return TRUE; } else if (opt_idx == 0) { /* add new */ getlin("What new message pattern?", mtbuf); - if (*mtbuf == '\033' || !*mtbuf) - goto msgtypes_again; - mttyp = query_msgtype(); - if (mttyp == -1) - goto msgtypes_again; - if (!msgtype_add(mttyp, mtbuf)) { + if (*mtbuf == '\033') + return TRUE; + if (*mtbuf + && (mttyp = query_msgtype()) != -1 + && !msgtype_add(mttyp, mtbuf)) { pline("Error adding the message type."); wait_synch(); - goto msgtypes_again; } - } else { /* list or remove */ + goto msgtypes_again; + } else { /* list (1) or remove (2) */ int pick_idx, pick_cnt; int mt_idx; + const char *mtype; menu_item *pick_list = (menu_item *) 0; struct plinemsg_type *tmp = plinemsg_types; @@ -4313,8 +4313,7 @@ boolean setinitial, setfromfile; any = zeroany; mt_idx = 0; while (tmp) { - const char *mtype = msgtype2name(tmp->msgtype); - + mtype = msgtype2name(tmp->msgtype); any.a_int = ++mt_idx; Sprintf(mtbuf, "%-5s \"%s\"", mtype, tmp->pattern); add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, mtbuf, @@ -4344,26 +4343,24 @@ boolean setinitial, setfromfile; menucolors_again: nmc = count_menucolors(); opt_idx = handle_add_list_remove("menucolor", nmc); - if (opt_idx == 3) { - ; /* done--fall through to function exit */ + if (opt_idx == 3) { /* done */ + return TRUE; } else if (opt_idx == 0) { /* add new */ getlin("What new menucolor pattern?", mcbuf); - if (*mcbuf == '\033' || !*mcbuf) - goto menucolors_again; - mcclr = query_color(); - if (mcclr == -1) - goto menucolors_again; - mcattr = query_attr(NULL); - if (mcattr == -1) - goto menucolors_again; - if (!add_menu_coloring_parsed(mcbuf, mcclr, mcattr)) { + if (*mcbuf == '\033') + return TRUE; + if (*mcbuf + && (mcclr = query_color()) != -1 + && (mcattr = query_attr((char *) 0)) != -1 + && !add_menu_coloring_parsed(mcbuf, mcclr, mcattr)) { pline("Error adding the menu color."); wait_synch(); - goto menucolors_again; } - } else { /* list or remove */ + goto menucolors_again; + } else { /* list (1) or remove (2) */ int pick_idx, pick_cnt; int mc_idx; + const char *sattr, *sclr; menu_item *pick_list = (menu_item *) 0; struct menucoloring *tmp = menu_colorings; @@ -4372,10 +4369,9 @@ boolean setinitial, setfromfile; any = zeroany; mc_idx = 0; while (tmp) { - const char *sattr = attr2attrname(tmp->attr); - const char *sclr = clr2colorname(tmp->color); - - any.a_int = (++mc_idx); + sattr = attr2attrname(tmp->attr); + sclr = clr2colorname(tmp->color); + any.a_int = ++mc_idx; Sprintf(mcbuf, "\"%s\"=%s%s%s", tmp->origstr, sclr, (tmp->attr != ATR_NONE) ? " & " : "", (tmp->attr != ATR_NONE) ? sattr : ""); @@ -4407,26 +4403,24 @@ boolean setinitial, setfromfile; ape_again: totalapes = count_ape_maps(&numapes[AP_LEAVE], &numapes[AP_GRAB]); opt_idx = handle_add_list_remove("autopickup exception", totalapes); - if (opt_idx == 3) { - ; /* done--fall through to function exit */ + if (opt_idx == 3) { /* done */ + return TRUE; } else if (opt_idx == 0) { /* add new */ getlin("What new autopickup exception pattern?", &apebuf[1]); mungspaces(&apebuf[1]); /* regularize whitespace */ - if (apebuf[1] == '\033') { - ; /* fall through to function exit */ - } else { - if (apebuf[1]) { - apebuf[0] = '\"'; - /* guarantee room for \" prefix and \"\0 suffix; - -2 is good enough for apebuf[] but -3 makes - sure the whole thing fits within normal BUFSZ */ - apebuf[sizeof apebuf - 3] = '\0'; - Strcat(apebuf, "\""); - add_autopickup_exception(apebuf); - } - goto ape_again; + if (apebuf[1] == '\033') + return TRUE; + if (apebuf[1]) { + apebuf[0] = '\"'; + /* guarantee room for \" prefix and \"\0 suffix; + -2 is good enough for apebuf[] but -3 makes + sure the whole thing fits within normal BUFSZ */ + apebuf[sizeof apebuf - 3] = '\0'; + Strcat(apebuf, "\""); + add_autopickup_exception(apebuf); } - } else { /* list or remove */ + goto ape_again; + } else { /* list (1) or remove (2) */ int pick_idx, pick_cnt; menu_item *pick_list = (menu_item *) 0; From 0508b981a29d0afad9791772c657f13692224ac7 Mon Sep 17 00:00:00 2001 From: Tung Nguyen Date: Wed, 23 Mar 2016 19:33:42 +1100 Subject: [PATCH 037/151] Revert "Omit accel and select char from menu colors" This reverts commit 2ff96797a8a52d0917529f68f1a3e38dca2668e8. Since this pull request was made to the DevTeam, a commit has appeared in the official repo's NetHack-3.6.0 branch which effectively does the same thing: NetHack commit 98b5f58 (tty menu coloring) by PatR. --- win/tty/wintty.c | 40 +++++++++++++--------------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/win/tty/wintty.c b/win/tty/wintty.c index e99418d7f..73166b7db 100644 --- a/win/tty/wintty.c +++ b/win/tty/wintty.c @@ -1774,15 +1774,6 @@ struct WinDesc *cw; page_lines++, curr = curr->next) { int color = NO_COLOR, attr = ATR_NONE; boolean menucolr = FALSE; - int select_pos = -1; /* -/+/# position for selectables */ - int format_start_pos = 0; /* menu color/attr start pos */ - - if (curr->identifier.a_void != 0) { - /* "a - whatever" */ - select_pos = 2; /* '-' */ - format_start_pos = 4; /* start of "whatever" */ - } - if (curr->selector) *rp++ = curr->selector; @@ -1799,31 +1790,27 @@ struct WinDesc *cw; * actually output the character. We're faster doing * this. */ + if (iflags.use_menu_color + && (menucolr = get_menu_coloring(curr->str, &color, + &attr))) { + term_start_attr(attr); +#ifdef TEXTCOLOR + if (color != NO_COLOR) + term_start_color(color); +#endif + } else + term_start_attr(curr->attr); for (n = 0, cp = curr->str; #ifndef WIN32CON *cp && (int) ++ttyDisplay->curx < (int) ttyDisplay->cols; - cp++, n++ + cp++, n++) #else *cp && (int) ttyDisplay->curx < (int) ttyDisplay->cols; - cp++, n++, ttyDisplay->curx++ + cp++, n++, ttyDisplay->curx++) #endif - ) { - if (n == format_start_pos) { - if (iflags.use_menu_color - && (menucolr = get_menu_coloring(curr->str, - &color, - &attr))) { - term_start_attr(attr); -#ifdef TEXTCOLOR - if (color != NO_COLOR) - term_start_color(color); -#endif - } else - term_start_attr(curr->attr); - } - if (n == select_pos && curr->identifier.a_void != 0 + if (n == 2 && curr->identifier.a_void != 0 && curr->selected) { if (curr->count == -1L) (void) putchar('+'); /* all selected */ @@ -1831,7 +1818,6 @@ struct WinDesc *cw; (void) putchar('#'); /* count selected */ } else (void) putchar(*cp); - } if (iflags.use_menu_color && menucolr) { #ifdef TEXTCOLOR if (color != NO_COLOR) From cd674b5cf3b6bea6cfb06ca1df9f30e1017d86bb Mon Sep 17 00:00:00 2001 From: PatR Date: Thu, 24 Mar 2016 17:31:35 -0700 Subject: [PATCH 038/151] fix #H4139 - commands missing in help menu The meta keystroke commands which use an uppercase letter were all missing from dat/hh: M-A annotate level M-C show conduct M-N name something (synonym for M-n, which is a synonyn for 'C'all) M-O display dungeon overview M-R ride/unride steed M-T tip a container (All meta keystroke command shortcuts are missing from dat/help.) --- dat/hh | 8 +++++++- doc/fixes36.1 | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/dat/hh b/dat/hh index 146af4275..09eac4c2d 100644 --- a/dat/hh +++ b/dat/hh @@ -80,7 +80,7 @@ Z Zap cast a spell (use Y instead of Z if number_pad is -1) $ gold count your gold + spells list the spells you know; also rearrange them if desired ` classkn display known items for one class of objects -_ travel move via a shortest-path algorithm to a point on the map +_ travel move via a shortest-path algorithm to a point on the map . rest wait a moment , pickup pick up all you can carry @ toggle "pickup" (auto pickup) option on and off @@ -93,7 +93,9 @@ via the meta modifier instead of the # prefix: M-? display extended command help (if the platform allows this) M-2 twoweapon toggle two-weapon combat (unless number_pad is enabled) M-a adjust adjust inventory letters +M-A annotate add a one-line note to the current dungeon level (see M-O) M-c chat talk to someone +M-C conduct view optional challanges M-d dip dip an object into something M-e enhance advance or check weapon and spell skills M-f force force a lock @@ -102,12 +104,16 @@ M-j jump jump to another location M-l loot loot a box on the floor M-m monster use a monster's special ability M-n name name a monster, an individual object, or a type of object +M-N name synonym for M-n M-o offer offer a sacrifice to the gods +M-O overview display information about visited levels and annotations M-p pray pray to the gods for help M-q quit stop playing M-r rub rub a lamp or a stone +M-R ride mount or dismount saddled steed M-s sit sit down M-t turn turn undead +M-T tip upend a container to dump out its contents M-u untrap untrap something M-v version print compile time options for this version M-w wipe wipe off your face diff --git a/doc/fixes36.1 b/doc/fixes36.1 index a2853713b..f74fa1358 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -195,6 +195,8 @@ unseen landmine explosion could result in "The statue crumbles." when using the 'O' command for regexp options (autopickup exceptions, menucolors, message types), provide opportunity to add more than one at a time [after 'add', return to add/list/remove menu] +commands invoked by uppercase meta characters: M-A, M-C, M-N, M-O, M-R, M-T + were missing from '? c' list of game commands (dat/hh) post-3.6.0: fix "object lost" panic during pickup caused by sortloot revamp post-3.6.0: more sortloot revisions From 36464df379dc31ad75899eff7c78126dd99c0608 Mon Sep 17 00:00:00 2001 From: PatR Date: Fri, 25 Mar 2016 16:45:26 -0700 Subject: [PATCH 039/151] mplayer.c formatting Some formatting cleanup for file that escaped the umpteen earlier rounds of such. --- src/mplayer.c | 60 ++++++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/src/mplayer.c b/src/mplayer.c index 6ad230d41..c2178b07f 100644 --- a/src/mplayer.c +++ b/src/mplayer.c @@ -1,5 +1,5 @@ -/* NetHack 3.6 mplayer.c $NHDT-Date: 1432512774 2015/05/25 00:12:54 $ $NHDT-Branch: master $:$NHDT-Revision: 1.19 $ */ -/* Copyright (c) Izchak Miller, 1992. */ +/* NetHack 3.6 mplayer.c $NHDT-Date: 1458949461 2016/03/25 23:44:21 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.21 $ */ +/* Copyright (c) Izchak Miller, 1992. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" @@ -134,23 +134,13 @@ register boolean special; special = FALSE; if ((mtmp = makemon(ptr, x, y, NO_MM_FLAGS)) != 0) { - short weapon = rn2(2) ? LONG_SWORD : rnd_class(SPEAR, BULLWHIP); - short armor = - rnd_class(GRAY_DRAGON_SCALE_MAIL, YELLOW_DRAGON_SCALE_MAIL); - short cloak = !rn2(8) - ? STRANGE_OBJECT - : rnd_class(OILSKIN_CLOAK, CLOAK_OF_DISPLACEMENT); - short helm = !rn2(8) ? STRANGE_OBJECT : rnd_class(ELVEN_LEATHER_HELM, - HELM_OF_TELEPATHY); - short shield = !rn2(8) - ? STRANGE_OBJECT - : rnd_class(ELVEN_SHIELD, SHIELD_OF_REFLECTION); + short weapon, armor, cloak, helm, shield; int quan; struct obj *otmp; mtmp->m_lev = (special ? rn1(16, 15) : rnd(16)); - mtmp->mhp = mtmp->mhpmax = - d((int) mtmp->m_lev, 10) + (special ? (30 + rnd(30)) : 30); + mtmp->mhp = mtmp->mhpmax = d((int) mtmp->m_lev, 10) + + (special ? (30 + rnd(30)) : 30); if (special) { get_mplname(mtmp, nam); mtmp = christen_monst(mtmp, nam); @@ -160,6 +150,16 @@ register boolean special; mtmp->mpeaceful = 0; set_malign(mtmp); /* peaceful may have changed again */ + /* default equipment; much of it will be overridden below */ + weapon = !rn2(2) ? LONG_SWORD : rnd_class(SPEAR, BULLWHIP); + armor = rnd_class(GRAY_DRAGON_SCALE_MAIL, YELLOW_DRAGON_SCALE_MAIL); + cloak = !rn2(8) ? STRANGE_OBJECT + : rnd_class(OILSKIN_CLOAK, CLOAK_OF_DISPLACEMENT); + helm = !rn2(8) ? STRANGE_OBJECT + : rnd_class(ELVEN_LEATHER_HELM, HELM_OF_TELEPATHY); + shield = !rn2(8) ? STRANGE_OBJECT + : rnd_class(ELVEN_SHIELD, SHIELD_OF_REFLECTION); + switch (monsndx(ptr)) { case PM_ARCHEOLOGIST: if (rn2(2)) @@ -285,18 +285,18 @@ register boolean special; mk_mplayer_armor(mtmp, helm); mk_mplayer_armor(mtmp, shield); if (rn2(8)) - mk_mplayer_armor( - mtmp, rnd_class(LEATHER_GLOVES, GAUNTLETS_OF_DEXTERITY)); + mk_mplayer_armor(mtmp, rnd_class(LEATHER_GLOVES, + GAUNTLETS_OF_DEXTERITY)); if (rn2(8)) - mk_mplayer_armor(mtmp, - rnd_class(LOW_BOOTS, LEVITATION_BOOTS)); + mk_mplayer_armor(mtmp, rnd_class(LOW_BOOTS, + LEVITATION_BOOTS)); m_dowear(mtmp, TRUE); quan = rn2(3) ? rn2(3) : rn2(16); while (quan--) (void) mongets(mtmp, rnd_class(DILITHIUM_CRYSTAL, JADE)); - /* To get the gold "right" would mean a player can double his */ - /* gold supply by killing one mplayer. Not good. */ + /* To get the gold "right" would mean a player can double his + gold supply by killing one mplayer. Not good. */ mkmonmoney(mtmp, rn2(1000)); quan = rn2(10); while (quan--) @@ -357,15 +357,17 @@ void mplayer_talk(mtmp) register struct monst *mtmp; { - static const char *same_class_msg[3] = - { - "I can't win, and neither will you!", "You don't deserve to win!", - "Mine should be the honor, not yours!", + static const char + *same_class_msg[3] = { + "I can't win, and neither will you!", + "You don't deserve to win!", + "Mine should be the honor, not yours!", }, - *other_class_msg[3] = { - "The low-life wants to talk, eh?", "Fight, scum!", - "Here is what I have to say!", - }; + *other_class_msg[3] = { + "The low-life wants to talk, eh?", + "Fight, scum!", + "Here is what I have to say!", + }; if (mtmp->mpeaceful) return; /* will drop to humanoid talk */ From 41b8f38272e81f8d17fdeae858a48cec4e1911ce Mon Sep 17 00:00:00 2001 From: PatR Date: Fri, 25 Mar 2016 16:49:01 -0700 Subject: [PATCH 040/151] shapechanger polymorph bit Rescuing an old revision from bit rot: If one of fog clouds or vampire bats has been genocided and you try to polymorph a vampire disguised as the other, it won't change form because the shape it's currently in is the only candidate shape left for vampshifting. This makes shapechangers who fail to take on a new shape when polymorphed try again, specifying original form on the second try. It's unlikely to affect chameleons, but disguised vampires will sometimes become undisguised instead of seeming to be immune from polymorph. --- src/mon.c | 27 +++++++++++---------------- src/zap.c | 21 +++++++++++++++++---- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/mon.c b/src/mon.c index b0eec652e..b3e95eca6 100644 --- a/src/mon.c +++ b/src/mon.c @@ -3209,7 +3209,7 @@ boolean msg; /* "The oldmon turns into a newmon!" */ int hpn, hpd; int mndx, tryct; struct permonst *olddata = mtmp->data; - char oldname[BUFSZ], l_oldname[BUFSZ], newname[BUFSZ]; + char *p, oldname[BUFSZ], l_oldname[BUFSZ], newname[BUFSZ]; /* Riders are immune to polymorph and green slime (but apparent Rider might actually be a doppelganger) */ @@ -3224,7 +3224,7 @@ boolean msg; /* "The oldmon turns into a newmon!" */ } /* we need this one whether msg is true or not */ Strcpy(l_oldname, x_monnam(mtmp, ARTICLE_THE, (char *) 0, - (has_mname(mtmp)) ? SUPPRESS_SADDLE : 0, FALSE)); + has_mname(mtmp) ? SUPPRESS_SADDLE : 0, FALSE)); /* mdat = 0 -> caller wants a random monster shape */ if (mdat == 0) { @@ -3247,23 +3247,18 @@ boolean msg; /* "The oldmon turns into a newmon!" */ } else if (mvitals[monsndx(mdat)].mvflags & G_GENOD) return 0; /* passed in mdat is genocided */ - if (mdat == mtmp->data) + if (mdat == olddata) return 0; /* still the same monster */ mgender_from_permonst(mtmp, mdat); - - if (In_endgame(&u.uz) && is_mplayer(olddata) && has_mname(mtmp)) { - /* mplayers start out as "Foo the Bar", but some of the - * titles are inappropriate when polymorphed, particularly - * into the opposite sex. players don't use ranks when - * polymorphed, so dropping the rank for mplayers seems - * reasonable. - */ - char *p = index(MNAME(mtmp), ' '); - - if (p) - *p = '\0'; - } + /* Endgame mplayers start out as "Foo the Bar", but some of the + * titles are inappropriate when polymorphed, particularly into + * the opposite sex. Player characters don't use ranks when + * polymorphed, so dropping rank for mplayers seems reasonable. + */ + if (In_endgame(&u.uz) && is_mplayer(olddata) + && has_mname(mtmp) && (p = strstr(MNAME(mtmp), " the ")) != 0) + *p = '\0'; if (mtmp->wormno) { /* throw tail away */ wormgone(mtmp); diff --git a/src/zap.c b/src/zap.c index 854c62fad..daa4d5ec6 100644 --- a/src/zap.c +++ b/src/zap.c @@ -221,11 +221,17 @@ struct obj *otmp; it guard against involuntary polymorph attacks too... */ shieldeff(mtmp->mx, mtmp->my); } else if (!resist(mtmp, otmp->oclass, 0, NOTELL)) { + boolean polyspot = (otyp != POT_POLYMORPH), + give_msg = (!Hallucination + && (canseemon(mtmp) + || (u.uswallow && mtmp == u.ustuck))); + /* dropped inventory (due to death by system shock, or loss of wielded weapon and/or worn armor due to limitations of new shape) won't be hit by this zap */ - for (obj = mtmp->minvent; obj; obj = obj->nobj) - bypass_obj(obj); + if (polyspot) + for (obj = mtmp->minvent; obj; obj = obj->nobj) + bypass_obj(obj); /* natural shapechangers aren't affected by system shock (unless protection from shapechangers is interfering with their metabolism...) */ @@ -238,8 +244,15 @@ struct obj *otmp; /* no corpse after system shock */ xkilled(mtmp, 3); } else if (newcham(mtmp, (struct permonst *) 0, - (otyp != POT_POLYMORPH), FALSE)) { - if (!Hallucination && canspotmon(mtmp)) + polyspot, give_msg) != 0 + /* if shapechange failed because there aren't + enough eligible candidates (most likely for + vampshifter), try reverting to original form */ + || (mtmp->cham >= LOW_PM + && newcham(mtmp, &mons[mtmp->cham], + polyspot, give_msg) != 0)) { + if (give_msg && (canspotmon(mtmp) + || (u.uswallow && mtmp == u.ustuck))) learn_it = TRUE; } } From 862d37c77a5cc9f363e3eba951791c1d98d00150 Mon Sep 17 00:00:00 2001 From: PatR Date: Fri, 25 Mar 2016 17:26:37 -0700 Subject: [PATCH 041/151] more recovered bits Some worthwhile stuff from abandoned 'git stash': is_plural() macro wasn't comprehensive; a couple of return values where writing with a magic marker was causing time to elapse even though nothing happened; comment formatting for saddle being left in shop by dying steed. --- include/obj.h | 2 +- src/steal.c | 9 +++++---- src/write.c | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/include/obj.h b/include/obj.h index a511e1cf6..bb872af4f 100644 --- a/include/obj.h +++ b/include/obj.h @@ -337,7 +337,7 @@ struct obj { /* helpers, simple enough to be macros */ #define is_plural(o) \ - ((o)->quan > 1 || (o)->oartifact == ART_EYES_OF_THE_OVERWORLD) + ((o)->quan != 1L || (o)->otyp == LENSES || is_gloves(o) || is_boots(o)) /* Flags for get_obj_location(). */ #define CONTAINED_TOO 0x1 diff --git a/src/steal.c b/src/steal.c index b3e5cb1f5..a973bf7a2 100644 --- a/src/steal.c +++ b/src/steal.c @@ -646,10 +646,11 @@ boolean verbosely; if (mon->mhp > 0) { mon->misc_worn_check &= ~obj->owornmask; update_mon = TRUE; - /* don't charge for an owned saddle on dead steed (provided - that the hero is within the same shop at the time) */ - } else if (mon->mtame && (obj->owornmask & W_SADDLE) && !obj->unpaid - && costly_spot(omx, omy) + + /* don't charge for an owned saddle on dead steed (provided + that the hero is within the same shop at the time) */ + } else if (mon->mtame && (obj->owornmask & W_SADDLE) != 0L + && !obj->unpaid && costly_spot(omx, omy) /* being at costly_spot guarantees lev->roomno is not 0 */ && index(in_rooms(u.ux, u.uy, SHOPBASE), levl[omx][omy].roomno)) { diff --git a/src/write.c b/src/write.c index 14db75706..b73bfb10b 100644 --- a/src/write.c +++ b/src/write.c @@ -128,12 +128,12 @@ register struct obj *pen; if (Blind) { if (!paper->dknown) { You("don't know if that %s is blank or not.", typeword); - return 1; + return 0; } else if (paper->oclass == SPBOOK_CLASS) { /* can't write a magic book while blind */ pline("%s can't create braille text.", upstart(ysimple_name(pen))); - return 1; + return 0; } } paper->dknown = 1; From d6fa06f8e878cba10f5b26d6c4693b6a918c6efa Mon Sep 17 00:00:00 2001 From: PatR Date: Sat, 26 Mar 2016 16:42:24 -0700 Subject: [PATCH 042/151] zeromonst Make 'zeromonst' global instead of local to makemon.c. Its address isn't used as a special value like &zeroobj, but it is useful to have available for initializing various pseudo-monsters. modified: include/decl.h src/decl.c, makemon.c, mkobj.c, mplayer.c, teleport.c --- include/decl.h | 6 ++++-- src/decl.c | 3 ++- src/makemon.c | 5 ++--- src/mkobj.c | 6 ++---- src/mplayer.c | 3 ++- src/teleport.c | 4 +++- 6 files changed, 15 insertions(+), 12 deletions(-) diff --git a/include/decl.h b/include/decl.h index 52145808e..7898d17ce 100644 --- a/include/decl.h +++ b/include/decl.h @@ -243,7 +243,8 @@ E NEARDATA struct obj *migrating_objs; E NEARDATA struct obj *billobjs; E NEARDATA struct obj *current_wand, *thrownobj, *kickedobj; -E NEARDATA struct obj zeroobj; /* init'd and defined in decl.c */ +E NEARDATA struct obj zeroobj; /* for init; &zeroobj used as special value */ + E NEARDATA anything zeroany; /* init'd and defined in decl.c */ #include "you.h" @@ -256,7 +257,8 @@ E NEARDATA struct u_realtime urealtime; #include "pm.h" #endif -E NEARDATA struct monst youmonst; /* init'd and defined in decl.c */ +E NEARDATA struct monst zeromonst; /* for init of new or temp monsters */ +E NEARDATA struct monst youmonst; /* monster details when hero is poly'd */ E NEARDATA struct monst *mydogs, *migrating_mons; E NEARDATA struct mvitals { diff --git a/src/decl.c b/src/decl.c index 944dbe368..479c075aa 100644 --- a/src/decl.c +++ b/src/decl.c @@ -194,8 +194,9 @@ NEARDATA struct obj *migrating_objs = (struct obj *) 0; /* objects not yet paid for */ NEARDATA struct obj *billobjs = (struct obj *) 0; -/* used to zero all elements of a struct obj */ +/* used to zero all elements of a struct obj and a struct monst */ NEARDATA struct obj zeroobj = DUMMY; +NEARDATA struct monst zeromonst = DUMMY; /* used to zero out union any; initializer deliberately omitted */ NEARDATA anything zeroany; diff --git a/src/makemon.c b/src/makemon.c index 9e2d5b9a6..61de5af0e 100644 --- a/src/makemon.c +++ b/src/makemon.c @@ -6,8 +6,6 @@ #include -STATIC_VAR NEARDATA struct monst zeromonst; - /* this assumes that a human quest leader or nemesis is an archetype of the corresponding role; that isn't so for some roles (tourist for instance) but is for the priests and monks we use it for... */ @@ -23,7 +21,8 @@ STATIC_DCL void FDECL(m_initgrp, (struct monst *, int, int, int)); STATIC_DCL void FDECL(m_initthrow, (struct monst *, int, int)); STATIC_DCL void FDECL(m_initweap, (struct monst *)); STATIC_DCL void FDECL(m_initinv, (struct monst *)); -STATIC_DCL boolean FDECL(makemon_rnd_goodpos, (struct monst *, unsigned, coord *)); +STATIC_DCL boolean FDECL(makemon_rnd_goodpos, (struct monst *, + unsigned, coord *)); extern const int monstr[]; diff --git a/src/mkobj.c b/src/mkobj.c index 6cb87e40b..3574eaece 100644 --- a/src/mkobj.c +++ b/src/mkobj.c @@ -107,13 +107,11 @@ struct obj *otmp; { if (!otmp->oextra) otmp->oextra = newoextra(); + if (!OMONST(otmp)) { struct monst *m = newmonst(); - /* newmonst() allocates memory but doesn't initialize anything */ - (void) memset((genericptr_t) m, 0, sizeof (struct monst)); - m->mextra = (struct mextra *) 0; - m->nmon = (struct monst *) 0; + *m = zeromonst; OMONST(otmp) = m; } } diff --git a/src/mplayer.c b/src/mplayer.c index c2178b07f..de5a2d680 100644 --- a/src/mplayer.c +++ b/src/mplayer.c @@ -331,12 +331,13 @@ boolean special; int pm, x, y; struct monst fakemon; + fakemon = zeromonst; while (num) { int tryct = 0; /* roll for character class */ pm = PM_ARCHEOLOGIST + rn2(PM_WIZARD - PM_ARCHEOLOGIST + 1); - fakemon.data = &mons[pm]; + set_mon_data(&fakemon, &mons[pm], -1); /* roll for an available location */ do { diff --git a/src/teleport.c b/src/teleport.c index 1c9b38872..289d6a351 100644 --- a/src/teleport.c +++ b/src/teleport.c @@ -128,7 +128,9 @@ unsigned entflags; /* default to player's original monster type */ mdat = &mons[u.umonster]; } - fakemon.data = mdat; /* set up for goodpos */ + fakemon = zeromonst; + set_mon_data(&fakemon, mdat, -1); /* set up for goodpos */ + good_ptr = good; range = 1; /* From 105fd0a9a2048b8595290c14446ca87e0eec2262 Mon Sep 17 00:00:00 2001 From: PatR Date: Sun, 27 Mar 2016 16:50:38 -0700 Subject: [PATCH 043/151] fix #H4286 - race condition during Unix build Update sys/unix/Makefile.src to force it to build monst.o and objects.o before attempting to build makedefs, so that building the latter doesn't use the rules for those two object files in Makefile.utl. Prior to this, if 'make' was building multiple targets in parallel monst.o and/or objects.o might be clobbered when a process using util/Makefile tries to build them while its parent is also building them via src/Makefile. This includes a bit of reformatting which wasn't present in yesterday's email attachment. --- sys/unix/Makefile.src | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/sys/unix/Makefile.src b/sys/unix/Makefile.src index 16d087472..7b477940f 100644 --- a/sys/unix/Makefile.src +++ b/sys/unix/Makefile.src @@ -1,5 +1,5 @@ # NetHack Makefile. -# NetHack 3.6 Makefile.src $NHDT-Date: 1447313972 2015/11/12 07:39:32 $ $NHDT-Branch: master $:$NHDT-Revision: 1.45 $ +# NetHack 3.6 Makefile.src $NHDT-Date: 1459122529 2016/03/27 23:48:49 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.46 $ # Root of source tree: NHSROOT=.. @@ -357,9 +357,9 @@ WINCSRC = $(WINTTYSRC) $(WINX11SRC) $(WINGNOMESRC) $(WINGEMSRC) WINCXXSRC = $(WINQTSRC) $(WINBESRC) # Files for window system chaining. Requires SYSCF; include via HINTSRC/HINTOBJ -CHAINSRC=../win/chain/wc_chainin.c ../win/chain/wc_chainout.c \ - ../win/chain/wc_trace.c -CHAINOBJ=wc_chainin.o wc_chainout.o wc_trace.o +CHAINSRC = ../win/chain/wc_chainin.c ../win/chain/wc_chainout.c \ + ../win/chain/wc_trace.c +CHAINOBJ = wc_chainin.o wc_chainout.o wc_trace.o # .c files for this version (for date.h) VERSOURCES = $(HACKCSRC) $(SYSSRC) $(WINSRC) $(CHAINSRC) $(GENCSRC) @@ -378,9 +378,9 @@ HACKINCL = align.h amiconf.h artifact.h artilist.h attrib.h beconf.h botl.h \ global.h hack.h lev.h lint.h macconf.h mextra.h mfndpos.h micro.h \ mkroom.h \ monattk.h mondata.h monflag.h monst.h monsym.h obj.h objclass.h \ - os2conf.h patchlevel.h pcconf.h permonst.h prop.h rect.h region.h rm.h \ - sp_lev.h spell.h sys.h system.h tcap.h timeout.h tosconf.h tradstdc.h \ - trampoli.h trap.h unixconf.h vision.h vmsconf.h wintty.h \ + os2conf.h patchlevel.h pcconf.h permonst.h prop.h rect.h region.h \ + rm.h sp_lev.h spell.h sys.h system.h tcap.h timeout.h tosconf.h \ + tradstdc.h trampoli.h trap.h unixconf.h vision.h vmsconf.h wintty.h \ winX.h winprocs.h wintype.h you.h youprop.h HSOURCES = $(HACKINCL) date.h onames.h pm.h vis_tab.h\ @@ -490,23 +490,31 @@ qt_win.moc: ../include/qt_win.h qttableview.moc: ../include/qttableview.h $(QTDIR)/bin/moc -o qttableview.moc ../include/qttableview.h -$(MAKEDEFS): ../util/makedefs.c $(CONFIG_H) ../include/permonst.h \ +# build monst.o and objects.o before executing '$(MAKE) makedefs' +$(MAKEDEFS): $(FIRSTOBJ) \ + ../util/makedefs.c $(CONFIG_H) ../include/permonst.h \ ../include/objclass.h ../include/monsym.h \ ../include/artilist.h ../include/dungeon.h ../include/obj.h \ ../include/monst.h ../include/you.h ../include/flag.h \ ../include/dlb.h ../include/patchlevel.h ../include/qtext.h - @( cd ../util ; $(MAKE) makedefs) + @( cd ../util ; $(MAKE) makedefs ) +# Source files created by 'makedefs' at build time. +# Each is given an artificial dependency upon the one before +# so that parallel makes will have to build them sequentially and +# not clobber makedefs by attempting simultaneous builds of it. ../include/onames.h: $(MAKEDEFS) @( cd ../util ; $(MAKE) ../include/onames.h ) -../include/pm.h: $(MAKEDEFS) +../include/pm.h: $(MAKEDEFS) ../include/onames.h @( cd ../util ; $(MAKE) ../include/pm.h ) -monstr.c: $(MAKEDEFS) +monstr.c: $(MAKEDEFS) ../include/pm.h @( cd ../util ; $(MAKE) ../src/monstr.c ) -../include/vis_tab.h: $(MAKEDEFS) +../include/vis_tab.h: $(MAKEDEFS) monstr.c @( cd ../util ; $(MAKE) ../include/vis_tab.h ) # makedefs -z makes both vis_tab.h and vis_tab.c, but writes the .h first vis_tab.c: ../include/vis_tab.h +# Created at build time for configurations which support tiles, +# but not by makedefs so not connected to the others. tile.c: ../win/share/tilemap.c $(HACK_H) @( cd ../util ; $(MAKE) ../src/tile.c ) From f81543aa4fc40834d66fc85a43dcb473546124df Mon Sep 17 00:00:00 2001 From: Haoyang Wang Date: Mon, 28 Mar 2016 02:36:43 -0700 Subject: [PATCH 044/151] MacOS 10.11.3 now requires the exec bits to be set on macosx.sh --- sys/unix/hints/macosx.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 sys/unix/hints/macosx.sh diff --git a/sys/unix/hints/macosx.sh b/sys/unix/hints/macosx.sh old mode 100644 new mode 100755 From 9e0a9d96f97783c719e9d3db3b20090e6abc2a93 Mon Sep 17 00:00:00 2001 From: PatR Date: Mon, 28 Mar 2016 17:23:00 -0700 Subject: [PATCH 045/151] makedefs lint The Makefile race condition report included a link to a log file of the build attempt, and it contained this: makedefs.c: In function 'do_grep_control': makedefs.c:611:26: warning: suggest parentheses around operand of '!' or change '|' to '||' or '!' to '~' [-Wparentheses] #define ST_LD(old, opp) (!!(old) | (!!(opp) << 1)) ^ makedefs.c:722:37: note: in expansion of macro 'ST_LD' grep_stack[++grep_sp] = ST_LD(grep_writing, !isif); ^ They're using a more recent version of gcc than I am, because my CFLAGS includes -Wparentheses (via -Wall) and I don't get that. It's a little confusing, but I think it's whining that we might have meant !!((old) | (!!(opp) << 1)) rather than (!!(old)) | (!!(opp) << 1). The latter is what we get (and what we intended--no bug here). I changed it to something that more directly reflects the intent since it's not bit twiddling within some crucial innermost loop. --- util/makedefs.c | 67 ++++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/util/makedefs.c b/util/makedefs.c index 2969b6645..53cb4d865 100644 --- a/util/makedefs.c +++ b/util/makedefs.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 makedefs.c $NHDT-Date: 1455357861 2016/02/13 10:04:21 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.109 $ */ +/* NetHack 3.6 makedefs.c $NHDT-Date: 1459208813 2016/03/28 23:46:53 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.110 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* Copyright (c) M. Stephenson, 1990, 1991. */ /* Copyright (c) Dean Luick, 1990. */ @@ -578,36 +578,35 @@ do_ext_makedefs(int argc, char **argv) #undef CONSUME /* - Filtering syntax: - Any line NOT starting with a caret is either suppressed or passed through - unchanged depending on the current conditional state. - - The default conditional state is printing on. - - Conditionals may be nested. - - makedefs will exit with a EXIT_FAILURE if any errors are detected; as many - errors as possible are detected before giving up. - - Unknown identifiers are treated as TRUE and also as an error to allow - processing to continue past the unknown identifier (note that "#undef" is - different than unknown). - - Any line starting with a caret is a control line; as in C, zero or more - spaces - may be embedded in the line almost anywhere; the caret MUST be in column 1. - (XXX for the moment, no white space is allowed after the caret because - existing lines in the docs look like that) - - Control lines: - ^^ a line starting with a (single) literal caret - ^# a comment - the line is ignored - ^?ID if defined(ID) - ^!ID if !defined(ID) - ^: else - ^. endif - -*/ + * Filtering syntax: + * Any line NOT starting with a caret is either suppressed or passed + * through unchanged depending on the current conditional state. + * + * The default conditional state is printing on. + * + * Conditionals may be nested. + * + * makedefs will exit with a EXIT_FAILURE if any errors are detected; + * as many errors as possible are detected before giving up. + * + * Unknown identifiers are treated as TRUE and also as an error to + * allow processing to continue past the unknown identifier (note + * that "#undef" is different than unknown). + * + * Any line starting with a caret is a control line; as in C, zero or + * more spaces may be embedded in the line almost anywhere; the caret + * MUST be in column 1. + * (XXX for the moment, no white space is allowed after the caret because + * existing lines in the docs look like that.) + * + * Control lines: + * ^^ a line starting with a (single) literal caret + * ^# a comment - the line is ignored + * ^?ID if defined(ID) + * ^!ID if !defined(ID) + * ^: else + * ^. endif + */ #define GREP_MAGIC '^' #define GREP_STACK_SIZE 100 #ifdef notyet @@ -616,9 +615,9 @@ static int grep_rewrite = 0; /* need to (possibly) rewrite lines */ static int grep_writing = 1; /* need to copy lines to output */ static int grep_errors = 0; static int grep_sp = 0; -#define ST_LD(old, opp) (!!(old) | (!!(opp) << 1)) -#define ST_OLD(v) ((v) &1) -#define ST_OPP(v) !!((v) &2) +#define ST_LD(old, opp) (((old) ? 1 : 0) | ((opp) ? 2 : 0)) +#define ST_OLD(v) (((v) & 1) != 0) +#define ST_OPP(v) (((v) & 2) != 0) #define ST_ELSE 4 static int grep_stack[GREP_STACK_SIZE] = { ST_LD(1, 0) }; static int grep_lineno = 0; From c3ef98c4d1fec23f8a7d58221a787495d96b58b8 Mon Sep 17 00:00:00 2001 From: PatR Date: Thu, 31 Mar 2016 17:56:11 -0700 Subject: [PATCH 046/151] REPRODUCIBLE_BUILD Take the 4-5 line Debian patch and turn it into six dozen lines of new code. The submitted patch introduces use of several C library routines that aren't presently in use, so would need testing by all functional or nearly-functional ports to verify that it wouldn't break anything. It also switched the formatted build date+time from localtime to UTC. This makes the code conditional so it can be ignored by anybody and avoid the risk of breakage. And a lot of the increase in size is comments attempting to explain what the new conditional is for: when REPRODUCIBLE_BUILD is defined, makedefs will use getenv("SOURCE_DATE_EPOCH") (whose value is an integer representing seconds since 1-Jan-1970) instead of current date+time when generating date.h. The purpose is to be able to rebuild at a later date and produce an identical program, which doesn't happen when compile time gets incorporated into the binary. I've added some sanity checking to try to make sure the getenv() value obtained isn't bogus. And the version string put into date.h will be slightly different, allowing someone who sees date.h or 'v' output to tell whether SOURCE_DATE_EPOCH was involved: showing " NetHack last revision " instead of the usual "... last build ". To test, checkout a new branch for building, make any local edits to unixconf.h and config.h, including enabling REPRODUCIBLE_BUILD, git add+commit them, then use SOURCE_DATE_EPOCH=`git log -1 --pretty=%ct` make install Other ports will need a bit more work to set up the environment, but can still use git to track file dates and supply the latest. Building with alternate configurations could be accomplished by using tags instead of 'log -1' or by using distinct build branches where nothing is commited/merged/rebased after completed build. Unresolved issue: BUILD_DATE, VERSION_ID, and COPYRIGHT_BANNER_C contain formatted date+time but omit timezone. SOURCE_DATE_EPOCH is assumed to be UTC but the formatted values don't say so, so it might appear to be incorrect when compared with local time. We definitely don't want to start mucking about with timezones within nethack, so I think we just live with this. It's not an issue for default configruation where REPRODUCIBLE_BUILD is left disabled. --- include/config.h | 23 ++++++++++ src/version.c | 4 +- util/makedefs.c | 110 +++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 122 insertions(+), 15 deletions(-) diff --git a/include/config.h b/include/config.h index 121ce5407..47b2951cc 100644 --- a/include/config.h +++ b/include/config.h @@ -11,6 +11,7 @@ * For "UNIX" select BSD, ULTRIX, SYSV, or HPUX in unixconf.h. * A "VMS" option is not needed since the VMS C-compilers * provide it (no need to change sec#1, vmsconf.h handles it). + * MacOSX uses the UNIX configruation, not the old MAC one. */ #define UNIX /* delete if no fork(), exec() available */ @@ -304,6 +305,28 @@ */ /* #define DLB */ /* not supported on all platforms */ +/* + * Defining REPRODUCIBLE_BUILD causes 'util/makedefs -v' to construct + * date+time in include/date.h (to be shown by nethack's 'v' command) + * from SOURCE_DATE_EPOCH in the build environment rather than use + * current date+time when makedefs is run. + * + * [The version string will show "last revision