/* NetHack 3.7 mhmap.c $NHDT-Date: 1596498353 2020/08/03 23:45:53 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.85 $ */ /* Copyright (C) 2001 by Alex Kompel */ /* NetHack may be freely redistributed. See license for details. */ #include "win10.h" #include "winMS.h" #include "winos.h" #include "mhfont.h" #include "mhinput.h" #include "mhmap.h" #include "mhmsg.h" #include "resource.h" #include "color.h" #if !defined(PATCHLEVEL_H) #include "patchlevel.h" #endif #define NHMAP_FONT_NAME TEXT("Terminal") #define NHMAP_TTFONT_NAME TEXT("Consolas") #define MAXWINDOWTEXT 255 #define CURSOR_BLINK_INTERVAL 1000 // milliseconds #define CURSOR_HEIGHT 2 // pixels #define TILEBMP_X(ntile) \ ((ntile % GetNHApp()->mapTilesPerLine) * GetNHApp()->mapTile_X) #define TILEBMP_Y(ntile) \ ((ntile / GetNHApp()->mapTilesPerLine) * GetNHApp()->mapTile_Y) /* map window data */ typedef struct mswin_nethack_map_window { HWND hWnd; /* window */ glyph_info map[COLNO][ROWNO]; glyph_info bkmap[COLNO][ROWNO]; boolean locDirty[COLNO][ROWNO]; /* dirty flag for map location */ boolean mapDirty; /* one or more map locations are dirty */ int mapMode; /* current map mode */ boolean bAsciiMode; /* switch ASCII/tiled mode */ boolean bFitToScreenMode; /* switch Fit map to screen mode on/off */ int xPos, yPos; /* scroll position */ int xPageSize, yPageSize; /* scroll page size */ int xMin, xMax, yMin, yMax; /* scroll range */ int xCur, yCur; /* position of the cursor */ int xFrontTile, yFrontTile; /* size of tile in front buffer in pixels */ int xBackTile, yBackTile; /* size of tile in back buffer in pixels */ POINT map_orig; /* map origin point */ HFONT hMapFont; /* font for ASCII mode */ HFONT hMapFontUnicode; /* font for Unicode mode */ boolean bUnicodeFont; /* font supports unicode page 437 */ int tileWidth; /* width of tile in pixels at 96 dpi */ int tileHeight; /* height of tile in pixels at 96 dpi */ double backScale; /* scaling from source to back buffer */ double frontScale; /* scaling from back to front */ double monitorScale; /* from 96dpi to monitor dpi*/ boolean cursorOn; int yNoBlinkCursor; /* non-blinking cursor height inback buffer in pixels */ int yBlinkCursor; /* blinking cursor height inback buffer in pixels */ int backWidth; /* back buffer width */ int backHeight; /* back buffer height */ HBITMAP hBackBuffer; /* back buffe bitmap */ HDC backBufferDC; /* back buffer drawing context */ HDC tileDC; /* tile drawing context */ } NHMapWindow, *PNHMapWindow; static TCHAR szNHMapWindowClass[] = TEXT("MSNethackMapWndClass"); LRESULT CALLBACK MapWndProc(HWND, UINT, WPARAM, LPARAM); static void register_map_window_class(void); static void onMSNHCommand(HWND hWnd, WPARAM wParam, LPARAM lParam); static void onMSNH_VScroll(HWND hWnd, WPARAM wParam, LPARAM lParam); static void onMSNH_HScroll(HWND hWnd, WPARAM wParam, LPARAM lParam); static void onPaint(HWND hWnd); static void onCreate(HWND hWnd, WPARAM wParam, LPARAM lParam); static void nhcoord2display(PNHMapWindow data, int x, int y, LPRECT lpOut); static void paint(PNHMapWindow data, int i, int j); static void dirtyAll(PNHMapWindow data); static void dirty(PNHMapWindow data, int i, int j); static void setGlyph(PNHMapWindow data, int i, int j, const glyph_info *fg, const glyph_info *bg); static void clearAll(PNHMapWindow data); #if (VERSION_MAJOR < 4) && (VERSION_MINOR < 4) && (PATCHLEVEL < 2) static void nhglyph2charcolor(short glyph, uchar *ch, int *color); #endif extern boolean win32_cursorblink; /* from sys\windows\windsys.c */ HWND mswin_init_map_window(void) { static int run_once = 0; HWND hWnd; RECT rt; if (!run_once) { register_map_window_class(); run_once = 1; } /* get window position */ if (GetNHApp()->bAutoLayout) { SetRect(&rt, 0, 0, 0, 0); } else { mswin_get_window_placement(NHW_MAP, &rt); } /* create map window object */ hWnd = CreateWindow( szNHMapWindowClass, /* registered class name */ NULL, /* window name */ WS_CHILD | WS_HSCROLL | WS_VSCROLL | WS_CLIPSIBLINGS | WS_SIZEBOX, /* window style */ rt.left, /* horizontal position of window */ rt.top, /* vertical position of window */ rt.right - rt.left, /* window width */ rt.bottom - rt.top, /* window height */ GetNHApp()->hMainWnd, /* handle to parent or owner window */ NULL, /* menu handle or child identifier */ GetNHApp()->hApp, /* handle to application instance */ NULL); /* window-creation data */ if (!hWnd) { panic("Cannot create map window"); } /* Set window caption */ SetWindowText(hWnd, "Map"); mswin_apply_window_style(hWnd); /* set cursor blink timer */ SetTimer(hWnd, 0, CURSOR_BLINK_INTERVAL, NULL); return hWnd; } void mswin_map_layout(HWND hWnd, LPSIZE map_size) { /* check arguments */ if (!IsWindow(hWnd) || !map_size || map_size->cx <= 0 || map_size->cy <= 0) return; PNHMapWindow data = (PNHMapWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA); /* calculate window size */ RECT client_rt; GetClientRect(hWnd, &client_rt); SIZE wnd_size; wnd_size.cx = client_rt.right - client_rt.left; wnd_size.cy = client_rt.bottom - client_rt.top; // calculate back buffer scale data->monitorScale = win10_monitor_scale(hWnd); boolean bText = data->bAsciiMode || (u.uz.dlevel != 0 && Is_rogue_level(&u.uz)); if (bText && !data->bFitToScreenMode) data->backScale = data->monitorScale; else data->backScale = 1.0; /* set back buffer tile size */ if (bText && data->bFitToScreenMode) { data->xBackTile = wnd_size.cx / COLNO; data->yBackTile = wnd_size.cy / ROWNO; data->yBackTile = max(data->yBackTile, 12); } else { data->xBackTile = (int)(data->tileWidth * data->backScale); data->yBackTile = (int)(data->tileHeight * data->backScale); } if (bText) { LOGFONT lgfnt; ZeroMemory(&lgfnt, sizeof(lgfnt)); if (data->bFitToScreenMode) { lgfnt.lfHeight = -data->yBackTile; // height of font lgfnt.lfWidth = 0; // average character width } else { lgfnt.lfHeight = -data->yBackTile; // height of font lgfnt.lfWidth = -data->xBackTile; // average character width } lgfnt.lfEscapement = 0; // angle of escapement lgfnt.lfOrientation = 0; // base-line orientation angle lgfnt.lfWeight = FW_NORMAL; // font weight lgfnt.lfItalic = FALSE; // italic attribute option lgfnt.lfUnderline = FALSE; // underline attribute option lgfnt.lfStrikeOut = FALSE; // strikeout attribute option lgfnt.lfCharSet = mswin_charset(); // character set identifier lgfnt.lfOutPrecision = OUT_DEFAULT_PRECIS; // output precision lgfnt.lfClipPrecision = CLIP_DEFAULT_PRECIS; // clipping precision if (data->bFitToScreenMode) { lgfnt.lfQuality = ANTIALIASED_QUALITY; // output quality } else { lgfnt.lfQuality = NONANTIALIASED_QUALITY; // output quality } if (iflags.wc_font_map && *iflags.wc_font_map) { lgfnt.lfPitchAndFamily = DEFAULT_PITCH; // pitch and family NH_A2W(iflags.wc_font_map, lgfnt.lfFaceName, LF_FACESIZE); } else { if (!data->bFitToScreenMode) { lgfnt.lfPitchAndFamily = FIXED_PITCH; // pitch and family NH_A2W(NHMAP_FONT_NAME, lgfnt.lfFaceName, LF_FACESIZE); } else { lgfnt.lfPitchAndFamily = DEFAULT_PITCH; // pitch and family NH_A2W(NHMAP_TTFONT_NAME, lgfnt.lfFaceName, LF_FACESIZE); } } TEXTMETRIC textMetrics; HFONT font = NULL; while (1) { if (font != NULL) DeleteObject(font); font = CreateFontIndirect(&lgfnt); SelectObject(data->backBufferDC, font); GetTextMetrics(data->backBufferDC, &textMetrics); if (!data->bFitToScreenMode) break; if ((textMetrics.tmHeight > data->yBackTile || textMetrics.tmAveCharWidth > data->xBackTile) && lgfnt.lfHeight < -MIN_FONT_HEIGHT) { lgfnt.lfHeight++; continue; } break; } if (data->hMapFont) DeleteObject(data->hMapFont); data->hMapFont = font; data->bUnicodeFont = winos_font_support_cp437(data->hMapFont); // Same as above, but with ANSI_CHARSET for IBM and Unicode modes lgfnt.lfCharSet = ANSI_CHARSET; if (data->hMapFontUnicode) DeleteObject(data->hMapFontUnicode); data->hMapFontUnicode = CreateFontIndirect(&lgfnt); // set tile size to match font metrics data->xBackTile = textMetrics.tmAveCharWidth; data->yBackTile = textMetrics.tmHeight; } int backWidth = COLNO * data->xBackTile; int backHeight = ROWNO * data->yBackTile; /* create back buffer */ if (data->backWidth != backWidth || data->backHeight != backHeight) { HDC frontBufferDC = GetDC(hWnd); HBITMAP hBackBuffer = CreateCompatibleBitmap(frontBufferDC, backWidth, backHeight); ReleaseDC(hWnd, frontBufferDC); if (data->hBackBuffer != NULL) { SelectBitmap(data->backBufferDC, hBackBuffer); DeleteObject(data->hBackBuffer); } data->backWidth = backWidth; data->backHeight = backHeight; SelectBitmap(data->backBufferDC, hBackBuffer); data->hBackBuffer = hBackBuffer; } /* calculate front buffer tile size */ if (wnd_size.cx > 0 && wnd_size.cy > 0 && !bText && data->bFitToScreenMode) { double windowAspectRatio = (double) wnd_size.cx / (double) wnd_size.cy; double backAspectRatio = (double) data->backWidth / (double) data->backHeight; if (windowAspectRatio > backAspectRatio) data->frontScale = (double) wnd_size.cy / (double) data->backHeight; else data->frontScale = (double) wnd_size.cx / (double) data->backWidth; } else { if (bText) { data->frontScale = 1.0; } else { data->frontScale = data->monitorScale; } } /* TODO: Should we round instead of clamping? */ data->xFrontTile = (int) ((double) data->xBackTile * data->frontScale); data->yFrontTile = (int) ((double) data->yBackTile * data->frontScale); /* ensure tile is at least one pixel in size */ if (data->xFrontTile < 1) data->xFrontTile = 1; if (data->yFrontTile < 1) data->yFrontTile = 1; /* ensure front tile is non-zero in size */ data->xFrontTile = max(data->xFrontTile, 1); data->yFrontTile = max(data->yFrontTile, 1); /* calculate ASCII cursor height */ data->yBlinkCursor = (int) ((double) CURSOR_HEIGHT * data->backScale); data->yNoBlinkCursor = data->yBackTile; /* set map origin point */ data->map_orig.x = max(0, client_rt.left + (wnd_size.cx - data->xFrontTile * COLNO) / 2); data->map_orig.y = max(0, client_rt.top + (wnd_size.cy - data->yFrontTile * ROWNO) / 2); data->map_orig.x -= data->map_orig.x % data->xFrontTile; data->map_orig.y -= data->map_orig.y % data->yFrontTile; // Set horizontal scroll data->xPageSize = min(COLNO, wnd_size.cx / data->xFrontTile); GetNHApp()->bNoHScroll = (data->xPageSize == COLNO); data->xMin = 0; data->xMax = COLNO - data->xPageSize; data->xPos = max(0, min(data->xMax, u.ux - (data->xPageSize / 2))); SCROLLINFO si; si.cbSize = sizeof(si); si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS; si.nMin = data->xMin; si.nMax = data->xMax; si.nPage = 1; si.nPos = data->xPos; SetScrollInfo(hWnd, SB_HORZ, &si, TRUE); data->yPageSize = min(ROWNO, wnd_size.cy / data->yFrontTile); GetNHApp()->bNoVScroll = (data->yPageSize == ROWNO); data->yMin = 0; data->yMax = ROWNO - data->yPageSize; data->yPos = max(0, min(data->yMax, u.uy - (data->yPageSize / 2))); si.cbSize = sizeof(si); si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS; si.nMin = data->yMin; si.nMax = data->yMax; si.nPage = 1; si.nPos = data->yPos; SetScrollInfo(hWnd, SB_VERT, &si, TRUE); mswin_cliparound(data->xCur, data->yCur); // redraw all map locations dirtyAll(data); // invalidate entire map window InvalidateRect(hWnd, NULL, TRUE); } /* set map mode */ int mswin_map_mode(HWND hWnd, int mode) { PNHMapWindow data; int oldMode; SIZE mapSize; data = (PNHMapWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA); if (mode == data->mapMode) return mode; oldMode = data->mapMode; data->mapMode = mode; switch (data->mapMode) { case MAP_MODE_ASCII4x6: data->bAsciiMode = TRUE; data->bFitToScreenMode = FALSE; data->tileWidth = 4; data->tileHeight = 6; break; case MAP_MODE_ASCII6x8: data->bAsciiMode = TRUE; data->bFitToScreenMode = FALSE; data->tileWidth = 6; data->tileHeight = 8; break; case MAP_MODE_ASCII8x8: data->bAsciiMode = TRUE; data->bFitToScreenMode = FALSE; data->tileWidth = 8; data->tileHeight = 8; break; case MAP_MODE_ASCII16x8: data->bAsciiMode = TRUE; data->bFitToScreenMode = FALSE; data->tileWidth = 16; data->tileHeight = 8; break; case MAP_MODE_ASCII7x12: data->bAsciiMode = TRUE; data->bFitToScreenMode = FALSE; data->tileWidth = 7; data->tileHeight = 12; break; case MAP_MODE_ASCII8x12: data->bAsciiMode = TRUE; data->bFitToScreenMode = FALSE; data->tileWidth = 8; data->tileHeight = 12; break; case MAP_MODE_ASCII16x12: data->bAsciiMode = TRUE; data->bFitToScreenMode = FALSE; data->tileWidth = 16; data->tileHeight = 12; break; case MAP_MODE_ASCII12x16: data->bAsciiMode = TRUE; data->bFitToScreenMode = FALSE; data->tileWidth = 12; data->tileHeight = 16; break; case MAP_MODE_ASCII10x18: data->bAsciiMode = TRUE; data->bFitToScreenMode = FALSE; data->tileWidth = 10; data->tileHeight = 18; break; case MAP_MODE_ASCII_FIT_TO_SCREEN: data->bAsciiMode = TRUE; data->bFitToScreenMode = TRUE; data->tileWidth = 12; data->tileHeight = 16; break; case MAP_MODE_TILES_FIT_TO_SCREEN: data->bAsciiMode = FALSE; data->bFitToScreenMode = TRUE; data->tileWidth = GetNHApp()->mapTile_X; data->tileHeight = GetNHApp()->mapTile_Y; break; case MAP_MODE_TILES: default: data->bAsciiMode = FALSE; data->bFitToScreenMode = FALSE; data->tileWidth = GetNHApp()->mapTile_X; data->tileHeight = GetNHApp()->mapTile_Y; break; } mapSize.cx = data->tileWidth * COLNO; mapSize.cy = data->tileHeight * ROWNO; mswin_map_layout(hWnd, &mapSize); mswin_update_inventory(0); /* for perm_invent to hide/show tiles */ return oldMode; } void mswin_map_update(HWND hWnd) { PNHMapWindow data = (PNHMapWindow)GetWindowLongPtr(hWnd, GWLP_USERDATA); if (data->mapDirty) { /* update back buffer */ HBITMAP savedBitmap = SelectObject(data->tileDC, GetNHApp()->bmpMapTiles); for (int i = 0; i < COLNO; i++) for (int j = 0; j < ROWNO; j++) if (data->locDirty[i][j]) { paint(data, i, j); RECT rect; nhcoord2display(data, i, j, &rect); InvalidateRect(data->hWnd, &rect, FALSE); } SelectObject(data->tileDC, savedBitmap); data->mapDirty = FALSE; } } /* register window class for map window */ void register_map_window_class(void) { WNDCLASS wcex; ZeroMemory(&wcex, sizeof(wcex)); /* window class */ wcex.style = CS_NOCLOSE | CS_DBLCLKS; wcex.lpfnWndProc = (WNDPROC) MapWndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = GetNHApp()->hApp; wcex.hIcon = NULL; wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = CreateSolidBrush(RGB(0, 0, 0)); /* set backgroup here */ wcex.lpszMenuName = NULL; wcex.lpszClassName = szNHMapWindowClass; if (!RegisterClass(&wcex)) { panic("cannot register Map window class"); } } /* map window procedure */ LRESULT CALLBACK MapWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { PNHMapWindow data; data = (PNHMapWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA); switch (message) { case WM_CREATE: onCreate(hWnd, wParam, lParam); break; case WM_MSNH_COMMAND: onMSNHCommand(hWnd, wParam, lParam); break; case WM_PAINT: onPaint(hWnd); break; case WM_SETFOCUS: /* transfer focus back to the main window */ SetFocus(GetNHApp()->hMainWnd); break; case WM_HSCROLL: onMSNH_HScroll(hWnd, wParam, lParam); break; case WM_VSCROLL: onMSNH_VScroll(hWnd, wParam, lParam); break; case WM_SIZE: { RECT rt; SIZE size; if (data->bFitToScreenMode) { size.cx = LOWORD(lParam); size.cy = HIWORD(lParam); } else { /* mapping factor is unchanged we just need to adjust scroll bars */ size.cx = data->xFrontTile * COLNO; size.cy = data->yFrontTile * ROWNO; } mswin_map_layout(hWnd, &size); /* update window placement */ GetWindowRect(hWnd, &rt); ScreenToClient(GetNHApp()->hMainWnd, (LPPOINT) &rt); ScreenToClient(GetNHApp()->hMainWnd, ((LPPOINT) &rt) + 1); mswin_update_window_placement(NHW_MAP, &rt); } break; case WM_MOVE: { RECT rt; GetWindowRect(hWnd, &rt); ScreenToClient(GetNHApp()->hMainWnd, (LPPOINT) &rt); ScreenToClient(GetNHApp()->hMainWnd, ((LPPOINT) &rt) + 1); mswin_update_window_placement(NHW_MAP, &rt); } return FALSE; case WM_LBUTTONDOWN: NHEVENT_MS(CLICK_1, max(0, min(COLNO, data->xPos + (LOWORD(lParam) - data->map_orig.x) / data->xFrontTile)), max(0, min(ROWNO, data->yPos + (HIWORD(lParam) - data->map_orig.y) / data->yFrontTile))); return 0; case WM_LBUTTONDBLCLK: case WM_RBUTTONDOWN: NHEVENT_MS(CLICK_2, max(0, min(COLNO, data->xPos + (LOWORD(lParam) - data->map_orig.x) / data->xFrontTile)), max(0, min(ROWNO, data->yPos + (HIWORD(lParam) - data->map_orig.y) / data->yFrontTile))); return 0; case WM_DESTROY: if (data->hMapFont) DeleteObject(data->hMapFont); if (data->hMapFontUnicode) DeleteObject(data->hMapFontUnicode); if (data->hBackBuffer) DeleteBitmap(data->hBackBuffer); if (data->backBufferDC) DeleteDC(data->backBufferDC); free(data); SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR) 0); windowdata[NHW_MAP].address = 0; break; case WM_TIMER: data->cursorOn = !data->cursorOn; dirty(data, data->xCur, data->yCur); break; case WM_DPICHANGED: { RECT rt; GetWindowRect(hWnd, &rt); ScreenToClient(GetNHApp()->hMainWnd, (LPPOINT)&rt); ScreenToClient(GetNHApp()->hMainWnd, ((LPPOINT)&rt) + 1); mswin_update_window_placement(NHW_MAP, &rt); } break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } /* on WM_COMMAND */ void onMSNHCommand(HWND hWnd, WPARAM wParam, LPARAM lParam) { PNHMapWindow data; data = (PNHMapWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA); switch (wParam) { case MSNH_MSG_PRINT_GLYPH: { PMSNHMsgPrintGlyph msg_data = (PMSNHMsgPrintGlyph) lParam; setGlyph(data, msg_data->x, msg_data->y, &msg_data->glyphinfo, &msg_data->bkglyphinfo); } break; case MSNH_MSG_CLIPAROUND: { PMSNHMsgClipAround msg_data = (PMSNHMsgClipAround) lParam; int x, y; BOOL scroll_x, scroll_y; int mcam = iflags.wc_scroll_margin; /* calculate if you should clip around */ scroll_x = !GetNHApp()->bNoHScroll && (msg_data->x < (data->xPos + mcam) || msg_data->x > (data->xPos + data->xPageSize - mcam)); scroll_y = !GetNHApp()->bNoVScroll && (msg_data->y < (data->yPos + mcam) || msg_data->y > (data->yPos + data->yPageSize - mcam)); mcam += iflags.wc_scroll_amount - 1; /* get page size and center horizontally on x-position */ if (scroll_x) { if (data->xPageSize <= 2 * mcam) { x = max(0, min(COLNO, msg_data->x - data->xPageSize / 2)); } else if (msg_data->x < data->xPos + data->xPageSize / 2) { x = max(0, min(COLNO, msg_data->x - mcam)); } else { x = max(0, min(COLNO, msg_data->x - data->xPageSize + mcam)); } SendMessage(hWnd, WM_HSCROLL, (WPARAM) MAKELONG(SB_THUMBTRACK, x), (LPARAM) NULL); } /* get page size and center vertically on y-position */ if (scroll_y) { if (data->yPageSize <= 2 * mcam) { y = max(0, min(ROWNO, msg_data->y - data->yPageSize / 2)); } else if (msg_data->y < data->yPos + data->yPageSize / 2) { y = max(0, min(ROWNO, msg_data->y - mcam)); } else { y = max(0, min(ROWNO, msg_data->y - data->yPageSize + mcam)); } SendMessage(hWnd, WM_VSCROLL, (WPARAM) MAKELONG(SB_THUMBTRACK, y), (LPARAM) NULL); } } break; case MSNH_MSG_CLEAR_WINDOW: clearAll(data); break; case MSNH_MSG_CURSOR: { PMSNHMsgCursor msg_data = (PMSNHMsgCursor) lParam; if (data->xCur != msg_data->x || data->yCur != msg_data->y) { dirty(data, data->xCur, data->yCur); dirty(data, msg_data->x, msg_data->y); data->xCur = msg_data->x; data->yCur = msg_data->y; } } break; case MSNH_MSG_GETTEXT: { PMSNHMsgGetText msg_data = (PMSNHMsgGetText) lParam; size_t index; int col, row; #if 0 int color; unsigned special = 0U; #endif int mgch; index = 0; for (row = 0; row < ROWNO; row++) { for (col = 0; col < COLNO; col++) { if (index >= msg_data->max_size) break; if (data->map[col][row].glyph == NO_GLYPH) mgch = ' '; msg_data->buffer[index] = data->map[col][row].ttychar; index++; } if (index >= msg_data->max_size - 1) break; msg_data->buffer[index++] = '\r'; msg_data->buffer[index++] = '\n'; } nhUse(mgch); } break; #ifdef ENHANCED_SYMBOLS case MSNH_MSG_GETWIDETEXT: { PMSNHMsgGetWideText msg_data = (PMSNHMsgGetWideText) lParam; size_t index; int col, row; index = 0; for (row = 0; row < ROWNO; row++) { for (col = 0; col < COLNO; col++) { glyph_info *glyphinfo; uint32 ch; if (index >= msg_data->max_size) break; glyphinfo = &data->map[col][row]; if (glyphinfo->gm.u && glyphinfo->gm.u->utf8str) { ch = glyphinfo->gm.u->utf32ch; } else { ch = glyphinfo->ttychar; } winos_ascii_to_wide(msg_data->buffer + index, ch); index += wcslen(msg_data->buffer + index); } if (index >= msg_data->max_size - 1) break; msg_data->buffer[index++] = '\r'; msg_data->buffer[index++] = '\n'; } } break; #endif case MSNH_MSG_RANDOM_INPUT: nhassert(0); // unexpected break; } /* end switch(wParam) */ } /* on WM_CREATE */ void onCreate(HWND hWnd, WPARAM wParam, LPARAM lParam) { PNHMapWindow data; UNREFERENCED_PARAMETER(wParam); UNREFERENCED_PARAMETER(lParam); /* set window data */ data = (PNHMapWindow) malloc(sizeof(NHMapWindow)); if (!data) panic("out of memory"); ZeroMemory(data, sizeof(NHMapWindow)); data->hWnd = hWnd; data->bAsciiMode = FALSE; data->cursorOn = TRUE; data->xFrontTile = GetNHApp()->mapTile_X; data->yFrontTile = GetNHApp()->mapTile_Y; data->tileWidth = GetNHApp()->mapTile_X; data->tileHeight = GetNHApp()->mapTile_Y; HDC hDC = GetDC(hWnd); data->backBufferDC = CreateCompatibleDC(hDC); data->tileDC = CreateCompatibleDC(hDC); ReleaseDC(hWnd, hDC); SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR) data); windowdata[NHW_MAP].address = (genericptr_t) data; clearAll(data); } static void paintTile(PNHMapWindow data, int i, int j, RECT * rect) { short ntile; int t_x, t_y; int glyph, bkglyph; int layer; #ifdef USE_PILEMARK // int color; // unsigned special = 0U; // int mgch; #endif layer = 0; glyph = data->map[i][j].glyph; bkglyph = data->bkmap[i][j].glyph; if (glyph == NO_GLYPH && bkglyph == NO_GLYPH) { HBRUSH blackBrush = CreateSolidBrush(RGB(0, 0, 0)); FillRect(data->backBufferDC, rect, blackBrush); DeleteObject(blackBrush); } if (glyph != NO_GLYPH) { if (bkglyph != NO_GLYPH) { ntile = data->bkmap[i][j].gm.tileidx; t_x = TILEBMP_X(ntile); t_y = TILEBMP_Y(ntile); StretchBlt(data->backBufferDC, rect->left, rect->top, data->xBackTile, data->yBackTile, data->tileDC, t_x, t_y, GetNHApp()->mapTile_X, GetNHApp()->mapTile_Y, SRCCOPY); layer++; } if (glyph != bkglyph) { /* rely on tileidx provided by NetHack core */ ntile = data->map[i][j].gm.tileidx; t_x = TILEBMP_X(ntile); t_y = TILEBMP_Y(ntile); /* Don't use all black GLYPH_UNEXPLORED tile as a background */ if (layer > 0 && bkglyph != GLYPH_UNEXPLORED) { (*GetNHApp()->lpfnTransparentBlt)( data->backBufferDC, rect->left, rect->top, data->xBackTile, data->yBackTile, data->tileDC, t_x, t_y, GetNHApp()->mapTile_X, GetNHApp()->mapTile_Y, TILE_BK_COLOR); } else { StretchBlt(data->backBufferDC, rect->left, rect->top, data->xBackTile, data->yBackTile, data->tileDC, t_x, t_y, GetNHApp()->mapTile_X, GetNHApp()->mapTile_Y, SRCCOPY); } layer++; } #ifdef USE_PILEMARK if ((data->map[i][j].gm.glyphflags & MG_PET) != 0 #else if (glyph_is_pet(glyph) #endif && iflags.wc_hilite_pet) { /* apply pet mark transparently over pet image */ HDC hdcPetMark; HBITMAP bmPetMarkOld; /* this is DC for petmark bitmap */ hdcPetMark = CreateCompatibleDC(data->backBufferDC); bmPetMarkOld = SelectObject(hdcPetMark, GetNHApp()->bmpPetMark); (*GetNHApp()->lpfnTransparentBlt)( data->backBufferDC, rect->left, rect->top, data->xBackTile, data->yBackTile, hdcPetMark, 0, 0, TILE_X, TILE_Y, TILE_BK_COLOR); SelectObject(hdcPetMark, bmPetMarkOld); DeleteDC(hdcPetMark); } #ifdef USE_PILEMARK if ((data->map[i][j].gm.glyphflags & MG_OBJPILE) != 0 && iflags.hilite_pile) { /* apply pilemark transparently over other image */ HDC hdcPileMark; HBITMAP bmPileMarkOld; /* this is DC for pilemark bitmap */ hdcPileMark = CreateCompatibleDC(data->backBufferDC); bmPileMarkOld = SelectObject(hdcPileMark, GetNHApp()->bmpPileMark); (*GetNHApp()->lpfnTransparentBlt)( data->backBufferDC, rect->left, rect->top, data->xBackTile, data->yBackTile, hdcPileMark, 0, 0, TILE_X, TILE_Y, TILE_BK_COLOR); SelectObject(hdcPileMark, bmPileMarkOld); DeleteDC(hdcPileMark); } #endif } /* glyph != NO_GLYPH */ if (i == data->xCur && j == data->yCur && (data->cursorOn || !win32_cursorblink)) DrawFocusRect(data->backBufferDC, rect); } static void paintGlyph(PNHMapWindow data, int i, int j, RECT * rect) { if (data->map[i][j].glyph >= 0) { glyph_info *glyphinfo; uint32 ch; WCHAR wch[3]; int color; uint32 rgbcolor; // unsigned special; // int mgch; HBRUSH back_brush; COLORREF OldFg; SetBkMode(data->backBufferDC, TRANSPARENT); HBRUSH blackBrush = CreateSolidBrush(RGB(0, 0, 0)); FillRect(data->backBufferDC, rect, blackBrush); DeleteObject(blackBrush); glyphinfo = &data->map[i][j]; ch = glyphinfo->ttychar; color = (int) glyphinfo->gm.sym.color; rgbcolor = nhcolor_to_RGB(color); #ifdef ENHANCED_SYMBOLS if (SYMHANDLING(H_UTF8) && glyphinfo->gm.u && glyphinfo->gm.u->utf8str) { ch = glyphinfo->gm.u->utf32ch; } #endif if (glyphinfo->gm.customcolor != 0 && (mswin_procs.wincap2 & WC2_EXTRACOLORS) != 0) { if ((glyphinfo->gm.customcolor & NH_BASIC_COLOR) != 0) { color = (int) COLORVAL(glyphinfo->gm.customcolor); rgbcolor = nhcolor_to_RGB(color); } } if (((data->map[i][j].gm.glyphflags & MG_PET) && iflags.hilite_pet) || ((data->map[i][j].gm.glyphflags & (MG_DETECT | MG_BW_LAVA | MG_BW_ICE | MG_BW_SINK | MG_BW_ENGR)) != 0)) { back_brush = CreateSolidBrush(nhcolor_to_RGB(CLR_GRAY)); FillRect(data->backBufferDC, rect, back_brush); DeleteObject(back_brush); switch (color) { case CLR_GRAY: case CLR_WHITE: OldFg = SetTextColor( data->backBufferDC, nhcolor_to_RGB(CLR_BLACK)); break; default: OldFg = SetTextColor(data->backBufferDC, rgbcolor); } } else { OldFg = SetTextColor(data->backBufferDC, rgbcolor); } if (data->bUnicodeFont || SYMHANDLING(H_UTF8)) { winos_ascii_to_wide(wch, ch); if (wch[0] == 0x2591 || wch[0] == 0x2592) { int intensity = 80; HBRUSH brush = CreateSolidBrush(RGB(intensity, intensity, intensity)); FillRect(data->backBufferDC, rect, brush); DeleteObject(brush); intensity = (wch[0] == 0x2591 ? 1 : 2); brush = CreateSolidBrush(RGB( GetRValue(rgbcolor)*intensity/2, GetGValue(rgbcolor)*intensity/2, GetBValue(rgbcolor)*intensity/2)); RECT smallRect = {0}; smallRect.left = rect->left + 1; smallRect.top = rect->top + 1; smallRect.right = rect->right - 1; smallRect.bottom = rect->bottom - 1; FillRect(data->backBufferDC, &smallRect, brush); DeleteObject(brush); } else { SelectObject(data->backBufferDC, data->hMapFontUnicode); DrawTextW(data->backBufferDC, wch, -1, rect, DT_CENTER | DT_VCENTER | DT_NOPREFIX | DT_SINGLELINE); } } else { char ch8 = (char) ch; SelectObject(data->backBufferDC, data->hMapFont); DrawTextA(data->backBufferDC, &ch8, 1, rect, DT_CENTER | DT_VCENTER | DT_NOPREFIX | DT_SINGLELINE); } SetTextColor(data->backBufferDC, OldFg); } if (i == data->xCur && j == data->yCur && (data->cursorOn || !win32_cursorblink)) { int yCursor = (win32_cursorblink ? data->yBlinkCursor : data->yNoBlinkCursor); PatBlt(data->backBufferDC, rect->left, rect->bottom - yCursor, rect->right - rect->left, yCursor, DSTINVERT); } } static void setGlyph(PNHMapWindow data, int i, int j, const glyph_info *fg, const glyph_info *bg) { if ((data->map[i][j].glyph != fg->glyph) || (data->bkmap[i][j].glyph != bg->glyph) || data->map[i][j].ttychar != fg->ttychar || data->map[i][j].gm.sym.color != fg->gm.sym.color || data->map[i][j].gm.customcolor != fg->gm.customcolor || data->map[i][j].gm.glyphflags != fg->gm.glyphflags || data->map[i][j].gm.tileidx != fg->gm.tileidx) { data->map[i][j] = *fg; data->bkmap[i][j] = *bg; data->locDirty[i][j] = TRUE; data->mapDirty = TRUE; } } static void clearAll(PNHMapWindow data) { for (int x = 0; x < COLNO; x++) for (int y = 0; y < ROWNO; y++) { data->map[x][y] = nul_glyphinfo; data->bkmap[x][y] = nul_glyphinfo; data->locDirty[x][y] = TRUE; } data->mapDirty = TRUE; } static void dirtyAll(PNHMapWindow data) { for (int i = 0; i < COLNO; i++) for (int j = 0; j < ROWNO; j++) data->locDirty[i][j] = TRUE; data->mapDirty = TRUE; } static void dirty(PNHMapWindow data, int x, int y) { data->locDirty[x][y] = TRUE; data->mapDirty = TRUE; } static void paint(PNHMapWindow data, int i, int j) { RECT rect; rect.left = i * data->xBackTile; rect.top = j * data->yBackTile; rect.right = rect.left + data->xBackTile; rect.bottom = rect.top + data->yBackTile; if (data->bAsciiMode || Is_rogue_level(&u.uz)) { paintGlyph(data, i, j, &rect); } else { paintTile(data, i, j, &rect); } data->locDirty[i][j] = FALSE; } /* on WM_PAINT */ void onPaint(HWND hWnd) { PNHMapWindow data = (PNHMapWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA); PAINTSTRUCT ps; HDC hFrontBufferDC = BeginPaint(hWnd, &ps); /* stretch back buffer onto front buffer window */ int frontWidth = COLNO * data->xFrontTile; int frontHeight = ROWNO * data->yFrontTile; StretchBlt(hFrontBufferDC, data->map_orig.x - (data->xPos * data->xFrontTile), data->map_orig.y - (data->yPos * data->yFrontTile), frontWidth, frontHeight, data->backBufferDC, 0, 0, data->backWidth, data->backHeight, SRCCOPY); EndPaint(hWnd, &ps); } /* on WM_VSCROLL */ void onMSNH_VScroll(HWND hWnd, WPARAM wParam, LPARAM lParam) { PNHMapWindow data; SCROLLINFO si; int yNewPos; int yDelta; UNREFERENCED_PARAMETER(lParam); /* get window data */ data = (PNHMapWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA); switch (LOWORD(wParam)) { /* User clicked shaft left of the scroll box. */ case SB_PAGEUP: yNewPos = data->yPos - data->yPageSize; break; /* User clicked shaft right of the scroll box. */ case SB_PAGEDOWN: yNewPos = data->yPos + data->yPageSize; break; /* User clicked the left arrow. */ case SB_LINEUP: yNewPos = data->yPos - 1; break; /* User clicked the right arrow. */ case SB_LINEDOWN: yNewPos = data->yPos + 1; break; /* User dragged the scroll box. */ case SB_THUMBTRACK: yNewPos = HIWORD(wParam); break; default: yNewPos = data->yPos; } yNewPos = max(0, min(data->yMax, yNewPos)); if (yNewPos == data->yPos) return; yDelta = yNewPos - data->yPos; data->yPos = yNewPos; ScrollWindowEx(hWnd, 0, -data->yFrontTile * yDelta, (CONST RECT *) NULL, (CONST RECT *) NULL, (HRGN) NULL, (LPRECT) NULL, SW_INVALIDATE | SW_ERASE); si.cbSize = sizeof(si); si.fMask = SIF_POS; si.nPos = data->yPos; SetScrollInfo(hWnd, SB_VERT, &si, TRUE); } /* on WM_HSCROLL */ void onMSNH_HScroll(HWND hWnd, WPARAM wParam, LPARAM lParam) { PNHMapWindow data; SCROLLINFO si; int xNewPos; int xDelta; UNREFERENCED_PARAMETER(lParam); /* get window data */ data = (PNHMapWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA); switch (LOWORD(wParam)) { /* User clicked shaft left of the scroll box. */ case SB_PAGEUP: xNewPos = data->xPos - data->xPageSize; break; /* User clicked shaft right of the scroll box. */ case SB_PAGEDOWN: xNewPos = data->xPos + data->xPageSize; break; /* User clicked the left arrow. */ case SB_LINEUP: xNewPos = data->xPos - 1; break; /* User clicked the right arrow. */ case SB_LINEDOWN: xNewPos = data->xPos + 1; break; /* User dragged the scroll box. */ case SB_THUMBTRACK: xNewPos = HIWORD(wParam); break; default: xNewPos = data->xPos; } xNewPos = max(0, min(data->xMax, xNewPos)); if (xNewPos == data->xPos) return; xDelta = xNewPos - data->xPos; data->xPos = xNewPos; ScrollWindowEx(hWnd, -data->xFrontTile * xDelta, 0, (CONST RECT *) NULL, (CONST RECT *) NULL, (HRGN) NULL, (LPRECT) NULL, SW_INVALIDATE | SW_ERASE); si.cbSize = sizeof(si); si.fMask = SIF_POS; si.nPos = data->xPos; SetScrollInfo(hWnd, SB_HORZ, &si, TRUE); } /* map nethack map coordinates to the screen location */ void nhcoord2display(PNHMapWindow data, int x, int y, LPRECT lpOut) { lpOut->left = (x - data->xPos) * data->xFrontTile + data->map_orig.x; lpOut->top = (y - data->yPos) * data->yFrontTile + data->map_orig.y; lpOut->right = lpOut->left + data->xFrontTile; lpOut->bottom = lpOut->top + data->yFrontTile; } #if (VERSION_MAJOR < 4) && (VERSION_MINOR < 4) && (PATCHLEVEL < 2) /* map glyph to character/color combination */ void nhglyph2charcolor(short g, uchar *ch, int *color) { int offset; #define zap_color(n) *color = iflags.use_color ? zapcolors[n] : NO_COLOR #define cmap_color(n) *color = iflags.use_color ? defsyms[n].color : NO_COLOR #define obj_color(n) \ *color = iflags.use_color ? objects[n].oc_color : NO_COLOR #define mon_color(n) *color = iflags.use_color ? mons[n].mcolor : NO_COLOR #define pet_color(n) *color = iflags.use_color ? mons[n].mcolor : NO_COLOR #define warn_color(n) \ *color = iflags.use_color ? def_warnsyms[n].color : NO_COLOR if ((offset = (g - GLYPH_WARNING_OFF)) >= 0) { /* a warning flash */ *ch = showsyms[offset + SYM_OFF_W]; warn_color(offset); } else if ((offset = (g - GLYPH_SWALLOW_OFF)) >= 0) { /* swallow */ /* see swallow_to_glyph() in display.c */ *ch = (uchar) showsyms[(S_sw_tl + (offset & 0x7)) + SYM_OFF_P]; mon_color(offset >> 3); } else if ((offset = (g - GLYPH_ZAP_OFF)) >= 0) { /* zap beam */ /* see zapdir_to_glyph() in display.c */ *ch = showsyms[(S_vbeam + (offset & 0x3)) + SYM_OFF_P]; zap_color((offset >> 2)); } else if ((offset = (g - GLYPH_CMAP_OFF)) >= 0) { /* cmap */ *ch = showsyms[offset + SYM_OFF_P]; cmap_color(offset); } else if ((offset = (g - GLYPH_OBJ_OFF)) >= 0) { /* object */ *ch = showsyms[(int) objects[offset].oc_class + SYM_OFF_O]; obj_color(offset); } else if ((offset = (g - GLYPH_BODY_OFF)) >= 0) { /* a corpse */ *ch = showsyms[(int) objects[CORPSE].oc_class + SYM_OFF_O]; mon_color(offset); } else if ((offset = (g - GLYPH_PET_OFF)) >= 0) { /* a pet */ *ch = showsyms[(int) mons[offset].mlet + SYM_OFF_M]; pet_color(offset); } else { /* a monster */ *ch = showsyms[(int) mons[g].mlet + SYM_OFF_M]; mon_color(g); } // end of wintty code } #endif /* map nethack color to RGB */ COLORREF nhcolor_to_RGB(int c) { if (c >= 0 && c < CLR_MAX) return GetNHApp()->regMapColors[c]; return RGB(0x00, 0x00, 0x00); }