feat: implemented menus and fixed bugs
This commit is contained in:
parent
8e0913b820
commit
1cfe4bc4c3
BIN
resources/escape.png
Normal file
BIN
resources/escape.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
BIN
resources/game_over.png
Normal file
BIN
resources/game_over.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
BIN
resources/maze.png
Normal file
BIN
resources/maze.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
|
@ -13,16 +13,16 @@ void CharacterLogic::set_character(Character *character) {
|
||||||
|
|
||||||
Character::Character(Tile location, CharacterLogic *logic, CharacterData stats)
|
Character::Character(Tile location, CharacterLogic *logic, CharacterData stats)
|
||||||
: data{stats}
|
: data{stats}
|
||||||
, logic{logic}
|
|
||||||
, health{stats.health}
|
, health{stats.health}
|
||||||
, location{location}
|
, location{location}
|
||||||
, world{nullptr} {
|
, world{nullptr}
|
||||||
|
, logic{logic} {
|
||||||
this->logic->set_character(this);
|
this->logic->set_character(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Character::act() {
|
void Character::act() {
|
||||||
assert(this->world != nullptr && "World generation did not initialize character properly");
|
assert(this->world != nullptr && "World generation did not initialize character properly");
|
||||||
if(this->health < 0) {
|
if(this->health <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->logic->set_character(this);
|
this->logic->set_character(this);
|
||||||
|
|
|
@ -12,12 +12,16 @@ InputHandler::~InputHandler() {
|
||||||
Input::remove_handler(this);
|
Input::remove_handler(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyEventHandler::KeyEventHandler(std::set<SDL_Scancode> code, Listener listener)
|
KeyEventHandler::KeyEventHandler(std::set<SDL_Scancode> code, Listener listener, bool allow_repeat)
|
||||||
: scancode{code}
|
: scancode{code}
|
||||||
, listener{listener} {}
|
, listener{listener}
|
||||||
|
, allow_repeat{allow_repeat}
|
||||||
|
{}
|
||||||
|
|
||||||
void KeyEventHandler::process_event(SDL_Event event) {
|
void KeyEventHandler::process_event(SDL_Event event) {
|
||||||
if((event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) && this->scancode.contains(event.key.keysym.scancode)) {
|
if((event.type == SDL_KEYDOWN || event.type == SDL_KEYUP)
|
||||||
|
&& (event.key.repeat == 0 || this->allow_repeat)
|
||||||
|
&& this->scancode.contains(event.key.keysym.scancode)) {
|
||||||
this->listener(event.type == SDL_KEYDOWN);
|
this->listener(event.type == SDL_KEYDOWN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,10 +15,11 @@ struct InputHandler {
|
||||||
|
|
||||||
struct KeyEventHandler : public InputHandler {
|
struct KeyEventHandler : public InputHandler {
|
||||||
typedef std::function<void(bool down)> Listener;
|
typedef std::function<void(bool down)> Listener;
|
||||||
KeyEventHandler(std::set<SDL_Scancode> code, Listener listener);
|
KeyEventHandler(std::set<SDL_Scancode> code, Listener listener, bool allow_repeat = true);
|
||||||
virtual void process_event(SDL_Event event) override;
|
virtual void process_event(SDL_Event event) override;
|
||||||
std::set<SDL_Scancode> scancode{};
|
std::set<SDL_Scancode> scancode{};
|
||||||
Listener listener{};
|
Listener listener{};
|
||||||
|
bool allow_repeat{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
class Input {
|
class Input {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include "renderer.h"
|
#include "renderer.h"
|
||||||
#include <SDL2/SDL_log.h>
|
#include <SDL2/SDL_log.h>
|
||||||
|
#include <SDL2/SDL_rect.h>
|
||||||
#include <SDL2/SDL_render.h>
|
#include <SDL2/SDL_render.h>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <SDL2/SDL_video.h>
|
#include <SDL2/SDL_video.h>
|
||||||
|
@ -35,15 +36,15 @@ RenderDataSetup &RenderDataSetup::with_resource_path(std::filesystem::path path)
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderDataSetup &RenderDataSetup::with_sprite(std::filesystem::path sprite_path) {
|
RenderDataSetup &RenderDataSetup::with_sprite(std::filesystem::path sprite_path) {
|
||||||
assert(this->renderer != nullptr && "Cannot create sprites without a renderer");
|
if(SDL_Texture *texture{this->load_texture(sprite_path)}) {
|
||||||
assert(this->resource_base_path.has_value() && "Resource base path has to be set before loading sprites");
|
|
||||||
std::filesystem::path complete_path{this->resource_base_path.value()/sprite_path};
|
|
||||||
SDL_Log("Adding sprite: %s", complete_path.c_str());
|
|
||||||
assert(std::filesystem::exists(complete_path) && "Sprite path has to exist");
|
|
||||||
if(SDL_Texture *texture{IMG_LoadTexture(this->renderer, complete_path.native().c_str())}) {
|
|
||||||
this->sprites.push_back(texture);
|
this->sprites.push_back(texture);
|
||||||
} else {
|
}
|
||||||
SDL_Log("Failed to load texture %s reason: %s", complete_path.c_str(), SDL_GetError());
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderDataSetup &RenderDataSetup::with_menu(std::filesystem::path file_path) {
|
||||||
|
if(SDL_Texture *texture{this->load_texture(file_path)}) {
|
||||||
|
this->menus.push_back(texture);
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
@ -56,13 +57,29 @@ RenderData RenderDataSetup::build() {
|
||||||
data.window=this->window;
|
data.window=this->window;
|
||||||
data.renderer=this->renderer;
|
data.renderer=this->renderer;
|
||||||
data.sprites = this->sprites;
|
data.sprites = this->sprites;
|
||||||
|
data.menus = this->menus;
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SDL_Texture *RenderDataSetup::load_texture(std::filesystem::path file_path) {
|
||||||
|
assert(this->renderer != nullptr && "Cannot create sprites without a renderer");
|
||||||
|
assert(this->resource_base_path.has_value() && "Resource base path has to be set before loading sprites");
|
||||||
|
std::filesystem::path complete_path{this->resource_base_path.value() / file_path};
|
||||||
|
SDL_Log("Adding sprite: %s", complete_path.string().c_str());
|
||||||
|
assert(std::filesystem::exists(complete_path) && "Sprite path has to exist");
|
||||||
|
if(SDL_Texture *texture{IMG_LoadTexture(this->renderer, complete_path.string().c_str())}) {
|
||||||
|
return texture;
|
||||||
|
} else {
|
||||||
|
SDL_Log("Failed to load texture %s reason: %s", complete_path.string().c_str(), SDL_GetError());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Tile Render::camera_center{0u, 0u};
|
Tile Render::camera_center{0u, 0u};
|
||||||
RenderData* Render::data{nullptr};
|
RenderData* Render::data{nullptr};
|
||||||
unsigned Render::camera_width{10};
|
unsigned Render::camera_width{10};
|
||||||
Tile Render::camera_offset{0, 0};
|
Tile Render::camera_offset{0, 0};
|
||||||
|
SDL_Point Render::window_size{0, 0};
|
||||||
int Render::tile_screen_width{2};
|
int Render::tile_screen_width{2};
|
||||||
int Render::half_tile_screen_width{1};
|
int Render::half_tile_screen_width{1};
|
||||||
|
|
||||||
|
@ -83,14 +100,13 @@ void Render::clear(Tile camera_center, unsigned fov) {
|
||||||
SDL_SetRenderDrawColor(Render::data->renderer, 0u, 0u, 0u, 255u);
|
SDL_SetRenderDrawColor(Render::data->renderer, 0u, 0u, 0u, 255u);
|
||||||
SDL_RenderClear(Render::data->renderer);
|
SDL_RenderClear(Render::data->renderer);
|
||||||
|
|
||||||
int width, height;
|
SDL_GetWindowSize(Render::data->window, &window_size.x, &window_size.y);
|
||||||
SDL_GetWindowSize(Render::data->window, &width, &height);
|
Render::tile_screen_width = int(window_size.x / Render::camera_width);
|
||||||
Render::tile_screen_width = int(width / Render::camera_width);
|
|
||||||
Render::half_tile_screen_width = Render::tile_screen_width >> 1;
|
Render::half_tile_screen_width = Render::tile_screen_width >> 1;
|
||||||
|
|
||||||
Render::camera_center = camera_center;
|
Render::camera_center = camera_center;
|
||||||
Render::camera_offset.x = -Render::camera_center.x * Render::tile_screen_width + (width >> 1);
|
Render::camera_offset.x = -Render::camera_center.x * Render::tile_screen_width + (window_size.x >> 1);
|
||||||
Render::camera_offset.y = -Render::camera_center.y * Render::tile_screen_width + (height >> 1);
|
Render::camera_offset.y = -Render::camera_center.y * Render::tile_screen_width + (window_size.y >> 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Render::draw(Tile world_space_tile, Sprite sprite) {
|
void Render::draw(Tile world_space_tile, Sprite sprite) {
|
||||||
|
@ -108,6 +124,30 @@ void Render::draw(Tile world_space_tile, Sprite sprite) {
|
||||||
SDL_RenderCopy(Render::data->renderer, Render::data->sprites[sprite], 0 , &rect);
|
SDL_RenderCopy(Render::data->renderer, Render::data->sprites[sprite], 0 , &rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Render::draw_menu(Sprite menu) {
|
||||||
|
assert_render_initialized();
|
||||||
|
Render::clear({0, 0});
|
||||||
|
SDL_Rect const rect {
|
||||||
|
.x = window_size.x/2 - window_size.y/2,
|
||||||
|
.y = 0,
|
||||||
|
.w = window_size.y,
|
||||||
|
.h = window_size.y
|
||||||
|
};
|
||||||
|
SDL_RenderCopy(Render::data->renderer, Render::data->menus[menu], 0, &rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Render::draw_healthbar(int current, int max, SDL_Rect area, int border) {
|
||||||
|
assert_render_initialized();
|
||||||
|
SDL_SetRenderDrawColor(Render::data->renderer, 0,0, 0, 255);
|
||||||
|
SDL_RenderFillRect(Render::data->renderer, &area);
|
||||||
|
area.x += border;
|
||||||
|
area.y += border;
|
||||||
|
area.h -= border * 2;
|
||||||
|
area.w = ((area.w - border * 2) * current) / max;
|
||||||
|
SDL_SetRenderDrawColor(Render::data->renderer, 200,200, 200, 255);
|
||||||
|
SDL_RenderFillRect(Render::data->renderer, &area);
|
||||||
|
}
|
||||||
|
|
||||||
void Render::present() {
|
void Render::present() {
|
||||||
assert_render_initialized();
|
assert_render_initialized();
|
||||||
SDL_RenderPresent(Render::data->renderer);
|
SDL_RenderPresent(Render::data->renderer);
|
||||||
|
|
|
@ -19,6 +19,7 @@ private: RenderData() = default;
|
||||||
SDL_Window *window{nullptr};
|
SDL_Window *window{nullptr};
|
||||||
SDL_Renderer *renderer{nullptr};
|
SDL_Renderer *renderer{nullptr};
|
||||||
std::vector<SDL_Texture*> sprites{};
|
std::vector<SDL_Texture*> sprites{};
|
||||||
|
std::vector<SDL_Texture*> menus{};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Global interface for rendering
|
// Global interface for rendering
|
||||||
|
@ -31,11 +32,14 @@ public:
|
||||||
|
|
||||||
static void clear(Tile camera_center, unsigned fov = 10);
|
static void clear(Tile camera_center, unsigned fov = 10);
|
||||||
static void draw(Tile world_space_tile, Sprite sprite);
|
static void draw(Tile world_space_tile, Sprite sprite);
|
||||||
|
static void draw_menu(Sprite menu);
|
||||||
|
static void draw_healthbar(int current, int max, SDL_Rect area = {0, 0, 500, 100}, int border = 5);
|
||||||
static void present();
|
static void present();
|
||||||
private:
|
private:
|
||||||
static Tile camera_center;
|
static Tile camera_center;
|
||||||
static unsigned camera_width;
|
static unsigned camera_width;
|
||||||
static Tile camera_offset;
|
static Tile camera_offset;
|
||||||
|
static SDL_Point window_size;
|
||||||
static int tile_screen_width;
|
static int tile_screen_width;
|
||||||
static int half_tile_screen_width;
|
static int half_tile_screen_width;
|
||||||
static RenderData *data;
|
static RenderData *data;
|
||||||
|
@ -49,13 +53,17 @@ public:
|
||||||
RenderDataSetup &with_renderer(Uint32 flags);
|
RenderDataSetup &with_renderer(Uint32 flags);
|
||||||
RenderDataSetup &with_resource_path(std::filesystem::path base_path);
|
RenderDataSetup &with_resource_path(std::filesystem::path base_path);
|
||||||
RenderDataSetup &with_sprite(std::filesystem::path sprite_path);
|
RenderDataSetup &with_sprite(std::filesystem::path sprite_path);
|
||||||
|
RenderDataSetup &with_menu(std::filesystem::path sprite_path);
|
||||||
RenderData build();
|
RenderData build();
|
||||||
|
private:
|
||||||
|
SDL_Texture *load_texture(std::filesystem::path file_path);
|
||||||
private:
|
private:
|
||||||
unsigned fov{10};
|
unsigned fov{10};
|
||||||
std::optional<std::filesystem::path> resource_base_path;
|
std::optional<std::filesystem::path> resource_base_path;
|
||||||
SDL_Window *window{nullptr};
|
SDL_Window *window{nullptr};
|
||||||
SDL_Renderer *renderer{nullptr};
|
SDL_Renderer *renderer{nullptr};
|
||||||
std::vector<SDL_Texture*> sprites{};
|
std::vector<SDL_Texture*> sprites{};
|
||||||
|
std::vector<SDL_Texture*> menus{};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,15 +47,23 @@ World::World()
|
||||||
std::bind(&World::on_input, this, std::placeholders::_1)
|
std::bind(&World::on_input, this, std::placeholders::_1)
|
||||||
} {}
|
} {}
|
||||||
|
|
||||||
|
void World::act_if_requested() {
|
||||||
|
if(this->turn_requested) {
|
||||||
|
this->act();
|
||||||
|
this->turn_requested = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void World::act() {
|
void World::act() {
|
||||||
player.act();
|
this->player.act();
|
||||||
|
this->boss.act();
|
||||||
for(Character &character : this->characters) {
|
for(Character &character : this->characters) {
|
||||||
character.act();
|
character.act();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void World::render() {
|
void World::render() {
|
||||||
Render::clear(this->player.location, 15);
|
Render::clear(this->player.location, 100);
|
||||||
for(size_t i{0}; i < this->rooms.size(); ++i) {
|
for(size_t i{0}; i < this->rooms.size(); ++i) {
|
||||||
this->rooms[i].draw({
|
this->rooms[i].draw({
|
||||||
.x = int(i % this->shear * this->chunk_size),
|
.x = int(i % this->shear * this->chunk_size),
|
||||||
|
@ -65,7 +73,9 @@ void World::render() {
|
||||||
for(Character &character : this->characters) {
|
for(Character &character : this->characters) {
|
||||||
character.draw();
|
character.draw();
|
||||||
}
|
}
|
||||||
|
boss.draw();
|
||||||
player.draw();
|
player.draw();
|
||||||
|
Render::draw_healthbar(player.health, player.data.health);
|
||||||
}
|
}
|
||||||
|
|
||||||
Room &World::get_room(Chunk chunk) {
|
Room &World::get_room(Chunk chunk) {
|
||||||
|
@ -76,6 +86,10 @@ Character *World::get_player() {
|
||||||
return &this->player;
|
return &this->player;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Character *World::get_boss() {
|
||||||
|
return &this->boss;
|
||||||
|
}
|
||||||
|
|
||||||
Chunk World::get_chunk(Tile tile) {
|
Chunk World::get_chunk(Tile tile) {
|
||||||
return {tile.x / this->chunk_size, tile.y / this->chunk_size};
|
return {tile.x / this->chunk_size, tile.y / this->chunk_size};
|
||||||
}
|
}
|
||||||
|
@ -86,8 +100,11 @@ TileData World::query_tile(Tile tile) {
|
||||||
.character = nullptr,
|
.character = nullptr,
|
||||||
.is_wall = room.tile_is_wall({tile.x % this->chunk_size, tile.y % this->chunk_size})
|
.is_wall = room.tile_is_wall({tile.x % this->chunk_size, tile.y % this->chunk_size})
|
||||||
};
|
};
|
||||||
if(player.location == tile) {
|
if(this->player.location == tile) {
|
||||||
out.character = &player;
|
out.character = &this->player;
|
||||||
|
return out;
|
||||||
|
} else if(this->boss.location == tile) {
|
||||||
|
out.character = &this->boss;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
for(Character &character : this->characters) {
|
for(Character &character : this->characters) {
|
||||||
|
@ -100,8 +117,8 @@ TileData World::query_tile(Tile tile) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void World::on_input(bool pressed) {
|
void World::on_input(bool pressed) {
|
||||||
if(!pressed) {
|
if(pressed) {
|
||||||
this->act();
|
this->turn_requested = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,8 +145,9 @@ WorldGenerator &WorldGenerator::with_room_size(unsigned min_side_length, unsigne
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
WorldGenerator &WorldGenerator::with_player(Character character) {
|
WorldGenerator &WorldGenerator::with_player(std::function<Character(Tile)> spawner) {
|
||||||
this->player.emplace(character);
|
assert(this->player_spawner == nullptr && "Player spawner cannot be assigned twice, remove previous call");
|
||||||
|
this->player_spawner = spawner;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,6 +156,12 @@ WorldGenerator &WorldGenerator::with_enemy(std::function<Character(Tile)> spawne
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WorldGenerator &WorldGenerator::with_boss(std::function<Character(Tile)> spawner) {
|
||||||
|
assert(this->boss_spawner == nullptr && "Boss spawner cannot be assigned twice, remove previous call");
|
||||||
|
this->boss_spawner = spawner;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
WorldGenerator &WorldGenerator::with_connecting_chance(unsigned num) {
|
WorldGenerator &WorldGenerator::with_connecting_chance(unsigned num) {
|
||||||
this->connecting_chance = num;
|
this->connecting_chance = num;
|
||||||
return *this;
|
return *this;
|
||||||
|
@ -148,7 +172,8 @@ std::unique_ptr<World> WorldGenerator::generate() {
|
||||||
assert(this->min_room_size > 0 && "Room size is required to generate world");
|
assert(this->min_room_size > 0 && "Room size is required to generate world");
|
||||||
assert(this->min_room_size < this->max_room_size && "Room size is required to generate world");
|
assert(this->min_room_size < this->max_room_size && "Room size is required to generate world");
|
||||||
assert(this->world_side_length > 0 && "World size is required to generate world");
|
assert(this->world_side_length > 0 && "World size is required to generate world");
|
||||||
assert(this->player.has_value() && "World cannot be generated without a player");
|
assert(this->player_spawner != nullptr && "World cannot be generated without a player");
|
||||||
|
assert(this->boss_spawner != nullptr && "Boss spawner is required to generate world");
|
||||||
|
|
||||||
std::unique_ptr<World> world{new World()};
|
std::unique_ptr<World> world{new World()};
|
||||||
Chunk start_room{this->world_side_length / 2, this->world_side_length / 2};
|
Chunk start_room{this->world_side_length / 2, this->world_side_length / 2};
|
||||||
|
@ -158,6 +183,7 @@ std::unique_ptr<World> WorldGenerator::generate() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void WorldGenerator::generate_world(std::unique_ptr<World> &world, Chunk start) {
|
void WorldGenerator::generate_world(std::unique_ptr<World> &world, Chunk start) {
|
||||||
|
this->boss_generated = false;
|
||||||
world->chunk_size = this->chunk_side_length;
|
world->chunk_size = this->chunk_side_length;
|
||||||
world->shear = this->world_side_length;
|
world->shear = this->world_side_length;
|
||||||
world->rooms.resize(this->world_side_length * this->world_side_length);
|
world->rooms.resize(this->world_side_length * this->world_side_length);
|
||||||
|
@ -201,7 +227,6 @@ void WorldGenerator::generate_room(std::unique_ptr<World> &world, Chunk const &l
|
||||||
{last.x, last.y + 1},
|
{last.x, last.y + 1},
|
||||||
{last.x, last.y - 1}
|
{last.x, last.y - 1}
|
||||||
};
|
};
|
||||||
this->add_enemies(world, last, last_room);
|
|
||||||
static const Directions directions[]{EAST, WEST, SOUTH, NORTH};
|
static const Directions directions[]{EAST, WEST, SOUTH, NORTH};
|
||||||
static const Directions inv_directions[] {WEST, EAST, NORTH, SOUTH};
|
static const Directions inv_directions[] {WEST, EAST, NORTH, SOUTH};
|
||||||
int const rand_offset{std::rand() % 4};
|
int const rand_offset{std::rand() % 4};
|
||||||
|
@ -213,7 +238,7 @@ void WorldGenerator::generate_room(std::unique_ptr<World> &world, Chunk const &l
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Room &option{world->get_room(next)};
|
Room &option{world->get_room(next)};
|
||||||
if(this->connecting_chance > 0 && !option.initialized || WRAP(std::rand(), 0, this->connecting_chance) == 0) {
|
if((this->connecting_chance > 0 && !option.initialized) || WRAP(std::rand(), 0, this->connecting_chance) == 0) {
|
||||||
// register hallway with the last chunk
|
// register hallway with the last chunk
|
||||||
last_room.hallway_paths |= directions[true_offset];
|
last_room.hallway_paths |= directions[true_offset];
|
||||||
// add returning hallway from the new chunk
|
// add returning hallway from the new chunk
|
||||||
|
@ -225,14 +250,25 @@ void WorldGenerator::generate_room(std::unique_ptr<World> &world, Chunk const &l
|
||||||
// generate surrounding rooms
|
// generate surrounding rooms
|
||||||
this->generate_room(world, next);
|
this->generate_room(world, next);
|
||||||
}
|
}
|
||||||
|
this->add_enemies(world, last, last_room);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WorldGenerator::add_enemies(std::unique_ptr<World> &world, Chunk const &chunk, Room &room) {
|
void WorldGenerator::add_enemies(std::unique_ptr<World> &world, Chunk const &chunk, Room &room) {
|
||||||
|
// this will get executed only by the first room to run out of neighbours to fill
|
||||||
|
if(!this->boss_generated) { // spawn boss at the center of the room
|
||||||
|
Character boss{this->boss_spawner({
|
||||||
|
chunk.x * this->chunk_side_length + room.rect.x + room.rect.w/2,
|
||||||
|
chunk.y * this->chunk_side_length + room.rect.y + room.rect.h/2
|
||||||
|
})};
|
||||||
|
boss.world = world.get();
|
||||||
|
world->boss = boss;
|
||||||
|
this->boss_generated = true;
|
||||||
|
}
|
||||||
for(std::pair<std::function<Character(Tile)>, int> const &pair : this->enemy_spawners) {
|
for(std::pair<std::function<Character(Tile)>, int> const &pair : this->enemy_spawners) {
|
||||||
if(WRAP(std::rand(), 0, pair.second) == 0) {
|
if(WRAP(std::rand(), 0, pair.second) == 0) {
|
||||||
Tile offset{
|
Tile offset{
|
||||||
WRAP(std::rand(), 1, room.rect.w - 2) + (chunk.x * this->chunk_side_length),
|
WRAP(std::rand(), 1, room.rect.w - 2) + (chunk.x * this->chunk_side_length) + room.rect.x,
|
||||||
WRAP(std::rand(), 1, room.rect.h - 2) + (chunk.y * this->chunk_side_length)
|
WRAP(std::rand(), 1, room.rect.h - 2) + (chunk.y * this->chunk_side_length) + room.rect.y
|
||||||
};
|
};
|
||||||
Character character{pair.first(offset)};
|
Character character{pair.first(offset)};
|
||||||
character.world = world.get();
|
character.world = world.get();
|
||||||
|
@ -242,11 +278,10 @@ void WorldGenerator::add_enemies(std::unique_ptr<World> &world, Chunk const &chu
|
||||||
}
|
}
|
||||||
|
|
||||||
void WorldGenerator::setup_player(std::unique_ptr<World> &world, Chunk spawn_chunk) {
|
void WorldGenerator::setup_player(std::unique_ptr<World> &world, Chunk spawn_chunk) {
|
||||||
world->player = this->player.value();
|
world->player = this->player_spawner({
|
||||||
world->player.world = world.get();
|
|
||||||
world->player.location = {
|
|
||||||
(spawn_chunk.x * this->chunk_side_length) + (this->chunk_side_length / 2),
|
(spawn_chunk.x * this->chunk_side_length) + (this->chunk_side_length / 2),
|
||||||
(spawn_chunk.y * this->chunk_side_length) + (this->chunk_side_length / 2),
|
(spawn_chunk.y * this->chunk_side_length) + (this->chunk_side_length / 2),
|
||||||
};
|
});
|
||||||
|
world->player.world = world.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,17 +32,21 @@ struct World {
|
||||||
friend class WorldGenerator;
|
friend class WorldGenerator;
|
||||||
World();
|
World();
|
||||||
~World() = default;
|
~World() = default;
|
||||||
|
void act_if_requested();
|
||||||
void act();
|
void act();
|
||||||
void render();
|
void render();
|
||||||
Room &get_room(Chunk chunk);
|
Room &get_room(Chunk chunk);
|
||||||
Character *get_player();
|
Character *get_player();
|
||||||
|
Character *get_boss();
|
||||||
Chunk get_chunk(Tile tile);
|
Chunk get_chunk(Tile tile);
|
||||||
TileData query_tile(Tile tile);
|
TileData query_tile(Tile tile);
|
||||||
void on_input(bool);
|
void on_input(bool);
|
||||||
private:
|
private:
|
||||||
int chunk_size{1};
|
int chunk_size{1};
|
||||||
unsigned shear{0};
|
unsigned shear{0};
|
||||||
|
bool turn_requested{false};
|
||||||
Character player{};
|
Character player{};
|
||||||
|
Character boss{};
|
||||||
std::vector<Room> rooms{};
|
std::vector<Room> rooms{};
|
||||||
std::vector<Character> characters{};
|
std::vector<Character> characters{};
|
||||||
KeyEventHandler direction_pressed;
|
KeyEventHandler direction_pressed;
|
||||||
|
@ -54,8 +58,9 @@ public:
|
||||||
WorldGenerator &with_chunk_size(unsigned side_length);
|
WorldGenerator &with_chunk_size(unsigned side_length);
|
||||||
WorldGenerator &with_world_size(unsigned side_length);
|
WorldGenerator &with_world_size(unsigned side_length);
|
||||||
WorldGenerator &with_room_size(unsigned min_side_length, unsigned max_side_length);
|
WorldGenerator &with_room_size(unsigned min_side_length, unsigned max_side_length);
|
||||||
WorldGenerator &with_player(Character character);
|
WorldGenerator &with_player(std::function<Character(Tile)> spawner);
|
||||||
WorldGenerator &with_enemy(std::function<Character(Tile)> spawner, int inv_frequency);
|
WorldGenerator &with_enemy(std::function<Character(Tile)> spawner, int inv_frequency);
|
||||||
|
WorldGenerator &with_boss(std::function<Character(Tile)> spawner);
|
||||||
WorldGenerator &with_connecting_chance(unsigned num);
|
WorldGenerator &with_connecting_chance(unsigned num);
|
||||||
std::unique_ptr<World> generate();
|
std::unique_ptr<World> generate();
|
||||||
private:
|
private:
|
||||||
|
@ -64,11 +69,13 @@ private:
|
||||||
void add_enemies(std::unique_ptr<World> &world, Chunk const &chunk, Room &room);
|
void add_enemies(std::unique_ptr<World> &world, Chunk const &chunk, Room &room);
|
||||||
void setup_player(std::unique_ptr<World> &world, Chunk spawn_chunk);
|
void setup_player(std::unique_ptr<World> &world, Chunk spawn_chunk);
|
||||||
private:
|
private:
|
||||||
std::optional<Character> player;
|
std::function<Character(Tile)> player_spawner{};
|
||||||
|
std::function<Character(Tile)> boss_spawner{};
|
||||||
std::vector<std::pair<std::function<Character(Tile)>, int>> enemy_spawners{};
|
std::vector<std::pair<std::function<Character(Tile)>, int>> enemy_spawners{};
|
||||||
int chunk_side_length{0}, world_side_length{0};
|
int chunk_side_length{0}, world_side_length{0};
|
||||||
int max_room_size{0}, min_room_size{0};
|
int max_room_size{0}, min_room_size{0};
|
||||||
unsigned connecting_chance{1};
|
unsigned connecting_chance{1};
|
||||||
|
bool boss_generated{false};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ Tile CritteLogic::move() {
|
||||||
} else {
|
} else {
|
||||||
// cache get_chunk call for easier reading
|
// cache get_chunk call for easier reading
|
||||||
std::function<Chunk(Tile)> get_chunk{std::bind(&World::get_chunk, this->character->world, std::placeholders::_1)};
|
std::function<Chunk(Tile)> get_chunk{std::bind(&World::get_chunk, this->character->world, std::placeholders::_1)};
|
||||||
this->is_aware_of_player |= get_chunk(player->location) != get_chunk(location);
|
this->is_aware_of_player |= get_chunk(player->location) == get_chunk(location);
|
||||||
}
|
}
|
||||||
// fallback return value, for if there is no desirable tile to move to
|
// fallback return value, for if there is no desirable tile to move to
|
||||||
return {
|
return {
|
||||||
|
|
123
src/main.cpp
123
src/main.cpp
|
@ -9,55 +9,142 @@
|
||||||
|
|
||||||
using namespace rogue;
|
using namespace rogue;
|
||||||
|
|
||||||
|
// the DOOM I/II solution :)
|
||||||
|
enum GameState {
|
||||||
|
MainMenu,
|
||||||
|
Game,
|
||||||
|
GameOver,
|
||||||
|
Escape
|
||||||
|
};
|
||||||
|
|
||||||
CharacterData const player_data{
|
CharacterData const player_data{
|
||||||
.health = 20,
|
.health = 20,
|
||||||
.damage = 2,
|
.damage = 2,
|
||||||
.sprite = 3
|
.sprite = 3
|
||||||
};
|
};
|
||||||
|
|
||||||
|
CharacterData const boss_data{
|
||||||
|
.health = 10,
|
||||||
|
.damage = 3,
|
||||||
|
.sprite = 0
|
||||||
|
};
|
||||||
|
|
||||||
CharacterData const critte_data{
|
CharacterData const critte_data{
|
||||||
.health = 5,
|
.health = 4,
|
||||||
.damage = 1,
|
.damage = 1,
|
||||||
.sprite = 2
|
.sprite = 2
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename TLogicType>
|
template <typename TLogicType>
|
||||||
Character create_character(CharacterData const &stats, Tile tile) {
|
static Character create_character(CharacterData const &stats, Tile tile) {
|
||||||
return Character(tile, new TLogicType, stats);
|
return Character(tile, new TLogicType, stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
std::unique_ptr<World> world{WorldGenerator()
|
GameState game_state{GameState::MainMenu};
|
||||||
.with_world_size(20)
|
WorldGenerator generator{WorldGenerator()
|
||||||
.with_chunk_size(10)
|
.with_world_size(10)
|
||||||
.with_room_size(6, 10)
|
.with_chunk_size(10)
|
||||||
.with_connecting_chance(5)
|
.with_room_size(6, 10)
|
||||||
.with_player(Character({0,0}, new PlayerCharacterLogic(), player_data))
|
.with_connecting_chance(5)
|
||||||
|
.with_player(std::bind(&create_character<PlayerCharacterLogic>, player_data, std::placeholders::_1))
|
||||||
.with_enemy(std::bind(&create_character<CritteLogic>, critte_data, std::placeholders::_1), 2)
|
.with_enemy(std::bind(&create_character<CritteLogic>, critte_data, std::placeholders::_1), 2)
|
||||||
.generate()
|
.with_enemy(std::bind(&create_character<CritteLogic>, critte_data, std::placeholders::_1), 2)
|
||||||
};
|
.with_enemy(std::bind(&create_character<CritteLogic>, critte_data, std::placeholders::_1), 2)
|
||||||
|
.with_boss(std::bind(&create_character<CritteLogic>, boss_data, std::placeholders::_1))};
|
||||||
RenderData data{RenderDataSetup()
|
RenderData data{RenderDataSetup()
|
||||||
|
// create window and renderer, allowing for texture creation
|
||||||
.with_window("roguelike", SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_FULLSCREEN)
|
.with_window("roguelike", SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_FULLSCREEN)
|
||||||
.with_renderer(SDL_RENDERER_ACCELERATED)
|
.with_renderer(SDL_RENDERER_ACCELERATED)
|
||||||
.with_resource_path("resources/")
|
// setup resources
|
||||||
.with_sprite("wizard.png")
|
.with_resource_path("resources/")
|
||||||
.with_sprite("wall.png")
|
// sprites
|
||||||
.with_sprite("critte.png")
|
.with_sprite("wizard.png")
|
||||||
.with_sprite("player.png")
|
.with_sprite("wall.png")
|
||||||
|
.with_sprite("critte.png")
|
||||||
|
.with_sprite("player.png")
|
||||||
|
// menus
|
||||||
|
.with_menu("maze.png")
|
||||||
|
.with_menu("game_over.png")
|
||||||
|
.with_menu("escape.png")
|
||||||
.build()
|
.build()
|
||||||
};
|
};
|
||||||
|
// use <functional> features to capture game_state and listen for input within main()
|
||||||
|
KeyEventHandler return_to_main_input{{SDL_SCANCODE_RETURN},
|
||||||
|
[&game_state](bool down) {
|
||||||
|
if(!down) return;
|
||||||
|
switch(game_state) {
|
||||||
|
case GameState::Game: // do nothing, let the input be handled by the world/characters
|
||||||
|
case GameState::MainMenu: // main menu starts with direction input not enter
|
||||||
|
break;
|
||||||
|
case GameState::GameOver:
|
||||||
|
game_state = GameState::MainMenu;
|
||||||
|
break;
|
||||||
|
case GameState::Escape:
|
||||||
|
game_state = GameState::MainMenu;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
false
|
||||||
|
};
|
||||||
|
KeyEventHandler start_game_input{{
|
||||||
|
SDL_SCANCODE_H, SDL_SCANCODE_J, SDL_SCANCODE_K, SDL_SCANCODE_L,
|
||||||
|
SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, SDL_SCANCODE_UP, SDL_SCANCODE_DOWN
|
||||||
|
},
|
||||||
|
[&game_state](bool down) {
|
||||||
|
if(!down) return;
|
||||||
|
switch(game_state) {
|
||||||
|
case GameState::Game:
|
||||||
|
case GameState::GameOver:
|
||||||
|
case GameState::Escape:
|
||||||
|
break;
|
||||||
|
case GameState::MainMenu:
|
||||||
|
game_state = GameState::Game;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
false
|
||||||
|
};
|
||||||
Render::provide_render_data(data);
|
Render::provide_render_data(data);
|
||||||
|
std::unique_ptr<World> world{nullptr};
|
||||||
SDL_Event evt;
|
SDL_Event evt;
|
||||||
for(;;) {
|
for(;;) {
|
||||||
world->render();
|
// DOOM-style menus
|
||||||
|
switch(game_state) {
|
||||||
|
case GameState::MainMenu:
|
||||||
|
Render::draw_menu(0);
|
||||||
|
break;
|
||||||
|
case GameState::Game:
|
||||||
|
// generate environment when needed
|
||||||
|
if(world == nullptr) {
|
||||||
|
world.reset(generator.generate().release());
|
||||||
|
}
|
||||||
|
world->act_if_requested();
|
||||||
|
world->render();
|
||||||
|
// game end conditions (player or boss death)
|
||||||
|
if(world->get_player()->health <= 0) {
|
||||||
|
game_state = GameState::GameOver;
|
||||||
|
world.reset();
|
||||||
|
} else if(world->get_boss()->health <= 0) {
|
||||||
|
game_state = GameState::Escape;
|
||||||
|
world.reset();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GameState::GameOver:
|
||||||
|
Render::draw_menu(1);
|
||||||
|
break;
|
||||||
|
case GameState::Escape:
|
||||||
|
Render::draw_menu(2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
Render::present();
|
Render::present();
|
||||||
if(SDL_WaitEvent(&evt)) {
|
if(SDL_WaitEvent(&evt)) {
|
||||||
if(evt.type == SDL_QUIT) {
|
if(evt.type == SDL_QUIT) {
|
||||||
goto main_loop;
|
goto break_main_loop;
|
||||||
} else {
|
} else {
|
||||||
Input::process_event(evt);
|
Input::process_event(evt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} main_loop:
|
} break_main_loop:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
#include "core/character.h"
|
#include "core/character.h"
|
||||||
#include "core/input.h"
|
#include "core/input.h"
|
||||||
#include "core/roguedefs.h"
|
#include "core/roguedefs.h"
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
namespace rogue {
|
namespace rogue {
|
||||||
class PlayerCharacterLogic : public CharacterLogic {
|
class PlayerCharacterLogic : public CharacterLogic {
|
||||||
|
|
Loading…
Reference in a new issue