diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ff5d3e..6c1caa7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -192,6 +192,7 @@ add_executable(breakhack src/util src/event src/player + src/save src/map src/map_lua src/camera diff --git a/src/main.c b/src/main.c index fbed10c..72f6e13 100644 --- a/src/main.c +++ b/src/main.c @@ -58,6 +58,7 @@ #include "sprite_util.h" #include "event.h" #include "config.h" +#include "save.h" #ifdef STEAM_BUILD #include "checksum.h" @@ -420,6 +421,56 @@ goToCharacterMenu(void *unused) gGameState = CHARACTER_MENU; } +static void +choose_music(void) +{ + if (cLevel > (unsigned int) (quickGame ? 11 : 19)) { + mixer_play_music(BOSS_MUSIC0); + } else if (cLevel % (quickGame ? 3 : 5) == 0) { + gui_log("You sense something powerful in the vicinity"); + mixer_play_music(BOSS_MUSIC0); + } else { + mixer_play_music(GAME_MUSIC0 + get_random(2)); + } +} + +static void +continueGame(void *unused) +{ + (void) unused; + const Save *save = save_get(); + quickGame = save->quickGame; + arcadeGame = save->arcadeGame; + + playerClass = save->player_class; + cLevel = save->map_level; + set_random_seed(save->seed); + debug("Loading seed: %d", save->seed); + debug("Loading map level: %d", save->map_level); + + gGameState = PLAYING; + if (gPlayer) + player_destroy(gPlayer); + gPlayer = player_create(playerClass, gCamera); + + // Load player from save + gPlayer->daggers = save->player_daggers; + gPlayer->xp = save->player_xp; + gPlayer->stateData = save->player_state; + gPlayer->stats = save->player_stats; + gPlayer->stat_data = save->player_player_stats; + gPlayer->potion_sips = save->player_potion_sips; + gPlayer->equipment = save->player_equipment; + gPlayer->gold = save->player_gold; + + choose_music(); + resetGame(); + skillbar_reset(gSkillBar); + gui_clear_message_log(); + gui_log("The Dungeon Crawl continues!"); + gui_event_message("Welcome back!"); +} + static void startRegularGame(void *unused) { @@ -459,6 +510,7 @@ static void goToMainMenu(void *unused) { UNUSED(unused); + save_load(); gui_clear_message_log(); gGameState = MENU; menu_destroy(inGameMenu); @@ -474,37 +526,47 @@ static void goToGameSelectMenu(void *unused) { UNUSED(unused); - static TEXT_MENU_ITEM menuItems[] = { - { - "STANDARD GAME", - "Standard 20 level game, recommended for new players", - startRegularGame - }, + int item_count = 3; #ifdef STEAM_BUILD - { - "WEEKLY CHALLENGE", - "Quick game with weekly leaderboards at breakhack.net", - startWeeklyGame - }, + item_count += 1; #endif - { - "QUICK GAME", - "Shorter 12 level game, with more action earlier in the game", - startQuickGame - }, - { - "ARCADE GAME", - "One big level with lots of action", - startArcadeGame - } + if (save_exists()) { + item_count += 1; + } + TEXT_MENU_ITEM *menuItems = ec_malloc(item_count * sizeof(TEXT_MENU_ITEM)); + int i = 0; + if (save_exists()) { + menuItems[i++] = (TEXT_MENU_ITEM) { + "CONTINUE", + "Continue your last session", + continueGame, + }; + } + menuItems[i++] = (TEXT_MENU_ITEM) { + "STANDARD GAME", + "Standard 20 level game, recommended for new players", + startRegularGame + }; +#ifdef STEAM_BUILD + menuItems[i++] = (TEXT_MENU_ITEM) { + "WEEKLY CHALLENGE", + "Quick game with weekly leaderboards at breakhack.net", + startWeeklyGame + }; +#endif + menuItems[i++] = (TEXT_MENU_ITEM) { + "QUICK GAME", + "Shorter 12 level game, with more action earlier in the game", + startQuickGame + }; + menuItems[i++] = (TEXT_MENU_ITEM) { + "ARCADE GAME", + "One big level with lots of action", + startArcadeGame }; -#ifdef STEAM_BUILD - int count = 4; -#else - int count = 3; -#endif - menu_create_text_menu(&gameSelectMenu, &menuItems[0], count, gRenderer); + menu_create_text_menu(&gameSelectMenu, menuItems, item_count, gRenderer); + free(menuItems); gGameState = GAME_SELECT; } @@ -691,6 +753,7 @@ init(void) event_register_listener(on_event_callback); settings_init(); + save_init(); hiscore_init(); initMainMenu(); @@ -874,17 +937,17 @@ check_next_level(void) return; } if (tile->levelExit) { - mixer_play_effect(NEXT_LEVEL); ++cLevel; - if (cLevel > (unsigned int) (quickGame ? 11 : 19)) { - mixer_play_music(BOSS_MUSIC0); - } else if (cLevel % (quickGame ? 3 : 5) == 0) { - gui_log("You sense something powerful in the vicinity"); - mixer_play_music(BOSS_MUSIC0); - } else { - mixer_play_music(GAME_MUSIC0 + get_random(2)); + if (!weeklyGame) { + save_save(get_random_seed(), + cLevel, + quickGame, + arcadeGame, + gPlayer); } + mixer_play_effect(NEXT_LEVEL); + choose_music(); if (!gameCompleted()) { resetGame(); } @@ -1116,6 +1179,7 @@ run_game(void) camera_shake(VECTOR2D_RIGHT, 800); gui_log("The dungeon consumed you"); gui_event_message("You died!"); + save_clear(); end_game_details(); mixer_play_effect(SPLAT); gGameState = GAME_OVER; @@ -1349,6 +1413,7 @@ void close(void) texturecache_close(); settings_close(); hiscore_close(); + save_close(); #ifdef STEAM_BUILD steam_shutdown(); diff --git a/src/menu.h b/src/menu.h index e103473..005c33e 100644 --- a/src/menu.h +++ b/src/menu.h @@ -26,8 +26,8 @@ #include "sprite.h" typedef struct TEXT_MENU_ITEM { - char label[20]; - char description[100]; + char *label; + char *description; void (*callback)(void*); } TEXT_MENU_ITEM; diff --git a/src/save.c b/src/save.c new file mode 100644 index 0000000..b68ed91 --- /dev/null +++ b/src/save.c @@ -0,0 +1,147 @@ +/* + * BreakHack - A dungeone crawler RPG + * Copyright (C) 2018 Linus Probert + * + * 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 3 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, see . + */ +#include +#include +#include +#include "save.h" +#include "sqlite3.h" +#include "db.h" +#include "defines.h" +#include "util.h" + +static sqlite3 *db = NULL; +static bool loaded = false; +static Save save; + +static +DbQuery MIGRATE_COMMAND = { + "CREATE TABLE IF NOT EXISTS saves(" + "major_version INTEGER, " + "minor_version INTEGER, " + "patch_version INTEGER, " + "save BLOB)", + NULL, NULL +}; + +static +DbQuery CLEAR_SAVE = { "DELETE FROM saves", NULL, NULL }; + +static void +create_table(void) +{ + db_execute(db, &MIGRATE_COMMAND); +} + +void +save_load(void) +{ + debug("Loading save"); + const char *query = + "SELECT save FROM saves " + "WHERE major_version = ?" + "AND minor_version = ?" + "AND patch_version = ?" + "LIMIT 1"; + + sqlite3_stmt *stmt = db_prepare(db, query); + sqlite3_bind_int(stmt, 1, MAJOR_VERSION); + sqlite3_bind_int(stmt, 2, MINOR_VERSION); + sqlite3_bind_int(stmt, 3, PATCH_VERSION); + if (SQLITE_ROW == sqlite3_step(stmt)) { + int size = sqlite3_column_bytes(stmt, 0); + debug("Reading save bytes: %d", size); + memcpy(&save, sqlite3_column_blob(stmt, 0), size); + loaded = true; + } else { + loaded = false; + } + sqlite3_finalize(stmt); +} + +void +save_init(void) +{ + if (!db_open(DB_FILE, &db)) { + db_close(&db); + fatal("Exiting"); + } + create_table(); + save_load(); +} + +const Save * +save_get(void) +{ + return &save; +} + +bool +save_exists(void) +{ + return loaded; +} + +void +save_save(unsigned int seed, + unsigned int map_level, + bool quickGame, + bool arcadeGame, + Player *player) +{ + debug("Saving game, Seed: %d, Map level: %d", seed, map_level); + save_clear(); + + save.seed = seed; + save.map_level = map_level; + save.quickGame = quickGame; + save.arcadeGame = arcadeGame; + save.player_stats = player->stats; + save.player_daggers = player->daggers; + save.player_gold = player->gold; + save.player_xp = player->xp; + save.player_potion_sips = player->potion_sips; + save.player_player_stats = player->stat_data; + save.player_state = player->stateData; + save.player_class = player->class; + save.player_equipment = player->equipment; + + const char *query = + "INSERT INTO saves" + "(major_version, minor_version, patch_version, save) " + "VALUES(?, ?, ?, ?)"; + + sqlite3_stmt *stmt = db_prepare(db, query); + sqlite3_bind_int(stmt, 1, MAJOR_VERSION); + sqlite3_bind_int(stmt, 2, MINOR_VERSION); + sqlite3_bind_int(stmt, 3, PATCH_VERSION); + sqlite3_bind_blob(stmt, 4, &save, sizeof(Save), SQLITE_STATIC); + sqlite3_step(stmt); + sqlite3_finalize(stmt); +} + +void +save_clear(void) +{ + db_execute(db, &CLEAR_SAVE); +} + +void +save_close(void) +{ + db_close(&db); +} diff --git a/src/save.h b/src/save.h index 8b34f2a..dd079dc 100644 --- a/src/save.h +++ b/src/save.h @@ -1,16 +1,67 @@ +/* + * BreakHack - A dungeone crawler RPG + * Copyright (C) 2018 Linus Probert + * + * 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 3 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, see . + */ #ifndef SAVE_H_ #define SAVE_H_ +#include #include "player.h" +#include "artifact.h" typedef struct Save { int seed; + bool quickGame; + bool arcadeGame; unsigned int map_level; - unsigned int player_level; unsigned int player_daggers; unsigned int player_xp; + unsigned int player_potion_sips; + unsigned int potion_sips; + Stats player_stats; + PlayerStatData player_player_stats; double player_gold; - class_t class; + PlayerStateData player_state; + class_t player_class; + PlayerEquipment player_equipment; } Save; +void +save_init(void); + +void +save_load(void); + +const Save * +save_get(void); + +bool +save_exists(void); + +void +save_save(unsigned int seed, + unsigned int map_level, + bool quickGame, + bool arcadeGame, + Player *player); + +void +save_clear(void); + +void +save_close(void); + #endif // SAVE_H_ diff --git a/src/texturecache.c b/src/texturecache.c index a1dcf76..7efdc0f 100644 --- a/src/texturecache.c +++ b/src/texturecache.c @@ -51,8 +51,6 @@ texturecache_add(const char *path) texture_load_from_file(tc->texture, path, renderer); ht_set(textures, path, tc); debug("Cached texture: %s", path); - } else { - debug("Retrieved cached texture: %s", path); } return tc->texture;