586 lines
15 KiB
C
586 lines
15 KiB
C
#include <SDL.h>
|
|
#include "system/stacktrace.h"
|
|
#include <stdio.h>
|
|
|
|
#include "game.h"
|
|
#include "game/level.h"
|
|
#include "game/sound_samples.h"
|
|
#include "game/level_picker.h"
|
|
#include "system/log.h"
|
|
#include "system/lt.h"
|
|
#include "system/nth_alloc.h"
|
|
#include "ui/console.h"
|
|
#include "ui/edit_field.h"
|
|
#include "ui/cursor.h"
|
|
#include "system/str.h"
|
|
#include "sdl/texture.h"
|
|
#include "game/level/level_editor/background_layer.h"
|
|
#include "game/level/level_editor.h"
|
|
#include "game/settings.h"
|
|
#include "game/credits.h"
|
|
|
|
typedef struct Game {
|
|
Lt *lt;
|
|
|
|
Game_state state;
|
|
Sprite_font font;
|
|
Memory level_editor_memory;
|
|
LevelPicker level_picker;
|
|
LevelEditor *level_editor;
|
|
Credits credits;
|
|
Level *level;
|
|
Settings settings;
|
|
Sound_samples *sound_samples;
|
|
Camera camera;
|
|
SDL_Renderer *renderer;
|
|
Console *console;
|
|
Cursor cursor;
|
|
int console_enabled;
|
|
} Game;
|
|
|
|
void game_switch_state(Game *game, Game_state state)
|
|
{
|
|
game->cursor.style = CURSOR_STYLE_POINTER;
|
|
if (state == GAME_STATE_LEVEL_PICKER) {
|
|
level_picker_clean_selection(&game->level_picker);
|
|
}
|
|
game->camera = create_camera(game->renderer, game->font);
|
|
game->state = state;
|
|
}
|
|
|
|
Game *create_game(const char *level_folder,
|
|
const char *sound_sample_files[],
|
|
size_t sound_sample_files_count,
|
|
SDL_Renderer *renderer)
|
|
{
|
|
trace_assert(level_folder);
|
|
|
|
Lt *lt = create_lt();
|
|
|
|
Game *game = PUSH_LT(lt, nth_calloc(1, sizeof(Game)), free);
|
|
if (game == NULL) {
|
|
RETURN_LT(lt, NULL);
|
|
}
|
|
game->lt = lt;
|
|
|
|
game->font.texture = load_bmp_font_texture(
|
|
renderer,
|
|
"./assets/images/charmap-oldschool.bmp");
|
|
|
|
game->level_editor_memory.capacity = LEVEL_EDITOR_MEMORY_CAPACITY;
|
|
game->level_editor_memory.buffer = malloc(LEVEL_EDITOR_MEMORY_CAPACITY);
|
|
trace_assert(game->level_editor_memory.buffer);
|
|
|
|
level_picker_populate(&game->level_picker, level_folder);
|
|
|
|
game->credits = create_credits();
|
|
|
|
game->sound_samples = PUSH_LT(
|
|
lt,
|
|
create_sound_samples(
|
|
sound_sample_files,
|
|
sound_sample_files_count),
|
|
destroy_sound_samples);
|
|
if (game->sound_samples == NULL) {
|
|
RETURN_LT(lt, NULL);
|
|
}
|
|
|
|
game->settings = create_settings();
|
|
|
|
game->renderer = renderer;
|
|
|
|
for (Cursor_Style style = 0; style < CURSOR_STYLE_N; ++style) {
|
|
game->cursor.texs[style] = PUSH_LT(
|
|
lt,
|
|
texture_from_bmp(cursor_style_tex_files[style], renderer),
|
|
SDL_DestroyTexture);
|
|
if (SDL_SetTextureBlendMode(
|
|
game->cursor.texs[style],
|
|
SDL_ComposeCustomBlendMode(
|
|
SDL_BLENDFACTOR_ONE_MINUS_DST_COLOR,
|
|
SDL_BLENDFACTOR_ONE_MINUS_SRC_COLOR,
|
|
SDL_BLENDOPERATION_ADD,
|
|
SDL_BLENDFACTOR_ONE,
|
|
SDL_BLENDFACTOR_ZERO,
|
|
SDL_BLENDOPERATION_ADD)) < 0) {
|
|
log_warn("SDL error while setting blending mode for `%s': %s\n",
|
|
cursor_style_tex_files[style],
|
|
SDL_GetError());
|
|
}
|
|
}
|
|
|
|
game->level_editor = create_level_editor(
|
|
&game->level_editor_memory,
|
|
&game->cursor);
|
|
|
|
game->console = PUSH_LT(
|
|
lt,
|
|
create_console(game),
|
|
destroy_console);
|
|
if (game->console == NULL) {
|
|
RETURN_LT(lt, NULL);
|
|
}
|
|
game->console_enabled = 0;
|
|
|
|
game_switch_state(game, GAME_STATE_LEVEL_PICKER);
|
|
|
|
return game;
|
|
}
|
|
|
|
void destroy_game(Game *game)
|
|
{
|
|
trace_assert(game);
|
|
destroy_level_picker(game->level_picker);
|
|
free(game->level_editor_memory.buffer);
|
|
RETURN_LT0(game->lt);
|
|
}
|
|
|
|
int game_render(const Game *game)
|
|
{
|
|
trace_assert(game);
|
|
|
|
switch(game->state) {
|
|
case GAME_STATE_LEVEL: {
|
|
if (level_render(game->level, &game->camera) < 0) {
|
|
return -1;
|
|
}
|
|
} break;
|
|
|
|
case GAME_STATE_LEVEL_PICKER: {
|
|
if (level_picker_render(&game->level_picker, &game->camera) < 0) {
|
|
return -1;
|
|
}
|
|
} break;
|
|
|
|
case GAME_STATE_LEVEL_EDITOR: {
|
|
if (level_editor_render(game->level_editor, &game->camera) < 0) {
|
|
return -1;
|
|
}
|
|
} break;
|
|
|
|
case GAME_STATE_CREDITS: {
|
|
if (credits_render(&game->credits, &game->camera) < 0) {
|
|
return -1;
|
|
}
|
|
} break;
|
|
|
|
case GAME_STATE_SETTINGS: {
|
|
settings_render(&game->settings, &game->camera);
|
|
} break;
|
|
|
|
case GAME_STATE_QUIT: break;
|
|
}
|
|
|
|
if (game->console_enabled) {
|
|
if (console_render(game->console, &game->camera) < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (cursor_render(&game->cursor, game->renderer) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int game_sound(Game *game)
|
|
{
|
|
switch (game->state) {
|
|
case GAME_STATE_LEVEL:
|
|
return level_sound(game->level, game->sound_samples);
|
|
case GAME_STATE_LEVEL_EDITOR:
|
|
level_editor_sound(game->level_editor, game->sound_samples);
|
|
return 0;
|
|
case GAME_STATE_LEVEL_PICKER:
|
|
case GAME_STATE_CREDITS:
|
|
case GAME_STATE_SETTINGS:
|
|
case GAME_STATE_QUIT:
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int game_update(Game *game, float delta_time)
|
|
{
|
|
trace_assert(game);
|
|
trace_assert(delta_time > 0.0f);
|
|
|
|
// TODO(#1218): effective scale recalculation should be probably done only when the size of the window is changed
|
|
SDL_Rect view_port;
|
|
SDL_RenderGetViewport(game->camera.renderer, &view_port);
|
|
game->camera.effective_scale = effective_scale(&view_port);
|
|
|
|
if (game->console_enabled) {
|
|
if (console_update(game->console, delta_time) < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
switch (game->state) {
|
|
case GAME_STATE_LEVEL: {
|
|
if (level_update(game->level, delta_time) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (level_enter_camera_event(game->level, &game->camera) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
} break;
|
|
|
|
case GAME_STATE_LEVEL_PICKER: {
|
|
if (level_picker_update(&game->level_picker, &game->camera, delta_time) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (level_picker_enter_camera_event(&game->level_picker, &game->camera) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
const char *level_filename = level_picker_selected_level(&game->level_picker);
|
|
|
|
if (level_filename != NULL) {
|
|
if (game_load_level(game, level_filename) < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
} break;
|
|
|
|
case GAME_STATE_LEVEL_EDITOR: {
|
|
if (level_editor_focus_camera(
|
|
game->level_editor,
|
|
&game->camera) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
level_editor_update(game->level_editor, delta_time);
|
|
} break;
|
|
|
|
case GAME_STATE_CREDITS: {
|
|
if (credits_update(&game->credits, &game->camera, delta_time) < 0) {
|
|
return -1;
|
|
}
|
|
} break;
|
|
|
|
case GAME_STATE_SETTINGS: {
|
|
settings_update(&game->settings, &game->camera, delta_time);
|
|
sound_samples_update_volume(
|
|
game->sound_samples,
|
|
game->settings.volume_slider.value);
|
|
} break;
|
|
|
|
case GAME_STATE_QUIT:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int game_event_running(Game *game, const SDL_Event *event)
|
|
{
|
|
trace_assert(game);
|
|
trace_assert(event);
|
|
|
|
if (!SDL_IsTextInputActive()) {
|
|
switch (event->type) {
|
|
case SDL_KEYDOWN: {
|
|
switch (event->key.keysym.sym) {
|
|
case SDLK_r: {
|
|
game->level = RESET_LT(
|
|
game->lt,
|
|
game->level,
|
|
create_level_from_level_editor(
|
|
game->level_editor));
|
|
if (game->level == NULL) {
|
|
game_switch_state(game, GAME_STATE_QUIT);
|
|
return -1;
|
|
}
|
|
|
|
level_disable_pause_mode(
|
|
game->level,
|
|
&game->camera,
|
|
game->sound_samples);
|
|
camera_disable_debug_mode(&game->camera);
|
|
} break;
|
|
|
|
case SDLK_TAB: {
|
|
game_switch_state(game, GAME_STATE_LEVEL_EDITOR);
|
|
} break;
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
return level_event(game->level, event, &game->camera, game->sound_samples);
|
|
}
|
|
|
|
static int game_event_level_picker(Game *game, const SDL_Event *event)
|
|
{
|
|
trace_assert(game);
|
|
trace_assert(event);
|
|
|
|
switch (event->type) {
|
|
case SDL_KEYDOWN: {
|
|
switch(event->key.keysym.sym) {
|
|
case SDLK_UP: {
|
|
level_picker_cursor_up(&game->level_picker);
|
|
} break;
|
|
|
|
case SDLK_DOWN: {
|
|
level_picker_cursor_down(&game->level_picker);
|
|
} break;
|
|
|
|
case SDLK_p: {
|
|
if (event->key.keysym.mod & KMOD_CTRL) {
|
|
level_picker_cursor_up(&game->level_picker);
|
|
}
|
|
} break;
|
|
|
|
case SDLK_n: {
|
|
if (event->key.keysym.mod & KMOD_CTRL) {
|
|
level_picker_cursor_down(&game->level_picker);
|
|
} else {
|
|
memory_clean(&game->level_editor_memory);
|
|
game->level_editor = create_level_editor(
|
|
&game->level_editor_memory,
|
|
&game->cursor);
|
|
|
|
if (game->level == NULL) {
|
|
game->level = PUSH_LT(
|
|
game->lt,
|
|
create_level_from_level_editor(
|
|
game->level_editor),
|
|
destroy_level);
|
|
} else {
|
|
game->level = RESET_LT(
|
|
game->lt,
|
|
game->level,
|
|
create_level_from_level_editor(
|
|
game->level_editor));
|
|
}
|
|
|
|
if (game->level == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
game_switch_state(game, GAME_STATE_LEVEL);
|
|
}
|
|
} break;
|
|
|
|
case SDLK_i: {
|
|
game_switch_state(game, GAME_STATE_CREDITS);
|
|
} break;
|
|
|
|
case SDLK_s: {
|
|
game_switch_state(game, GAME_STATE_SETTINGS);
|
|
} break;
|
|
}
|
|
} break;
|
|
}
|
|
|
|
return level_picker_event(&game->level_picker, event);
|
|
}
|
|
|
|
static int game_event_level_editor(Game *game, const SDL_Event *event)
|
|
{
|
|
trace_assert(game);
|
|
trace_assert(event);
|
|
|
|
switch (event->type) {
|
|
case SDL_KEYDOWN: {
|
|
switch (event->key.keysym.sym) {
|
|
case SDLK_TAB: {
|
|
game->level = RESET_LT(
|
|
game->lt,
|
|
game->level,
|
|
create_level_from_level_editor(
|
|
game->level_editor));
|
|
if (game->level == NULL) {
|
|
return -1;
|
|
}
|
|
game_switch_state(game, GAME_STATE_LEVEL);
|
|
} break;
|
|
}
|
|
} break;
|
|
}
|
|
|
|
return level_editor_event(game->level_editor, event, &game->camera, &game->level_editor_memory);
|
|
}
|
|
|
|
int game_event(Game *game, const SDL_Event *event)
|
|
{
|
|
trace_assert(game);
|
|
trace_assert(event);
|
|
|
|
// Global event handling
|
|
switch (event->type) {
|
|
case SDL_QUIT: {
|
|
game_switch_state(game, GAME_STATE_QUIT);
|
|
return 0;
|
|
} break;
|
|
|
|
case SDL_KEYDOWN: {
|
|
if ((event->key.keysym.sym == SDLK_q && event->key.keysym.mod & KMOD_CTRL) ||
|
|
(event->key.keysym.sym == SDLK_F4 && event->key.keysym.mod & KMOD_ALT)) {
|
|
game_switch_state(game, GAME_STATE_QUIT);
|
|
return 0;
|
|
}
|
|
} break;
|
|
}
|
|
|
|
// Console event handling
|
|
if (game->console_enabled) {
|
|
switch (event->type) {
|
|
case SDL_KEYDOWN:
|
|
switch (event->key.keysym.sym) {
|
|
case SDLK_ESCAPE:
|
|
SDL_StopTextInput();
|
|
game->console_enabled = 0;
|
|
return 0;
|
|
default: {}
|
|
}
|
|
|
|
default: {}
|
|
}
|
|
|
|
return console_handle_event(game->console, event);
|
|
} else {
|
|
switch (event->type) {
|
|
case SDL_KEYUP: {
|
|
switch (event->key.keysym.sym) {
|
|
case SDLK_BACKQUOTE:
|
|
case SDLK_c: {
|
|
if (event->key.keysym.mod == KMOD_NONE || event->key.keysym.mod == KMOD_NUM) {
|
|
SDL_StartTextInput();
|
|
game->console_enabled = 1;
|
|
console_slide_down(game->console);
|
|
}
|
|
} break;
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
// State event handling
|
|
switch (game->state) {
|
|
case GAME_STATE_LEVEL:
|
|
return game_event_running(game, event);
|
|
|
|
case GAME_STATE_LEVEL_PICKER:
|
|
return game_event_level_picker(game, event);
|
|
|
|
case GAME_STATE_LEVEL_EDITOR:
|
|
return game_event_level_editor(game, event);
|
|
|
|
case GAME_STATE_CREDITS: {
|
|
switch (event->type) {
|
|
case SDL_KEYDOWN: {
|
|
if (event->key.keysym.sym == SDLK_ESCAPE) {
|
|
game_switch_state(game, GAME_STATE_LEVEL_PICKER);
|
|
return 0;
|
|
}
|
|
} break;
|
|
}
|
|
|
|
return 0;
|
|
} break;
|
|
|
|
case GAME_STATE_SETTINGS: {
|
|
switch (event->type) {
|
|
case SDL_KEYDOWN: {
|
|
if (event->key.keysym.sym == SDLK_ESCAPE) {
|
|
game_switch_state(game, GAME_STATE_LEVEL_PICKER);
|
|
return 0;
|
|
}
|
|
} break;
|
|
}
|
|
|
|
settings_event(&game->settings, &game->camera, event);
|
|
return 0;
|
|
} break;
|
|
|
|
case GAME_STATE_QUIT:
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
// TODO(#1145): get rid of keyboard_state and introduce *_joystick methods
|
|
//
|
|
// keyboard_state is a global var and can be check anywhere anyway
|
|
int game_input(Game *game,
|
|
const Uint8 *const keyboard_state,
|
|
SDL_Joystick *the_stick_of_joy)
|
|
{
|
|
trace_assert(game);
|
|
trace_assert(keyboard_state);
|
|
|
|
if (game->console_enabled) {
|
|
return 0;
|
|
}
|
|
|
|
switch (game->state) {
|
|
case GAME_STATE_SETTINGS:
|
|
case GAME_STATE_CREDITS:
|
|
case GAME_STATE_QUIT:
|
|
case GAME_STATE_LEVEL_EDITOR:
|
|
return 0;
|
|
|
|
case GAME_STATE_LEVEL:
|
|
return level_input(game->level, keyboard_state, the_stick_of_joy);
|
|
|
|
case GAME_STATE_LEVEL_PICKER:
|
|
return level_picker_input(&game->level_picker, keyboard_state, the_stick_of_joy);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int game_over_check(const Game *game)
|
|
{
|
|
return game->state == GAME_STATE_QUIT;
|
|
}
|
|
|
|
int game_load_level(Game *game, const char *level_filename)
|
|
{
|
|
trace_assert(game);
|
|
trace_assert(level_filename);
|
|
|
|
memory_clean(&game->level_editor_memory);
|
|
game->level_editor =
|
|
create_level_editor_from_file(
|
|
&game->level_editor_memory,
|
|
&game->cursor,
|
|
level_filename);
|
|
|
|
if (!game->level_editor) {
|
|
game_switch_state(game, GAME_STATE_LEVEL_PICKER);
|
|
return 0;
|
|
}
|
|
|
|
if (game->level == NULL) {
|
|
game->level = PUSH_LT(
|
|
game->lt,
|
|
create_level_from_level_editor(
|
|
game->level_editor),
|
|
destroy_level);
|
|
} else {
|
|
game->level = RESET_LT(
|
|
game->lt,
|
|
game->level,
|
|
create_level_from_level_editor(
|
|
game->level_editor));
|
|
}
|
|
|
|
if (game->level == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
game_switch_state(game, GAME_STATE_LEVEL);
|
|
|
|
return 0;
|
|
}
|