// Copyright (c) 2023 Alex Diener. All rights reserved. #include "3dmodelio/TextureAtlasData.h" #include "audioplayer/AudioPlayer.h" #include "binaryserialization/BinaryDeserializationContext.h" #include "dynamictypes/DataDeserializationContext.h" #include "dynamictypes/DataSerializationContext.h" #include "dynamictypes/DataSerializer.h" #include "gamepad/Gamepad.h" #include "imageio/ImageIO.h" #include "jsonserialization/JSONDeserializationContext.h" #include "jsonserialization/JSONSerializationContext.h" #include "PROJECT_NAME/Atoms.h" #include "PROJECT_NAME/GameSession.h" #include "PROJECT_NAME/Globals.h" #include "PROJECT_NAME/InputMapDefaults.h" #include "PROJECT_NAME/SharedDefinitions.h" #include "PROJECT_NAME/Sounds.h" #include "shell/Shell.h" #include "shell/ShellKeyCodes.h" #include "uitoolkit/UIDrawingInterface2DTexture.h" #include "uitoolkit/UIToolkitAppearance.h" #include "uitoolkit/UITypeface_BitmapFont.h" #include "utilities/IOUtilities.h" #include "stem_core.h" #include #include #include #define stemobject_implementation GameSession v_begin(); v_func(dispose); v_end(); #define PREFERENCE_KEY_PLAY_SOUND "play_sound" #define PREFERENCE_KEY_PLAY_MUSIC "play_music" #define PREFERENCE_KEY_SOUND_VOLUME "sound_volume" #define PREFERENCE_KEY_MUSIC_VOLUME "music_volume" #define PREFERENCE_KEY_FULL_SCREEN "full_screen" #define PREFERENCE_KEY_INPUT_MAP "input_map" #define PREFERENCE_KEY_REPEAT_DELAY "repeat_delay" #define PREFERENCE_KEY_REPEAT_INTERVAL "repeat_interval" #define REPEAT_DELAY_MIN 1 #define REPEAT_DELAY_MAX 5 #define REPEAT_INTERVAL_MIN 1 #define REPEAT_INTERVAL_MAX 5 GameSession * GameSession_create(void) { stemobject_create_implementation(init) } static void updateRepeatRate(GameSession * self) { double repeatDelays[REPEAT_DELAY_MAX - REPEAT_DELAY_MIN + 1] = { 0.0, 0.06, 0.12, 0.2, 0.3, }; double repeatIntervals[REPEAT_INTERVAL_MAX - REPEAT_INTERVAL_MIN + 1] = { 0.06, 0.08, 0.1, 0.2, 0.3 }; unsigned int delayIndex = GameSession_getRepeatDelay(self) - REPEAT_DELAY_MIN; unsigned int intervalIndex = GameSession_getRepeatInterval(self) - REPEAT_INTERVAL_MIN; InputController_setRepeatRate(self->inputController, repeatDelays[delayIndex] + repeatIntervals[intervalIndex], repeatIntervals[intervalIndex], repeatIntervals[intervalIndex]); } static void writeInputMapToPreferences(GameSession * self) { DataSerializationContext * serializationContext = DataSerializationContext_create(); InputMap_serialize(self->inputMap, serializationContext); Preferences_set(self->preferences, PREFERENCE_KEY_INPUT_MAP, *DataSerializationContext_result(serializationContext)); call_virtual(dispose, serializationContext); } bool GameSession_init(GameSession * self) { call_super(init, self); self->preferences = Preferences_create(); Preferences_set(self->preferences, PREFERENCE_KEY_PLAY_SOUND, valueCreateBoolean(true)); Preferences_set(self->preferences, PREFERENCE_KEY_PLAY_MUSIC, valueCreateBoolean(true)); Preferences_set(self->preferences, PREFERENCE_KEY_SOUND_VOLUME, valueCreateFloat(1.0f)); Preferences_set(self->preferences, PREFERENCE_KEY_MUSIC_VOLUME, valueCreateFloat(1.0f)); Preferences_set(self->preferences, PREFERENCE_KEY_FULL_SCREEN, valueCreateBoolean(false)); Preferences_set(self->preferences, PREFERENCE_KEY_REPEAT_DELAY, valueCreateUInt8(3)); Preferences_set(self->preferences, PREFERENCE_KEY_REPEAT_INTERVAL, valueCreateUInt8(3)); Preferences_set(self->preferences, PREFERENCE_KEY_INPUT_MAP, valueCreateHashTable(hashCreate(), true, false)); extern const char EMBEDDATA_InputMapSchema_bin[]; extern unsigned int EMBEDSIZE_InputMapSchema_bin; BinaryDeserializationContext * binaryDeserializationContext = BinaryDeserializationContext_createWithBytes(EMBEDDATA_InputMapSchema_bin, EMBEDSIZE_InputMapSchema_bin); DataValueSchema * inputMapSchema = DataValueSchema_deserialize(binaryDeserializationContext); BinaryDeserializationContext_dispose(binaryDeserializationContext); DataValueSchemaField preferencesSchemaFields[] = { {DATA_TYPE_BOOLEAN, PREFERENCE_KEY_PLAY_SOUND, valueCreateBoolean(true), 0, NULL, NULL}, {DATA_TYPE_BOOLEAN, PREFERENCE_KEY_PLAY_MUSIC, valueCreateBoolean(true), 0, NULL, NULL}, {DATA_TYPE_FLOAT, PREFERENCE_KEY_SOUND_VOLUME, valueCreateFloat(1.0f), 0, NULL, NULL}, {DATA_TYPE_FLOAT, PREFERENCE_KEY_MUSIC_VOLUME, valueCreateFloat(1.0f), 0, NULL, NULL}, {DATA_TYPE_BOOLEAN, PREFERENCE_KEY_FULL_SCREEN, valueCreateBoolean(false), 0, NULL, NULL}, {DATA_TYPE_UINT8, PREFERENCE_KEY_REPEAT_DELAY, valueCreateUInt8(3), 0, NULL, NULL}, {DATA_TYPE_UINT8, PREFERENCE_KEY_REPEAT_INTERVAL, valueCreateUInt8(3), 0, NULL, NULL}, {DATA_TYPE_HASH_TABLE, PREFERENCE_KEY_INPUT_MAP, valueCreateBoolean(false), 0, NULL, inputMapSchema} }; self->preferencesSchema = DataValueSchema_create(sizeof_count(preferencesSchemaFields), preferencesSchemaFields); DataValueSchema_dispose(inputMapSchema); char path[PATH_MAX]; Preferences_getFilePath(PREFERENCES_FILE_NAME, path, sizeof(path)); JSONDeserializationContext * context = JSONDeserializationContext_createWithFile(path); if (context->status == SERIALIZATION_ERROR_OK) { call_virtual(beginStructure, context, "preferences"); uint16_t preferencesVersion = call_virtual(readUInt16, context, "version"); if (preferencesVersion == PREFERENCES_LATEST_VERSION) { DataValue preferencesData = DataValue_deserializeWithSchema(self->preferencesSchema, "preferences", DATA_TYPE_HASH_TABLE, context, true); call_virtual(endStructure, context); if (preferencesData.type == DATA_TYPE_HASH_TABLE) { Preferences_import(self->preferences, preferencesData.value.hashTable); } valueDispose(&preferencesData); } } JSONDeserializationContext_dispose(context); self->inputMap = NULL; DataDeserializationContext * dataDeserializationContext = DataDeserializationContext_create(*Preferences_get(self->preferences, PREFERENCE_KEY_INPUT_MAP)); if (dataDeserializationContext->status == SERIALIZATION_ERROR_OK) { self->inputMap = InputMap_deserialize(dataDeserializationContext, true); } DataDeserializationContext_dispose(dataDeserializationContext); if (self->inputMap == NULL) { self->inputMap = InputMap_create(); bindDefaultActions(self->inputMap); writeInputMapToPreferences(self); } self->gamepadMap = GamepadMap_create(); GamepadMap_registerGlobalDefaults(self->gamepadMap); self->inputController = InputController_create(self->gamepadMap, self->inputMap, ATOM_input_left, ATOM_input_right, ATOM_input_up, ATOM_input_down, ATOM_input_undo, ATOM_input_redo, ATOM_input_reset, ATOM_input_wait, ATOM_input_interact, ATOM_input_pause, ATOM_input_save, ATOM_input_load, ATOM_input_menu_left, ATOM_input_menu_right, ATOM_input_menu_up, ATOM_input_menu_down, ATOM_input_menu_next, ATOM_input_menu_previous, ATOM_input_menu_accept, ATOM_input_menu_cancel, ATOM_input_menu_alternate, ATOM_input_menu_scroll_up, ATOM_input_menu_scroll_down, NULL); updateRepeatRate(self); self->eventDispatcher = EventDispatcher_create(); return true; } void GameSession_dispose(GameSession * self) { InputController_dispose(self->inputController); InputMap_dispose(self->inputMap); GamepadMap_dispose(self->gamepadMap); Preferences_dispose(self->preferences); DataValueSchema_dispose(self->preferencesSchema); EventDispatcher_dispose(self->eventDispatcher); call_super(dispose, self); } bool GameSession_saveControlBindings(GameSession * self) { writeInputMapToPreferences(self); return GameSession_savePreferences(self); } bool GameSession_savePreferences(GameSession * self) { char filePath[PATH_MAX]; Preferences_getFilePath(PREFERENCES_FILE_NAME, filePath, sizeof(filePath)); JSONSerializationContext * serializationContext = JSONSerializationContext_create(); call_virtual(beginStructure, serializationContext, "preferences"); call_virtual(writeUInt16, serializationContext, "version", PREFERENCES_LATEST_VERSION); DataValue preferencesData = valueCreateHashTable(Preferences_export(self->preferences), true, false); DataValue_serializeWithSchema(&preferencesData, self->preferencesSchema, "preferences", serializationContext); valueDispose(&preferencesData); call_virtual(endStructure, serializationContext); struct JSONEmissionError error = {.description = NULL}; bool success = JSONSerializationContext_writeToFile(serializationContext, JSONEmitterFormat_multiLine, filePath, &error); #ifdef DEBUG if (!success) { fprintf(stderr, "Error: Couldn't save preferences: %s (%d)\n", error.description, error.code); } #endif call_virtual(dispose, serializationContext); return success; } bool GameSession_getSoundEnabled(GameSession * self) { return valueGetBoolean(Preferences_get(self->preferences, PREFERENCE_KEY_PLAY_SOUND)); } void GameSession_setSoundEnabled(GameSession * self, bool value) { Preferences_set(self->preferences, PREFERENCE_KEY_PLAY_SOUND, valueCreateBoolean(value)); GameSession_savePreferences(self); if (value) { AudioPlayer_setCategoryVolume(AUDIO_CATEGORY_SOUND, GameSession_getSoundVolume(self) / 10.0f); } else { AudioPlayer_setCategoryVolume(AUDIO_CATEGORY_SOUND, 0.0f); } } bool GameSession_getMusicEnabled(GameSession * self) { return valueGetBoolean(Preferences_get(self->preferences, PREFERENCE_KEY_PLAY_MUSIC)); } void GameSession_setMusicEnabled(GameSession * self, bool value) { Preferences_set(self->preferences, PREFERENCE_KEY_PLAY_MUSIC, valueCreateBoolean(value)); GameSession_savePreferences(self); if (value) { AudioPlayer_setCategoryVolume(AUDIO_CATEGORY_MUSIC, GameSession_getMusicVolume(self) / 10.0f); } else { AudioPlayer_setCategoryVolume(AUDIO_CATEGORY_MUSIC, 0.0f); } } float GameSession_getSoundVolume(GameSession * self) { float value = valueGetFloat(Preferences_get(self->preferences, PREFERENCE_KEY_SOUND_VOLUME)); if (value > 1.0f) { value = 1.0f; } if (value < 0.0f) { value = 0.0f; } return value; } void GameSession_setSoundVolume(GameSession * self, float value) { if (value > 1.0f) { value = 1.0f; } if (value < 0.0f) { value = 0.0f; } Preferences_set(self->preferences, PREFERENCE_KEY_SOUND_VOLUME, valueCreateFloat(value)); GameSession_savePreferences(self); if (GameSession_getSoundEnabled(self)) { AudioPlayer_setCategoryVolume(AUDIO_CATEGORY_SOUND, value); } } float GameSession_getMusicVolume(GameSession * self) { float value = valueGetFloat(Preferences_get(self->preferences, PREFERENCE_KEY_MUSIC_VOLUME)); if (value > 1.0f) { value = 1.0f; } if (value < 0.0f) { value = 0.0f; } return value; } void GameSession_setMusicVolume(GameSession * self, float value) { if (value > 1.0f) { value = 1.0f; } if (value < 0.0f) { value = 0.0f; } Preferences_set(self->preferences, PREFERENCE_KEY_MUSIC_VOLUME, valueCreateFloat(value)); GameSession_savePreferences(self); if (GameSession_getMusicEnabled(self)) { AudioPlayer_setCategoryVolume(AUDIO_CATEGORY_MUSIC, value); } } bool GameSession_getFullScreen(GameSession * self) { return valueGetBoolean(Preferences_get(self->preferences, PREFERENCE_KEY_FULL_SCREEN)); } void GameSession_setFullScreen(GameSession * self, bool value) { Preferences_set(self->preferences, PREFERENCE_KEY_FULL_SCREEN, valueCreateBoolean(value)); GameSession_savePreferences(self); if (Shell_isFullScreen() != value) { if (value) { Shell_enterFullScreen(Shell_getDisplayIndexFromWindow(), false); } else { Shell_exitFullScreen(); } } } unsigned int GameSession_getRepeatDelay(GameSession * self) { unsigned int value = valueGetUInt8(Preferences_get(self->preferences, PREFERENCE_KEY_REPEAT_DELAY)); if (value < REPEAT_DELAY_MIN) { value = REPEAT_DELAY_MIN; } else if (value > REPEAT_DELAY_MAX) { value = REPEAT_DELAY_MAX; } return value; } void GameSession_setRepeatDelay(GameSession * self, unsigned int value) { if (value < REPEAT_DELAY_MIN) { value = REPEAT_DELAY_MIN; } else if (value > REPEAT_DELAY_MAX) { value = REPEAT_DELAY_MAX; } Preferences_set(self->preferences, PREFERENCE_KEY_REPEAT_DELAY, valueCreateUInt8(value)); GameSession_savePreferences(self); updateRepeatRate(self); } unsigned int GameSession_getRepeatInterval(GameSession * self) { unsigned int value = valueGetUInt8(Preferences_get(self->preferences, PREFERENCE_KEY_REPEAT_INTERVAL)); if (value < REPEAT_INTERVAL_MIN) { value = REPEAT_INTERVAL_MIN; } else if (value > REPEAT_INTERVAL_MAX) { value = REPEAT_INTERVAL_MAX; } return value; } void GameSession_setRepeatInterval(GameSession * self, unsigned int value) { if (value < REPEAT_INTERVAL_MIN) { value = REPEAT_INTERVAL_MIN; } else if (value > REPEAT_INTERVAL_MAX) { value = REPEAT_INTERVAL_MAX; } Preferences_set(self->preferences, PREFERENCE_KEY_REPEAT_INTERVAL, valueCreateUInt8(value)); GameSession_savePreferences(self); updateRepeatRate(self); }