mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-08 02:42:34 +00:00
cfe8f4889c
Reported by Clang.
4001 lines
121 KiB
C++
4001 lines
121 KiB
C++
/* 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/magnetic/magnetic_defs.h"
|
|
#include "glk/magnetic/magnetic.h"
|
|
|
|
namespace Glk {
|
|
namespace Magnetic {
|
|
|
|
const gms_command_t Magnetic::GMS_COMMAND_TABLE[14] = {
|
|
{ &Magnetic::gms_command_summary, "summary", false, false },
|
|
{ &Magnetic::gms_command_undo, "undo", false, true },
|
|
{ &Magnetic::gms_command_script, "script", true, false },
|
|
{ &Magnetic::gms_command_inputlog, "inputlog", true, false },
|
|
{ &Magnetic::gms_command_readlog, "readlog", true, false },
|
|
{ &Magnetic::gms_command_abbreviations, "abbreviations", true, false },
|
|
{ &Magnetic::gms_command_graphics, "graphics", true, false },
|
|
{ &Magnetic::gms_command_gamma, "gamma", true, false },
|
|
{ &Magnetic::gms_command_animations, "animations", true, false },
|
|
{ &Magnetic::gms_command_prompts, "prompts", true, false },
|
|
{ &Magnetic::gms_command_version, "version", false, false },
|
|
{ &Magnetic::gms_command_commands, "commands", true, false },
|
|
{ &Magnetic::gms_command_help, "help", true, false },
|
|
|
|
{ nullptr, nullptr, false, false}
|
|
};
|
|
|
|
const gms_gamma_t Magnetic::GMS_GAMMA_TABLE[38] = {
|
|
{ "0.90", { 0, 29, 63, 99, 137, 175, 215, 255 }, true },
|
|
{ "0.95", { 0, 33, 68, 105, 141, 179, 217, 255 }, true },
|
|
{ "1.00", { 0, 36, 73, 109, 146, 182, 219, 255 }, false },
|
|
{ "1.05", { 0, 40, 77, 114, 150, 185, 220, 255 }, true },
|
|
{ "1.10", { 0, 43, 82, 118, 153, 188, 222, 255 }, true },
|
|
{ "1.15", { 0, 47, 86, 122, 157, 190, 223, 255 }, true },
|
|
{ "1.20", { 0, 50, 90, 126, 160, 193, 224, 255 }, true },
|
|
{ "1.25", { 0, 54, 94, 129, 163, 195, 225, 255 }, true },
|
|
{ "1.30", { 0, 57, 97, 133, 166, 197, 226, 255 }, true },
|
|
{ "1.35", { 0, 60, 101, 136, 168, 199, 227, 255 }, true },
|
|
{ "1.40", { 0, 64, 104, 139, 171, 201, 228, 255 }, true },
|
|
{ "1.45", { 0, 67, 107, 142, 173, 202, 229, 255 }, true },
|
|
{ "1.50", { 0, 70, 111, 145, 176, 204, 230, 255 }, true },
|
|
{ "1.55", { 0, 73, 114, 148, 178, 205, 231, 255 }, true },
|
|
{ "1.60", { 0, 76, 117, 150, 180, 207, 232, 255 }, true },
|
|
{ "1.65", { 0, 78, 119, 153, 182, 208, 232, 255 }, true },
|
|
{ "1.70", { 0, 81, 122, 155, 183, 209, 233, 255 }, true },
|
|
{ "1.75", { 0, 84, 125, 157, 185, 210, 233, 255 }, true },
|
|
{ "1.80", { 0, 87, 127, 159, 187, 212, 234, 255 }, true },
|
|
{ "1.85", { 0, 89, 130, 161, 188, 213, 235, 255 }, true },
|
|
{ "1.90", { 0, 92, 132, 163, 190, 214, 235, 255 }, true },
|
|
{ "1.95", { 0, 94, 134, 165, 191, 215, 236, 255 }, true },
|
|
{ "2.00", { 0, 96, 136, 167, 193, 216, 236, 255 }, true },
|
|
{ "2.05", { 0, 99, 138, 169, 194, 216, 237, 255 }, true },
|
|
{ "2.10", { 0, 101, 140, 170, 195, 217, 237, 255 }, true },
|
|
{ "2.15", { 0, 103, 142, 172, 197, 218, 237, 255 }, true },
|
|
{ "2.20", { 0, 105, 144, 173, 198, 219, 238, 255 }, true },
|
|
{ "2.25", { 0, 107, 146, 175, 199, 220, 238, 255 }, true },
|
|
{ "2.30", { 0, 109, 148, 176, 200, 220, 238, 255 }, true },
|
|
{ "2.35", { 0, 111, 150, 178, 201, 221, 239, 255 }, true },
|
|
{ "2.40", { 0, 113, 151, 179, 202, 222, 239, 255 }, true },
|
|
{ "2.45", { 0, 115, 153, 180, 203, 222, 239, 255 }, true },
|
|
{ "2.50", { 0, 117, 154, 182, 204, 223, 240, 255 }, true },
|
|
{ "2.55", { 0, 119, 156, 183, 205, 223, 240, 255 }, true },
|
|
{ "2.60", { 0, 121, 158, 184, 206, 224, 240, 255 }, true },
|
|
{ "2.65", { 0, 122, 159, 185, 206, 225, 241, 255 }, true },
|
|
{ "2.70", { 0, 124, 160, 186, 207, 225, 241, 255 }, true },
|
|
{ NULL, { 0, 0, 0, 0, 0, 0, 0, 0 }, false }
|
|
};
|
|
|
|
static gms_abbreviation_t GMS_ABBREVIATIONS[] = {
|
|
{'c', "close"}, {'g', "again"}, {'i', "inventory"},
|
|
{'k', "attack"}, {'l', "look"}, {'p', "open"},
|
|
{'q', "quit"}, {'r', "drop"}, {'t', "take"},
|
|
{'x', "examine"}, {'y', "yes"}, {'z', "wait"},
|
|
{'\0', NULL}
|
|
};
|
|
|
|
/*---------------------------------------------------------------------*/
|
|
/* Module constants */
|
|
/*---------------------------------------------------------------------*/
|
|
|
|
#ifndef GARGLK
|
|
/*
|
|
* Maximum number of regions to consider in a single repaint pass. A
|
|
* couple of hundred seems to strike the right balance between not too
|
|
* sluggardly picture updates, and responsiveness to input during graphics
|
|
* rendering, when combined with short timeouts.
|
|
*/
|
|
static const int GMS_REPAINT_LIMIT = 256;
|
|
#endif
|
|
|
|
/*
|
|
* Graphics timeout; we like an update call after this period (ms). In
|
|
* practice, this timeout may actually be shorter than the time taken
|
|
* to reach the limit on repaint regions, but because Glk guarantees that
|
|
* user interactions (in this case, line events) take precedence over
|
|
* timeouts, this should be okay; we'll still see a game that responds to
|
|
* input each time the background repaint function yields.
|
|
*
|
|
* Setting this value is tricky. We'd like it to be the shortest possible
|
|
* consistent with getting other stuff done, say 10ms. However, Xglk has
|
|
* a granularity of 50ms on checking for timeouts, as it uses a 1/20s
|
|
* timeout on X select. This means that the shortest timeout we'll ever
|
|
* get from Xglk will be 50ms, so there's no point in setting this shorter
|
|
* than that. With luck, other Glk libraries will be more efficient than
|
|
* this, and can give us higher timer resolution; we'll set 50ms here, and
|
|
* hope that no other Glk library is worse.
|
|
*/
|
|
static const glui32 GMS_GRAPHICS_TIMEOUT = 50;
|
|
|
|
/*
|
|
* Count of timeouts to wait in between animation paints, and to wait on
|
|
* repaint request. Waiting for 2 timeouts of around 50ms, gets us to the
|
|
* 100ms recommended animation frame rate. Waiting after a repaint smooths
|
|
* the display where the frame is being resized, by helping to avoid
|
|
* graphics output while more resize events are received; around 1/2 second
|
|
* seems okay.
|
|
*/
|
|
static const int GMS_GRAPHICS_ANIMATION_WAIT = 2,
|
|
GMS_GRAPHICS_REPAINT_WAIT = 10;
|
|
|
|
/* Pixel size multiplier for image size scaling. */
|
|
static const int GMS_GRAPHICS_PIXEL = 2;
|
|
|
|
/* Proportion of the display to use for graphics. */
|
|
static const glui32 GMS_GRAPHICS_PROPORTION = 60;
|
|
|
|
/*
|
|
* Border and shading control. For cases where we can't detect the back-
|
|
* ground color of the main window, there's a default, white, background.
|
|
* Bordering is black, with a 1 pixel border, 2 pixel shading, and 8 steps
|
|
* of shading fade.
|
|
*/
|
|
static const glui32 GMS_GRAPHICS_DEFAULT_BACKGROUND = 0x00ffffff,
|
|
GMS_GRAPHICS_BORDER_COLOR = 0x00000000;
|
|
static const int GMS_GRAPHICS_BORDER = 1,
|
|
GMS_GRAPHICS_SHADING = 2,
|
|
GMS_GRAPHICS_SHADE_STEPS = 8;
|
|
|
|
/*
|
|
* Guaranteed unused pixel value. This value is used to fill the on-screen
|
|
* buffer on new pictures or repaints, resulting in a full paint of all
|
|
* pixels since no off-screen, real picture, pixel will match it.
|
|
*/
|
|
static const int GMS_GRAPHICS_UNUSED_PIXEL = 0xff;
|
|
|
|
/* Default width used for non-windowing Glk status lines. */
|
|
static const int GMS_DEFAULT_STATUS_WIDTH = 74;
|
|
|
|
/* Success and fail return codes from hint functions. */
|
|
static const type8 GMS_HINT_SUCCESS = 1;
|
|
// static const type8 GMS_HINT_ERROR = 0;
|
|
|
|
/* Default window sizes for non-windowing Glk libraries. */
|
|
static const glui32 GMS_HINT_DEFAULT_WIDTH = 72,
|
|
GMS_HINT_DEFAULT_HEIGHT = 25;
|
|
|
|
/*
|
|
* Special hint nodes indicating the root hint node, and a value to signal
|
|
* quit from hints subsystem.
|
|
*/
|
|
static const type16 GMS_HINT_ROOT_NODE = 0,
|
|
GMS_HINTS_DONE = UINT16_MAX_VAL;
|
|
|
|
/* Generic hint topic for the root hints node. */
|
|
static const char *const GMS_GENERIC_TOPIC = "Hints Menu";
|
|
|
|
/*---------------------------------------------------------------------*/
|
|
/* Glk port utility functions */
|
|
/*---------------------------------------------------------------------*/
|
|
|
|
void Magnetic::gms_fatal(const char *str) {
|
|
/*
|
|
* If the failure happens too early for us to have a window, print
|
|
* the message to stderr.
|
|
*/
|
|
if (!gms_main_window)
|
|
error("\n\nINTERNAL ERROR: %s", str);
|
|
|
|
/* Cancel all possible pending window input events. */
|
|
glk_cancel_line_event(gms_main_window, NULL);
|
|
glk_cancel_char_event(gms_main_window);
|
|
if (gms_hint_menu_window) {
|
|
glk_cancel_char_event(gms_hint_menu_window);
|
|
glk_window_close(gms_hint_menu_window, NULL);
|
|
}
|
|
if (gms_hint_text_window) {
|
|
glk_cancel_char_event(gms_hint_text_window);
|
|
glk_window_close(gms_hint_text_window, NULL);
|
|
}
|
|
|
|
/* Print a message indicating the error. */
|
|
glk_set_window(gms_main_window);
|
|
glk_set_style(style_Normal);
|
|
glk_put_string("\n\nINTERNAL ERROR: ");
|
|
glk_put_string(str);
|
|
|
|
glk_put_string("\n\nPlease record the details of this error, try to"
|
|
" note down everything you did to cause it, and email"
|
|
" this information to simon_baldwin@yahoo.com.\n\n");
|
|
}
|
|
|
|
void *Magnetic::gms_malloc(size_t size) {
|
|
void *pointer;
|
|
|
|
pointer = malloc(size);
|
|
if (!pointer) {
|
|
gms_fatal("GLK: Out of system memory");
|
|
glk_exit();
|
|
}
|
|
|
|
return pointer;
|
|
}
|
|
|
|
void *Magnetic::gms_realloc(void *ptr, size_t size) {
|
|
void *pointer;
|
|
|
|
pointer = realloc(ptr, size);
|
|
if (!pointer) {
|
|
gms_fatal("GLK: Out of system memory");
|
|
glk_exit();
|
|
}
|
|
|
|
return pointer;
|
|
}
|
|
|
|
int Magnetic::gms_strncasecmp(const char *s1, const char *s2, size_t n) {
|
|
size_t index;
|
|
|
|
for (index = 0; index < n; index++) {
|
|
int diff;
|
|
|
|
diff = glk_char_to_lower(s1[index]) - glk_char_to_lower(s2[index]);
|
|
if (diff < 0 || diff > 0)
|
|
return diff < 0 ? -1 : 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int Magnetic::gms_strcasecmp(const char *s1, const char *s2) {
|
|
size_t s1len, s2len;
|
|
int result;
|
|
|
|
s1len = strlen(s1);
|
|
s2len = strlen(s2);
|
|
|
|
result = gms_strncasecmp(s1, s2, s1len < s2len ? s1len : s2len);
|
|
if (result < 0 || result > 0)
|
|
return result;
|
|
else
|
|
return s1len < s2len ? -1 : s1len > s2len ? 1 : 0;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------*/
|
|
/* Glk port CRC functions */
|
|
/*---------------------------------------------------------------------*/
|
|
|
|
glui32 Magnetic::gms_get_buffer_crc(const void *void_buffer, size_t length) {
|
|
const char *buf = (const char *) void_buffer;
|
|
uint32 crc;
|
|
size_t index;
|
|
|
|
/*
|
|
* Start with all ones in the crc, then update using table entries. Xor
|
|
* with all ones again, finally, before returning.
|
|
*/
|
|
crc = 0xffffffff;
|
|
for (index = 0; index < length; index++)
|
|
crc = crc_table[(crc ^ buf[index]) & BYTE_MAX_VAL] ^ (crc >> BITS_PER_BYTE);
|
|
return crc ^ 0xffffffff;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------*/
|
|
/* Glk port game identification data and identification functions */
|
|
/*---------------------------------------------------------------------*/
|
|
|
|
type32 Magnetic::gms_gameid_read_uint32(int offset, Common::SeekableReadStream *stream) {
|
|
if (!stream->seek(offset))
|
|
return 0;
|
|
return stream->readUint32BE();
|
|
}
|
|
|
|
void Magnetic::gms_gameid_identify_game(const Common::String &text_file) {
|
|
Common::File stream;
|
|
|
|
if (!stream.open(text_file))
|
|
error("Error opening game file");
|
|
|
|
type32 game_size, game_pc;
|
|
gms_game_tableref_t game;
|
|
|
|
/* Read the game's signature undo size and undo pc values. */
|
|
game_size = gms_gameid_read_uint32(0x22, &stream);
|
|
game_pc = gms_gameid_read_uint32(0x26, &stream);
|
|
|
|
/* Search for these values in the table, and set game name if found. */
|
|
game = gms_gameid_lookup_game(game_size, game_pc);
|
|
gms_gameid_game_name = game ? game->name : NULL;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------*/
|
|
/* Glk port picture functions */
|
|
/*---------------------------------------------------------------------*/
|
|
|
|
int Magnetic::gms_graphics_open() {
|
|
if (!gms_graphics_window) {
|
|
gms_graphics_window = glk_window_open(gms_main_window,
|
|
winmethod_Above
|
|
| winmethod_Proportional,
|
|
GMS_GRAPHICS_PROPORTION,
|
|
wintype_Graphics, 0);
|
|
}
|
|
|
|
return gms_graphics_window != NULL;
|
|
}
|
|
|
|
void Magnetic::gms_graphics_close() {
|
|
if (gms_graphics_window) {
|
|
glk_window_close(gms_graphics_window, NULL);
|
|
gms_graphics_window = NULL;
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_graphics_start() {
|
|
if (gms_graphics_enabled) {
|
|
/* If not running, start the updating "thread". */
|
|
if (!gms_graphics_active) {
|
|
glk_request_timer_events(GMS_GRAPHICS_TIMEOUT);
|
|
gms_graphics_active = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_graphics_stop() {
|
|
/* If running, stop the updating "thread". */
|
|
if (gms_graphics_active) {
|
|
glk_request_timer_events(0);
|
|
gms_graphics_active = false;
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_graphics_paint() {
|
|
if (gms_graphics_enabled && gms_graphics_are_displayed()) {
|
|
/* Set the repaint flag, and start graphics. */
|
|
gms_graphics_repaint = true;
|
|
gms_graphics_start();
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_graphics_restart() {
|
|
if (gms_graphics_enabled && gms_graphics_are_displayed()) {
|
|
/*
|
|
* If the picture is animated, we'll need to be able to re-get the
|
|
* first animation frame so that the picture can be treated as if
|
|
* it is a new one. So here, we'll try to re-extract the current
|
|
* picture to do this. Calling ms_extract() is safe because we
|
|
* don't get here unless graphics are displayed, and graphics aren't
|
|
* displayed until there's a valid picture loaded, and ms_showpic
|
|
* only loads a picture after it's called ms_extract and set the
|
|
* picture id into gms_graphics_picture.
|
|
*
|
|
* The bitmap and other picture stuff can be ignored because it's
|
|
* the precise same stuff as we already have in picture details
|
|
* variables. If the ms_extract() fails, we'll carry on regardless,
|
|
* which may, or may not, result in the ideal picture display.
|
|
*
|
|
* One or two non-animated pictures return NULL from ms_extract()
|
|
* being re-called, so we'll restrict calls to animations only.
|
|
* And just to be safe, we'll also call only if we're already
|
|
* holding a bitmap (and we should be; how else could the graphics
|
|
* animation flag be set?...).
|
|
*/
|
|
if (gms_graphics_animated && gms_graphics_bitmap) {
|
|
type8 animated;
|
|
type16 width, height, palette[GMS_PALETTE_SIZE];
|
|
|
|
/* Extract the bitmap into dummy variables. */
|
|
(void)ms_extract(gms_graphics_picture, &width, &height, palette, &animated);
|
|
}
|
|
|
|
/* Set the new picture flag, and start graphics. */
|
|
gms_graphics_new_picture = true;
|
|
gms_graphics_start();
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_graphics_count_colors(type8 bitmap[], type16 width, type16 height,
|
|
int *color_count, long color_usage[]) {
|
|
int x, y, count;
|
|
long usage[GMS_PALETTE_SIZE], index_row;
|
|
assert(bitmap);
|
|
|
|
/*
|
|
* Traverse the image, counting each pixel usage. For the y iterator,
|
|
* maintain an index row as an optimization to avoid multiplications in
|
|
* the loop.
|
|
*/
|
|
count = 0;
|
|
memset(usage, 0, sizeof(usage));
|
|
for (y = 0, index_row = 0; y < height; y++, index_row += width) {
|
|
for (x = 0; x < width; x++) {
|
|
long index;
|
|
|
|
/* Get the pixel index, and update the count for this color. */
|
|
index = index_row + x;
|
|
usage[bitmap[index]]++;
|
|
|
|
/* If color usage is now 1, note new color encountered. */
|
|
if (usage[bitmap[index]] == 1)
|
|
count++;
|
|
}
|
|
}
|
|
|
|
if (color_count)
|
|
*color_count = count;
|
|
|
|
if (color_usage)
|
|
memcpy(color_usage, usage, sizeof(usage));
|
|
}
|
|
|
|
void Magnetic::gms_graphics_game_to_rgb_color(type16 color, gms_gammaref_t gamma,
|
|
gms_rgbref_t rgb_color) {
|
|
assert(gamma && rgb_color);
|
|
|
|
/*
|
|
* Convert Magnetic Scrolls color, through gamma, into RGB. This splits
|
|
* the color into components based on the 3-bits used in the game palette,
|
|
* and gamma-corrects and rescales each to the range 0-255, using the given
|
|
* correction.
|
|
*/
|
|
rgb_color->red = gamma->table[(color & 0x700) >> 8];
|
|
rgb_color->green = gamma->table[(color & 0x070) >> 4];
|
|
rgb_color->blue = gamma->table[(color & 0x007)];
|
|
}
|
|
|
|
void Magnetic::gms_graphics_split_color(glui32 color, gms_rgbref_t rgb_color) {
|
|
assert(rgb_color);
|
|
|
|
rgb_color->red = (color >> 16) & 0xff;
|
|
rgb_color->green = (color >> 8) & 0xff;
|
|
rgb_color->blue = color & 0xff;
|
|
}
|
|
|
|
glui32 Magnetic::gms_graphics_combine_color(gms_rgbref_t rgb_color) {
|
|
assert(rgb_color && _screen->format.bytesPerPixel == 2);
|
|
return _screen->format.RGBToColor(rgb_color->red, rgb_color->green, rgb_color->blue);
|
|
}
|
|
|
|
int Magnetic::gms_graphics_color_luminance(gms_rgbref_t rgb_color) {
|
|
/* Calculate the luminance and scale back by 1000 to 0-255 before return. */
|
|
long luminance = ((long) rgb_color->red * (long) GMS_LUMINANCE_WEIGHTS.red
|
|
+ (long) rgb_color->green * (long) GMS_LUMINANCE_WEIGHTS.green
|
|
+ (long) rgb_color->blue * (long) GMS_LUMINANCE_WEIGHTS.blue);
|
|
|
|
assert(luminance_weighting > 0);
|
|
return (int)(luminance / luminance_weighting);
|
|
}
|
|
|
|
int Magnetic::gms_graphics_compare_luminance(const void *void_first,
|
|
const void *void_second) {
|
|
long first = *(const long *)void_first;
|
|
long second = *(const long *)void_second;
|
|
|
|
return first > second ? 1 : second > first ? -1 : 0;
|
|
}
|
|
|
|
long Magnetic::gms_graphics_contrast_variance(type16 palette[],
|
|
long color_usage[], gms_gammaref_t gamma) {
|
|
int index, count, has_black, mean;
|
|
long sum;
|
|
int contrast[GMS_PALETTE_SIZE];
|
|
int luminance[GMS_PALETTE_SIZE + 1]; /* Luminance for each color,
|
|
plus one extra for black */
|
|
|
|
/* Calculate the luminance energy of each palette color at this gamma. */
|
|
has_black = false;
|
|
for (index = 0, count = 0; index < GMS_PALETTE_SIZE; index++) {
|
|
if (color_usage[index] > 0) {
|
|
gms_rgb_t rgb_color;
|
|
|
|
/*
|
|
* Convert the 16-bit base picture color to RGB using the gamma
|
|
* currently under consideration. Calculate luminance for this
|
|
* color and store in the next available luminance array entry.
|
|
*/
|
|
gms_graphics_game_to_rgb_color(palette[index], gamma, &rgb_color);
|
|
luminance[count++] = gms_graphics_color_luminance(&rgb_color);
|
|
|
|
/* Note if black is present in the palette. */
|
|
has_black |= luminance[count - 1] == 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* For best results, we want to anchor contrast calculations to black, so
|
|
* if black is not represented in the palette, add it as an extra luminance.
|
|
*/
|
|
if (!has_black)
|
|
luminance[count++] = 0;
|
|
|
|
/* Sort luminance values so that the darkest color is at index 0. */
|
|
qsort(luminance, count,
|
|
sizeof(*luminance), gms_graphics_compare_luminance);
|
|
|
|
/*
|
|
* Calculate the difference in luminance between adjacent luminances in
|
|
* the sorted array, as contrast, and at the same time sum contrasts to
|
|
* calculate the mean.
|
|
*/
|
|
sum = 0;
|
|
for (index = 0; index < count - 1; index++) {
|
|
contrast[index] = luminance[index + 1] - luminance[index];
|
|
sum += contrast[index];
|
|
}
|
|
mean = sum / (count - 1);
|
|
|
|
/* Calculate and return the variance in contrasts. */
|
|
sum = 0;
|
|
for (index = 0; index < count - 1; index++)
|
|
sum += (contrast[index] - mean) * (contrast[index] - mean);
|
|
|
|
return sum / (count - 1);
|
|
}
|
|
|
|
gms_gammaref_t Magnetic::gms_graphics_equal_contrast_gamma(type16 palette[], long color_usage[]) {
|
|
gms_gammaref_t gamma, result;
|
|
long lowest_variance;
|
|
assert(palette && color_usage);
|
|
|
|
result = NULL;
|
|
lowest_variance = INT32_MAX_VAL;
|
|
|
|
/* Search the gamma table for the entry with the lowest contrast variance. */
|
|
for (gamma = GMS_GAMMA_TABLE; gamma->level; gamma++) {
|
|
long variance;
|
|
|
|
/* Find the color contrast variance of the palette at this gamma. */
|
|
variance = gms_graphics_contrast_variance(palette, color_usage, gamma);
|
|
|
|
/*
|
|
* Compare the variance to the lowest so far, and if it is lower, note
|
|
* the gamma entry that produced it as being the current best found.
|
|
*/
|
|
if (variance < lowest_variance) {
|
|
result = gamma;
|
|
lowest_variance = variance;
|
|
}
|
|
}
|
|
|
|
assert(result);
|
|
return result;
|
|
}
|
|
|
|
gms_gammaref_t Magnetic::gms_graphics_select_gamma(type8 bitmap[],
|
|
type16 width, type16 height, type16 palette[]) {
|
|
long color_usage[GMS_PALETTE_SIZE];
|
|
int color_count;
|
|
gms_gammaref_t contrast_gamma;
|
|
assert(linear_gamma);
|
|
|
|
/*
|
|
* Check to see if automated correction is turned off; if it is, return
|
|
* the linear gamma.
|
|
*/
|
|
if (gms_gamma_mode == GAMMA_OFF)
|
|
return linear_gamma;
|
|
|
|
/*
|
|
* Get the color usage and count of total colors represented. For a
|
|
* degenerate picture with one color or less, return the linear gamma.
|
|
*/
|
|
gms_graphics_count_colors(bitmap, width, height, &color_count, color_usage);
|
|
if (color_count <= 1)
|
|
return linear_gamma;
|
|
|
|
/*
|
|
* Now calculate a gamma setting to give the most equal contrast across the
|
|
* picture colors. We'll return either half this gamma, or all of it.
|
|
*/
|
|
contrast_gamma = gms_graphics_equal_contrast_gamma(palette, color_usage);
|
|
|
|
/*
|
|
* For normal automated correction, return a gamma value half way between
|
|
* the linear gamma and the equal contrast gamma.
|
|
*/
|
|
if (gms_gamma_mode == GAMMA_NORMAL)
|
|
return linear_gamma + (contrast_gamma - linear_gamma) / 2;
|
|
|
|
/* Correction must be high; return the equal contrast gamma. */
|
|
assert(gms_gamma_mode == GAMMA_HIGH);
|
|
return contrast_gamma;
|
|
}
|
|
|
|
void Magnetic::gms_graphics_clear_and_border(winid_t glk_window,
|
|
int x_offset, int y_offset, int pixel_size, type16 width, type16 height) {
|
|
uint background;
|
|
glui32 fade_color, shading_color;
|
|
gms_rgb_t rgb_background, rgb_border, rgb_fade;
|
|
int index;
|
|
assert(glk_window);
|
|
|
|
/*
|
|
* Try to detect the background color of the main window, by getting the
|
|
* background for Normal style (Glk offers no way to directly get a window's
|
|
* background color). If we can get it, we'll match the graphics window
|
|
* background to it. If we can't, we'll default the color to white.
|
|
*/
|
|
if (!glk_style_measure(gms_main_window,
|
|
style_Normal, stylehint_BackColor, &background)) {
|
|
/*
|
|
* Unable to get the main window background, so assume, and default
|
|
* graphics to white.
|
|
*/
|
|
background = GMS_GRAPHICS_DEFAULT_BACKGROUND;
|
|
}
|
|
|
|
/*
|
|
* Set the graphics window background to match the main window background,
|
|
* as best as we can tell, and clear the window.
|
|
*/
|
|
glk_window_set_background_color(glk_window, background);
|
|
glk_window_clear(glk_window);
|
|
|
|
/*
|
|
* For very small pictures, just border them, but don't try and do any
|
|
* shading. Failing this check is probably highly unlikely.
|
|
*/
|
|
if (width < 2 * GMS_GRAPHICS_SHADE_STEPS
|
|
|| height < 2 * GMS_GRAPHICS_SHADE_STEPS) {
|
|
/* Paint a rectangle bigger than the picture by border pixels. */
|
|
glk_window_fill_rect(glk_window,
|
|
GMS_GRAPHICS_BORDER_COLOR,
|
|
x_offset - GMS_GRAPHICS_BORDER,
|
|
y_offset - GMS_GRAPHICS_BORDER,
|
|
width * pixel_size + GMS_GRAPHICS_BORDER * 2,
|
|
height * pixel_size + GMS_GRAPHICS_BORDER * 2);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Paint a rectangle bigger than the picture by border pixels all round,
|
|
* and with additional shading pixels right and below. Some of these
|
|
* shading pixels are later overwritten by the fading loop below. The
|
|
* picture will sit over this rectangle.
|
|
*/
|
|
glk_window_fill_rect(glk_window,
|
|
GMS_GRAPHICS_BORDER_COLOR,
|
|
x_offset - GMS_GRAPHICS_BORDER,
|
|
y_offset - GMS_GRAPHICS_BORDER,
|
|
width * pixel_size + GMS_GRAPHICS_BORDER * 2
|
|
+ GMS_GRAPHICS_SHADING,
|
|
height * pixel_size + GMS_GRAPHICS_BORDER * 2
|
|
+ GMS_GRAPHICS_SHADING);
|
|
|
|
/*
|
|
* Split the main window background color and the border color into
|
|
* components.
|
|
*/
|
|
gms_graphics_split_color(background, &rgb_background);
|
|
gms_graphics_split_color(GMS_GRAPHICS_BORDER_COLOR, &rgb_border);
|
|
|
|
/*
|
|
* Generate the incremental color to use in fade steps. Here we're
|
|
* assuming that the border is always darker than the main window
|
|
* background (currently valid, as we're using black).
|
|
*/
|
|
rgb_fade.red = (rgb_background.red - rgb_border.red)
|
|
/ GMS_GRAPHICS_SHADE_STEPS;
|
|
rgb_fade.green = (rgb_background.green - rgb_border.green)
|
|
/ GMS_GRAPHICS_SHADE_STEPS;
|
|
rgb_fade.blue = (rgb_background.blue - rgb_border.blue)
|
|
/ GMS_GRAPHICS_SHADE_STEPS;
|
|
|
|
/* Combine RGB fade into a single incremental Glk color. */
|
|
fade_color = gms_graphics_combine_color(&rgb_fade);
|
|
|
|
/* Fade in edge, from background to border, shading in stages. */
|
|
shading_color = background;
|
|
for (index = 0; index < GMS_GRAPHICS_SHADE_STEPS; index++) {
|
|
/* Shade the two border areas with this color. */
|
|
glk_window_fill_rect(glk_window, shading_color,
|
|
x_offset + width * pixel_size
|
|
+ GMS_GRAPHICS_BORDER,
|
|
y_offset + index - GMS_GRAPHICS_BORDER,
|
|
GMS_GRAPHICS_SHADING, 1);
|
|
glk_window_fill_rect(glk_window, shading_color,
|
|
x_offset + index - GMS_GRAPHICS_BORDER,
|
|
y_offset + height * pixel_size
|
|
+ GMS_GRAPHICS_BORDER,
|
|
1, GMS_GRAPHICS_SHADING);
|
|
|
|
/* Update the shading color for the fade next iteration. */
|
|
shading_color -= fade_color;
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_graphics_convert_palette(type16 ms_palette[], gms_gammaref_t gamma,
|
|
glui32 glk_palette[]) {
|
|
int index;
|
|
assert(ms_palette && gamma && glk_palette);
|
|
|
|
for (index = 0; index < GMS_PALETTE_SIZE; index++) {
|
|
gms_rgb_t rgb_color;
|
|
|
|
/*
|
|
* Convert the 16-bit base picture color through gamma to a 32-bit
|
|
* RGB color, and combine into a Glk color and store in the Glk palette.
|
|
*/
|
|
gms_graphics_game_to_rgb_color(ms_palette[index], gamma, &rgb_color);
|
|
glk_palette[index] = gms_graphics_combine_color(&rgb_color);
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_graphics_position_picture(winid_t glk_window,
|
|
int pixel_size, type16 width, type16 height, int *x_offset, int *y_offset) {
|
|
uint window_width, window_height;
|
|
assert(glk_window && x_offset && y_offset);
|
|
|
|
/* Measure the current graphics window dimensions. */
|
|
glk_window_get_size(glk_window, &window_width, &window_height);
|
|
|
|
/*
|
|
* Calculate and return an x and y offset to use on point plotting, so that
|
|
* the image centers inside the graphical window.
|
|
*/
|
|
*x_offset = ((int) window_width - width * pixel_size) / 2;
|
|
*y_offset = ((int) window_height - height * pixel_size) / 2;
|
|
}
|
|
|
|
void Magnetic::gms_graphics_apply_animation_frame(type8 bitmap[],
|
|
type16 frame_width, type16 frame_height, type8 mask[], int frame_x, int frame_y,
|
|
type8 off_screen[], type16 width, type16 height) {
|
|
int mask_width, x, y;
|
|
type8 mask_hibit;
|
|
long frame_row, buffer_row, mask_row;
|
|
assert(bitmap && off_screen);
|
|
|
|
/*
|
|
* It turns out that the mask isn't quite as described in defs.h, and thanks
|
|
* to Torbjorn Andersson and his Gtk port of Magnetic for illuminating this.
|
|
* The mask is made up of lines of 16-bit words, so the mask width is always
|
|
* even. Here we'll calculate the real width of a mask, and also set a high
|
|
* bit for later on.
|
|
*/
|
|
mask_width = (((frame_width - 1) / BITS_PER_BYTE) + 2) & (~1);
|
|
mask_hibit = 1 << (BITS_PER_BYTE - 1);
|
|
|
|
/*
|
|
* Initialize row index components; these are optimizations to avoid the
|
|
* need for multiplications in the frame iteration loop.
|
|
*/
|
|
frame_row = 0;
|
|
buffer_row = frame_y * width;
|
|
mask_row = 0;
|
|
|
|
/*
|
|
* Iterate over each frame row, clipping where y lies outside the main
|
|
* picture area.
|
|
*/
|
|
for (y = 0; y < frame_height; y++) {
|
|
/* Clip if y is outside the main picture area. */
|
|
if (y + frame_y < 0 || y + frame_y >= height) {
|
|
/* Update optimization variables as if not clipped. */
|
|
frame_row += frame_width;
|
|
buffer_row += width;
|
|
mask_row += mask_width;
|
|
continue;
|
|
}
|
|
|
|
/* Iterate over each frame column, clipping again. */
|
|
for (x = 0; x < frame_width; x++) {
|
|
long frame_index, buffer_index;
|
|
|
|
/* Clip if x is outside the main picture area. */
|
|
if (x + frame_x < 0 || x + frame_x >= width)
|
|
continue;
|
|
|
|
/*
|
|
* If there's a mask, check the bit associated with this x,y, and
|
|
* ignore any transparent pixels.
|
|
*/
|
|
if (mask) {
|
|
type8 mask_byte;
|
|
|
|
/* Isolate the mask byte, and test the transparency bit. */
|
|
mask_byte = mask[mask_row + (x / BITS_PER_BYTE)];
|
|
if ((mask_byte & (mask_hibit >> (x % BITS_PER_BYTE))) != 0)
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Calculate indexes for this pixel into the frame, and into the
|
|
* main off-screen buffer, and transfer the frame pixel into the
|
|
* off-screen buffer.
|
|
*/
|
|
frame_index = frame_row + x;
|
|
buffer_index = buffer_row + x + frame_x;
|
|
off_screen[buffer_index] = bitmap[frame_index];
|
|
}
|
|
|
|
/* Update row index components on change of y. */
|
|
frame_row += frame_width;
|
|
buffer_row += width;
|
|
mask_row += mask_width;
|
|
}
|
|
}
|
|
|
|
int Magnetic::gms_graphics_animate(type8 off_screen[], type16 width, type16 height) {
|
|
struct ms_position *positions;
|
|
type16 count;
|
|
type8 status;
|
|
int frame;
|
|
assert(off_screen);
|
|
|
|
/* Search for more animation frames, and return zero if none. */
|
|
status = ms_animate(&positions, &count);
|
|
if (status == 0)
|
|
return false;
|
|
|
|
/* Apply each animation frame to the off-screen buffer. */
|
|
for (frame = 0; frame < count; frame++) {
|
|
type8 *bitmap, *mask;
|
|
type16 frame_width, frame_height;
|
|
|
|
/*
|
|
* Get the bitmap and other details for this frame. If we can't get
|
|
* this animation frame, skip it and see if any others are available.
|
|
*/
|
|
bitmap = ms_get_anim_frame(positions[frame].number,
|
|
&frame_width, &frame_height, &mask);
|
|
if (bitmap) {
|
|
gms_graphics_apply_animation_frame(bitmap,
|
|
frame_width, frame_height, mask,
|
|
positions[frame].x,
|
|
positions[frame].y,
|
|
off_screen, width, height);
|
|
}
|
|
}
|
|
|
|
/* Return true since more animation frames remain. */
|
|
return true;
|
|
}
|
|
|
|
#ifndef GARGLK
|
|
int Magnetic::gms_graphics_is_vertex(type8 off_screen[], type16 width, type16 height,
|
|
int x, int y) {
|
|
type8 pixel;
|
|
int above, below, left, right;
|
|
long index_row;
|
|
assert(off_screen);
|
|
|
|
/* Use an index row to cut down on multiplications. */
|
|
index_row = y * width;
|
|
|
|
/* Find the color of the reference pixel. */
|
|
pixel = off_screen[index_row + x];
|
|
assert(pixel < GMS_PALETTE_SIZE);
|
|
|
|
/*
|
|
* Detect differences between the reference pixel and its upper, lower, left
|
|
* and right neighbors. Mark as different if the neighbor doesn't exist,
|
|
* that is, at the edge of the picture.
|
|
*/
|
|
above = (y == 0 || off_screen[index_row - width + x] != pixel);
|
|
below = (y == height - 1 || off_screen[index_row + width + x] != pixel);
|
|
left = (x == 0 || off_screen[index_row + x - 1] != pixel);
|
|
right = (x == width - 1 || off_screen[index_row + x + 1] != pixel);
|
|
|
|
/*
|
|
* Return true if this pixel lies at the vertex of a rectangular, fillable,
|
|
* area. That is, if two adjacent neighbors aren't the same color (or if
|
|
* absent -- at the edge of the picture).
|
|
*/
|
|
return ((above || below) && (left || right));
|
|
}
|
|
|
|
int Magnetic::gms_graphics_compare_layering_inverted(const void *void_first,
|
|
const void *void_second) {
|
|
gms_layering_t *first = (gms_layering_t *) void_first;
|
|
gms_layering_t *second = (gms_layering_t *) void_second;
|
|
|
|
/*
|
|
* Order by complexity first, then by usage, putting largest first. Some
|
|
* colors may have no vertices at all when doing animation frames, but
|
|
* rendering optimization relies on the first layer that contains no areas
|
|
* to fill halting the rendering loop. So it's important here that we order
|
|
* indexes so that colors that render complex shapes come first, non-empty,
|
|
* but simpler shaped colors next, and finally all genuinely empty layers.
|
|
*/
|
|
return second->complexity > first->complexity ? 1 :
|
|
first->complexity > second->complexity ? -1 :
|
|
second->usage > first->usage ? 1 :
|
|
first->usage > second->usage ? -1 : 0;
|
|
}
|
|
|
|
void Magnetic::gms_graphics_assign_layers(type8 off_screen[], type8 on_screen[],
|
|
type16 width, type16 height,
|
|
int layers[], long layer_usage[]) {
|
|
int index, x, y;
|
|
long index_row;
|
|
gms_layering_t layering[GMS_PALETTE_SIZE];
|
|
assert(off_screen && on_screen && layers && layer_usage);
|
|
|
|
/* Clear initial complexity and usage counts, and set initial colors. */
|
|
for (index = 0; index < GMS_PALETTE_SIZE; index++) {
|
|
layering[index].complexity = 0;
|
|
layering[index].usage = 0;
|
|
layering[index].color = index;
|
|
}
|
|
|
|
/*
|
|
* Traverse the image, counting vertices and pixel usage where the pixels
|
|
* differ between the off-screen and on-screen buffers. Optimize by
|
|
* maintaining an index row to avoid multiplications.
|
|
*/
|
|
for (y = 0, index_row = 0; y < height; y++, index_row += width) {
|
|
for (x = 0; x < width; x++) {
|
|
long idx;
|
|
|
|
/*
|
|
* Get the index for this pixel, and update complexity and usage
|
|
* if off-screen and on-screen pixels differ.
|
|
*/
|
|
idx = index_row + x;
|
|
if (on_screen[idx] != off_screen[idx]) {
|
|
if (gms_graphics_is_vertex(off_screen, width, height, x, y))
|
|
layering[off_screen[idx]].complexity++;
|
|
|
|
layering[off_screen[idx]].usage++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Sort counts to form color indexes. The primary sort is on the shape
|
|
* complexity, and within this, on color usage.
|
|
*/
|
|
qsort(layering, GMS_PALETTE_SIZE,
|
|
sizeof(*layering), gms_graphics_compare_layering_inverted);
|
|
|
|
/*
|
|
* Assign a layer to each palette color, and also return the layer usage
|
|
* for each layer.
|
|
*/
|
|
for (index = 0; index < GMS_PALETTE_SIZE; index++) {
|
|
layers[layering[index].color] = index;
|
|
layer_usage[index] = layering[index].usage;
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_graphics_paint_region(winid_t glk_window, glui32 palette[], int layers[],
|
|
type8 off_screen[], type8 on_screen[],
|
|
int x, int y, int x_offset, int y_offset,
|
|
int pixel_size, type16 width, type16 height) {
|
|
type8 pixel;
|
|
int layer, x_min, x_max, y_min, y_max, x_index, y_index;
|
|
long index_row;
|
|
assert(glk_window && palette && layers && off_screen && on_screen);
|
|
|
|
/* Find the color and layer for the initial pixel. */
|
|
pixel = off_screen[y * width + x];
|
|
layer = layers[pixel];
|
|
assert(pixel < GMS_PALETTE_SIZE);
|
|
|
|
/*
|
|
* Start by finding the extent to which we can pull the x coordinate and
|
|
* still find either invalidated pixels, or pixels in this layer.
|
|
*
|
|
* Use an index row to remove multiplications from the loops.
|
|
*/
|
|
index_row = y * width;
|
|
for (x_min = x; x_min - 1 >= 0; x_min--) {
|
|
long index = index_row + x_min - 1;
|
|
|
|
if (on_screen[index] == off_screen[index]
|
|
&& layers[off_screen[index]] != layer)
|
|
break;
|
|
}
|
|
for (x_max = x; x_max + 1 < width; x_max++) {
|
|
long index = index_row + x_max + 1;
|
|
|
|
if (on_screen[index] == off_screen[index]
|
|
&& layers[off_screen[index]] != layer)
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Now try to stretch the height of the region, by extending the y
|
|
* coordinate as much as possible too. Again, we're looking for pixels
|
|
* that are invalidated or ones in the same layer. We need to check
|
|
* across the full width of the current region.
|
|
*
|
|
* As above, an index row removes multiplications from the loops.
|
|
*/
|
|
for (y_min = y, index_row = (y - 1) * width;
|
|
y_min - 1 >= 0; y_min--, index_row -= width) {
|
|
for (x_index = x_min; x_index <= x_max; x_index++) {
|
|
long index = index_row + x_index;
|
|
|
|
if (on_screen[index] == off_screen[index]
|
|
&& layers[off_screen[index]] != layer)
|
|
goto break_y_min;
|
|
}
|
|
}
|
|
break_y_min:
|
|
|
|
for (y_max = y, index_row = (y + 1) * width;
|
|
y_max + 1 < height; y_max++, index_row += width) {
|
|
for (x_index = x_min; x_index <= x_max; x_index++) {
|
|
long index = index_row + x_index;
|
|
|
|
if (on_screen[index] == off_screen[index]
|
|
&& layers[off_screen[index]] != layer)
|
|
goto break_y_max;
|
|
}
|
|
}
|
|
break_y_max:
|
|
|
|
/* Fill the region using Glk's rectangle fill. */
|
|
glk_window_fill_rect(glk_window, palette[pixel],
|
|
x_min * pixel_size + x_offset,
|
|
y_min * pixel_size + y_offset,
|
|
(x_max - x_min + 1) * pixel_size,
|
|
(y_max - y_min + 1) * pixel_size);
|
|
|
|
/*
|
|
* Validate each pixel in the reference layer that was rendered by the
|
|
* rectangle fill. We don't validate pixels that are not in this layer
|
|
* (and are by definition in higher layers, as we've validated all lower
|
|
* layers), since although we colored them, we did it for optimization
|
|
* reasons, and they're not yet colored correctly.
|
|
*
|
|
* Maintain an index row as an optimization to avoid multiplication.
|
|
*/
|
|
index_row = y_min * width;
|
|
for (y_index = y_min; y_index <= y_max; y_index++) {
|
|
for (x_index = x_min; x_index <= x_max; x_index++) {
|
|
long index;
|
|
|
|
/*
|
|
* Get the index for x_index,y_index. If the layers match, update
|
|
* the on-screen buffer.
|
|
*/
|
|
index = index_row + x_index;
|
|
if (layers[off_screen[index]] == layer) {
|
|
assert(off_screen[index] == pixel);
|
|
on_screen[index] = off_screen[index];
|
|
}
|
|
}
|
|
|
|
/* Update row index component on change of y. */
|
|
index_row += width;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void Magnetic::gms_graphics_paint_everything(winid_t glk_window,
|
|
glui32 palette[], type8 off_screen[], int x_offset, int y_offset,
|
|
type16 width, type16 height) {
|
|
type16 x, y;
|
|
glui32 pixel;
|
|
|
|
Graphics::ManagedSurface s(width, height, _screen->format);
|
|
|
|
for (y = 0; y < height; y++) {
|
|
uint16 *lineP = (uint16 *)s.getBasePtr(0, y);
|
|
|
|
for (x = 0; x < width; ++x, ++lineP) {
|
|
pixel = palette[off_screen[y * width + x]];
|
|
*lineP = pixel;
|
|
}
|
|
}
|
|
|
|
glk_image_draw_scaled(glk_window, s, (uint)-1, x_offset, y_offset,
|
|
width * GMS_GRAPHICS_PIXEL, height * GMS_GRAPHICS_PIXEL);
|
|
}
|
|
|
|
void Magnetic::gms_graphics_timeout() {
|
|
static glui32 palette[GMS_PALETTE_SIZE]; /* Precomputed Glk palette */
|
|
#ifndef GARGLK
|
|
static int layers[GMS_PALETTE_SIZE]; /* Assigned image layers */
|
|
static long layer_usage[GMS_PALETTE_SIZE]; /* Image layer occupancies */
|
|
#endif
|
|
|
|
static int deferred_repaint = false; /* Local delayed repaint flag */
|
|
static int ignore_counter; /* Count of calls ignored */
|
|
|
|
static int x_offset, y_offset; /* Point plot offsets */
|
|
static int yield_counter; /* Yields in rendering */
|
|
#ifndef GARGLK
|
|
static int saved_layer; /* Saved current layer */
|
|
static int saved_x, saved_y; /* Saved x,y coord */
|
|
static int total_regions; /* Debug statistic */
|
|
#endif
|
|
|
|
type8 *on_screen; /* On-screen image buffer */
|
|
type8 *off_screen; /* Off-screen image buffer */
|
|
long picture_size; /* Picture size in pixels */
|
|
// int layer; /* Image layer iterator */
|
|
// int x, y; /* Image iterators */
|
|
// int regions; /* Count of regions painted */
|
|
|
|
/* Ignore the call if the current graphics state is inactive. */
|
|
if (!gms_graphics_active)
|
|
return;
|
|
assert(gms_graphics_window);
|
|
|
|
/*
|
|
* On detecting a repaint request, note the flag in a local static variable,
|
|
* then set up a graphics delay to wait until, hopefully, the resize, if
|
|
* that's what caused it, is complete, and return. This makes resizing the
|
|
* window a lot smoother, since it prevents unnecessary region paints where
|
|
* we are receiving consecutive Glk arrange or redraw events.
|
|
*/
|
|
if (gms_graphics_repaint) {
|
|
deferred_repaint = true;
|
|
gms_graphics_repaint = false;
|
|
ignore_counter = GMS_GRAPHICS_REPAINT_WAIT - 1;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* If asked to ignore a given number of calls, decrement the ignore counter
|
|
* and return having done nothing more. This lets us delay graphics
|
|
* operations by a number of timeouts, providing animation timing and
|
|
* partial protection from resize event "storms".
|
|
*
|
|
* Note -- to wait for N timeouts, set the count of timeouts to be ignored
|
|
* to N-1.
|
|
*/
|
|
assert(ignore_counter >= 0);
|
|
if (ignore_counter > 0) {
|
|
ignore_counter--;
|
|
return;
|
|
}
|
|
|
|
/* Calculate the picture size, and synchronize screen buffer pointers. */
|
|
picture_size = gms_graphics_width * gms_graphics_height;
|
|
off_screen = gms_graphics_off_screen;
|
|
on_screen = gms_graphics_on_screen;
|
|
|
|
/*
|
|
* If we received a new picture, set up the local static variables for that
|
|
* picture -- decide on gamma correction, convert the color palette, and
|
|
* initialize the off_screen buffer to be the base picture.
|
|
*/
|
|
if (gms_graphics_new_picture) {
|
|
/* Initialize the off_screen buffer to be a copy of the base picture. */
|
|
free(off_screen);
|
|
off_screen = (type8 *)gms_malloc(picture_size * sizeof(*off_screen));
|
|
memcpy(off_screen, gms_graphics_bitmap,
|
|
picture_size * sizeof(*off_screen));
|
|
|
|
/* Note the buffer for freeing on cleanup. */
|
|
gms_graphics_off_screen = off_screen;
|
|
|
|
/*
|
|
* If the picture is animated, apply the first animation frames now.
|
|
* This is important, since they form an intrinsic part of the first
|
|
* displayed image (in type2 animation cases, perhaps _all_ of the
|
|
* first displayed image).
|
|
*/
|
|
if (gms_graphics_animated) {
|
|
gms_graphics_animate(off_screen,
|
|
gms_graphics_width, gms_graphics_height);
|
|
}
|
|
|
|
/*
|
|
* Select a suitable gamma for the picture, taking care to use the
|
|
* off-screen buffer.
|
|
*/
|
|
gms_graphics_current_gamma =
|
|
gms_graphics_select_gamma(off_screen,
|
|
gms_graphics_width,
|
|
gms_graphics_height,
|
|
gms_graphics_palette);
|
|
|
|
/*
|
|
* Pre-convert all the picture palette colors into their corresponding
|
|
* Glk colors.
|
|
*/
|
|
gms_graphics_convert_palette(gms_graphics_palette,
|
|
gms_graphics_current_gamma, palette);
|
|
|
|
/* Save the color count for possible queries later. */
|
|
gms_graphics_count_colors(off_screen,
|
|
gms_graphics_width, gms_graphics_height,
|
|
&gms_graphics_color_count, NULL);
|
|
}
|
|
|
|
/*
|
|
* For a new picture, or a repaint of a prior one, calculate new values for
|
|
* the x and y offsets used to draw image points, and set the on-screen
|
|
* buffer to an unused pixel value, in effect invalidating all on-screen
|
|
* data. Also, reset the saved image scan coordinates so that we scan for
|
|
* unpainted pixels from top left starting at layer zero, and clear the
|
|
* graphics window.
|
|
*/
|
|
if (gms_graphics_new_picture || deferred_repaint) {
|
|
/*
|
|
* Calculate the x and y offset to center the picture in the graphics
|
|
* window.
|
|
*/
|
|
gms_graphics_position_picture(gms_graphics_window,
|
|
GMS_GRAPHICS_PIXEL,
|
|
gms_graphics_width, gms_graphics_height,
|
|
&x_offset, &y_offset);
|
|
|
|
/*
|
|
* Reset all on-screen pixels to an unused value, guaranteed not to
|
|
* match any in a real picture. This forces all pixels to be repainted
|
|
* on a buffer/on-screen comparison.
|
|
*/
|
|
free(on_screen);
|
|
on_screen = (type8 *)gms_malloc(picture_size * sizeof(*on_screen));
|
|
memset(on_screen, GMS_GRAPHICS_UNUSED_PIXEL,
|
|
picture_size * sizeof(*on_screen));
|
|
|
|
/* Note the buffer for freeing on cleanup. */
|
|
gms_graphics_on_screen = on_screen;
|
|
|
|
/*
|
|
* Assign new layers to the current image. This sorts colors by usage
|
|
* and puts the most used colors in the lower layers. It also hands us
|
|
* a count of pixels in each layer, useful for knowing when to stop
|
|
* scanning for layers in the rendering loop.
|
|
*/
|
|
#ifndef GARGLK
|
|
gms_graphics_assign_layers(off_screen, on_screen,
|
|
gms_graphics_width, gms_graphics_height,
|
|
layers, layer_usage);
|
|
|
|
saved_layer = 0;
|
|
saved_x = 0;
|
|
saved_y = 0;
|
|
total_regions = 0;
|
|
#endif
|
|
|
|
/* Clear the graphics window. */
|
|
gms_graphics_clear_and_border(gms_graphics_window,
|
|
x_offset, y_offset,
|
|
GMS_GRAPHICS_PIXEL,
|
|
gms_graphics_width, gms_graphics_height);
|
|
|
|
/* Start a fresh picture rendering pass. */
|
|
yield_counter = 0;
|
|
|
|
/* Clear the new picture and deferred repaint flags. */
|
|
gms_graphics_new_picture = false;
|
|
deferred_repaint = false;
|
|
}
|
|
|
|
#ifndef GARGLK
|
|
/*
|
|
* Make a portion of an image pass, from lower to higher image layers,
|
|
* scanning for invalidated pixels that are in the current image layer we
|
|
* are painting. Each invalidated pixel gives rise to a region paint,
|
|
* which equates to one Glk rectangle fill.
|
|
*
|
|
* When the limit on regions is reached, save the current image pass layer
|
|
* and coordinates, and yield control to the main game playing code by
|
|
* returning. On the next call, pick up where we left off.
|
|
*
|
|
* As an optimization, we can leave the loop on the first empty layer we
|
|
* encounter. Since layers are ordered by complexity and color usage, all
|
|
* layers higher than the first unused one will also be empty, so we don't
|
|
* need to scan them.
|
|
*/
|
|
regions = 0;
|
|
for (layer = saved_layer;
|
|
layer < GMS_PALETTE_SIZE && layer_usage[layer] > 0; layer++) {
|
|
long index_row;
|
|
|
|
/*
|
|
* As an optimization to avoid multiplications in the loop, maintain a
|
|
* separate index row.
|
|
*/
|
|
index_row = saved_y * gms_graphics_width;
|
|
for (y = saved_y; y < gms_graphics_height; y++) {
|
|
for (x = saved_x; x < gms_graphics_width; x++) {
|
|
long index;
|
|
|
|
/* Get the index for this pixel. */
|
|
index = index_row + x;
|
|
assert(index < picture_size * sizeof(*off_screen));
|
|
|
|
/*
|
|
* Ignore pixels not in the current layer, and pixels not
|
|
* currently invalid (that is, ones whose on-screen represen-
|
|
* tation matches the off-screen buffer).
|
|
*/
|
|
if (layers[off_screen[index]] == layer
|
|
&& on_screen[index] != off_screen[index]) {
|
|
/*
|
|
* Rather than painting just one pixel, here we try to
|
|
* paint the maximal region we can for the layer of the
|
|
* given pixel.
|
|
*/
|
|
gms_graphics_paint_region(gms_graphics_window,
|
|
palette, layers,
|
|
off_screen, on_screen,
|
|
x, y, x_offset, y_offset,
|
|
GMS_GRAPHICS_PIXEL,
|
|
gms_graphics_width,
|
|
gms_graphics_height);
|
|
|
|
/*
|
|
* Increment count of regions handled, and yield, by
|
|
* returning, if the limit on paint regions is reached.
|
|
* Before returning, save the current layer and scan
|
|
* coordinates, so we can pick up here on the next call.
|
|
*/
|
|
regions++;
|
|
if (regions >= GMS_REPAINT_LIMIT) {
|
|
yield_counter++;
|
|
saved_layer = layer;
|
|
saved_x = x;
|
|
saved_y = y;
|
|
total_regions += regions;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Reset the saved x coordinate on y increment. */
|
|
saved_x = 0;
|
|
|
|
/* Update the index row on change of y. */
|
|
index_row += gms_graphics_width;
|
|
}
|
|
|
|
/* Reset the saved y coordinate on layer change. */
|
|
saved_y = 0;
|
|
}
|
|
|
|
/*
|
|
* If we reach this point, then we didn't get to the limit on regions
|
|
* painted on this pass. In that case, we've finished rendering the
|
|
* image.
|
|
*/
|
|
assert(regions < GMS_REPAINT_LIMIT);
|
|
total_regions += regions;
|
|
|
|
#else
|
|
gms_graphics_paint_everything
|
|
(gms_graphics_window,
|
|
palette, off_screen,
|
|
x_offset, y_offset,
|
|
gms_graphics_width,
|
|
gms_graphics_height);
|
|
#endif
|
|
|
|
/*
|
|
* If animated, and if animations are enabled, handle further animation
|
|
* frames, if any.
|
|
*/
|
|
if (gms_animation_enabled && gms_graphics_animated) {
|
|
int more_animation;
|
|
|
|
/*
|
|
* Reset the off-screen buffer to a copy of the base picture. This is
|
|
* the correct state for applying animation frames.
|
|
*/
|
|
memcpy(off_screen, gms_graphics_bitmap,
|
|
picture_size * sizeof(*off_screen));
|
|
|
|
/*
|
|
* Apply any further animations. If none, then stop the graphics
|
|
* "thread" and return. There's no more to be done until something
|
|
* restarts us.
|
|
*/
|
|
more_animation = gms_graphics_animate(off_screen,
|
|
gms_graphics_width,
|
|
gms_graphics_height);
|
|
if (!more_animation) {
|
|
/*
|
|
* There's one extra wrinkle here. The base picture we've just put
|
|
* into the off-screen buffer isn't really complete (and for type2
|
|
* animations, might be pure garbage), so if we happen to get a
|
|
* repaint after an animation has ended, the off-screen data we'll
|
|
* be painting could well look wrong.
|
|
*
|
|
* So... here we want to set the off-screen buffer to contain the
|
|
* final animation frame. Fortunately, we still have it in the
|
|
* on-screen buffer.
|
|
*/
|
|
memcpy(off_screen, on_screen, picture_size * sizeof(*off_screen));
|
|
gms_graphics_stop();
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Re-assign layers based on animation changes to the off-screen
|
|
* buffer.
|
|
*/
|
|
#ifndef GARGLK
|
|
gms_graphics_assign_layers(off_screen, on_screen,
|
|
gms_graphics_width, gms_graphics_height,
|
|
layers, layer_usage);
|
|
#endif
|
|
|
|
/*
|
|
* Set up an animation wait, adjusted here by the number of times we
|
|
* had to yield while rendering, as we're now that late with animations,
|
|
* and capped at zero, as we can't do anything to compensate for being
|
|
* too late. In practice, we're running too close to the edge to have
|
|
* much of an effect here, but nevertheless...
|
|
*/
|
|
ignore_counter = GMS_GRAPHICS_ANIMATION_WAIT - 1;
|
|
if (yield_counter > ignore_counter)
|
|
ignore_counter = 0;
|
|
else
|
|
ignore_counter -= yield_counter;
|
|
|
|
/* Start a fresh picture rendering pass. */
|
|
yield_counter = 0;
|
|
#ifndef GARGLK
|
|
saved_layer = 0;
|
|
saved_x = 0;
|
|
saved_y = 0;
|
|
total_regions = 0;
|
|
#endif
|
|
} else {
|
|
/*
|
|
* Not an animated picture, so just stop graphics, as again, there's
|
|
* no more to be done until something restarts us.
|
|
*/
|
|
gms_graphics_stop();
|
|
}
|
|
}
|
|
|
|
void Magnetic::ms_showpic(type32 picture, type8 mode) {
|
|
type8 *bitmap, animated;
|
|
type16 width, height, palette[GMS_PALETTE_SIZE];
|
|
long picture_bytes;
|
|
glui32 crc;
|
|
|
|
/* See if the mode indicates no graphics. */
|
|
if (mode == 0) {
|
|
/* Note that the interpreter turned graphics off. */
|
|
gms_graphics_interpreter = false;
|
|
|
|
/*
|
|
* If we are currently displaying the graphics window, stop any update
|
|
* "thread" and turn off graphics.
|
|
*/
|
|
if (gms_graphics_enabled && gms_graphics_are_displayed()) {
|
|
gms_graphics_stop();
|
|
gms_graphics_close();
|
|
}
|
|
|
|
/* Nothing more to do now graphics are off. */
|
|
return;
|
|
}
|
|
|
|
/* Note that the interpreter turned graphics on. */
|
|
gms_graphics_interpreter = true;
|
|
|
|
/*
|
|
* Obtain the image details for the requested picture. The call returns
|
|
* NULL if there's a problem with the picture.
|
|
*/
|
|
bitmap = ms_extract(picture, &width, &height, palette, &animated);
|
|
if (!bitmap)
|
|
return;
|
|
|
|
/* Note the last thing passed to ms_extract, in case of graphics restarts. */
|
|
gms_graphics_picture = picture;
|
|
|
|
/* Calculate the picture size, and the CRC for the bitmap data. */
|
|
picture_bytes = width * height * sizeof(*bitmap);
|
|
crc = gms_get_buffer_crc(bitmap, picture_bytes);
|
|
|
|
/*
|
|
* If there is no change of picture, we might be able to largely ignore the
|
|
* call. Check for a change, and if we don't see one, and if graphics are
|
|
* enabled and being displayed, we can safely ignore the call.
|
|
*/
|
|
if (width == gms_graphics_width
|
|
&& height == gms_graphics_height
|
|
&& crc == pic_current_crc
|
|
&& gms_graphics_enabled && gms_graphics_are_displayed())
|
|
return;
|
|
|
|
/*
|
|
* We know now that this is either a genuine change of picture, or graphics
|
|
* were off and have been turned on. So, record picture details, ensure
|
|
* graphics is on, set the flags, and start the background graphics update.
|
|
*/
|
|
|
|
/*
|
|
* Save the picture details for the update code. Here we take a complete
|
|
* local copy of the bitmap, since the interpreter core may reuse part of
|
|
* its memory for animations.
|
|
*/
|
|
free(gms_graphics_bitmap);
|
|
gms_graphics_bitmap = (type8 *)gms_malloc(picture_bytes);
|
|
memcpy(gms_graphics_bitmap, bitmap, picture_bytes);
|
|
gms_graphics_width = width;
|
|
gms_graphics_height = height;
|
|
memcpy(gms_graphics_palette, palette, sizeof(palette));
|
|
gms_graphics_animated = animated;
|
|
|
|
/* Retain the new picture CRC. */
|
|
pic_current_crc = crc;
|
|
|
|
/*
|
|
* If graphics are enabled, ensure the window is displayed, set the
|
|
* appropriate flags, and start graphics update. If they're not enabled,
|
|
* the picture details will simply stick around in module variables until
|
|
* they are required.
|
|
*/
|
|
if (gms_graphics_enabled) {
|
|
/*
|
|
* Ensure graphics on, then set the new picture flag and start the
|
|
* updating "thread".
|
|
*/
|
|
if (gms_graphics_open()) {
|
|
gms_graphics_new_picture = true;
|
|
gms_graphics_start();
|
|
}
|
|
}
|
|
}
|
|
|
|
int Magnetic::gms_graphics_get_picture_details(int *width, int *height, int *is_animated) {
|
|
if (gms_graphics_picture_is_available()) {
|
|
if (width)
|
|
*width = gms_graphics_width;
|
|
if (height)
|
|
*height = gms_graphics_height;
|
|
if (is_animated)
|
|
*is_animated = gms_graphics_animated;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int Magnetic::gms_graphics_get_rendering_details(const char **gamma,
|
|
int *color_count, int *is_active) {
|
|
if (gms_graphics_enabled && gms_graphics_are_displayed()) {
|
|
/*
|
|
* Return the string representing the gamma correction. If racing
|
|
* with timeouts, we might return the gamma for the last picture.
|
|
*/
|
|
if (gamma) {
|
|
assert(gms_graphics_current_gamma);
|
|
*gamma = gms_graphics_current_gamma->level;
|
|
}
|
|
|
|
/*
|
|
* Return the color count noted by timeouts on the first timeout
|
|
* following a new picture. Again, we might return the one for
|
|
* the prior picture.
|
|
*/
|
|
if (color_count)
|
|
*color_count = gms_graphics_color_count;
|
|
|
|
/* Return graphics active flag. */
|
|
if (is_active)
|
|
*is_active = gms_graphics_active;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int Magnetic::gms_graphics_interpreter_enabled() {
|
|
return gms_graphics_interpreter;
|
|
}
|
|
|
|
void Magnetic::gms_graphics_cleanup() {
|
|
free(gms_graphics_bitmap);
|
|
gms_graphics_bitmap = NULL;
|
|
free(gms_graphics_off_screen);
|
|
gms_graphics_off_screen = NULL;
|
|
free(gms_graphics_on_screen);
|
|
gms_graphics_on_screen = NULL;
|
|
|
|
gms_graphics_animated = false;
|
|
gms_graphics_picture = 0;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------*/
|
|
/* Glk port status line functions */
|
|
/*---------------------------------------------------------------------*/
|
|
|
|
void Magnetic::ms_statuschar(type8 c) {
|
|
static char buffer_[GMS_STATBUFFER_LENGTH];
|
|
static int length = 0;
|
|
|
|
/*
|
|
* If the status character is newline, transfer locally buffered data to
|
|
* the common buffer, empty the local buffer; otherwise, if space permits,
|
|
* buffer the character.
|
|
*/
|
|
if (c == '\n') {
|
|
memcpy(gms_status_buffer, buffer_, length);
|
|
gms_status_length = length;
|
|
|
|
length = 0;
|
|
} else {
|
|
if (length < (int)sizeof(buffer_))
|
|
buffer_[length++] = c;
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_status_update() {
|
|
uint width, height;
|
|
int index;
|
|
assert(gms_status_window);
|
|
|
|
glk_window_get_size(gms_status_window, &width, &height);
|
|
if (height > 0) {
|
|
glk_window_clear(gms_status_window);
|
|
glk_window_move_cursor(gms_status_window, 0, 0);
|
|
glk_set_window(gms_status_window);
|
|
|
|
glk_set_style(style_User1);
|
|
for (index = 0; index < (int)width; index++)
|
|
glk_put_char(' ');
|
|
glk_window_move_cursor(gms_status_window, 1, 0);
|
|
|
|
if (gms_status_length > 0) {
|
|
/*
|
|
* Output each character from the status line buffer. If the
|
|
* character is Tab, position the cursor to eleven characters shy
|
|
* of the status window right.
|
|
*/
|
|
for (index = 0; index < gms_status_length; index++) {
|
|
if (gms_status_buffer[index] == '\t')
|
|
glk_window_move_cursor(gms_status_window, width - 11, 0);
|
|
else
|
|
glk_put_char(gms_status_buffer[index]);
|
|
}
|
|
} else {
|
|
const char *game_name;
|
|
|
|
/*
|
|
* We have no status line to display, so print the game's name, or
|
|
* a standard message if unable to identify the game. Having no
|
|
* status line is common with Magnetic Windows games, which don't,
|
|
* in general, seem to use one.
|
|
*/
|
|
game_name = gms_gameid_get_game_name();
|
|
glk_put_string(game_name ? game_name : "ScummVM Magnetic version 2.3");
|
|
}
|
|
|
|
glk_set_window(gms_main_window);
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_status_print() {
|
|
static char buffer_[GMS_STATBUFFER_LENGTH];
|
|
static int length = 0;
|
|
|
|
int index, column;
|
|
|
|
/*
|
|
* Do nothing if there is no status line to print, or if the status
|
|
* line hasn't changed since last printed.
|
|
*/
|
|
if (gms_status_length == 0
|
|
|| (gms_status_length == length
|
|
&& strncmp(buffer_, gms_status_buffer, length)) == 0)
|
|
return;
|
|
|
|
/* Set fixed width font to try to preserve status line formatting. */
|
|
glk_set_style(style_Preformatted);
|
|
|
|
/* Bracket, and output the status line buffer_. */
|
|
glk_put_string("[ ");
|
|
column = 1;
|
|
for (index = 0; index < gms_status_length; index++) {
|
|
/*
|
|
* If the character is Tab, position the cursor to eleven characters
|
|
* shy of the right edge. In the absence of the real window dimensions,
|
|
* we'll select 74 characters, which gives us a 78 character status
|
|
* line; pretty standard.
|
|
*/
|
|
if (gms_status_buffer[index] == '\t') {
|
|
while (column <= GMS_DEFAULT_STATUS_WIDTH - 11) {
|
|
glk_put_char(' ');
|
|
column++;
|
|
}
|
|
} else {
|
|
glk_put_char(gms_status_buffer[index]);
|
|
column++;
|
|
}
|
|
}
|
|
|
|
while (column <= GMS_DEFAULT_STATUS_WIDTH) {
|
|
glk_put_char(' ');
|
|
column++;
|
|
}
|
|
glk_put_string(" ]\n");
|
|
|
|
/* Save the details of the printed status buffer_. */
|
|
memcpy(buffer_, gms_status_buffer, gms_status_length);
|
|
length = gms_status_length;
|
|
}
|
|
|
|
void Magnetic::gms_status_notify() {
|
|
if (gms_status_window)
|
|
gms_status_update();
|
|
else
|
|
gms_status_print();
|
|
}
|
|
|
|
void Magnetic::gms_status_redraw() {
|
|
if (gms_status_window) {
|
|
winid_t parent;
|
|
|
|
/*
|
|
* Rearrange the status window, without changing its actual arrangement
|
|
* in any way. This is a hack to work round incorrect window repainting
|
|
* in Xglk; it forces a complete repaint of affected windows on Glk
|
|
* window resize and arrange events, and works in part because Xglk
|
|
* doesn't check for actual arrangement changes in any way before
|
|
* invalidating its windows. The hack should be harmless to Glk
|
|
* libraries other than Xglk, moreover, we're careful to activate it
|
|
* only on resize and arrange events.
|
|
*/
|
|
parent = glk_window_get_parent(gms_status_window);
|
|
glk_window_set_arrangement(parent,
|
|
winmethod_Above | winmethod_Fixed, 1, NULL);
|
|
|
|
gms_status_update();
|
|
}
|
|
}
|
|
|
|
/*---------------------------------------------------------------------*/
|
|
/* Glk port output functions */
|
|
/*---------------------------------------------------------------------*/
|
|
|
|
void Magnetic::gms_output_register_help_request() {
|
|
gms_help_requested = true;
|
|
}
|
|
|
|
void Magnetic::gms_output_silence_help_hints() {
|
|
gms_help_hints_silenced = true;
|
|
}
|
|
|
|
void Magnetic::gms_output_provide_help_hint() {
|
|
if (gms_help_requested && !gms_help_hints_silenced) {
|
|
glk_set_style(style_Emphasized);
|
|
glk_put_string("[Try 'glk help' for help on special interpreter"
|
|
" commands]\n");
|
|
|
|
gms_help_requested = false;
|
|
glk_set_style(style_Normal);
|
|
}
|
|
}
|
|
|
|
int Magnetic::gms_game_prompted() {
|
|
int result;
|
|
|
|
result = gms_output_prompt;
|
|
gms_output_prompt = false;
|
|
|
|
return result;
|
|
}
|
|
|
|
void Magnetic::gms_detect_game_prompt() {
|
|
int index;
|
|
|
|
gms_output_prompt = false;
|
|
|
|
/*
|
|
* Search for a prompt across any last unterminated buffered line; a prompt
|
|
* is any non-space character on that line.
|
|
*/
|
|
for (index = gms_output_length - 1;
|
|
index >= 0 && gms_output_buffer[index] != '\n'; index--) {
|
|
if (gms_output_buffer[index] != ' ') {
|
|
gms_output_prompt = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_output_delete() {
|
|
free(gms_output_buffer);
|
|
gms_output_buffer = NULL;
|
|
gms_output_allocation = gms_output_length = 0;
|
|
}
|
|
|
|
void Magnetic::gms_output_flush() {
|
|
assert(glk_stream_get_current());
|
|
|
|
if (gms_output_length > 0) {
|
|
/*
|
|
* See if the game issued a standard prompt, then print the buffer to
|
|
* the main window. If providing a help hint, position that before
|
|
* the game's prompt (if any).
|
|
*/
|
|
gms_detect_game_prompt();
|
|
glk_set_style(style_Normal);
|
|
|
|
if (gms_output_prompt) {
|
|
int index;
|
|
|
|
for (index = gms_output_length - 1;
|
|
index >= 0 && gms_output_buffer[index] != '\n';)
|
|
index--;
|
|
|
|
glk_put_buffer(gms_output_buffer, index + 1);
|
|
gms_output_provide_help_hint();
|
|
glk_put_buffer(gms_output_buffer + index + 1,
|
|
gms_output_length - index - 1);
|
|
} else {
|
|
glk_put_buffer(gms_output_buffer, gms_output_length);
|
|
gms_output_provide_help_hint();
|
|
}
|
|
|
|
gms_output_delete();
|
|
}
|
|
}
|
|
|
|
void Magnetic::ms_putchar(type8 c) {
|
|
int bytes;
|
|
assert(gms_output_length <= gms_output_allocation);
|
|
|
|
/*
|
|
* See if the character is a backspace. Magnetic Scrolls games can send
|
|
* backspace characters to the display. We'll need to handle such
|
|
* characters specially, by taking the last character out of the buffer.
|
|
*/
|
|
if (c == '\b') {
|
|
if (gms_output_length > 0)
|
|
gms_output_length--;
|
|
|
|
return;
|
|
}
|
|
|
|
/* Grow the output buffer if necessary, then add the character. */
|
|
for (bytes = gms_output_allocation; bytes < gms_output_length + 1;)
|
|
bytes = bytes == 0 ? 1 : bytes << 1;
|
|
|
|
if (bytes > gms_output_allocation) {
|
|
gms_output_buffer = (char *)gms_realloc(gms_output_buffer, bytes);
|
|
gms_output_allocation = bytes;
|
|
}
|
|
|
|
gms_output_buffer[gms_output_length++] = c;
|
|
}
|
|
|
|
void Magnetic::gms_styled_string(glui32 style, const char *message) {
|
|
assert(message);
|
|
|
|
glk_set_style(style);
|
|
glk_put_string(message);
|
|
glk_set_style(style_Normal);
|
|
}
|
|
|
|
void Magnetic::gms_styled_char(glui32 style, char c) {
|
|
char str[2];
|
|
|
|
str[0] = c;
|
|
str[1] = '\0';
|
|
gms_styled_string(style, str);
|
|
}
|
|
|
|
void Magnetic::gms_standout_string(const char *message) {
|
|
gms_styled_string(style_Emphasized, message);
|
|
}
|
|
|
|
void Magnetic::gms_normal_string(const char *message) {
|
|
gms_styled_string(style_Normal, message);
|
|
}
|
|
|
|
void Magnetic::gms_normal_char(char c) {
|
|
gms_styled_char(style_Normal, c);
|
|
}
|
|
|
|
void Magnetic::gms_header_string(const char *message) {
|
|
gms_styled_string(style_Header, message);
|
|
}
|
|
|
|
void Magnetic::gms_banner_string(const char *message) {
|
|
gms_styled_string(style_Subheader, message);
|
|
}
|
|
|
|
void Magnetic::ms_flush() {
|
|
}
|
|
|
|
/*---------------------------------------------------------------------*/
|
|
/* Glk port hint functions */
|
|
/*---------------------------------------------------------------------*/
|
|
|
|
type16 Magnetic::gms_get_hint_max_node(const struct ms_hint hints_[], type16 node) {
|
|
const struct ms_hint *hint;
|
|
int index;
|
|
type16 max_node;
|
|
assert(hints_);
|
|
|
|
hint = hints_ + node;
|
|
max_node = node;
|
|
|
|
switch (hint->nodetype) {
|
|
case GMS_HINT_TYPE_TEXT:
|
|
break;
|
|
|
|
case GMS_HINT_TYPE_FOLDER:
|
|
/*
|
|
* Recursively find the maximum node reference for each link, and keep
|
|
* the largest value found.
|
|
*/
|
|
for (index = 0; index < hint->elcount; index++) {
|
|
type16 link_max;
|
|
|
|
link_max = gms_get_hint_max_node(hints_, hint->links[index]);
|
|
if (link_max > max_node)
|
|
max_node = link_max;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
gms_fatal("GLK: Invalid hints_ node type encountered");
|
|
glk_exit();
|
|
}
|
|
|
|
/*
|
|
* Return the largest node reference found, capped to avoid overlapping the
|
|
* special end-hints_ value.
|
|
*/
|
|
return max_node < GMS_HINTS_DONE ? max_node : GMS_HINTS_DONE - 1;
|
|
}
|
|
|
|
const char *Magnetic::gms_get_hint_content(const struct ms_hint hints_[], type16 node, int number) {
|
|
const struct ms_hint *hint;
|
|
int offset, index;
|
|
assert(hints_);
|
|
|
|
hint = hints_ + node;
|
|
|
|
/* Run through content until 'number' strings found. */
|
|
offset = 0;
|
|
for (index = 0; index < number; index++)
|
|
offset += strlen(hint->content + offset) + 1;
|
|
|
|
/* Return the start of the number'th string encountered. */
|
|
return hint->content + offset;
|
|
}
|
|
|
|
const char *Magnetic::gms_get_hint_topic(const ms_hint hints_[], type16 node) {
|
|
assert(hints_);
|
|
|
|
if (node == GMS_HINT_ROOT_NODE) {
|
|
/* If the node is the root node, return a generic string. */
|
|
return GMS_GENERIC_TOPIC;
|
|
} else {
|
|
type16 parent;
|
|
int index;
|
|
const char *topic;
|
|
|
|
/*
|
|
* Search the parent for a link to node, and use that as the hint topic;
|
|
* NULL if none found.
|
|
*/
|
|
parent = hints_[node].parent;
|
|
|
|
topic = NULL;
|
|
for (index = 0; index < hints_[parent].elcount; index++) {
|
|
if (hints_[parent].links[index] == node) {
|
|
topic = gms_get_hint_content(hints_, parent, index);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return topic ? topic : GMS_GENERIC_TOPIC;
|
|
}
|
|
}
|
|
|
|
int Magnetic::gms_hint_open() {
|
|
if (!gms_hint_menu_window) {
|
|
assert(!gms_hint_text_window);
|
|
|
|
/*
|
|
* Open the hint menu window. The initial size is two lines, but we'll
|
|
* change this later to suit the hint.
|
|
*/
|
|
gms_hint_menu_window = glk_window_open(gms_main_window,
|
|
winmethod_Above | winmethod_Fixed,
|
|
2, wintype_TextGrid, 0);
|
|
if (!gms_hint_menu_window)
|
|
return false;
|
|
|
|
/*
|
|
* Now open the hints text window. This is set to be 100% of the size
|
|
* of the main window, so should cover what remains of it completely.
|
|
*/
|
|
gms_hint_text_window = glk_window_open(gms_main_window,
|
|
winmethod_Above
|
|
| winmethod_Proportional,
|
|
100, wintype_TextBuffer, 0);
|
|
if (!gms_hint_text_window) {
|
|
glk_window_close(gms_hint_menu_window, NULL);
|
|
gms_hint_menu_window = NULL;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Magnetic::Magnetic::gms_hint_close() {
|
|
if (gms_hint_menu_window) {
|
|
assert(gms_hint_text_window);
|
|
|
|
glk_window_close(gms_hint_menu_window, NULL);
|
|
gms_hint_menu_window = NULL;
|
|
glk_window_close(gms_hint_text_window, NULL);
|
|
gms_hint_text_window = NULL;
|
|
}
|
|
}
|
|
|
|
int Magnetic::gms_hint_windows_available() {
|
|
return (gms_hint_menu_window && gms_hint_text_window);
|
|
}
|
|
|
|
void Magnetic::gms_hint_menu_print(int line, int column, const char *string_,
|
|
glui32 width, glui32 height) {
|
|
assert(string_);
|
|
|
|
/* Ignore the call if the text position is outside the window. */
|
|
if (!(line > (int)height || column > (int)width)) {
|
|
if (gms_hint_windows_available()) {
|
|
int posn, index;
|
|
|
|
glk_window_move_cursor(gms_hint_menu_window, column, line);
|
|
glk_set_window(gms_hint_menu_window);
|
|
|
|
/* Write until the end of the string_, or the end of the window. */
|
|
for (posn = column, index = 0;
|
|
posn < (int)width && index < (int)strlen(string_); posn++, index++) {
|
|
glk_put_char(string_[index]);
|
|
}
|
|
|
|
glk_set_window(gms_main_window);
|
|
} else {
|
|
static int current_line = 0; /* Retained line number */
|
|
static int current_column = 0; /* Retained col number */
|
|
|
|
int index;
|
|
|
|
/*
|
|
* Check the line number against the last one output. If it is less,
|
|
* assume the start of a new block. In this case, perform a hokey
|
|
* type of screen clear.
|
|
*/
|
|
if (line < current_line) {
|
|
for (index = 0; index < (int)height; index++)
|
|
gms_normal_char('\n');
|
|
|
|
current_line = 0;
|
|
current_column = 0;
|
|
}
|
|
|
|
/* Print blank lines until the target line is reached. */
|
|
for (; current_line < line; current_line++) {
|
|
gms_normal_char('\n');
|
|
current_column = 0;
|
|
}
|
|
|
|
/* Now print spaces until the target column is reached. */
|
|
for (; current_column < column; current_column++)
|
|
gms_normal_char(' ');
|
|
|
|
/*
|
|
* Write characters until the end of the string_, or the end of the
|
|
* (self-imposed not-really-there) window.
|
|
*/
|
|
for (index = 0;
|
|
current_column < (int)width && index < (int)strlen(string_);
|
|
current_column++, index++) {
|
|
gms_normal_char(string_[index]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_hint_menu_header(int line, const char *string_,
|
|
glui32 width, glui32 height) {
|
|
int posn, length;
|
|
assert(string_);
|
|
|
|
/* Output the text in the approximate line center. */
|
|
length = strlen(string_);
|
|
posn = length < (int)width ? (width - length) / 2 : 0;
|
|
gms_hint_menu_print(line, posn, string_, width, height);
|
|
}
|
|
|
|
void Magnetic::gms_hint_menu_justify(int line, const char *left_string,
|
|
const char *right_string, glui32 width, glui32 height) {
|
|
int posn, length;
|
|
assert(left_string && right_string);
|
|
|
|
/* Write left text normally to window left. */
|
|
gms_hint_menu_print(line, 0, left_string, width, height);
|
|
|
|
/* Output the right text flush with the right of the window. */
|
|
length = strlen(right_string);
|
|
posn = length < (int)width ? width - length : 0;
|
|
gms_hint_menu_print(line, posn, right_string, width, height);
|
|
}
|
|
|
|
void Magnetic::gms_hint_text_print(const char *string_) {
|
|
assert(string_);
|
|
|
|
if (gms_hint_windows_available()) {
|
|
glk_set_window(gms_hint_text_window);
|
|
glk_put_string(string_);
|
|
glk_set_window(gms_main_window);
|
|
} else
|
|
gms_normal_string(string_);
|
|
}
|
|
|
|
void Magnetic::gms_hint_menutext_start() {
|
|
/*
|
|
* Twiddle for non-windowing libraries; 'clear' the main window by writing
|
|
* a null string at line 1, then a null string at line 0. This works
|
|
* because we know the current output line in gms_hint_menu_print() is zero,
|
|
* since we set it that way with gms_hint_menutext_done(), or if this is
|
|
* the first call, then that's its initial value.
|
|
*/
|
|
if (!gms_hint_windows_available()) {
|
|
gms_hint_menu_print(1, 0, "",
|
|
GMS_HINT_DEFAULT_WIDTH, GMS_HINT_DEFAULT_HEIGHT);
|
|
gms_hint_menu_print(0, 0, "",
|
|
GMS_HINT_DEFAULT_WIDTH, GMS_HINT_DEFAULT_HEIGHT);
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_hint_menutext_done() {
|
|
/*
|
|
* Twiddle for non-windowing libraries; 'clear' the main window by writing
|
|
* an empty string to line zero. For windowing Glk libraries, this function
|
|
* does nothing.
|
|
*/
|
|
if (!gms_hint_windows_available()) {
|
|
gms_hint_menu_print(0, 0, "",
|
|
GMS_HINT_DEFAULT_WIDTH, GMS_HINT_DEFAULT_HEIGHT);
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_hint_menutext_char_event(event_t *event) {
|
|
assert(event);
|
|
|
|
if (gms_hint_windows_available()) {
|
|
glk_request_char_event(gms_hint_menu_window);
|
|
glk_request_char_event(gms_hint_text_window);
|
|
|
|
gms_event_wait(evtype_CharInput, event);
|
|
assert(event->window == gms_hint_menu_window
|
|
|| event->window == gms_hint_text_window);
|
|
|
|
glk_cancel_char_event(gms_hint_menu_window);
|
|
glk_cancel_char_event(gms_hint_text_window);
|
|
} else {
|
|
glk_request_char_event(gms_main_window);
|
|
gms_event_wait(evtype_CharInput, event);
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_hint_arrange_windows(int requested_lines, glui32 *width, glui32 *height) {
|
|
if (gms_hint_windows_available()) {
|
|
winid_t parent;
|
|
|
|
/* Resize the hint menu window to fit the current hint. */
|
|
parent = glk_window_get_parent(gms_hint_menu_window);
|
|
glk_window_set_arrangement(parent,
|
|
winmethod_Above | winmethod_Fixed,
|
|
requested_lines, NULL);
|
|
|
|
uint width_temp, height_temp;
|
|
|
|
/* Measure, and return the size of the hint menu window. */
|
|
glk_window_get_size(gms_hint_menu_window, &width_temp, &height_temp);
|
|
|
|
*width = width_temp;
|
|
*height = height_temp;
|
|
|
|
/* Clear both the hint menu and the hint text window. */
|
|
glk_window_clear(gms_hint_menu_window);
|
|
glk_window_clear(gms_hint_text_window);
|
|
} else {
|
|
/*
|
|
* No hints windows, so default width and height. The hints output
|
|
* functions will cope with this.
|
|
*/
|
|
if (width)
|
|
*width = GMS_HINT_DEFAULT_WIDTH;
|
|
if (height)
|
|
*height = GMS_HINT_DEFAULT_HEIGHT;
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_hint_display_folder(const ms_hint hints_[],
|
|
const int cursor[], type16 node) {
|
|
glui32 width, height;
|
|
int line, index;
|
|
assert(hints_ && cursor);
|
|
|
|
/*
|
|
* Arrange windows to suit the hint folder. For a folder menu window we
|
|
* use one line for each element, three for the controls, and two spacers,
|
|
* making a total of five additional lines. Width and height receive the
|
|
* actual menu window dimensions.
|
|
*/
|
|
gms_hint_arrange_windows(hints_[node].elcount + 5, &width, &height);
|
|
|
|
/* Paint in the menu header. */
|
|
line = 0;
|
|
gms_hint_menu_header(line++,
|
|
gms_get_hint_topic(hints_, node),
|
|
width, height);
|
|
gms_hint_menu_justify(line++,
|
|
" N = next subject ", " P = previous ",
|
|
width, height);
|
|
gms_hint_menu_justify(line++,
|
|
" RETURN = read subject ",
|
|
node == GMS_HINT_ROOT_NODE
|
|
? " Q = resume game " : " Q = previous menu ",
|
|
width, height);
|
|
|
|
/*
|
|
* Output a blank line, then the menu for the node's folder hint. The folder
|
|
* text for the selected hint is preceded by a '>' pointer.
|
|
*/
|
|
line++;
|
|
for (index = 0; index < hints_[node].elcount; index++) {
|
|
gms_hint_menu_print(line, 3,
|
|
index == cursor[node] ? ">" : " ",
|
|
width, height);
|
|
gms_hint_menu_print(line++, 5,
|
|
gms_get_hint_content(hints_, node, index),
|
|
width, height);
|
|
}
|
|
|
|
/*
|
|
* Terminate with a blank line; using a single space here improves cursor
|
|
* positioning for optimized output libraries (for example, without it,
|
|
* curses output will leave the cursor at the end of the previous line).
|
|
*/
|
|
gms_hint_menu_print(line, 0, " ", width, height);
|
|
}
|
|
|
|
void Magnetic::gms_hint_display_text(const ms_hint hints_[],
|
|
const int cursor[], type16 node) {
|
|
glui32 width, height;
|
|
int line, index;
|
|
assert(hints_ && cursor);
|
|
|
|
/*
|
|
* Arrange windows to suit the hint text. For a hint menu, we use a simple
|
|
* two-line set of controls; everything else is in the hints_ text window.
|
|
* Width and height receive the actual menu window dimensions.
|
|
*/
|
|
gms_hint_arrange_windows(2, &width, &height);
|
|
|
|
/* Paint in a short menu header. */
|
|
line = 0;
|
|
gms_hint_menu_header(line++,
|
|
gms_get_hint_topic(hints_, node),
|
|
width, height);
|
|
gms_hint_menu_justify(line++,
|
|
" RETURN = read hint ", " Q = previous menu ",
|
|
width, height);
|
|
|
|
/*
|
|
* Output hints_ to the hints_ text window. hints_ not yet exposed are
|
|
* indicated by the cursor for the hint, and are displayed as a dash.
|
|
*/
|
|
gms_hint_text_print("\n");
|
|
for (index = 0; index < hints_[node].elcount; index++) {
|
|
char buf[16];
|
|
|
|
sprintf(buf, "%3d. ", index + 1);
|
|
gms_hint_text_print(buf);
|
|
|
|
gms_hint_text_print(index < cursor[node]
|
|
? gms_get_hint_content(hints_, node, index) : "-");
|
|
gms_hint_text_print("\n");
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_hint_display(const ms_hint hints_[], const int cursor[], type16 node) {
|
|
assert(hints_ && cursor);
|
|
|
|
switch (hints_[node].nodetype) {
|
|
case GMS_HINT_TYPE_TEXT:
|
|
gms_hint_display_text(hints_, cursor, node);
|
|
break;
|
|
|
|
case GMS_HINT_TYPE_FOLDER:
|
|
gms_hint_display_folder(hints_, cursor, node);
|
|
break;
|
|
|
|
default:
|
|
gms_fatal("GLK: Invalid hints_ node type encountered");
|
|
glk_exit();
|
|
}
|
|
}
|
|
|
|
type16 Magnetic::gms_hint_handle_folder(const ms_hint hints_[],
|
|
int cursor[], type16 node, glui32 keycode) {
|
|
unsigned char response;
|
|
type16 next_node;
|
|
assert(hints_ && cursor);
|
|
|
|
/* Convert key code into a single response character. */
|
|
switch (keycode) {
|
|
case keycode_Down:
|
|
response = 'N';
|
|
break;
|
|
case keycode_Up:
|
|
response = 'P';
|
|
break;
|
|
case keycode_Right:
|
|
case keycode_Return:
|
|
response = '\n';
|
|
break;
|
|
case keycode_Left:
|
|
case keycode_Escape:
|
|
response = 'Q';
|
|
break;
|
|
default:
|
|
response = keycode <= BYTE_MAX_VAL ? glk_char_to_upper(keycode) : 0;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Now handle the response character. We'll default the next node to be
|
|
* this node, but a response case can change it.
|
|
*/
|
|
next_node = node;
|
|
switch (response) {
|
|
case 'N':
|
|
/* Advance the hint cursor, wrapping at the folder end. */
|
|
if (cursor[node] < hints_[node].elcount - 1)
|
|
cursor[node]++;
|
|
else
|
|
cursor[node] = 0;
|
|
break;
|
|
|
|
case 'P':
|
|
/* Regress the hint cursor, wrapping at the folder start. */
|
|
if (cursor[node] > 0)
|
|
cursor[node]--;
|
|
else
|
|
cursor[node] = hints_[node].elcount - 1;
|
|
break;
|
|
|
|
case '\n':
|
|
/* The next node is the hint node at the cursor position. */
|
|
next_node = hints_[node].links[cursor[node]];
|
|
break;
|
|
|
|
case 'Q':
|
|
/* If root, we're done; if not, next node is node's parent. */
|
|
next_node = node == GMS_HINT_ROOT_NODE
|
|
? GMS_HINTS_DONE : hints_[node].parent;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return next_node;
|
|
}
|
|
|
|
type16 Magnetic::gms_hint_handle_text(const ms_hint hints_[],
|
|
int cursor[], type16 node, glui32 keycode) {
|
|
unsigned char response;
|
|
type16 next_node;
|
|
assert(hints_ && cursor);
|
|
|
|
/* Convert key code into a single response character. */
|
|
switch (keycode) {
|
|
case keycode_Right:
|
|
case keycode_Return:
|
|
response = '\n';
|
|
break;
|
|
case keycode_Left:
|
|
case keycode_Escape:
|
|
response = 'Q';
|
|
break;
|
|
default:
|
|
response = keycode <= BYTE_MAX_VAL ? glk_char_to_upper(keycode) : 0;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Now handle the response character. We'll default the next node to be
|
|
* this node, but a response case can change it.
|
|
*/
|
|
next_node = node;
|
|
switch (response) {
|
|
case '\n':
|
|
/* If not at end of the hint, advance the hint cursor. */
|
|
if (cursor[node] < hints_[node].elcount)
|
|
cursor[node]++;
|
|
break;
|
|
|
|
case 'Q':
|
|
/* Done with this hint node, so next node is its parent. */
|
|
next_node = hints_[node].parent;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return next_node;
|
|
}
|
|
|
|
type16 Magnetic::gms_hint_handle(const ms_hint hints_[],
|
|
int cursor[], type16 node, glui32 keycode) {
|
|
type16 next_node;
|
|
assert(hints_ && cursor);
|
|
|
|
next_node = GMS_HINT_ROOT_NODE;
|
|
switch (hints_[node].nodetype) {
|
|
case GMS_HINT_TYPE_TEXT:
|
|
next_node = gms_hint_handle_text(hints_, cursor, node, keycode);
|
|
break;
|
|
|
|
case GMS_HINT_TYPE_FOLDER:
|
|
next_node = gms_hint_handle_folder(hints_, cursor, node, keycode);
|
|
break;
|
|
|
|
default:
|
|
gms_fatal("GLK: Invalid hints_ node type encountered");
|
|
glk_exit();
|
|
}
|
|
|
|
return next_node;
|
|
}
|
|
|
|
type8 Magnetic::ms_showhints(ms_hint *hints_) {
|
|
type16 hint_count;
|
|
glui32 crc;
|
|
assert(hints_);
|
|
|
|
/*
|
|
* Find the number of hints_ in the array. To do this, we'll visit every
|
|
* node in a tree search, starting at the root, to locate the maximum node
|
|
* number found, then add one to that. It's a pity that the interpreter
|
|
* doesn't hand us this information directly.
|
|
*/
|
|
hint_count = gms_get_hint_max_node(hints_, GMS_HINT_ROOT_NODE) + 1;
|
|
|
|
/*
|
|
* Calculate a CRC for the hints_ array data. If the CRC has changed, or
|
|
* this is the first call, assign a new cursor array.
|
|
*/
|
|
crc = gms_get_buffer_crc(hints_, hint_count * sizeof(*hints_));
|
|
if (crc != hints_current_crc || !hints_crc_initialized) {
|
|
int bytes;
|
|
|
|
/* Allocate new cursors, and set all to zero initial state. */
|
|
free(gms_hint_cursor);
|
|
bytes = hint_count * sizeof(*gms_hint_cursor);
|
|
gms_hint_cursor = (int *)gms_malloc(bytes);
|
|
memset(gms_hint_cursor, 0, bytes);
|
|
|
|
/*
|
|
* Retain the hints_ CRC, for later comparisons, and set is_initialized
|
|
* flag.
|
|
*/
|
|
hints_current_crc = crc;
|
|
hints_crc_initialized = true;
|
|
}
|
|
|
|
/*
|
|
* Save the hints_ array passed in. This is done here since even if the data
|
|
* remains the same (found by the CRC check above), the pointer to it might
|
|
* have changed.
|
|
*/
|
|
gms_hints = hints_;
|
|
|
|
/*
|
|
* Try to create the hints_ windows. If they can't be created, perhaps
|
|
* because the Glk library doesn't support it, the output functions will
|
|
* work around this.
|
|
*/
|
|
gms_hint_open();
|
|
gms_hint_menutext_start();
|
|
|
|
/*
|
|
* Begin hints_ display at the root node, and navigate until the user exits
|
|
* hints_.
|
|
*/
|
|
gms_current_hint_node = GMS_HINT_ROOT_NODE;
|
|
while (gms_current_hint_node != GMS_HINTS_DONE) {
|
|
event_t event;
|
|
|
|
assert(gms_current_hint_node < hint_count);
|
|
gms_hint_display(gms_hints, gms_hint_cursor, gms_current_hint_node);
|
|
|
|
/* Get and handle a character key event for hint navigation. */
|
|
gms_hint_menutext_char_event(&event);
|
|
assert(event.type == evtype_CharInput);
|
|
gms_current_hint_node = gms_hint_handle(gms_hints,
|
|
gms_hint_cursor,
|
|
gms_current_hint_node,
|
|
event.val1);
|
|
}
|
|
|
|
/* Done with hint windows. */
|
|
gms_hint_menutext_done();
|
|
gms_hint_close();
|
|
|
|
return GMS_HINT_SUCCESS;
|
|
}
|
|
|
|
void Magnetic::gms_hint_redraw() {
|
|
if (gms_hint_windows_available()) {
|
|
assert(gms_hints && gms_hint_cursor);
|
|
gms_hint_display(gms_hints, gms_hint_cursor, gms_current_hint_node);
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_hints_cleanup() {
|
|
free(gms_hint_cursor);
|
|
gms_hint_cursor = NULL;
|
|
|
|
gms_hints = NULL;
|
|
gms_current_hint_node = 0;
|
|
}
|
|
|
|
void Magnetic::ms_playmusic(type8 *midi_data, type32 length, type16 tempo) {
|
|
}
|
|
|
|
/*---------------------------------------------------------------------*/
|
|
/* Glk command escape functions */
|
|
/*---------------------------------------------------------------------*/
|
|
|
|
void Magnetic::gms_command_undo(const char *argument) {
|
|
assert(argument);
|
|
}
|
|
|
|
void Magnetic::gms_command_script(const char *argument) {
|
|
assert(argument);
|
|
|
|
if (gms_strcasecmp(argument, "on") == 0) {
|
|
frefid_t fileref;
|
|
|
|
if (gms_transcript_stream) {
|
|
gms_normal_string("Glk transcript is already on.\n");
|
|
return;
|
|
}
|
|
|
|
fileref = glk_fileref_create_by_prompt(fileusage_Transcript
|
|
| fileusage_TextMode,
|
|
filemode_WriteAppend, 0);
|
|
if (!fileref) {
|
|
gms_standout_string("Glk transcript failed.\n");
|
|
return;
|
|
}
|
|
|
|
gms_transcript_stream = glk_stream_open_file(fileref,
|
|
filemode_WriteAppend, 0);
|
|
glk_fileref_destroy(fileref);
|
|
if (!gms_transcript_stream) {
|
|
gms_standout_string("Glk transcript failed.\n");
|
|
return;
|
|
}
|
|
|
|
glk_window_set_echo_stream(gms_main_window, gms_transcript_stream);
|
|
|
|
gms_normal_string("Glk transcript is now on.\n");
|
|
}
|
|
|
|
else if (gms_strcasecmp(argument, "off") == 0) {
|
|
if (!gms_transcript_stream) {
|
|
gms_normal_string("Glk transcript is already off.\n");
|
|
return;
|
|
}
|
|
|
|
glk_stream_close(gms_transcript_stream, NULL);
|
|
gms_transcript_stream = NULL;
|
|
|
|
glk_window_set_echo_stream(gms_main_window, NULL);
|
|
|
|
gms_normal_string("Glk transcript is now off.\n");
|
|
}
|
|
|
|
else if (strlen(argument) == 0) {
|
|
gms_normal_string("Glk transcript is ");
|
|
gms_normal_string(gms_transcript_stream ? "on" : "off");
|
|
gms_normal_string(".\n");
|
|
}
|
|
|
|
else {
|
|
gms_normal_string("Glk transcript can be ");
|
|
gms_standout_string("on");
|
|
gms_normal_string(", or ");
|
|
gms_standout_string("off");
|
|
gms_normal_string(".\n");
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_command_inputlog(const char *argument) {
|
|
assert(argument);
|
|
|
|
if (gms_strcasecmp(argument, "on") == 0) {
|
|
frefid_t fileref;
|
|
|
|
if (gms_inputlog_stream) {
|
|
gms_normal_string("Glk input logging is already on.\n");
|
|
return;
|
|
}
|
|
|
|
fileref = glk_fileref_create_by_prompt(fileusage_InputRecord
|
|
| fileusage_BinaryMode,
|
|
filemode_WriteAppend, 0);
|
|
if (!fileref) {
|
|
gms_standout_string("Glk input logging failed.\n");
|
|
return;
|
|
}
|
|
|
|
gms_inputlog_stream = glk_stream_open_file(fileref,
|
|
filemode_WriteAppend, 0);
|
|
glk_fileref_destroy(fileref);
|
|
if (!gms_inputlog_stream) {
|
|
gms_standout_string("Glk input logging failed.\n");
|
|
return;
|
|
}
|
|
|
|
gms_normal_string("Glk input logging is now on.\n");
|
|
}
|
|
|
|
else if (gms_strcasecmp(argument, "off") == 0) {
|
|
if (!gms_inputlog_stream) {
|
|
gms_normal_string("Glk input logging is already off.\n");
|
|
return;
|
|
}
|
|
|
|
glk_stream_close(gms_inputlog_stream, NULL);
|
|
gms_inputlog_stream = NULL;
|
|
|
|
gms_normal_string("Glk input log is now off.\n");
|
|
}
|
|
|
|
else if (strlen(argument) == 0) {
|
|
gms_normal_string("Glk input logging is ");
|
|
gms_normal_string(gms_inputlog_stream ? "on" : "off");
|
|
gms_normal_string(".\n");
|
|
}
|
|
|
|
else {
|
|
gms_normal_string("Glk input logging can be ");
|
|
gms_standout_string("on");
|
|
gms_normal_string(", or ");
|
|
gms_standout_string("off");
|
|
gms_normal_string(".\n");
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_command_readlog(const char *argument) {
|
|
assert(argument);
|
|
|
|
if (gms_strcasecmp(argument, "on") == 0) {
|
|
frefid_t fileref;
|
|
|
|
if (gms_readlog_stream) {
|
|
gms_normal_string("Glk read log is already on.\n");
|
|
return;
|
|
}
|
|
|
|
fileref = glk_fileref_create_by_prompt(fileusage_InputRecord
|
|
| fileusage_BinaryMode,
|
|
filemode_Read, 0);
|
|
if (!fileref) {
|
|
gms_standout_string("Glk read log failed.\n");
|
|
return;
|
|
}
|
|
|
|
if (!glk_fileref_does_file_exist(fileref)) {
|
|
glk_fileref_destroy(fileref);
|
|
gms_standout_string("Glk read log failed.\n");
|
|
return;
|
|
}
|
|
|
|
gms_readlog_stream = glk_stream_open_file(fileref, filemode_Read, 0);
|
|
glk_fileref_destroy(fileref);
|
|
if (!gms_readlog_stream) {
|
|
gms_standout_string("Glk read log failed.\n");
|
|
return;
|
|
}
|
|
|
|
gms_normal_string("Glk read log is now on.\n");
|
|
}
|
|
|
|
else if (gms_strcasecmp(argument, "off") == 0) {
|
|
if (!gms_readlog_stream) {
|
|
gms_normal_string("Glk read log is already off.\n");
|
|
return;
|
|
}
|
|
|
|
glk_stream_close(gms_readlog_stream, NULL);
|
|
gms_readlog_stream = NULL;
|
|
|
|
gms_normal_string("Glk read log is now off.\n");
|
|
}
|
|
|
|
else if (strlen(argument) == 0) {
|
|
gms_normal_string("Glk read log is ");
|
|
gms_normal_string(gms_readlog_stream ? "on" : "off");
|
|
gms_normal_string(".\n");
|
|
}
|
|
|
|
else {
|
|
gms_normal_string("Glk read log can be ");
|
|
gms_standout_string("on");
|
|
gms_normal_string(", or ");
|
|
gms_standout_string("off");
|
|
gms_normal_string(".\n");
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_command_abbreviations(const char *argument) {
|
|
assert(argument);
|
|
|
|
if (gms_strcasecmp(argument, "on") == 0) {
|
|
if (gms_abbreviations_enabled) {
|
|
gms_normal_string("Glk abbreviation expansions are already on.\n");
|
|
return;
|
|
}
|
|
|
|
gms_abbreviations_enabled = true;
|
|
gms_normal_string("Glk abbreviation expansions are now on.\n");
|
|
}
|
|
|
|
else if (gms_strcasecmp(argument, "off") == 0) {
|
|
if (!gms_abbreviations_enabled) {
|
|
gms_normal_string("Glk abbreviation expansions are already off.\n");
|
|
return;
|
|
}
|
|
|
|
gms_abbreviations_enabled = false;
|
|
gms_normal_string("Glk abbreviation expansions are now off.\n");
|
|
}
|
|
|
|
else if (strlen(argument) == 0) {
|
|
gms_normal_string("Glk abbreviation expansions are ");
|
|
gms_normal_string(gms_abbreviations_enabled ? "on" : "off");
|
|
gms_normal_string(".\n");
|
|
}
|
|
|
|
else {
|
|
gms_normal_string("Glk abbreviation expansions can be ");
|
|
gms_standout_string("on");
|
|
gms_normal_string(", or ");
|
|
gms_standout_string("off");
|
|
gms_normal_string(".\n");
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_command_graphics(const char *argument) {
|
|
assert(argument);
|
|
|
|
if (!gms_graphics_possible) {
|
|
gms_normal_string("Glk graphics are not available.\n");
|
|
return;
|
|
}
|
|
|
|
if (gms_strcasecmp(argument, "on") == 0) {
|
|
if (gms_graphics_enabled) {
|
|
gms_normal_string("Glk graphics are already on.\n");
|
|
return;
|
|
}
|
|
|
|
gms_graphics_enabled = true;
|
|
|
|
/* If a picture is loaded, call the restart function to repaint it. */
|
|
if (gms_graphics_picture_is_available()) {
|
|
if (!gms_graphics_open()) {
|
|
gms_normal_string("Glk graphics error.\n");
|
|
return;
|
|
}
|
|
gms_graphics_restart();
|
|
}
|
|
|
|
gms_normal_string("Glk graphics are now on.\n");
|
|
}
|
|
|
|
else if (gms_strcasecmp(argument, "off") == 0) {
|
|
if (!gms_graphics_enabled) {
|
|
gms_normal_string("Glk graphics are already off.\n");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Set graphics to disabled, and stop any graphics processing. Close
|
|
* the graphics window.
|
|
*/
|
|
gms_graphics_enabled = false;
|
|
gms_graphics_stop();
|
|
gms_graphics_close();
|
|
|
|
gms_normal_string("Glk graphics are now off.\n");
|
|
}
|
|
|
|
else if (strlen(argument) == 0) {
|
|
gms_normal_string("Glk graphics are available,");
|
|
gms_normal_string(gms_graphics_enabled
|
|
? " and enabled.\n" : " but disabled.\n");
|
|
|
|
if (gms_graphics_picture_is_available()) {
|
|
int width, height, is_animated;
|
|
|
|
if (gms_graphics_get_picture_details(&width, &height, &is_animated)) {
|
|
char buf[16];
|
|
|
|
gms_normal_string("There is ");
|
|
gms_normal_string(is_animated ? "an animated" : "a");
|
|
gms_normal_string(" picture loaded, ");
|
|
|
|
sprintf(buf, "%d", width);
|
|
gms_normal_string(buf);
|
|
gms_normal_string(" by ");
|
|
|
|
sprintf(buf, "%d", height);
|
|
gms_normal_string(buf);
|
|
|
|
gms_normal_string(" pixels.\n");
|
|
}
|
|
}
|
|
|
|
if (!gms_graphics_interpreter_enabled())
|
|
gms_normal_string("Interpreter graphics are disabled.\n");
|
|
|
|
if (gms_graphics_enabled && gms_graphics_are_displayed()) {
|
|
int color_count, is_active;
|
|
const char *gamma;
|
|
|
|
if (gms_graphics_get_rendering_details(&gamma, &color_count,
|
|
&is_active)) {
|
|
char buf[16];
|
|
|
|
gms_normal_string("Graphics are ");
|
|
gms_normal_string(is_active ? "active, " : "displayed, ");
|
|
|
|
sprintf(buf, "%d", color_count);
|
|
gms_normal_string(buf);
|
|
gms_normal_string(" colours");
|
|
|
|
if (gms_gamma_mode == GAMMA_OFF)
|
|
gms_normal_string(", without gamma correction");
|
|
else {
|
|
gms_normal_string(", with gamma ");
|
|
gms_normal_string(gamma);
|
|
gms_normal_string(" correction");
|
|
}
|
|
gms_normal_string(".\n");
|
|
} else
|
|
gms_normal_string("Graphics are being displayed.\n");
|
|
}
|
|
|
|
if (gms_graphics_enabled && !gms_graphics_are_displayed())
|
|
gms_normal_string("Graphics are not being displayed.\n");
|
|
}
|
|
|
|
else {
|
|
gms_normal_string("Glk graphics can be ");
|
|
gms_standout_string("on");
|
|
gms_normal_string(", or ");
|
|
gms_standout_string("off");
|
|
gms_normal_string(".\n");
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_command_gamma(const char *argument) {
|
|
assert(argument);
|
|
|
|
if (!gms_graphics_possible) {
|
|
gms_normal_string("Glk automatic gamma correction is not available.\n");
|
|
return;
|
|
}
|
|
|
|
if (gms_strcasecmp(argument, "high") == 0) {
|
|
if (gms_gamma_mode == GAMMA_HIGH) {
|
|
gms_normal_string("Glk automatic gamma correction mode is"
|
|
" already 'high'.\n");
|
|
return;
|
|
}
|
|
|
|
gms_gamma_mode = GAMMA_HIGH;
|
|
gms_graphics_restart();
|
|
|
|
gms_normal_string("Glk automatic gamma correction mode is"
|
|
" now 'high'.\n");
|
|
}
|
|
|
|
else if (gms_strcasecmp(argument, "normal") == 0
|
|
|| gms_strcasecmp(argument, "on") == 0) {
|
|
if (gms_gamma_mode == GAMMA_NORMAL) {
|
|
gms_normal_string("Glk automatic gamma correction mode is"
|
|
" already 'normal'.\n");
|
|
return;
|
|
}
|
|
|
|
gms_gamma_mode = GAMMA_NORMAL;
|
|
gms_graphics_restart();
|
|
|
|
gms_normal_string("Glk automatic gamma correction mode is"
|
|
" now 'normal'.\n");
|
|
}
|
|
|
|
else if (gms_strcasecmp(argument, "none") == 0
|
|
|| gms_strcasecmp(argument, "off") == 0) {
|
|
if (gms_gamma_mode == GAMMA_OFF) {
|
|
gms_normal_string("Glk automatic gamma correction mode is"
|
|
" already 'off'.\n");
|
|
return;
|
|
}
|
|
|
|
gms_gamma_mode = GAMMA_OFF;
|
|
gms_graphics_restart();
|
|
|
|
gms_normal_string("Glk automatic gamma correction mode is"
|
|
" now 'off'.\n");
|
|
}
|
|
|
|
else if (strlen(argument) == 0) {
|
|
gms_normal_string("Glk automatic gamma correction mode is '");
|
|
switch (gms_gamma_mode) {
|
|
case GAMMA_OFF:
|
|
default:
|
|
gms_normal_string("off");
|
|
break;
|
|
case GAMMA_NORMAL:
|
|
gms_normal_string("normal");
|
|
break;
|
|
case GAMMA_HIGH:
|
|
gms_normal_string("high");
|
|
break;
|
|
}
|
|
gms_normal_string("'.\n");
|
|
}
|
|
|
|
else {
|
|
gms_normal_string("Glk automatic gamma correction mode can be ");
|
|
gms_standout_string("high");
|
|
gms_normal_string(", ");
|
|
gms_standout_string("normal");
|
|
gms_normal_string(", or ");
|
|
gms_standout_string("off");
|
|
gms_normal_string(".\n");
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_command_animations(const char *argument) {
|
|
assert(argument);
|
|
|
|
if (!gms_graphics_possible) {
|
|
gms_normal_string("Glk graphics animations are not available.\n");
|
|
return;
|
|
}
|
|
|
|
if (gms_strcasecmp(argument, "on") == 0) {
|
|
int is_animated;
|
|
|
|
if (gms_animation_enabled) {
|
|
gms_normal_string("Glk graphics animations are already on.\n");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Set animation to on, and restart graphics if the current picture
|
|
* is animated; if it isn't, we can leave it displayed as is, since
|
|
* changing animation mode doesn't affect this picture.
|
|
*/
|
|
gms_animation_enabled = true;
|
|
if (gms_graphics_get_picture_details(NULL, NULL, &is_animated)) {
|
|
if (is_animated)
|
|
gms_graphics_restart();
|
|
}
|
|
|
|
gms_normal_string("Glk graphics animations are now on.\n");
|
|
}
|
|
|
|
else if (gms_strcasecmp(argument, "off") == 0) {
|
|
int is_animated;
|
|
|
|
if (!gms_animation_enabled) {
|
|
gms_normal_string("Glk graphics animations are already off.\n");
|
|
return;
|
|
}
|
|
|
|
gms_animation_enabled = false;
|
|
if (gms_graphics_get_picture_details(NULL, NULL, &is_animated)) {
|
|
if (is_animated)
|
|
gms_graphics_restart();
|
|
}
|
|
|
|
gms_normal_string("Glk graphics animations are now off.\n");
|
|
}
|
|
|
|
else if (strlen(argument) == 0) {
|
|
gms_normal_string("Glk graphics animations are ");
|
|
gms_normal_string(gms_animation_enabled ? "on" : "off");
|
|
gms_normal_string(".\n");
|
|
}
|
|
|
|
else {
|
|
gms_normal_string("Glk graphics animations can be ");
|
|
gms_standout_string("on");
|
|
gms_normal_string(", or ");
|
|
gms_standout_string("off");
|
|
gms_normal_string(".\n");
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_command_prompts(const char *argument) {
|
|
assert(argument);
|
|
|
|
if (gms_strcasecmp(argument, "on") == 0) {
|
|
if (gms_prompt_enabled) {
|
|
gms_normal_string("Glk extra prompts are already on.\n");
|
|
return;
|
|
}
|
|
|
|
gms_prompt_enabled = true;
|
|
gms_normal_string("Glk extra prompts are now on.\n");
|
|
|
|
/* Check for a game prompt to clear the flag. */
|
|
gms_game_prompted();
|
|
}
|
|
|
|
else if (gms_strcasecmp(argument, "off") == 0) {
|
|
if (!gms_prompt_enabled) {
|
|
gms_normal_string("Glk extra prompts are already off.\n");
|
|
return;
|
|
}
|
|
|
|
gms_prompt_enabled = false;
|
|
gms_normal_string("Glk extra prompts are now off.\n");
|
|
}
|
|
|
|
else if (strlen(argument) == 0) {
|
|
gms_normal_string("Glk extra prompts are ");
|
|
gms_normal_string(gms_prompt_enabled ? "on" : "off");
|
|
gms_normal_string(".\n");
|
|
}
|
|
|
|
else {
|
|
gms_normal_string("Glk extra prompts can be ");
|
|
gms_standout_string("on");
|
|
gms_normal_string(", or ");
|
|
gms_standout_string("off");
|
|
gms_normal_string(".\n");
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_command_print_version_number(glui32 version_) {
|
|
Common::String str = Common::String::format("%lu.%lu.%lu",
|
|
(unsigned long)version_ >> 16,
|
|
(unsigned long)(version_ >> 8) & 0xff,
|
|
(unsigned long)version_ & 0xff);
|
|
gms_normal_string(str.c_str());
|
|
}
|
|
|
|
void Magnetic::gms_command_version(const char *argument) {
|
|
glui32 version_;
|
|
assert(argument);
|
|
|
|
gms_normal_string("This is version_ ");
|
|
gms_command_print_version_number(GMS_PORT_VERSION);
|
|
gms_normal_string(" of the Glk Magnetic port.\n");
|
|
|
|
version_ = glk_gestalt(gestalt_Version, 0);
|
|
gms_normal_string("The Glk library version_ is ");
|
|
gms_command_print_version_number(version_);
|
|
gms_normal_string(".\n");
|
|
}
|
|
|
|
void Magnetic::gms_command_commands(const char *argument) {
|
|
assert(argument);
|
|
|
|
if (gms_strcasecmp(argument, "on") == 0) {
|
|
gms_normal_string("Glk commands are already on.\n");
|
|
}
|
|
|
|
else if (gms_strcasecmp(argument, "off") == 0) {
|
|
gms_commands_enabled = false;
|
|
gms_normal_string("Glk commands are now off.\n");
|
|
}
|
|
|
|
else if (strlen(argument) == 0) {
|
|
gms_normal_string("Glk commands are ");
|
|
gms_normal_string(gms_commands_enabled ? "on" : "off");
|
|
gms_normal_string(".\n");
|
|
}
|
|
|
|
else {
|
|
gms_normal_string("Glk commands can be ");
|
|
gms_standout_string("on");
|
|
gms_normal_string(", or ");
|
|
gms_standout_string("off");
|
|
gms_normal_string(".\n");
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_command_summary(const char *argument) {
|
|
const gms_command_t *entry;
|
|
assert(argument);
|
|
|
|
/*
|
|
* Call handlers that have status to report with an empty argument,
|
|
* prompting each to print its current setting.
|
|
*/
|
|
for (entry = GMS_COMMAND_TABLE; entry->command; entry++) {
|
|
if (entry->handler == &Magnetic::gms_command_summary
|
|
|| entry->handler == &Magnetic::gms_command_undo
|
|
|| entry->handler == &Magnetic::gms_command_help)
|
|
continue;
|
|
|
|
(this->*entry->handler)("");
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_command_help(const char *command) {
|
|
const gms_command_t *entry, *matched;
|
|
assert(command);
|
|
|
|
if (strlen(command) == 0) {
|
|
gms_normal_string("Glk commands are");
|
|
for (entry = GMS_COMMAND_TABLE; entry->command; entry++) {
|
|
const gms_command_t *next;
|
|
|
|
next = entry + 1;
|
|
gms_normal_string(next->command ? " " : " and ");
|
|
gms_standout_string(entry->command);
|
|
gms_normal_string(next->command ? "," : ".\n\n");
|
|
}
|
|
|
|
gms_normal_string("Glk commands may be abbreviated, as long as"
|
|
" the abbreviation is unambiguous. Use ");
|
|
gms_standout_string("glk help");
|
|
gms_normal_string(" followed by a Glk command name for help on that"
|
|
" command.\n");
|
|
return;
|
|
}
|
|
|
|
matched = NULL;
|
|
for (entry = GMS_COMMAND_TABLE; entry->command; entry++) {
|
|
if (gms_strncasecmp(command, entry->command, strlen(command)) == 0) {
|
|
if (matched) {
|
|
gms_normal_string("The Glk command ");
|
|
gms_standout_string(command);
|
|
gms_normal_string(" is ambiguous. Try ");
|
|
gms_standout_string("glk help");
|
|
gms_normal_string(" for more information.\n");
|
|
return;
|
|
}
|
|
matched = entry;
|
|
}
|
|
}
|
|
if (!matched) {
|
|
gms_normal_string("The Glk command ");
|
|
gms_standout_string(command);
|
|
gms_normal_string(" is not valid. Try ");
|
|
gms_standout_string("glk help");
|
|
gms_normal_string(" for more information.\n");
|
|
return;
|
|
}
|
|
|
|
if (matched->handler == &Magnetic::gms_command_summary) {
|
|
gms_normal_string("Prints a summary of all the current Glk Magnetic"
|
|
" settings.\n");
|
|
}
|
|
|
|
else if (matched->handler == &Magnetic::gms_command_undo) {
|
|
gms_normal_string("Undoes a single game turn.\n\nEquivalent to the"
|
|
" standalone game 'undo' command.\n");
|
|
}
|
|
|
|
else if (matched->handler == &Magnetic::gms_command_script) {
|
|
gms_normal_string("Logs the game's output to a file.\n\nUse ");
|
|
gms_standout_string("glk script on");
|
|
gms_normal_string(" to begin logging game output, and ");
|
|
gms_standout_string("glk script off");
|
|
gms_normal_string(" to end it. Glk Magnetic will ask you for a file"
|
|
" when you turn scripts on.\n");
|
|
}
|
|
|
|
else if (matched->handler == &Magnetic::gms_command_inputlog) {
|
|
gms_normal_string("Records the commands you type into a game.\n\nUse ");
|
|
gms_standout_string("glk inputlog on");
|
|
gms_normal_string(", to begin recording your commands, and ");
|
|
gms_standout_string("glk inputlog off");
|
|
gms_normal_string(" to turn off input logs. You can play back"
|
|
" recorded commands into a game with the ");
|
|
gms_standout_string("glk readlog");
|
|
gms_normal_string(" command.\n");
|
|
}
|
|
|
|
else if (matched->handler == &Magnetic::gms_command_readlog) {
|
|
gms_normal_string("Plays back commands recorded with ");
|
|
gms_standout_string("glk inputlog on");
|
|
gms_normal_string(".\n\nUse ");
|
|
gms_standout_string("glk readlog on");
|
|
gms_normal_string(". Command play back stops at the end of the"
|
|
" file. You can also play back commands from a"
|
|
" text file created using any standard editor.\n");
|
|
}
|
|
|
|
else if (matched->handler == &Magnetic::gms_command_abbreviations) {
|
|
gms_normal_string("Controls abbreviation expansion.\n\nGlk Magnetic"
|
|
" automatically expands several standard single"
|
|
" letter abbreviations for you; for example, \"x\""
|
|
" becomes \"examine\". Use ");
|
|
gms_standout_string("glk abbreviations on");
|
|
gms_normal_string(" to turn this feature on, and ");
|
|
gms_standout_string("glk abbreviations off");
|
|
gms_normal_string(" to turn it off. While the feature is on, you"
|
|
" can bypass abbreviation expansion for an"
|
|
" individual game command by prefixing it with a"
|
|
" single quote.\n");
|
|
}
|
|
|
|
else if (matched->handler == &Magnetic::gms_command_graphics) {
|
|
gms_normal_string("Turns interpreter graphics on and off.\n\nUse ");
|
|
gms_standout_string("glk graphics on");
|
|
gms_normal_string(" to enable interpreter graphics, and ");
|
|
gms_standout_string("glk graphics off");
|
|
gms_normal_string(" to turn graphics off and close the graphics window."
|
|
" This control works slightly differently to the"
|
|
" 'graphics' command in Magnetic Windows and Magnetic"
|
|
" Scrolls games themselves; the game's 'graphics'"
|
|
" command may disable new images, but leave old ones"
|
|
" displayed. For graphics to be displayed, they"
|
|
" must be turned on in both the game and the"
|
|
" interpreter.\n");
|
|
}
|
|
|
|
else if (matched->handler == &Magnetic::gms_command_gamma) {
|
|
gms_normal_string("Sets the level of automatic gamma correction applied"
|
|
" to game graphics.\n\nUse ");
|
|
gms_standout_string("glk gamma normal");
|
|
gms_normal_string(" to set moderate automatic colour contrast"
|
|
" correction, ");
|
|
gms_standout_string("glk gamma high");
|
|
gms_normal_string(" to set high automatic colour contrast correction,"
|
|
" or ");
|
|
gms_standout_string("glk gamma off");
|
|
gms_normal_string(" to turn off all automatic gamma correction.\n");
|
|
}
|
|
|
|
else if (matched->handler == &Magnetic::gms_command_animations) {
|
|
gms_normal_string("Turns graphic animations on and off.\n\nUse ");
|
|
gms_standout_string("glk animation on");
|
|
gms_normal_string(" to enable animations, or ");
|
|
gms_standout_string("glk animation off");
|
|
gms_normal_string(" to turn animations off. Not all game graphics are"
|
|
" animated, so this control works only on graphics"
|
|
" that are animated. When animation is off, Glk"
|
|
" Magnetic displays only the static portions of a"
|
|
" game's pictures.\n");
|
|
}
|
|
|
|
else if (matched->handler == &Magnetic::gms_command_prompts) {
|
|
gms_normal_string("Controls extra input prompting.\n\n"
|
|
"Glk Magnetic can issue a replacement '>' input"
|
|
" prompt if it detects that the game hasn't prompted"
|
|
" after, say, an empty input line. Use ");
|
|
gms_standout_string("glk prompts on");
|
|
gms_normal_string(" to turn this feature on, and ");
|
|
gms_standout_string("glk prompts off");
|
|
gms_normal_string(" to turn it off.\n");
|
|
}
|
|
|
|
else if (matched->handler == &Magnetic::gms_command_version) {
|
|
gms_normal_string("Prints the version numbers of the Glk library"
|
|
" and the Glk Magnetic port.\n");
|
|
}
|
|
|
|
else if (matched->handler == &Magnetic::gms_command_commands) {
|
|
gms_normal_string("Turn off Glk commands.\n\nUse ");
|
|
gms_standout_string("glk commands off");
|
|
gms_normal_string(" to disable all Glk commands, including this one."
|
|
" Once turned off, there is no way to turn Glk"
|
|
" commands back on while inside the game.\n");
|
|
}
|
|
|
|
else if (matched->handler == &Magnetic::gms_command_help)
|
|
gms_command_help("");
|
|
|
|
else
|
|
gms_normal_string("There is no help available on that Glk command."
|
|
" Sorry.\n");
|
|
}
|
|
|
|
int Magnetic::gms_command_escape(const char *string_, int *undo_command) {
|
|
int posn;
|
|
char *string_copy, *command, *argument;
|
|
assert(string_ && undo_command);
|
|
|
|
/*
|
|
* Return false if the string doesn't begin with the Glk command escape
|
|
* introducer.
|
|
*/
|
|
posn = strspn(string_, "\t ");
|
|
if (gms_strncasecmp(string_ + posn, "glk", strlen("glk")) != 0)
|
|
return false;
|
|
|
|
/* Take a copy of the string_, without any leading space or introducer. */
|
|
string_copy = (char *)gms_malloc(strlen(string_ + posn) + 1 - strlen("glk"));
|
|
strcpy(string_copy, string_ + posn + strlen("glk"));
|
|
|
|
/*
|
|
* Find the subcommand; the first word in the string copy. Find its end,
|
|
* and ensure it terminates with a NUL.
|
|
*/
|
|
posn = strspn(string_copy, "\t ");
|
|
command = string_copy + posn;
|
|
posn += strcspn(string_copy + posn, "\t ");
|
|
if (string_copy[posn] != '\0')
|
|
string_copy[posn++] = '\0';
|
|
|
|
/*
|
|
* Now find any argument data for the command, ensuring it too terminates
|
|
* with a NUL.
|
|
*/
|
|
posn += strspn(string_copy + posn, "\t ");
|
|
argument = string_copy + posn;
|
|
posn += strcspn(string_copy + posn, "\t ");
|
|
string_copy[posn] = '\0';
|
|
|
|
/*
|
|
* Try to handle the command and argument as a Glk subcommand. If it
|
|
* doesn't run unambiguously, print command usage. Treat an empty command
|
|
* as "help".
|
|
*/
|
|
if (strlen(command) > 0) {
|
|
const gms_command_t *entry, *matched;
|
|
int matches;
|
|
|
|
/*
|
|
* Search for the first unambiguous table command string_ matching
|
|
* the command passed in.
|
|
*/
|
|
matches = 0;
|
|
matched = NULL;
|
|
for (entry = GMS_COMMAND_TABLE; entry->command; entry++) {
|
|
if (gms_strncasecmp(command, entry->command, strlen(command)) == 0) {
|
|
matches++;
|
|
matched = entry;
|
|
}
|
|
}
|
|
|
|
/* If the match was unambiguous, call the command handler. */
|
|
if (matches == 1) {
|
|
if (!matched->undo_return)
|
|
gms_normal_char('\n');
|
|
(this->*(matched->handler))(argument);
|
|
|
|
if (!matched->takes_argument && strlen(argument) > 0) {
|
|
gms_normal_string("[The ");
|
|
gms_standout_string(matched->command);
|
|
gms_normal_string(" command ignores arguments.]\n");
|
|
}
|
|
|
|
*undo_command = matched->undo_return;
|
|
}
|
|
|
|
/* No match, or the command was ambiguous. */
|
|
else {
|
|
gms_normal_string("\nThe Glk command ");
|
|
gms_standout_string(command);
|
|
gms_normal_string(" is ");
|
|
gms_normal_string(matches == 0 ? "not valid" : "ambiguous");
|
|
gms_normal_string(". Try ");
|
|
gms_standout_string("glk help");
|
|
gms_normal_string(" for more information.\n");
|
|
}
|
|
} else {
|
|
gms_normal_char('\n');
|
|
gms_command_help("");
|
|
}
|
|
|
|
/* The string_ contained a Glk command; return true. */
|
|
free(string_copy);
|
|
return true;
|
|
}
|
|
|
|
int Magnetic::gms_command_undo_special(const char *string_) {
|
|
int posn, end;
|
|
assert(string_);
|
|
|
|
/* Find the start and end of the first string_ word. */
|
|
posn = strspn(string_, "\t ");
|
|
end = posn + strcspn(string_ + posn, "\t ");
|
|
|
|
/* See if string_ contains an "undo" request, with nothing following. */
|
|
if (end - posn == (int)strlen("undo")
|
|
&& gms_strncasecmp(string_ + posn, "undo", end - posn) == 0) {
|
|
posn = end + strspn(string_ + end, "\t ");
|
|
if (string_[posn] == '\0')
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------*/
|
|
/* Glk port input functions */
|
|
/*---------------------------------------------------------------------*/
|
|
|
|
void Magnetic::gms_expand_abbreviations(char *buffer_, int size) {
|
|
char *command, abbreviation;
|
|
const char *expansion;
|
|
gms_abbreviationref_t entry;
|
|
assert(buffer_);
|
|
|
|
/* Ignore anything that isn't a single letter command. */
|
|
command = buffer_ + strspn(buffer_, "\t ");
|
|
if (!(strlen(command) == 1
|
|
|| (strlen(command) > 1 && Common::isSpace(command[1]))))
|
|
return;
|
|
|
|
/* Scan the abbreviations table for a match. */
|
|
abbreviation = glk_char_to_lower((unsigned char) command[0]);
|
|
expansion = NULL;
|
|
for (entry = GMS_ABBREVIATIONS; entry->expansion; entry++) {
|
|
if (entry->abbreviation == abbreviation) {
|
|
expansion = entry->expansion;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If a match found, check for a fit, then replace the character with the
|
|
* expansion string.
|
|
*/
|
|
if (expansion) {
|
|
if ((int)(strlen(buffer_) + strlen(expansion)) - 1 >= size)
|
|
return;
|
|
|
|
memmove(command + strlen(expansion) - 1, command, strlen(command) + 1);
|
|
memcpy(command, expansion, strlen(expansion));
|
|
|
|
#if 0
|
|
gms_standout_string("[");
|
|
gms_standout_char(abbreviation);
|
|
gms_standout_string(" -> ");
|
|
gms_standout_string(expansion);
|
|
gms_standout_string("]\n");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void Magnetic::gms_buffer_input() {
|
|
event_t event;
|
|
|
|
/*
|
|
* Update the current status line display, and flush any pending buffered
|
|
* output.
|
|
*/
|
|
gms_status_notify();
|
|
gms_output_flush();
|
|
|
|
/*
|
|
* Magnetic Windows games tend not to issue a prompt after reading an empty
|
|
* line of input. This can make for a very blank looking screen.
|
|
*
|
|
* To slightly improve things, if it looks like we didn't get a prompt from
|
|
* the game, do our own.
|
|
*/
|
|
if (gms_prompt_enabled && !gms_game_prompted()) {
|
|
gms_normal_char('\n');
|
|
gms_normal_string(GMS_INPUT_PROMPT);
|
|
}
|
|
|
|
/*
|
|
* If we have an input log to read from, use that until it is exhausted. On
|
|
* end of file, close the stream and resume input from line requests.
|
|
*/
|
|
if (gms_readlog_stream) {
|
|
glui32 chars;
|
|
|
|
/* Get the next line from the log stream. */
|
|
chars = glk_get_line_stream(gms_readlog_stream,
|
|
gms_input_buffer, sizeof(gms_input_buffer));
|
|
if (chars > 0) {
|
|
/* Echo the line just read in input style. */
|
|
glk_set_style(style_Input);
|
|
glk_put_buffer(gms_input_buffer, chars);
|
|
glk_set_style(style_Normal);
|
|
|
|
/* Note how many characters buffered, and return. */
|
|
gms_input_length = chars;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* We're at the end of the log stream. Close it, and then continue
|
|
* on to request a line from Glk.
|
|
*/
|
|
glk_stream_close(gms_readlog_stream, NULL);
|
|
gms_readlog_stream = NULL;
|
|
}
|
|
|
|
/*
|
|
* No input log being read, or we just hit the end of file on one. Revert
|
|
* to normal line input; start by getting a new line from Glk.
|
|
*/
|
|
glk_request_line_event(gms_main_window,
|
|
gms_input_buffer, sizeof(gms_input_buffer) - 1, 0);
|
|
gms_event_wait(evtype_LineInput, &event);
|
|
if (shouldQuit()) {
|
|
glk_cancel_line_event(gms_main_window, &event);
|
|
return;
|
|
}
|
|
|
|
/* Terminate the input line with a NUL. */
|
|
assert(event.val1 <= sizeof(gms_input_buffer) - 1);
|
|
gms_input_buffer[event.val1] = '\0';
|
|
|
|
/* Special handling for "undo" commands. */
|
|
if (gms_command_undo_special(gms_input_buffer)) {
|
|
/* Write the "undo" to any input log. */
|
|
if (gms_inputlog_stream) {
|
|
glk_put_string_stream(gms_inputlog_stream, gms_input_buffer);
|
|
glk_put_char_stream(gms_inputlog_stream, '\n');
|
|
}
|
|
|
|
/* Overwrite buffer with an empty line if we saw "undo". */
|
|
gms_input_buffer[0] = '\n';
|
|
gms_input_length = 1;
|
|
|
|
gms_undo_notification = true;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* If neither abbreviations nor local commands are enabled, use the data
|
|
* read above without further massaging.
|
|
*/
|
|
if (gms_abbreviations_enabled || gms_commands_enabled) {
|
|
char *command;
|
|
|
|
/*
|
|
* If the first non-space input character is a quote, bypass all
|
|
* abbreviation expansion and local command recognition, and use the
|
|
* unadulterated input, less introductory quote.
|
|
*/
|
|
command = gms_input_buffer + strspn(gms_input_buffer, "\t ");
|
|
if (command[0] == '\'') {
|
|
/* Delete the quote with memmove(). */
|
|
memmove(command, command + 1, strlen(command));
|
|
} else {
|
|
/* Check for, and expand, any abbreviated commands. */
|
|
if (gms_abbreviations_enabled) {
|
|
gms_expand_abbreviations(gms_input_buffer,
|
|
sizeof(gms_input_buffer));
|
|
}
|
|
|
|
/*
|
|
* Check for standalone "help", then for Glk port special commands;
|
|
* suppress the interpreter's use of this input for Glk commands
|
|
* by overwriting the line with a single newline character.
|
|
*/
|
|
if (gms_commands_enabled) {
|
|
int posn;
|
|
|
|
posn = strspn(gms_input_buffer, "\t ");
|
|
if (gms_strncasecmp(gms_input_buffer + posn,
|
|
"help", strlen("help")) == 0) {
|
|
if (strspn(gms_input_buffer + posn + strlen("help"), "\t ")
|
|
== strlen(gms_input_buffer + posn + strlen("help"))) {
|
|
gms_output_register_help_request();
|
|
}
|
|
}
|
|
|
|
if (gms_command_escape(gms_input_buffer,
|
|
&gms_undo_notification)) {
|
|
gms_output_silence_help_hints();
|
|
gms_input_buffer[0] = '\n';
|
|
gms_input_length = 1;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If there is an input log active, log this input string to it. Note that
|
|
* by logging here we get any abbreviation expansions but we won't log glk
|
|
* special commands, nor any input read from a current open input log.
|
|
*/
|
|
if (gms_inputlog_stream) {
|
|
glk_put_string_stream(gms_inputlog_stream, gms_input_buffer);
|
|
glk_put_char_stream(gms_inputlog_stream, '\n');
|
|
}
|
|
|
|
/*
|
|
* Now append a newline to the buffer, since Glk line input doesn't provide
|
|
* one, and in any case, abbreviation expansion may have edited the buffer
|
|
* contents (and in particular, changed the length).
|
|
*/
|
|
gms_input_buffer[strlen(gms_input_buffer) + 1] = '\0';
|
|
gms_input_buffer[strlen(gms_input_buffer)] = '\n';
|
|
|
|
/* Note how many characters are buffered after all of the above. */
|
|
gms_input_length = strlen(gms_input_buffer);
|
|
}
|
|
|
|
type8 Magnetic::ms_getchar(type8 trans) {
|
|
/* See if we are at the end of the input buffer. */
|
|
if (gms_input_cursor == gms_input_length) {
|
|
/*
|
|
* Try to read in more data, and rewind buffer cursor. As well as
|
|
* reading input, this may set an undo notification.
|
|
*/
|
|
gms_buffer_input();
|
|
gms_input_cursor = 0;
|
|
|
|
if (shouldQuit())
|
|
return '\0';
|
|
|
|
if (gms_undo_notification) {
|
|
/*
|
|
* Clear the undo notification, and discard buffered input (usually
|
|
* just the '\n' placed there when the undo command was recognized).
|
|
*/
|
|
gms_undo_notification = false;
|
|
gms_input_length = 0;
|
|
|
|
/*
|
|
* Return the special 0, or a blank line if no undo is allowed at
|
|
* this point.
|
|
*/
|
|
return trans ? 0 : '\n';
|
|
}
|
|
}
|
|
|
|
/* Return the next character from the input buffer. */
|
|
assert(gms_input_cursor < gms_input_length);
|
|
return gms_input_buffer[gms_input_cursor++];
|
|
}
|
|
|
|
/*---------------------------------------------------------------------*/
|
|
/* Glk port event functions */
|
|
/*---------------------------------------------------------------------*/
|
|
|
|
void Magnetic::gms_event_wait(glui32 wait_type, event_t *event) {
|
|
assert(event);
|
|
|
|
do {
|
|
glk_select(event);
|
|
|
|
switch (event->type) {
|
|
case evtype_Arrange:
|
|
case evtype_Redraw:
|
|
/* Refresh any sensitive windows on size events. */
|
|
gms_status_redraw();
|
|
gms_hint_redraw();
|
|
gms_graphics_paint();
|
|
break;
|
|
|
|
case evtype_Timer:
|
|
/* Do background graphics updates on timeout. */
|
|
gms_graphics_timeout();
|
|
break;
|
|
|
|
case evtype_Quit:
|
|
return;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
} while (event->type != (EvType)wait_type);
|
|
}
|
|
|
|
/*---------------------------------------------------------------------*/
|
|
/* Functions intercepted by link-time wrappers */
|
|
/*---------------------------------------------------------------------*/
|
|
|
|
int Magnetic::__wrap_toupper(int ch) {
|
|
unsigned char uch;
|
|
|
|
uch = glk_char_to_upper((unsigned char) ch);
|
|
return (int) uch;
|
|
}
|
|
|
|
int Magnetic::__wrap_tolower(int ch) {
|
|
unsigned char lch;
|
|
|
|
lch = glk_char_to_lower((unsigned char) ch);
|
|
return (int) lch;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------*/
|
|
/* main() and options parsing */
|
|
/*---------------------------------------------------------------------*/
|
|
|
|
void Magnetic::gms_establish_filenames(const char *name, char **text, char **graphics, char **hints_) {
|
|
char *base, *text_file, *graphics_file, *hints_file;
|
|
Common::File stream;
|
|
assert(name && text && graphics && hints_);
|
|
|
|
/* Take a destroyable copy of the input filename. */
|
|
base = (char *)gms_malloc(strlen(name) + 1);
|
|
strcpy(base, name);
|
|
|
|
/* If base has an extension .MAG, .GFX, or .HNT, remove it. */
|
|
if (strlen(base) > strlen(".XXX")) {
|
|
if (gms_strcasecmp(base + strlen(base) - strlen(".MAG"), ".MAG") == 0
|
|
|| gms_strcasecmp(base + strlen(base) - strlen(".GFX"), ".GFX") == 0
|
|
|| gms_strcasecmp(base + strlen(base) - strlen(".HNT"), ".HNT") == 0)
|
|
base[strlen(base) - strlen(".XXX")] = '\0';
|
|
}
|
|
|
|
/* Allocate space for the return text file. */
|
|
text_file = (char *)gms_malloc(strlen(base) + strlen(".MAG") + 1);
|
|
|
|
/* Form a candidate text file, by adding a .MAG extension. */
|
|
strcpy(text_file, base);
|
|
strcat(text_file, ".MAG");
|
|
|
|
if (!stream.open(text_file)) {
|
|
/* Retry, using a .mag extension instead. */
|
|
strcpy(text_file, base);
|
|
strcat(text_file, ".mag");
|
|
|
|
if (!stream.open(text_file)) {
|
|
/*
|
|
* No access to a usable game text file. Return immediately,
|
|
* without looking for any associated graphics or hints_ files.
|
|
*/
|
|
*text = NULL;
|
|
*graphics = NULL;
|
|
*hints_ = NULL;
|
|
|
|
free(text_file);
|
|
free(base);
|
|
return;
|
|
}
|
|
}
|
|
stream.close();
|
|
|
|
/* Now allocate space for the return graphics file. */
|
|
graphics_file = (char *)gms_malloc(strlen(base) + strlen(".GFX") + 1);
|
|
|
|
/* As above, form a candidate graphics file, using a .GFX extension. */
|
|
strcpy(graphics_file, base);
|
|
strcat(graphics_file, ".GFX");
|
|
|
|
if (!stream.open(graphics_file)) {
|
|
/* Retry, using a .gfx extension instead. */
|
|
strcpy(graphics_file, base);
|
|
strcat(graphics_file, ".gfx");
|
|
|
|
if (!stream.open(graphics_file)) {
|
|
/*
|
|
* No access to any graphics file. In this case, free memory and
|
|
* reset graphics file to NULL.
|
|
*/
|
|
free(graphics_file);
|
|
graphics_file = NULL;
|
|
}
|
|
}
|
|
stream.close();
|
|
|
|
/* Now allocate space for the return hints_ file. */
|
|
hints_file = (char *)gms_malloc(strlen(base) + strlen(".HNT") + 1);
|
|
|
|
/* As above, form a candidate graphics file, using a .HNT extension. */
|
|
strcpy(hints_file, base);
|
|
strcat(hints_file, ".HNT");
|
|
|
|
if (!stream.open(hints_file)) {
|
|
/* Retry, using a .hnt extension instead. */
|
|
strcpy(hints_file, base);
|
|
strcat(hints_file, ".hnt");
|
|
|
|
if (!stream.open(hints_file)) {
|
|
/*
|
|
* No access to any hints_ file. In this case, free memory and
|
|
* reset hints_ file to NULL.
|
|
*/
|
|
free(hints_file);
|
|
hints_file = NULL;
|
|
}
|
|
}
|
|
stream.close();
|
|
|
|
/* Return the text file, and graphics and hints_, which may be NULL. */
|
|
*text = text_file;
|
|
*graphics = graphics_file;
|
|
*hints_ = hints_file;
|
|
|
|
free(base);
|
|
}
|
|
|
|
void Magnetic::gms_main() {
|
|
char *text_file = NULL, *graphics_file = NULL, *hints_file = NULL;
|
|
int ms_init_status, is_running;
|
|
|
|
/* Create the main Glk window, and set its stream as current. */
|
|
gms_main_window = glk_window_open(0, 0, 0, wintype_TextBuffer, 0);
|
|
if (!gms_main_window) {
|
|
gms_fatal("GLK: Can't open main window");
|
|
glk_exit();
|
|
return;
|
|
}
|
|
glk_window_clear(gms_main_window);
|
|
glk_set_window(gms_main_window);
|
|
glk_set_style(style_Normal);
|
|
|
|
/*
|
|
* Given the basic game name, try to come up with usable text, graphics,
|
|
* and hints filenames. The graphics and hints files may be null, but the
|
|
* text file may not.
|
|
*/
|
|
Common::String gameFile = getFilename();
|
|
gms_establish_filenames(gameFile.c_str(), &text_file, &graphics_file, &hints_file);
|
|
|
|
/* Set the possibility of pictures depending on graphics file. */
|
|
if (graphics_file) {
|
|
/*
|
|
* Check Glk library capabilities, and note pictures are impossible if
|
|
* the library can't offer both graphics and timers. We need timers to
|
|
* create the background "thread" for picture updates.
|
|
*/
|
|
gms_graphics_possible = glk_gestalt(gestalt_Graphics, 0)
|
|
&& glk_gestalt(gestalt_Timer, 0);
|
|
} else
|
|
gms_graphics_possible = false;
|
|
|
|
|
|
/*
|
|
* If pictures are impossible, clear pictures enabled flag. That is, act
|
|
* as if -np was given on the command line, even though it may not have
|
|
* been. If pictures are impossible, they can never be enabled.
|
|
*/
|
|
if (!gms_graphics_possible)
|
|
gms_graphics_enabled = false;
|
|
|
|
/* Try to create a one-line status window. We can live without it. */
|
|
glk_stylehint_set(wintype_TextGrid, style_User1, stylehint_ReverseColor, 1);
|
|
gms_status_window = glk_window_open(gms_main_window,
|
|
winmethod_Above | winmethod_Fixed,
|
|
1, wintype_TextGrid, 0);
|
|
|
|
/*
|
|
* Load the game. If no graphics are possible, then passing the NULL to
|
|
* ms_init() runs a game without graphics.
|
|
*/
|
|
if (gms_graphics_possible) {
|
|
assert(graphics_file);
|
|
ms_init_status = ms_init(text_file, graphics_file, hints_file, NULL);
|
|
} else
|
|
ms_init_status = ms_init(text_file, NULL, hints_file, NULL);
|
|
|
|
/* Look for a complete failure to load the game. */
|
|
if (ms_init_status == 0) {
|
|
if (gms_status_window)
|
|
glk_window_close(gms_status_window, NULL);
|
|
gms_header_string("Glk Magnetic Error\n\n");
|
|
gms_normal_string("Can't load game '");
|
|
gms_normal_string(gameFile.c_str());
|
|
gms_normal_char('\'');
|
|
|
|
gms_normal_char('\n');
|
|
|
|
/*
|
|
* Free the text file path, any graphics/hints file path, and
|
|
* interpreter allocated memory.
|
|
*/
|
|
free(text_file);
|
|
free(graphics_file);
|
|
free(hints_file);
|
|
ms_freemem();
|
|
glk_exit();
|
|
}
|
|
|
|
/* Try to identify the game from its text file header. */
|
|
gms_gameid_identify_game(text_file);
|
|
|
|
/* Look for failure to load just game graphics. */
|
|
if (gms_graphics_possible && ms_init_status == 1) {
|
|
/*
|
|
* Output a warning if graphics failed, but the main game text
|
|
* initialized okay.
|
|
*/
|
|
gms_standout_string("Error: Unable to open graphics file\n"
|
|
"Continuing without pictures...\n\n");
|
|
|
|
gms_graphics_possible = false;
|
|
}
|
|
|
|
/* Run the game opcodes -- ms_rungame() returns false on game end. */
|
|
do {
|
|
is_running = ms_rungame() && !shouldQuit();
|
|
glk_tick();
|
|
} while (is_running);
|
|
|
|
/* Handle any updated status and pending buffered output. */
|
|
gms_status_notify();
|
|
gms_output_flush();
|
|
|
|
/* Turn off any background graphics "thread". */
|
|
gms_graphics_stop();
|
|
|
|
/* Free interpreter allocated memory. */
|
|
ms_freemem();
|
|
|
|
/*
|
|
* Free any temporary memory that may have been used by graphics and hints.
|
|
*/
|
|
gms_graphics_cleanup();
|
|
gms_hints_cleanup();
|
|
|
|
/* Close any open transcript, input log, and/or read log. */
|
|
if (gms_transcript_stream) {
|
|
glk_stream_close(gms_transcript_stream, NULL);
|
|
gms_transcript_stream = NULL;
|
|
}
|
|
if (gms_inputlog_stream) {
|
|
glk_stream_close(gms_inputlog_stream, NULL);
|
|
gms_inputlog_stream = NULL;
|
|
}
|
|
if (gms_readlog_stream) {
|
|
glk_stream_close(gms_readlog_stream, NULL);
|
|
gms_readlog_stream = NULL;
|
|
}
|
|
|
|
/* Free the text file path, and any graphics/hints file path. */
|
|
free(text_file);
|
|
free(graphics_file);
|
|
free(hints_file);
|
|
}
|
|
|
|
/*---------------------------------------------------------------------*/
|
|
/* Linkage between Glk entry/exit calls and the Magnetic interpreter */
|
|
/*---------------------------------------------------------------------*/
|
|
|
|
void Magnetic::glk_main() {
|
|
assert(gms_startup_called && !gms_main_called);
|
|
gms_main_called = true;
|
|
|
|
/* Call the interpreter main function. */
|
|
gms_main();
|
|
}
|
|
|
|
void Magnetic::write(const char *fmt, ...) {
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
Common::String s = Common::String::format(fmt, ap);
|
|
va_end(ap);
|
|
glk_put_buffer(s.c_str(), s.size());
|
|
}
|
|
|
|
void Magnetic::writeChar(char c) {
|
|
glk_put_char(c);
|
|
}
|
|
|
|
void Magnetic::script_write(type8 c) {
|
|
if (log_on == 2) {
|
|
if (_log1) {
|
|
_log1->writeByte(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Magnetic::transcript_write(type8 c) {
|
|
if (_log2) {
|
|
_log2->writeByte(c);
|
|
}
|
|
}
|
|
|
|
} // End of namespace Magnetic
|
|
} // End of namespace Glk
|