1
0
mirror of https://git.code.sf.net/p/fuse-emulator/fuse synced 2026-01-27 01:41:34 +03:00
Files
fuse/phantom_typist.c
Fredrick Meunier 3c4e3dd995 Only disable phantom typist if it was activated
More improvements disabling phantom typist after finishing loading TAP
or standard ROM TZX files (thanks, Alberto Garcia) (fixes bug #423).
2018-07-03 19:49:58 +10:00

554 lines
16 KiB
C

/* phantom_typist.c: starting game loading automatically
Copyright (c) 2017 Philip Kendall
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.
Author contact information:
E-mail: philip-fuse@shadowmagic.org.uk
*/
#include <string.h>
#ifdef HAVE_STRINGS_STRCASECMP
#include <strings.h>
#endif /* #ifdef HAVE_STRINGS_STRCASECMP */
#include <config.h>
#include <libspectrum.h>
#include "compat.h"
#include "infrastructure/startup_manager.h"
#include "keyboard.h"
#include "module.h"
#include "phantom_typist.h"
#include "settings.h"
#include "timer/timer.h"
/* The various high level ways the phantom typist can type */
typedef enum phantom_typist_highlevel_mode_t {
/* Use keyword entry */
PHANTOM_TYPIST_HIGHLEVEL_MODE_KEYWORD,
/* Use keystroke entry */
PHANTOM_TYPIST_HIGHLEVEL_MODE_KEYSTROKE,
/* Use the loader option from the menu */
PHANTOM_TYPIST_HIGHLEVEL_MODE_MENU,
/* Use the loader option from the menu but with an additional delay */
PHANTOM_TYPIST_HIGHLEVEL_MODE_PLUS2A,
/* Use the loader option from the menu but with an additional delay
and special handling of LOAD ""CODE programs */
PHANTOM_TYPIST_HIGHLEVEL_MODE_PLUS3
} phantom_typist_highlevel_mode_t;
/* The various strings of keypresses that the phantom typist can use */
typedef enum phantom_typist_mode_t {
/* J, SS + P, SS + P, Enter => LOAD "" */
PHANTOM_TYPIST_MODE_JPP,
/* J, SS + P, SS + P, CS + SS, I, Enter => LOAD ""CODE */
PHANTOM_TYPIST_MODE_JPPI,
PHANTOM_TYPIST_MODE_ENTER, /* Enter only */
/* Down, Enter, L, O, A, D, SS + P, SS + P, C, O, D, E =>
LOAD ""CODE in BASIC */
PHANTOM_TYPIST_MODE_DOWN_LOADPPCODE,
/* Wait a bit and then perform the above */
PHANTOM_TYPIST_MODE_WAIT_DOWN_LOADPPCODE,
/* Wait a bit, Down, Enter,
L, O, A, D, SS + P, T, SS + Z, SS + P, Enter,
L, O, A, D, SS + P, SS + P, C, O, D, E, Enter =>
LOAD "t" followed by LOAD ""CODE in BASIC */
PHANTOM_TYPIST_MODE_PLUS3_CODE_BLOCK,
/* L, O, A, D, SS + P, SS + P, Enter => LOAD "" */
PHANTOM_TYPIST_MODE_LOADPP,
/* L, O, A, D, SS + P, SS + P, C, O, D, E, Enter => LOAD ""CODE */
PHANTOM_TYPIST_MODE_LOADPPCODE
} phantom_typist_mode_t;
typedef enum phantom_typist_state_t {
/* The phantom typist is inactive */
PHANTOM_TYPIST_STATE_INACTIVE,
/* The phantom typist is waiting for the keyboard to be read */
PHANTOM_TYPIST_STATE_WAITING,
/* JPP mode */
PHANTOM_TYPIST_STATE_LOAD,
PHANTOM_TYPIST_STATE_QUOTE1,
PHANTOM_TYPIST_STATE_QUOTE2,
PHANTOM_TYPIST_STATE_ENTER,
/* Enter mode */
PHANTOM_TYPIST_STATE_ENTER_ONLY,
PHANTOM_TYPIST_STATE_WAIT_AFTER_ENTER,
/* LOAD mode - chains into the quotes of JPP mode */
PHANTOM_TYPIST_STATE_LOAD_L,
PHANTOM_TYPIST_STATE_LOAD_O,
PHANTOM_TYPIST_STATE_LOAD_A,
PHANTOM_TYPIST_STATE_LOAD_D,
/* For producing "CODE" in keyword mode */
PHANTOM_TYPIST_STATE_EXTENDED_MODE,
PHANTOM_TYPIST_STATE_CODE,
/* For producing "CODE" in single character entry mode */
PHANTOM_TYPIST_STATE_CODE_C,
PHANTOM_TYPIST_STATE_CODE_O,
PHANTOM_TYPIST_STATE_CODE_D,
PHANTOM_TYPIST_STATE_CODE_E,
/* For selecting 128K BASIC or similar */
PHANTOM_TYPIST_STATE_LONG_WAIT_DOWN,
PHANTOM_TYPIST_STATE_WAIT_DOWN,
PHANTOM_TYPIST_STATE_DOWN,
PHANTOM_TYPIST_STATE_SELECT_BASIC,
PHANTOM_TYPIST_STATE_WAIT,
/* For selecting tape on a +3 machine */
PHANTOM_TYPIST_STATE_TCOLON_T,
PHANTOM_TYPIST_STATE_TCOLON_COLON,
/* For typing L and O somewhat slowly */
PHANTOM_TYPIST_STATE_SLOW_LOAD_L,
PHANTOM_TYPIST_STATE_SLOW_LOAD_O,
} phantom_typist_state_t;
/* States for the phantom typist state machine */
struct state_info_t {
/* The keys to be pressed in this state */
keyboard_key_name keys_to_press[2];
/* The function used to determine the next state */
phantom_typist_state_t (*next_state_fn)( void );
/* The next state to move to - used if next_state_fn is NULL */
phantom_typist_state_t next_state;
/* The number of frames to wait before acting in this state */
int delay_before_state;
};
/* Next state selector functions */
static phantom_typist_state_t t_colon_or_quote( void );
static phantom_typist_state_t code_or_enter( void );
static phantom_typist_state_t next_command_or_end( void );
/* Reset function */
static void phantom_typist_reset( int hard_reset );
/* Definitions for the phantom typist's state machine */
static struct state_info_t state_info[] = {
/* INACTIVE and WAITING - data not used */
{ { 0, 0 }, NULL, 0, 0 },
{ { 0, 0 }, NULL, 0, 0 },
{ { KEYBOARD_j, KEYBOARD_NONE }, NULL, PHANTOM_TYPIST_STATE_QUOTE1, 8 },
{ { KEYBOARD_Symbol, KEYBOARD_p }, t_colon_or_quote, 0, 0 },
{ { KEYBOARD_Symbol, KEYBOARD_p }, code_or_enter, 0, 5 },
{ { KEYBOARD_Enter, KEYBOARD_NONE }, next_command_or_end, 0, 0 },
{ { KEYBOARD_Enter, KEYBOARD_NONE }, NULL, PHANTOM_TYPIST_STATE_WAIT_AFTER_ENTER, 3 },
/* This state is here to "swallow" the pause while the +3 checks for a disk */
{ { KEYBOARD_NONE, KEYBOARD_NONE }, NULL, PHANTOM_TYPIST_STATE_INACTIVE, 600 },
{ { KEYBOARD_l, KEYBOARD_NONE }, NULL, PHANTOM_TYPIST_STATE_LOAD_O, 2 },
{ { KEYBOARD_o, KEYBOARD_NONE }, NULL, PHANTOM_TYPIST_STATE_LOAD_A, 0 },
{ { KEYBOARD_a, KEYBOARD_NONE }, NULL, PHANTOM_TYPIST_STATE_LOAD_D, 5 },
{ { KEYBOARD_d, KEYBOARD_NONE }, NULL, PHANTOM_TYPIST_STATE_QUOTE1, 4 },
{ { KEYBOARD_Caps, KEYBOARD_Symbol }, NULL, PHANTOM_TYPIST_STATE_CODE, 0 },
{ { KEYBOARD_i, KEYBOARD_NONE }, NULL, PHANTOM_TYPIST_STATE_ENTER, 4 },
{ { KEYBOARD_c, KEYBOARD_NONE }, NULL, PHANTOM_TYPIST_STATE_CODE_O, 0 },
{ { KEYBOARD_o, KEYBOARD_NONE }, NULL, PHANTOM_TYPIST_STATE_CODE_D, 4 },
{ { KEYBOARD_d, KEYBOARD_NONE }, NULL, PHANTOM_TYPIST_STATE_CODE_E, 0 },
{ { KEYBOARD_e, KEYBOARD_NONE }, NULL, PHANTOM_TYPIST_STATE_ENTER, 4 },
{ { KEYBOARD_NONE, KEYBOARD_NONE }, NULL, PHANTOM_TYPIST_STATE_WAIT_DOWN, 50 },
{ { KEYBOARD_NONE, KEYBOARD_NONE }, NULL, PHANTOM_TYPIST_STATE_DOWN, 24 },
{ { KEYBOARD_Caps, KEYBOARD_6 }, NULL, PHANTOM_TYPIST_STATE_SELECT_BASIC, 4 },
{ { KEYBOARD_Enter, KEYBOARD_NONE }, NULL, PHANTOM_TYPIST_STATE_WAIT, 10 },
{ { KEYBOARD_NONE, KEYBOARD_NONE }, NULL, PHANTOM_TYPIST_STATE_LOAD_L, 28 },
{ { KEYBOARD_t, KEYBOARD_NONE }, NULL, PHANTOM_TYPIST_STATE_TCOLON_COLON, 4 },
{ { KEYBOARD_Symbol, KEYBOARD_z }, NULL, PHANTOM_TYPIST_STATE_QUOTE2, 4 },
{ { KEYBOARD_l, KEYBOARD_NONE }, NULL, PHANTOM_TYPIST_STATE_SLOW_LOAD_O, 5 },
{ { KEYBOARD_o, KEYBOARD_NONE }, NULL, PHANTOM_TYPIST_STATE_LOAD_A, 30 }
};
static phantom_typist_mode_t phantom_typist_mode;
static phantom_typist_state_t phantom_typist_state = PHANTOM_TYPIST_STATE_INACTIVE;
static phantom_typist_state_t next_phantom_typist_state = PHANTOM_TYPIST_STATE_INACTIVE;
static int delay = 0;
static libspectrum_byte keyboard_ports_read = 0x00;
static int command_count;
static module_info_t phantom_typist_module_info = {
phantom_typist_reset,
NULL,
NULL,
NULL,
NULL,
};
static int
phantom_typist_init( void *context )
{
module_register( &phantom_typist_module_info );
return 0;
}
void
phantom_typist_register_startup( void )
{
startup_manager_module dependencies[] = { STARTUP_MANAGER_MODULE_SETUID };
startup_manager_register( STARTUP_MANAGER_MODULE_PHANTOM_TYPIST, dependencies,
ARRAY_SIZE( dependencies ), phantom_typist_init,
NULL, NULL );
}
static void
phantom_typist_reset( int hard_reset )
{
phantom_typist_state = PHANTOM_TYPIST_STATE_INACTIVE;
next_phantom_typist_state = PHANTOM_TYPIST_STATE_INACTIVE;
}
static phantom_typist_highlevel_mode_t
get_highlevel_mode( libspectrum_machine machine )
{
phantom_typist_highlevel_mode_t highlevel_mode;
switch( machine ) {
case LIBSPECTRUM_MACHINE_16:
case LIBSPECTRUM_MACHINE_48:
case LIBSPECTRUM_MACHINE_48_NTSC:
case LIBSPECTRUM_MACHINE_TC2048:
case LIBSPECTRUM_MACHINE_TC2068:
case LIBSPECTRUM_MACHINE_TS2068:
default:
highlevel_mode = PHANTOM_TYPIST_HIGHLEVEL_MODE_KEYWORD;
break;
case LIBSPECTRUM_MACHINE_128:
case LIBSPECTRUM_MACHINE_128E:
case LIBSPECTRUM_MACHINE_PLUS2:
case LIBSPECTRUM_MACHINE_PENT:
case LIBSPECTRUM_MACHINE_PENT512:
case LIBSPECTRUM_MACHINE_PENT1024:
case LIBSPECTRUM_MACHINE_SCORP:
highlevel_mode = PHANTOM_TYPIST_HIGHLEVEL_MODE_MENU;
break;
case LIBSPECTRUM_MACHINE_PLUS2A:
highlevel_mode = PHANTOM_TYPIST_HIGHLEVEL_MODE_PLUS2A;
break;
case LIBSPECTRUM_MACHINE_PLUS3:
case LIBSPECTRUM_MACHINE_PLUS3E:
highlevel_mode = PHANTOM_TYPIST_HIGHLEVEL_MODE_PLUS3;
break;
case LIBSPECTRUM_MACHINE_SE:
highlevel_mode = PHANTOM_TYPIST_HIGHLEVEL_MODE_KEYSTROKE;
break;
}
return highlevel_mode;
}
static void
set_state_waiting( void )
{
command_count = 0;
phantom_typist_state = PHANTOM_TYPIST_STATE_WAITING;
next_phantom_typist_state = PHANTOM_TYPIST_STATE_WAITING;
timer_start_fastloading();
}
void
phantom_typist_activate( libspectrum_machine machine, int needs_code )
{
phantom_typist_highlevel_mode_t highlevel_mode;
const char *setting = settings_current.phantom_typist_mode;
if( strcasecmp( setting, "Keyword" ) == 0 ) {
highlevel_mode = PHANTOM_TYPIST_HIGHLEVEL_MODE_KEYWORD;
} else if( strcasecmp( setting, "Keystroke" ) == 0) {
highlevel_mode = PHANTOM_TYPIST_HIGHLEVEL_MODE_KEYSTROKE;
} else if( strcasecmp( setting, "Menu" ) == 0) {
highlevel_mode = PHANTOM_TYPIST_HIGHLEVEL_MODE_MENU;
} else if( strcasecmp( setting, "Plus 2A" ) == 0 ||
strcasecmp( setting, "plus2a" ) == 0) {
highlevel_mode = PHANTOM_TYPIST_HIGHLEVEL_MODE_PLUS2A;
} else if( strcasecmp( setting, "Plus 3" ) == 0 ||
strcasecmp( setting, "plus3" ) == 0) {
highlevel_mode = PHANTOM_TYPIST_HIGHLEVEL_MODE_PLUS3;
} else if( strcasecmp( setting, "Auto" ) == 0) {
highlevel_mode = get_highlevel_mode( machine );
} else {
highlevel_mode = PHANTOM_TYPIST_HIGHLEVEL_MODE_KEYWORD;
}
switch( highlevel_mode ) {
case PHANTOM_TYPIST_HIGHLEVEL_MODE_KEYWORD:
default:
phantom_typist_mode = needs_code ?
PHANTOM_TYPIST_MODE_JPPI :
PHANTOM_TYPIST_MODE_JPP;
break;
case PHANTOM_TYPIST_HIGHLEVEL_MODE_KEYSTROKE:
phantom_typist_mode = needs_code ?
PHANTOM_TYPIST_MODE_LOADPPCODE :
PHANTOM_TYPIST_MODE_LOADPP;
break;
case PHANTOM_TYPIST_HIGHLEVEL_MODE_MENU:
phantom_typist_mode = needs_code ?
PHANTOM_TYPIST_MODE_DOWN_LOADPPCODE :
PHANTOM_TYPIST_MODE_ENTER;
break;
case PHANTOM_TYPIST_HIGHLEVEL_MODE_PLUS2A:
phantom_typist_mode = needs_code ?
PHANTOM_TYPIST_MODE_WAIT_DOWN_LOADPPCODE :
PHANTOM_TYPIST_MODE_ENTER;
break;
case PHANTOM_TYPIST_HIGHLEVEL_MODE_PLUS3:
phantom_typist_mode = needs_code ?
PHANTOM_TYPIST_MODE_PLUS3_CODE_BLOCK :
PHANTOM_TYPIST_MODE_ENTER;
break;
}
set_state_waiting();
}
void
phantom_typist_activate_disk( void )
{
phantom_typist_mode = PHANTOM_TYPIST_MODE_ENTER;
set_state_waiting();
}
void
phantom_typist_deactivate( void )
{
if( phantom_typist_is_active() ) {
phantom_typist_state = PHANTOM_TYPIST_STATE_WAITING;
next_phantom_typist_state = PHANTOM_TYPIST_STATE_INACTIVE;
}
}
static void
process_waiting_state( libspectrum_byte high_byte )
{
switch( high_byte ) {
case 0xfe:
case 0xfd:
case 0xfb:
case 0xf7:
case 0xef:
case 0xdf:
case 0xbf:
case 0x7f:
keyboard_ports_read |= ~high_byte;
break;
}
if( keyboard_ports_read == 0xff ) {
switch( phantom_typist_mode ) {
case PHANTOM_TYPIST_MODE_JPP:
case PHANTOM_TYPIST_MODE_JPPI:
next_phantom_typist_state = PHANTOM_TYPIST_STATE_LOAD;
break;
case PHANTOM_TYPIST_MODE_ENTER:
next_phantom_typist_state = PHANTOM_TYPIST_STATE_ENTER_ONLY;
break;
case PHANTOM_TYPIST_MODE_DOWN_LOADPPCODE:
next_phantom_typist_state = PHANTOM_TYPIST_STATE_DOWN;
break;
case PHANTOM_TYPIST_MODE_WAIT_DOWN_LOADPPCODE:
next_phantom_typist_state = PHANTOM_TYPIST_STATE_WAIT_DOWN;
break;
case PHANTOM_TYPIST_MODE_PLUS3_CODE_BLOCK:
next_phantom_typist_state = PHANTOM_TYPIST_STATE_LONG_WAIT_DOWN;
break;
case PHANTOM_TYPIST_MODE_LOADPP:
case PHANTOM_TYPIST_MODE_LOADPPCODE:
next_phantom_typist_state = PHANTOM_TYPIST_STATE_LOAD_L;
break;
default:
next_phantom_typist_state = PHANTOM_TYPIST_STATE_INACTIVE;
break;
}
}
}
static phantom_typist_state_t
t_colon_or_quote( void )
{
phantom_typist_state_t next_state;
if( phantom_typist_mode == PHANTOM_TYPIST_MODE_PLUS3_CODE_BLOCK &&
command_count == 0 ) {
next_state = PHANTOM_TYPIST_STATE_TCOLON_T;
} else {
next_state = PHANTOM_TYPIST_STATE_QUOTE2;
}
return next_state;
}
static phantom_typist_state_t
code_or_enter( void )
{
phantom_typist_state_t next_state;
switch( phantom_typist_mode ) {
case PHANTOM_TYPIST_MODE_JPP:
case PHANTOM_TYPIST_MODE_LOADPP:
next_state = PHANTOM_TYPIST_STATE_ENTER;
break;
case PHANTOM_TYPIST_MODE_PLUS3_CODE_BLOCK:
next_state = command_count == 0 ?
PHANTOM_TYPIST_STATE_ENTER :
PHANTOM_TYPIST_STATE_CODE_C;
break;
case PHANTOM_TYPIST_MODE_JPPI:
next_state = PHANTOM_TYPIST_STATE_EXTENDED_MODE;
break;
case PHANTOM_TYPIST_MODE_LOADPPCODE:
case PHANTOM_TYPIST_MODE_DOWN_LOADPPCODE:
case PHANTOM_TYPIST_MODE_WAIT_DOWN_LOADPPCODE:
next_state = PHANTOM_TYPIST_STATE_CODE_C;
break;
default:
next_state = PHANTOM_TYPIST_STATE_INACTIVE;
break;
}
return next_state;
}
static phantom_typist_state_t
next_command_or_end( void )
{
phantom_typist_state_t next_state;
if( phantom_typist_mode == PHANTOM_TYPIST_MODE_PLUS3_CODE_BLOCK &&
command_count == 0 ) {
next_state = PHANTOM_TYPIST_STATE_SLOW_LOAD_L;
} else {
next_state = PHANTOM_TYPIST_STATE_INACTIVE;
}
return next_state;
}
static libspectrum_byte
process_state( libspectrum_byte high_byte )
{
libspectrum_byte r = 0xff;
struct state_info_t *this_state = &state_info[phantom_typist_state];
r &= keyboard_simulate_keypress( high_byte, this_state->keys_to_press[0] );
r &= keyboard_simulate_keypress( high_byte, this_state->keys_to_press[1] );
next_phantom_typist_state = this_state->next_state_fn ?
this_state->next_state_fn() :
this_state->next_state;
return r;
}
libspectrum_byte
phantom_typist_ula_read( libspectrum_word port )
{
libspectrum_byte r = 0xff;
libspectrum_byte high_byte = port >> 8;
if( delay != 0 ) {
return r;
}
switch( phantom_typist_state ) {
case PHANTOM_TYPIST_STATE_INACTIVE:
/* Do nothing */
break;
case PHANTOM_TYPIST_STATE_WAITING:
process_waiting_state( high_byte );
break;
default:
r &= process_state( high_byte );
break;
}
return r;
}
int
phantom_typist_is_active( void )
{
return phantom_typist_state != PHANTOM_TYPIST_STATE_INACTIVE;
}
void
phantom_typist_frame( void )
{
keyboard_ports_read = 0x00;
if( next_phantom_typist_state != phantom_typist_state ) {
if( phantom_typist_mode == PHANTOM_TYPIST_MODE_PLUS3_CODE_BLOCK &&
phantom_typist_state == PHANTOM_TYPIST_STATE_ENTER ) {
command_count++;
}
phantom_typist_state = next_phantom_typist_state;
delay = state_info[phantom_typist_state].delay_before_state;
if( next_phantom_typist_state == PHANTOM_TYPIST_STATE_INACTIVE ) {
timer_stop_fastloading();
}
}
if( delay > 0 ) {
delay--;
}
}