// Copyright (c) 2014 Alex Diener. All rights reserved. #include "dynamictypes/DataDeserializationContext.h" #include "dynamictypes/DataSerializationContext.h" #include "dynamictypes/DataSerializer.h" #include "jsonserialization/JSONDeserializationContext.h" #include "jsonserialization/JSONSerializationContext.h" #include "shell/Shell.h" #include "shell/ShellKeyCodes.h" #include "utilities/IOUtilities.h" #include "watertowerclassic/GameSession.h" #include "watertowerclassic/LevelFileIO.h" #include "watertowerclassic/ResourceWiring.h" #include #include #include #include #include #define SUPERCLASS StemObject #define PREFERENCES_FILE_NAME "watertowerclassic_preferences.json" #define SUPPORT_PATH_SUBDIRECTORY "watertowerclassic" GameSession * GameSession_create() { stemobject_create_implementation(GameSession, init) } #define TRIGGER_THRESHOLD 0.5f #define RELEASE_THRESHOLD 0.375f #define VENDOR_ID_XBOX360 0x45E #define PRODUCT_ID_XBOX360 0x28E #define VENDOR_ID_DUALSHOCK4 0x54C #define PRODUCT_ID_DUALSHOCK4 0x5C4 bool GameSession_init(GameSession * self) { JSONDeserializationContext * jsonDeserializationContext; HashTable * hashTable = NULL; char filePath[PATH_MAX]; call_super(init, self); self->dispose = GameSession_dispose; self->preferences = Preferences_create(); Preferences_set(self->preferences, "play_sound", valueCreateBoolean(true)); Preferences_set(self->preferences, "play_music", valueCreateBoolean(true)); Preferences_set(self->preferences, "integer_scale", valueCreateBoolean(false)); Preferences_set(self->preferences, "linear_filter", valueCreateBoolean(false)); Preferences_set(self->preferences, "fullscreen", valueCreateBoolean(false)); Preferences_set(self->preferences, "vsync_window", valueCreateBoolean(false)); Preferences_set(self->preferences, "vsync_fullscreen", valueCreateBoolean(true)); Preferences_getFilePath(PREFERENCES_FILE_NAME, filePath, PATH_MAX); jsonDeserializationContext = JSONDeserializationContext_createWithFile(filePath); if (jsonDeserializationContext->status == SERIALIZATION_ERROR_OK) { DataValue preferencesData; preferencesData = DataValue_deserialize(jsonDeserializationContext); hashTable = valueGetHashTable(&preferencesData); if (hashTable != NULL) { Preferences_import(self->preferences, hashTable); } valueDispose(&preferencesData); } JSONDeserializationContext_dispose(jsonDeserializationContext); self->inputMap = NULL; if (Preferences_get(self->preferences, "input_map") != NULL) { DataDeserializationContext * dataDeserializationContext; dataDeserializationContext = DataDeserializationContext_create(*Preferences_get(self->preferences, "input_map")); if (dataDeserializationContext->status == SERIALIZATION_ERROR_OK) { self->inputMap = InputMap_deserialize(dataDeserializationContext); } DataDeserializationContext_dispose(dataDeserializationContext); } if (self->inputMap == NULL) { self->inputMap = InputMap_create(); InputMap_bindKey(self->inputMap, ATOM("left"), KEYBOARD_LEFT_ARROW); InputMap_bindKey(self->inputMap, ATOM("right"), KEYBOARD_RIGHT_ARROW); InputMap_bindKey(self->inputMap, ATOM("down"), KEYBOARD_DOWN_ARROW); InputMap_bindKey(self->inputMap, ATOM("up"), KEYBOARD_UP_ARROW); InputMap_bindKey(self->inputMap, ATOM("jump"), KEYBOARD_SPACEBAR); InputMap_bindKey(self->inputMap, ATOM("action"), KEYBOARD_UP_ARROW); InputMap_bindKey(self->inputMap, ATOM("pause"), KEYBOARD_P); #if defined(STEM_PLATFORM_macosx) InputMap_bindAxis(self->inputMap, ATOM("left"), VENDOR_ID_XBOX360, PRODUCT_ID_XBOX360, 0, -TRIGGER_THRESHOLD, -RELEASE_THRESHOLD); InputMap_bindAxis(self->inputMap, ATOM("right"), VENDOR_ID_XBOX360, PRODUCT_ID_XBOX360, 0, TRIGGER_THRESHOLD, RELEASE_THRESHOLD); InputMap_bindAxis(self->inputMap, ATOM("down"), VENDOR_ID_XBOX360, PRODUCT_ID_XBOX360, 1, TRIGGER_THRESHOLD, RELEASE_THRESHOLD); InputMap_bindAxis(self->inputMap, ATOM("up"), VENDOR_ID_XBOX360, PRODUCT_ID_XBOX360, 1, -TRIGGER_THRESHOLD, -RELEASE_THRESHOLD); InputMap_bindButton(self->inputMap, ATOM("jump"), VENDOR_ID_XBOX360, PRODUCT_ID_XBOX360, 16); InputMap_bindButton(self->inputMap, ATOM("action"), VENDOR_ID_XBOX360, PRODUCT_ID_XBOX360, 18); InputMap_bindButton(self->inputMap, ATOM("pause"), VENDOR_ID_XBOX360, PRODUCT_ID_XBOX360, 9); #elif defined(STEM_PLATFORM_win32) || defined(STEM_PLATFORM_win64) InputMap_bindAxis(self->inputMap, ATOM("left"), VENDOR_ID_XBOX360, PRODUCT_ID_XBOX360, 0, -TRIGGER_THRESHOLD, -RELEASE_THRESHOLD); InputMap_bindAxis(self->inputMap, ATOM("right"), VENDOR_ID_XBOX360, PRODUCT_ID_XBOX360, 0, TRIGGER_THRESHOLD, RELEASE_THRESHOLD); InputMap_bindAxis(self->inputMap, ATOM("down"), VENDOR_ID_XBOX360, PRODUCT_ID_XBOX360, 1, -TRIGGER_THRESHOLD, -RELEASE_THRESHOLD); InputMap_bindAxis(self->inputMap, ATOM("up"), VENDOR_ID_XBOX360, PRODUCT_ID_XBOX360, 1, TRIGGER_THRESHOLD, RELEASE_THRESHOLD); InputMap_bindButton(self->inputMap, ATOM("jump"), VENDOR_ID_XBOX360, PRODUCT_ID_XBOX360, 10); InputMap_bindButton(self->inputMap, ATOM("action"), VENDOR_ID_XBOX360, PRODUCT_ID_XBOX360, 12); InputMap_bindButton(self->inputMap, ATOM("pause"), VENDOR_ID_XBOX360, PRODUCT_ID_XBOX360, 4); #elif defined(STEM_PLATFORM_linux32) || defined(STEM_PLATFORM_linux64) InputMap_bindAxis(self->inputMap, ATOM("left"), VENDOR_ID_XBOX360, PRODUCT_ID_XBOX360, 0, -TRIGGER_THRESHOLD, -RELEASE_THRESHOLD); InputMap_bindAxis(self->inputMap, ATOM("right"), VENDOR_ID_XBOX360, PRODUCT_ID_XBOX360, 0, TRIGGER_THRESHOLD, RELEASE_THRESHOLD); InputMap_bindAxis(self->inputMap, ATOM("down"), VENDOR_ID_XBOX360, PRODUCT_ID_XBOX360, 1, TRIGGER_THRESHOLD, RELEASE_THRESHOLD); InputMap_bindAxis(self->inputMap, ATOM("up"), VENDOR_ID_XBOX360, PRODUCT_ID_XBOX360, 1, -TRIGGER_THRESHOLD, -RELEASE_THRESHOLD); InputMap_bindButton(self->inputMap, ATOM("jump"), VENDOR_ID_XBOX360, PRODUCT_ID_XBOX360, 0); InputMap_bindButton(self->inputMap, ATOM("action"), VENDOR_ID_XBOX360, PRODUCT_ID_XBOX360, 2); InputMap_bindButton(self->inputMap, ATOM("pause"), VENDOR_ID_XBOX360, PRODUCT_ID_XBOX360, 7); #endif #if defined(STEM_PLATFORM_macosx) InputMap_bindAxis(self->inputMap, ATOM("left"), VENDOR_ID_DUALSHOCK4, PRODUCT_ID_DUALSHOCK4, 4, -TRIGGER_THRESHOLD, -RELEASE_THRESHOLD); InputMap_bindAxis(self->inputMap, ATOM("right"), VENDOR_ID_DUALSHOCK4, PRODUCT_ID_DUALSHOCK4, 4, TRIGGER_THRESHOLD, RELEASE_THRESHOLD); InputMap_bindAxis(self->inputMap, ATOM("down"), VENDOR_ID_DUALSHOCK4, PRODUCT_ID_DUALSHOCK4, 5, TRIGGER_THRESHOLD, RELEASE_THRESHOLD); InputMap_bindAxis(self->inputMap, ATOM("up"), VENDOR_ID_DUALSHOCK4, PRODUCT_ID_DUALSHOCK4, 5, -TRIGGER_THRESHOLD, -RELEASE_THRESHOLD); InputMap_bindButton(self->inputMap, ATOM("jump"), VENDOR_ID_DUALSHOCK4, PRODUCT_ID_DUALSHOCK4, 1); InputMap_bindButton(self->inputMap, ATOM("action"), VENDOR_ID_DUALSHOCK4, PRODUCT_ID_DUALSHOCK4, 0); InputMap_bindButton(self->inputMap, ATOM("pause"), VENDOR_ID_DUALSHOCK4, PRODUCT_ID_DUALSHOCK4, 9); #elif defined(STEM_PLATFORM_win32) || defined(STEM_PLATFORM_win64) InputMap_bindAxis(self->inputMap, ATOM("left"), VENDOR_ID_DUALSHOCK4, PRODUCT_ID_DUALSHOCK4, 4, -TRIGGER_THRESHOLD, -RELEASE_THRESHOLD); InputMap_bindAxis(self->inputMap, ATOM("right"), VENDOR_ID_DUALSHOCK4, PRODUCT_ID_DUALSHOCK4, 4, TRIGGER_THRESHOLD, RELEASE_THRESHOLD); InputMap_bindAxis(self->inputMap, ATOM("down"), VENDOR_ID_DUALSHOCK4, PRODUCT_ID_DUALSHOCK4, 5, TRIGGER_THRESHOLD, RELEASE_THRESHOLD); InputMap_bindAxis(self->inputMap, ATOM("up"), VENDOR_ID_DUALSHOCK4, PRODUCT_ID_DUALSHOCK4, 5, -TRIGGER_THRESHOLD, -RELEASE_THRESHOLD); InputMap_bindButton(self->inputMap, ATOM("jump"), VENDOR_ID_DUALSHOCK4, PRODUCT_ID_DUALSHOCK4, 1); InputMap_bindButton(self->inputMap, ATOM("action"), VENDOR_ID_DUALSHOCK4, PRODUCT_ID_DUALSHOCK4, 0); InputMap_bindButton(self->inputMap, ATOM("pause"), VENDOR_ID_DUALSHOCK4, PRODUCT_ID_DUALSHOCK4, 9); #elif defined(STEM_PLATFORM_linux32) || defined(STEM_PLATFORM_linux64) InputMap_bindAxis(self->inputMap, ATOM("left"), VENDOR_ID_DUALSHOCK4, PRODUCT_ID_DUALSHOCK4, 6, -TRIGGER_THRESHOLD, -RELEASE_THRESHOLD); InputMap_bindAxis(self->inputMap, ATOM("right"), VENDOR_ID_DUALSHOCK4, PRODUCT_ID_DUALSHOCK4, 6, TRIGGER_THRESHOLD, RELEASE_THRESHOLD); InputMap_bindAxis(self->inputMap, ATOM("down"), VENDOR_ID_DUALSHOCK4, PRODUCT_ID_DUALSHOCK4, 7, TRIGGER_THRESHOLD, RELEASE_THRESHOLD); InputMap_bindAxis(self->inputMap, ATOM("up"), VENDOR_ID_DUALSHOCK4, PRODUCT_ID_DUALSHOCK4, 7, -TRIGGER_THRESHOLD, -RELEASE_THRESHOLD); InputMap_bindButton(self->inputMap, ATOM("jump"), VENDOR_ID_DUALSHOCK4, PRODUCT_ID_DUALSHOCK4, 1); InputMap_bindButton(self->inputMap, ATOM("action"), VENDOR_ID_DUALSHOCK4, PRODUCT_ID_DUALSHOCK4, 0); InputMap_bindButton(self->inputMap, ATOM("pause"), VENDOR_ID_DUALSHOCK4, PRODUCT_ID_DUALSHOCK4, 9); #endif } self->resourceManager = ResourceManager_create(); self->resourceManager->addTypeHandler(self->resourceManager, "png_flip", loadPNGFlip, unloadPNG, self->resourceManager); self->resourceManager->addTypeHandler(self->resourceManager, "texture", loadTexture, unloadTexture, self->resourceManager); self->resourceManager->addTypeHandler(self->resourceManager, "texture_atlas", loadTextureAtlas, unloadTextureAtlas, self->resourceManager); self->resourceManager->addTypeHandler(self->resourceManager, "bitmap_font", loadBitmapFont, unloadBitmapFont, self->resourceManager); self->resourceManager->addTypeHandler(self->resourceManager, "vorbis", loadOggVorbis, unloadOggVorbis, self->resourceManager); self->levelSetCount = BUILT_IN_LEVEL_SET_COUNT; self->levelSets = malloc(sizeof(LevelSetModel *) * LEVEL_SET_COUNT_MAX); self->levelSets[0] = LevelFileIO_createLevelSetFromFile("tutorial.lvl", "Tutorial"); self->levelSets[1] = LevelFileIO_createLevelSetFromFile("easy.lvl", "Easy Levels"); self->levelSets[2] = LevelFileIO_createLevelSetFromFile("intermediate.lvl", "Intermediate Levels"); self->levelSets[3] = LevelFileIO_createLevelSetFromFile("advanced.lvl", "Advanced Levels"); self->importedLevelNames = valueGetAssociativeArray(Preferences_get(self->preferences, "imported_levels")); if (self->importedLevelNames == NULL) { self->importedLevelNames = associativeArrayCreate(); } else { size_t levelIndex; LevelSetModel * levelSet; self->importedLevelNames = associativeArrayCopy(self->importedLevelNames); for (levelIndex = 0; levelIndex < self->importedLevelNames->count; levelIndex++) { if (levelIndex >= LEVEL_SET_COUNT_MAX - BUILT_IN_LEVEL_SET_COUNT || self->importedLevelNames->values[levelIndex].type != DATA_TYPE_STRING || (levelSet = LevelFileIO_createLevelSetFromFile(self->importedLevelNames->values[levelIndex].value.string, self->importedLevelNames->keys[levelIndex])) == NULL) { associativeArrayDelete(self->importedLevelNames, levelIndex); levelIndex--; } else { self->levelSets[levelIndex + BUILT_IN_LEVEL_SET_COUNT] = levelSet; self->levelSetCount++; } } } self->selectedLevelSetIndex = 0; self->startLevelIndex = 0; self->testingLevel = false; self->viewingSettingsFromGameplay = false; self->hasUnsavedLevelChanges = false; return true; } void GameSession_dispose(GameSession * self) { unsigned int levelSetIndex; InputMap_dispose(self->inputMap); Preferences_dispose(self->preferences); ResourceManager_dispose(self->resourceManager); associativeArrayDispose(self->importedLevelNames); for (levelSetIndex = 0; levelSetIndex < self->levelSetCount; levelSetIndex++) { LevelSetModel_dispose(self->levelSets[levelSetIndex]); } free(self->levelSets); call_super(dispose, self); } void GameSession_saveControlBindings(GameSession * self) { DataSerializationContext * serializationContext; serializationContext = DataSerializationContext_create(); InputMap_serialize(self->inputMap, serializationContext); Preferences_set(self->preferences, "input_map", *DataSerializationContext_result(serializationContext)); serializationContext->dispose(serializationContext); GameSession_savePreferences(self); } void GameSession_savePreferences(GameSession * self) { JSONSerializationContext * serializationContext; struct JSONEmissionError error; char filePath[PATH_MAX]; DataValue preferencesData; Preferences_getFilePath(PREFERENCES_FILE_NAME, filePath, PATH_MAX); serializationContext = JSONSerializationContext_create(); preferencesData = valueCreateHashTable(Preferences_export(self->preferences), true, false); DataValue_serialize(&preferencesData, serializationContext); valueDispose(&preferencesData); error.description = NULL; if (!JSONSerializationContext_writeToFile(serializationContext, JSONEmitterFormat_multiLine, filePath, &error)) { fprintf(stderr, "Error: Couldn't save settings: %s (%d)\n", error.description, error.code); } serializationContext->dispose(serializationContext); } static void saveImportedLevelSetList(GameSession * self) { Preferences_set(self->preferences, "imported_levels", valueCreateAssociativeArray(self->importedLevelNames, false, false)); GameSession_savePreferences(self); } void GameSession_addLevelSet(GameSession * self, LevelSetModel * levelSet) { const char * supportPath; char filePath[PATH_MAX]; unsigned int length, extensionIndex; unsigned int fileSuffix = 0; struct stat statBuf; if (self->levelSetCount >= LEVEL_SET_COUNT_MAX) { return; } self->levelSets[self->levelSetCount++] = levelSet; supportPath = Shell_getSupportPath(SUPPORT_PATH_SUBDIRECTORY); length = strlen(levelSet->name); for (extensionIndex = length - 1; extensionIndex > 0; extensionIndex--) { if (levelSet->name[extensionIndex] == '.') { break; } } if (extensionIndex == 0) { extensionIndex = length; } snprintf_safe(filePath, PATH_MAX, "%s/%.*s.lvl", supportPath, extensionIndex, levelSet->name); while (!stat(filePath, &statBuf)) { snprintf_safe(filePath, PATH_MAX, "%s/%.*s-%u.lvl", supportPath, extensionIndex, levelSet->name, ++fileSuffix); } LevelFileIO_writeLevelSetToFile(levelSet, filePath); associativeArrayAppend(self->importedLevelNames, levelSet->name, valueCreateString(filePath, DATA_USE_STRLEN, true, true)); saveImportedLevelSetList(self); } void GameSession_deleteLevelSet(GameSession * self, unsigned int levelSetIndex) { LevelSetModel_dispose(self->levelSets[levelSetIndex]); unlink(valueGetString(associativeArrayGetValueAtIndex(self->importedLevelNames, levelSetIndex - BUILT_IN_LEVEL_SET_COUNT))); associativeArrayDelete(self->importedLevelNames, levelSetIndex - BUILT_IN_LEVEL_SET_COUNT); self->levelSetCount--; for (; levelSetIndex < self->levelSetCount; levelSetIndex++) { self->levelSets[levelSetIndex] = self->levelSets[levelSetIndex + 1]; } if (self->selectedLevelSetIndex >= self->levelSetCount) { self->selectedLevelSetIndex = self->levelSetCount - 1; } saveImportedLevelSetList(self); } void GameSession_replaceLevelSetAtIndex(GameSession * self, unsigned int levelSetIndex, LevelSetModel * levelSet) { LevelSetModel_dispose(self->levelSets[levelSetIndex]); self->levelSets[levelSetIndex] = levelSet; LevelFileIO_writeLevelSetToFile(levelSet, valueGetString(associativeArrayGetValueAtIndex(self->importedLevelNames, levelSetIndex - BUILT_IN_LEVEL_SET_COUNT))); } void GameSession_levelSetNameUpdated(GameSession * self, unsigned int levelIndex) { associativeArrayReplace(self->importedLevelNames, levelIndex - BUILT_IN_LEVEL_SET_COUNT, self->levelSets[levelIndex]->name, valueCopy(associativeArrayGetValueAtIndex(self->importedLevelNames, levelIndex - BUILT_IN_LEVEL_SET_COUNT))); saveImportedLevelSetList(self); }