/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "glk/frotz/processor.h" #include "glk/frotz/frotz.h" #include "glk/conf.h" #include "glk/events.h" namespace Glk { namespace Frotz { void Processor::screen_mssg_on() { Window &w = _wp.currWin(); if (w == _wp._lower) { w._oldStyle = w._currStyle; glk_set_style(style_Preformatted); glk_put_string("\n "); } } void Processor::screen_mssg_off() { Window &w = _wp.currWin(); if (w == _wp._lower) { glk_put_char('\n'); w.setStyle(0); w.setStyle(w._oldStyle); } } static const uint32 zchar_runes[] = { // This mapping is based on the Amiga font in the Z-Machine // specification, with some liberties taken. 0x16AA, // RUNIC LETTER AC A 0x16D2, // RUNIC LETTER BERKANAN BEORC BJARKAN B 0x16C7, // RUNIC LETTER IWAZ EOH 0x16D1, // RUNIC LETTER DAGAZ DAEG D 0x16D6, // RUNIC LETTER EHWAZ EH E 0x16A0, // RUNIC LETTER FEHU FEOH FE F 0x16B7, // RUNIC LETTER GEBO GYFU G 0x16BB, // RUNIC LETTER HAEGL H 0x16C1, // RUNIC LETTER ISAZ IS ISS I 0x16C4, // RUNIC LETTER GER 0x16E6, // RUNIC LETTER LONG-BRANCH-YR 0x16DA, // RUNIC LETTER LAUKAZ LAGU LOGR L 0x16D7, // RUNIC LETTER MANNAZ MAN M 0x16BE, // RUNIC LETTER NAUDIZ NYD NAUD N 0x16A9, // RUNIC LETTER OS O 0x16C8, // RUNIC LETTER PERTHO PEORTH P 0x16B3, // RUNIC LETTER CEN 0x16B1, // RUNIC LETTER RAIDO RAD REID R 0x16CB, // RUNIC LETTER SIGEL LONG-BRANCH-SOL S 0x16CF, // RUNIC LETTER TIWAZ TIR TYR T 0x16A2, // RUNIC LETTER URUZ UR U 0x16E0, // RUNIC LETTER EAR 0x16B9, // RUNIC LETTER WUNJO WYNN W 0x16C9, // RUNIC LETTER ALGIZ EOLHX 0x16A5, // RUNIC LETTER W 0x16DF // RUNIC LETTER OTHALAN ETHEL O }; uint32 Processor::zchar_to_unicode_rune(zchar c) { // There are only runic characters for a-z. Some versions of Beyond // Zork will render the conversation between Prince Foo and the black // rider in runic script, even though it contained upper case letters. // This produced an ugly mix of runes and map-drawing characters, etc. // which is probably why it was removed in later versions. // // Still, it's probably a good idea to convert the upper case letters // to lower case to get an appropriate rune. As far as I can tell, the // upper case letters are all used for drawing maps and progress bars. // I don't think they're ever intended for the lower window. // // Apart from the runes, the arrow glyphs could perhaps also be // sensibly converted to Unicode? if (c >= 'a' && c <= 'z') return zchar_runes[c - 'a']; else if (c >= 'A' && c <= 'Z') return zchar_runes[c - 'A']; else return 0; } void Processor::screen_char(zchar c) { Window &w = _wp.currWin(); w.ensureTextWindow(); if (gos_linepending && (w == gos_linewin)) { gos_cancel_pending_line(); if (_wp.currWin() == _wp._upper) { _wp._upper.setCursor(Point(1, _wp._upper[Y_CURSOR] + 1)); } if (c == '\n') return; } // check fixed flag in header, game can change it at whim int forcefix = ((h_flags & FIXED_FONT_FLAG) != 0); int curfix = ((w._currStyle & FIXED_WIDTH_STYLE) != 0); if (forcefix && !curfix) { w.setStyle(); fixforced = true; } else if (!forcefix && fixforced) { w.setStyle(); fixforced = false; } if (_wp._upper && _wp.currWin() == _wp._upper) { if (c == '\n' || c == ZC_RETURN) { glk_put_char('\n'); _wp._upper.setCursor(Point(1, _wp._upper[Y_CURSOR] + 1)); } else { int curx = _wp._upper[X_CURSOR], cury = _wp._upper[Y_CURSOR]; if (cury == 1) { if (curx <= (int)((sizeof statusline / sizeof(zchar)) - 1)) { statusline[curx - 1] = c; statusline[curx] = 0; } if (curx < h_screen_cols) { glk_put_char_uni(c); } else if (curx == h_screen_cols) { glk_put_char_uni(c); glk_window_move_cursor(_wp.currWin(), curx-1, cury-1); } else { smartstatusline(); } curx++; } else { if (curx < h_screen_cols) { glk_put_char_uni(c); } else if (curx == (h_screen_cols)) { glk_put_char_uni(c); glk_window_move_cursor(_wp.currWin(), curx-1, cury-1); } curx++; } } } else if (w == _wp._lower) { if (c == ZC_RETURN) glk_put_char('\n'); else { if (w._currFont == GRAPHICS_FONT) { uint32 runic_char = zchar_to_unicode_rune(c); if (runic_char != 0) { glk_set_style(style_User2); glk_put_char_uni(runic_char); glk_set_style(style_User1); } else glk_put_char_uni(c); } else glk_put_char_uni(c); } } } void Processor::screen_new_line() { screen_char('\n'); } void Processor::screen_word(const zchar *s) { zchar c; while ((c = *s++) != 0) { if (c == ZC_NEW_FONT) s++; else if (c == ZC_NEW_STYLE) s++; else screen_char(c); } } void Processor::erase_screen(zword win) { if ((short)win == -1) { if (_wp._upper) { _wp._upper.updateColors(); _wp._upper.clear(); } _wp._lower.clear(); split_window(0); _wp.setWindow(0); } } void Processor::erase_window(zword win) { if (h_version == V6 && win != _wp._cwin && h_interpreter_number != INTERP_AMIGA) _wp[win].updateColors(); _wp[win].clear(); if (h_version == V6 && win != _wp._cwin && h_interpreter_number != INTERP_AMIGA) _wp[_wp._cwin].updateColors(); } void Processor::z_buffer_mode() { // No implementation } void Processor::z_buffer_screen() { store(0); } void Processor::z_erase_line() { int i; flush_buffer(); if (_wp._upper && _wp.currWin() == _wp._upper) { int curx = _wp[_wp._cwin][X_CURSOR], cury = _wp[_wp._cwin][Y_CURSOR]; for (i = 0; i < h_screen_cols + 1 - curx; i++) glk_put_char(' '); _wp._upper.setCursor(Point(curx, cury)); } } void Processor::z_erase_window() { short w = (short)zargs[0]; flush_buffer(); if (w == -1 || w == -2) erase_screen(w); else erase_window(winarg0()); } void Processor::z_get_cursor() { zword y, x; flush_buffer(); x = _wp[_wp._cwin][X_CURSOR]; y = _wp[_wp._cwin][Y_CURSOR]; if (h_version != V6) { // convert to grid positions y = (y - 1) / h_font_height + 1; x = (x - 1) / h_font_width + 1; } storew((zword)(zargs[0] + 0), y); storew((zword)(zargs[0] + 2), x); } void Processor::z_print_table() { zword addr = zargs[0]; int curx = _wp[_wp._cwin][X_CURSOR], cury = _wp[_wp._cwin][Y_CURSOR]; zword xs = curx; int i, j; zbyte c; // Supply default arguments if (zargc < 3) zargs[2] = 1; if (zargc < 4) zargs[3] = 0; // Write text in width x height rectangle for (i = 0; i < zargs[2]; i++, curx = xs, cury++) { _wp[_wp._cwin].setCursor(Point(xs, cury)); for (j = 0; j < zargs[1]; j++) { LOW_BYTE(addr, c); addr++; print_char(c); } addr += zargs[3]; } } void Processor::z_set_true_colour() { int zfore = zargs[0]; int zback = zargs[1]; if (!(zfore < 0)) zfore = zRGB(zargs[0]); if (!(zback < 0)) zback = zRGB(zargs[1]); _wp[_wp._cwin].updateColors(zfore, zback); } void Processor::z_set_colour() { int fg = (short)zargs[0]; int bg = (short)zargs[1]; zword win = (h_version == V6) ? winarg2() : 0; if (win == 1 && h_version == V6) bg = zcolor_Transparent; flush_buffer(); if (fg == -1) // Get color at cursor fg = os_peek_color(); if (bg == -1) bg = zcolor_Transparent; if (fg == 0) // keep current colour fg = _wp[win][TRUE_FG_COLOR]; if (bg == 0) bg = _wp[win][TRUE_BG_COLOR]; if (fg == 1) fg = h_default_foreground; if (bg == 1) bg = h_default_background; if (fg >= 0 && fg < zcolor_NUMCOLORS) fg = zcolors[fg]; if (bg >= 0 && bg < zcolor_NUMCOLORS) bg = zcolors[bg]; if (h_version == V6 && h_interpreter_number == INTERP_AMIGA) { // Changing colours of window 0 affects the entire screen if (win == 0) { for (int i = 1; i < 8; ++i) { int bg2 = _wp[i][TRUE_BG_COLOR]; int fg2 = _wp[i][TRUE_FG_COLOR]; if (bg2 < 16) bg2 = (bg2 == (int)_wp[0][TRUE_BG_COLOR]) ? fg : bg; if (fg2 < 16) fg2 = (fg2 == (int)_wp[0][TRUE_FG_COLOR]) ? fg : bg; _wp[i][TRUE_FG_COLOR] = fg2; _wp[i][TRUE_BG_COLOR] = bg2; } } } _wp[win][TRUE_FG_COLOR] = fg; _wp[win][TRUE_BG_COLOR] = bg; if (win == _wp._cwin || h_version != V6) _wp.currWin().updateColors(fg, bg); } void Processor::z_set_font() { zword font = zargs[0]; store(_wp.currWin().setFont(font)); } void Processor::z_set_cursor() { int x = (int16)zargs[1], y = (int16)zargs[0]; int win = (h_version == V6) ? winarg2() : _wp._cwin; if (zargc < 3) zargs[2] = (zword)-3; flush_buffer(); _wp[win].setCursor(Point(x, y)); if (_wp.currWin() == _wp._upper && _wp[win][Y_CURSOR] > (uint)mach_status_ht) { mach_status_ht = _wp[win][Y_CURSOR]; reset_status_ht(); } } void Processor::z_set_text_style() { _wp[_wp._cwin].setStyle(zargs[0]); } void Processor::z_set_window() { _wp.setWindow(zargs[0]); if (_wp._cwin == 0) enable_scripting = true; else enable_scripting = false; zargs[0] = 0xf000; // tickle tickle! z_set_text_style(); } void Processor::pad_status_line(int column) { int curx = _wp._upper[X_CURSOR]; int spaces = (h_screen_cols + 1 - curx) - column; while (spaces-- > 0) print_char(' '); } void Processor::z_show_status() { zword global0; zword global1; zword global2; zword addr; bool brief = false; if (!_wp._upper) return; // One V5 game (Wishbringer Solid Gold) contains this opcode by accident, // so just return if the version number does not fit if (h_version >= V4) return; // Read all relevant global variables from the memory of the Z-machine // into local variables addr = h_globals; LOW_WORD(addr, global0); addr += 2; LOW_WORD(addr, global1); addr += 2; LOW_WORD(addr, global2); // Move to top of the status window, and print in reverse style. _wp.setWindow(1); _wp._upper.setReverseVideo(true); _wp._upper.setCursor(Point(1, 1)); // If the screen width is below 55 characters then we have to use // the brief status line format if (h_screen_cols < 55) brief = true; // Print the object description for the global variable 0 print_char (' '); print_object (global0); // A header flag tells us whether we have to display the current // time or the score/moves information if (h_config & CONFIG_TIME) { // print hours and minutes zword hours = (global1 + 11) % 12 + 1; pad_status_line (brief ? 15 : 20); print_string ("Time: "); if (hours < 10) print_char (' '); print_num (hours); print_char (':'); if (global2 < 10) print_char ('0'); print_num (global2); print_char (' '); print_char ((global1 >= 12) ? 'p' : 'a'); print_char ('m'); } else { // print score and moves pad_status_line (brief ? 15 : 30); print_string (brief ? "S: " : "Score: "); print_num (global1); pad_status_line (brief ? 8 : 14); print_string (brief ? "M: " : "Moves: "); print_num (global2); } // Pad the end of the status line with spaces pad_status_line (0); // Return to the lower window _wp.setWindow(0); } void Processor::z_split_window() { split_window(zargs[0]); } } // End of namespace Frotz } // End of namespace Glk