// MoonVideo // // Get the video output technology of yesterday - today! // I'll throw in keyboard handling and random numbers as well... // // Changes: // 12 Aug 2006 - Console output (mode 0) working. // 14 Aug 2006 - Window Spawning and TextOut working (mode 1). // 15 Aug 2006 - Drew a 10x20 monochrome font. // 16 Aug 2006 - Put in BMP loading code for any bitdepth, mode 2 working. // 18 Aug 2006 - Unified graphic modes into mode 1, dynamically combines // fonts and tiles now. // 19 Aug 2006 - Can run plain font-generated tiles too; fallback works. // Made a 12x20 monochrome font. // 20 Aug 2006 - Blinking cursor works in graphics mode. Made vampiric font. // 06 Oct 2006 - Fixed keypress detection routine a bit. // 07 Jan 2007 - Fixed graphical mode extended key reading bug. // 19 Mar 2007 - Eliminated visible mouse cursor in fullscreen console when // using >25 rows, added cursor visibility restoration at quit // 20 Apr 2007 - Added mv_Window[].updatebegin and .updateend to only redraw // rows with changes; modularized the BMP loader and wrote // mv_ScaleBMP and mv_Render* for output of bitmaps, along with // related palette dithering stuff. // 29 Apr 2007 - Refactored the BMP loader for efficiency, adjusted BMP // resource modularization, reducing horrible random hangs. // 03 May 2007 - Added proper window destruction at graphic mode exit. // 05 May 2007 - Added ALT + anykey detection under graphical mode, at last. // 07 May 2007 - Replaced PChar variables with plain strings out of paranoia. // Also created mv_Rest to replace the Sleep function. // 26 Aug 2007 - Moved some extra core functions into MoonVideo: // FreeP, RND, Dice; windows are now displayed through // mv_DrawWindow, no more need for mv_Window[].updatebegin/end. // 16 Sep 2007 - Finally fixed the random crash bug!!! Forgot to multiply // a getmem amount by 3. Const'd palette defaults. Made // mv_DrawWindow accept more efficient, non-full row updates. // 01 Feb 2008 - Fixed the palette size at 32 primary colors in graphical // mode. The extra space was used to add a lightness variable // for graphical mode. Added the ColorX table and mv_BlankCell. // Changed color match calculations from YUV to YCbCr space, // a marginal improvement. Added some more comments in code. // Hacked past idiotic Win32 screen bitmap alignment demand // that caused some window sizes to look glitched. Slight // optimization in tiledrawing. Fixed a blinking cursor ghost. // 07 Feb 2008 - Fixed lenx(). // 12 Jun 2008 - Added mv_CloseWindows for convenient closure. // 16 Jul 2008 - Rewrote tile storage and the assembly drawing routine. // 25% speed increase in mv_DrawWindows, and tile storage uses // a bit less memory now. // 19 Aug 2008 - Added basic mouse support for all display modes, and added // a new mode, 2: tiled fullscreen. Console resizing improved. // Added a callback option for ReadKey, so a program can // process animations or mouseovers while waiting. // 21 Nov 2008 - Forgot to use the 32-bit versions of assembly PUSHA/POPA // commands, fixed into PUSHAD/POPAD. // TODO: Emulate lightness and dynamic palette in 16-color consoles. // Make graphical fullscreen switchable like SuperSakura already does. // // Check for updates at my site, Bunnyworks. // CC0, 2013 :: Kirinn Bunnylin / MoonCore // This program and the included font bitmaps are all free for anything ever! {$packrecords 1} // save some memory for those large array/records {$asmmode intel} type // these two are for easy array-based access of pointer memory lonkero = array[0..$FFFFFF] of dword; bytearray = array[0..$FFFFFF] of byte; mv_bmp = record // graphical bitmap in memory, 3 bytes per pixel size_x, size_y : word; data : pointer; end; // The palette RGB values are initialized to basic EGA colors, and sixteen // other pretty ones. The blue component is in the low end of the dword. // Logical windows have a pointer that can point to this or a different // palette, for window-specific adjustments. const mv_Pal : array[0..31] of dword = ( $00000000, $000000A0, $0000A700, $0000A7A0, $00A00000, $00A000A0, $00A05000, $00A0A7A0, $00505050, $005050FF, $0050F850, $0050F8FF, $00FF5050, $00FF50FF, $00FFF850, $00FFFFFF, $00CAA97B, $00FF9C12, $00F9EA48, $00E5E8F7, $0046C00B, $0064EADC, $00000000, $00000000, $00000000, $00000000, $00000000, $00000000, $00000000, $00000000, $00000000, $00000000); mv_PalName : array[0..31] of string[32] = ( 'black', 'dark blue', 'green', 'cyan', 'crimson', 'violet', 'brown', 'dust gray', 'dark gray', 'blue', 'light green', 'aquamarine', 'red', 'pink', 'yellow', 'white', 'tan', 'orange', 'gold', 'silver', 'grass green','turquoise','','', '','','','', '','','',''); // The ColorX table can be used to easily convert a number 0..31 into the // corresponding symbol to use with mWrite, to choose a foreground color. mv_ColorX : array[0..31] of char = ( '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H', 'I','J','K','L','M','N','O','P','Q','R','S','T','U','V'); var mv_ConOutH, mv_ConInH : handle; // for win32 console IO mv_HWindow : hwnd; // win32 graphical window handle mv_DC, mv_OutputBuffyDC : hdc; mv_OutputBuffyHandle, mv_OldOutputBuffyHandle : hbitmap; mv_ScreenSets : devmode; // for switching resolution etc for fullscreen mv_aMessage : msg; // win32 message loop variable mv_CtrlDown : byte; mv_ConInputRecord : tinputrecord; mv_ConBuffyInfo : console_screen_buffer_info; mv_OldOutMode, mv_OldInMode, mv_ConEvents : dword; mv_NewConSize, mv_WriteSize: coord; mv_NewWinSize : small_rect; mv_WinSizeX, mv_WinSizeY : word; // pixel size of graphical window mv_OutputBuffy : pointer; mv_OutputMode : byte; // 0 = console, 1 = graphical, 2 = 1 in fullscreen mv_BlockSize : byte; // each symbol+color takes this many bytes, = 4 mv_BlankCell : dword; // the default clear symbol+color mv_OutputSizeCols, mv_OutputSizeRows : byte; mv_TileSizeX, mv_TileSizeY : byte; mv_TileName : string; mv_CursorX, mv_CursorY, mv_CursorLuma, // lightness of blinking cursor in graphical mode mv_CursorSize: byte; mv_WaitCallback : pointer; // this proc is called at 40Hz while waiting mv_MouseProc : pointer; // mouse messages are forwarded here mv_MouseX, mv_MouseY : word; mv_MouseEars : byte; mv_EndProgram : boolean; // set to TRUE when terminating mv_ProgramName : string[63]; // add chr(0) at end mv_InputBuffy : array[0..16] of word; // keyboard input is fed here // YCC representation of the palette, calculated at init; // This is used to make some color transformations more credible. mv_PalYCC : array[0..31] of record Y, Cb, Cr : longint; end; mv_NumTiles : word; mv_TileBuffy : pointer; mv_TileLookup : array of dword; mv_Window : array[0..31] of record // the user must get&free sizex*sizey*4 bytes for the buffy // see mWrite() for notes on how video data is stored here buffy : pointer; palette : pointer; locx, locy : byte; sizex, sizey : byte; end; mv_NumWindows : byte; // range 0 to 32 // -------------------------------------------------------------------------- procedure freep(poks : pointer); inline; // Use this to ensure pointers are unallocated logically. // I check for free pointers by asking if they are NIL. // The freemem() call doesn't clear the pointers on its own. // // Example: freep(@soon_free_pointer); begin freemem(pointer(poks^)); pointer(poks^) := NIL; end; function strdec(luku : dword) : string; // Takes a value and returns it in plain numbers in an ascii string var tempstr : string; begin tempstr := ''; while luku > 9 do begin tempstr := chr(luku mod 10 + 48) + tempstr; luku := luku div 10; end; strdec := chr(luku + 48) + tempstr; end; function strhex(luku : dword) : string; // Takes a value and returns it in hexadecimal in an ascii string var tempstr : string; begin tempstr := ''; while luku > 15 do begin if luku and 15 < 10 then tempstr := chr(luku and 15 + 48) + tempstr else tempstr := chr(luku and 15 + 55) + tempstr; luku := luku shr 4; end; if luku and 15 < 10 then strhex := chr(luku and 15 + 48) + tempstr else strhex := chr(luku and 15 + 55) + tempstr; end; function valx(luku : string) : dword; // Takes a string and returns any possible value it encounters at the start var tempvar : byte; begin valx := 0; tempvar := 1; if luku = '' then exit; while (tempvar <= length(luku)) and (not (ord(luku[tempvar]) in [48..57])) do inc(tempvar); if tempvar > length(luku) then exit; while (ord(luku[tempvar]) >= 48) and (ord(luku[tempvar]) <= 57) // 0..9 do begin valx := valx * 10 + ord(luku[tempvar]) - 48; inc(tempvar); if tempvar > length(luku) then break; end; end; function lenx(txt : string) : byte; // Returns the character length of the string, after stripping MWrite // command codes from it. var moo : byte; begin lenx := length(txt); moo := lenx; while moo > 0 do begin if txt[moo] = chr(255) then begin dec(lenx); case txt[moo + 1] of '0'..'9', 'A'..'V', 'a'..'h': dec(lenx); '*': dec(lenx, 3); end; end; dec(moo); end; end; // -------------------------------------------------------------------------- // This series of magic numbers is used to completely screw up a random // number seed of your choice. The shuffling routine, just below, uses some // mean assembly that will return different results if compiled on a platform // that uses different bit ordering... // The variable MagicHat is used to loop through the magic numbers, // one BYTE at a time. When initializing the rndseed, also reset magichat. const magicnumber : array[0..16] of dword = (184594917, 3519992270, 49374, 12648430, 89005, 53456, 2881145546, 88748, 45248, 53596, 14343440, 763164, 64222, 11651868, 23262, 261, 0); var magichat : dword; rndseed : dword; function rnd(limit : dword) : dword; assembler; stdcall; // Returns a pseudo-random number [0..limit-1]. // Rndseed is shuffled while at it. // If mr. smartypants asks for rnd(0) or rnd(1), he gets a 0. // The magic hat spawns different numbers depending on platform's bit order! asm push esi; push ebx; push edx mov ebx, rndseed // EBX <-- seed mov eax, limit // EAX <-- limit mul ebx // EDX:EAX <-- seed * limit push edx // save the result sub ebx, eax; sub ebx, edx // mess with the seed... lea esi, magicnumber mov eax, magichat; inc eax; and eax, 63; mov magichat, eax add esi, eax; mov eax, [esi]; xor ebx, eax; mul ebx ror ebx, 13; add eax, edx; xor ebx, eax mov rndseed, ebx // ... and store the seed pop eax // restore the result pop edx; pop ebx; pop esi end; function dice(numdice, diesides : dword) : dword; assembler; stdcall; // Returns a pseudo-random die roll (numdice d diesides). // Quantum dice with 1 or 0 sides will always roll a natural 1. // The seed is shuffled while at it. asm push esi; push edi; push ebx; push ecx; push edx mov ebx, rndseed // EBX <-- seed mov ecx, numdice // ECX <-- numdice mov edi, ecx // EDI <-- 0 + numdice @bones: mov eax, diesides // EAX <-- diesides mul ebx // EDX:EAX <-- seed * limit add edi, edx // add up the result sub ebx, eax; add ebx, edx; rol ebx, 11 loop @bones lea esi, magicnumber // mess with the seed... mov eax, magichat; inc eax; and eax, 63; mov magichat, eax add esi, eax; mov eax, [esi]; xor ebx, eax; imul ebx ror ebx, 13; sub eax, edx; xor ebx, eax mov rndseed, ebx // ... and store the seed mov eax, edi // EAX <-- the sum pop edx; pop ecx; pop ebx; pop edi; pop esi end; // -------------------------------------------------------------------------- procedure mv_LoadBMP(namu : string; poku : pointer); // Loads the bitmap from "namu" into mv_BMP(poku^). var mv_Filu : file; palu : array[0..255] of record b, g, r, junk : byte; end; mv_i, mv_j, mv_k, mv_l, mv_m, mv_n, bm_ofs : dword; bm_buf : pointer; bmfh : record bfType : word; bfSize : dword; r1, r2 : word; bfOffBits : dword; end; bmih : record biSize : dword; biWidth, biHeight : longint; biPlanes, biBitCount : word; biComp, biSizeImage : dword; biX, biY : longint; biClrUsed, biClrImp : dword; end; begin if (namu = '') or (poku = NIL) then exit; assign(mv_Filu, namu); {$I-} reset(mv_Filu, 1); if IOresult <> 0 then begin close(mv_Filu); exit; end; // File doesn't exist or other IO error getmem(bm_buf, filesize(mv_Filu)); blockread(mv_Filu, bm_buf^, filesize(mv_Filu)); close(mv_Filu); move(bm_buf^, bmfh, 14); bm_ofs := 14; if bmfh.bfType <> 19778 then begin freep(@bm_buf); exit; end; // Not a bitmap file? move(pointer(dword(bm_buf) + bm_ofs)^, bmih, 40); inc(bm_ofs, 40); if bmih.bicomp <> 0 then begin freep(@bm_buf); exit; end; // Compressed bitmap??? mv_BMP(poku^).size_x := bmih.biwidth; mv_BMP(poku^).size_y := bmih.biheight; getmem(mv_BMP(poku^).data, bmih.biwidth * bmih.biheight * 3); if bmih.bibitcount < 16 then begin mv_i := (1 shl bmih.bibitcount) * 4; move(pointer(dword(bm_buf) + bm_ofs)^, palu, mv_i); inc(bm_ofs, mv_i); end; mv_i := bmih.biheight; // vertical loop var mv_j := 0; // source offset mv_k := (bmih.biheight - 1) * bmih.biwidth * 3; // dest offset case bmih.bibitcount of 1: begin mv_m := 0; mv_n := 0; while mv_i > 0 do begin for mv_l := 0 to bmih.biwidth - 1 do begin if mv_n = 0 then begin mv_n := 8; mv_m := byte(pointer(dword(bm_buf) + bm_ofs + mv_j)^); inc(mv_j); end; dec(mv_n); move(palu[(mv_m shr mv_n) and 1], pointer(dword(mv_BMP(poku^).data) + mv_k)^, 3); inc(mv_k, 3); end; if mv_j and 3 <> 0 then inc(mv_j, 4 - (mv_j and 3)); mv_n := 0; dec(mv_i); if mv_i > 0 then dec(mv_k, bmih.biwidth * 6); end; end; 4: begin mv_m := 0; mv_n := 0; while mv_i > 0 do begin for mv_l := 0 to bmih.biwidth - 1 do begin if mv_n = 0 then begin mv_n := 8; mv_m := byte(pointer(dword(bm_buf) + bm_ofs + mv_j)^); inc(mv_j); end; dec(mv_n, 4); move(palu[(mv_m shr mv_n) and $F], pointer(dword(mv_BMP(poku^).data) + mv_k)^, 3); inc(mv_k, 3); end; if mv_j and 3 <> 0 then inc(mv_j, 4 - (mv_j and 3)); mv_n := 0; dec(mv_i); if mv_i > 0 then dec(mv_k, bmih.biwidth * 6); end; end; 8: while mv_i > 0 do begin for mv_l := 0 to bmih.biwidth - 1 do begin move(palu[byte(pointer(dword(bm_buf) + bm_ofs + mv_j)^)], pointer(dword(mv_BMP(poku^).data) + mv_k)^, 3); inc(mv_k, 3); inc(mv_j); end; if mv_j and 3 <> 0 then inc(mv_j, 4 - (mv_j and 3)); dec(mv_i); if mv_i > 0 then dec(mv_k, bmih.biwidth * 6); end; 24: while mv_i > 0 do begin move(pointer(dword(bm_buf) + bm_ofs + mv_j)^, pointer(dword(mv_BMP(poku^).data) + mv_k)^, bmih.biwidth * 3); inc(mv_j, abs(bmih.biwidth) * 3); if mv_j and 3 <> 0 then inc(mv_j, 4 - (mv_j and 3)); dec(mv_i); if mv_i > 0 then dec(mv_k, bmih.biwidth * 3); end; else begin freep(@bm_buf); exit; end; // Nonstandard bit depth! end; freep(@bm_buf); end; procedure mv_LoadTiles; var mv_tp : pointer; mv_i, mv_j, mv_m, mv_l, mv_k : dword; mv_n, mv_o : byte; begin getmem(mv_tp, sizeof(mv_BMP)); mv_BMP(mv_tp^).data := NIL; mv_LoadBMP(mv_TileName, mv_tp); if mv_BMP(mv_tp^).data <> NIL then begin mv_NumTiles := (mv_BMP(mv_tp^).size_x div mv_TileSizeX) * (mv_BMP(mv_tp^).size_y div mv_TileSizeY); getmem(mv_TileBuffy, mv_TileSizeX * mv_TileSizeY * mv_NumTiles + 1); setlength(mv_TileLookup, mv_NumTiles); // Now process the tiles into our TileBuffy^ ! mv_i := 0; mv_m := 0; while mv_i < mv_NumTiles do begin mv_TileLookup[mv_i] := mv_m; // save offset from beginning to this tile mv_j := mv_TileSizeX * word(mv_i mod word(mv_BMP(mv_tp^).size_x div mv_TileSizeX)) + mv_BMP(mv_tp^).size_x * mv_TileSizeY * dword(mv_i div word(mv_BMP(mv_tp^).size_x div mv_TileSizeX)); mv_o := 0; for mv_l := 0 to mv_TileSizeY * mv_TileSizeX - 1 do begin mv_k := mv_j * 3; if bytearray(mv_BMP(mv_tp^).data^)[mv_k] or bytearray(mv_BMP(mv_tp^).data^)[mv_k + 1] or bytearray(mv_BMP(mv_tp^).data^)[mv_k + 2] <> 0 then mv_n := 1 else mv_n := 0; if mv_o = 0 then mv_o := (mv_n shl 7) or 1 else if (mv_o shr 7 <> mv_n) or (mv_o and 63 = 62) then begin bytearray(mv_TileBuffy^)[mv_m] := mv_o; inc(mv_m); mv_o := (mv_n shl 7) or 1; end else inc(mv_o); if (mv_l + 1) mod mv_TileSizeX = 0 then begin inc(mv_j, mv_BMP(mv_tp^).size_x - mv_TileSizeX); bytearray(mv_TileBuffy^)[mv_m] := mv_o or $40; inc(mv_m); mv_o := 0; end; inc(mv_j); end; bytearray(mv_TileBuffy^)[mv_m - 1] := bytearray(mv_TileBuffy^)[mv_m - 1] and $BF; bytearray(mv_TileBuffy^)[mv_m] := $FF; inc(mv_m); inc(mv_i); end; freep(@mv_BMP(mv_tp^).data); end; freep(@mv_tp); end; // -------------------------------------------------------------------------- procedure mv_DrawWindow(winu : byte; x1, y1, x2, y2 : byte); // Copies the zero-based rectangle (x1, y1) - (x2, y2) of logical window // number "winu" into the output buffer. X1 must be <= X2, Y1 <= Y2. // If X2 or Y2 exceed the bounds of the window, they are clipped to the // maximum coordinates; use 0, 0, $FF, $FF for a full update. var mv_i, mv_k : word; mv_x, mv_y : byte; mv_j : word; mv_xx, mv_yy, ofsuti, mv_c, mv_b : dword; strutsi : string; rr : rect; begin if winu >= mv_NumWindows then exit; if mv_Window[winu].Buffy = NIL then exit; if x2 >= mv_Window[winu].sizeX then x2 := mv_Window[winu].sizeX - 1; if y2 >= mv_Window[winu].sizeY then y2 := mv_Window[winu].sizeY - 1; if x1 > x2 then x1 := x2; if y1 > y2 then y1 := y2; case mv_OutputMode of // Console mode 0: begin mv_WriteSize.x := mv_Window[winu].SizeX; mv_WriteSize.y := mv_Window[winu].SizeY; mv_NewConsize.x := x1; mv_NewConsize.y := y1; mv_NewWinsize.top := mv_Window[winu].LocY + mv_NewConsize.y; mv_NewWinsize.left := mv_Window[winu].LocX + mv_NewConsize.x; mv_NewWinsize.bottom := mv_Window[winu].LocY + y2; mv_NewWinsize.right := mv_Window[winu].LocX + x2; writeConsoleOutput(mv_ConOutH, mv_Window[winu].Buffy, mv_WriteSize, mv_NewConSize, mv_NewWinSize); end; // Tile mode 1,2: begin // Ofsuti --> ASCII data source buffy // Mv_xx --> destination graphic buffy for mv_y := y1 to y2 do begin ofsuti := dword(mv_Window[winu].Buffy) + (mv_y * mv_Window[winu].SizeX + x1) * mv_BlockSize; mv_xx := dword(mv_OutputBuffy) + ( (mv_Window[winu].LocY + mv_y) * mv_TileSizeY * mv_WinSizeX + (mv_Window[winu].LocX + x1) * mv_TileSizeX ) * 3; for mv_x := x1 to x2 do begin // Update colors for this tile mv_c := word(pointer(ofsuti + 2)^); mv_j := mv_c shr 9; // grab the lightness at this point mv_c := (mv_c and $F) or ((mv_c shr 4) and $10); mv_b := (word(pointer(ofsuti + 2)^) shr 4) and $F; if mv_j <> $60 then // lightness modification needed if mv_j < $60 then begin // darken mv_c := ((lonkero(mv_Window[winu].palette^)[mv_c] shr 16) * mv_j div $60) shl 16 or ((lonkero(mv_Window[winu].palette^)[mv_c] shr 8 and $FF) * mv_j div $60) shl 8 or (lonkero(mv_Window[winu].palette^)[mv_c] and $FF) * mv_j div $60; if mv_b = 0 then mv_b := lonkero(mv_Window[winu].palette^)[mv_b] else begin mv_b := ((lonkero(mv_Window[winu].palette^)[mv_b] shr 16) * mv_j div $60) shl 16 or ((lonkero(mv_Window[winu].palette^)[mv_b] shr 8 and $FF) * mv_j div $60) shl 8 or (lonkero(mv_Window[winu].palette^)[mv_b] and $FF) * mv_j div $60; end; end else begin // lighten mv_i := (lonkero(mv_Window[winu].palette^)[mv_c] shr 16) * mv_j div $60; if mv_i > 255 then mv_i := 255; mv_k := (lonkero(mv_Window[winu].palette^)[mv_c] shr 8 and $FF) * mv_j div $60; if mv_k > 255 then mv_k := 255; mv_c := (lonkero(mv_Window[winu].palette^)[mv_c] and $FF) * mv_j div $60; if mv_c > 255 then mv_c := 255; mv_c := mv_c or (mv_k shl 8) or (mv_i shl 16); if mv_b = 0 then mv_b := lonkero(mv_Window[winu].palette^)[mv_b] else begin mv_i := (lonkero(mv_Window[winu].palette^)[mv_b] shr 16) * mv_j div $60; if mv_i > 255 then mv_i := 255; mv_k := (lonkero(mv_Window[winu].palette^)[mv_b] shr 8 and $FF) * mv_j div $60; if mv_k > 255 then mv_k := 255; mv_b := (lonkero(mv_Window[winu].palette^)[mv_b] and $FF) * mv_j div $60; if mv_b > 255 then mv_b := 255; mv_b := mv_b or (mv_k shl 8) or (mv_i shl 16); end; end else begin // no color modification needed mv_b := lonkero(mv_Window[winu].palette^)[mv_b]; mv_c := lonkero(mv_Window[winu].palette^)[mv_c]; end; mv_yy := (mv_WinSizeX - mv_TileSizeX) * 3; asm pushad // AX = ASCII character or tile number mov esi, ofsuti; and eax, 0; mov ax, [esi] // Fetch source: TileBuffy[ TileLookup[AX] ] mov esi, mv_TileBuffy mov edi, mv_TileLookup shl eax, 2 add edi, eax mov eax, [edi] add esi, eax // ESI <-- TileBuffy^ + offset of desired tile mov edi, mv_xx mov ecx, 0; mov eax, 0 @outerboing: lodsb // fetch next bytecode cmp al, $FF; jz @excape mov ebx, mv_b; mov edx, mv_c mov cl, al; shr cl, 7; dec ecx and ebx, ecx // if skipbit 1, clear bkg color xor ecx, $FFFFFFFF; and edx, ecx // if skipbit 0, clear forecolor or ebx, edx // EBX <-- color to be used mov cl, al; and ecx, $3F // CL <-- repeat count mov edx, ebx; shr ebx, 16 @innerboing: mov [edi], dx; mov [edi+2], bl; add edi, 3 loop @innerboing test al, $40; jz @outerboing add edi, mv_yy jmp @outerboing @excape: popad end; inc(mv_xx, mv_TileSizeX * 3); inc(ofsuti, 4); end; end; rr.top := (mv_Window[winu].LocY + y1) * mv_TileSizeY; rr.bottom := (mv_Window[winu].LocY + y2 + 1) * mv_TileSizeY; rr.left := (mv_Window[winu].LocX + x1) * mv_TileSizeX; rr.right := (mv_Window[winu].LocX + x2 + 1) * mv_TileSizeX; invalidaterect(mv_HWindow, rr, FALSE); end; end; end; // -------------------------------------------------------------------------- procedure mv_ScaleBMP(poku : pointer; tox, toy : word); // Resizes the BMP resource to tox:toy resolution. var processor : pointer; loopx, loopy : word; start, finish, source, target, a1, a2, a3, span : dword; begin if mv_BMP(poku^).data = NIL then exit; getmem(processor, mv_BMP(poku^).size_x * toy * 3); // Vertical squeeze/stretch for loopx := 0 to mv_BMP(poku^).size_x - 1 do begin start := 0; target := dword(processor) + dword(loopx * 3); for loopy := 0 to toy - 1 do begin finish := (loopy * 256 + 255) * mv_BMP(poku^).size_y div toy; span := finish - start; source := dword(mv_BMP(poku^).data) + ((start shr 8) * mv_BMP(poku^).size_x + loopx) * 3; if finish shr 8 = start shr 8 then begin // result within one source pixel byte(pointer(target)^) := byte(pointer(source)^); byte(pointer(target + 1)^) := byte(pointer(source + 1)^); byte(pointer(target + 2)^) := byte(pointer(source + 2)^); end else begin // result within more than one source pixel a1 := byte(pointer(source)^) * (256 - start and $FF); a2 := byte(pointer(source + 1)^) * (256 - start and $FF); a3 := byte(pointer(source + 2)^) * (256 - start and $FF); start := (start and $FFFFFF00) + 256; inc(source, mv_BMP(poku^).size_x * 3); while finish shr 8 > start shr 8 do begin inc(a1, byte(pointer(source)^) shl 8); inc(a2, byte(pointer(source + 1)^) shl 8); inc(a3, byte(pointer(source + 2)^) shl 8); inc(start, 256); inc(source, mv_BMP(poku^).size_x * 3); end; inc(a1, byte(pointer(source)^) * (finish and $FF)); inc(a2, byte(pointer(source + 1)^) * (finish and $FF)); inc(a3, byte(pointer(source + 2)^) * (finish and $FF)); byte(pointer(target)^) := a1 div span; byte(pointer(target + 1)^) := a2 div span; byte(pointer(target + 2)^) := a3 div span; end; start := finish + 1; inc(target, mv_BMP(poku^).size_x * 3); end; end; freep(@mv_BMP(poku^).data); getmem(mv_BMP(poku^).data, toy * tox * 3); // Horizontal squeeze/stretch target := dword(mv_BMP(poku^).data); for loopy := 0 to toy - 1 do begin start := 0; for loopx := 0 to tox - 1 do begin finish := (loopx * 256 + 255) * mv_BMP(poku^).size_x div tox; span := finish - start; source := dword(processor) + (start shr 8 + loopy * mv_BMP(poku^).size_x) * 3; if finish shr 8 = start shr 8 then begin // result within one source pixel byte(pointer(target)^) := byte(pointer(source)^); byte(pointer(target + 1)^) := byte(pointer(source + 1)^); byte(pointer(target + 2)^) := byte(pointer(source + 2)^); end else begin // result within more than one source pixel a1 := byte(pointer(source)^) * (256 - start and $FF); a2 := byte(pointer(source + 1)^) * (256 - start and $FF); a3 := byte(pointer(source + 2)^) * (256 - start and $FF); start := (start and $FFFFFF00) + 256; inc(source, 3); while finish shr 8 > start shr 8 do begin inc(a1, byte(pointer(source)^) shl 8); inc(a2, byte(pointer(source + 1)^) shl 8); inc(a3, byte(pointer(source + 2)^) shl 8); inc(start, 256); inc(source, 3); end; inc(a1, byte(pointer(source)^) * (finish and $FF)); inc(a2, byte(pointer(source + 1)^) * (finish and $FF)); inc(a3, byte(pointer(source + 2)^) * (finish and $FF)); byte(pointer(target)^) := a1 div span; byte(pointer(target + 1)^) := a2 div span; byte(pointer(target + 2)^) := a3 div span; end; start := finish + 1; inc(target, 3); end; end; mv_BMP(poku^).size_x := tox; mv_BMP(poku^).size_y := toy; freep(@processor); end; procedure mv_RenderAscii(poku : pointer; targetwindow : byte); // Prints a bitmap with ascii characters into a logical window. // The algorithm for determining dithering: // 1. Get a pixel // 2. Compare the pixel as YUV to each palette entry, // find the two perceptually closest ones // 3. Check if dithering the two gives a better match than one flat color var mv_sorsa, mv_desti, mv_size : dword; ron, gon, bon, diff1, diff2, newdiff : dword; first, second : byte; Y, U, V : longint; // actually U is Cb and V is Cr :) kala : byte; begin if targetwindow >= mv_NumWindows then exit; if (mv_BMP(poku^).size_x <> mv_Window[targetwindow].SizeX) or (mv_BMP(poku^).size_y <> mv_Window[targetwindow].SizeY) then // The bitmap does not fit the window exactly! Call ScaleBMP. mv_ScaleBMP(poku, mv_Window[targetwindow].SizeX, mv_Window[targetwindow].SizeY); mv_sorsa := dword(mv_BMP(poku^).data); mv_desti := dword(mv_Window[targetwindow].Buffy); mv_size := mv_Window[targetwindow].SizeX * mv_Window[targetwindow].SizeY; while mv_size > 0 do begin // Beginning of processing loop // get a source pixel // caching would go right here ron := byte(pointer(mv_sorsa + 2)^); gon := byte(pointer(mv_sorsa + 1)^); bon := byte(pointer(mv_sorsa)^); Y := dword(13933 * ron) + dword(46871 * gon) + dword(4732 * bon); U := ((bon shl 16 - Y) * 69) div 128; V := ((ron shl 16 - Y) * 81) div 128; first := 0; second := 0; diff1 := $FFFFFFFF; diff2 := $FFFFFFFF; // Find the two palette entries closest to this pixel's color for kala := 0 to 15 do begin ron := abs(Y - mv_PalYCC[kala].Y) shr 13; gon := abs(U - mv_PalYCC[kala].Cb) shr 14; bon := abs(V - mv_PalYCC[kala].Cr) shr 14; inc(bon, bon shr 2); newdiff := ron * ron + gon * gon + bon * bon; if newdiff < diff1 then begin if (kala < 8) or (first < 8) then begin diff2 := diff1; second := first; end; diff1 := newdiff; first := kala; end else if (newdiff < diff2) then if (kala < 8) or (first < 8) then begin diff2 := newdiff; second := kala; end; end; // Default to a solid block of the closest palette color dword(pointer(mv_desti)^) := $C0000000 or (first shl 16) or 219; // Make sure the background color is not intense if (second >= 8) or (first < 8) and (second < first) then begin kala := second; second := first; first := kala; end; // Check if 50% dither matches better ron := abs(Y - (mv_PalYCC[first].Y + mv_PalYCC[second].Y) div 2) shr 13; gon := abs(U - (mv_PalYCC[first].Cb + mv_PalYCC[second].Cb) div 2) shr 14; bon := abs(V - (mv_PalYCC[first].CR + mv_PalYCC[second].Cr) div 2) shr 14; inc(bon, bon shr 2); newdiff := ron * ron + gon * gon + bon * bon; if newdiff < diff1 then begin diff1 := newdiff; dword(pointer(mv_desti)^) := $C0000000 or (second shl 20) or (first shl 16) or 177; end; // Check 75% dither ron := abs(Y - (mv_PalYCC[first].Y * 3 + mv_PalYCC[second].Y) div 4) shr 13; gon := abs(U - (mv_PalYCC[first].Cb * 3 + mv_PalYCC[second].Cb) div 4) shr 14; bon := abs(V - (mv_PalYCC[first].Cr * 3 + mv_PalYCC[second].Cr) div 4) shr 14; inc(bon, bon shr 2); newdiff := ron * ron + gon * gon + bon * bon; if newdiff < diff1 then begin diff1 := newdiff; dword(pointer(mv_desti)^) := $C0000000 or (second shl 20) or (first shl 16) or 178; end; // Check 25% dither ron := abs(Y - (mv_PalYCC[first].Y + 3 * mv_PalYCC[second].Y) div 4) shr 13; gon := abs(U - (mv_PalYCC[first].Cb + 3 * mv_PalYCC[second].Cb) div 4) shr 14; bon := abs(V - (mv_PalYCC[first].Cr + 3 * mv_PalYCC[second].Cr) div 4) shr 14; inc(bon, bon shr 2); newdiff := ron * ron + gon * gon + bon * bon; if newdiff < diff1 then begin diff1 := newdiff; dword(pointer(mv_desti)^) := $C0000000 or (second shl 20) or (first shl 16) or 176; end; // End of processing loop inc(mv_sorsa, 3); inc(mv_desti, 4); dec(mv_size); end; mv_DrawWindow(targetwindow, 0, 0, $FF, $FF); end; procedure mv_RenderBMP(poku : pointer; towin : byte); // Copies a bitmap into a graphical window. var source, dest : dword; mv_y : word; rr : rect; begin if towin >= mv_NumWindows then exit; if mv_OutputMode = 0 then // graphics are not enabled... redirect. mv_RenderASCII(poku, towin); if (mv_BMP(poku^).size_x <> mv_Window[towin].SizeX * mv_TileSizeX) or (mv_BMP(poku^).size_y <> mv_Window[towin].SizeY * mv_TileSizeY) then // The bitmap does not fit the window exactly! Call ScaleBMP. mv_ScaleBMP(poku, mv_Window[towin].SizeX * mv_TileSizeX, mv_Window[towin].SizeY * mv_TileSizeY); source := dword(mv_BMP(poku^).data); dest := dword(mv_OutputBuffy) + (mv_Window[towin].LocX * mv_TileSizeX + (mv_Window[towin].LocY * mv_TileSizeY) * mv_WinSizeX) * 3; for mv_y := 0 to mv_BMP(poku^).size_y - 1 do begin move(pointer(source)^, pointer(dest)^, mv_Window[towin].SizeX * mv_TileSizeX * 3); inc(source, mv_BMP(poku^).size_x * 3); inc(dest, mv_WinSizeX * 3); end; rr.top := mv_Window[towin].LocY * mv_TileSizeY; rr.bottom := (mv_Window[towin].LocY + mv_Window[towin].SizeY) * mv_TileSizeY; rr.left := mv_Window[towin].LocX * mv_TileSizeX; rr.right := rr.left + word(mv_Window[towin].SizeX * mv_TileSizeX); invalidaterect(mv_HWindow, rr, FALSE); end; procedure mv_ShowPic(picname : string; towin, mode : byte); // Loads the BMP "picname", scales it to fit the target logical window, // then draws it out either using characters or pixels. // towin - target logical window, zero-based // mode - 0 means dithered block character rendition, 1 is pixel-based var mv_tp : pointer; begin if towin >= mv_NumWindows then exit; getmem(mv_tp, sizeof(mv_BMP)); mv_LoadBMP(picname, mv_tp); if mv_BMP(mv_tp^).data <> NIL then begin if (mode <> 0) and (mv_OutputMode = 0) then mode := 0; if mode = 0 then mv_RenderAscii(mv_tp, towin) else mv_RenderBMP(mv_tp, towin); freep(@mv_BMP(mv_tp^).data); end; freep(@mv_tp); end; // -------------------------------------------------------------------------- procedure mv_CloseWindows; // Politely frees all virtual window memory buffers and sets windows to 0. var mv_i : byte; begin for mv_i := 0 to 31 do if mv_Window[mv_i].Buffy <> NIL then freep(@mv_Window[mv_i].Buffy); mv_NumWindows := 0; end; procedure mv_ClearWindows; // Wipes the initialized virtual windows clean. var mv_i : dword; kortti : coord; begin // Clean the physical window to start with case mv_OutputMode of 0 : begin kortti.x := 0; kortti.y := 0; fillConsoleOutputCharacter(mv_ConOutH, ' ', mv_OutputSizeRows * mv_OutputSizeCols, kortti, mv_i); fillConsoleOutputAttribute(mv_ConOutH, 7, mv_OutputSizeRows * mv_OutputSizeCols, kortti, mv_i); end; 1,2 : begin fillbyte(mv_OutputBuffy^, mv_WinSizeX * mv_WinSizeY * 3, 0); invalidateRect(mv_HWindow, NIL, FALSE); end; end; if mv_NumWindows = 0 then exit; // Clean all defined logical windows for mv_i := 0 to mv_NumWindows - 1 do if mv_Window[mv_i].Buffy <> NIL then filldword(mv_Window[mv_i].Buffy^, mv_Window[mv_i].SizeX * mv_Window[mv_i].SizeY, mv_BlankCell); end; procedure mWrite (targetwindow : byte; targetx, targety : byte; data : string); // Prints formatted text within a target logical window. // Mainly intended for actual text output; if pseudo-graphical output is // needed, such as a roguelike's adventure view, use direct writes instead. // // Use character 255 followed by a special code for formatting. // Codes 0..9 and capital letters A..V change text color. // Small letters a..h change background color. // Asterisk * followed by a two-hex number 00..7F sets the lightness. // // The native win32 console mode memory format is: BCBCAAAA (nibbles) // where AAAA is the character, CC the foreground and BB the background color // Win9x consoles only support 256 characters, but I keep it as a word in // case someone wants to use lots of tiles in graphical mode. Win9x consoles // only support 16 colors. // // The MoonVideo video memory format is: LLLLLLLC BBBBCCCC AAAAAAAA AAAAAAAA // MoonVideo allows 32 color choices for the foreground, so the lowest bit of // the top byte is the highest bit of the foreground color. The 7 other bits // form the lightness value for smooth fades, which defaults to $60. var mv_attribute, mv_offset, mv_windowsize: dword; taku : byte; begin // Safety first if (targetwindow > mv_NumWindows) or (mv_Window[targetwindow].Buffy = NIL) or (targetx >= mv_Window[targetwindow].SizeX) or (targety >= mv_Window[targetwindow].SizeY) then exit; mv_attribute := mv_BlankCell and $FFFF0000; mv_offset := mv_Window[targetwindow].SizeX * targety + targetx; taku := 1; mv_windowsize := mv_Window[targetwindow].SizeX * mv_Window[targetwindow].SizeY; while (taku <= length(data)) and (mv_offset < mv_windowsize) do begin if data[taku] = chr($FF) then begin // handle special codes inc(taku); case data[taku] of // foreground color change '0'..'9': mv_attribute := (mv_attribute and $FEF00000) or ((ord(data[taku]) - 48) shl 16); 'A'..'F': mv_attribute := (mv_attribute and $FEF00000) or ((ord(data[taku]) - 55) shl 16); 'G'..'V': mv_attribute := (mv_attribute and $FEF00000) or ((ord(data[taku]) - 71) shl 16) or $1000000; // background color change 'a'..'h': mv_attribute := (mv_attribute and $FF0F0000) or ((ord(data[taku]) - 97) shl 20); // lightness change, requires two hexadecimals '*': if taku + 2 < length(data) then begin inc(taku); mv_attribute := (mv_attribute and $1FF0000) or ((ord(data[taku]) - 48) shl 29); inc(taku); if ord(data[taku]) < 65 then // it's a number mv_attribute := mv_attribute or ((ord(data[taku]) - 48) shl 25) else // it's a letter mv_attribute := mv_attribute or ((ord(data[taku]) - 55) shl 25); end; end; end else begin lonkero(mv_Window[targetwindow].Buffy^)[mv_offset] := mv_attribute or ord(data[taku]); inc(mv_offset); end; inc(taku); end; // Flush it to the screen // Just the drawn characters if all on same line, else entire affected lines dec(mv_Offset); mv_Attribute := targetx; mv_windowsize := mv_Offset mod mv_Window[targetwindow].SizeX; mv_Offset := mv_Offset div mv_Window[targetwindow].SizeX; if mv_Offset <> targety then begin mv_Attribute := 0; mv_windowsize := mv_Window[targetwindow].SizeX; end; mv_DrawWindow(targetwindow, mv_attribute, targety, mv_windowsize, mv_offset); end; // -------------------------------------------------------------------------- function mv_WindowProc (window : hwnd; amex : uint; wepu : wparam; lapu : lparam) : lresult; stdcall; // Processing function for win32 messages like keypresses or window updates var mv_PS : paintstruct; begin case amex of // Copy stuff to screen from our own buffer wm_Paint: begin mv_DC := beginPaint (window, @mv_PS); bitBlt (mv_DC, mv_PS.rcPaint.left, mv_PS.rcPaint.top, mv_PS.rcPaint.right - mv_PS.rcPaint.left + 1, mv_PS.rcPaint.bottom - mv_PS.rcPaint.top + 1, mv_OutputBuffyDC, mv_PS.rcPaint.left, mv_PS.rcPaint.top, SRCCOPY); endPaint (window, mv_PS); mv_WindowProc := 0; end; // Mouse messages - update the mouse variables and call whatever procedure // is pointed to by MouseProc. No arguments can be passed directly. // To use, create a procedure that acts on state changes in the mouse // variables, then set mv_MouseProc := @MouseFunction; wm_MouseMove: if mv_MouseProc <> NIL then begin mv_MouseX := (lapu and $FFFF) div mv_TileSizeX; mv_MouseY := (lapu shr 16) div mv_TileSizeY; asm call mv_MouseProc end; end; wm_LButtonDown: if mv_MouseProc <> NIL then begin mv_MouseEars := mv_MouseEars or 1; asm call mv_MouseProc end; end; wm_LButtonUp: if mv_MouseProc <> NIL then begin mv_MouseEars := (mv_MouseEars and $FE) or 2; asm call mv_MouseProc end; end; wm_RButtonDown: if mv_MouseProc <> NIL then begin mv_MouseEars := mv_MouseEars or 4; asm call mv_MouseProc end; end; wm_RButtonUp: if mv_MouseProc <> NIL then begin mv_MouseEars := (mv_MouseEars and $FB) or 8; asm call mv_MouseProc end; end; // Get keypresses and add them in our input buffer // Handle only special keys here wm_KeyDown: case wepu of 12,33..40,45,46,112..123: begin mv_WindowProc := 0; if mv_InputBuffy[0] < 16 then begin inc(mv_InputBuffy[0]); mv_InputBuffy[mv_InputBuffy[0]] := (300 + wepu) or (mv_CtrlDown shl 14); end; end; 17: begin mv_WindowProc := 0; mv_CtrlDown := 1; end; else mv_WindowProc := DefWindowProc (Window, AMex, Wepu, Lapu); end; wm_KeyUp: if wepu = 17 then begin mv_WindowProc := 0; mv_CtrlDown := 0; end else mv_WindowProc := DefWindowProc (Window, AMex, wepu, lapu); // Handle ALT // (alt-enter returns 33081, alt-F4 returns 33183) wm_SysKeyDown: begin case wepu of 12,13,33..40,45,46,112..123: begin mv_WindowProc := 0; if mv_InputBuffy[0] < 16 then begin inc(mv_InputBuffy[0]); mv_InputBuffy[mv_InputBuffy[0]] := (300 + wepu) or $8000; end; end; else mv_WindowProc := DefWindowProc(Window, AMex, wepu, lapu); end; end; wm_SysChar: begin if mv_InputBuffy[0] < 16 then begin inc(mv_InputBuffy[0]); mv_InputBuffy[mv_InputBuffy[0]] := byte(wepu) or $8000; end; mv_WindowProc := 0; end; // Handle all normal keys wm_Char: begin if mv_InputBuffy[0] < 16 then begin inc(mv_InputBuffy[0]); if (mv_CtrlDown <> 0) and (wepu < 27) then mv_InputBuffy[mv_InputBuffy[0]] := byte(wepu) or $4000 else mv_InputBuffy[mv_InputBuffy[0]] := byte(wepu); end; mv_WindowProc := 0; end; // If we are dealing with fullscreen, handle activation and deactivation wm_Activate: if mv_OutputMode = 2 then begin if wepu and $FFFF = wa_inactive then begin changeDisplaySettings(NIL, 0); ShowWindow(mv_HWindow, SW_MINIMIZE); end else begin changeDisplaySettings(mv_ScreenSets, CDS_FULLSCREEN); ShowWindow(mv_HWindow, SW_RESTORE); end; mv_WindowProc := 0; end; // Somebody desires our destruction! // wm_close could spawn a confirmation rather than ignoring the call... wm_Close: begin //destroyWindow(mv_HWindow); mv_WindowProc := 0; end; wm_Destroy: begin mv_EndProgram := TRUE; postQuitMessage(0); mv_WindowProc := 0; end; else mv_WindowProc := DefWindowProc (Window, AMex, wepu, lapu); end; end; // -------------------------------------------------------------------------- function mv_SpawnWindow : boolean; // In the tile-based output modes 1+2, this creates the graphical window. var windowclass : wndclass; flaguz, exflaguz : dword; bminfo : bitmapinfo; rr : rect; begin mv_SpawnWindow := FALSE; // Register our new window as a barebones nothing-special thing windowclass.style := CS_OWNDC; windowclass.lpfnwndproc := wndproc(@mv_WindowProc); windowclass.cbclsextra := 0; windowclass.cbwndextra := 0; windowclass.hinstance := system.maininstance; windowclass.hicon := loadicon(0, idi_application); windowclass.hcursor := loadcursor(0, idc_arrow); windowclass.hbrbackground := 0; windowclass.lpszmenuname := NIL; windowclass.lpszclassname := @mv_ProgramName[1]; if registerClass (windowclass) = 0 then begin messagebox(0, 'Failed to create a graphical window (registerClass).', NIL, mb_Ok); exit; end; // Figure out window client area in pixels mv_WinSizeX := mv_OutputSizeCols * mv_TileSizeX; mv_WinSizeY := mv_OutputSizeRows * mv_TileSizeY; // Hack to force all rows to align on a dword boundary, grrr if mv_WinSizeX and 3 <> 0 then inc(mv_WinSizeX, 4 - mv_WinSizeX and 3); // Attempt to go to fullscreen... // Only succeeds if the window size calculated above is a resolution that // the user's display can switch to. Otherwise should fail gracefully. if mv_OutputMode = 2 then begin fillbyte(mv_ScreenSets, 0, sizeof(mv_ScreenSets)); mv_ScreenSets.dmSize := sizeof(mv_ScreenSets); mv_ScreenSets.dmPelsWidth := mv_WinSizeX; mv_ScreenSets.dmPelsHeight := mv_WinSizeY; mv_ScreenSets.dmBitsPerPel := 24; mv_ScreenSets.dmFields := DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT; if changeDisplaySettings(mv_ScreenSets, CDS_FULLSCREEN) <> DISP_CHANGE_SUCCESSFUL then begin mv_Tilename := 'Could not change display to resolution ' + strdec(mv_WinSizeX) + 'x' + strdec(mv_WinSizeY) + 'x24' + chr(0); MessageBox(0, @mv_Tilename[1], NIL, mb_ok); exit; end; flaguz := WS_POPUP or WS_VISIBLE; exflaguz := 0; ShowCursor(FALSE); end else begin // Windowed mode gets all sorts of decorations flaguz := ws_caption or ws_minimizebox or ws_sysmenu or ws_border or ws_visible; exflaguz := ws_ex_windowedge; end; // Create the window! rr.top := 0; rr.left := 0; rr.right := mv_WinSizeX; rr.bottom := mv_WinSizeY; adjustWindowRectEx (@rr, flaguz, FALSE, exflaguz); mv_HWindow := CreateWindowEx (exflaguz, @mv_ProgramName[1], @mv_ProgramName[1], WS_CLIPSIBLINGS or WS_CLIPCHILDREN or flaguz, $8000, $8000, rr.right - rr.left, rr.bottom - rr.top, 0, 0, system.maininstance, NIL); if mv_HWindow = 0 then begin messagebox(0, 'Failed to create a graphical window (winCreate).', nil, mb_Ok); exit; end; // We want a 24-bit color bitmap to fill the client area, // and its contents will be read from our mv_OutputBuffy^. // By defining the DC here, there is no need to regenerate it every time // the system sends a WM_PAINT message; a simple BitBlt can be used to copy // from the outputbuffy "device context". with bminfo.bmiheader do begin bisize := sizeof(bminfo.bmiheader); biwidth := mv_WinSizeX; biheight := -mv_WinSizeY; bisizeimage := 0; biplanes := 1; bibitcount := 24; bicompression := bi_rgb; biclrused := 0; biclrimportant := 0; bixpelspermeter := 28000; biypelspermeter := 28000; end; dword(bminfo.bmicolors) := 0; mv_DC := getDC(mv_HWindow); mv_OutputBuffyDC := createCompatibleDC(mv_DC); mv_OutputBuffyHandle := createDIBsection(mv_OutputBuffyDC, bminfo, dib_rgb_colors, mv_OutputBuffy, 0, 0); mv_OldOutputBuffyHandle := selectObject(mv_OutputBuffyDC, mv_OutputBuffyHandle); // Make sure we start with a clean, black window fillbyte(mv_OutputBuffy^, mv_WinSizeX * mv_WinSizeY * 3, 0); // Just in case, make sure we are in the user's face SetForegroundWindow(mv_HWindow); SetFocus(mv_HWindow); // Get rid of init messages and give the window its first layer of paint while peekmessage(@mv_amessage, mv_HWindow, 0, 0, PM_REMOVE) do begin translatemessage(mv_amessage); dispatchmessage(mv_amessage); end; mv_SpawnWindow := TRUE; end; // -------------------------------------------------------------------------- procedure mv_GetEvents; begin case mv_OutputMode of 0: begin getNumberOfConsoleInputEvents(mv_ConInH, mv_ConEvents); while mv_ConEvents > 0 do begin readConsoleInput(mv_ConInH, mv_ConInputRecord, 1, mv_ConEvents); // console mouse (mouse_event = 2, can't specify it by name since // a WinNT function exists with that very name) // To make this work, you must separately do these // mv_MouseProc := @mymousefunction; // AND // setConsoleMode(mv_ConInH, enable_mouse_input); // after calling mv_Init! It's not done automatically, since the // mouse cursor is drawn by the system in fullscreen consoles and // that can be distracting. It also messes MoonVideo's mouse cursor... // I recommend using XOR 3F for the console mouse cursor's color. // I know of no way to detect whether a console is fullscreen. if (mv_MouseProc <> NIL) and (mv_ConInputRecord.eventtype and 2 = 2) then begin mv_MouseX := mv_ConInputrecord.event.mouseevent.dwMousePosition.x; mv_MouseY := mv_ConInputrecord.event.mouseevent.dwMousePosition.y; if (mv_MouseEars and 1 = 1) // left button down and (mv_ConInputrecord.event.mouseevent.dwButtonState and from_left_1st_button_pressed = 0) then mv_MouseEars := (mv_MouseEars and $FE) or 2; if mv_ConInputrecord.event.mouseevent.dwButtonState and from_left_1st_button_pressed <> 0 then mv_MouseEars := mv_MouseEars or 1; if (mv_MouseEars and 4 = 4) // right button down and (mv_ConInputrecord.event.mouseevent.dwButtonState and rightmost_button_pressed = 0) then mv_MouseEars := (mv_MouseEars and $FB) or 8; if mv_ConInputrecord.event.mouseevent.dwButtonState and rightmost_button_pressed <> 0 then mv_MouseEars := mv_MouseEars or 4; // Hack to make up for 40-column mode weirdness in win98 if mv_OutputSizeCols = 40 then mv_MouseX := mv_MouseX shr 1; asm call mv_MouseProc end; end; // console keyboard if (mv_ConInputRecord.eventtype and key_event = key_event) and (mv_ConInputRecord.event.keyevent.bkeydown) then if mv_InputBuffy[0] < 16 then begin // Was the keypress one of these: // 12 - Numpad 5 // 33..40 - Directional keys // 45, 46 - Insert or Delete // 112..123 - F1 through F12 if mv_ConInputRecord.event.keyevent.wvirtualkeycode in [12,33..40,45,46,112..123] then begin inc(mv_InputBuffy[0]); mv_InputBuffy[mv_InputBuffy[0]] := mv_ConInputRecord.event.keyevent.wvirtualkeycode + 300; // if necessary, check dwControlKeyState here and add to inputbuffy end else // Did the keypress translate to a proper ASCII character? if mv_ConInputRecord.event.keyevent.AsciiChar <> chr(0) then begin inc(mv_InputBuffy[0]); mv_InputBuffy[mv_InputBuffy[0]] := byte(mv_ConInputRecord.event.keyevent.AsciiChar); if mv_ConInputRecord.event.keyevent.dwControlKeyState and (LEFT_ALT_PRESSED or RIGHT_ALT_PRESSED) <> 0 then mv_InputBuffy[mv_InputBuffy[0]] := mv_InputBuffy[mv_InputBuffy[0]] or $8000 else if mv_ConInputRecord.event.keyevent.dwControlKeyState and (LEFT_CTRL_PRESSED or RIGHT_CTRL_PRESSED) <> 0 then mv_InputBuffy[mv_InputBuffy[0]] := mv_InputBuffy[mv_InputBuffy[0]] or $4000; end; // If neither was the case, it can go hang. ^_^ // (the keypress, not the program) end; getNumberOfConsoleInputEvents(mv_ConInH, mv_ConEvents); end; end; 1,2: while peekmessage(@mv_amessage, mv_HWindow, 0, 0, PM_REMOVE) do begin translatemessage(mv_amessage); dispatchmessage(mv_amessage); end; end; end; procedure mv_Rest(millis : dword); // Call this instead of Sleep(), which may rarely cause deadlocks. var ticktock : dword; hand : handle; begin if millis > 3000 then millis := 3000; // safeguard against wrap-around ticktock := GetTickCount; hand := GetCurrentProcess; repeat MsgWaitForMultipleObjects(1, hand, FALSE, millis, QS_ALLINPUT); mv_GetEvents; until (GetTickCount - ticktock > millis) or (mv_EndProgram); end; procedure mv_SetCursor(targetwindow, targetx, targety, sizey : byte); // Sizey is the desired cursor height; 0 to 100, with 0 being invisible. // If you need absolute positioning, you can set it in mv_CursorX and Y, // though you will need to update the console cursor position if applicable. // In graphical mode, you can affect the brightness of the cursor caret by // changing mv_CursorLuma, from 0 black to 255 white. var cci : console_Cursor_Info; begin if (mv_Window[targetwindow].Buffy = NIL) or (targetx >= mv_Window[targetwindow].SizeX) or (targety >= mv_Window[targetwindow].SizeY) then exit; mv_CursorX := mv_Window[targetwindow].LocX + targetx; mv_CursorY := mv_Window[targetwindow].LocY + targety; if sizey > 100 then sizey := 100; mv_CursorSize := (mv_TileSizeY * sizey) div 100; // In console mode, use the console's own cursor if mv_OutputMode = 0 then begin mv_WriteSize.x := mv_CursorX; mv_WriteSize.y := mv_CursorY; setConsoleCursorPosition(mv_ConOutH, mv_WriteSize); if sizey > 0 then cci.dwSize := sizey else cci.dwSize := 1; cci.bVisible := (sizey <> 0); setConsoleCursorInfo(mv_ConOutH, cci); end; // In graphical mode... we draw our own, but only in ReadKey end; procedure mv_Scroll (targetwindow : byte); // Scrolls the targetwindow up by one row. var mv_i : byte; begin if mv_Window[targetwindow].Buffy = NIL then exit; if mv_Window[targetwindow].SizeY >= 2 then for mv_i := 0 to mv_Window[targetwindow].SizeY - 2 do move((mv_Window[targetwindow].Buffy + (mv_i + 1) * mv_Window[targetwindow].SizeX * mv_BlockSize)^, (mv_Window[targetwindow].Buffy + mv_i * mv_Window[targetwindow].SizeX * mv_BlockSize)^, mv_Window[targetwindow].SizeX * mv_BlockSize); filldword((mv_Window[targetwindow].Buffy + mv_Window[targetwindow].SizeX * (mv_Window[targetwindow].SizeY - 1) * mv_BlockSize)^, mv_Window[targetwindow].SizeX, mv_BlankCell); mv_DrawWindow(targetwindow, 0, 0, $FF, $FF); mv_GetEvents; end; // -------------------------------------------------------------------------- function keypressed : boolean; begin mv_GetEvents; if mv_InputBuffy[0] > 0 then keypressed := TRUE else keypressed := FALSE; end; function ReadKey : word; var mv_i, blink : byte; crofs, crofx : dword; bkg : pointer; bkgrr : rect; begin case mv_OutputMode of 0: repeat if mv_InputBuffy[0] = 0 then if mv_WaitCallback = NIL then waitForSingleObject(mv_ConInH, infinite) else waitForSingleObject(mv_ConInH, 25); if mv_WaitCallback <> NIL then begin asm call mv_WaitCallback end; end; mv_GetEvents; if mv_EndProgram then begin readkey := 0; break; end; if mv_InputBuffy[0] > 0 then begin readkey := mv_InputBuffy[1]; for mv_i := 1 to 15 do mv_InputBuffy[mv_i] := mv_InputBuffy[mv_i + 1]; dec(mv_InputBuffy[0]); // Remove keypress from input buffer break; end; until FALSE; 1,2: begin if mv_CursorSize > 0 then begin getmem(bkg, mv_CursorSize * mv_TilesizeX * 3); blink := 0; bkgrr.top := word((mv_CursorY + 1) * mv_TileSizeY - mv_CursorSize); bkgrr.bottom := bkgrr.top + mv_CursorSize; bkgrr.left := mv_CursorX * mv_TileSizeX; bkgrr.right := bkgrr.left + mv_TileSizeX; crofs := dword(mv_OutputBuffy) + (mv_CursorX * mv_TileSizeX + word(bkgrr.top) * mv_WinSizeX) * 3; crofx := (mv_WinSizeX - mv_TileSizeX) * 3; asm pushad mov esi, crofs // source <-- mv_OutputBuffy^ [cursor corner pixel] mov edi, bkg // destination <-- BKG^ mov ecx, 0 mov bl, mv_CursorSize mov al, 3; mul mv_TileSizeX @hop1: mov cx, ax rep movsb add esi, crofx dec bl; jnz @hop1 popad end; end; repeat if mv_CursorSize > 0 then begin // Draw the blinking cursor if blink = 0 then begin asm pushad mov edi, crofs mov ecx, 0 mov bl, mv_CursorSize mov al, 3; mul mv_TileSizeX; mov dx, ax mov al, mv_CursorLuma @hop2: mov cx, dx rep stosb add edi, crofx dec bl; jnz @hop2 popad end; invalidateRect(mv_HWindow, bkgrr, FALSE); end else if blink = 8 then begin asm pushad mov esi, bkg mov edi, crofs mov ecx, 0 mov bl, mv_CursorSize mov al, 3; mul mv_TileSizeX @hop3: mov cx, ax rep movsb add edi, crofx dec bl; jnz @hop3 popad end; invalidateRect(mv_HWindow, bkgrr, FALSE); end; blink := (blink + 1) and 15; end; if mv_WaitCallback <> NIL then begin asm call mv_WaitCallback end; end; mv_GetEvents; if (mv_EndProgram) then begin readkey := 0; break; end; if mv_InputBuffy[0] > 0 then begin readkey := mv_InputBuffy[1]; for mv_i := 1 to 15 do mv_InputBuffy[mv_i] := mv_InputBuffy[mv_i + 1]; dec(mv_InputBuffy[0]); // Remove keypress from input buffer break; end; mv_Rest(25); until FALSE; if mv_CursorSize > 0 then begin asm pushad mov esi, bkg mov edi, crofs mov ecx, 0 mov bl, mv_CursorSize mov al, 3; mul mv_TileSizeX @hop3: mov cx, ax rep movsb add edi, crofx dec bl; jnz @hop3 popad end; invalidateRect(mv_HWindow, bkgrr, FALSE); freep(@bkg); end; end; end; end; // -------------------------------------------------------------------------- procedure mv_ResizeCon(sx, sy : byte); // Attempts to adjust the console size. Easier said than done on 9x. begin if mv_OutputMode <> 0 then exit; mv_NewConSize.x := sx; mv_NewConSize.y := sy; mv_NewWinSize.top := 0; mv_NewWinSize.left := 0; mv_NewWinSize.bottom := sy - 1; mv_NewWinSize.right := sx - 1; setConsoleScreenBufferSize(mv_ConOutH, mv_NewConSize); setConsoleWindowInfo(mv_ConOutH, TRUE, mv_NewWinSize); setConsoleScreenBufferSize(mv_ConOutH, mv_NewConSize); end; procedure mv_Init; var lich, zur, zug, zub: byte; begin mv_NumWindows := 0; mv_EndProgram := FALSE; mv_MouseProc := NIL; mv_WaitCallback := NIL; mv_BlockSize := 4; if mv_ProgramName = '' then mv_ProgramName := 'Give Me A Name ! ^_^'; if mv_ProgramName[length(mv_ProgramName)] <> chr(0) then mv_ProgramName := mv_ProgramName + chr(0); // Win95/98/ME consoles have arbitrary size limitations... I think the // NT-based ones allow all sorts of neat sizes, as does my tiled mode. // Word of warning: Full-screen mode only works properly on the // standard console sizes: 40/80 columns, 25/43/50 rows. if mv_OutputSizeCols < 40 then mv_OutputSizeCols := 40; if mv_OutputSizeRows < 16 then mv_OutputSizeRows := 16; case mv_OutputMode of // Console mode 0: begin mv_ConOutH := stdoutputhandle; mv_ConInH := stdinputhandle; getConsoleMode(mv_ConInH, @mv_OldInMode); getConsoleMode(mv_ConOutH, @mv_OldOutMode); getConsoleScreenBufferInfo(mv_ConOutH, @mv_ConBuffyInfo); // Disable extra OS controls - we will handle that stuff... personally setConsoleMode(mv_ConInH, 0); setConsolemode(mv_ConOutH, 0); // Resize console, if needed if (mv_ConBuffyInfo.dwSize.x <> mv_OutputSizeCols) or (mv_ConBuffyInfo.dwSize.y <> mv_OutputSizeRows) then mv_ResizeCon(mv_OutputSizeCols, mv_OutputSizeRows); // This may be a good occasion to check the console's output codepage. // Codepages available on the system are found in the registry under // HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\Codepage // and we should probably use 850 if available... test them! // Some foreign (cyrillic) systems use a different default codepage... // Then store the old codepage with GetConsoleOutputCP and set a new // one with SetConsoleOutputCP. // But how to use a custom palette in the console? It is possible to // alter the colors with a DOS program the old-school way - 3C8/3C9. // The possibly included COLORME.EXE will do this for you. // But FPC/win32 does not support output to ports, and Windows itself // claims the console does not support paletted operations... // Aha - an undocumented function for this exists: wm_setconsoleinfo. // Looks kinda complex to use though. // Set up a fancy title setConsoleTitle(@mv_ProgramName[1]); // Dump impatient keypresses that may have accumulated flushConsoleInputBuffer(mv_ConInH); end; // Graphical window, output using tiles and maybe font 1,2: begin // Very hard tile size limit; in the DrawWindow procedure under // this OutputMode I use some assembly with a mul by 3... so... if mv_TileSizeY > 85 then mv_TileSizeY := 85; if mv_TileSizeX = 0 then mv_TileSizeX := 1; if mv_TileSizeY = 0 then mv_TileSizeY := 1; // Load the tiles mv_LoadTiles; if (mv_TileBuffy = NIL) then begin mv_TileName := 'Could not load tiles from ' + upcase(mv_TileName) + '.' + chr(0); MessageBox(0, @mv_TileName[1], NIL, mb_ok); halt; end; // Set up the window if mv_SpawnWindow = FALSE then halt; // FreeConsole; end; else halt; end; // Initialize cursor mv_CursorX := 0; mv_CursorY := 0; mv_CursorSize := 0; mv_CursorLuma := $AA; mv_BlankCell := $C0070020; // Build a YCC palette for dithering purposes // This uses the ITU-R BT.709 standard for Kb and Kr constants... because // wikipedia says that's particularly nice for computer displays. // PalYCC uses longints. Fixed point calculations are [sign + 15.16] for lich := 0 to 31 do begin zur := mv_Pal[lich] shr 16; zug := (mv_Pal[lich] shr 8) and $FF; zub := mv_Pal[lich] and $FF; mv_PalYCC[lich].Y := dword(13933 * zur) + dword(46871 * zug) + dword(4732 * zub); mv_PalYCC[lich].Cb:= ((zub shl 16 - mv_PalYCC[lich].Y) * 69) div 128; mv_PalYCC[lich].Cr:= ((zur shl 16 - mv_PalYCC[lich].Y) * 81) div 128; mv_Window[lich].palette := @mv_Pal; // init pal ptrs while at it end; // Initialize the random number grue randomize; rndseed := random($FFFFFFFF); end; // -------------------------------------------------------------------------- procedure mv_Quit; var cci : console_cursor_info; begin case mv_OutputMode of // Closing down in console mode 0: begin // Place the cursor in the bottom left corner and visible mv_WriteSize.x := 0; mv_WriteSize.y := mv_ConBuffyInfo.dwSize.y - 1; setConsoleCursorPosition(mv_ConOutH, mv_WriteSize); cci.dwSize := 10; cci.bVisible := TRUE; setConsoleCursorInfo(mv_ConOutH, cci); mv_WriteSize.x := 0; mv_WriteSize.y := 0; fillConsoleOutputCharacter(mv_ConOutH, ' ', mv_OutputSizeRows * mv_OutputSizeCols, mv_WriteSize, mv_ConEvents); fillConsoleOutputAttribute(mv_ConOutH, 7, mv_OutputSizeRows * mv_OutputSizeCols, mv_WriteSize, mv_ConEvents); setConsoleMode(mv_ConInH,mv_OldInMode); setConsoleMode(mv_ConOutH,mv_OldOutMode); // Return original size if needed if (mv_ConBuffyInfo.dwSize.x <> mv_OutputSizeCols) or (mv_ConBuffyInfo.dwSize.y <> mv_OutputSizeRows) then mv_ResizeCon(mv_ConBuffyInfo.dwSize.x, mv_ConBuffyInfo.dwSize.y); end; // Closing down in graphical mode // (If in fullscreen, for whatever reason on my Win98 the program icon // remains in the taskbar after the window is gone, only to disappear when // clicked. However, all fullscreen applications do that to me, so it's // probably just 98 being eccentric.) 1,2: begin if mv_OutputMode = 2 then begin // remove fullscreen mode if on ChangeDisplaySettings(NIL, 0); ShowCursor(TRUE); end; destroyWindow(mv_HWindow); mv_GetEvents; selectObject (mv_OutputBuffyDC, mv_OldOutputBuffyHandle); deleteDC (mv_OutputBuffyDC); deleteObject (mv_OutputBuffyHandle); end; end; mv_CloseWindows; if mv_TileBuffy <> NIL then freep(@mv_TileBuffy); end; // -------------------------------------------------------------------------- {$ifdef bonk} procedure releaseall; // The exit procedure - this tries to end the program gracefully. // This is automatically called upon any kind of program termination, // as long as the program starts with addExitProc(@ReleaseAll). // Probably best copy this into your core program and add whatever other // memory cleaning is needed. begin // Error handling rests here if erroraddr <> NIL then case exitcode of 200: writeln('+++ Division by zero +++ don''t implode the universe +++'); 201: writeln('+++ Range check error +++'); 202: writeln('+++ Stack overflow error +++'); 204: writeln('+++ Null pointer dereferencing +++ is so much fun +++'); 215: writeln('+++ Arithmetic overflow error +++ but why? +++'); 216: writeln('+++ General protection fault +++ have a nice day :) +++'); else writeln('Crash! Runtime error ' + strdec(exitcode) + '!'); end; mv_Quit; end; {$endif}