diff options
author | Steph Enders <smenders@gmail.com> | 2022-06-15 23:36:43 -0400 |
---|---|---|
committer | Steph Enders <smenders@gmail.com> | 2022-06-21 15:20:33 -0400 |
commit | 09615be926efb7302bc348aa66feccb694b23ba8 (patch) | |
tree | 0adf5eeb39312649c4adc288e051d5251428a2c2 /src |
v0.1.0 - Initial Commit
Create dng - a maze/puzzle game enging using Lua and SFML
---
Initial setup commit
This setups up the project for messing around with C++ and Lua bindings.
So far this project just prints the defined dng map and lets you move
the character around.
What this fails to do is actually provide any reason to use Lua at the
moment. So I need to figure out some way of enabling logic on the
processing side of things.
Fixup warning from IntelliJ
Added onUpdate logic to move the enemies etc
Created some algorithm logic for enemy movement
Allowed for default overrides
Made shortest path alg more efficent
In the previous commit this algo waited until the "success" step came up
in the queue. Now we have the check during the push - and if an hits we
return true from the push_moves fn.
Since we're only interested in the initial move (since we check moves
every frame) we can only return true and then use the current step as
the origin position to diff against the start to get the dx,dy
Add scene controls and win/loss scenarios
Setup ability to check collisions and transition game scene
Remove SFML for now
Create level 2
Fixup - Can now have levels without a proc.lua
Checked existence of wrong file for loading defaults
Add readme and ignore build files
Setup so it can build on debian
Reformat bill merge
Make installable
You can now do: `sudo make install` and have it publish and distribute
the dng lua files to the share dir
Bump version to 0.3.1
Opps forgot to unignore dng folder
Force local version if in current dir
This should allow development to ALWAYS prefer the local version of the
lib - so if you have an installed version it won't override local
changes
Set version to 0.3.2
Bump version to 0.3.3
Update to use SFML (for gameplay)
Intro/Win/Loss not supported yet
Remove install logic for now + make mov overrides
Allow for restart on win/loss
Add scroll viewport and max window sizes
Display win/loss (need restart view fix)
Fix recenter view on restart
v0.4.0 - SFML
Dynamic text rescale
This isn't the best since we rescale it every frame we render it on, we
should render all the text once and remember it.
If we want to support "resizing" we can do that in its own logic loop
MessageBox + Filesystem lookups for files
Created MessageBox which is a helper class to allow for printing any
dialog in a scalable way.
Added path overrides for lua files as well as the ability to seek the
filesystem for specific paths for the fonts and such.
Set version to 0.4.2
Creating submodules?
Added SFML as third party lib
Use git submodules for dependencies
Set license to zlib/png
Add thirdparty licenses and include in package
Set version to 0.1.0
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; +} |