Skip to content

Commit

Permalink
Engine: implement "accessibility" config for speech and text skip
Browse files Browse the repository at this point in the history
Adds two config options under "[acess]" group:
* speechskip
* textskip

Both may take following values:
* "none", or empty string (default)
* "input", meaning any player input
* "any", meaning any player input, or time
* "time", meaning time only

These options override in-game settings for skipping speech and displayed messages respectively.
  • Loading branch information
ivan-mogilko committed Aug 26, 2024
1 parent 1642521 commit 29196bd
Show file tree
Hide file tree
Showing 12 changed files with 86 additions and 14 deletions.
28 changes: 25 additions & 3 deletions Common/util/string_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,19 +95,29 @@ namespace StrUtil


// Parses enum value by name, using provided C-string array,
// where strings are compared as case-insensitive; returns def_val if failed;
// where strings are compared as case-insensitive; returns def_val if failed
template<typename T, std::size_t SIZE>
T ParseEnum(const String &option, const CstrArr<SIZE>& arr, const T def_val = static_cast<T>(-1))
T ParseEnum(const String &option, const CstrArr<SIZE> &arr, const T &def_val)
{
for (auto it = arr.cbegin(); it < arr.cend(); ++it)
if ((*it && *it[0] != 0) && (option.CompareNoCase(*it) == 0))
return static_cast<T>(it - arr.begin());
return def_val;
}
// Parses enum value by name, using provided C-string array and a base value (e.g. 0, -1, +1, etc),
// where strings are compared as case-insensitive; returns def_val if failed
template<typename T, std::size_t SIZE>
T ParseEnumWithBase(const String &option, const CstrArr<SIZE> &arr, const T &base_val, const T &def_val)
{
for (auto it = arr.cbegin(); it < arr.cend(); ++it)
if ((*it && *it[0] != 0) && (option.CompareNoCase(*it) == 0))
return static_cast<T>(it - arr.begin() + base_val);
return def_val;
}
// Parses enum value either as a number, or searching withing the C-string array,
// where strings are compared as case-insensitive; returns def_val if failed to do both
template<typename T, std::size_t SIZE>
T ParseEnumAllowNum(const String &option, const CstrArr<SIZE>& arr, const T def_val = static_cast<T>(-1))
T ParseEnumAllowNum(const String &option, const CstrArr<SIZE> &arr, const T &def_val)
{
int num = StrUtil::StringToInt(option, -1);
if (num >= 0) return static_cast<T>(num);
Expand All @@ -116,6 +126,18 @@ namespace StrUtil
return static_cast<T>(it - arr.begin());
return def_val;
}
// Parses enum value by name, using provided map of correspondence between
// C-strings and enum constants, where strings are compared as case-insensitive;
// returns def_val if failed
template<typename T, std::size_t SIZE>
T ParseEnumOptions(const String &option, const std::array<std::pair<const char*, T>, SIZE> &arr,
const T &def_val)
{
for (auto it = arr.cbegin(); it < arr.cend(); ++it)
if ((it->first && it->first[0] != 0) && (option.CompareNoCase(it->first) == 0))
return static_cast<T>(it->second);
return def_val;
}

// Convert utf-8 string to ascii/ansi representation;
// writes into out_cstr buffer limited by out_sz bytes; returns bytes written.
Expand Down
6 changes: 5 additions & 1 deletion Engine/ac/dynobj/scriptgame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "ac/dynobj/scriptgame.h"
#include "ac/gamesetupstruct.h"
#include "ac/game.h"
#include "ac/gamesetup.h"
#include "ac/gamestate.h"
#include "ac/gui.h"
#include "debug/debug_log.h"
Expand Down Expand Up @@ -157,7 +158,10 @@ void CCScriptGame::WriteInt32(void *address, intptr_t offset, int32_t val)
case 68: play.speech_textwindow_gui = val; break;
case 69: play.follow_change_room_timer = val; break;
case 70: play.totalscore = val; break;
case 71: play.skip_display = val; break;
case 71:
if (usetup.access_textskip == kSkipSpeechNone)
play.skip_display = static_cast<SkipSpeechStyle>(val);
break;
case 72: play.no_multiloop_repeat = val; break;
case 73: play.roomscript_finished = val; break;
case 74: play.used_inv_on = val; break;
Expand Down
7 changes: 7 additions & 0 deletions Engine/ac/gamesetup.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#define __AC_GAMESETUP_H

#include "ac/game_version.h"
#include "ac/speech.h"
#include "ac/sys_events.h"
#include "main/graphics_mode.h"
#include "util/string.h"
Expand Down Expand Up @@ -121,6 +122,12 @@ struct GameSetup
int key_save_game = 0;
int key_restore_game = 0;

// Accessibility settings and overrides;
// these are meant to make playing the game easier, by modifying certain
// game properties which are non-critical for the game progression.
SkipSpeechStyle access_speechskip = kSkipSpeechNone; // speech skip style
SkipSpeechStyle access_textskip = kSkipSpeechNone; // display box skip style

GameSetup();
};

Expand Down
2 changes: 1 addition & 1 deletion Engine/ac/gamestate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ void GamePlayState::ReadFromSavegame(Stream *in, GameDataVersion data_ver, GameS
speech_textwindow_gui = in->ReadInt32();
follow_change_room_timer = in->ReadInt32();
totalscore = in->ReadInt32();
skip_display = in->ReadInt32();
skip_display = static_cast<SkipSpeechStyle>(in->ReadInt32());
no_multiloop_repeat = in->ReadInt32();
roomscript_finished = in->ReadInt32();
used_inv_on = in->ReadInt32();
Expand Down
5 changes: 3 additions & 2 deletions Engine/ac/gamestate.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ struct GamePlayState
int speech_textwindow_gui = 0; // textwindow used for sierra-style speech
int follow_change_room_timer = 0; // delay before moving following characters into new room
int totalscore = 0; // maximum possible score
int skip_display = 0; // how the user can skip normal Display windows
SkipSpeechStyle skip_display = kSkipSpeechKeyMouse; // how the user can skip normal Display windows
int no_multiloop_repeat = 0; // for backwards compatibility
int roomscript_finished = 0; // on_call finished in room
int used_inv_on = 0; // inv item they clicked on
Expand Down Expand Up @@ -193,6 +193,7 @@ struct GamePlayState
short wait_counter = 0;
char wait_skipped_by = 0; // tells how last blocking wait was skipped [not serialized]
int wait_skipped_by_data = 0; // extended data telling how last blocking wait was skipped [not serialized]
SkipSpeechStyle skip_timed_display = kSkipSpeechKeyMouseTime; // how the timed room messages may be skipped (see MSG_TIMELIMIT) [not serialized]
short mboundx1 = 0;
short mboundx2 = 0;
short mboundy1 = 0;
Expand All @@ -211,7 +212,7 @@ struct GamePlayState
int entered_edge = 0;
bool voice_avail = false; // whether voice-over is available
SpeechMode speech_mode = kSpeech_TextOnly; // speech mode (text, voice, or both)
int speech_skip_style = 0;
int speech_skip_style = 0; // stores SKIP_* flags
int script_timers[MAX_TIMERS]{};
int sound_volume = 0;
int speech_volume = 0;
Expand Down
10 changes: 6 additions & 4 deletions Engine/ac/global_display.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "ac/display.h"
#include "ac/draw.h"
#include "ac/game.h"
#include "ac/gamesetup.h"
#include "ac/gamesetupstruct.h"
#include "ac/gamestate.h"
#include "ac/global_character.h"
Expand Down Expand Up @@ -122,13 +123,13 @@ void DisplayMessageImpl(int msnum, int aschar, int ypos) {
}
else {
// time out automatically if they have set that
int oldGameSkipDisp = play.skip_display;
const SkipSpeechStyle old_skip_display = play.skip_display;
if (thisroom.MessageInfos[msnum].Flags & MSG_TIMELIMIT)
play.skip_display = 0;
play.skip_display = play.skip_timed_display;

DisplayAtY(ypos, msgbufr);

play.skip_display = oldGameSkipDisp;
play.skip_display = old_skip_display;
}
if (thisroom.MessageInfos[msnum].Flags & MSG_DISPLAYNEXT) {
msnum++;
Expand Down Expand Up @@ -204,7 +205,8 @@ void SetSkipSpeech (SkipSpeechStyle newval) {
quit("!SetSkipSpeech: invalid skip mode specified");

debug_script_log("SkipSpeech style set to %d", newval);
play.speech_skip_style = user_to_internal_skip_speech((SkipSpeechStyle)newval);
if (usetup.access_speechskip != kSkipSpeechNone)
play.speech_skip_style = user_to_internal_skip_speech((SkipSpeechStyle)newval);
}

SkipSpeechStyle GetSkipSpeech()
Expand Down
7 changes: 6 additions & 1 deletion Engine/ac/speech.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,13 @@ enum SkipSpeechStyle
kSkipSpeechKey = 5,
kSkipSpeechMouse = 6,

// Aliases for the most useful styles (used in accessibility options)
kSkipSpeech_AnyInput = kSkipSpeechKeyMouse,
kSkipSpeech_AnyInputOrTime = kSkipSpeechKeyMouseTime,

kSkipSpeechFirst = kSkipSpeechNone,
kSkipSpeechLast = kSkipSpeechMouse
kSkipSpeechLast = kSkipSpeechMouse,
kNumSpeechSkipOptions = kSkipSpeechLast - kSkipSpeechFirst
};

enum SpeechMode
Expand Down
17 changes: 17 additions & 0 deletions Engine/game/game_init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -588,8 +588,25 @@ HGameInitError InitGameState(const LoadedGameEntities &ents, GameDataVersion dat
if (create_global_script())
return new GameInitError(kGameInitErr_ScriptLinkFailed, cc_get_error().ErrorString);

// Apply accessibility options, must be done last, because some
// may override startup game settings.
ApplyAccessibilityOptions();

return HGameInitError::None();
}

void ApplyAccessibilityOptions()
{
if (usetup.access_speechskip != kSkipSpeechNone)
{
play.speech_skip_style = user_to_internal_skip_speech(usetup.access_speechskip);
}
if (usetup.access_textskip != kSkipSpeechNone)
{
play.skip_display = usetup.access_textskip;
play.skip_timed_display = usetup.access_textskip;
}
}

} // namespace Engine
} // namespace AGS
4 changes: 3 additions & 1 deletion Engine/game/game_init.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ typedef TypedCodeError<GameInitErrorType, GetGameInitErrorText> GameInitError;
typedef ErrorHandle<GameInitError> HGameInitError;

// Sets up game state for play using preloaded data
HGameInitError InitGameState(const LoadedGameEntities &ents, GameDataVersion data_ver);
HGameInitError InitGameState(const LoadedGameEntities &ents, GameDataVersion data_ver);
// Applies accessibility options, some of them may override game settings
void ApplyAccessibilityOptions();

} // namespace Engine
} // namespace AGS
Expand Down
4 changes: 4 additions & 0 deletions Engine/game/savegame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,10 @@ HSaveError DoAfterRestore(const PreservedParams &pp, RestoredData &r_data)

set_game_speed(r_data.FPS);

// Apply accessibility options, must be done last, because some
// may override restored game settings
ApplyAccessibilityOptions();

return HSaveError::None();
}

Expand Down
8 changes: 8 additions & 0 deletions Engine/main/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,14 @@ void apply_config(const ConfigTree &cfg)
usetup.override_upscale = CfgReadBoolInt(cfg, "override", "upscale", usetup.override_upscale);
usetup.key_save_game = CfgReadInt(cfg, "override", "save_game_key", 0);
usetup.key_restore_game = CfgReadInt(cfg, "override", "restore_game_key", 0);

// Accessibility settings
std::array<std::pair<const char*, SkipSpeechStyle>, 4> skip_speech_arr{
{ { "none", kSkipSpeechNone }, { "input", kSkipSpeech_AnyInput }, { "any", kSkipSpeech_AnyInputOrTime }, { "time", kSkipSpeechTime } } };
usetup.access_speechskip = StrUtil::ParseEnumOptions<SkipSpeechStyle>(
CfgReadString(cfg, "access", "speechskip"), skip_speech_arr, kSkipSpeechNone);
usetup.access_textskip = StrUtil::ParseEnumOptions<SkipSpeechStyle>(
CfgReadString(cfg, "access", "textskip"), skip_speech_arr, kSkipSpeechNone);
}

// Apply logging configuration
Expand Down
2 changes: 1 addition & 1 deletion Engine/main/engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -766,7 +766,7 @@ void engine_init_game_settings()
play.swap_portrait_lastchar = -1;
play.swap_portrait_lastlastchar = -1;
play.in_conversation = 0;
play.skip_display = 3;
play.skip_display = kSkipSpeechKeyMouse;
play.no_multiloop_repeat = 0;
play.in_cutscene = 0;
play.fast_forward = 0;
Expand Down

0 comments on commit 29196bd

Please sign in to comment.