breakhack/src/main.c

1121 lines
25 KiB
C
Raw Normal View History

2018-02-16 18:11:26 +01:00
/*
* BreakHack - A dungeone crawler RPG
* Copyright (C) 2018 Linus Probert <linus.probert@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
2017-11-30 21:00:47 +01:00
#include <stdio.h>
#include <stdlib.h>
2017-11-30 21:00:47 +01:00
#include <stdbool.h>
2018-02-21 00:29:21 +01:00
#include <SDL.h>
#include <SDL_image.h>
#include <physfs.h>
#include "linkedlist.h"
2017-11-30 21:00:47 +01:00
#include "player.h"
#include "screenresolution.h"
#include "dimension.h"
2017-12-01 16:03:19 +01:00
#include "camera.h"
#include "map.h"
2017-12-02 23:32:40 +01:00
#include "map_lua.h"
2017-12-05 08:30:08 +01:00
#include "timer.h"
#include "roommatrix.h"
2017-12-22 06:27:58 +01:00
#include "gamestate.h"
#include "gui.h"
#include "util.h"
#include "item_builder.h"
2018-01-31 09:15:33 +01:00
#include "pointer.h"
#include "gui_button.h"
2018-02-03 23:39:49 +01:00
#include "particle_engine.h"
#include "menu.h"
2018-02-13 06:44:09 +01:00
#include "keyboard.h"
#include "mixer.h"
#include "random.h"
#include "skillbar.h"
#include "texturecache.h"
#include "update_data.h"
2018-03-17 00:04:26 +01:00
#include "settings.h"
2018-05-15 11:16:56 +02:00
#include "actiontextbuilder.h"
#include "input.h"
2018-08-12 09:13:18 +02:00
#include "screen.h"
#include "hiscore.h"
#include "io_util.h"
#include "tooltip.h"
#ifdef STEAM_BUILD
#include "steam/steamworks_api_wrapper.h"
#endif // STEAM_BUILD
2018-08-21 12:30:44 +02:00
static char *artifacts_tooltip[] = {
"CONGRATULATIONS!",
"",
" You just picked up your first artifact!",
"",
" Your current artifacts and corresponding level are",
" listed next to your skills."
"",
"",
" Artifacts have mystical effects that improve your offensive",
" or defensive advantage in the dungeon. However it is sometimes",
" hard to know what effect an artifact has.",
"",
"",
" Perhaps an experienced dungeoner will know more?",
"",
"",
"Press ESC to close",
NULL
};
static char *skills_tooltip[] = {
"CONGRATULATIONS!",
"",
" You have aquired a new level and a new skill!",
"",
" Skills are listed in the bar below the game screen.",
"",
"",
2018-08-21 16:21:22 +02:00
" SKILL INFO: SHIFT + <NUM>",
" Where <NUM> is the skill number (1-5)",
"",
" DISABLE TOOLTIPS: CTRL + D",
"",
"",
"Press ESC to close",
NULL
};
static char *how_to_play_tooltip[] = {
"HOW TO PLAY",
"",
" NAVIGATION: Use ARROWS or WASD or HJKL to move",
"",
" ATTACK: Walk into a monster to attack it",
"",
" HOLD TURN: Press SPACE",
"",
" THROW DAGGER: Press 4 then chose a direction (nav keys)",
"",
" DRINK HEALTH: Press 5 (if you need health and have potions)",
"",
" TOGGLE MUSIC: CTRL + M",
"",
" TOGGLE SOUND: CTRL + S",
"",
" TOGGLE FULLSCREEN: CTRL + F",
"",
" TOGGLE MENU: ESC",
"",
" Your stats and inventory are listed in the right panel",
"",
"",
" GOOD LUCK!",
" May your death be quick and painless...",
"",
"",
"",
"Press ESC to close",
NULL
};
2017-11-30 21:00:47 +01:00
2018-02-19 15:09:04 +01:00
typedef enum Turn_t {
PLAYER,
MONSTER
} Turn;
static SDL_Window *gWindow = NULL;
static SDL_Renderer *gRenderer = NULL;
static Player *gPlayer = NULL;
static Map *gMap = NULL;
static RoomMatrix *gRoomMatrix = NULL;
static Gui *gGui = NULL;
static SkillBar *gSkillBar = NULL;
static Menu *mainMenu = NULL;
static Menu *inGameMenu = NULL;
static Timer *menuTimer = NULL;
static Camera *gCamera = NULL;
static Screen *creditsScreen = NULL;
static Screen *scoreScreen = NULL;
static Sprite *new_skill_tooltip = NULL;
static Sprite *howto_tooltip = NULL;
2018-08-21 12:30:44 +02:00
static Sprite *new_artifact_tooltip = NULL;
static unsigned int cLevel = 1;
static float deltaTime = 1.0;
static double renderScale = 1.0;
static Turn currentTurn = PLAYER;
static GameState gGameState;
static SDL_Rect mainViewport;
2018-01-23 12:14:44 +01:00
static SDL_Rect gameViewport;
2018-02-22 15:42:43 +01:00
static SDL_Rect skillBarViewport;
2018-01-23 12:14:44 +01:00
static SDL_Rect bottomGuiViewport;
2018-08-21 15:44:12 +02:00
static SDL_Rect statsGuiViewport;
static SDL_Rect minimapViewport;
2018-02-09 13:27:25 +01:00
static SDL_Rect menuViewport;
static Input input;
2017-11-30 21:00:47 +01:00
#ifdef DEBUG
static Sprite *fpsSprite = NULL;
static Pointer *gPointer = NULL;
#endif // DEBUG
2018-05-15 14:21:54 +02:00
static SDL_Color C_MENU_DEFAULT = { 255, 255, 0, 255 };
2018-05-22 15:48:09 +02:00
static SDL_Color C_MENU_OUTLINE_DEFAULT = { 0, 0, 0, 255 };
2018-05-15 14:21:54 +02:00
static SDL_Color C_MENU_HOVER = { 255, 0, 0, 255 };
2018-02-13 06:44:09 +01:00
struct MENU_ITEM {
char label[20];
void (*callback)(void*);
};
static void resetGame(void);
2018-02-13 06:44:09 +01:00
static void initMainMenu(void);
static bool is_player_dead(void);
2017-11-30 21:00:47 +01:00
static
bool initSDL(void)
2017-11-30 21:00:47 +01:00
{
int imgFlags = IMG_INIT_PNG;
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0)
{
error("Could not initiate SDL2: %s", SDL_GetError());
return false;
}
2017-12-03 11:09:57 +01:00
Dimension dim = getScreenDimensions();
2017-11-30 21:00:47 +01:00
2017-12-22 06:27:58 +01:00
if (dim.height > 1080) {
info("Hi resolution screen detected (%u x %u)", dim.width, dim.height);
2018-01-24 08:52:50 +01:00
renderScale = ((double) dim.height)/1080;
info("Scaling by %f", renderScale);
2017-11-30 21:00:47 +01:00
}
2017-12-22 06:27:58 +01:00
if ( (IMG_Init(imgFlags) & imgFlags) == 0 ) {
error("Unable to initiate img loading: %s",
2017-12-22 06:27:58 +01:00
IMG_GetError());
return false;
}
if ( TTF_Init() == -1 ) {
error("Unable to initiate ttf library: %s",
2017-12-22 06:27:58 +01:00
TTF_GetError());
return false;
}
mixer_init();
char title_buffer[100];
m_sprintf(title_buffer, 100, "%s %d.%d.%d %s", GAME_TITLE, MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION, RELEASE_TYPE);
gWindow = SDL_CreateWindow(title_buffer,
2017-11-30 21:00:47 +01:00
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
2018-01-23 12:14:44 +01:00
(int)(SCREEN_WIDTH * renderScale),
(int)(SCREEN_HEIGHT * renderScale),
2017-11-30 21:00:47 +01:00
SDL_WINDOW_SHOWN);
if (gWindow == NULL)
{
error("Unable to create window: %s", SDL_GetError());
2017-11-30 21:00:47 +01:00
return false;
}
// Set the window icon
SDL_SetWindowIcon(gWindow, IMG_Load_RW(io_load_rwops("Extras/icon.png"), true));
gRenderer = SDL_CreateRenderer(gWindow, -1,
SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
2017-11-30 21:00:47 +01:00
if (gRenderer == NULL)
{
error("Unable to create renderer: %s", SDL_GetError());
2017-11-30 21:00:47 +01:00
return false;
}
2017-12-10 19:51:24 +01:00
if (SDL_SetRenderDrawBlendMode(gRenderer, SDL_BLENDMODE_BLEND) < 0) {
error("Unable to set blend mode: %s", SDL_GetError());
2017-12-10 19:51:24 +01:00
return false;
}
2017-11-30 21:00:47 +01:00
if (SDL_RenderSetLogicalSize(gRenderer, SCREEN_WIDTH, SCREEN_HEIGHT) < 0)
{
error("Unable to initiate scaling: %s",
2017-12-15 15:03:29 +01:00
SDL_GetError());
2017-11-30 21:00:47 +01:00
return false;
}
return true;
}
2018-01-23 12:14:44 +01:00
static void
initViewports(Uint32 offset)
2018-01-23 12:14:44 +01:00
{
mainViewport = (SDL_Rect) { offset, 0,
SCREEN_WIDTH, SCREEN_HEIGHT
};
gameViewport = (SDL_Rect) { offset, 0,
2018-01-23 12:14:44 +01:00
GAME_VIEW_WIDTH, GAME_VIEW_HEIGHT };
skillBarViewport = (SDL_Rect) { offset, GAME_VIEW_HEIGHT,
2018-02-22 15:42:43 +01:00
SKILL_BAR_WIDTH, SKILL_BAR_HEIGHT };
bottomGuiViewport = (SDL_Rect) { offset, GAME_VIEW_HEIGHT + SKILL_BAR_HEIGHT,
2018-01-23 12:14:44 +01:00
BOTTOM_GUI_WIDTH, BOTTOM_GUI_WIDTH };
statsGuiViewport = (SDL_Rect) { offset + GAME_VIEW_WIDTH, 0,
2018-08-21 15:44:12 +02:00
RIGHT_GUI_WIDTH, STATS_GUI_HEIGHT };
minimapViewport = (SDL_Rect) { offset + GAME_VIEW_WIDTH, STATS_GUI_HEIGHT,
2018-08-21 15:44:12 +02:00
RIGHT_GUI_WIDTH, MINIMAP_GUI_HEIGHT };
2018-02-09 13:27:25 +01:00
menuViewport = (SDL_Rect) {
offset + ((SCREEN_WIDTH - GAME_VIEW_WIDTH) >> 1),
(SCREEN_HEIGHT - GAME_VIEW_HEIGHT) >> 1,
2018-02-09 13:27:25 +01:00
GAME_VIEW_WIDTH,
GAME_VIEW_HEIGHT
};
2018-01-23 12:14:44 +01:00
}
static bool
initGame(void)
2017-11-30 21:00:47 +01:00
{
initViewports(0);
input_init(&input);
texturecache_init(gRenderer);
gCamera = camera_create(gRenderer);
gRoomMatrix = roommatrix_create();
gGui = gui_create(gCamera);
2018-08-21 12:30:44 +02:00
gSkillBar = skillbar_create(gCamera);
item_builder_init(gRenderer);
#ifdef DEBUG
gPointer = pointer_create(gRenderer);
#endif // DEBUG
particle_engine_init();
2018-02-09 13:27:25 +01:00
menuTimer = timer_create();
2018-05-15 11:16:56 +02:00
actiontextbuilder_init(gRenderer);
2018-01-31 09:15:33 +01:00
#ifdef DEBUG
fpsSprite = sprite_create();
sprite_load_text_texture(fpsSprite, "GUI/SDS_8x8.ttf", 0, 14, 1);
fpsSprite->pos = POS(16, 16);
fpsSprite->fixed = true;
#endif // DEBUG
2018-01-31 09:15:33 +01:00
return true;
2017-11-30 21:00:47 +01:00
}
static void
startGame(void *unused)
{
UNUSED(unused);
cLevel = 1;
gGameState = PLAYING;
if (gPlayer)
player_destroy(gPlayer);
gPlayer = player_create(WARRIOR, gCamera);
mixer_play_music(GAME_MUSIC0 + get_random(2));
resetGame();
skillbar_reset(gSkillBar);
gui_clear_message_log();
gui_log("The Dungeon Crawl begins!");
gui_event_message("Welcome to the dungeon!");
Settings *settings = settings_get();
if (!settings->howto_tooltip_shown)
gGui->activeTooltip = howto_tooltip;
settings->howto_tooltip_shown = true;
}
static void
exitGame(void *unused)
{
UNUSED(unused);
gGameState = QUIT;
}
static void
2018-02-13 06:44:09 +01:00
toggleInGameMenu(void *unused)
{
2018-02-13 06:44:09 +01:00
UNUSED(unused);
if (gGameState == PLAYING ||
gGameState == GAME_OVER ||
gGameState == COMPLETED)
2018-02-13 06:44:09 +01:00
gGameState = IN_GAME_MENU;
else if (is_player_dead())
gGameState = GAME_OVER;
else if (cLevel >= 20)
gGameState = COMPLETED;
2018-02-13 06:44:09 +01:00
else
gGameState = PLAYING;
}
2018-02-13 06:44:09 +01:00
static void
goToMainMenu(void *unused)
{
UNUSED(unused);
gui_clear_message_log();
2018-02-13 06:44:09 +01:00
gGameState = MENU;
menu_destroy(inGameMenu);
inGameMenu = NULL;
initMainMenu();
Position p = { 0, 0 };
gPlayer->sprite->pos = (Position) { 32, 32 };
map_set_current_room(gMap, &p);
camera_follow_position(gCamera, &p);
2018-02-13 06:44:09 +01:00
}
2018-02-09 13:27:25 +01:00
2018-02-13 06:44:09 +01:00
static void
createMenu(Menu **menu, struct MENU_ITEM menu_items[], unsigned int size)
{
if (*menu == NULL)
*menu = menu_create();
2018-02-09 13:27:25 +01:00
2018-02-13 06:44:09 +01:00
for (unsigned int i = 0; i < size; ++i) {
2018-02-22 09:44:27 +01:00
unsigned int hcenter;
2018-02-09 13:27:25 +01:00
Sprite *s1 = sprite_create();
2018-05-15 14:12:38 +02:00
sprite_load_text_texture(s1, "GUI/SDS_8x8.ttf", 0, 25, 2);
texture_load_from_text(s1->textures[0], menu_items[i].label,
2018-05-15 14:12:38 +02:00
C_MENU_DEFAULT, C_MENU_OUTLINE_DEFAULT, gRenderer);
2018-02-09 13:27:25 +01:00
hcenter = (SCREEN_WIDTH/2) - (s1->textures[0]->dim.width/2);
2018-02-22 09:44:27 +01:00
s1->pos = (Position) { (int) hcenter, (int) 200 + (i*50) };
s1->dim = s1->textures[0]->dim;
s1->fixed = true;
Sprite *s2 = sprite_create();
2018-05-15 14:12:38 +02:00
sprite_load_text_texture(s2, "GUI/SDS_8x8.ttf", 0, 25, 2);
texture_load_from_text(s2->textures[0], menu_items[i].label,
2018-05-15 14:12:38 +02:00
C_MENU_HOVER, C_MENU_OUTLINE_DEFAULT, gRenderer);
2018-02-09 13:27:25 +01:00
2018-02-22 09:44:27 +01:00
s2->pos = (Position) { (int) hcenter, (int) 200 + (i*50) };
s2->dim = s2->textures[0]->dim;
s2->fixed = true;
2018-02-13 06:44:09 +01:00
menu_item_add(*menu, s1, s2, menu_items[i].callback);
}
}
static void
showHowToTooltip(void *unused)
{
UNUSED(unused);
toggleInGameMenu(NULL);
gGui->activeTooltip = howto_tooltip;
}
2018-02-13 06:44:09 +01:00
static void
initInGameMenu(void)
{
struct MENU_ITEM menu_items[] = {
{ "RESUME", toggleInGameMenu },
{ "HOW TO PLAY", showHowToTooltip },
2018-02-13 06:44:09 +01:00
{ "MAIN MENU", goToMainMenu },
{ "QUIT", exitGame },
};
createMenu(&inGameMenu, menu_items, 4);
2018-02-13 06:44:09 +01:00
}
static void
createInGameGameOverMenu(void)
{
struct MENU_ITEM menu_items[] = {
{ "NEW GAME", startGame },
{ "MAIN MENU", goToMainMenu },
{ "QUIT", exitGame },
};
if (inGameMenu) {
menu_destroy(inGameMenu);
inGameMenu = NULL;
}
createMenu(&inGameMenu, menu_items, 3);
}
2018-08-12 09:13:18 +02:00
static void
viewCredits(void *unused)
{
UNUSED(unused);
gGameState = CREDITS;
}
2018-08-12 19:43:33 +02:00
static void
viewScoreScreen(void *unused)
{
UNUSED(unused);
gGameState = SCORE_SCREEN;
}
2018-02-13 06:44:09 +01:00
static void
initMainMenu(void)
{
struct MENU_ITEM menu_items[] = {
{ "PLAY", startGame },
2018-08-12 19:43:33 +02:00
{ "SCORES", viewScoreScreen },
2018-08-12 09:13:18 +02:00
{ "CREDITS", viewCredits },
2018-02-13 06:44:09 +01:00
{ "QUIT", exitGame },
};
if (gMap)
map_destroy(gMap);
gMap = map_lua_generator_single_room__run(cLevel, gRenderer);
2018-08-12 19:43:33 +02:00
createMenu(&mainMenu, menu_items, 4);
2018-02-15 14:00:59 +01:00
mixer_play_music(MENU_MUSIC);
2018-08-12 09:13:18 +02:00
creditsScreen = screen_create_credits(gRenderer);
2018-08-12 19:43:33 +02:00
scoreScreen = screen_create_hiscore(gRenderer);
2018-02-13 06:44:09 +01:00
}
static void
repopulate_roommatrix(void)
{
roommatrix_populate_from_map(gRoomMatrix, gMap);
roommatrix_add_lightsource(gRoomMatrix,
&gPlayer->sprite->pos);
roommatrix_build_lightmap(gRoomMatrix);
}
static void
resetGame(void)
{
2018-02-09 10:18:22 +01:00
SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT);
2018-08-12 09:13:18 +02:00
if (mainMenu)
2018-02-09 13:55:57 +01:00
menu_destroy(mainMenu);
2018-08-12 09:13:18 +02:00
mainMenu = NULL;
if (creditsScreen)
screen_destroy(creditsScreen);
creditsScreen = NULL;
2018-02-09 13:55:57 +01:00
2018-08-12 19:43:33 +02:00
if (scoreScreen)
screen_destroy(scoreScreen);
scoreScreen = NULL;
if (inGameMenu)
menu_destroy(inGameMenu);
inGameMenu = NULL;
initInGameMenu();
2018-02-13 06:44:09 +01:00
if (gMap)
map_destroy(gMap);
particle_engine_clear();
info("Building new map");
2018-02-09 09:36:24 +01:00
gMap = map_lua_generator_run(cLevel, gRenderer);
gPlayer->sprite->pos = (Position) {
TILE_DIMENSION, TILE_DIMENSION };
2018-02-09 09:36:24 +01:00
map_set_current_room(gMap, &gPlayer->sprite->pos);
camera_follow_position(gCamera, &gPlayer->sprite->pos);
repopulate_roommatrix();
}
static bool
init(void)
2017-11-30 21:00:47 +01:00
{
if (!initSDL()) {
return false;
} else if (!initGame()) {
return false;
}
#ifdef STEAM_BUILD
steam_init();
#endif // STEAM_BUILD
2018-03-17 00:04:26 +01:00
settings_init();
hiscore_init();
initMainMenu();
2017-12-17 13:43:41 +01:00
howto_tooltip = tooltip_create(how_to_play_tooltip, gCamera);
new_skill_tooltip = tooltip_create(skills_tooltip, gCamera);
2018-08-21 12:30:44 +02:00
new_artifact_tooltip = tooltip_create(artifacts_tooltip, gCamera);
gCamera->pos = (Position) { 0, 0 };
gGameState = MENU;
2017-12-22 06:27:58 +01:00
return true;
2017-11-30 21:00:47 +01:00
}
static void
toggle_fullscreen(void)
{
bool isFullscreen = SDL_GetWindowFlags(gWindow) & SDL_WINDOW_FULLSCREEN_DESKTOP;
Settings *settings = settings_get();
if (isFullscreen) {
initViewports(0);
SDL_SetWindowFullscreen(gWindow, 0);
settings->fullscreen_enabled = false;
}
else {
int w, h, lw, lh;
SDL_RenderGetLogicalSize(gRenderer, &lw, &lh);
SDL_GetWindowSize(gWindow, &w, &h);
double lratio = (double) w / (double) lw;
SDL_SetWindowFullscreen(gWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);
SDL_DisplayMode dMode;
SDL_GetWindowDisplayMode(gWindow, &dMode);
double ratio = (double) (dMode.w) / w;
double offset = ((dMode.w - w) / 2);
initViewports((Uint32)(offset/(ratio*lratio)));
settings->fullscreen_enabled = true;
}
}
2018-08-12 09:13:18 +02:00
static void
handle_main_input(void)
{
if (gGameState == PLAYING
2018-03-17 09:03:29 +01:00
|| gGameState == IN_GAME_MENU
|| gGameState == GAME_OVER
|| gGameState == COMPLETED)
{
if (!gGui->activeTooltip && input_key_is_pressed(&input, KEY_ESC))
toggleInGameMenu(NULL);
2018-03-17 09:03:29 +01:00
}
2018-02-20 10:45:54 +01:00
2018-08-12 09:13:18 +02:00
if (gGameState == CREDITS && input_key_is_pressed(&input, KEY_ESC))
gGameState = MENU;
2018-08-12 19:43:33 +02:00
else if (gGameState == SCORE_SCREEN && input_key_is_pressed(&input, KEY_ESC))
gGameState = MENU;
2018-08-12 09:13:18 +02:00
else if (gGameState == MENU && input_key_is_pressed(&input, KEY_ESC))
gGameState = QUIT;
else if (gGui->activeTooltip && input_key_is_pressed(&input, KEY_ESC))
gGui->activeTooltip = NULL;
2018-08-12 09:13:18 +02:00
if (input_modkey_is_pressed(&input, KEY_CTRL_M)) {
2018-03-17 09:03:29 +01:00
if (mixer_toggle_music(&gGameState))
gui_log("Music enabled");
else
gui_log("Music disabled");
}
2018-02-20 10:45:54 +01:00
if (input_modkey_is_pressed(&input, KEY_CTRL_S)) {
2018-03-17 09:03:29 +01:00
if (mixer_toggle_sound())
gui_log("Sound enabled");
else
gui_log("Sound disabled");
}
if (input_modkey_is_pressed(&input, KEY_CTRL_D)) {
Settings *s = settings_get();
s->tooltips_enabled = !s->tooltips_enabled;
if (s->tooltips_enabled)
gui_log("Tooltips enabled");
else
gui_log("Tooltips disabled");
}
if (input_modkey_is_pressed(&input, KEY_CTRL_F)) {
toggle_fullscreen();
}
}
static bool
handle_events(void)
2017-12-03 11:09:57 +01:00
{
static SDL_Event event;
bool quit = false;
int handleCount = 0;
2017-12-03 11:09:57 +01:00
input_reset(&input);
2017-12-03 11:09:57 +01:00
while (SDL_PollEvent(&event) != 0) {
if (event.type == SDL_QUIT) {
quit = true;
continue;
}
input_handle_event(&input, &event);
handleCount++;
if (handleCount >= 20) {
debug("Flushing event queue");
SDL_PumpEvents();
SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT);
break;
}
2017-12-03 11:09:57 +01:00
}
2017-12-03 11:09:57 +01:00
return quit;
}
static bool
is_player_dead(void)
{
if (gPlayer->stats.hp <= 0) {
return true;
}
return false;
}
static void
end_game_details(void)
{
gui_log("You earned %.2f gold", gPlayer->gold);
gui_event_message("You earned %.2f gold", gPlayer->gold);
2018-08-29 22:13:22 +02:00
if (hiscore_get_top_gold() < gPlayer->gold) {
gui_event_message("NEW HIGHSCORE");
gui_log("NEW HIGHSCORE");
}
}
2017-12-22 06:27:58 +01:00
static void
check_next_level(void)
{
if (cLevel >= 20) {
return;
}
2017-12-22 06:27:58 +01:00
Room *room = gMap->rooms[gMap->currentRoom.x][gMap->currentRoom.y];
Position pos = position_to_matrix_coords(&gPlayer->sprite->pos);
MapTile *tile = room->tiles[pos.x][pos.y];
if (!tile) {
error("Looks like we are out of place");
2017-12-22 06:27:58 +01:00
return;
}
if (tile->levelExit) {
2018-02-15 14:00:59 +01:00
mixer_play_effect(NEXT_LEVEL);
2018-02-09 09:36:24 +01:00
++cLevel;
if (cLevel > 19) {
mixer_play_music(BOSS_MUSIC0);
} else if (cLevel % 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 (cLevel < 20) {
resetGame();
}
2017-12-22 06:27:58 +01:00
}
}
static void
populateUpdateData(UpdateData *data, float deltatime)
{
data->player = gPlayer;
data->map = gMap;
data->matrix = gRoomMatrix;
data->input = &input;
data->gui = gGui;
data->deltatime = deltatime;
2018-08-21 12:30:44 +02:00
data->cam = gCamera;
}
static void
run_game_update(void)
{
static UpdateData updateData;
static unsigned int playerLevel = 1;
2018-08-21 12:30:44 +02:00
static bool artifactTooltipShown = false;
if (gGameState == IN_GAME_MENU)
menu_update(inGameMenu, &input);
populateUpdateData(&updateData, deltaTime);
bool skillActivated = false;
if (playerLevel != gPlayer->stats.lvl) {
playerLevel = gPlayer->stats.lvl;
skillActivated = skillbar_check_skill_activation(gSkillBar,
gPlayer);
}
Settings *settings = settings_get();
if (skillActivated && settings->tooltips_enabled && playerLevel < 5) {
gGui->activeTooltip = new_skill_tooltip;
}
if (!artifactTooltipShown && gPlayer->equipment.hasArtifacts) {
2018-08-21 12:30:44 +02:00
artifactTooltipShown = true;
if (settings->tooltips_enabled)
gGui->activeTooltip = new_artifact_tooltip;
2018-08-21 12:30:44 +02:00
}
if (gGameState == PLAYING && currentTurn == PLAYER)
player_update(&updateData);
2018-02-01 09:04:19 +01:00
gui_update_player_stats(gGui, gPlayer, gMap, gRenderer);
camera_update(gCamera, updateData.deltatime);
2018-02-03 23:39:49 +01:00
particle_engine_update(deltaTime);
roommatrix_update(&updateData);
2018-05-15 11:16:56 +02:00
actiontextbuilder_update(&updateData);
skillbar_update(gSkillBar, &updateData);
camera_follow_position(gCamera, &gPlayer->sprite->pos);
map_set_current_room(gMap, &gPlayer->sprite->pos);
map_update(&updateData);
bool turnSwitch = false;
2018-02-19 15:09:04 +01:00
if (currentTurn == PLAYER) {
if (player_turn_over(gPlayer)) {
2018-02-19 15:09:04 +01:00
currentTurn = MONSTER;
player_reset_steps(gPlayer);
2018-08-13 13:11:32 +02:00
map_on_new_turn(gMap);
turnSwitch = true;
2018-02-19 15:09:04 +01:00
}
} else if (currentTurn == MONSTER) {
if (map_move_monsters(gMap, gRoomMatrix)) {
2018-02-19 15:09:04 +01:00
currentTurn = PLAYER;
turnSwitch = true;
}
}
map_clear_expired_entities(gMap, gPlayer);
if (turnSwitch)
repopulate_roommatrix();
}
static void
render_gui(void)
{
SDL_RenderSetViewport(gRenderer, &statsGuiViewport);
gui_render_panel(gGui, gCamera);
SDL_RenderSetViewport(gRenderer, &minimapViewport);
gui_render_minimap(gGui, gMap, gCamera);
SDL_RenderSetViewport(gRenderer, &skillBarViewport);
skillbar_render(gSkillBar, gPlayer, gCamera);
SDL_RenderSetViewport(gRenderer, &bottomGuiViewport);
gui_render_log(gGui, gCamera);
SDL_RenderSetViewport(gRenderer, &mainViewport);
}
static void
render_game_completed(void)
{
SDL_RenderSetViewport(gRenderer, &gameViewport);
if (!is_player_dead()) {
player_render(gPlayer, gCamera);
player_render_toplayer(gPlayer, gCamera);
}
actiontextbuilder_render(gCamera);
gui_render_event_message(gGui, gCamera);
if (gGameState == IN_GAME_MENU) {
SDL_Rect dimmer = { 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT };
SDL_SetRenderDrawColor(gRenderer, 0, 0, 0, 150);
SDL_RenderFillRect(gRenderer, &dimmer);
menu_render(inGameMenu, gCamera);
}
#ifdef DEBUG
sprite_render(fpsSprite, gCamera);
pointer_render(gPointer, gCamera);
#endif // DEBUG
}
static void
render_game(void)
{
2018-01-23 12:14:44 +01:00
SDL_RenderSetViewport(gRenderer, &gameViewport);
map_render(gMap, gCamera);
particle_engine_render_game(gCamera);
map_render_mid_layer(gMap, gCamera);
if (!is_player_dead()) {
player_render(gPlayer, gCamera);
player_render_toplayer(gPlayer, gCamera);
}
map_render_top_layer(gMap, gRoomMatrix, gCamera);
if (gPlayer->class == MAGE || gPlayer->class == PALADIN)
roommatrix_render_mouse_square(gRoomMatrix, gCamera);
roommatrix_render_lightmap(gRoomMatrix, gCamera);
2018-05-15 11:19:28 +02:00
actiontextbuilder_render(gCamera);
gui_render_event_message(gGui, gCamera);
}
2018-01-23 12:14:44 +01:00
static void
run_game_render(void)
{
SDL_SetRenderDrawColor(gRenderer, 0, 0, 0, 0);
SDL_RenderClear(gRenderer);
2018-02-22 15:42:43 +01:00
render_game();
render_gui();
SDL_RenderSetViewport(gRenderer, &mainViewport);
particle_engine_render_global(gCamera);
gui_render_tooltip(gGui, gCamera);
2018-02-13 06:44:09 +01:00
if (gGameState == IN_GAME_MENU) {
SDL_Rect dimmer = { 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT };
SDL_SetRenderDrawColor(gRenderer, 0, 0, 0, 150);
SDL_RenderFillRect(gRenderer, &dimmer);
menu_render(inGameMenu, gCamera);
2018-02-13 06:44:09 +01:00
}
#ifdef DEBUG
sprite_render(fpsSprite, gCamera);
pointer_render(gPointer, gCamera);
#endif // DEBUG
2018-01-31 09:15:33 +01:00
SDL_RenderPresent(gRenderer);
}
static void
run_game(void)
{
run_game_update();
if (cLevel >= 20) {
SDL_SetRenderDrawColor(gRenderer, 0, 0, 0, 255);
SDL_RenderClear(gRenderer);
render_game_completed();
render_gui();
SDL_RenderPresent(gRenderer);
} else {
run_game_render();
}
if (gGameState == PLAYING && is_player_dead()) {
camera_shake(VECTOR2D_RIGHT, 800);
gui_log("The dungeon consumed you");
gui_event_message("You died!");
end_game_details();
2018-02-15 14:00:59 +01:00
mixer_play_effect(SPLAT);
gGameState = GAME_OVER;
createInGameGameOverMenu();
hiscore_register(gPlayer, cLevel);
2018-08-29 22:13:22 +02:00
#ifdef STEAM_BUILD
uint8_t details[4] = { (uint8_t) gPlayer->stats.lvl, (uint8_t) cLevel, 0, 0 };
steam_register_score((int) gPlayer->gold, (int32_t*) &details, 1);
steam_register_kills((int) gPlayer->stat_data.kills, (int32_t*) &details, 1);
2018-08-29 22:13:22 +02:00
#endif // STEAM_BUILD
2018-08-12 19:43:33 +02:00
2018-05-21 21:03:59 +02:00
} else {
check_next_level();
}
if (gGameState == PLAYING && cLevel >= 20) {
gGameState = COMPLETED;
createInGameGameOverMenu();
gui_event_message("Your break is over!");
gui_log("Your break is over!");
gui_event_message("Well done!");
end_game_details();
2018-08-29 14:26:07 +02:00
#ifdef STEAM_BUILD
steam_set_achievement(BACK_TO_WORK);
#endif // STEAM_BUILD
}
}
static void
run_menu(void)
{
2018-02-09 13:27:25 +01:00
if (!timer_started(menuTimer))
timer_start(menuTimer);
roommatrix_populate_from_map(gRoomMatrix, gMap);
roommatrix_build_lightmap(gRoomMatrix);
if (timer_get_ticks(menuTimer) > 1000) {
timer_stop(menuTimer);
timer_start(menuTimer);
map_move_monsters(gMap, gRoomMatrix);
}
menu_update(mainMenu, &input);
2018-08-12 19:43:33 +02:00
if (gGameState != MENU && gGameState != CREDITS && gGameState != SCORE_SCREEN)
return;
2018-02-09 09:36:24 +01:00
SDL_SetRenderDrawColor(gRenderer, 0, 0, 0, 0);
SDL_RenderClear(gRenderer);
2018-02-09 13:27:25 +01:00
SDL_RenderSetViewport(gRenderer, &menuViewport);
map_render(gMap, gCamera);
map_render_mid_layer(gMap, gCamera);
map_render_top_layer(gMap, gRoomMatrix, gCamera);
roommatrix_render_lightmap(gRoomMatrix, gCamera);
2018-02-09 13:27:25 +01:00
SDL_RenderSetViewport(gRenderer, &mainViewport);
2018-08-12 09:13:18 +02:00
if (gGameState == MENU)
menu_render(mainMenu, gCamera);
else if (gGameState == CREDITS)
screen_render(creditsScreen, gCamera);
2018-08-12 19:43:33 +02:00
else if (gGameState == SCORE_SCREEN)
screen_render(scoreScreen, gCamera);
2018-08-12 09:13:18 +02:00
#ifdef DEBUG
sprite_render(fpsSprite, gCamera);
pointer_render(gPointer, gCamera);
#endif // DEBUG
2018-02-09 13:27:25 +01:00
SDL_RenderPresent(gRenderer);
}
static void
run(void)
2017-11-30 21:00:47 +01:00
{
2018-02-03 23:39:49 +01:00
static int oldTime = 0;
static int currentTime = 0;
2017-11-30 21:00:47 +01:00
bool quit = false;
#ifdef DEBUG
Uint32 frame = 0;
Timer *fpsTime = timer_create();
Timer *updateTimer = timer_create();
timer_start(fpsTime);
timer_start(updateTimer);
#endif // DEBUG
Timer *fpsTimer = timer_create();
2017-11-30 21:00:47 +01:00
while (!quit)
{
2017-12-05 08:30:08 +01:00
timer_start(fpsTimer);
2017-11-30 21:00:47 +01:00
2017-12-03 11:09:57 +01:00
quit = handle_events();
handle_main_input();
#ifdef DEBUG
pointer_handle_input(gPointer, &input);
#endif // DEBUG
2017-11-30 21:00:47 +01:00
#ifdef STEAM_BUILD
steam_run_callbacks();
#endif // STEAM_BUILD
switch (gGameState) {
case PLAYING:
2018-02-13 06:44:09 +01:00
case IN_GAME_MENU:
case GAME_OVER:
case COMPLETED:
run_game();
break;
case MENU:
2018-08-12 09:13:18 +02:00
case CREDITS:
2018-08-12 19:43:33 +02:00
case SCORE_SCREEN:
run_menu();
break;
case QUIT:
quit = true;
break;
default:
break;
}
2017-12-22 06:27:58 +01:00
2018-02-22 09:44:27 +01:00
unsigned int ticks = timer_get_ticks(fpsTimer);
2017-11-30 21:00:47 +01:00
if (ticks < 1000/60)
SDL_Delay((1000/60) - ticks);
2017-12-05 08:30:08 +01:00
timer_stop(fpsTimer);
2018-02-03 23:39:49 +01:00
if (currentTime == 0)
currentTime = SDL_GetTicks();
else {
oldTime = currentTime;
currentTime = SDL_GetTicks();
2018-02-22 09:44:27 +01:00
deltaTime = (float) ((currentTime - oldTime) / 1000.0);
2018-02-03 23:39:49 +01:00
}
#ifdef DEBUG
frame++;
if (timer_get_ticks(updateTimer) > 1000) {
char buffer[20];
m_sprintf(buffer, 20, "FPS: %u", frame / (timer_get_ticks(fpsTime) / 1000));
texture_load_from_text(fpsSprite->textures[0], buffer, C_RED, C_WHITE, gRenderer);
fpsSprite->dim = fpsSprite->textures[0]->dim;
timer_start(updateTimer);
}
#endif // DEBUG
2017-11-30 21:00:47 +01:00
}
2017-12-05 08:30:08 +01:00
timer_destroy(fpsTimer);
#ifdef DEBUG
timer_destroy(fpsTime);
timer_destroy(updateTimer);
#endif // DEBUG
2017-11-30 21:00:47 +01:00
}
static
void close(void)
2017-11-30 21:00:47 +01:00
{
2018-02-09 09:36:24 +01:00
if (gPlayer)
player_destroy(gPlayer);
if (gMap)
map_destroy(gMap);
if (mainMenu)
menu_destroy(mainMenu);
2018-08-12 09:13:18 +02:00
if (creditsScreen)
screen_destroy(creditsScreen);
2018-08-12 19:43:33 +02:00
if (scoreScreen)
screen_destroy(scoreScreen);
2018-02-13 06:44:09 +01:00
if (inGameMenu)
menu_destroy(inGameMenu);
2018-02-09 09:36:24 +01:00
sprite_destroy(howto_tooltip);
sprite_destroy(new_skill_tooltip);
camera_destroy(gCamera);
roommatrix_destroy(gRoomMatrix);
gui_destroy(gGui);
skillbar_destroy(gSkillBar);
#ifdef DEBUG
pointer_destroy(gPointer);
sprite_destroy(fpsSprite);
#endif // DEBUG
2018-05-15 11:16:56 +02:00
actiontextbuilder_close();
item_builder_close();
2018-02-03 23:39:49 +01:00
particle_engine_close();
2018-02-09 13:27:25 +01:00
timer_destroy(menuTimer);
mixer_close();
texturecache_close();
2018-03-17 00:04:26 +01:00
settings_close();
hiscore_close();
#ifdef STEAM_BUILD
steam_shutdown();
#endif // STEAM_BUILD
SDL_DestroyRenderer(gRenderer);
2017-11-30 21:00:47 +01:00
SDL_DestroyWindow(gWindow);
gWindow = NULL;
TTF_Quit();
2017-11-30 21:00:47 +01:00
IMG_Quit();
SDL_Quit();
}
int main(int argc, char *argv[])
2017-11-30 21:00:47 +01:00
{
UNUSED(argc);
PHYSFS_init(argv[0]);
2018-02-22 19:18:38 +01:00
#ifndef DEBUG
PHYSFS_mount("assets.pack", NULL, 0);
PHYSFS_mount("data.pack", NULL, 0);
2018-02-22 19:18:38 +01:00
#else
PHYSFS_mount("assets", NULL, 0);
2018-02-23 19:32:01 +01:00
PHYSFS_mount("data", NULL, 0);
#endif // DEBUG
2017-11-30 21:00:47 +01:00
if (!init())
return 1;
if (settings_get()->fullscreen_enabled) {
// Game starts in windowed mode so this will
// change to fullscreen
toggle_fullscreen();
}
2017-11-30 21:00:47 +01:00
run();
close();
PHYSFS_deinit();
2017-11-30 21:00:47 +01:00
return 0;
}