diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/CApi.h | 235 | ||||
-rw-r--r-- | src/Level.cpp | 161 | ||||
-rw-r--r-- | src/Level.h | 84 | ||||
-rw-r--r-- | src/LuaApi.h | 120 | ||||
-rw-r--r-- | src/MessageBox.h | 98 | ||||
-rw-r--r-- | src/Scene.h | 31 | ||||
-rw-r--r-- | src/SfmlUtils.h | 110 | ||||
-rw-r--r-- | src/linux/res.h | 77 | ||||
-rw-r--r-- | src/lua.hpp | 8 | ||||
-rw-r--r-- | src/main.cpp | 278 |
10 files changed, 1202 insertions, 0 deletions
diff --git a/src/CApi.h b/src/CApi.h new file mode 100644 index 0000000..21ad9eb --- /dev/null +++ b/src/CApi.h @@ -0,0 +1,235 @@ +//======================================================================== +// dng +//------------------------------------------------------------------------ +// Copyright (c) 2022 Steph Enders <steph@senders.io> +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would +// be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not +// be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source +// distribution. +// +//======================================================================== + +#ifndef DNG_CAPI_H +#define DNG_CAPI_H + +#include "Level.h" +#include "Scene.h" +#include "lua.hpp" +#include <memory> + +extern std::shared_ptr<Level> lvl; +extern Scene scene; + +/* + * c_get_player_position(int x, int y) + */ +static int c_get_player_position(lua_State *L) { + lua_createtable(L, 0, 2); + lua_pushnumber(L, lvl->player.y + 1); + lua_setfield(L, -2, "y"); + lua_pushnumber(L, lvl->player.x + 1); + lua_setfield(L, -2, "x"); + return 1; +} + +/* + * c_move_player(int dx, int dy) + */ +static int c_move_player(lua_State *L) { + // stack ordering + int dy = static_cast<int>(lua_tonumber(L, -1)); + int dx = static_cast<int>(lua_tonumber(L, -2)); + + if (lvl->playerCanStep(dx, dy)) { + lvl->player.x += dx; + lvl->player.y += dy; + } + + lua_createtable(L, 0, 2); + lua_pushnumber(L, lvl->player.y + 1); + lua_setfield(L, -2, "y"); + lua_pushnumber(L, lvl->player.x + 1); + lua_setfield(L, -2, "x"); + + return 1; +} + +/* + * c_move_enemy(int id, int dx, int dy) + */ +static int c_move_enemy(lua_State *L) { + // stack ordering + int dy = static_cast<int>(lua_tonumber(L, -1)); + int dx = static_cast<int>(lua_tonumber(L, -2)); + int id = static_cast<int>(lua_tonumber(L, -3)); + + int i = lvl->getEnemyIndex(id); + // guard against enemy not found + if (i == -1) { + return 1; + } + + if (lvl->enemyCanStep(lvl->enemyPositions[i], dx, dy)) { + lvl->enemyPositions[i].x += dx; + lvl->enemyPositions[i].y += dy; + } + lua_createtable(L, 0, 3); + lua_pushnumber(L, lvl->enemyPositions[i].y + 1); + lua_setfield(L, -2, "y"); + lua_pushnumber(L, lvl->enemyPositions[i].x + 1); + lua_setfield(L, -2, "x"); + lua_pushnumber(L, lvl->enemyPositions[i].id); + lua_setfield(L, -2, "id"); + + return 1; +} + +/* + * c_get_enemies() + */ +static int c_get_enemies(lua_State *L) { + lua_createtable(L, int(lvl->enemyPositions.size()), 0); + + int idx = 0; + + for (auto &pos : lvl->enemyPositions) { + lua_pushnumber(L, ++idx); + lua_createtable(L, 0, 3); + lua_pushnumber(L, pos.id); + lua_setfield(L, -2, "id"); + lua_pushnumber(L, pos.x + 1); + lua_setfield(L, -2, "x"); + lua_pushnumber(L, pos.y + 1); + lua_setfield(L, -2, "y"); + lua_settable(L, -3); + } + + return 1; +} + +/* + * c_spawn_enemy(int x, int y) + */ +static int c_spawn_enemy(lua_State *L) { return 1; } + +/* + * c_destroy_enemy(int id) + */ +static int c_destroy_enemy(lua_State *L) { return 1; } + +/* + * c_get_map() + */ +static int c_get_map(lua_State *L) { + lua_createtable(L, int(lvl->map.size()), 0); + int idx = 0; + for (auto &vec : lvl->map) { + lua_pushnumber(L, ++idx); + lua_createtable(L, int(vec.size()), 0); + int inner_idx = 0; + for (auto &c : vec) { + lua_pushnumber(L, ++inner_idx); + lua_pushnumber(L, c == WALL_SPACE ? 1 : 0); + lua_rawset(L, -3); + } + lua_rawset(L, -3); + } + return 1; +} + +/* + * c_trigger_level_start() + */ +static int c_trigger_level_start(lua_State *L) { + scene = Scene::LEVEL; + lua_pushboolean(L, true); + return 1; +} +/* + * c_trigger_win() + */ +static int c_trigger_win(lua_State *L) { + scene = Scene::WIN; + lua_pushboolean(L, true); + return 1; +} + +/* + * c_trigger_loss() + */ +static int c_trigger_loss(lua_State *L) { + scene = Scene::LOSS; + lua_pushboolean(L, true); + return 1; +} + +static int c_get_scene(lua_State *L) { + lua_pushnumber(L, scene); + return 1; +} + +static int c_get_treasures(lua_State *L) { + lua_createtable(L, static_cast<int>(lvl->treasurePositions.size()), 0); + int idx = 0; + for (auto &t : lvl->treasurePositions) { + lua_pushnumber(L, ++idx); + lua_createtable(L, 0, 3); + lua_pushnumber(L, t.y + 1); + lua_setfield(L, -2, "y"); + lua_pushnumber(L, t.x + 1); + lua_setfield(L, -2, "x"); + lua_pushnumber(L, t.id); + lua_setfield(L, -2, "id"); + lua_settable(L, -3); + } + return 1; +} + +static int c_score_treasure(lua_State *L) { + int id = static_cast<int>(lua_tonumber(L, -1)); + + erase_if(lvl->treasurePositions, [id](Pos t) { return t.id == id; }); + + return 1; +} + +static int c_trigger_restart(lua_State *L) { + lvl->reset(); + scene = Scene::INTRO; + return 1; +} + +// not for lua use +void init_c_api(lua_State *L) { + lua_register(L, "c_move_player", c_move_player); + lua_register(L, "c_move_enemy", c_move_enemy); + lua_register(L, "c_spawn_enemy", c_spawn_enemy); + lua_register(L, "c_destroy_enemy", c_destroy_enemy); + lua_register(L, "c_get_enemies", c_get_enemies); + lua_register(L, "c_get_player_position", c_get_player_position); + lua_register(L, "c_get_map", c_get_map); + lua_register(L, "c_trigger_level_start", c_trigger_level_start); + lua_register(L, "c_trigger_win", c_trigger_win); + lua_register(L, "c_trigger_loss", c_trigger_loss); + lua_register(L, "c_get_scene", c_get_scene); + lua_register(L, "c_score_treasure", c_score_treasure); + lua_register(L, "c_get_treasures", c_get_treasures); + lua_register(L, "c_trigger_restart", c_trigger_restart); +} + +#endif // DNG_CAPI_H diff --git a/src/Level.cpp b/src/Level.cpp new file mode 100644 index 0000000..6db4b4e --- /dev/null +++ b/src/Level.cpp @@ -0,0 +1,161 @@ +//======================================================================== +// dng +//------------------------------------------------------------------------ +// Copyright (c) 2022 Steph Enders <steph@senders.io> +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would +// be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not +// be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source +// distribution. +// +//======================================================================== + +#include "Level.h" +#include "SfmlUtils.h" +#include <fstream> +#include <iostream> +#include <string> + +bool canStep(Pos pos, int dx, int dy, std::vector<std::vector<char>> map) { + return map[pos.y + dy][pos.x + dx] != WALL_SPACE; +} + +void Level::loadLevelFromFile(const char *filePath) { + std::ifstream mapFile(filePath); + if (mapFile.is_open()) { + this->file = filePath; + // each element in the map has a unique ID + // some magic but player is always 0 + const int playerId = 0; + // from 1 -> N each enemy and treasure has its own unique ID + // IDs are unique entirely, not just per enemy or treasure + + std::string line; + int y = 0; + while (std::getline(mapFile, line)) { + this->map.emplace_back(); + int x = 0; + for (char c : line) { + if (c == WALL_TKN) { + this->map[y].push_back(WALL_SPACE); + auto w = create_wall(x, y); + this->displayMap.push_back(w); + } else if (c == EMPTY_TKN) { + this->map[y].push_back(BLANK_SPACE); + // no display info + } else if (c == ENEMY_TKN) { + auto e = create_enemy(x, y); + this->enemyPositions.push_back( + {.id = this->nextId(), .x = x, .y = y, .sprite = e}); + this->map[y].push_back(BLANK_SPACE); + } else if (c == PLAYER_TKN) { + auto p = create_player(x, y); + this->player = {.id = playerId, .x = x, .y = y, .sprite = p}; + this->map[y].push_back(BLANK_SPACE); + } else if (c == TREASURE_TKN) { + auto t = create_treasure(x, y); + this->treasurePositions.push_back( + {.id = this->nextId(), .x = x, .y = y, .sprite = t}); + this->map[y].push_back(BLANK_SPACE); + } else { + continue; + } + ++x; + } + ++y; + } + + unsigned long max_width = 0; + for (auto &rows : this->map) { + if (max_width < rows.size()) { + max_width = rows.size(); + } + } + this->width = static_cast<int>(max_width); + this->height = static_cast<int>(this->map.size()); + } + mapFile.close(); +} + +void Level::reset() { + this->map.clear(); + this->treasurePositions.clear(); + this->displayMap.clear(); + this->enemyPositions.clear(); + this->loadLevelFromFile(this->file); +} + +bool Level::isEmpty(int x, int y) { return map[y][x] == BLANK_SPACE; } + +bool Level::playerCanStep(int dx, int dy) { + return canStep(player, dx, dy, map); +} + +void Level::print() { + int x = 0; + int y = 0; + for (auto &row : map) { + for (auto &tile : row) { + bool printed = false; + if (player.x == x && player.y == y) { + std::cout << "p"; + printed = true; + } + for (auto pos : enemyPositions) { + if (pos.x == x && pos.y == y) { + std::cout << "e"; + printed = true; + } + } + for (auto pos : treasurePositions) { + if (pos.x == x && pos.y == y) { + std::cout << "t"; + printed = true; + } + } + if (tile == WALL_SPACE) { + std::cout << tile; + printed = true; + } + if (!printed) { + std::cout << " "; + } + std::cout << " "; + ++x; + } + std::cout << "\n"; + ++y; + x = 0; + } +} + +int Level::nextId() { return idCounter++; } + +int Level::getEnemyIndex(int id) { + for (int i = 0; i < enemyPositions.size(); i++) { + if (enemyPositions[i].id == id) { + return i; + } + } + + return -1; +} +bool Level::enemyCanStep(Pos pos, int dx, int dy) { + return canStep(pos, dx, dy, map); +} +int Level::getWidth() { return this->width; } +int Level::getHeight() { return this->height; } diff --git a/src/Level.h b/src/Level.h new file mode 100644 index 0000000..f0cec68 --- /dev/null +++ b/src/Level.h @@ -0,0 +1,84 @@ +//======================================================================== +// dng +//------------------------------------------------------------------------ +// Copyright (c) 2022 Steph Enders <steph@senders.io> +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would +// be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not +// be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source +// distribution. +// +//======================================================================== + +#ifndef DNG_LEVEL_H +#define DNG_LEVEL_H + +#include "SFML/Graphics/RectangleShape.hpp" +#include <memory> +#include <vector> + +// tokens from map file +static const char PLAYER_TKN = 'p'; +static const char WALL_TKN = 'w'; +static const char EMPTY_TKN = '0'; +static const char TREASURE_TKN = 't'; +static const char ENEMY_TKN = 'e'; +static const char BLANK_SPACE = '\0'; +static const char WALL_SPACE = '#'; + +struct Pos { + int id; + int x; + int y; + sf::RectangleShape sprite; +} typedef Pos; + +class Level { + +public: + void loadLevelFromFile(const char *filePath); + + bool isEmpty(int x, int y); + + bool playerCanStep(int dx, int dy); + + int getEnemyIndex(int id); + + bool enemyCanStep(Pos pos, int dx, int dy); + + void reset(); + /* deprecate*/ + void print(); + + int nextId(); + int getWidth(); + int getHeight(); + + std::vector<std::vector<char>> map; // source copy of map + std::vector<sf::RectangleShape> displayMap; + Pos player; + std::vector<Pos> enemyPositions; + std::vector<Pos> treasurePositions; + +private: + int idCounter = 1; // defaults at 1 (player always 0) + int width; + int height; + const char *file; +}; + +#endif // DNG_LEVEL_H diff --git a/src/LuaApi.h b/src/LuaApi.h new file mode 100644 index 0000000..43bd50f --- /dev/null +++ b/src/LuaApi.h @@ -0,0 +1,120 @@ +//======================================================================== +// dng +//------------------------------------------------------------------------ +// Copyright (c) 2022 Steph Enders <steph@senders.io> +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would +// be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not +// be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source +// distribution. +// +//======================================================================== + +#ifndef DNG_LUA_API_H +#define DNG_LUA_API_H + +#include "lua.hpp" +#include <iostream> + +struct LState { + lua_State *onkeypress; + lua_State *onupdate; + lua_State *onintro; + lua_State *onwin; + lua_State *onloss; +} typedef LState; + +const char *ON_KEYPRESS = "onKeyPress"; +const char *ON_UPDATE = "onUpdate"; +const char *ON_INTRO = "onIntro"; +const char *ON_WIN = "onWin"; +const char *ON_LOSS = "onLoss"; + +LState *init_default(lua_State *L) { + auto *state = static_cast<LState *>(malloc(sizeof(LState))); + + state->onkeypress = L; + state->onupdate = L; + state->onintro = L; + state->onwin = L; + state->onloss = L; + + return state; +} + +bool check_fn(lua_State *L, const char *fn) { + lua_getglobal(L, fn); + return lua_isfunction(L, -1); +} + +void override_file_fns(lua_State *L, LState *state) { + if (check_fn(L, ON_KEYPRESS)) { + state->onkeypress = L; + } + if (check_fn(L, ON_UPDATE)) { + state->onupdate = L; + } + if (check_fn(L, ON_INTRO)) { + state->onintro = L; + } + if (check_fn(L, ON_WIN)) { + state->onwin = L; + } + if (check_fn(L, ON_LOSS)) { + state->onloss = L; + } +} + +bool lua_dofn(lua_State *L, const char *fn) { + lua_getglobal(L, fn); + if (!lua_isfunction(L, -1)) { + std::cout << "[C] Error " << fn << " not function | not found" << std::endl; + return false; + } + lua_pcall(L, 0, 1, 0); + return true; +} + +bool lua_dofn_with_number(lua_State *L, const char *fn, lua_Number num) { + lua_getglobal(L, fn); + if (!lua_isfunction(L, -1)) { + std::cout << "[C] Error " << fn << " not function | not found" << std::endl; + return false; + } + lua_pushnumber(L, num); + lua_pcall(L, 1, 1, 0); + return true; +} + +bool lua_onkeypress(lua_State *L, int pressedKey) { + if (pressedKey == -1) { + return true; + } + return lua_dofn_with_number(L, "onKeyPress", pressedKey); +} + +bool lua_onupdate(lua_State *L, float dt) { + return lua_dofn_with_number(L, "onUpdate", dt); +} + +bool lua_onintro(lua_State *L) { return lua_dofn(L, "onIntro"); } + +bool lua_onwin(lua_State *L) { return lua_dofn(L, "onWin"); } + +bool lua_onloss(lua_State *L) { return lua_dofn(L, "onLoss"); } + +#endif // DNG_LUA_API_H diff --git a/src/MessageBox.h b/src/MessageBox.h new file mode 100644 index 0000000..0e1fa8f --- /dev/null +++ b/src/MessageBox.h @@ -0,0 +1,98 @@ +//======================================================================== +// dng +//------------------------------------------------------------------------ +// Copyright (c) 2022 Steph Enders <steph@senders.io> +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would +// be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not +// be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source +// distribution. +// +//======================================================================== + +#ifndef DNG_MESSAGE_BOX_H +#define DNG_MESSAGE_BOX_H + +#include "SFML/Graphics/RectangleShape.hpp" +#include "SFML/Graphics/Text.hpp" +#include "SfmlUtils.h" +#include <vector> + +const int LARGE_TEXT = 54; +const int MEDIUM_TEXT = 36; +const int SMALL_TEXT = 24; +const float PADDING = 16.f; +const float LINE_HEIGHT = 1.2f; +struct MessageBox { + + std::vector<sf::Text> msgs; + sf::RectangleShape box; + +} typedef MessageBox; + +struct DisplayText { + std::string msg; + int textSize; +} typedef DisplayText; + +inline MessageBox initializeMessageBox(const std::vector<DisplayText> &strs, + const sf::Font &font, + sf::Vector2u windowSize) { + + std::vector<sf::Text> messages; + sf::RectangleShape box; + float width = 0.f; + float height = 0.f; + float left = MAXFLOAT; + sf::Text prev; + for (const auto &str : strs) { + sf::Text text = write_text(str.msg.c_str(), str.textSize, LINE_HEIGHT, font, + windowSize); + // move below previous + if (!prev.getString().isEmpty()) { + text.move(0.f, prev.getLocalBounds().height * prev.getLineSpacing()); + } + // add to vector + messages.push_back(text); + + // find widest + if (text.getGlobalBounds().width > width) { + width = text.getGlobalBounds().width; + } + // find leftmost + if (text.getGlobalBounds().left < left) { + left = text.getGlobalBounds().left; + } + // heights added together + line height + height += (text.getLocalBounds().height * text.getLineSpacing()); + prev = text; + } + box = sf::RectangleShape(sf::Vector2f(width + PADDING, height + PADDING)); + box.setOutlineColor(sf::Color::Black); + box.setOutlineThickness(4.f); + box.setFillColor(sf::Color(128, 128, 128, 128)); + box.setPosition(left - (PADDING / 2.f), + // Top will always be the first message + messages[0].getGlobalBounds().top - (PADDING / 2.f)); + + return { + .msgs = messages, + .box = box, + }; +} + +#endif // DNG_MESSAGE_BOX_H
\ No newline at end of file diff --git a/src/Scene.h b/src/Scene.h new file mode 100644 index 0000000..0db80cd --- /dev/null +++ b/src/Scene.h @@ -0,0 +1,31 @@ +//======================================================================== +// dng +//------------------------------------------------------------------------ +// Copyright (c) 2022 Steph Enders <steph@senders.io> +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would +// be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not +// be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source +// distribution. +// +//======================================================================== + +#ifndef DNG_SCENE_H +#define DNG_SCENE_H +/* sync with constants.lua */ +enum Scene { INTRO, LEVEL, WIN, LOSS }; +#endif // DNG_SCENE_H diff --git a/src/SfmlUtils.h b/src/SfmlUtils.h new file mode 100644 index 0000000..dd63cd6 --- /dev/null +++ b/src/SfmlUtils.h @@ -0,0 +1,110 @@ +//======================================================================== +// dng +//------------------------------------------------------------------------ +// Copyright (c) 2022 Steph Enders <steph@senders.io> +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would +// be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not +// be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source +// distribution. +// +//======================================================================== + +#ifndef DNG_SFML_UTILS_H +#define DNG_SFML_UTILS_H + +#include "SFML/Graphics/RectangleShape.hpp" +#include "SFML/Graphics/Text.hpp" +#include "SFML/Window/Event.hpp" +#include "SFML/Window/Keyboard.hpp" +#include <cmath> +#include <iostream> + +const float SPRITE_SIZE = 16.f; // squares +const sf::Color WALL_COLOR = sf::Color(150, 150, 150, 255); +const sf::Color BLANK_COLOR = sf::Color(216, 216, 216, 255); + +inline sf::Keyboard::Key get_key(sf::Event event) { + if (event.type == sf::Event::KeyPressed || + sf::Keyboard::isKeyPressed(event.key.code)) { + return event.key.code; + } + return sf::Keyboard::Unknown; +} + +inline sf::Vector2f to_position_xy(int x, int y) { + return {static_cast<float>(x) * SPRITE_SIZE, + static_cast<float>(y) * SPRITE_SIZE}; +} +inline sf::Vector2f to_position(const Pos &pos) { + return to_position_xy(pos.x, pos.y); +} + +inline sf::RectangleShape create_square(sf::Color color, int x, int y) { + sf::RectangleShape rect({SPRITE_SIZE - 1.f, SPRITE_SIZE - 1.f}); + rect.setFillColor(color); + rect.setOutlineColor(sf::Color::Black); + rect.setOutlineThickness(1.f); + rect.setPosition(to_position_xy(x, y)); + return rect; +} + +inline sf::RectangleShape create_wall(int x, int y) { + return create_square(WALL_COLOR, x, y); +} + +inline sf::RectangleShape create_enemy(int x, int y) { + return create_square(sf::Color::Magenta, x, y); +} + +inline sf::RectangleShape create_player(int x, int y) { + return create_square(sf::Color::Cyan, x, y); +} + +inline sf::RectangleShape create_treasure(int x, int y) { + return create_square(sf::Color::Yellow, x, y); +} + +inline sf::Vector2f round(const sf::Vector2f vector) { + return sf::Vector2f{std::round(vector.x), std::round(vector.y)}; +} + +inline sf::Text write_text(const char *msg, unsigned int fontSize, + float lineSpacing, const sf::Font &font, + const sf::Vector2u windowSize) { + sf::Text text(msg, font, fontSize); + text.setOutlineThickness(4.f); + text.setOrigin(round(sf::Vector2f{text.getLocalBounds().width / 2, + text.getLocalBounds().height / 2})); + text.setPosition(sf::Vector2f(windowSize / 2u)); + text.setLineSpacing(lineSpacing); + while (text.getGlobalBounds().top < 0 || text.getGlobalBounds().left < 0 || + static_cast<unsigned int>( + (text.getGlobalBounds().width + text.getGlobalBounds().left) * + text.getLineSpacing()) > windowSize.x || + static_cast<unsigned int>( + (text.getGlobalBounds().height + text.getGlobalBounds().top) * + text.getLineSpacing()) > windowSize.y) { + text.setCharacterSize(text.getCharacterSize() - 1); + text.setOrigin(round(sf::Vector2f{text.getLocalBounds().width / 2, + text.getLocalBounds().height / 2})); + text.setPosition(sf::Vector2f(windowSize / 2u)); + } + return text; +} + +#endif // DNG_SFML_UTILS_H
\ No newline at end of file diff --git a/src/linux/res.h b/src/linux/res.h new file mode 100644 index 0000000..9e3b8c5 --- /dev/null +++ b/src/linux/res.h @@ -0,0 +1,77 @@ +//======================================================================== +// dng +//------------------------------------------------------------------------ +// Copyright (c) 2022 Steph Enders <steph@senders.io> +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would +// be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not +// be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source +// distribution. +// +//======================================================================== + +#ifndef DNG_RES_H +#define DNG_RES_H + +#include <filesystem> + +const std::filesystem::path DEFAULT_PROC{"dnglib/defaults.lua"}; +const std::filesystem::path DEFAULT_FONT{"res/PressStart2P-vaV7.ttf"}; +// TODO setup to allow switching to monospace instead of game font +// const std::filesystem::path DEFAULT_MONOSPACE_FONT{ +// "res/LiberationMono-Regular.ttf"}; + +struct Res { + + std::filesystem::path defaultsFile; + std::filesystem::path fontFile; + +} typedef Res; + +inline Res get_resources() { + using namespace std::filesystem; + + auto current_dir = current_path(); + auto exe_dir = canonical("/proc/self/exe").remove_filename(); + auto install_dir = path{"/usr/local/share/dng/"}; + + Res res; + if (exists(current_dir / DEFAULT_PROC)) { + res.defaultsFile = current_dir / DEFAULT_PROC; + } else if (exists(exe_dir / DEFAULT_PROC)) { + res.defaultsFile = exe_dir / DEFAULT_PROC; + } else if (exists(install_dir / DEFAULT_PROC)) { + res.defaultsFile = install_dir / DEFAULT_PROC; + } else { + res.defaultsFile = DEFAULT_PROC; // just return w/ no path info + } + // TODO make configurable + path fontp = DEFAULT_FONT; + if (exists(current_dir / fontp)) { + res.fontFile = current_dir / fontp; + } else if (exists(exe_dir / fontp)) { + res.fontFile = exe_dir / fontp; + } else if (exists(install_dir / fontp)) { + res.fontFile = install_dir / fontp; + } else { + res.fontFile = fontp; // just return w/ no path info + } + + return res; +} + +#endif // DNG_RES_H
\ No newline at end of file diff --git a/src/lua.hpp b/src/lua.hpp new file mode 100644 index 0000000..4cf79d2 --- /dev/null +++ b/src/lua.hpp @@ -0,0 +1,8 @@ +// Included locally +// Building from sources doesn't include this by default +// See Copyright Notice in thirdparty/lua/lua.h +extern "C" { +#include "lauxlib.h" +#include "lua.h" +#include "lualib.h" +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..99f8603 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,278 @@ +//======================================================================== +// dng +//------------------------------------------------------------------------ +// Copyright (c) 2022 Steph Enders <steph@senders.io> +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would +// be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not +// be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source +// distribution. +// +//======================================================================== + +#include "CApi.h" +#include "Level.h" +#include "LuaApi.h" +#include "MessageBox.h" +#include "SfmlUtils.h" +#ifdef __linux__ +#include "linux/res.h" +#endif +#include "lua.hpp" +#include <SFML/Graphics.hpp> +#include <cmath> +#include <filesystem> +#include <iostream> + +std::shared_ptr<Level> lvl; + +Scene scene; +const int MAX_WIDTH = static_cast<int>(SPRITE_SIZE) * 20 * 4; +const int MAX_HEIGHT = static_cast<int>(SPRITE_SIZE) * 20 * 3; + +int main(int argc, char **argv) { + + if (argc <= 1) { + std::cerr << "Must pass in path to level directory" << std::endl; + return -1; + } + + auto res = get_resources(); + + std::string lvl_pfx = argv[1]; + + std::filesystem::path mapFile{lvl_pfx + "/dng.map"}; + std::filesystem::path luaFile{lvl_pfx + "/proc.lua"}; + + lvl = std::make_shared<Level>(); + scene = Scene::INTRO; + + lvl->loadLevelFromFile(mapFile.c_str()); + + lua_State *L_lvl = luaL_newstate(); + luaL_openlibs(L_lvl); + init_c_api(L_lvl); + + lua_State *L_default = luaL_newstate(); + luaL_openlibs(L_default); + init_c_api(L_default); + + if (std::filesystem::exists(res.defaultsFile) && + luaL_dofile(L_default, res.defaultsFile.c_str()) != LUA_OK) { + std::cout << "Failed to load default proc" << std::endl; + luaL_error(L_default, "Error: %s", lua_tostring(L_default, -1)); + return EXIT_FAILURE; + } + + // Initialize to default + LState *l_state = init_default(L_default); + + if (std::filesystem::exists(luaFile) && + luaL_dofile(L_lvl, luaFile.c_str()) == LUA_OK) { + override_file_fns(L_lvl, l_state); + } else if (std::filesystem::exists(luaFile)) { + std::cout << "[C] No Good" << std::endl; + luaL_error(L_lvl, "Error: %s\n", lua_tostring(L_lvl, -1)); + return EXIT_FAILURE; + } + + float ZOOM = 0.5f; + sf::Vector2f mapBounds = {static_cast<float>(lvl->getWidth()) * SPRITE_SIZE, + static_cast<float>(lvl->getHeight()) * SPRITE_SIZE}; + int width = static_cast<int>(static_cast<float>(lvl->getWidth()) / ZOOM * + SPRITE_SIZE / ZOOM); + int height = static_cast<int>(static_cast<float>(lvl->getHeight()) / ZOOM * + SPRITE_SIZE / ZOOM); + bool useViewport = false; + if (width > MAX_WIDTH) { + width = MAX_WIDTH; + useViewport = true; + } + if (height > MAX_HEIGHT) { + height = MAX_HEIGHT; + useViewport = true; + } + + sf::RenderWindow window(sf::VideoMode(width, height), "dng"); + window.setMouseCursorVisible(false); + window.setFramerateLimit(30); + sf::View view(window.getDefaultView()); + view.zoom(ZOOM); + view.setSize(view.getSize() * ZOOM); + view.setViewport(sf::FloatRect(0.f, 0.f, 1.f, 1.f)); + if (useViewport) { + view.setCenter(to_position(lvl->player) + + sf::Vector2f(SPRITE_SIZE / 2.f, SPRITE_SIZE / 2.f)); + } else { + view.setCenter(view.getSize() / 2.f); + } + + window.setView(view); + sf::Clock deltaClock; + sf::Font font; + font.loadFromFile(res.fontFile); + + MessageBox intro; + MessageBox win; + MessageBox loss; + + do { + sf::Event event{}; + float dt = deltaClock.restart().asSeconds(); + + while (window.pollEvent(event)) { + if (event.type == sf::Event::Closed || + sf::Keyboard::isKeyPressed(sf::Keyboard::Q)) { + window.close(); + } + if (!lua_onkeypress(l_state->onkeypress, get_key(event))) { + window.close(); + } + } + + if (scene == Scene::INTRO) { + if (!lua_onintro(l_state->onintro)) { + window.close(); + } + } else if (scene == Scene::LEVEL) { + if (!lua_onupdate(l_state->onupdate, dt)) { + window.close(); + } + } else if (scene == Scene::WIN) { + lua_onwin(l_state->onwin); + // window.close(); + } else if (scene == Scene::LOSS) { + lua_onloss(l_state->onloss); + } + + // Render + if (useViewport) { + // Reinitialize center view + if (scene == INTRO) { + if (useViewport) { + view.setCenter(to_position(lvl->player) + + sf::Vector2f(SPRITE_SIZE / 2.f, SPRITE_SIZE / 2.f)); + } else { + view.setCenter(view.getSize() / 2.f); + } + } + sf::Vector2f newPos = to_position(lvl->player) + // center + sf::Vector2f(SPRITE_SIZE / 2.f, SPRITE_SIZE / 2.f); + sf::Vector2f diff = + newPos - (lvl->player.sprite.getPosition() + + sf::Vector2f(SPRITE_SIZE / 2.f, SPRITE_SIZE / 2.f)); + + if (diff.x > 0.f && newPos.x > view.getSize().x / 2.f) { + view.move({diff.x, 0.f}); + } + if (diff.y > 0.f && newPos.y > view.getSize().y / 2.f) { + view.move({0.f, diff.y}); + } + if (diff.x < 0.f && newPos.x < view.getCenter().x) { + view.move({diff.x, 0.f}); + } + if (diff.y < 0.f && newPos.y < view.getCenter().y) { + view.move({0.f, diff.y}); + } + // readjust for OB + if (view.getCenter().x - view.getSize().x / 2.f < 0.f) { + view.move({(view.getCenter().x - view.getSize().x / 2.f) * -1.f, 0.f}); + } + if (view.getCenter().y - view.getSize().y / 2.f < 0.f) { + view.move({0.f, (view.getCenter().y - view.getSize().y / 2.f) * -1.f}); + } + if (view.getCenter().x + view.getSize().x / 2.f > mapBounds.x) { + view.move( + {-1 * ((view.getCenter().x + view.getSize().x / 2.f) - mapBounds.x), + 0.f}); + } + if (view.getCenter().y + view.getSize().y / 2.f > mapBounds.y) { + view.move({0.f, -1 * ((view.getCenter().y + view.getSize().y / 2.f) - + mapBounds.y)}); + } + } + + window.clear(BLANK_COLOR); + window.setView(view); + for (auto &rect : lvl->displayMap) { + window.draw(rect); + } + if (scene == Scene::LEVEL) { + for (auto &enemy : lvl->enemyPositions) { + enemy.sprite.setPosition(to_position(enemy)); + window.draw(enemy.sprite); + } + for (auto &treasure : lvl->treasurePositions) { + treasure.sprite.setPosition(to_position(treasure)); + window.draw(treasure.sprite); + } + } + + if (scene != Scene::LOSS) { + lvl->player.sprite.setPosition(to_position(lvl->player)); + window.draw(lvl->player.sprite); + } + if (scene == Scene::WIN) { + window.setView(window.getDefaultView()); + if (win.msgs.empty()) { + win = initializeMessageBox({{"You Win!", LARGE_TEXT}, + {"press [space] to restart", SMALL_TEXT}}, + font, window.getSize()); + } + + window.draw(win.box); + for (auto &msg : win.msgs) { + window.draw(msg); + } + window.setView(view); + } + if (scene == Scene::LOSS) { + window.setView(window.getDefaultView()); + if (loss.msgs.empty()) { + loss = initializeMessageBox({{"You Lose!", LARGE_TEXT}, + {"press [space] to restart", SMALL_TEXT}}, + font, window.getSize()); + } + + window.draw(loss.box); + for (auto &msg : loss.msgs) { + window.draw(msg); + } + window.setView(view); + } + if (scene == INTRO) { + window.setView(window.getDefaultView()); + if (intro.msgs.empty()) { + intro = initializeMessageBox( + {{"Start!", LARGE_TEXT}, {"press [space]", SMALL_TEXT}}, font, + window.getSize()); + } + + window.draw(intro.box); + for (auto &msg : intro.msgs) { + window.draw(msg); + } + window.setView(view); + } + window.setView(view); + window.display(); + } while (window.isOpen()); + + std::cout << "[C] Quit" << std::endl; + + return EXIT_SUCCESS; +} |