├── .gitignore ├── CMakeLists.txt ├── CMakeSettings.json ├── LICENSE ├── README.md ├── assets ├── VGA8x16.png ├── background_image.png ├── fonts.txt ├── nyan.xp ├── terminal16x16.png ├── terminal32x32.png └── terminal8x8.png ├── cmake_modules ├── FindSFML.cmake └── Findcereal.cmake ├── examples ├── ex1 │ └── main.cpp ├── ex10 │ └── main.cpp ├── ex11 │ └── main.cpp ├── ex2 │ └── main.cpp ├── ex3 │ └── main.cpp ├── ex4 │ └── main.cpp ├── ex5 │ └── main.cpp ├── ex6 │ └── main.cpp ├── ex7 │ └── main.cpp ├── ex8 │ └── main.cpp └── ex9 │ └── main.cpp ├── rltk ├── astar.hpp ├── color_t.cpp ├── color_t.hpp ├── colors.hpp ├── ecs.cpp ├── ecs.hpp ├── ecs_impl.hpp ├── filesystem.hpp ├── font_manager.cpp ├── font_manager.hpp ├── fsa.hpp ├── geometry.cpp ├── geometry.hpp ├── gui.cpp ├── gui.hpp ├── gui_control_t.cpp ├── gui_control_t.hpp ├── input_handler.cpp ├── input_handler.hpp ├── layer_t.cpp ├── layer_t.hpp ├── path_finding.hpp ├── perlin_noise.cpp ├── perlin_noise.hpp ├── rexspeeder.cpp ├── rexspeeder.hpp ├── rltk.cpp ├── rltk.hpp ├── rng.cpp ├── rng.hpp ├── scaling.cpp ├── scaling.hpp ├── serialization_utils.hpp ├── texture.hpp ├── texture_resources.cpp ├── texture_resources.hpp ├── vchar.hpp ├── virtual_terminal.cpp ├── virtual_terminal.hpp ├── virtual_terminal_sparse.cpp ├── virtual_terminal_sparse.hpp ├── visibility.hpp ├── xml.cpp └── xml.hpp └── tutorial_images ├── example1.png ├── example10.gif ├── example11.png ├── example2.gif ├── example3.gif ├── example4.gif ├── example5.gif ├── example6.gif ├── example7.png ├── example8.gif └── example9.gif /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /.kdev4/ 3 | /bgame.kdev4 4 | /build_eclipse/ 5 | .DS_Store 6 | x64 7 | x84 8 | 9 | /RltkMaster/packages 10 | /RltkMaster/rltk/Debug 11 | /RltkMaster/rltk/SFML-2.3.2 12 | *.opendb 13 | /RltkMaster/.vs/RltkMaster/v14/.suo 14 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | project("rltk") 3 | 4 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake_modules") 5 | set(CMAKE_CXX_STANDARD 14) 6 | set(CMAKE_CXX_STANDARD_REQUIRED on) 7 | 8 | find_package(ZLIB REQUIRED) 9 | find_package(SFML 2 COMPONENTS system window graphics REQUIRED) 10 | find_package(cereal REQUIRED) 11 | 12 | add_library(rltk rltk/rltk.cpp 13 | rltk/texture_resources.cpp 14 | rltk/color_t.cpp 15 | rltk/virtual_terminal.cpp 16 | rltk/rng.cpp 17 | rltk/geometry.cpp 18 | rltk/input_handler.cpp 19 | rltk/font_manager.cpp 20 | rltk/gui.cpp 21 | rltk/layer_t.cpp 22 | rltk/gui_control_t.cpp 23 | rltk/virtual_terminal_sparse.cpp 24 | rltk/ecs.cpp 25 | rltk/xml.cpp 26 | rltk/perlin_noise.cpp 27 | rltk/rexspeeder.cpp 28 | rltk/scaling.cpp) 29 | target_include_directories(rltk PUBLIC 30 | "$" 31 | "$" 32 | "$" 33 | ) 34 | target_link_libraries(rltk PUBLIC ${ZLIB_LIBRARIES} ${SFML_LIBRARIES}) 35 | if(NOT MSVC) # Why was this here? I exempted the wierd linker flags 36 | target_compile_options(rltk PUBLIC -O3 -Wall -Wpedantic -march=native -mtune=native -g) 37 | else() 38 | target_compile_options(rltk PUBLIC /W3 /EHsc) 39 | endif() 40 | 41 | install (TARGETS rltk 42 | ARCHIVE DESTINATION lib 43 | LIBRARY DESTINATION lib 44 | RUNTIME DESTINATION bin) 45 | 46 | set(RLTK_HEADERS 47 | rltk/astar.hpp 48 | rltk/colors.hpp 49 | rltk/color_t.hpp 50 | rltk/ecs.hpp 51 | rltk/ecs_impl.hpp 52 | rltk/filesystem.hpp 53 | rltk/font_manager.hpp 54 | rltk/fsa.hpp 55 | rltk/geometry.hpp 56 | rltk/gui.hpp 57 | rltk/gui_control_t.hpp 58 | rltk/input_handler.hpp 59 | rltk/layer_t.hpp 60 | rltk/path_finding.hpp 61 | rltk/perlin_noise.hpp 62 | rltk/rexspeeder.hpp 63 | rltk/rltk.hpp 64 | rltk/rng.hpp 65 | rltk/scaling.hpp 66 | rltk/serialization_utils.hpp 67 | rltk/texture.hpp 68 | rltk/texture_resources.hpp 69 | rltk/vchar.hpp 70 | rltk/virtual_terminal.hpp 71 | rltk/virtual_terminal_sparse.hpp 72 | rltk/visibility.hpp 73 | rltk/xml.hpp) 74 | 75 | install(FILES ${RLTK_HEADERS} 76 | DESTINATION "include/rltk" 77 | ) 78 | 79 | # Examples 80 | 81 | # Add all of the example executables and their library dependency 82 | add_executable(ex1 examples/ex1/main.cpp) 83 | add_executable(ex2 examples/ex2/main.cpp) 84 | add_executable(ex3 examples/ex3/main.cpp) 85 | add_executable(ex4 examples/ex4/main.cpp) 86 | add_executable(ex5 examples/ex5/main.cpp) 87 | add_executable(ex6 examples/ex6/main.cpp) 88 | add_executable(ex7 examples/ex7/main.cpp) 89 | add_executable(ex8 examples/ex8/main.cpp) 90 | add_executable(ex9 examples/ex9/main.cpp) 91 | add_executable(ex10 examples/ex10/main.cpp) 92 | add_executable(ex11 examples/ex11/main.cpp) 93 | target_link_libraries(ex1 rltk) 94 | target_link_libraries(ex2 rltk) 95 | target_link_libraries(ex3 rltk) 96 | target_link_libraries(ex4 rltk) 97 | target_link_libraries(ex5 rltk) 98 | target_link_libraries(ex6 rltk) 99 | target_link_libraries(ex7 rltk) 100 | target_link_libraries(ex8 rltk) 101 | target_link_libraries(ex9 rltk) 102 | target_link_libraries(ex10 rltk) 103 | target_link_libraries(ex11 rltk) 104 | -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com//fwlink//?linkid=834763 for more information about this file. 3 | "configurations": [ 4 | { 5 | "name": "x86-Debug", 6 | "generator": "Visual Studio 15 2017", 7 | "configurationType": "Debug", 8 | "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}", 9 | "cmakeCommandArgs": "", 10 | "buildCommandArgs": "-m -v:minimal", 11 | "ctestCommandArgs": "", 12 | "variables": [ 13 | { 14 | "name": "CMAKE_TOOLCHAIN_FILE", 15 | "value": "C:/Users/Herbert/Source/Repos/vcpkg/scripts/buildsystems/vcpkg.cmake" 16 | } 17 | ] 18 | }, 19 | { 20 | "name": "x86-Release", 21 | "generator": "Visual Studio 15 2017", 22 | "configurationType": "Release", 23 | "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}", 24 | "cmakeCommandArgs": "", 25 | "buildCommandArgs": "-m -v:minimal", 26 | "ctestCommandArgs": "", 27 | "variables": [ 28 | { 29 | "name": "CMAKE_TOOLCHAIN_FILE", 30 | "value": "C:/Users/Herbert/Source/Repos/vcpkg/scripts/buildsystems/vcpkg.cmake" 31 | } 32 | ] 33 | }, 34 | { 35 | "name": "x64-Debug", 36 | "generator": "Visual Studio 15 2017 Win64", 37 | "configurationType": "Debug", 38 | "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}", 39 | "cmakeCommandArgs": "", 40 | "buildCommandArgs": "-m -v:minimal", 41 | "ctestCommandArgs": "", 42 | "variables": [ 43 | { 44 | "name": "CMAKE_TOOLCHAIN_FILE", 45 | "value": "C:/Users/Herbert/Source/Repos/vcpkg/scripts/buildsystems/vcpkg.cmake" 46 | } 47 | ] 48 | }, 49 | { 50 | "name": "x64-Release", 51 | "generator": "Visual Studio 15 2017 Win64", 52 | "configurationType": "Release", 53 | "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}", 54 | "cmakeCommandArgs": "", 55 | "buildCommandArgs": "-m -v:minimal", 56 | "ctestCommandArgs": "", 57 | "variables": [ 58 | { 59 | "name": "CMAKE_TOOLCHAIN_FILE", 60 | "value": "C:/Users/Herbert/Source/Repos/vcpkg/scripts/buildsystems/vcpkg.cmake" 61 | } 62 | ] 63 | } 64 | ] 65 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Bracket Productions (Herbert Wolverson) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rltk : Roguelike Toolkit - Modern (C++14) SFML-based toolkit for creating roguelikes. 2 | 3 | It's very early days, but I hope that this can be a useful tool. Black Future was getting messy, and I needed to separate out some 4 | engine logic from game logic for my sanity; I realized that others might find the underlying engine code useful. 5 | 6 | Right now, it's a very fast ASCII (code-page 437) terminal renderer compatible with fonts from libtcod and Dwarf Fortress. 7 | Eventually, it will provide assistance with a number of game-related topics including path-finding, line-plotting, 8 | and probably some of the entity-component-system I've been enjoying. 9 | 10 | **Credit to Pyridine for the REXPaint format code. Original located at: [https://github.com/pyridine/REXSpeeder](https://github.com/pyridine/REXSpeeder)** 11 | 12 | ## Building from source 13 | 14 | You need SFML for your platform, Boost, and cmake. Make a "build" folder, and use CMake to generate build files for your platform (I'll expand upon this later, when this is a more useful library). 15 | 16 | ## Building on Visual Studio 2017 17 | 18 | * Setup VCPKG, following the instructions [here](https://blogs.msdn.microsoft.com/vcblog/2016/09/19/vcpkg-a-tool-to-acquire-and-build-c-open-source-libraries-on-windows/) 19 | * Ensure that you've integrated it, with `vcpkg integrate install` (see [here](https://github.com/Microsoft/vcpkg/blob/master/docs/examples/using-sqlite.md)) 20 | * Install packages: `vcpkg install sfml` and `vcpkg install cereal`. These take a while. 21 | * Open Visual Studio 2017, and use "Open Folder" to open the RLTK folder to which you cloned everything. The CVPKG stuff will ensure that SFML is linked correctly. 22 | * If you've previously opened the project in VS2017, use the CMake menu to delete your cache and regenerate everything. You really shouldn't have to do this, but CMake integration is young. 23 | * You should now be able to select an output, and build/run it. 24 | 25 | ## Included Examples (with lots of comments!) 26 | 27 | I'll write proper documentation as the library evolves; I don't really want to write up a lot of docs and have to revise them 28 | heavily as things solidify. I'm doing my best to include examples evolving towards a real roguelike. Currently, these are: 29 | 30 | ### Example 1: Hello World 31 | ![Hello World](https://raw.githubusercontent.com/thebracket/rltk/master/tutorial_images/example1.png "Hello World") 32 | 33 | [Example 1](https://github.com/thebracket/rltk/blob/master/examples/ex1/main.cpp): demonstrates a Hello World with frame-rate. 34 | 35 | ### Example 2: Randomly moving @ 36 | ![Randomly Moving @](https://raw.githubusercontent.com/thebracket/rltk/master/tutorial_images/example2.gif "Randomly Moving @") 37 | 38 | [Example 2](https://github.com/thebracket/rltk/blob/master/examples/ex2/main.cpp): a randomly moving yellow @ on a field of white dots. 39 | 40 | ### Example 3: Bresenham's Line Pathing 41 | ![Bresenham Line Pathing](https://raw.githubusercontent.com/thebracket/rltk/master/tutorial_images/example3.gif "Bresenham Line Pathing") 42 | 43 | [Example 3](https://github.com/thebracket/rltk/blob/master/examples/ex3/main.cpp): our @ dude again, this time using Bresenham's line to find his way. It also renders additional glyphs, as Mr @ finds his way to his destination. 44 | 45 | ### Example 4: A-Star Pathing 46 | ![A Star Pathing](https://raw.githubusercontent.com/thebracket/rltk/master/tutorial_images/example4.gif "A Star Pathing") 47 | 48 | [Example 4](https://github.com/thebracket/rltk/blob/master/examples/ex4/main.cpp): our @ dude again, now on a defined map with obstacles and using A* to find his way to the heart. This demonstrates using templates to specialize map support - we won't force you to use a specific map representation! 49 | 50 | ### Example 5: Mouse Controlled Path-Finding 51 | ![Mouse Driven A Star Pathing](https://raw.githubusercontent.com/thebracket/rltk/master/tutorial_images/example5.gif "Mouse Driven A Star Pathing") 52 | 53 | [Example 5](https://github.com/thebracket/rltk/blob/master/examples/ex5/main.cpp): our @ dude again, using A* pathing to find his way to the mouse 54 | cursor. Click and he goes there. 55 | 56 | ### Example 6: Visibility 57 | ![Visibility](https://raw.githubusercontent.com/thebracket/rltk/master/tutorial_images/example6.gif "Visibility") 58 | 59 | [Example 6](https://github.com/thebracket/rltk/blob/master/examples/ex6/main.cpp): Example 5, but now we have true visibility plotted as you wander. 60 | 61 | ### Example 7: Multi-font ASCII Layout 62 | ![ComplexGui](https://raw.githubusercontent.com/thebracket/rltk/master/tutorial_images/example7.png "Complex GUI") 63 | 64 | [Example 7](https://github.com/thebracket/rltk/blob/master/examples/ex7/main.cpp): A complex GUI with multiple fonts and layers, dynamically resizable. 65 | 66 | ### Example 8: Owner draw, and retained-mode GUI elements 67 | ![RetainedMode](https://raw.githubusercontent.com/thebracket/rltk/master/tutorial_images/example8.gif "Retained Mode") 68 | 69 | [Example 8](https://github.com/thebracket/rltk/blob/master/examples/ex8/main.cpp): Demonstrates an "owner draw" panel (with SFML render target callback), drawing a background image. Some ASCII consoles are spawned, and one is populated with a mouse-over, a checkbox, radio-button set, a list-box and some status bars. The other panel displays the results. 70 | 71 | ### Example 9: Sparse layer with effects 72 | ![Sparse](https://raw.githubusercontent.com/thebracket/rltk/master/tutorial_images/example9.gif "Sparse") 73 | 74 | [Example 9](https://github.com/thebracket/rltk/blob/master/examples/ex9/main.cpp): This demo uses a regular console layer to draw the map, 75 | and a "sparse" console layer for the character and traversal path. It uses sub-character alignment to smoothly move the @ around, and 76 | demonstrates rotation of the @ by leaning left or right as he travels (not an effect I recommend for a game, but it works as a demo!). 77 | 78 | ### Example 10: The beginnings of a roguelike 79 | ![Sparse](https://raw.githubusercontent.com/thebracket/rltk/master/tutorial_images/example10.gif "RogueBeginnings") 80 | 81 | [Example 10](https://github.com/thebracket/rltk/blob/master/examples/ex10/main.cpp): This example generates a random map, and you can move your @ around with the arrow keys. It is the first example to use the Entity-Component-System (ECS) provided by RLTK; it makes for a relatively straightforward and modern way to design a roguelike - with very little code, we have the basics of wandering around a map. Note: message passing isn't implemented yet; when it is - this example will be even smaller! 82 | 83 | ### Example 11: REXPaint support (http://www.gridsagegames.com/rexpaint/) 84 | ![RexPaint](https://raw.githubusercontent.com/thebracket/rltk/master/tutorial_images/example11.png "RexPaint") 85 | 86 | [Example 11](https://github.com/thebracket/rltk/blob/master/examples/ex11/main.cpp): This example is basically Hello World, but with a REX Paint image loaded (Nyan Cat) and displayed. 87 | 88 | 89 | ## Example 90 | The goal is to keep it simple from the user's point of view. The following code is enough to setup an ASCII terminal, 91 | and display **Hello World** with a frame-rate displayed (around 100 FPS on my workstation): 92 | 93 | ```c++ 94 | #include "../../rltk/rltk.hpp" 95 | #include 96 | 97 | using namespace rltk; 98 | using namespace rltk::colors; 99 | 100 | void tick(double duration_ms) { 101 | std::stringstream ss; 102 | ss << "Frame duration: " << duration_ms << " ms (" << (1000.0/duration_ms) << " FPS)."; 103 | console->print(1, 1, "Hello World", WHITE, BLACK); 104 | console->print(1, 2, ss.str(), YELLOW, BLUE); 105 | } 106 | 107 | int main() 108 | { 109 | init(config_simple_px("../assets")); 110 | run(tick); 111 | return 0; 112 | } 113 | ``` 114 | -------------------------------------------------------------------------------- /assets/VGA8x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/rltk/58635419d2488dca8e8d7fd2e29b4ba144300cff/assets/VGA8x16.png -------------------------------------------------------------------------------- /assets/background_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/rltk/58635419d2488dca8e8d7fd2e29b4ba144300cff/assets/background_image.png -------------------------------------------------------------------------------- /assets/fonts.txt: -------------------------------------------------------------------------------- 1 | 8x8,terminal8x8.png,8,8 2 | 16x16,terminal16x16.png,16,16 3 | 32x32,terminal32x32.png,32,32 4 | 8x16,VGA8x16.png,8,16 5 | 6 | -------------------------------------------------------------------------------- /assets/nyan.xp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/rltk/58635419d2488dca8e8d7fd2e29b4ba144300cff/assets/nyan.xp -------------------------------------------------------------------------------- /assets/terminal16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/rltk/58635419d2488dca8e8d7fd2e29b4ba144300cff/assets/terminal16x16.png -------------------------------------------------------------------------------- /assets/terminal32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/rltk/58635419d2488dca8e8d7fd2e29b4ba144300cff/assets/terminal32x32.png -------------------------------------------------------------------------------- /assets/terminal8x8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/rltk/58635419d2488dca8e8d7fd2e29b4ba144300cff/assets/terminal8x8.png -------------------------------------------------------------------------------- /cmake_modules/Findcereal.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find Cereal lib 2 | # 3 | # This sets the following variables: 4 | # CEREAL_FOUND - True if Cereal was found. 5 | # CEREAL_INCLUDE_DIRS - Directories containing the Cereal include files. 6 | 7 | 8 | find_path(CEREAL_INCLUDE_DIRS cereal 9 | HINTS "$ENV{CMAKE_SOURCE_DIR}/include" "/usr/include" "$ENV{CMAKE_BINARY_DIR}/cereal/include") 10 | 11 | set(CEREAL_INCLUDE_DIRS ${CEREAL_INCLUDE_DIRS}) 12 | 13 | include(FindPackageHandleStandardArgs) 14 | find_package_handle_standard_args(Cereal DEFAULT_MSG CEREAL_INCLUDE_DIRS) 15 | 16 | mark_as_advanced(CEREAL_INCLUDE_DIRS) 17 | 18 | if(CEREAL_FOUND) 19 | MESSAGE(STATUS "Found Cereal: ${CEREAL_INCLUDE_DIRS}") 20 | endif(CEREAL_FOUND) 21 | 22 | -------------------------------------------------------------------------------- /examples/ex1/main.cpp: -------------------------------------------------------------------------------- 1 | /* RLTK (RogueLike Tool Kit) 1.00 2 | * Copyright (c) 2016-Present, Bracket Productions. 3 | * Licensed under the MIT license - see LICENSE file. 4 | * 5 | * Example 1: A truly minimal hello world root console, demonstrating 6 | * how to get started with RLTK. 7 | */ 8 | 9 | // You need to include the RLTK header 10 | #include "../../rltk/rltk.hpp" 11 | 12 | // We're using a stringstream to build the hello world message. 13 | #include 14 | 15 | // For convenience, import the whole rltk namespace. You may not want to do this 16 | // in larger projects, to avoid naming collisions. 17 | using namespace rltk; 18 | using namespace rltk::colors; 19 | 20 | // Tick is called every frame. The parameter specifies how many ms have elapsed 21 | // since the last time it was called. 22 | void tick(double duration_ms) { 23 | // In this case, we just want to print "Hello World" in white on black. 24 | if (console->dirty) { 25 | console->clear(); 26 | console->print(1,1,"Hello World", WHITE, BLACK); 27 | } 28 | } 29 | 30 | // Your main function 31 | int main() 32 | { 33 | // Initialize the library. Here, we are providing plenty of into so you can see what is 34 | // available. There's also the option to use config_simple_px to configure by pixels 35 | // rather than characters. 36 | // The first parameter is the path to the font files. 37 | // The second and third parameters specify the desired console size in screen characters (80x25). 38 | // The fourth parameter is the window title. 39 | // The fifth parameter says that we'd like the default console to use an 8x16 VGA font. Not so great for games, but easy to read! 40 | // The final parameter controls whether or not we want to go full screen. 41 | init(config_simple("../assets", 80, 25, "RLTK Hello World", "8x16", false)); 42 | 43 | // Enter the main loop. "tick" is the function we wrote above. 44 | run(tick); 45 | 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /examples/ex10/main.cpp: -------------------------------------------------------------------------------- 1 | /* RLTK (RogueLike Tool Kit) 1.00 2 | * Copyright (c) 2016-Present, Bracket Productions. 3 | * Licensed under the MIT license - see LICENSE file. 4 | * 5 | * Example 10: Not really an example yet, playing with getting the ECS working. 6 | */ 7 | 8 | // You need to include the RLTK header 9 | #include "../../rltk/rltk.hpp" 10 | 11 | // We're using a stringstream to build the hello world message. 12 | #include 13 | #include 14 | 15 | // For convenience, import the whole rltk namespace. You may not want to do this 16 | // in larger projects, to avoid naming collisions. 17 | using namespace rltk; 18 | using namespace rltk::colors; 19 | using std::size_t; 20 | 21 | constexpr int map_width = 100; 22 | constexpr int map_height = 100; 23 | constexpr int map_size = map_width * map_height; 24 | int map_idx(const int x, const int y) { return (y * map_width) + x; } 25 | std::vector map_tiles; 26 | std::vector visible; 27 | std::vector revealed; 28 | random_number_generator rng; 29 | 30 | size_t player_id; 31 | 32 | struct position { 33 | position() {} 34 | position(const int X, const int Y) : x(X), y(Y) {} 35 | 36 | int x, y; 37 | 38 | void bounds_check() { 39 | if (x < 0) x = 0; 40 | if (x > map_width) x = map_width; 41 | if (y < 0) y = 0; 42 | if (y > map_height) y = map_height; 43 | } 44 | }; 45 | 46 | struct renderable { 47 | renderable() {} 48 | renderable(const char Glyph, const color_t foreground) : glyph(Glyph), fg(foreground) {} 49 | int glyph; 50 | color_t fg=colors::WHITE; 51 | color_t bg=colors::BLACK; 52 | }; 53 | 54 | struct navigator_helper { 55 | static int get_x(const position &loc) { return loc.x; } 56 | static int get_y(const position &loc) { return loc.y; } 57 | static position get_xy(const int &x, const int &y) { return position{x,y}; } 58 | }; 59 | 60 | // Clipping info 61 | int left_x, right_x, top_y, bottom_y; 62 | 63 | struct actor_moved_message : base_message_t { 64 | actor_moved_message() {} 65 | actor_moved_message(entity_t * ACTOR, const int fx, const int fy, const int dx, const int dy) : 66 | mover(ACTOR), from_x(fx), from_y(fy), destination_x(dx), destination_y(dy) {} 67 | 68 | entity_t * mover; 69 | int from_x, from_y, destination_x, destination_y; 70 | }; 71 | 72 | struct player_moved_message : base_message_t {}; 73 | 74 | struct camera_system : public base_system { 75 | virtual void configure() override { 76 | system_name = "Camera System"; 77 | // Create the player 78 | auto player = create_entity() 79 | ->assign(position{map_width/2, map_height/2}) 80 | ->assign(renderable('@', colors::YELLOW)); 81 | player_id = player->id; 82 | } 83 | 84 | virtual void update(const double duration_ms) override { 85 | if (console->dirty) { 86 | console->clear(); 87 | 88 | // Find the camera 89 | auto camera_loc = entity(player_id)->component(); 90 | left_x = camera_loc->x - (console->term_width / 2); 91 | right_x = camera_loc->x + (console->term_width / 2); 92 | top_y = camera_loc->y - (console->term_height/2); 93 | bottom_y = camera_loc->y + (console->term_height/2)+1; 94 | 95 | for (int y=top_y; y= 0 && x < map_width && y >= 0 && y < map_height) { 98 | vchar map_char{'.', color_t(0,0,64), colors::BLACK}; 99 | const int map_index = map_idx(x,y); 100 | 101 | if (revealed[map_index]) { 102 | switch (map_tiles[map_index]) { 103 | case 0 : map_char.glyph = '.'; break; 104 | case 1 : map_char.glyph = '#'; break; 105 | default : map_char.glyph = 'E'; // This shouldn't happen 106 | } 107 | map_char.foreground = color_t(96,96,96); 108 | } 109 | if (visible[map_index] > 0) { 110 | uint8_t brightness =(visible[map_index]*16) + 127; 111 | map_char.foreground = color_t(brightness, brightness, brightness); 112 | } else { 113 | if (map_tiles[map_index] == 0) map_char.glyph = ' '; 114 | } 115 | console->set_char(x-left_x, y-top_y, map_char); 116 | } 117 | } 118 | } 119 | } 120 | } 121 | }; 122 | 123 | struct actor_render_system : public base_system { 124 | virtual void configure() override { 125 | system_name = "Actor Render System"; 126 | } 127 | 128 | virtual void update(const double duration_ms) override { 129 | each([] (entity_t &entity, position &pos, renderable &render) { 130 | const int map_index = map_idx(pos.x, pos.y); 131 | if (visible[map_index]) { 132 | console->set_char(pos.x-left_x, pos.y-top_y, vchar{ render.glyph, render.fg, render.bg }); 133 | } 134 | }); 135 | } 136 | }; 137 | 138 | struct player_system : public base_system { 139 | virtual void configure() override { 140 | system_name = "Player System"; 141 | subscribe([this](actor_moved_message &msg) { 142 | if (map_tiles[map_idx(msg.destination_x, msg.destination_y)] == 0) { 143 | msg.mover->component()->x = msg.destination_x; 144 | msg.mover->component()->y = msg.destination_y; 145 | msg.mover->component()->bounds_check(); 146 | console->dirty = true; 147 | emit(player_moved_message{}); 148 | } 149 | }); 150 | 151 | subscribe_mbox(); 152 | } 153 | 154 | virtual void update(const double duration_ms) override { 155 | auto camera_loc = entity(player_id)->component(); 156 | 157 | // Loop through the keyboard input list 158 | std::queue * messages = mbox(); 159 | while (!messages->empty()) { 160 | key_pressed_t e = messages->front(); 161 | messages->pop(); 162 | 163 | if (e.event.key.code == sf::Keyboard::Left) emit(actor_moved_message{ entity(player_id), camera_loc->x, camera_loc->y, camera_loc->x - 1, camera_loc->y }); 164 | if (e.event.key.code == sf::Keyboard::Right) emit(actor_moved_message{ entity(player_id), camera_loc->x, camera_loc->y, camera_loc->x + 1, camera_loc->y }); 165 | if (e.event.key.code == sf::Keyboard::Up) emit(actor_moved_message{ entity(player_id), camera_loc->x, camera_loc->y, camera_loc->x, camera_loc->y-1 }); 166 | if (e.event.key.code == sf::Keyboard::Down) emit(actor_moved_message{ entity(player_id), camera_loc->x, camera_loc->y, camera_loc->x, camera_loc->y+1 }); 167 | if (e.event.key.code == sf::Keyboard::F1) { 168 | std::string timings = ecs_profile_dump(); 169 | std::cout << timings << "\n"; 170 | } 171 | } 172 | } 173 | }; 174 | 175 | struct visibility_system : public base_system { 176 | virtual void configure() override { 177 | system_name = "Visibility System"; 178 | subscribe([this](player_moved_message &msg) { 179 | auto camera_loc = entity(player_id)->component(); 180 | position camera_loc_deref = *camera_loc; 181 | 182 | std::fill(visible.begin(), visible.end(), 0); 183 | visibility_sweep_2d(camera_loc_deref, 10, 184 | [](position reveal) { 185 | reveal.bounds_check(); 186 | const int idx = map_idx(reveal.x, reveal.y); 187 | ++visible[idx]; 188 | if (visible[idx] > 8) visible[idx] = 8; 189 | revealed[idx] = true; 190 | }, [](position visibility_check) { 191 | visibility_check.bounds_check(); 192 | return (map_tiles[map_idx(visibility_check.x, visibility_check.y)] == 0); 193 | }); 194 | }); 195 | } 196 | 197 | bool firstrun = true; 198 | 199 | virtual void update(const double duration_ms) override { 200 | if (firstrun) { 201 | emit(player_moved_message{}); 202 | firstrun = false; 203 | } 204 | } 205 | }; 206 | 207 | // Tick is called every frame. The parameter specifies how many ms have elapsed 208 | // since the last time it was called. 209 | void tick(double duration_ms) { 210 | ecs_tick(duration_ms); 211 | } 212 | 213 | // Your main function 214 | int main() 215 | { 216 | // Initialize the library. Here, we are providing plenty of into so you can see what is 217 | // available. There's also the option to use config_simple_px to configure by pixels 218 | // rather than characters. 219 | // The first parameter is the path to the font files. 220 | // The second and third parameters specify the desired console size in screen characters (80x25). 221 | // The fourth parameter is the window title. 222 | // The final parameter says that we'd like the default console to use an 8x16 VGA font. Not so great for games, but easy to read! 223 | init(config_simple("../assets", 80, 50, "RLTK Hello World", "8x8")); 224 | 225 | // Zero the map other than the edges 226 | map_tiles.resize(map_size); 227 | visible.resize(map_size); 228 | revealed.resize(map_size); 229 | std::fill(map_tiles.begin(), map_tiles.end(), 0); 230 | std::fill(visible.begin(), visible.end(), false); 231 | std::fill(revealed.begin(), revealed.end(), false); 232 | for (int i=0; i(); 249 | add_system(); 250 | add_system(); 251 | add_system(); 252 | 253 | // Enter the main loop. "tick" is the function we wrote above. 254 | ecs_configure(); 255 | run(tick); 256 | 257 | return 0; 258 | } 259 | -------------------------------------------------------------------------------- /examples/ex11/main.cpp: -------------------------------------------------------------------------------- 1 | /* RLTK (RogueLike Tool Kit) 1.00 2 | * Copyright (c) 2016-Present, Bracket Productions. 3 | * Licensed under the MIT license - see LICENSE file. 4 | * 5 | * Example 11: Hello World and a REX Paint object 6 | */ 7 | 8 | // You need to include the RLTK header 9 | #include "../../rltk/rltk.hpp" 10 | 11 | // We're using a stringstream to build the hello world message. 12 | #include 13 | 14 | // For convenience, import the whole rltk namespace. You may not want to do this 15 | // in larger projects, to avoid naming collisions. 16 | using namespace rltk; 17 | using namespace rltk::colors; 18 | 19 | rltk::xp::rex_sprite nyan_cat("../assets/nyan.xp"); 20 | 21 | // Tick is called every frame. The parameter specifies how many ms have elapsed 22 | // since the last time it was called. 23 | void tick(double duration_ms) { 24 | // In this case, we just want to print "Hello World" in white on black. 25 | if (console->dirty) { 26 | console->clear(); 27 | console->print(1,1,"Hello World", WHITE, BLACK); 28 | console->draw_sprite(1,3,nyan_cat); 29 | } 30 | } 31 | 32 | // Your main function 33 | int main() 34 | { 35 | // Initialize the library. Here, we are providing plenty of into so you can see what is 36 | // available. There's also the option to use config_simple_px to configure by pixels 37 | // rather than characters. 38 | // The first parameter is the path to the font files. 39 | // The second and third parameters specify the desired console size in screen characters (80x25). 40 | // The fourth parameter is the window title. 41 | // The final parameter says that we'd like the default console to use an 8x16 VGA font. Not so great for games, but easy to read! 42 | init(config_simple("../assets", 80, 25, "RLTK Hello World", "8x16")); 43 | 44 | // Enter the main loop. "tick" is the function we wrote above. 45 | run(tick); 46 | 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /examples/ex2/main.cpp: -------------------------------------------------------------------------------- 1 | /* RLTK (RogueLike Tool Kit) 1.00 2 | * Copyright (c) 2016-Present, Bracket Productions. 3 | * Licensed under the MIT license - see LICENSE file. 4 | * 5 | * Example 2: A wandering @ dude, demonstrating the random number generator, 6 | * character rendering, directed screen clearing, and frame-rate independent 7 | * tick lengths. 8 | */ 9 | 10 | // You need to include the RLTK header 11 | #include "../../rltk/rltk.hpp" 12 | 13 | // For convenience, import the whole rltk namespace. You may not want to do this 14 | // in larger projects, to avoid naming collisions. 15 | using namespace rltk; 16 | using namespace rltk::colors; 17 | 18 | // For now, we always want our "dude" to be a yellow @ - so he's constexpr 19 | const vchar dude{'@', YELLOW, BLACK}; 20 | // The dude's location in X/Y terms 21 | int dude_x = 10; 22 | int dude_y = 10; 23 | 24 | // A default-defined random number generator. You can specify a seed to get 25 | // the same results each time, but for now we're keeping it simple. 26 | random_number_generator rng; 27 | 28 | // We want to keep the game running at a steady pace, rather than however 29 | // fast our super graphics card wants to go! To do this, we set a constant 30 | // duration for a "tick" and only process after that much time has elapsed. 31 | // Most roguelikes are turn-based and won't actually use this, but that's 32 | // for a later example when we get to input. 33 | constexpr double tick_duration = 5.0; 34 | double tick_time = 0.0; 35 | 36 | // Tick is called every frame. The parameter specifies how many ms have elapsed 37 | // since the last time it was called. 38 | void tick(double duration_ms) { 39 | // Rather than clearing the screen to black, we set it to all white dots. We only want 40 | // to do this if something has forced the screen to re-render (such as re-sizing) 41 | if (console->dirty) console->clear(vchar{'.', GREY, BLACK}); 42 | 43 | // Increase the tick time by the frame duration. If it has exceeded 44 | // the tick duration, then we move the @. 45 | tick_time += duration_ms; 46 | if (tick_time > tick_duration) { 47 | 48 | // Using the RNG's handy roll_dice function, we roll 1d4. Each option 49 | // represents a possible move in this case. The function is just like D&D's 50 | // venerable XdY system - you can roll 10d12 with roll_dice(10,12). You 51 | // aren't limited to dice sizes that exist or make sense. 52 | int direction = rng.roll_dice(1,4); 53 | switch (direction) { 54 | case 1 : --dude_x; break; 55 | case 2 : ++dude_x; break; 56 | case 3 : --dude_y; break; 57 | case 4 : ++dude_y; break; 58 | } 59 | 60 | // Important: we clear the tick count after the update. 61 | tick_time = 0.0; 62 | 63 | // Clear the console, which has the nice side effect of setting the terminal 64 | // to dirty. 65 | console->clear(vchar{'.', GREY, BLACK}); 66 | // Clipping: keep the dude on the screen. Why are we doing this here, and not 67 | // after an update? For now, we aren't handling the concept of a map that is larger 68 | // than the screen - so if the window resizes, the @ gets clipped to a visible area. 69 | if (dude_x < 0) dude_x = 0; 70 | if (dude_x > console->term_width) dude_x = console->term_width; 71 | if (dude_y < 0) dude_y = 0; 72 | if (dude_y > console->term_height) dude_x = console->term_height; 73 | 74 | // Finally, we render the @ symbol. dude_x and dude_y are in terminal coordinates. 75 | console->set_char(console->at(dude_x, dude_y), dude); 76 | } 77 | } 78 | 79 | // Your main function 80 | int main() 81 | { 82 | // Initialize with defaults. 83 | init(config_simple_px("../assets")); 84 | 85 | // Enter the main loop. "tick" is the function we wrote above. 86 | run(tick); 87 | 88 | return 0; 89 | } 90 | -------------------------------------------------------------------------------- /examples/ex3/main.cpp: -------------------------------------------------------------------------------- 1 | /* RLTK (RogueLike Tool Kit) 1.00 2 | * Copyright (c) 2016-Present, Bracket Productions. 3 | * Licensed under the MIT license - see LICENSE file. 4 | * 5 | * Example 3: A wandering @ dude, this time using Bresenham's line functions to 6 | * plot his path through the world. We play around a bit, rendering the destination 7 | * and path as well as the simple "world" our little @ lives in. 8 | */ 9 | 10 | // You need to include the RLTK header 11 | #include "../../rltk/rltk.hpp" 12 | 13 | // We're using a deque to represent our path 14 | #include 15 | 16 | // For convenience, import the whole rltk namespace. You may not want to do this 17 | // in larger projects, to avoid naming collisions. 18 | using namespace rltk; 19 | using namespace rltk::colors; 20 | 21 | // For now, we always want our "dude" to be a yellow @ - so he's constexpr 22 | const vchar dude{'@', YELLOW, BLACK}; 23 | // We're also going to render our destination as a pink heart. Aww. 24 | const vchar destination{3, MAGENTA, BLACK}; 25 | // We'll also render our planned path ahead as a series of stars 26 | const vchar star{'*', GREEN, BLACK}; 27 | // The dude's location in X/Y terms 28 | int dude_x = 10; 29 | int dude_y = 10; 30 | 31 | // Where the dude would like to go; we'll start with him being happy where he is. 32 | int destination_x = 10; 33 | int destination_y = 10; 34 | 35 | // We'll store the path to the goal as a simple queue of x/y (represented as an std::pair of ints). 36 | // A queue naturally represents the task - each step, in order. A deque has the added property 37 | // of being iteratable - so we are using it. 38 | std::deque> path; 39 | 40 | // A default-defined random number generator. You can specify a seed to get 41 | // the same results each time, but for now we're keeping it simple. 42 | random_number_generator rng; 43 | 44 | // We want to keep the game running at a steady pace, rather than however 45 | // fast our super graphics card wants to go! To do this, we set a constant 46 | // duration for a "tick" and only process after that much time has elapsed. 47 | // Most roguelikes are turn-based and won't actually use this, but that's 48 | // for a later example when we get to input. 49 | // Note that this is faster than previous examples; I liked it better that way! 50 | constexpr double tick_duration = 5.0; 51 | double tick_time = 0.0; 52 | 53 | // Tick is called every frame. The parameter specifies how many ms have elapsed 54 | // since the last time it was called. 55 | void tick(double duration_ms) { 56 | // Rather than clearing the screen to black, we set it to all white dots. 57 | console->clear(vchar{'.', GREY, BLACK}); 58 | 59 | // Increase the tick time by the frame duration. If it has exceeded 60 | // the tick duration, then we move the @. 61 | tick_time += duration_ms; 62 | if (tick_time > tick_duration) { 63 | 64 | // If we're at our destination, we need a new one! 65 | if ((destination_x == dude_x && destination_y == dude_y) || path.empty()) { 66 | // We use the RNG to determine where we want to go 67 | destination_x = rng.roll_dice(1, console->term_width)-1; 68 | destination_y = rng.roll_dice(1, console->term_height)-1; 69 | 70 | // Now we use "line_func". The prototype for this is: 71 | // void line_func(int x1, int y1, const int x2, const int y2, std::function func); 72 | // What this means in practice is line_func(from_x, from_y, to_x, to_y, callback function for each step). 73 | // We'll use a lambda for the callback, to keep it inline and tight. 74 | line_func(dude_x, dude_y, destination_x, destination_y, [] (int nx, int ny) { 75 | // Simply add the next step to the path 76 | path.push_back(std::make_pair(nx,ny)); 77 | }); 78 | } else { 79 | // We aren't there yet, so we follow our path. We take the first element on the list, 80 | // and then use pop_back to remove it. 81 | // std::tie is a handy way to extract two parts of an std::pair (or tuple) in one fell swoop. 82 | std::tie(dude_x, dude_y) = path.front(); 83 | path.pop_front(); 84 | } 85 | 86 | 87 | // Important: we clear the tick count after the update. 88 | tick_time = 0.0; 89 | } 90 | 91 | // Clipping: keep the dude on the screen. Why are we doing this here, and not 92 | // after an update? For now, we aren't handling the concept of a map that is larger 93 | // than the screen - so if the window resizes, the @ gets clipped to a visible area. 94 | if (dude_x < 0) dude_x = 0; 95 | if (dude_x > console->term_width) dude_x = console->term_width; 96 | if (dude_y < 0) dude_y = 0; 97 | if (dude_y > console->term_height) dude_x = console->term_height; 98 | 99 | // Render our planned path. We're using auto and a range-for to avoid typing all 100 | // the iterator stuff 101 | for (auto step : path) { 102 | console->set_char(console->at(step.first, step.second), star); 103 | } 104 | 105 | // Render our destination 106 | console->set_char(console->at(destination_x, destination_y), destination); 107 | // Finally, we render the @ symbol. dude_x and dude_y are in terminal coordinates. 108 | console->set_char(console->at(dude_x, dude_y), dude); 109 | } 110 | 111 | // Your main function 112 | int main() 113 | { 114 | // Initialize with defaults. 115 | init(config_simple_px("../assets")); 116 | 117 | // Enter the main loop. "tick" is the function we wrote above. 118 | run(tick); 119 | 120 | return 0; 121 | } 122 | -------------------------------------------------------------------------------- /examples/ex4/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | /* RLTK (RogueLike Tool Kit) 1.00 3 | * Copyright (c) 2016-Present, Bracket Productions. 4 | * Licensed under the MIT license - see LICENSE file. 5 | * 6 | * Example 4: Now we implement a basic map, and use A* to find our way around it. 7 | * This example is a bit more in-depth, since it demonstrates the library's ability 8 | * to use templates to specialize itself around your map design - we won't force a 9 | * map type on you! 10 | */ 11 | 12 | // You need to include the RLTK header 13 | #include "../../rltk/rltk.hpp" 14 | 15 | // We're using a vector to represent the map 16 | #include 17 | 18 | // We're also going to be using a shared_ptr to a map. Why shared? Because the library 19 | // hands it off to you and it's up to you to use it; this provides some safety that it 20 | // will be disposed when you are done with it. 21 | #include 22 | 23 | // For convenience, import the whole rltk namespace. You may not want to do this 24 | // in larger projects, to avoid naming collisions. 25 | using namespace rltk; 26 | using namespace rltk::colors; 27 | 28 | // A default-defined random number generator. You can specify a seed to get 29 | // the same results each time, but for now we're keeping it simple. 30 | random_number_generator rng; 31 | 32 | // For now, we always want our "dude" to be a yellow @ - so he's constexpr 33 | const vchar dude{'@', YELLOW, BLACK}; 34 | // We're also going to render our destination as a pink heart. Aww. 35 | const vchar destination_glyph{3, MAGENTA, BLACK}; 36 | // We now need to represent walls and floors, too 37 | const vchar wall_tile{'#', WHITE, BLACK}; 38 | const vchar floor_tile{'.', GREY, BLACK}; // Note that "floor" is taken as a name in C++! 39 | 40 | // Now we define a structure to represent a location. In this case, it's a simple 41 | // x/y coordinate. 42 | struct location_t { 43 | int x=-1; // I like to set uninitialized values to something invalid for help with debugging 44 | int y=-1; 45 | 46 | // For convenience, we're overriding the quality operator. This gives a very 47 | // quick and natural looking way to say "are these locations the same?" 48 | bool operator==(location_t &rhs) { return (x==rhs.x && y==rhs.y); } 49 | 50 | location_t() {} 51 | location_t(const int X, const int Y) : x(X), y(Y) {} 52 | }; 53 | 54 | // Now we define our basic map. Why a struct? Because a struct is just a class with 55 | // everything public in it! 56 | struct map_t { 57 | map_t(const int &w, const int &h) : width(w), height(h) { 58 | // Resize the vector to hold the whole map; this way it won't reallocate 59 | walkable.resize(w*h); 60 | 61 | // Set the entire map to walkable 62 | std::fill(walkable.begin(), walkable.end(), true); 63 | 64 | // We want the perimeter to be solid 65 | for (int x=0; x walkable; 90 | }; 91 | 92 | // The A* library returns a navigation path with a template specialization to our location_t. 93 | // Store the path here. Normally, you'd use "auto" for this type, it is a lot less typing! 94 | std::shared_ptr> path; 95 | 96 | // We're using 1024x768, with 8 pixel wide chars. That gives a console grid of 97 | // 128 x 96. We'll go with that for the map, even though in reality the screen 98 | // might change. Worrying about that is for a future example! 99 | constexpr int MAP_WIDTH = 128; 100 | constexpr int MAP_HEIGHT = 96; 101 | map_t map(MAP_WIDTH, MAP_HEIGHT); 102 | 103 | // Instead of raw ints, we'll use the location structure to represent where our 104 | // dude is. Using C++14 initialization, it's nice and clean. 105 | location_t dude_position {10,10}; 106 | 107 | // We'll also use a location_t to represent the intended destination. 108 | location_t destination {10,10}; 109 | 110 | // The A* library also requires a helper class to understand your map format. 111 | struct navigator { 112 | // This lets you define a distance heuristic. Manhattan distance works really well, but 113 | // for now we'll just use a simple euclidian distance squared. 114 | // The geometry system defines one for us. 115 | static float get_distance_estimate(location_t &pos, location_t &goal) { 116 | float d = distance2d_squared(pos.x, pos.y, goal.x, goal.y); 117 | return d; 118 | } 119 | 120 | // Heuristic to determine if we've reached our destination? In some cases, you'd not want 121 | // this to be a simple comparison with the goal - for example, if you just want to be 122 | // adjacent to (or even a preferred distance from) the goal. In this case, 123 | // we're trying to get to the goal rather than near it. 124 | static bool is_goal(location_t &pos, location_t &goal) { 125 | return pos == goal; 126 | } 127 | 128 | // This is where we calculate where you can go from a given tile. In this case, we check 129 | // all 8 directions, and if the destination is walkable return it as an option. 130 | static bool get_successors(location_t pos, std::vector &successors) { 131 | //std::cout << pos.x << "/" << pos.y << "\n"; 132 | 133 | if (map.walkable[map.at(pos.x-1, pos.y-1)]) successors.push_back(location_t(pos.x-1, pos.y-1)); 134 | if (map.walkable[map.at(pos.x, pos.y-1)]) successors.push_back(location_t(pos.x, pos.y-1)); 135 | if (map.walkable[map.at(pos.x+1, pos.y-1)]) successors.push_back(location_t(pos.x+1, pos.y-1)); 136 | 137 | if (map.walkable[map.at(pos.x-1, pos.y)]) successors.push_back(location_t(pos.x-1, pos.y)); 138 | if (map.walkable[map.at(pos.x+1, pos.y)]) successors.push_back(location_t(pos.x+1, pos.y)); 139 | 140 | if (map.walkable[map.at(pos.x-1, pos.y+1)]) successors.push_back(location_t(pos.x-1, pos.y+1)); 141 | if (map.walkable[map.at(pos.x, pos.y+1)]) successors.push_back(location_t(pos.x, pos.y+1)); 142 | if (map.walkable[map.at(pos.x+1, pos.y+1)]) successors.push_back(location_t(pos.x+1, pos.y+1)); 143 | return true; 144 | } 145 | 146 | // This function lets you set a cost on a tile transition. For now, we'll always use a cost of 1.0. 147 | static float get_cost(location_t &position, location_t &successor) { 148 | return 1.0f; 149 | } 150 | 151 | // This is a simple comparison to determine if two locations are the same. It just passes 152 | // through to the location_t's equality operator in this instance (we didn't do that automatically) 153 | // because there are times you might want to behave differently. 154 | static bool is_same_state(location_t &lhs, location_t &rhs) { 155 | return lhs == rhs; 156 | } 157 | }; 158 | 159 | // Lets go really fast! 160 | constexpr double tick_duration = 1.0; 161 | double tick_time = 0.0; 162 | 163 | // Tick is called every frame. The parameter specifies how many ms have elapsed 164 | // since the last time it was called. 165 | void tick(double duration_ms) { 166 | // Iterate over the whole map, rendering as appropriate 167 | for (int y=0; yset_char(console->at(x,y), floor_tile); 171 | } else { 172 | console->set_char(console->at(x,y), wall_tile); 173 | } 174 | } 175 | } 176 | 177 | // Increase the tick time by the frame duration. If it has exceeded 178 | // the tick duration, then we move the @. 179 | tick_time += duration_ms; 180 | if (tick_time > tick_duration) { 181 | // Are we there yet? 182 | if (dude_position == destination) { 183 | // We are there! We need to pick a new destination. 184 | destination.x = rng.roll_dice(1, MAP_WIDTH-1); 185 | destination.y = rng.roll_dice(1, MAP_HEIGHT-1); 186 | 187 | // Lets make sure that the destination is walkable 188 | while (map.walkable[map.at(destination.x,destination.y)] == false) { 189 | destination.x = rng.roll_dice(1, MAP_WIDTH-1); 190 | destination.y = rng.roll_dice(1, MAP_HEIGHT-1); 191 | } 192 | 193 | // Now determine how to get there 194 | if (path) path.reset(); 195 | path = find_path(dude_position, destination); 196 | if (!path->success) { 197 | destination = dude_position; 198 | std::cout << "RESET: THIS ISN'T MEANT TO HAPPEN!\n"; 199 | } 200 | } else { 201 | // Follow the breadcrumbs! 202 | location_t next_step = path->steps.front(); 203 | dude_position.x = next_step.x; 204 | dude_position.y = next_step.y; 205 | path->steps.pop_front(); 206 | } 207 | 208 | // Important: we clear the tick count after the update. 209 | tick_time = 0.0; 210 | } 211 | 212 | // Render our planned path. We're using auto and a range-for to avoid typing all 213 | // the iterator stuff 214 | if (path) { 215 | // We're going to show off a bit and "lerp" the color along the path; the red 216 | // lightens as it approaches the destination. This is a preview of some of the 217 | // color functions. 218 | const float n_steps = static_cast(path->steps.size()); 219 | float i = 0; 220 | for (auto step : path->steps) { 221 | const float lerp_amount = i / n_steps; 222 | vchar highlight{ 177, lerp(DARK_GREEN, LIGHTEST_GREEN, lerp_amount), BLACK }; 223 | console->set_char(console->at(step.x, step.y), highlight); 224 | ++i; 225 | } 226 | } 227 | 228 | // Render our destination 229 | console->set_char(console->at(destination.x, destination.y), destination_glyph); 230 | 231 | // Finally, we render the @ symbol. dude_x and dude_y are in terminal coordinates. 232 | console->set_char(console->at(dude_position.x, dude_position.y), dude); 233 | } 234 | 235 | // Your main function 236 | int main() 237 | { 238 | // Initialize with defaults 239 | init(config_simple_px("../assets")); 240 | 241 | // Enter the main loop. "tick" is the function we wrote above. 242 | run(tick); 243 | 244 | return 0; 245 | } 246 | -------------------------------------------------------------------------------- /examples/ex7/main.cpp: -------------------------------------------------------------------------------- 1 | /* RLTK (RogueLike Tool Kit) 1.00 2 | * Copyright (c) 2016-Present, Bracket Productions. 3 | * Licensed under the MIT license - see LICENSE file. 4 | * 5 | * Example 7: Introduction to complex GUIs. This example demonstrates how you can create multiple layers, 6 | * and use call-backs to resize them as the window adjusts. It also displays a layer on top of another, 7 | * with alpha transparency (useful for effects). 8 | */ 9 | 10 | // You need to include the RLTK header 11 | #include "../../rltk/rltk.hpp" 12 | #include 13 | #include 14 | 15 | // For convenience, import the whole rltk namespace. You may not want to do this 16 | // in larger projects, to avoid naming collisions. 17 | using namespace rltk; 18 | using namespace rltk::colors; 19 | 20 | // For convenience, we'll define our GUI section handles here. These are just ID numbers. 21 | constexpr int TITLE_LAYER = 0; 22 | constexpr int MAIN_LAYER = 1; 23 | constexpr int LOG_LAYER = 2; 24 | constexpr int OVERLAY_LAYER = 3; 25 | 26 | // Tick is called every frame. The parameter specifies how many ms have elapsed 27 | // since the last time it was called. 28 | void tick(double duration_ms) { 29 | term(TITLE_LAYER)->clear(vchar{' ', YELLOW, BLUE}); 30 | term(TITLE_LAYER)->print_center(0, "Big 32x32 Title", YELLOW, BLUE); 31 | term(MAIN_LAYER)->clear(vchar{'.', GREY, BLACK}); 32 | term(MAIN_LAYER)->box(GREY, BLACK, true); 33 | term(MAIN_LAYER)->set_char(10, 10, vchar{'@', YELLOW, BLACK}); 34 | term(LOG_LAYER)->clear(vchar{' ', WHITE, DARKEST_GREEN}); 35 | term(LOG_LAYER)->box(DARKEST_GREEN, BLACK); 36 | term(LOG_LAYER)->print(1,1, "Log Entry", LIGHT_GREEN, DARKEST_GREEN); 37 | term(LOG_LAYER)->print(1,2, "More text!", LIGHT_GREEN, DARKEST_GREEN); 38 | term(LOG_LAYER)->print(1,3, "Even more...", LIGHT_GREEN, DARKEST_GREEN); 39 | term(LOG_LAYER)->print(1,4, "... goes here", LIGHT_GREEN, DARKEST_GREEN); 40 | term(OVERLAY_LAYER)->clear(); 41 | term(OVERLAY_LAYER)->set_char(11, 10, vchar{17, LIGHT_GREEN, BLACK}); // Draw a left arrow 42 | term(OVERLAY_LAYER)->print(12, 10, "Translucent Tool-tip", LIGHT_GREEN, BLACK); 43 | 44 | std::stringstream ss; 45 | ss << std::setiosflags(std::ios::fixed) << std::setprecision(0) << (1000.0/duration_ms) << " FPS"; 46 | term(LOG_LAYER)->print(1,6, ss.str(), WHITE, DARKEST_GREEN); 47 | } 48 | 49 | // This is called when the screen resizes, to allow the GUI to redefine itself. 50 | void resize_title(layer_t * l, int w, int h) { 51 | // Simply set the width to the whole window width 52 | l->w = w; 53 | l->h = 32; // Not really necessary - here for clarity 54 | } 55 | 56 | // This is called when the screen resizes, to allow the GUI to redefine itself. 57 | void resize_main(layer_t * l, int w, int h) { 58 | // Simply set the width to the whole window width, and the whole window minus 16 pixels (for the heading) 59 | l->w = w - 160; 60 | l->h = h - 32; 61 | if (l->w < 0) l->w = 160; // If the width is too small with the log window, display anyway. 62 | } 63 | 64 | // This is called when the screen resizes, to allow the GUI to redefine itself. 65 | void resize_log(layer_t * l, int w, int h) { 66 | // Simply set the width to the whole window width, and the whole window minus 16 pixels (for the heading) 67 | l->w = w - 160; 68 | l->h = h - 32; 69 | 70 | // If the log window would take up the whole screen, hide it 71 | if (l->w < 0) { 72 | l->console->visible = false; 73 | } else { 74 | l->console->visible = true; 75 | } 76 | l->x = w - 160; 77 | } 78 | 79 | // Your main function 80 | int main() 81 | { 82 | // This time, we're using a full initialization: width, height, window title, and "false" meaning we don't 83 | // want an automatically generated root console. This is necessary when you want to use the complex layout 84 | // functions. 85 | init(config_advanced("../assets")); 86 | 87 | gui->add_layer(TITLE_LAYER, 0, 0, 1024, 32, "32x32", resize_title); 88 | gui->add_layer(MAIN_LAYER, 0, 32, 1024-160, 768-32, "8x8", resize_main); 89 | gui->add_layer(LOG_LAYER, 864, 32, 160, 768-32, "8x16", resize_log); 90 | gui->add_layer(OVERLAY_LAYER, 0, 32, 1024-160, 768-32, "8x8", resize_main); // We re-use resize_main, we want it over the top 91 | term(OVERLAY_LAYER)->set_alpha(196); // Make the overlay translucent 92 | 93 | run(tick); 94 | 95 | return 0; 96 | } 97 | -------------------------------------------------------------------------------- /examples/ex8/main.cpp: -------------------------------------------------------------------------------- 1 | /* RLTK (RogueLike Tool Kit) 1.00 2 | * Copyright (c) 2016-Present, Bracket Productions. 3 | * Licensed under the MIT license - see LICENSE file. 4 | * 5 | * Example 7: Advanced GUI with retained-mode GUI elements and an owner-draw background. 6 | */ 7 | 8 | // You need to include the RLTK header 9 | #include "../../rltk/rltk.hpp" 10 | #include 11 | #include 12 | 13 | // For convenience, import the whole rltk namespace. You may not want to do this 14 | // in larger projects, to avoid naming collisions. 15 | using namespace rltk; 16 | using namespace rltk::colors; 17 | 18 | random_number_generator rng; 19 | 20 | constexpr int BACKDROP_LAYER = 1; 21 | constexpr int LOG_LAYER = 2; 22 | constexpr int RETAINED_TEST_LAYER = 3; 23 | 24 | constexpr int TEST_BOUNDARY_BOX = 1; 25 | constexpr int TEST_STATIC_TEST = 2; 26 | constexpr int TEST_MOUSE_HOVER = 3; 27 | constexpr int TEST_CHECKBOX = 4; 28 | constexpr int TEST_RADIOSET = 5; 29 | constexpr int TEST_HBAR = 6; 30 | constexpr int TEST_VBAR = 7; 31 | constexpr int TEST_LISTBOX = 8; 32 | 33 | void resize_bg(layer_t * l, int w, int h) { 34 | // Use the whole window 35 | l->w = w; 36 | l->h = h; 37 | } 38 | 39 | void draw_bg(layer_t * l, sf::RenderTexture &window) { 40 | sf::Texture * bg = get_texture("backdrop"); 41 | sf::Sprite backdrop(*bg); 42 | window.draw(backdrop); 43 | } 44 | 45 | void resize_log(layer_t * l, int w, int h) { 46 | // Simply set the width to the whole window width, and the whole window minus 16 pixels (for the heading) 47 | l->w = w - 160; 48 | l->h = h - 32; 49 | 50 | // If the log window would take up the whole screen, hide it 51 | if (l->w < 0) { 52 | l->console->visible = false; 53 | } else { 54 | l->console->visible = true; 55 | } 56 | l->x = w - 160; 57 | } 58 | 59 | void resize_retained(layer_t * l, int w, int h) { 60 | // Do nothing - we'll just keep on rendering away. 61 | l->x = 100; 62 | l->y = 100; 63 | l->w = 400; 64 | l->h = 200; 65 | } 66 | 67 | // Tick is called every frame. The parameter specifies how many ms have elapsed 68 | // since the last time it was called. 69 | void tick(double duration_ms) { 70 | term(LOG_LAYER)->clear(vchar{' ', WHITE, DARKEST_GREEN}); 71 | term(LOG_LAYER)->box(DARKEST_GREEN, BLACK); 72 | 73 | layer_t * retained = layer(RETAINED_TEST_LAYER); 74 | 75 | if ( retained->control(TEST_CHECKBOX)->checked ) { 76 | term(LOG_LAYER)->print(1,1, "Checked", LIGHTEST_GREEN, DARKEST_GREEN); 77 | } else { 78 | term(LOG_LAYER)->print(1,1, "Not Checked", DARK_GREEN, DARKEST_GREEN); 79 | } 80 | 81 | std::stringstream radio_ss; 82 | radio_ss << "Option: " << retained->control(TEST_RADIOSET)->selected_value; 83 | term(LOG_LAYER)->print(1,2, radio_ss.str(), LIGHT_GREEN, DARKEST_GREEN); 84 | 85 | std::stringstream list_ss; 86 | list_ss << "List: " << retained->control(TEST_LISTBOX)->selected_value; 87 | term(LOG_LAYER)->print(1,3, list_ss.str(), LIGHT_GREEN, DARKEST_GREEN); 88 | term(LOG_LAYER)->print(1,4, "... goes here", LIGHT_GREEN, DARKEST_GREEN); 89 | 90 | std::stringstream ss; 91 | ss << std::setiosflags(std::ios::fixed) << std::setprecision(0) << (1000.0/duration_ms) << " FPS"; 92 | term(LOG_LAYER)->print(1,6, ss.str(), WHITE, DARKEST_GREEN); 93 | 94 | if (rng.roll_dice(1,20)==1) { 95 | retained->control(TEST_HBAR)->value = rng.roll_dice(1,100); 96 | retained->control(TEST_VBAR)->value = rng.roll_dice(1,100); 97 | } 98 | } 99 | 100 | // Your main function 101 | int main() 102 | { 103 | // This time, we're using a full initialization: width, height, window title, and "false" meaning we don't 104 | // want an automatically generated root console. This is necessary when you want to use the complex layout 105 | // functions. 106 | init(config_advanced("../assets")); 107 | 108 | // We're going to be using a bitmap, so we need to load it. The library makes this easy: 109 | register_texture("../assets/background_image.png", "backdrop"); 110 | 111 | // Now we add an owner-draw background layer. "Owner-draw" means that it the library will ask it to 112 | // draw itself with a call-back function. 113 | gui->add_owner_layer(BACKDROP_LAYER, 0, 0, 1024, 768, resize_bg, draw_bg); 114 | gui->add_layer(LOG_LAYER, 864, 32, 160, 768-32, "8x16", resize_log); 115 | term(LOG_LAYER)->set_alpha(196); // Make the overlay translucent 116 | gui->add_layer(RETAINED_TEST_LAYER, 100, 100, 400, 400, "8x16", resize_retained); 117 | 118 | // To reduce typing, grab a pointer to the retained layer: 119 | layer_t * retained = layer(RETAINED_TEST_LAYER); 120 | 121 | // Now we build some retained-mode controls. These don't require additional work during rendering 122 | // Note that we are providing a handle to the control. That lets us access it later with 123 | // layer(layerhandle)->control(controlhandle). It's up to you to store the handles; they can be any 124 | // int. 125 | retained->add_boundary_box(TEST_BOUNDARY_BOX, true, DARK_GREY, BLACK); 126 | retained->add_static_text(TEST_STATIC_TEST, 1, 1, "Retained Mode Static Text", YELLOW, BLACK); 127 | 128 | // For this control, we'll define an on-mouse-over. We're using a lambda, but it could be any function 129 | // with that takes a gui_control_t * as a parameter. We'll also use "on render start" to define a function 130 | // run when the control rendering starts. 131 | retained->add_static_text(TEST_MOUSE_HOVER, 1, 2, "Hover the mouse over me!", WHITE, BLACK); 132 | retained->control(TEST_MOUSE_HOVER)->on_render_start = [] (gui_control_t * control) { 133 | auto static_text = static_cast(control); 134 | static_text->background = BLACK; 135 | static_text->text = "Hover the mouse over me!"; 136 | }; 137 | retained->control(TEST_MOUSE_HOVER)->on_mouse_over = [] (gui_control_t * control, int terminal_x, int terminal_y) { 138 | auto static_text = static_cast(control); 139 | static_text->background = RED; 140 | static_text->text = "Why Hello There! "; 141 | }; 142 | 143 | // A checkbox 144 | retained->add_checkbox(TEST_CHECKBOX, 1, 3, "I'm a checkbox - click me!", false, LIGHT_GREEN, BLACK); 145 | 146 | // A radioset 147 | retained->add_radioset(TEST_RADIOSET, 1, 5, "Test radio buttons", CYAN, BLACK, { 148 | {true, "Option A", 0}, {false, "Option B", 1}, {false, "Option C", 2} 149 | }); 150 | 151 | // Add a horizontal and vertical color bar (e.g. health) 152 | retained->add_hbar(TEST_HBAR, 1, 9, 46, 0, 100, 50, color_t(128,0,0), color_t(255,0,0), color_t(128,128,128), color_t(64,64,64), WHITE, "Health: "); 153 | retained->add_vbar(TEST_VBAR, 48, 1, 20, 0, 100, 50, color_t(0,0,128), color_t(0,0,128), color_t(128,128,128), color_t(64,64,64), CYAN, "Mana: "); 154 | 155 | // Listbox 156 | retained->add_listbox(TEST_LISTBOX, 1, 11, 1, { {1, "Option 1"}, {2, "Option 2"}, {3, "Option 3"} }, "Listbox Options", WHITE, BLACK, WHITE, BLACK, WHITE, BLUE); 157 | 158 | // Main loop - calls the 'tick' function you defined for each frame. 159 | run(tick); 160 | 161 | return 0; 162 | } 163 | -------------------------------------------------------------------------------- /rltk/color_t.cpp: -------------------------------------------------------------------------------- 1 | #include "color_t.hpp" 2 | #include 3 | 4 | using std::min; 5 | using std::max; 6 | using std::fmod; 7 | 8 | namespace rltk { 9 | 10 | // Credit: https://gist.github.com/fairlight1337/4935ae72bcbcc1ba5c72 11 | std::tuple color_to_hsv(const color_t &col) { 12 | float fR = (col.r / 255.0f); 13 | float fG = (col.g / 255.0f); 14 | float fB = (col.b / 255.0f); 15 | 16 | float fH=0.0f, fS=0.0f, fV=0.0f; 17 | 18 | float fCMax = max(max(fR, fG), fB); 19 | float fCMin = min(min(fR, fG), fB); 20 | float fDelta = fCMax - fCMin; 21 | 22 | if(fDelta > 0) { 23 | if(fCMax == fR) { 24 | fH = 60.0f * (fmodf(((fG - fB) / fDelta), 6.0f)); 25 | } else if(fCMax == fG) { 26 | fH = 60.0f * (((fB - fR) / fDelta) + 2.0f); 27 | } else if(fCMax == fB) { 28 | fH = 60.0f * (((fR - fG) / fDelta) + 4.0f); 29 | } 30 | 31 | if(fCMax > 0) { 32 | fS = fDelta / fCMax; 33 | } else { 34 | fS = 0; 35 | } 36 | 37 | fV = fCMax; 38 | } else { 39 | fH = 0; 40 | fS = 0; 41 | fV = fCMax; 42 | } 43 | 44 | if(fH < 0) { 45 | fH = 360 + fH; 46 | } 47 | 48 | return std::make_tuple(fH, fS, fV); 49 | } 50 | 51 | // Credit: https://gist.github.com/fairlight1337/4935ae72bcbcc1ba5c72 52 | std::tuple color_from_hsv(const float hue, const float saturation, const float value) { 53 | float fH = hue; 54 | float fS = saturation; 55 | float fV = value; 56 | 57 | float fR, fG, fB; 58 | 59 | float fC = fV * fS; // Chroma 60 | float fHPrime = fmodf(fH / 60.0f, 6.0f); 61 | float fX = fC * (1.0f - fabsf(fmodf(fHPrime, 2.0f) - 1.0f)); 62 | float fM = fV - fC; 63 | 64 | if(0 <= fHPrime && fHPrime < 1) { 65 | fR = fC; 66 | fG = fX; 67 | fB = 0; 68 | } else if(1 <= fHPrime && fHPrime < 2) { 69 | fR = fX; 70 | fG = fC; 71 | fB = 0; 72 | } else if(2 <= fHPrime && fHPrime < 3) { 73 | fR = 0; 74 | fG = fC; 75 | fB = fX; 76 | } else if(3 <= fHPrime && fHPrime < 4) { 77 | fR = 0; 78 | fG = fX; 79 | fB = fC; 80 | } else if(4 <= fHPrime && fHPrime < 5) { 81 | fR = fX; 82 | fG = 0; 83 | fB = fC; 84 | } else if(5 <= fHPrime && fHPrime < 6) { 85 | fR = fC; 86 | fG = 0; 87 | fB = fX; 88 | } else { 89 | fR = 0; 90 | fG = 0; 91 | fB = 0; 92 | } 93 | 94 | fR += fM; 95 | fG += fM; 96 | fB += fM; 97 | 98 | return std::make_tuple(static_cast(fR*255.0), static_cast(fG*255.0), static_cast(fB*255.0)); 99 | } 100 | 101 | /* 102 | * Calculates the luminance of a color, and converts it to grey-scale. 103 | */ 104 | color_t greyscale(const color_t &col) 105 | { 106 | unsigned char red = col.r; 107 | unsigned char green = col.g; 108 | unsigned char blue = col.b; 109 | 110 | float RED = red / 255.0F; 111 | float GREEN = green / 255.0F; 112 | float BLUE = blue / 255.0F; 113 | float luminance = 0.299f * RED + 0.587f * GREEN + 0.114f * BLUE; 114 | 115 | red = static_cast(luminance * 255.0F); 116 | green = static_cast(luminance * 255.0F); 117 | blue = static_cast(luminance * 255.0F); 118 | 119 | return color_t(red, green, blue); 120 | } 121 | 122 | /* 123 | * Darkens a color by the specified amount. 124 | */ 125 | color_t darken(const int &amount, const color_t &col) 126 | { 127 | unsigned char red = col.r; 128 | unsigned char green = col.g; 129 | unsigned char blue = col.b; 130 | 131 | if (red > amount) 132 | { 133 | red -= amount; 134 | } 135 | else 136 | { 137 | red = 0; 138 | } 139 | if (green > amount) 140 | { 141 | green -= amount; 142 | } 143 | else 144 | { 145 | green = 0; 146 | } 147 | if (blue > amount) 148 | { 149 | blue -= amount; 150 | } 151 | else 152 | { 153 | blue = 0; 154 | } 155 | 156 | return color_t(red, green, blue); 157 | } 158 | 159 | /* Applies colored lighting effect; colors that don't exist remain dark. Lights are from 0.0 to 1.0. */ 160 | color_t apply_colored_light(const color_t &col, const std::tuple &light) { 161 | unsigned char red = col.r; 162 | unsigned char green = col.g; 163 | unsigned char blue = col.b; 164 | 165 | float RED = red / 255.0F; 166 | float GREEN = green / 255.0F; 167 | float BLUE = blue / 255.0F; 168 | 169 | RED *= std::get<0>(light); 170 | GREEN *= std::get<1>(light); 171 | BLUE *= std::get<2>(light); 172 | 173 | if (RED > 1.0) RED = 1.0; 174 | if (RED < 0.0) RED = 0.0; 175 | if (GREEN > 1.0) GREEN = 1.0; 176 | if (GREEN < 0.0) GREEN = 0.0; 177 | if (BLUE > 1.0) BLUE = 1.0; 178 | if (BLUE < 0.0) BLUE = 0.0; 179 | 180 | red = static_cast(RED * 255.0F); 181 | green = static_cast(GREEN * 255.0F); 182 | blue = static_cast(BLUE * 255.0F); 183 | 184 | return color_t(red, green, blue); 185 | } 186 | 187 | color_t lerp(const color_t &first, const color_t &second, float amount) { 188 | const float r1 = first.r; 189 | const float g1 = first.g; 190 | const float b1 = first.b; 191 | 192 | const float r2 = second.r; 193 | const float g2 = second.g; 194 | const float b2 = second.b; 195 | 196 | const float rdiff = r2 - r1; 197 | const float gdiff = g2 - g1; 198 | const float bdiff = b2 - b1; 199 | 200 | float red = r1 + (rdiff * amount); 201 | float green = g1 + (gdiff * amount); 202 | float blue = b1 + (bdiff * amount); 203 | if (red > 255.0F) red = 255.0F; 204 | if (green > 255.0F) green = 255.0F; 205 | if (blue > 255.0F) blue = 255.0F; 206 | if (red < 0.0F) red = 0.0F; 207 | if (green < 0.0F) green = 0.0F; 208 | if (blue < 0.0F) blue = 0.0F; 209 | 210 | const int r = static_cast(red); 211 | const int g = static_cast(green); 212 | const int b = static_cast(blue); 213 | 214 | return color_t(r,g,b); 215 | } 216 | 217 | } -------------------------------------------------------------------------------- /rltk/color_t.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* RLTK (RogueLike Tool Kit) 1.00 4 | * Copyright (c) 2016-Present, Bracket Productions. 5 | * Licensed under the MIT license - see LICENSE file. 6 | * 7 | * Color functions 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace rltk { 15 | 16 | /* Converts HSV into an RGB tuple */ 17 | extern std::tuple color_from_hsv(const float hue, const float saturation, const float value); 18 | 19 | struct color_t { 20 | /* Default empty constructor */ 21 | color_t() {} 22 | 23 | /* Convenience constructor from red/green/blue; accepts ints and casts them */ 24 | color_t(const int R, const int G, const int B) : r(static_cast(R)), g(static_cast(G)), b(static_cast(B)) {} 25 | 26 | /* Construct from red/green/blue, in the range of 0-255 per component. */ 27 | color_t(const uint8_t R, const uint8_t G, const uint8_t B) : r(R), g(G), b(B) {} 28 | 29 | /* Construct from HSV, in the range of 0-1.0 as floats. */ 30 | color_t(const float hue, const float saturation, const float value) { 31 | std::tie(r,g,b) = color_from_hsv(hue, saturation, value); 32 | } 33 | 34 | /* Construct from an RGB tuple */ 35 | color_t(const std::tuple &c) { std::tie(r,g,b) = c; } 36 | 37 | /* You can add colors together, for example as a quick blend/lighten */ 38 | color_t operator+(const color_t &other) { 39 | int red = r + other.r; 40 | int green = g + other.g; 41 | int blue = b + other.b; 42 | if (red > 255) red = 255; 43 | if (green > 255) green = 255; 44 | if (blue > 255) blue = 255; 45 | return color_t(red, green, blue); 46 | } 47 | 48 | /* You can subtract colors */ 49 | color_t operator-(const color_t &other) { 50 | int red = r - other.r; 51 | int green = g - other.g; 52 | int blue = b - other.b; 53 | if (red < 0) red = 0; 54 | if (green < 0) green = 0; 55 | if (blue < 0) blue = 0; 56 | return color_t(red, green, blue); 57 | } 58 | 59 | /* You can multiply colors */ 60 | color_t operator*(const color_t &other) { 61 | int red = r * other.r; 62 | int green = g * other.g; 63 | int blue = b * other.b; 64 | if (red < 0) red = 0; 65 | if (green < 0) green = 0; 66 | if (blue < 0) blue = 0; 67 | if (red > 255) red = 255; 68 | if (green > 255) green = 255; 69 | if (blue > 255) blue = 255; 70 | return color_t(red, green, blue); 71 | } 72 | 73 | /* You can compare colors */ 74 | bool operator==(const color_t &other) const { 75 | if (other.r == r && other.g == g && other.b == b) { 76 | return true; 77 | } else { 78 | return false; 79 | } 80 | } 81 | 82 | /* RGB storage */ 83 | uint8_t r,g,b; 84 | 85 | template 86 | void serialize(Archive & archive) 87 | { 88 | archive( r, g, b ); // serialize things by passing them to the archive 89 | } 90 | }; 91 | 92 | /* Converts a color_t to an SFML color */ 93 | inline sf::Color color_to_sfml(const color_t &col) { return sf::Color(col.r, col.g, col.b); } 94 | 95 | /* Converts a color_t to an RGB tuple */ 96 | inline std::tuple color_to_rgb(const color_t &col) { return std::make_tuple(col.r, col.g, col.b); } 97 | 98 | /* Converts a color_t to an HSV tuple */ 99 | extern std::tuple color_to_hsv(const color_t &col); 100 | 101 | /* Calculates the luminance of a color, and converts it to grey-scale. */ 102 | extern color_t greyscale(const color_t &col); 103 | 104 | /* Darkens a color by the specified amount. */ 105 | color_t darken(const int &amount, const color_t &col); 106 | 107 | /* Applies colored lighting effect; colors that don't exist remain dark. Lights are from 0.0 to 1.0. */ 108 | color_t apply_colored_light(const color_t &col, const std::tuple &light); 109 | 110 | /* Calculates an intermediate color on a linear RGB color ramp. Amount is from 0 to 1 */ 111 | extern color_t lerp(const color_t &first, const color_t &second, float amount); 112 | 113 | } -------------------------------------------------------------------------------- /rltk/colors.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "color_t.hpp" 4 | 5 | namespace rltk { 6 | 7 | namespace colors { 8 | 9 | const color_t AliceBlue(240, 248, 255); 10 | const color_t AntiqueWhite(250, 235, 215); 11 | const color_t Aqua(0, 255, 255); 12 | const color_t Aquamarine(127, 255, 212); 13 | const color_t Beige(245, 245, 220); 14 | const color_t Black(0, 0, 0); 15 | const color_t BlanchedAlmond(255, 235, 205); 16 | const color_t Blue(0, 0, 255); 17 | const color_t BlueViolet(138, 43, 226); 18 | const color_t Brown(165, 42, 42); 19 | const color_t BurlyWood(222, 184, 135); 20 | const color_t CadetBlue(95, 158, 160); 21 | const color_t Chartreuse(127, 255, 0); 22 | const color_t Chocolate(210, 105, 20); 23 | const color_t Coral(255, 127, 80); 24 | const color_t CornflowerBlue(100, 149, 237); 25 | const color_t Cornsilk(255, 248, 220); 26 | const color_t Crimson(220, 20, 60); 27 | const color_t Cyan(0, 0, 255); 28 | const color_t DarkBlue(0, 0, 139); 29 | const color_t DarkCyan(0, 139, 139); 30 | const color_t DarkGoldenRod(184, 134, 11); 31 | const color_t DarkGray(169, 169, 169); 32 | const color_t DarkGreen(0, 100, 0); 33 | const color_t DarkKhaki(189, 183, 107); 34 | const color_t DarkMagenta(139, 0, 139); 35 | const color_t DarkOliveGreen(85, 107, 47); 36 | const color_t Darkorange(255, 140, 0); 37 | const color_t DarkOrchid(255, 140, 0); 38 | const color_t DarkRed(139, 0, 0); 39 | const color_t DarkSalmon(233, 150, 122); 40 | const color_t DarkSeaGreen(143, 188, 143); 41 | const color_t DarkSlateBlue(72, 61, 139); 42 | const color_t DarkSlateGray(47, 79, 79); 43 | const color_t DarkTurquoise(0, 206, 209); 44 | const color_t DarkViolet(148, 0, 211); 45 | const color_t DeepPink(255, 20, 147); 46 | const color_t DeepSkyBlue(0, 191, 255); 47 | const color_t DimGray(105, 105, 105); 48 | const color_t DodgerBlue(30, 144, 255); 49 | const color_t FireBrick(178, 34, 34); 50 | const color_t FloralWhite(255, 250, 240); 51 | const color_t ForestGreen(34, 139, 34); 52 | const color_t Fuchsia(255, 0, 255); 53 | const color_t Gainsboro(220, 220, 220); 54 | const color_t GhostWhite(248, 248, 255); 55 | const color_t Gold(255, 215, 0); 56 | const color_t GoldenRod(218, 165, 32); 57 | const color_t Grey(128, 128, 128); 58 | const color_t Green(0, 128, 0); 59 | const color_t GreenYellow(173, 255, 47); 60 | const color_t HoneyDew(240, 255, 240); 61 | const color_t HotPink(255, 105, 180); 62 | const color_t IndianRed(205, 92, 92); 63 | const color_t Indigo(72, 0, 130); 64 | const color_t Ivory(255, 255, 240); 65 | const color_t Khaki(240, 230, 140); 66 | const color_t Lavender(230, 230, 250); 67 | const color_t LavenderBlush(255, 240, 245); 68 | const color_t LawnGreen(124, 252, 0); 69 | const color_t LemonChiffon(255, 250, 205); 70 | const color_t LightBlue(173, 216, 203); 71 | const color_t LightCoral(240, 128, 128); 72 | const color_t LightCyan(240, 128, 128); 73 | const color_t LightGoldenRodYellow(250, 250, 210); 74 | const color_t LightGrey(211, 211, 211); 75 | const color_t LightGreen(144, 238, 144); 76 | const color_t LightPink(255, 182, 193); 77 | const color_t LightSalmon(255, 160, 122); 78 | const color_t LightSeaGreen(32, 178, 170); 79 | const color_t LightSkyBlue(135, 206, 250); 80 | const color_t LightSlateGrey(119, 136, 153); 81 | const color_t LightSteelBlue(176, 196, 222); 82 | const color_t LightYellow(255, 255, 224); 83 | const color_t Lime(0, 255, 0); 84 | const color_t LimeGreen(50, 205, 50); 85 | const color_t Linen(250, 240, 230); 86 | const color_t Magenta(255, 0, 255); 87 | const color_t Maroon(128, 0, 0); 88 | const color_t MediumAquaMarine(102, 205, 170); 89 | const color_t MediumBlue(0, 0, 205); 90 | const color_t MediumOrchid(186, 85, 211); 91 | const color_t MediumPurple(147, 112, 219); 92 | const color_t MediumSeaGreen(60, 179, 113); 93 | const color_t MediumSlateBlue(123, 104, 238); 94 | const color_t MediumSpringGreen(0, 250, 154); 95 | const color_t MediumTurquoise(72, 209, 204); 96 | const color_t MediumVioletRed(199, 21, 133); 97 | const color_t MidnightBlue(25, 25, 112); 98 | const color_t MintCream(245, 255, 250); 99 | const color_t MistyRose(255, 228, 225); 100 | const color_t Moccasin(255, 228, 181); 101 | const color_t NavajoWhite(255, 222, 173); 102 | const color_t Navy(0, 0, 128); 103 | const color_t OldLace(253, 245, 230); 104 | const color_t Olive(128, 128, 0); 105 | const color_t OliveDrab(107, 142, 35); 106 | const color_t Orange(255, 165, 0); 107 | const color_t OrangeRed(255, 69, 0); 108 | const color_t Orchid(218, 112, 214); 109 | const color_t PaleGoldenRod(238, 232, 170); 110 | const color_t PaleGreen(152, 251, 152); 111 | const color_t PaleTurquoise(175, 238, 238); 112 | const color_t PaleVioletRed(219, 112, 147); 113 | const color_t PapayaWhip(225, 239, 213); 114 | const color_t PeachPuff(225, 218, 185); 115 | const color_t Peru(205, 133, 63); 116 | const color_t Pink(255, 192, 203); 117 | const color_t Plum(221, 160, 221); 118 | const color_t PowderBlue(176, 224, 230); 119 | const color_t Purple(128, 0, 128); 120 | const color_t Red(255, 0, 0); 121 | const color_t RosyBrown(188, 143, 143); 122 | const color_t RoyalBlue(65, 105, 225); 123 | const color_t SaddleBrown(139, 69, 19); 124 | const color_t Salmon(250, 128, 114); 125 | const color_t SandyBrown(244, 164, 96); 126 | const color_t SeaGreen(46, 139, 87); 127 | const color_t SeaShell(255, 245, 238); 128 | const color_t Sienna(160, 82, 45); 129 | const color_t Silver(192, 192, 192); 130 | const color_t SkyBlue(135, 206, 235); 131 | const color_t SlateBlue(106, 90, 205); 132 | const color_t SlateGrey(112, 128, 144); 133 | const color_t Snow(255, 250, 250); 134 | const color_t SpringGreen(0, 255, 127); 135 | const color_t SteelBlue(70, 130, 180); 136 | const color_t Tan(210, 180, 140); 137 | const color_t Teal(0, 128, 128); 138 | const color_t Thistle(216, 191, 216); 139 | const color_t Tomato(255, 99, 71); 140 | const color_t Turquoise(64, 224, 208); 141 | const color_t Violet(238, 130, 238); 142 | const color_t Wheat(245, 222, 179); 143 | const color_t White(255, 255, 255); 144 | const color_t WhiteSmoke(245, 245, 245); 145 | const color_t Yellow(255, 0, 0); 146 | const color_t YellowGreen(154, 205, 50); 147 | 148 | const color_t BLACK(0,0,0); 149 | const color_t WHITE(255,255,255); 150 | 151 | const color_t DESATURATED_RED(128,64,64); 152 | const color_t LIGHTEST_RED(255,191,191); 153 | const color_t LIGHTER_RED(255,166,166); 154 | const color_t LIGHT_RED(255,115,115); 155 | const color_t RED(255,0,0); 156 | const color_t DARK_RED(191,0,0); 157 | const color_t DARKER_RED(128,0,0); 158 | const color_t DARKEST_RED(64,0,0); 159 | const color_t FLAME(255,63,0); 160 | const color_t ORANGE(255,127,0); 161 | const color_t AMBER(255,191,0); 162 | const color_t YELLOW(255,255,0); 163 | const color_t LIME(191,255,0); 164 | const color_t CHARTREUSE(127,255,0); 165 | const color_t DESATURATED_GREEN(64,128,64); 166 | const color_t LIGHTEST_GREEN(191,255,191); 167 | const color_t LIGHTER_GREEN(166,255,166); 168 | const color_t LIGHT_GREEN(115,255,115); 169 | const color_t GREEN(0,255,0); 170 | const color_t DARK_GREEN(0,191,0); 171 | const color_t DARKER_GREEN(0,128,0); 172 | const color_t DARKEST_GREEN(0,64,0); 173 | const color_t SEA(0,255,127); 174 | const color_t TURQUOISE(0,255,191); 175 | const color_t CYAN(0,255,255); 176 | const color_t SKY(0,191,255); 177 | const color_t AZURE(0,127,255); 178 | const color_t BLUE(0,0,255); 179 | const color_t HAN(63,0,255); 180 | const color_t VIOLET(127,0,255); 181 | const color_t PURPLE(191,0,255); 182 | const color_t FUCHSIA(255,0,191); 183 | const color_t MAGENTA(255,0,255); 184 | const color_t PINK(255,0,127); 185 | const color_t CRIMSON(255,0,63); 186 | 187 | const color_t BRASS(191,151,96); 188 | const color_t COPPER(200,117,51); 189 | const color_t GOLD(229,191,0); 190 | const color_t SILVER(203,203,203); 191 | 192 | const color_t CELADON(172,255,171); 193 | const color_t PEACH(255,159,127); 194 | 195 | const color_t LIGHTEST_GREY(223,223,223); 196 | const color_t LIGHTER_GREY(191,191,191); 197 | const color_t LIGHT_GREY(159,159,159); 198 | const color_t GREY(127,127,127); 199 | const color_t DARK_GREY(95,95,95); 200 | const color_t DARKER_GREY(63,63,63); 201 | const color_t DARKEST_GREY(31,31,31); 202 | 203 | const color_t LIGHTEST_SEPIA(222,211,195); 204 | const color_t LIGHTER_SEPIA(191,171,143); 205 | const color_t LIGHT_SEPIA(158,134,100); 206 | const color_t SEPIA(127,101,63); 207 | const color_t DARK_SEPIA(94,75,47); 208 | const color_t DARKER_SEPIA(63,50,31); 209 | const color_t DARKEST_SEPIA(31,24,15); 210 | 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /rltk/ecs.cpp: -------------------------------------------------------------------------------- 1 | #include "ecs.hpp" 2 | #include 3 | #include 4 | 5 | namespace rltk { 6 | 7 | std::size_t impl::base_component_t::type_counter = 1; 8 | std::size_t base_message_t::type_counter = 1; 9 | std::size_t entity_t::entity_counter{1}; // Not using zero since it is used as null so often 10 | ecs default_ecs; 11 | 12 | entity_t * ecs::entity(const std::size_t id) noexcept { 13 | entity_t * result = nullptr; 14 | auto finder = entity_store.find(id); 15 | if (finder == entity_store.end()) return result; 16 | if (finder->second.deleted) return result; 17 | result = &finder->second; 18 | return result; 19 | } 20 | 21 | entity_t * ecs::create_entity() { 22 | entity_t new_entity; 23 | while (entity_store.find(new_entity.id) != entity_store.end()) { 24 | ++entity_t::entity_counter; 25 | new_entity.id = entity_t::entity_counter; 26 | } 27 | //std::cout << "New Entity ID#: " << new_entity.id << "\n"; 28 | 29 | entity_store.emplace(new_entity.id, new_entity); 30 | return entity(new_entity.id); 31 | } 32 | 33 | entity_t * ecs::create_entity(const std::size_t new_id) { 34 | entity_t new_entity(new_id); 35 | if (entity_store.find(new_entity.id) != entity_store.end()) { 36 | throw std::runtime_error("WARNING: Duplicate entity ID. Odd things will happen\n"); 37 | } 38 | entity_store.emplace(new_entity.id, new_entity); 39 | return entity(new_entity.id); 40 | } 41 | 42 | void ecs::each(std::function &&func) { 43 | for (auto it=entity_store.begin(); it!=entity_store.end(); ++it) { 44 | if (!it->second.deleted) { 45 | func(it->second); 46 | } 47 | } 48 | } 49 | 50 | 51 | void ecs::delete_all_systems() { 52 | system_store.clear(); 53 | system_profiling.clear(); 54 | pubsub_holder.clear(); 55 | } 56 | 57 | void ecs::ecs_configure() { 58 | for (std::unique_ptr & sys : system_store) { 59 | sys->configure(); 60 | } 61 | } 62 | 63 | void ecs::ecs_tick(const double duration_ms) { 64 | std::size_t count = 0; 65 | for (std::unique_ptr & sys : system_store) { 66 | std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now(); 67 | sys->update(duration_ms); 68 | deliver_messages(); 69 | std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now(); 70 | double duration = static_cast(std::chrono::duration_cast( t2 - t1 ).count()); 71 | 72 | system_profiling[count].last = duration; 73 | if (duration > system_profiling[count].worst) system_profiling[count].worst = duration; 74 | if (duration < system_profiling[count].best) system_profiling[count].best = duration; 75 | ++count; 76 | } 77 | ecs_garbage_collect(); 78 | } 79 | 80 | void ecs::ecs_save(std::unique_ptr &lbfile) { 81 | cereal::BinaryOutputArchive oarchive(*lbfile); 82 | oarchive(*this); 83 | } 84 | 85 | void ecs::ecs_load(std::unique_ptr &lbfile) { 86 | entity_store.clear(); 87 | component_store.clear(); 88 | cereal::BinaryInputArchive iarchive(*lbfile); 89 | iarchive(*this); 90 | std::cout << "Loaded " << entity_store.size() << " entities, and " << component_store.size() << " component types.\n"; 91 | } 92 | 93 | std::string ecs::ecs_profile_dump() { 94 | std::stringstream ss; 95 | ss.precision(3); 96 | ss << std::fixed; 97 | ss << "SYSTEMS PERFORMANCE IN MICROSECONDS:\n"; 98 | ss << std::setw(20) << "System" << std::setw(20) << "Last" << std::setw(20) << "Best" << std::setw(20) << "Worst\n"; 99 | for (std::size_t i=0; isystem_name 101 | << std::setw(20) << system_profiling[i].last 102 | << std::setw(20) << system_profiling[i].best 103 | << std::setw(20) << system_profiling[i].worst << "\n"; 104 | } 105 | return ss.str(); 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /rltk/ecs.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include "serialization_utils.hpp" 21 | #include "xml.hpp" 22 | #include 23 | #include "ecs_impl.hpp" 24 | 25 | namespace rltk { 26 | 27 | /* Public interface to allow existing calls to continue to work */ 28 | 29 | extern ecs default_ecs; 30 | 31 | inline entity_t * entity(ecs &ECS, const std::size_t id) noexcept { 32 | return ECS.entity(id); 33 | } 34 | 35 | inline entity_t * entity(const std::size_t id) noexcept { 36 | return entity(default_ecs, id); 37 | } 38 | 39 | inline entity_t * create_entity(ecs &ECS) { 40 | return ECS.create_entity(); 41 | } 42 | 43 | inline entity_t * create_entity() { 44 | return create_entity(default_ecs); 45 | } 46 | 47 | inline entity_t * create_entity(ecs &ECS, const std::size_t new_id) { 48 | return ECS.create_entity(new_id); 49 | } 50 | 51 | inline entity_t * create_entity(const std::size_t new_id) { 52 | return create_entity(default_ecs, new_id); 53 | } 54 | 55 | inline void delete_entity(ecs &ECS, const std::size_t id) noexcept { 56 | ECS.delete_entity(id); 57 | } 58 | 59 | inline void delete_entity(const std::size_t id) noexcept { 60 | delete_entity(default_ecs, id); 61 | } 62 | 63 | inline void delete_entity(ecs &ECS, entity_t &e) noexcept { 64 | ECS.delete_entity(e); 65 | } 66 | 67 | inline void delete_entity(entity_t &e) noexcept { 68 | delete_entity(default_ecs, e); 69 | } 70 | 71 | inline void delete_all_entities(ecs &ECS) noexcept { 72 | ECS.delete_all_entities(); 73 | } 74 | 75 | inline void delete_all_entities() noexcept { 76 | delete_all_entities(default_ecs); 77 | } 78 | 79 | template 80 | inline void delete_component(ecs &ECS, const std::size_t entity_id, bool delete_entity_if_empty=false) noexcept { 81 | ECS.delete_component(entity_id, delete_entity_if_empty); 82 | } 83 | 84 | template 85 | inline void delete_component(const std::size_t entity_id, bool delete_entity_if_empty=false) noexcept { 86 | delete_component(default_ecs, entity_id, delete_entity_if_empty); 87 | } 88 | 89 | template 90 | inline std::vector entities_with_component(ecs &ECS) { 91 | return ECS.entities_with_component(); 92 | } 93 | 94 | template 95 | inline std::vector entities_with_component() { 96 | return entities_with_component(default_ecs); 97 | } 98 | 99 | template 100 | inline void all_components(ecs &ECS, typename std::function func) { 101 | ECS.all_components(func); 102 | } 103 | 104 | template 105 | inline void all_components(typename std::function func) { 106 | all_components(default_ecs, func); 107 | } 108 | 109 | template 110 | inline void each(ecs &ECS, F callback) { 111 | ECS.each(callback); 112 | } 113 | 114 | template 115 | inline void each(F callback) { 116 | each(default_ecs, callback); 117 | } 118 | 119 | template 120 | inline void each_if(ecs &ECS, P&& predicate, F callback) { 121 | ECS.each_if(predicate, callback); 122 | } 123 | 124 | template 125 | inline void each_if(P&& predicate, F callback) { 126 | each_if(default_ecs, predicate, callback); 127 | } 128 | 129 | inline void ecs_garbage_collect(ecs &ECS) { 130 | ECS.ecs_garbage_collect(); 131 | } 132 | 133 | inline void ecs_garbage_collect() { 134 | ecs_garbage_collect(default_ecs); 135 | } 136 | 137 | template 138 | inline void emit(ecs &ECS, MSG message) { 139 | ECS.emit(message); 140 | } 141 | 142 | template 143 | inline void emit(MSG message) { 144 | default_ecs.emit(message); 145 | } 146 | 147 | template 148 | inline void emit_deferred(ecs &ECS, MSG message) { 149 | ECS.emit_deferred(message); 150 | } 151 | 152 | template 153 | inline void emit_deferred(MSG message) { 154 | emit_deferred(default_ecs, message); 155 | } 156 | 157 | template 158 | inline void add_system( ecs &ECS, Args && ... args ) { 159 | ECS.add_system(args...); 160 | } 161 | 162 | template 163 | inline void add_system( Args && ... args ) { 164 | add_system(default_ecs, args...); 165 | } 166 | 167 | inline void delete_all_systems(ecs &ECS) { 168 | ECS.delete_all_systems(); 169 | } 170 | 171 | inline void delete_all_systems() { 172 | delete_all_systems(default_ecs); 173 | } 174 | 175 | inline void ecs_configure(ecs &ECS) { 176 | ECS.ecs_configure(); 177 | } 178 | 179 | inline void ecs_configure() { 180 | ecs_configure(default_ecs); 181 | } 182 | 183 | inline void ecs_tick(ecs &ECS, const double duration_ms) { 184 | ECS.ecs_tick(duration_ms); 185 | } 186 | 187 | inline void ecs_tick(const double duration_ms) { 188 | ecs_tick(default_ecs, duration_ms); 189 | } 190 | 191 | inline void ecs_save(ecs &ECS, std::unique_ptr &lbfile) { 192 | ECS.ecs_save(lbfile); 193 | } 194 | 195 | inline void ecs_save(std::unique_ptr &lbfile) { 196 | ecs_save(default_ecs, lbfile); 197 | } 198 | 199 | inline void ecs_load(ecs &ECS, std::unique_ptr &lbfile) { 200 | ECS.ecs_load(lbfile); 201 | } 202 | 203 | inline void ecs_load(std::unique_ptr &lbfile) { 204 | ecs_load(default_ecs, lbfile); 205 | } 206 | 207 | inline std::string ecs_profile_dump(ecs &ECS) { 208 | return ECS.ecs_profile_dump(); 209 | } 210 | 211 | inline std::string ecs_profile_dump() { 212 | return ecs_profile_dump(default_ecs); 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /rltk/filesystem.hpp: -------------------------------------------------------------------------------- 1 | /* RLTK (RogueLike Tool Kit) 1.00 2 | * Copyright (c) 2016-Present, Bracket Productions. 3 | * Licensed under the MIT license - see LICENSE file. 4 | * 5 | * Minimal filesystem tools 6 | */ 7 | #pragma once 8 | 9 | #include 10 | #include 11 | 12 | namespace rltk { 13 | 14 | inline bool exists(const std::string &filename) noexcept { 15 | struct stat buffer; 16 | return (stat (filename.c_str(), &buffer) == 0); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /rltk/font_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "font_manager.hpp" 2 | #include "texture_resources.hpp" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "filesystem.hpp" 9 | 10 | namespace rltk { 11 | 12 | namespace font_detail { 13 | 14 | std::vector split ( const std::string &str, const char &delimiter ) 15 | { 16 | std::vector internal; 17 | std::stringstream ss ( str ); // Turn the string into a stream. 18 | std::string tok; 19 | 20 | while ( getline ( ss, tok, delimiter ) ) { 21 | internal.push_back ( tok ); 22 | } 23 | 24 | return internal; 25 | } 26 | 27 | std::unordered_map atlas; 28 | } 29 | 30 | bitmap_font * get_font(const std::string font_tag) { 31 | auto finder = font_detail::atlas.find(font_tag); 32 | if (finder == font_detail::atlas.end()) { 33 | throw std::runtime_error("Unable to locate bitmap font with tag " + font_tag); 34 | } else { 35 | return &finder->second; 36 | } 37 | } 38 | 39 | inline void check_for_duplicate_font(const std::string &tag) { 40 | auto finder = font_detail::atlas.find(tag); 41 | if (finder != font_detail::atlas.end()) { 42 | throw std::runtime_error("Attempting to insert duplicate font with tag " + tag); 43 | } 44 | } 45 | 46 | inline void check_texture_exists(const std::string &texture_tag) { 47 | if (get_texture(texture_tag) == nullptr) { 48 | throw std::runtime_error("No such texture resource: " + texture_tag); 49 | } 50 | } 51 | 52 | void register_font(const std::string font_tag, const std::string filename, int width, int height) { 53 | const std::string texture_tag = "font_tex_" + filename; 54 | check_for_duplicate_font(font_tag); 55 | register_texture(filename, texture_tag); 56 | check_texture_exists(texture_tag); 57 | font_detail::atlas.emplace(std::make_pair(font_tag, bitmap_font(texture_tag, width, height))); 58 | } 59 | 60 | void register_font_directory(const std::string path) { 61 | if (!exists(path)) throw std::runtime_error("Font directory does not exist."); 62 | const std::string info_file = path + "/fonts.txt"; 63 | if (!exists(info_file)) throw std::runtime_error("No fonts.txt file in font directory."); 64 | 65 | std::ifstream f(info_file); 66 | std::string line; 67 | while (getline(f, line)) { 68 | auto split = font_detail::split(line, ','); 69 | if (split.size() == 4) { 70 | register_font(split[0], path + "/" + split[1], std::stoi(split[2]), std::stoi(split[3])); 71 | } 72 | } 73 | 74 | /* 75 | ptree font_tree; 76 | read_json(info_file, font_tree); 77 | 78 | ptree::const_iterator end = font_tree.get_child("fonts").end(); 79 | for (ptree::const_iterator it = font_tree.get_child("fonts").begin(); it != end; ++it) { 80 | const std::string font_name = it->first; 81 | const std::string font_tree_path = "fonts." + font_name + "."; 82 | const std::string font_file = font_tree.get(font_tree_path + "file"); 83 | const int width = font_tree.get(font_tree_path + "width"); 84 | const int height = font_tree.get(font_tree_path + "height"); 85 | 86 | register_font(font_name, path + "/" + font_file, width, height); 87 | }*/ 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /rltk/font_manager.hpp: -------------------------------------------------------------------------------- 1 | /* RLTK (RogueLike Tool Kit) 1.00 2 | * Copyright (c) 2016-Present, Bracket Productions. 3 | * Licensed under the MIT license - see LICENSE file. 4 | * 5 | * Font manager 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | namespace rltk { 14 | 15 | struct bitmap_font { 16 | bitmap_font(const std::string tag, const int width, const int height) : texture_tag(tag), character_size({width,height}) {} 17 | 18 | const std::string texture_tag; 19 | const std::pair character_size; 20 | }; 21 | 22 | void register_font_directory(const std::string path); 23 | bitmap_font * get_font(const std::string font_tag); 24 | void register_font(const std::string font_tag, const std::string filename, int width=8, int height=8); 25 | 26 | 27 | } -------------------------------------------------------------------------------- /rltk/fsa.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | A* Algorithm Implementation using STL is 4 | Copyright (C)2001-2005 Justin Heyes-Jones 5 | 6 | Permission is given by the author to freely redistribute and 7 | include this code in any program as long as this credit is 8 | given where due. 9 | 10 | COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, 11 | WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, 12 | INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE 13 | IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE 14 | OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND 15 | PERFORMANCE OF THE COVERED CODE IS WITH YOU. SHOULD ANY COVERED 16 | CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL 17 | DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY 18 | NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF 19 | WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE 20 | OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER 21 | THIS DISCLAIMER. 22 | 23 | Use at your own risk! 24 | 25 | 26 | 27 | FixedSizeAllocator class 28 | Copyright 2001 Justin Heyes-Jones 29 | 30 | This class is a constant time O(1) memory manager for objects of 31 | a specified type. The type is specified using a template class. 32 | 33 | Memory is allocated from a fixed size buffer which you can specify in the 34 | class constructor or use the default. 35 | 36 | Using GetFirst and GetNext it is possible to iterate through the elements 37 | one by one, and this would be the most common use for the class. 38 | 39 | I would suggest using this class when you want O(1) add and delete 40 | and you don't do much searching, which would be O(n). Structures such as binary 41 | trees can be used instead to get O(logn) access time. 42 | 43 | */ 44 | 45 | #pragma once 46 | 47 | #include 48 | #include 49 | 50 | template class FixedSizeAllocator 51 | { 52 | 53 | public: 54 | // Constants 55 | enum 56 | { 57 | FSA_DEFAULT_SIZE = 100 58 | }; 59 | 60 | // This class enables us to transparently manage the extra data 61 | // needed to enable the user class to form part of the double-linked 62 | // list class 63 | struct FSA_ELEMENT 64 | { 65 | USER_TYPE UserType; 66 | 67 | FSA_ELEMENT *pPrev; 68 | FSA_ELEMENT *pNext; 69 | }; 70 | 71 | public: 72 | // methods 73 | FixedSizeAllocator(unsigned int MaxElements = FSA_DEFAULT_SIZE) : 74 | m_pFirstUsed(NULL), m_MaxElements(MaxElements) 75 | { 76 | // Allocate enough memory for the maximum number of elements 77 | 78 | char *pMem = new char[m_MaxElements * sizeof(FSA_ELEMENT)]; 79 | 80 | m_pMemory = (FSA_ELEMENT *) pMem; 81 | 82 | // Set the free list first pointer 83 | m_pFirstFree = m_pMemory; 84 | 85 | // Clear the memory 86 | memset(m_pMemory, 0, sizeof(FSA_ELEMENT) * m_MaxElements); 87 | 88 | // Point at first element 89 | FSA_ELEMENT *pElement = m_pFirstFree; 90 | 91 | // Set the double linked free list 92 | for (unsigned int i = 0; i < m_MaxElements; i++) 93 | { 94 | pElement->pPrev = pElement - 1; 95 | pElement->pNext = pElement + 1; 96 | 97 | pElement++; 98 | } 99 | 100 | // first element should have a null prev 101 | m_pFirstFree->pPrev = NULL; 102 | // last element should have a null next 103 | (pElement - 1)->pNext = NULL; 104 | 105 | } 106 | 107 | ~FixedSizeAllocator() 108 | { 109 | // Free up the memory 110 | delete[] (char *) m_pMemory; 111 | } 112 | 113 | // Allocate a new USER_TYPE and return a pointer to it 114 | USER_TYPE *alloc() 115 | { 116 | 117 | FSA_ELEMENT *pNewNode = NULL; 118 | 119 | if (!m_pFirstFree) 120 | { 121 | return NULL; 122 | } 123 | else 124 | { 125 | pNewNode = m_pFirstFree; 126 | m_pFirstFree = pNewNode->pNext; 127 | 128 | // if the new node points to another free node then 129 | // change that nodes prev free pointer... 130 | if (pNewNode->pNext) 131 | { 132 | pNewNode->pNext->pPrev = NULL; 133 | } 134 | 135 | // node is now on the used list 136 | 137 | pNewNode->pPrev = NULL; // the allocated node is always first in the list 138 | 139 | if (m_pFirstUsed == NULL) 140 | { 141 | pNewNode->pNext = NULL; // no other nodes 142 | } 143 | else 144 | { 145 | m_pFirstUsed->pPrev = pNewNode; // insert this at the head of the used list 146 | pNewNode->pNext = m_pFirstUsed; 147 | } 148 | 149 | m_pFirstUsed = pNewNode; 150 | } 151 | 152 | return reinterpret_cast(pNewNode); 153 | } 154 | 155 | // Free the given user type 156 | // For efficiency I don't check whether the user_data is a valid 157 | // pointer that was allocated. I may add some debug only checking 158 | // (To add the debug check you'd need to make sure the pointer is in 159 | // the m_pMemory area and is pointing at the start of a node) 160 | void free(USER_TYPE *user_data) 161 | { 162 | FSA_ELEMENT *pNode = reinterpret_cast(user_data); 163 | 164 | // manage used list, remove this node from it 165 | if (pNode->pPrev) 166 | { 167 | pNode->pPrev->pNext = pNode->pNext; 168 | } 169 | else 170 | { 171 | // this handles the case that we delete the first node in the used list 172 | m_pFirstUsed = pNode->pNext; 173 | } 174 | 175 | if (pNode->pNext) 176 | { 177 | pNode->pNext->pPrev = pNode->pPrev; 178 | } 179 | 180 | // add to free list 181 | if (m_pFirstFree == NULL) 182 | { 183 | // free list was empty 184 | m_pFirstFree = pNode; 185 | pNode->pPrev = NULL; 186 | pNode->pNext = NULL; 187 | } 188 | else 189 | { 190 | // Add this node at the start of the free list 191 | m_pFirstFree->pPrev = pNode; 192 | pNode->pNext = m_pFirstFree; 193 | m_pFirstFree = pNode; 194 | } 195 | 196 | } 197 | 198 | // For debugging this displays both lists (using the prev/next list pointers) 199 | void Debug() 200 | { 201 | printf("free list "); 202 | 203 | FSA_ELEMENT *p = m_pFirstFree; 204 | while (p) 205 | { 206 | printf("%x!%x ", p->pPrev, p->pNext); 207 | p = p->pNext; 208 | } 209 | printf("\n"); 210 | 211 | printf("used list "); 212 | 213 | p = m_pFirstUsed; 214 | while (p) 215 | { 216 | printf("%x!%x ", p->pPrev, p->pNext); 217 | p = p->pNext; 218 | } 219 | printf("\n"); 220 | } 221 | 222 | // Iterators 223 | 224 | USER_TYPE *GetFirst() 225 | { 226 | return reinterpret_cast(m_pFirstUsed); 227 | } 228 | 229 | USER_TYPE *GetNext(USER_TYPE *node) 230 | { 231 | return reinterpret_cast((reinterpret_cast(node))->pNext); 232 | } 233 | 234 | public: 235 | // data 236 | 237 | private: 238 | // methods 239 | 240 | private: 241 | // data 242 | 243 | FSA_ELEMENT *m_pFirstFree; 244 | FSA_ELEMENT *m_pFirstUsed; 245 | unsigned int m_MaxElements; 246 | FSA_ELEMENT *m_pMemory; 247 | 248 | }; 249 | -------------------------------------------------------------------------------- /rltk/geometry.cpp: -------------------------------------------------------------------------------- 1 | #include "geometry.hpp" 2 | #include 3 | #include 4 | 5 | namespace rltk { 6 | 7 | /* 8 | * From a given point x/y, project forward radius units (generally tiles) at an angle of degrees_radians degrees 9 | * (in radians). 10 | */ 11 | std::pair project_angle(const int &x, const int &y, const double &radius, const double °rees_radians) noexcept 12 | { 13 | return std::make_pair(static_cast(x + radius * std::cos(degrees_radians)), static_cast(y + radius * std::sin(degrees_radians))); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /rltk/geometry.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* RLTK (RogueLike Tool Kit) 1.00 4 | * Copyright (c) 2016-Present, Bracket Productions. 5 | * Licensed under the MIT license - see LICENSE file. 6 | * 7 | * Random number generator class. 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace rltk { 15 | 16 | /* 17 | * From a given point x/y, project forward radius units (generally tiles) at an angle of degrees_radians degrees 18 | * (in radians). 19 | */ 20 | std::pair project_angle(const int &x, const int &y, const double &radius, const double °rees_radians) noexcept; 21 | 22 | /* 23 | * Provides a correct 2D distance between two points. 24 | */ 25 | inline float distance2d(const int &x1, const int &y1, const int &x2, const int &y2) noexcept { 26 | const float dx = (float)x1 - (float)x2; 27 | const float dy = (float)y1 - (float)y2; 28 | return std::sqrt((dx*dx) + (dy*dy)); 29 | } 30 | 31 | /* 32 | * Provides a fast 2D distance between two points, omitting the square-root; compare 33 | * with other squared distances. 34 | */ 35 | inline float distance2d_squared(const int &x1, const int &y1, const int &x2, const int &y2) noexcept { 36 | const float dx = (float)x1 - (float)x2; 37 | const float dy = (float)y1 - (float)y2; 38 | return (dx*dx) + (dy*dy); 39 | } 40 | 41 | /* 42 | * Provides 2D Manhattan distance between two points. 43 | */ 44 | inline float distance2d_manhattan(const int &x1, const int &y1, const int &x2, const int &y2) noexcept { 45 | const float dx = (float)x1 - (float)x2; 46 | const float dy = (float)y1 - (float)y2; 47 | return std::abs(dx) + std::abs(dy); 48 | } 49 | 50 | /* 51 | * Provides a correct 3D distance between two points. 52 | */ 53 | inline float distance3d(const int &x1, const int &y1, const int &z1, const int &x2, const int &y2, const int &z2) noexcept 54 | { 55 | const float dx = (float)x1 - (float)x2; 56 | const float dy = (float)y1 - (float)y2; 57 | const float dz = (float)z1 - (float)z2; 58 | return std::sqrt((dx*dx) + (dy*dy) + (dz*dz)); 59 | } 60 | 61 | /* 62 | * Provides a fast 3D distance between two points, omitting the square-root; compare 63 | * with other squared distances. 64 | */ 65 | inline float distance3d_squared(const int &x1, const int &y1, const int &z1, const int &x2, const int &y2, const int &z2) noexcept 66 | { 67 | float dx = (float)x1 - (float)x2; 68 | float dy = (float)y1 - (float)y2; 69 | float dz = (float)z1 - (float)z2; 70 | return (dx*dx) + (dy*dy) + (dz*dz); 71 | } 72 | 73 | /* 74 | * Provides Manhattan distance between two 3D points. 75 | */ 76 | inline float distance3d_manhattan(const int &x1, const int &y1, const int &z1, const int &x2, const int &y2, const int &z2) noexcept 77 | { 78 | const float dx = (float)x1 - (float)x2; 79 | const float dy = (float)y1 - (float)y2; 80 | const float dz = (float)z1 - (float)z2; 81 | return std::abs(dx) + std::abs(dy) + std::abs(dz); 82 | } 83 | 84 | /* 85 | * Perform a function for each line element between x1/y1 and x2/y2. We used to use Bresenham's line, 86 | * but benchmarking showed a simple float-based setup to be faster. 87 | */ 88 | inline void line_func(const int &x1, const int &y1, const int &x2, const int &y2, std::function &&func) noexcept 89 | { 90 | float x = static_cast(x1) + 0.5F; 91 | float y = static_cast(y1) + 0.5F; 92 | const float dest_x = static_cast(x2); 93 | const float dest_y = static_cast(y2); 94 | const float n_steps = distance2d(x1,y1,x2,y2); 95 | const int steps = static_cast(std::floor(n_steps) + 1); 96 | const float slope_x = (dest_x - x) / n_steps; 97 | const float slope_y = (dest_y - y) / n_steps; 98 | 99 | for (int i = 0; i < steps; ++i) { 100 | func(static_cast(x), static_cast(y)); 101 | x += slope_x; 102 | y += slope_y; 103 | } 104 | } 105 | 106 | /* 107 | * Perform a function for each line element between x1/y1/z1 and x2/y2/z2. Uses a 3D 108 | * implementation of Bresenham's line algorithm. 109 | * https://gist.github.com/yamamushi/5823518 110 | */ 111 | template 112 | void line_func_3d(const int &x1, const int &y1, const int &z1, const int &x2, const int &y2, const int &z2, F &&func) noexcept 113 | { 114 | float x = static_cast(x1)+0.5F; 115 | float y = static_cast(y1)+0.5F; 116 | float z = static_cast(z1)+0.5F; 117 | 118 | float length = distance3d(x1, y1, z1, x2, y2, z2); 119 | int steps = static_cast(std::floor(length)); 120 | float x_step = (x - x2) / length; 121 | float y_step = (y - y2) / length; 122 | float z_step = (z - z2) / length; 123 | 124 | for (int i=0; i(std::floor(x)), static_cast(std::floor(y)), static_cast(std::floor(z))); 129 | } 130 | } 131 | 132 | /* 133 | * Perform a function for each line element between x1/y1 and x2/y2. We used to use Bresenham's algorithm, 134 | * but benchmarking showed that a simple float based vector was faster. 135 | */ 136 | template 137 | inline void line_func_cancellable(const int &x1, const int &y1, const int &x2, const int &y2, F &&func) noexcept { 138 | float x = static_cast(x1) + 0.5F; 139 | float y = static_cast(y1) + 0.5F; 140 | const float dest_x = static_cast(x2); 141 | const float dest_y = static_cast(y2); 142 | const float n_steps = distance2d(x1,y1,x2,y2); 143 | const int steps = static_cast(std::floor(n_steps) + 1); 144 | const float slope_x = (dest_x - x) / n_steps; 145 | const float slope_y = (dest_y - y) / n_steps; 146 | 147 | for (int i = 0; i < steps; ++i) { 148 | if (!func(static_cast(x), static_cast(y))) return; 149 | x += slope_x; 150 | y += slope_y; 151 | } 152 | } 153 | 154 | /* 155 | * Perform a function for each line element between x1/y1/z1 and x2/y2/z2. Uses a 3D 156 | * implementation of Bresenham's line algorithm. 157 | * https://gist.github.com/yamamushi/5823518 158 | */ 159 | template 160 | void line_func_3d_cancellable(const int &x1, const int &y1, const int &z1, const int &x2, const int &y2, const int &z2, F &&func) noexcept 161 | { 162 | float x = static_cast(x1)+0.5F; 163 | float y = static_cast(y1)+0.5F; 164 | float z = static_cast(z1)+0.5F; 165 | 166 | float length = distance3d(x1, y1, z1, x2, y2, z2); 167 | int steps = static_cast(std::floor(length)); 168 | float x_step = (x - x2) / length; 169 | float y_step = (y - y2) / length; 170 | float z_step = (z - z2) / length; 171 | 172 | for (int i=0; i(std::floor(x)), static_cast(std::floor(y)), static_cast(std::floor(z))); 177 | if (!keep_going) return; 178 | } 179 | } 180 | } -------------------------------------------------------------------------------- /rltk/gui.cpp: -------------------------------------------------------------------------------- 1 | #include "gui.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | namespace rltk { 7 | 8 | namespace gui_detail { 9 | 10 | std::vector> render_order; 11 | 12 | } 13 | 14 | void gui_t::on_resize(const int w, const int h) { 15 | screen_width = w; 16 | screen_height = h; 17 | 18 | for (auto it = layers.begin(); it != layers.end(); ++it) { 19 | it->second.on_resize(w, h); 20 | } 21 | } 22 | 23 | void gui_t::render(sf::RenderWindow &window) { 24 | for (auto l : gui_detail::render_order) { 25 | l.second->render(window); 26 | } 27 | } 28 | 29 | void gui_t::add_layer(const int handle, const int X, const int Y, const int W, const int H, 30 | std::string font_name, std::function resize_fun, bool has_background, 31 | int order) 32 | { 33 | check_handle_uniqueness(handle); 34 | layers.emplace(std::make_pair(handle, layer_t(X, Y, W, H, font_name, resize_fun, has_background))); 35 | if (order == -1) { 36 | order = render_order; 37 | ++render_order; 38 | } 39 | gui_detail::render_order.push_back(std::make_pair(order, get_layer(handle))); 40 | std::sort(gui_detail::render_order.begin(), gui_detail::render_order.end(), 41 | [] (std::pair a, std::pair b) 42 | { 43 | return a.first < b.first; 44 | } 45 | ); 46 | } 47 | 48 | void gui_t::add_sparse_layer(const int handle, const int X, const int Y, const int W, const int H, 49 | std::string font_name, std::function resize_fun, int order) 50 | { 51 | check_handle_uniqueness(handle); 52 | layers.emplace(std::make_pair(handle, layer_t(true, X, Y, W, H, font_name, resize_fun))); 53 | if (order == -1) { 54 | order = render_order; 55 | ++render_order; 56 | } 57 | gui_detail::render_order.push_back(std::make_pair(order, get_layer(handle))); 58 | std::sort(gui_detail::render_order.begin(), gui_detail::render_order.end(), 59 | [] (std::pair a, std::pair b) 60 | { 61 | return a.first < b.first; 62 | } 63 | ); 64 | } 65 | 66 | void gui_t::add_owner_layer(const int handle, const int X, const int Y, const int W, const int H, 67 | std::function resize_fun, std::function owner_draw_fun, int order) 68 | { 69 | check_handle_uniqueness(handle); 70 | layers.emplace(std::make_pair(handle, layer_t(X, Y, W, H, resize_fun, owner_draw_fun))); 71 | if (order == -1) { 72 | order = render_order; 73 | ++render_order; 74 | } 75 | gui_detail::render_order.push_back(std::make_pair(order, get_layer(handle))); 76 | std::sort(gui_detail::render_order.begin(), gui_detail::render_order.end(), 77 | [] (std::pair a, std::pair b) 78 | { 79 | return a.first < b.first; 80 | } 81 | ); 82 | } 83 | 84 | 85 | void gui_t::delete_layer(const int handle) { 86 | gui_detail::render_order.erase(std::remove_if(gui_detail::render_order.begin(), gui_detail::render_order.end(), 87 | [&handle, this] (std::pair a) 88 | { 89 | if (a.second == this->get_layer(handle)) return true; 90 | return false; 91 | } 92 | ), gui_detail::render_order.end()); 93 | layers.erase(handle); 94 | } 95 | 96 | layer_t * gui_t::get_layer(const int handle) { 97 | auto finder = layers.find(handle); 98 | if (finder == layers.end()) throw std::runtime_error("Unknown layer handle: " + std::to_string(handle)); 99 | return &(finder->second); 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /rltk/gui.hpp: -------------------------------------------------------------------------------- 1 | /* RLTK (RogueLike Tool Kit) 1.00 2 | * Copyright (c) 2016-Present, Bracket Productions. 3 | * Licensed under the MIT license - see LICENSE file. 4 | * 5 | * Provides support for complicated GUIs. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "layer_t.hpp" 17 | 18 | namespace rltk { 19 | 20 | /* 21 | * The overall GUI - holds layers and handles render calls. Access via rltk::gui 22 | */ 23 | struct gui_t { 24 | public: 25 | gui_t(const int w, const int h) : screen_width(w), screen_height(h) {} 26 | void on_resize(const int w, const int h); 27 | void render(sf::RenderWindow &window); 28 | 29 | // Specialization for adding console layers 30 | void add_layer(const int handle, const int X, const int Y, const int W, const int H, std::string font_name, std::function resize_fun, bool has_background=true, int order=-1); 31 | 32 | // Specialization for sparse layers 33 | void add_sparse_layer(const int handle, const int X, const int Y, const int W, const int H, std::string font_name, std::function resize_fun, int order=-1); 34 | 35 | // Specialization for adding owner-draw layers 36 | void add_owner_layer(const int handle, const int X, const int Y, const int W, const int H, std::function resize_fun, std::function owner_draw_fun, int order=-1); 37 | void delete_layer(const int handle); 38 | layer_t * get_layer(const int handle); 39 | 40 | private: 41 | int screen_width; 42 | int screen_height; 43 | int render_order = 0; 44 | 45 | std::unordered_map layers; 46 | 47 | inline void check_handle_uniqueness(const int handle) { 48 | auto finder = layers.find(handle); 49 | if (finder != layers.end()) throw std::runtime_error("Adding a duplicate layer handle: " + std::to_string(handle)); 50 | } 51 | }; 52 | 53 | } -------------------------------------------------------------------------------- /rltk/gui_control_t.cpp: -------------------------------------------------------------------------------- 1 | #include "gui_control_t.hpp" 2 | #include 3 | #include 4 | 5 | namespace rltk { 6 | 7 | void gui_static_text_t::render(virtual_terminal * console) { 8 | console->print(x, y, text, foreground, background); 9 | } 10 | 11 | void gui_border_box_t::render(virtual_terminal * console) { 12 | console->box(foreground, background, is_double); 13 | } 14 | 15 | void gui_checkbox_t::render(virtual_terminal * console) { 16 | console->set_char(x, y, vchar{'[', foreground, background}); 17 | if (checked) { 18 | console->set_char(x+1, y, vchar{'X', foreground, background}); 19 | } else { 20 | console->set_char(x+1, y, vchar{' ', foreground, background}); 21 | } 22 | console->print(x+2, y, "] " + label, foreground, background); 23 | } 24 | 25 | void gui_radiobuttons_t::render(virtual_terminal * console) { 26 | console->print(x, y, caption, foreground, background); 27 | int current_y = y+1; 28 | for (const radio &r : options) { 29 | console->set_char(x, current_y, vchar{'(', foreground, background}); 30 | if (r.checked) { 31 | console->set_char(x+1, current_y, vchar{'*', foreground, background}); 32 | } else { 33 | console->set_char(x+1, current_y, vchar{' ', foreground, background}); 34 | } 35 | console->print(x+2, current_y, ") " + r.label, foreground, background); 36 | ++current_y; 37 | } 38 | } 39 | 40 | void gui_hbar_t::render(virtual_terminal * console) { 41 | float fullness = (float)(value - min) / (float)max; 42 | float full_w_f = fullness * (float)w; 43 | std::size_t full_w = static_cast(full_w_f); 44 | 45 | std::stringstream ss; 46 | for (std::size_t i=0; i((w/2) - (tmp.size() / 2)); 55 | for (std::size_t i=0; i < std::min(tmp.size(),w); ++i) { 56 | s[i + start] = tmp[i]; 57 | } 58 | 59 | for (std::size_t i=0; iset_char(x+i, y, vchar{s[i], text_color, lerp(full_start, full_end, pct)}); 63 | } else { 64 | console->set_char(x+i, y, vchar{s[i], text_color, lerp(empty_start, empty_end, pct)}); 65 | } 66 | } 67 | } 68 | 69 | void gui_vbar_t::render(virtual_terminal * console) { 70 | float fullness = (float)(value - min) / (float)max; 71 | float full_h_f = fullness * (float)h; 72 | std::size_t full_h = static_cast(full_h_f); 73 | 74 | std::stringstream ss; 75 | for (std::size_t i=0; i((h/2) - (tmp.size() / 2)); 84 | for (std::size_t i=0; i < std::min(tmp.size(), h); ++i) { 85 | s[i + start] = tmp[i]; 86 | } 87 | 88 | for (std::size_t i=0; iset_char(x, y+i, vchar{s[i], text_color, lerp(full_start, full_end, pct)}); 92 | } else { 93 | console->set_char(x, y+i, vchar{s[i], text_color, lerp(empty_start, empty_end, pct)}); 94 | } 95 | } 96 | } 97 | 98 | void gui_listbox_t::render(virtual_terminal * console) { 99 | console->box(x, y, w+3, static_cast(items.size())+1, caption_fg, caption_bg, false); 100 | console->print(x+3, y, caption, caption_fg, caption_bg); 101 | console->set_char(x+1, y, vchar{180, caption_fg, caption_bg}); 102 | console->set_char(x+2, y, vchar{' ', caption_fg, caption_bg}); 103 | console->set_char(x+w, y, vchar{' ', caption_fg, caption_bg}); 104 | console->set_char(x+w+1, y, vchar{195, caption_fg, caption_bg}); 105 | int current_y = y+1; 106 | for (const list_item &item : items) { 107 | if (item.value == selected_value) { 108 | console->print(x+2, current_y, item.label, selected_fg, selected_bg); 109 | } else { 110 | console->print(x+2, current_y, item.label, item_fg, item_bg); 111 | } 112 | ++current_y; 113 | } 114 | } 115 | 116 | } -------------------------------------------------------------------------------- /rltk/gui_control_t.hpp: -------------------------------------------------------------------------------- 1 | /* RLTK (RogueLike Tool Kit) 1.00 2 | * Copyright (c) 2016-Present, Bracket Productions. 3 | * Licensed under the MIT license - see LICENSE file. 4 | * 5 | * Retained mode GUI controls 6 | */ 7 | #pragma once 8 | 9 | #include "virtual_terminal.hpp" 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | // TODO: Text box, display rich text, combo box, slider, spinner 16 | 17 | namespace rltk { 18 | 19 | /* 20 | * Base type for retained-mode GUI controls. 21 | */ 22 | struct gui_control_t { 23 | virtual void render(virtual_terminal * console)=0; 24 | virtual bool mouse_in_control(const int tx, const int ty) { return false; } 25 | 26 | // Callbacks 27 | std::function on_render_start = nullptr; 28 | std::function on_mouse_over = nullptr; 29 | std::function on_mouse_down = nullptr; 30 | std::function on_mouse_up = nullptr; 31 | }; 32 | 33 | struct gui_static_text_t : public gui_control_t { 34 | gui_static_text_t(const int X, const int Y, const std::string txt, const color_t fg, const color_t bg) : 35 | text(txt), x(X), y(Y), foreground(fg), background(bg) {} 36 | 37 | std::string text = ""; 38 | int x=0; 39 | int y=0; 40 | color_t foreground; 41 | color_t background; 42 | 43 | virtual void render(virtual_terminal * console) override; 44 | virtual bool mouse_in_control(const int tx, const int ty) override { return (tx >= x && tx <= x + (static_cast(text.size())) && ty==y); } 45 | }; 46 | 47 | struct gui_border_box_t : public gui_control_t { 48 | gui_border_box_t(const bool double_lines, const color_t fg, const color_t bg) : 49 | is_double(double_lines), foreground(fg), background(bg) {} 50 | 51 | bool is_double = false; 52 | color_t foreground; 53 | color_t background; 54 | 55 | virtual void render(virtual_terminal * console) override; 56 | }; 57 | 58 | struct gui_checkbox_t : public gui_control_t { 59 | gui_checkbox_t(const int X, const int Y, const bool is_checked, const std::string text, const color_t fg, const color_t bg) : 60 | checked(is_checked), label(text), foreground(fg), background(bg), x(X), y(Y) { 61 | on_mouse_down = [] (gui_control_t * ctrl, int tx, int ty) { 62 | gui_checkbox_t * me = static_cast(ctrl); 63 | me->click_started = true; 64 | }; 65 | on_mouse_up = [] (gui_control_t * ctrl, int tx, int ty) { 66 | gui_checkbox_t * me = static_cast(ctrl); 67 | if (me->click_started) { 68 | me->checked = !me->checked; 69 | } 70 | me->click_started = false; 71 | }; 72 | } 73 | 74 | bool checked = false; 75 | std::string label; 76 | color_t foreground; 77 | color_t background; 78 | int x=0; 79 | int y=0; 80 | bool click_started = false; 81 | 82 | virtual void render(virtual_terminal * console) override; 83 | virtual bool mouse_in_control(const int tx, const int ty) override { 84 | return (tx >= x && tx <= x + (static_cast(label.size())+4) && ty==y); 85 | } 86 | }; 87 | 88 | struct radio { 89 | bool checked; 90 | std::string label; 91 | int value; 92 | }; 93 | 94 | struct gui_radiobuttons_t : public gui_control_t { 95 | gui_radiobuttons_t(const int X, const int Y, const std::string heading, const color_t fg, const color_t bg, std::vector opts) : 96 | caption(heading), foreground(fg), background(bg), options(opts), x(X), y(Y) 97 | { 98 | width = static_cast(caption.size()); 99 | for (const radio &r : options) { 100 | if (width < static_cast(r.label.size())) width = static_cast(r.label.size()); 101 | if (r.checked) selected_value = r.value; 102 | } 103 | height = static_cast(options.size()) + 1; 104 | 105 | on_mouse_down = [] (gui_control_t * ctrl, int tx, int ty) { 106 | gui_radiobuttons_t * me = static_cast(ctrl); 107 | me->click_started = true; 108 | }; 109 | on_mouse_up = [] (gui_control_t * ctrl, int tx, int ty) { 110 | gui_radiobuttons_t * me = static_cast(ctrl); 111 | if (me->click_started) { 112 | const int option_number = (ty - me->y) -1; 113 | if (option_number >= 0 && option_number <= static_cast(me->options.size())) { 114 | me->selected_value = me->options[option_number].value; 115 | for (auto &r : me->options) { 116 | if (r.value == me->selected_value) { 117 | r.checked = true; 118 | } else { 119 | r.checked = false; 120 | } 121 | } 122 | } 123 | } 124 | me->click_started = false; 125 | }; 126 | } 127 | 128 | std::string caption; 129 | color_t foreground; 130 | color_t background; 131 | std::vector options; 132 | int x; 133 | int y; 134 | int width; 135 | int height; 136 | bool click_started = false; 137 | int selected_value = -1; 138 | 139 | virtual void render(virtual_terminal * console) override; 140 | virtual bool mouse_in_control(const int tx, const int ty) override { 141 | if (tx >= x && tx <= (x + width) && ty >= y && ty <= (y + height)) { 142 | return true; 143 | } 144 | return false; 145 | } 146 | }; 147 | 148 | struct gui_hbar_t : public gui_control_t { 149 | gui_hbar_t(const int X, const int Y, const int W, const int MIN, const int MAX, const int VAL, 150 | const color_t FULL_START, const color_t FULL_END, const color_t EMPTY_START, const color_t EMPTY_END, 151 | const color_t TEXT_COL, std::string PREFIX = "") : 152 | x(X), y(Y), w(W), min(MIN), max(MAX), value(VAL), full_start(FULL_START), full_end(FULL_END), 153 | empty_start(EMPTY_START), empty_end(EMPTY_END), text_color(TEXT_COL), prefix(PREFIX) 154 | {} 155 | 156 | int x; 157 | int y; 158 | std::size_t w; 159 | int min; 160 | int max; 161 | int value; 162 | color_t full_start; 163 | color_t full_end; 164 | color_t empty_start; 165 | color_t empty_end; 166 | color_t text_color; 167 | std::string prefix; 168 | 169 | virtual void render(virtual_terminal * console) override; 170 | }; 171 | 172 | struct gui_vbar_t : public gui_control_t { 173 | gui_vbar_t(const int X, const int Y, const int H, const int MIN, const int MAX, const int VAL, 174 | const color_t FULL_START, const color_t FULL_END, const color_t EMPTY_START, const color_t EMPTY_END, 175 | const color_t TEXT_COL, std::string PREFIX = "") : 176 | x(X), y(Y), h(H), min(MIN), max(MAX), value(VAL), full_start(FULL_START), full_end(FULL_END), 177 | empty_start(EMPTY_START), empty_end(EMPTY_END), text_color(TEXT_COL), prefix(PREFIX) 178 | {} 179 | 180 | int x; 181 | int y; 182 | std::size_t h; 183 | int min; 184 | int max; 185 | int value; 186 | color_t full_start; 187 | color_t full_end; 188 | color_t empty_start; 189 | color_t empty_end; 190 | color_t text_color; 191 | std::string prefix; 192 | 193 | virtual void render(virtual_terminal * console) override; 194 | }; 195 | 196 | struct list_item { 197 | int value; 198 | std::string label; 199 | }; 200 | 201 | struct gui_listbox_t : public gui_control_t { 202 | gui_listbox_t(const int X, const int Y, const int VAL, std::vector options, std::string label, 203 | const color_t label_fg, const color_t label_bg, const color_t ITEM_FG, const color_t ITEM_BG, 204 | const color_t sel_fg, const color_t sel_bg) : 205 | x(X), y(Y), selected_value(VAL), items(options), caption_fg(label_fg), caption_bg(label_bg), 206 | item_fg(ITEM_FG), item_bg(ITEM_BG), selected_fg(sel_fg), selected_bg(sel_bg) 207 | { 208 | caption = label; 209 | w = static_cast(caption.size()) + 2; 210 | for (const list_item &item : items) { 211 | if (w < static_cast(item.label.size())) w = static_cast(item.label.size()); 212 | } 213 | 214 | on_mouse_down = [] (gui_control_t * ctrl, int tx, int ty) { 215 | gui_listbox_t * me = static_cast(ctrl); 216 | me->click_started = true; 217 | }; 218 | on_mouse_up = [] (gui_control_t * ctrl, int tx, int ty) { 219 | gui_listbox_t * me = static_cast(ctrl); 220 | if (me->click_started) { 221 | const int option_number = (ty - me->y) -1; 222 | if (option_number >= 0 && option_number <= static_cast(me->items.size())) { 223 | me->selected_value = me->items[option_number].value; 224 | } 225 | } 226 | me->click_started = false; 227 | }; 228 | } 229 | 230 | int x; 231 | int y; 232 | int selected_value; 233 | std::vector items; 234 | std::string caption; 235 | color_t caption_fg; 236 | color_t caption_bg; 237 | color_t item_fg; 238 | color_t item_bg; 239 | color_t selected_fg; 240 | color_t selected_bg; 241 | int w = 0; 242 | bool click_started = false; 243 | 244 | virtual void render(virtual_terminal * console) override; 245 | virtual bool mouse_in_control(const int tx, const int ty) override { 246 | if (tx >= x && tx <= (x + w) && ty >= y && ty <= (y + static_cast(items.size())+1)) { 247 | return true; 248 | } 249 | return false; 250 | } 251 | }; 252 | 253 | } 254 | -------------------------------------------------------------------------------- /rltk/input_handler.cpp: -------------------------------------------------------------------------------- 1 | #include "input_handler.hpp" 2 | #include "scaling.hpp" 3 | #include 4 | #include 5 | 6 | namespace rltk { 7 | 8 | namespace state { 9 | bool window_focused = true; 10 | std::array mouse_button_pressed; 11 | int mouse_x = 0; 12 | int mouse_y = 0; 13 | } 14 | 15 | bool is_window_focused() { 16 | return rltk::state::window_focused; 17 | } 18 | 19 | void set_window_focus_state(const bool &s) { 20 | rltk::state::window_focused = s; 21 | } 22 | 23 | void reset_mouse_state() { 24 | std::fill(rltk::state::mouse_button_pressed.begin(), rltk::state::mouse_button_pressed.end(), false); 25 | rltk::state::mouse_x = 0; 26 | rltk::state::mouse_y = 0; 27 | } 28 | 29 | void set_mouse_button_state(const int button, const bool state) { 30 | rltk::state::mouse_button_pressed[button] = state; 31 | } 32 | 33 | bool get_mouse_button_state(const int button) { 34 | return rltk::state::mouse_button_pressed[button]; 35 | } 36 | 37 | void set_mouse_position(const int x, const int y) { 38 | rltk::state::mouse_x = static_cast(x / scale_factor); 39 | rltk::state::mouse_y = static_cast(y / scale_factor); 40 | } 41 | 42 | std::pair get_mouse_position() { 43 | return std::make_pair(rltk::state::mouse_x, rltk::state::mouse_y); 44 | } 45 | 46 | void enqueue_key_pressed(sf::Event &event) { 47 | emit(key_pressed_t{event}); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /rltk/input_handler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* RLTK (RogueLike Tool Kit) 1.00 3 | * Copyright (c) 2016-Present, Bracket Productions. 4 | * Licensed under the MIT license - see LICENSE file. 5 | * 6 | * Class for providing a simple input interface. 7 | * Provides functions that can be queried for the current state of 8 | * the input system. 9 | * 10 | */ 11 | 12 | #include 13 | #include 14 | #include "ecs.hpp" 15 | 16 | namespace rltk { 17 | 18 | /* Helper constants to represent mouse buttons */ 19 | namespace button { 20 | constexpr int LEFT = 0; 21 | constexpr int RIGHT = 1; 22 | constexpr int MIDDLE = 2; 23 | constexpr int SIDE1 = 3; 24 | constexpr int SIDE2 = 4; 25 | constexpr int WHEEL_UP = 5; 26 | constexpr int WHEEL_DOWN = 6; 27 | } 28 | 29 | /* Does the game window currently have focus? You might want to pause if it doesn't. */ 30 | extern bool is_window_focused(); 31 | 32 | /* Setter function for window focus */ 33 | extern void set_window_focus_state(const bool &s); 34 | 35 | /* Mouse state reset: clears all mouse state */ 36 | extern void reset_mouse_state(); 37 | 38 | /* Update the stored mouse position. Does not actually move the mouse. */ 39 | extern void set_mouse_position(const int x, const int y); 40 | extern std::pair get_mouse_position(); 41 | 42 | /* Mouse button state */ 43 | extern void set_mouse_button_state(const int button, const bool state); 44 | extern bool get_mouse_button_state(const int button); 45 | 46 | /* Keyboard queue */ 47 | extern void enqueue_key_pressed(sf::Event &event); 48 | 49 | struct key_pressed_t : base_message_t { 50 | public: 51 | key_pressed_t() {} 52 | key_pressed_t(sf::Event ev) : event(ev) {} 53 | sf::Event event; 54 | }; 55 | 56 | } 57 | -------------------------------------------------------------------------------- /rltk/layer_t.cpp: -------------------------------------------------------------------------------- 1 | #include "layer_t.hpp" 2 | #include "input_handler.hpp" 3 | 4 | namespace rltk { 5 | 6 | void layer_t::make_owner_draw_backing() { 7 | if (!backing) { 8 | backing = std::make_unique(); 9 | } 10 | backing->create(w, h); 11 | } 12 | 13 | void layer_t::on_resize(const int width, const int height) { 14 | resize_func(this, width, height); 15 | if (console && console->visible) { 16 | console->set_offset(x,y); 17 | console->resize_pixels(w, h); 18 | console->dirty = true; 19 | } else { 20 | make_owner_draw_backing(); 21 | } 22 | } 23 | 24 | void layer_t::render(sf::RenderWindow &window) { 25 | if (console) { 26 | if (!controls.empty()) { 27 | 28 | // Render start events 29 | for (auto it=controls.begin(); it != controls.end(); ++it) { 30 | if (it->second->on_render_start) { 31 | auto callfunc = it->second->on_render_start; 32 | callfunc(it->second.get()); 33 | } 34 | } 35 | 36 | int mouse_x, mouse_y; 37 | std::tie(mouse_x, mouse_y) = get_mouse_position(); 38 | 39 | if (mouse_x >= x && mouse_x <= (x+w) && mouse_y >= y && mouse_y <= (y+h)) { 40 | // Mouse over in here is possible. 41 | auto font_dimensions = console->get_font_size(); 42 | const int terminal_x = (mouse_x - x) / font_dimensions.first; 43 | const int terminal_y = (mouse_y - y) / font_dimensions.second; 44 | 45 | for (auto it=controls.begin(); it != controls.end(); ++it) { 46 | // Mouse over 47 | if (it->second->mouse_in_control(terminal_x, terminal_y)) { 48 | if (it->second->on_mouse_over) { 49 | auto callfunc = it->second->on_mouse_over; 50 | callfunc(it->second.get(), terminal_x, terminal_y); 51 | } 52 | 53 | // Mouse down and up 54 | if (get_mouse_button_state(button::LEFT) && it->second->on_mouse_down) { 55 | auto callfunc = it->second->on_mouse_down; 56 | callfunc(it->second.get(), terminal_x, terminal_y); 57 | } 58 | if (!get_mouse_button_state(button::LEFT) && it->second->on_mouse_up) { 59 | auto callfunc = it->second->on_mouse_up; 60 | callfunc(it->second.get(), terminal_x, terminal_y); 61 | } 62 | } 63 | } 64 | } 65 | 66 | for (auto it=controls.begin(); it != controls.end(); ++it) { 67 | it->second->render(console.get()); 68 | } 69 | } 70 | console->render(window); 71 | } else if (sconsole) { 72 | sconsole->render(window); 73 | } else { 74 | if (!backing) make_owner_draw_backing(); 75 | backing->clear(sf::Color(0,0,0,0)); 76 | owner_draw_func(this, *backing); 77 | backing->display(); 78 | sf::Sprite compositor(backing->getTexture()); 79 | compositor.move(static_cast(x), static_cast(y)); 80 | window.draw(sf::Sprite(compositor)); 81 | } 82 | } 83 | 84 | void resize_fullscreen(rltk::layer_t * l, int w, int h) { 85 | // Use the whole window 86 | l->w = w; 87 | l->h = h; 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /rltk/layer_t.hpp: -------------------------------------------------------------------------------- 1 | /* RLTK (RogueLike Tool Kit) 1.00 2 | * Copyright (c) 2016-Present, Bracket Productions. 3 | * Licensed under the MIT license - see LICENSE file. 4 | * 5 | * Layer type used by the GUI 6 | */ 7 | #pragma once 8 | 9 | #include "virtual_terminal.hpp" 10 | #include "virtual_terminal_sparse.hpp" 11 | #include "gui_control_t.hpp" 12 | #include 13 | 14 | namespace rltk { 15 | 16 | /* 17 | * A renderable layer. You won't use this type directly. 18 | */ 19 | struct layer_t { 20 | /* This specialization is for generic consoles */ 21 | layer_t(const int X, const int Y, const int W, const int H, std::string font_name, std::function resize_fun, bool render_background=true) : 22 | x(X), y(Y), w(W), h(H), font(font_name), resize_func(resize_fun), has_background(render_background) 23 | { 24 | console = std::make_unique(font_name, x, y, has_background); 25 | console->resize_pixels(w, h); 26 | } 27 | 28 | /* This specialization is for sparse consoles */ 29 | layer_t(bool sparse, const int X, const int Y, const int W, const int H, std::string font_name, std::function resize_fun) : 30 | x(X), y(Y), w(W), h(H), font(font_name), resize_func(resize_fun) 31 | { 32 | // Sparse is unusued, but is there to differentiate the signature. 33 | sconsole = std::make_unique(font_name, x, y); 34 | sconsole->resize_pixels(w, h); 35 | } 36 | 37 | /* This specialization is for owner-draw panels */ 38 | layer_t(const int X, const int Y, const int W, const int H, std::function resize_fun, std::function owner_draw_fun) : 39 | x(X), y(Y), w(W), h(H), resize_func(resize_fun), owner_draw_func(owner_draw_fun) 40 | { 41 | } 42 | 43 | // The bounding box of the layer 44 | int x; 45 | int y; 46 | int w; 47 | int h; 48 | 49 | // Font tag - used only if there is a console 50 | std::string font; 51 | 52 | // Callbacks: 53 | // resize_func is called when the window changes size. It receives the WINDOW dimensions - it's up to you to 54 | // determine how to lay things out. 55 | std::function resize_func; 56 | 57 | // Passed through to virtual console; if false then no background will be rendered (helpful for text overlays) 58 | bool has_background; 59 | 60 | // owner_draw_func is used only for owner draw layers, and is called at render time. 61 | std::function owner_draw_func; 62 | 63 | // If a console is present, this is it. 64 | std::unique_ptr console; 65 | 66 | // If it has a sparse console, this is it. 67 | std::unique_ptr sconsole; 68 | 69 | // If retained-mode controls are present, they are in here. 70 | std::unordered_map> controls; 71 | 72 | // Used for owner-draw layers. We need to render to texture and then compose to: 73 | // a) permit threading, should you so wish (so there is a single composite run) 74 | // b) allow the future "effects" engine to run. 75 | std::unique_ptr backing; 76 | 77 | // Used by the owner-draw code to ensure that a texture is available for use 78 | void make_owner_draw_backing(); 79 | 80 | // Called by GUI when a resize event occurs. 81 | void on_resize(const int width, const int height); 82 | 83 | // Called by GUI when a render event occurs. 84 | void render(sf::RenderWindow &window); 85 | 86 | // Retained Mode Controls 87 | template 88 | T * control(const int handle) { 89 | auto finder = controls.find(handle); 90 | if (finder == controls.end()) throw std::runtime_error("Unknown GUI control handle: " + std::to_string(handle)); 91 | return static_cast(finder->second.get()); 92 | } 93 | 94 | gui_control_t * control(const int handle) { 95 | auto finder = controls.find(handle); 96 | if (finder == controls.end()) throw std::runtime_error("Unknown GUI control handle: " + std::to_string(handle)); 97 | return finder->second.get(); 98 | } 99 | 100 | inline void remove_control(const int handle) { 101 | controls.erase(handle); 102 | } 103 | 104 | inline void check_handle_uniqueness(const int handle) { 105 | auto finder = controls.find(handle); 106 | if (finder != controls.end()) throw std::runtime_error("Adding a duplicate control handle: " + std::to_string(handle)); 107 | } 108 | 109 | inline void add_static_text(const int handle, const int x, const int y, const std::string text, const color_t fg, const color_t bg) { 110 | check_handle_uniqueness(handle); 111 | controls.emplace(handle, std::make_unique(x, y, text, fg, bg)); 112 | } 113 | 114 | inline void add_boundary_box(const int handle, const bool double_lines, const color_t fg, const color_t bg) { 115 | check_handle_uniqueness(handle); 116 | controls.emplace(handle, std::make_unique(double_lines, fg, bg)); 117 | } 118 | 119 | inline void add_checkbox(const int handle, const int x, const int y, const std::string label, const bool checked, const color_t fg, const color_t bg) { 120 | check_handle_uniqueness(handle); 121 | controls.emplace(handle, std::make_unique(x, y, checked, label, fg, bg)); 122 | } 123 | 124 | inline void add_radioset(const int handle, const int x, const int y, const std::string caption, const color_t fg, const color_t bg, std::vector opts) { 125 | check_handle_uniqueness(handle); 126 | controls.emplace(handle, std::make_unique(x, y, caption, fg, bg, opts)); 127 | } 128 | 129 | inline void add_hbar(const int handle, const int X, const int Y, const int W, const int MIN, const int MAX, const int VAL, 130 | const color_t FULL_START, const color_t FULL_END, const color_t EMPTY_START, const color_t EMPTY_END, const color_t TEXT_COL, const std::string prefix="") 131 | { 132 | check_handle_uniqueness(handle); 133 | controls.emplace(handle, std::make_unique(X, Y, W, MIN, MAX, VAL, FULL_START, FULL_END, EMPTY_START, EMPTY_END, TEXT_COL, prefix)); 134 | } 135 | 136 | inline void add_vbar(const int handle, const int X, const int Y, const int H, const int MIN, const int MAX, const int VAL, 137 | const color_t FULL_START, const color_t FULL_END, const color_t EMPTY_START, const color_t EMPTY_END, const color_t TEXT_COL, const std::string prefix="") 138 | { 139 | check_handle_uniqueness(handle); 140 | controls.emplace(handle, std::make_unique(X, Y, H, MIN, MAX, VAL, FULL_START, FULL_END, EMPTY_START, EMPTY_END, TEXT_COL, prefix)); 141 | } 142 | 143 | inline void add_listbox(const int handle, const int X, const int Y, const int VAL, std::vector options, std::string label, 144 | const color_t label_fg, const color_t label_bg, const color_t ITEM_FG, const color_t ITEM_BG, 145 | const color_t sel_fg, const color_t sel_bg) 146 | { 147 | check_handle_uniqueness(handle); 148 | controls.emplace(handle, std::make_unique(X, Y, VAL, options, label, label_fg, label_bg, ITEM_FG, ITEM_BG, sel_fg, sel_bg)); 149 | } 150 | 151 | inline void clear_gui() { 152 | controls.clear(); 153 | } 154 | }; 155 | 156 | // Convenience: ready-made function to resize to use the whole screen. 157 | extern void resize_fullscreen(rltk::layer_t * l, int w, int h); 158 | 159 | } 160 | -------------------------------------------------------------------------------- /rltk/path_finding.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* RLTK (RogueLike Tool Kit) 1.00 4 | * Copyright (c) 2016-Present, Bracket Productions. 5 | * Licensed under the MIT license - see LICENSE file. 6 | * 7 | * Path finding - interface to the A-Star system 8 | */ 9 | 10 | #include "astar.hpp" 11 | #include "geometry.hpp" 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace rltk { 18 | 19 | // Template class used to forward to specialize the algorithm to the user's map format and 20 | // and behaviors defined in navigator_t. This avoids the library mandating what your map 21 | // looks like. 22 | template 23 | class map_search_node { 24 | public: 25 | location_t pos; 26 | 27 | map_search_node() {} 28 | map_search_node(location_t loc) : pos(loc) {} 29 | 30 | float GoalDistanceEstimate(map_search_node &goal) { 31 | float result = navigator_t::get_distance_estimate(pos, goal.pos); 32 | //std::cout << "GoalDistanceEstimate called (" << result << ").\n"; 33 | return result; 34 | } 35 | 36 | bool IsGoal(map_search_node &node_goal) { 37 | bool result = navigator_t::is_goal(pos, node_goal.pos); 38 | //std::cout << "IsGoal called (" << result << ").\n"; 39 | return result; 40 | } 41 | 42 | bool GetSuccessors(AStarSearch> * a_star_search, map_search_node * parent_node) { 43 | //std::cout << "GetSuccessors called.\n"; 44 | std::vector successors; 45 | 46 | if (parent_node != nullptr) { 47 | navigator_t::get_successors(parent_node->pos, successors); 48 | } else { 49 | throw std::runtime_error("Null parent error."); 50 | } 51 | for (location_t loc : successors) { 52 | map_search_node tmp(loc); 53 | //std::cout << " --> " << loc.x << "/" << loc.y << "\n"; 54 | a_star_search->AddSuccessor( tmp ); 55 | } 56 | return true; 57 | } 58 | 59 | 60 | float GetCost(map_search_node &successor) { 61 | float result = navigator_t::get_cost(pos, successor.pos); 62 | //std::cout << "GetCost called (" << result << ").\n"; 63 | return result; 64 | } 65 | 66 | bool IsSameState(map_search_node &rhs) { 67 | bool result = navigator_t::is_same_state(pos, rhs.pos); 68 | //std::cout << "IsSameState called (" << result << ").\n"; 69 | return result; 70 | } 71 | }; 72 | 73 | // Template class used to define what a navigation path looks like 74 | template 75 | struct navigation_path { 76 | bool success = false; 77 | location_t destination; 78 | std::deque steps; 79 | }; 80 | 81 | /* 82 | * find_path_3d implements A*, and provides an optimization that scans a 3D Bresenham line at the beginning 83 | * to check for a simple line-of-sight (and paths along it). 84 | * 85 | * We jump through a few hoops to make sure that it will work with whatever map format you choose to use, 86 | * hence: it requires that the navigator_t class provide: 87 | * - get_x, get_y_, get_z - to translate X/Y/Z into whatever name the user wishes to utilize. 88 | * - get_xyz - returns a location_t given X/Y/Z co-ordinates. 89 | */ 90 | template 91 | std::shared_ptr> find_path_3d(const location_t start, const location_t end) 92 | { 93 | { 94 | std::shared_ptr> result = std::shared_ptr>(new navigation_path()); 95 | result->success = true; 96 | line_func3d(navigator_t::get_x(start), navigator_t::get_y(start), navigator_t::get_z(start), navigator_t::get_x(end), navigator_t::get_y(end), navigator_t::get_z(end), [result] (int X, int Y, int Z) { 97 | location_t step = navigator_t::get_xyz(X,Y, Z); 98 | if (result->success and navigator_t::is_walkable(step)) { 99 | result->steps.push_back(step); 100 | } else { 101 | result->success = false; 102 | } 103 | }); 104 | if (result->success) { 105 | return result; 106 | } 107 | } 108 | 109 | AStarSearch> a_star_search; 110 | map_search_node a_start(start); 111 | map_search_node a_end(end); 112 | 113 | a_star_search.SetStartAndGoalStates(a_start, a_end); 114 | unsigned int search_state; 115 | unsigned int search_steps = 0; 116 | 117 | do { 118 | search_state = a_star_search.SearchStep(); 119 | ++search_steps; 120 | } while (search_state == AStarSearch>::SEARCH_STATE_SEARCHING); 121 | 122 | if (search_state == AStarSearch>::SEARCH_STATE_SUCCEEDED) { 123 | std::shared_ptr> result = std::shared_ptr>(new navigation_path()); 124 | result->destination = end; 125 | map_search_node * node = a_star_search.GetSolutionStart(); 126 | for (;;) { 127 | node = a_star_search.GetSolutionNext(); 128 | if (!node) break; 129 | result->steps.push_back(node->pos); 130 | } 131 | a_star_search.FreeSolutionNodes(); 132 | a_star_search.EnsureMemoryFreed(); 133 | result->success = true; 134 | return result; 135 | } 136 | 137 | std::shared_ptr> result = std::make_shared>(); 138 | a_star_search.EnsureMemoryFreed(); 139 | return result; 140 | } 141 | 142 | /* 143 | * find_path_2d implements A*, and provides an optimization that scans a 2D Bresenham line at the beginning 144 | * to check for a simple line-of-sight (and paths along it). 145 | * 146 | * We jump through a few hoops to make sure that it will work with whatever map format you choose to use, 147 | * hence: it requires that the navigator_t class provide: 148 | * - get_x, get_y - to translate X/Y/Z into whatever name the user wishes to utilize. 149 | * - get_xy - returns a location_t given X/Y/Z co-ordinates. 150 | */ 151 | template 152 | std::shared_ptr> find_path_2d(const location_t start, const location_t end) 153 | { 154 | { 155 | std::shared_ptr> result = std::shared_ptr>(new navigation_path()); 156 | result->success = true; 157 | line_func(navigator_t::get_x(start), navigator_t::get_y(start), navigator_t::get_x(end), navigator_t::get_y(end), [result] (int X, int Y) { 158 | location_t step = navigator_t::get_xy(X,Y); 159 | if (result->success && navigator_t::is_walkable(step)) { 160 | result->steps.push_back(step); 161 | } else { 162 | result->success = false; 163 | } 164 | }); 165 | if (result->success) { 166 | return result; 167 | } 168 | } 169 | 170 | AStarSearch> a_star_search; 171 | map_search_node a_start(start); 172 | map_search_node a_end(end); 173 | 174 | a_star_search.SetStartAndGoalStates(a_start, a_end); 175 | unsigned int search_state; 176 | unsigned int search_steps = 0; 177 | 178 | do { 179 | search_state = a_star_search.SearchStep(); 180 | ++search_steps; 181 | } while (search_state == AStarSearch>::SEARCH_STATE_SEARCHING); 182 | 183 | if (search_state == AStarSearch>::SEARCH_STATE_SUCCEEDED) { 184 | std::shared_ptr> result = std::shared_ptr>(new navigation_path()); 185 | result->destination = end; 186 | map_search_node * node = a_star_search.GetSolutionStart(); 187 | for (;;) { 188 | node = a_star_search.GetSolutionNext(); 189 | if (!node) break; 190 | result->steps.push_back(node->pos); 191 | } 192 | a_star_search.FreeSolutionNodes(); 193 | a_star_search.EnsureMemoryFreed(); 194 | result->success = true; 195 | return result; 196 | } 197 | 198 | std::shared_ptr> result = std::make_shared>(); 199 | a_star_search.EnsureMemoryFreed(); 200 | return result; 201 | } 202 | 203 | /* 204 | * Implements a simple A-Star path, with no line-search optimization. This has the benefit of avoiding 205 | * requiring as much additional translation between the template and your preferred map format, at the 206 | * expense of being potentially slower for some paths. 207 | */ 208 | template 209 | std::shared_ptr> find_path(const location_t start, const location_t end) 210 | { 211 | AStarSearch> a_star_search; 212 | map_search_node a_start(start); 213 | map_search_node a_end(end); 214 | 215 | a_star_search.SetStartAndGoalStates(a_start, a_end); 216 | unsigned int search_state; 217 | std::size_t search_steps = 0; 218 | 219 | do { 220 | search_state = a_star_search.SearchStep(); 221 | ++search_steps; 222 | } while (search_state == AStarSearch>::SEARCH_STATE_SEARCHING); 223 | 224 | if (search_state == AStarSearch>::SEARCH_STATE_SUCCEEDED) { 225 | std::shared_ptr> result = std::shared_ptr>(new navigation_path()); 226 | result->destination = end; 227 | map_search_node * node = a_star_search.GetSolutionStart(); 228 | for (;;) { 229 | node = a_star_search.GetSolutionNext(); 230 | if (!node) break; 231 | result->steps.push_back(node->pos); 232 | } 233 | a_star_search.FreeSolutionNodes(); 234 | a_star_search.EnsureMemoryFreed(); 235 | result->success = true; 236 | return result; 237 | } 238 | 239 | std::shared_ptr> result = std::make_shared>(); 240 | a_star_search.EnsureMemoryFreed(); 241 | return result; 242 | } 243 | 244 | } 245 | -------------------------------------------------------------------------------- /rltk/perlin_noise.cpp: -------------------------------------------------------------------------------- 1 | #include "perlin_noise.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace rltk { 9 | 10 | // Initialize with the reference values for the permutation vector 11 | perlin_noise::perlin_noise() { 12 | 13 | // Initialize the permutation vector with the reference values 14 | p = { 15 | 151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142, 16 | 8,99,37,240,21,10,23,190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117, 17 | 35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71, 18 | 134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41, 19 | 55,46,245,40,244,102,143,54, 65,25,63,161,1,216,80,73,209,76,132,187,208, 89, 20 | 18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226, 21 | 250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182, 22 | 189,28,42,223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 23 | 43,172,9,129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246, 24 | 97,228,251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239, 25 | 107,49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254, 26 | 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180 }; 27 | // Duplicate the permutation vector 28 | p.insert(p.end(), p.begin(), p.end()); 29 | } 30 | 31 | // Generate a new permutation vector based on the value of seed 32 | perlin_noise::perlin_noise(unsigned int seed) { 33 | p.resize(256); 34 | 35 | // Fill p with values from 0 to 255 36 | std::iota(p.begin(), p.end(), 0); 37 | 38 | // Initialize a random engine with seed 39 | std::default_random_engine engine(seed); 40 | 41 | // Suffle using the above random engine 42 | std::shuffle(p.begin(), p.end(), engine); 43 | 44 | // Duplicate the permutation vector 45 | p.insert(p.end(), p.begin(), p.end()); 46 | } 47 | 48 | double perlin_noise::noise(double x, double y, double z) const noexcept { 49 | // Find the unit cube that contains the point 50 | int X = (int) floor(x) & 255; 51 | int Y = (int) floor(y) & 255; 52 | int Z = (int) floor(z) & 255; 53 | 54 | // Find relative x, y,z of point in cube 55 | x -= floor(x); 56 | y -= floor(y); 57 | z -= floor(z); 58 | 59 | // Compute fade curves for each of x, y, z 60 | double u = fade(x); 61 | double v = fade(y); 62 | double w = fade(z); 63 | 64 | // Hash coordinates of the 8 cube corners 65 | int A = p[X] + Y; 66 | int AA = p[A] + Z; 67 | int AB = p[A + 1] + Z; 68 | int B = p[X + 1] + Y; 69 | int BA = p[B] + Z; 70 | int BB = p[B + 1] + Z; 71 | 72 | // Add blended results from 8 corners of cube 73 | double res = lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, z), grad(p[BA], x-1, y, z)), lerp(u, grad(p[AB], x, y-1, z), grad(p[BB], x-1, y-1, z))), lerp(v, lerp(u, grad(p[AA+1], x, y, z-1), grad(p[BA+1], x-1, y, z-1)), lerp(u, grad(p[AB+1], x, y-1, z-1), grad(p[BB+1], x-1, y-1, z-1)))); 74 | return (res + 1.0)/2.0; 75 | } 76 | 77 | double perlin_noise::noise_octaves(double x, double y, double z, int octaves, double persistence, double frequency) const noexcept { 78 | double total = 0; 79 | double amplitude = 1; 80 | double maxValue = 0; // Used for normalizing result to 0.0 - 1.0 81 | for(int i=0;i 8 | 9 | namespace rltk { 10 | 11 | class perlin_noise { 12 | // The permutation vector 13 | std::vector p; 14 | public: 15 | // Initialize with the reference values for the permutation vector 16 | perlin_noise(); 17 | // Generate a new permutation vector based on the value of seed 18 | perlin_noise(unsigned int seed); 19 | // Get a noise value, for 2D images z can have any value 20 | double noise(double x, double y, double z) const noexcept; 21 | double noise_octaves(double x, double y, double z, int octaves, double persistence, double frequency) const noexcept; 22 | private: 23 | double fade(double t) const noexcept; 24 | double lerp(double t, double a, double b) const noexcept; 25 | double grad(int hash, double x, double y, double z) const noexcept; 26 | }; 27 | 28 | } -------------------------------------------------------------------------------- /rltk/rexspeeder.cpp: -------------------------------------------------------------------------------- 1 | #include "rexspeeder.hpp" 2 | extern "C" { 3 | #include 4 | } 5 | #include 6 | 7 | namespace rltk { 8 | 9 | //===========================================================================================================// 10 | // Safe I/O (where "safe" means "will throw errors") // 11 | // // 12 | // These functions will throw an error message from gzerror, and set errno to the error code. // 13 | //===========================================================================================================// 14 | 15 | inline std::runtime_error make_rexception(gzFile g) { 16 | /*The exception creation is a bit verbose.*/ 17 | int errnum = 0; 18 | const char* errstr = gzerror(g, &errnum); 19 | return std::runtime_error(std::to_string(errnum) + std::string(" ") + std::string(errstr)); 20 | } 21 | 22 | static void s_gzread(gzFile g, voidp buf, unsigned int len) 23 | { 24 | if (gzread(g, buf, len) > 0) 25 | return; 26 | 27 | /*We expect to read past the end of the file after the last layer.*/ 28 | if (gzeof(g)) 29 | return; 30 | 31 | throw make_rexception(g); 32 | } 33 | 34 | static void s_gzwrite(gzFile g, voidp buf, unsigned int len) 35 | { 36 | if (gzwrite(g, buf, len) > 0) 37 | return; 38 | 39 | throw make_rexception(g); 40 | } 41 | 42 | static gzFile s_gzopen(const std::string filename, const char* permissions) 43 | { 44 | gzFile g = gzopen(filename.c_str(), permissions); 45 | 46 | if (g != Z_NULL) 47 | return g; 48 | 49 | int err = 0; 50 | const char* errstr = gzerror(g, &err); 51 | if (err == 0) { 52 | /*Assume the file simply didn't exist.*/ 53 | std::string s("File " + filename + " does not exist."); 54 | throw std::runtime_error(s); 55 | } 56 | throw std::runtime_error(std::string(errstr)); 57 | } 58 | 59 | 60 | namespace xp { 61 | 62 | //===========================================================================================================// 63 | // Loading an xp file // 64 | //===========================================================================================================// 65 | rex_sprite::rex_sprite(std::string const & filename) 66 | { 67 | typedef void* vp; 68 | //Number of bytes in a tile. Not equal to sizeof(RexTile) due to padding. 69 | const int tileLen = 10; 70 | 71 | gzFile gz; 72 | try { 73 | gz = s_gzopen(filename.c_str(), "rb"); 74 | 75 | s_gzread(gz, (vp)&version, sizeof(version)); 76 | s_gzread(gz, (vp)&num_layers, sizeof(num_layers)); 77 | s_gzread(gz, (vp)&width, sizeof(width)); 78 | s_gzread(gz, (vp)&height, sizeof(height)); 79 | 80 | layers.resize(num_layers); 81 | 82 | for (int i = 0; i < num_layers; i++) 83 | layers[i] = rex_layer(width, height); 84 | 85 | for (int layer_index = 0; layer_index < num_layers; layer_index++) { 86 | for (int i = 0; i < width*height; ++i) 87 | s_gzread(gz, get_tile(layer_index, i), tileLen); 88 | 89 | //The layer and height information is repeated. 90 | //This is expected to read off the end after the last layer. 91 | s_gzread(gz, (vp)&width, sizeof(width)); 92 | s_gzread(gz, (vp)&height, sizeof(height)); 93 | } 94 | } 95 | catch (...) { throw; } 96 | 97 | gzclose(gz); 98 | } 99 | 100 | //===========================================================================================================// 101 | // Saving an xp file // 102 | //===========================================================================================================// 103 | void rex_sprite::save(std::string const & filename) 104 | { 105 | typedef void* vp; 106 | //Number of bytes in a tile. Not equal to sizeof(RexTile) due to padding. 107 | const int tileLen = 10; 108 | 109 | try { 110 | gzFile gz = s_gzopen(filename.c_str(), "wb"); 111 | 112 | s_gzwrite(gz, (vp)&version, sizeof(version)); 113 | s_gzwrite(gz, (vp)&num_layers, sizeof(num_layers)); 114 | 115 | for (int layer = 0; layer < num_layers; ++layer) { 116 | s_gzwrite(gz, (vp)&width, sizeof(width)); 117 | s_gzwrite(gz, (vp)&height, sizeof(height)); 118 | 119 | for (int i = 0; i < width*height; ++i) 120 | //Note: not "sizeof(RexTile)" because of padding. 121 | s_gzwrite(gz, (vp)get_tile(layer,i), tileLen); 122 | 123 | } 124 | 125 | gzflush(gz, Z_FULL_FLUSH); 126 | gzclose(gz); 127 | } 128 | catch (...) { throw; } 129 | } 130 | 131 | //===========================================================================================================// 132 | // Constructors / Destructors // 133 | //===========================================================================================================// 134 | rex_sprite::rex_sprite(int _version, int _width, int _height, int _num_layers) 135 | :version(_version), width(_width), height(_height), num_layers(_num_layers) 136 | { 137 | layers.resize(num_layers); 138 | 139 | //All layers above the first are set transparent. 140 | for (int l = 1; l < num_layers; l++) { 141 | for (int i = 0; i < width*height; ++i) { 142 | rltk::vchar t = transparent_tile(); 143 | set_tile(l, i, t); 144 | } 145 | } 146 | } 147 | 148 | //===========================================================================================================// 149 | // Utility Functions // 150 | //===========================================================================================================// 151 | void rex_sprite::flatten() { 152 | if (num_layers == 1) 153 | return; 154 | 155 | //Paint the last layer onto the second-to-last 156 | for (int i = 0; i < width*height; ++i) { 157 | rltk::vchar* overlay = get_tile(num_layers - 1, i); 158 | if (!is_transparent(overlay)) { 159 | *get_tile(num_layers - 2, i) = *overlay; 160 | } 161 | } 162 | 163 | //Remove the last layer 164 | --num_layers; 165 | 166 | //Recurse 167 | flatten(); 168 | } 169 | 170 | } 171 | 172 | } 173 | -------------------------------------------------------------------------------- /rltk/rexspeeder.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* RLTK (RogueLike Tool Kit) 1.00 3 | * Copyright (c) 2016-Present, Bracket Productions. 4 | * Licensed under the MIT license - see LICENSE file. 5 | * 6 | * REXPaint 1.02 file reader. Credit to https://github.com/pyridine/REXSpeeder for the original. 7 | */ 8 | 9 | /*For version 1.02 of REXPaint*/ 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "vchar.hpp" 15 | 16 | namespace rltk { 17 | 18 | namespace xp { 19 | 20 | //REXpaint identifies transparent tiles by setting their background color to 255,0,255. 21 | //You may want to check this for each tile before drawing or converting a RexFile. 22 | //(By default, no tile in the first layer is transaprent). 23 | inline bool is_transparent(const rltk::vchar * tile) 24 | { 25 | //This might be faster than comparing with transparentTile(), despite it being a constexpr 26 | return (tile->background.r == 255 && tile->background.g == 0 && tile->background.b == 255); 27 | } 28 | 29 | //Returns a transparent tile. 30 | inline rltk::vchar transparent_tile() { 31 | return rltk::vchar{0, 0, 0, 0, 255, 0, 255}; 32 | } 33 | 34 | struct rex_layer { 35 | rex_layer() {} 36 | rex_layer(int width, int height) 37 | { 38 | tiles.resize(width * height); 39 | } 40 | 41 | ~rex_layer() 42 | { 43 | tiles.clear(); 44 | } 45 | 46 | std::vector tiles; 47 | }; 48 | 49 | class rex_sprite { 50 | public: 51 | //Load an .xp file into a new RexFile. 52 | //Note: May throw a const char* error message and set errno. 53 | //Both the error message and the value of errno may be as gzopen or gzread set them. 54 | //It may also throw an error with code REXSPEEDER_FILE_DOES_NOT_EXIST. 55 | //Will not throw an error if the file specified by `filename` is not zlib compressed. 56 | rex_sprite(std::string const& filename); 57 | 58 | //Save this RexFile into a valid .xp file that RexPaint can load (if the ".xp" suffix is present). 59 | //Note: May throw a const char* error message and set errno. 60 | //Both the error message and the value of errno may be as gzopen or gzwrite set them. 61 | void save(std::string const& filename); 62 | 63 | //Create a blank RexFile with the specified attributes. 64 | //Layers above the first will be made of transparent tiles. 65 | rex_sprite(int _version, int _width, int _height, int _num_layers); 66 | 67 | //Image attributes 68 | inline int get_version() { return version; }; 69 | inline int get_width() { return width; }; 70 | inline int get_height() { return height; }; 71 | inline int get_num_layers() { return num_layers; }; 72 | 73 | //Returns a pointer to a single tile specified by layer, x coordinate, y coordinate. 74 | //0,0 is the top-left corner. 75 | inline rltk::vchar* get_tile(int layer, int x, int y) { return &layers[layer].tiles[y + (x * height)]; }; 76 | 77 | //Returns a pointer to a single tile specified by layer and the actual index into the array. 78 | //Useful for iterating through a whole layer in one go for coordinate-nonspecific tasks. 79 | inline rltk::vchar* get_tile(int layer, int index) { return &layers[layer].tiles[index]; }; 80 | 81 | //Replaces the data for a tile. Not super necessary, but might save you a couple lines. 82 | inline void set_tile(int layer, int x, int y, rltk::vchar& val) { *get_tile(layer, x, y) = val; }; 83 | 84 | //Replaces the data for a tile. Not super necessary, but might save you a couple lines. 85 | inline void set_tile(int layer, int i, rltk::vchar& val) { *get_tile(layer, i) = val; }; 86 | 87 | //Combines all the layers of the image into one layer. 88 | //Respects transparency. 89 | void flatten(); 90 | 91 | private: 92 | //Image properties 93 | int version; 94 | int width, height, num_layers; 95 | std::vector layers; //layers[0] is the first layer. 96 | 97 | //Forbid default construction. 98 | rex_sprite(); 99 | }; 100 | } 101 | } -------------------------------------------------------------------------------- /rltk/rltk.cpp: -------------------------------------------------------------------------------- 1 | #include "rltk.hpp" 2 | #include "texture.hpp" 3 | #include 4 | 5 | namespace rltk { 6 | 7 | std::unique_ptr main_window; 8 | std::unique_ptr console; 9 | std::unique_ptr gui; 10 | 11 | namespace main_detail { 12 | bool use_root_console; 13 | bool taking_screenshot = false; 14 | std::string screenshot_filename = ""; 15 | } 16 | 17 | sf::RenderWindow * get_window() { 18 | return main_window.get(); 19 | } 20 | 21 | gui_t * get_gui() { 22 | return gui.get(); 23 | } 24 | 25 | void init(const config_simple &config) { 26 | register_font_directory(config.font_path); 27 | bitmap_font * font = get_font(config.root_font); 28 | if (!config.fullscreen) { 29 | main_window = std::make_unique(sf::VideoMode(config.width * font->character_size.first, 30 | config.height * font->character_size.second), 31 | config.window_title, sf::Style::Titlebar | sf::Style::Resize | sf::Style::Close); 32 | } else { 33 | sf::VideoMode desktop = sf::VideoMode::getDesktopMode(); 34 | main_window = std::make_unique(sf::VideoMode(desktop.width, desktop.height, desktop.bitsPerPixel) 35 | , config.window_title, sf::Style::Fullscreen); 36 | } 37 | main_window->setVerticalSyncEnabled(true); 38 | main_detail::use_root_console = true; 39 | 40 | console = std::make_unique(config.root_font, 0, 0); 41 | sf::Vector2u size_pixels = main_window->getSize(); 42 | console->resize_pixels(size_pixels.x, size_pixels.y); 43 | } 44 | 45 | void init(const config_simple_px &config) { 46 | register_font_directory(config.font_path); 47 | if (!config.fullscreen) { 48 | main_window = std::make_unique(sf::VideoMode(config.width_px, config.height_px), 49 | config.window_title, sf::Style::Titlebar | sf::Style::Resize | sf::Style::Close); 50 | } else { 51 | sf::VideoMode desktop = sf::VideoMode::getDesktopMode(); 52 | main_window = std::make_unique(sf::VideoMode(desktop.width, desktop.height, desktop.bitsPerPixel) 53 | , config.window_title, sf::Style::Fullscreen); 54 | } 55 | main_window->setVerticalSyncEnabled(true); 56 | main_detail::use_root_console = true; 57 | 58 | console = std::make_unique(config.root_font, 0, 0); 59 | sf::Vector2u size_pixels = main_window->getSize(); 60 | console->resize_pixels(size_pixels.x, size_pixels.y); 61 | } 62 | 63 | void init(const config_advanced &config) { 64 | register_font_directory(config.font_path); 65 | 66 | sf::ContextSettings settings; 67 | settings.antialiasingLevel = 4; 68 | settings.depthBits = 32; 69 | settings.stencilBits = 8; 70 | 71 | if (!config.fullscreen) { 72 | main_window = std::make_unique(sf::VideoMode(config.width_px, config.height_px), 73 | config.window_title, sf::Style::Titlebar | sf::Style::Resize | sf::Style::Close, settings); 74 | } else { 75 | sf::VideoMode desktop = sf::VideoMode::getDesktopMode(); 76 | main_window = std::make_unique(sf::VideoMode(desktop.width, desktop.height, desktop.bitsPerPixel) 77 | , config.window_title, sf::Style::Fullscreen, settings); 78 | } 79 | main_window->setVerticalSyncEnabled(true); 80 | main_detail::use_root_console = false; 81 | 82 | gui = std::make_unique(config.width_px, config.height_px); 83 | } 84 | 85 | std::function optional_event_hook = nullptr; 86 | std::function optional_display_hook = nullptr; 87 | 88 | void run(std::function on_tick) { 89 | reset_mouse_state(); 90 | 91 | double duration_ms = 0.0; 92 | while (main_window->isOpen()) 93 | { 94 | clock_t start_time = clock(); 95 | 96 | sf::Event event; 97 | while (main_window->pollEvent(event)) 98 | { 99 | bool handle_events = true; 100 | if (optional_event_hook) { 101 | handle_events = optional_event_hook(event); 102 | } 103 | if (handle_events) { 104 | if (event.type == sf::Event::Closed) { 105 | main_window->close(); 106 | } else if (event.type == sf::Event::Resized) { 107 | main_window->setView(sf::View(sf::FloatRect(0.f, 0.f, static_cast(event.size.width), 108 | static_cast(event.size.height)))); 109 | if (main_detail::use_root_console) console->resize_pixels(event.size.width, event.size.height); 110 | if (gui) gui->on_resize(event.size.width, event.size.height); 111 | } else if (event.type == sf::Event::LostFocus) { 112 | set_window_focus_state(false); 113 | } else if (event.type == sf::Event::GainedFocus) { 114 | set_window_focus_state(true); 115 | } else if (event.type == sf::Event::MouseButtonPressed) { 116 | set_mouse_button_state(event.mouseButton.button, true); 117 | } else if (event.type == sf::Event::MouseButtonReleased) { 118 | set_mouse_button_state(event.mouseButton.button, false); 119 | } else if (event.type == sf::Event::MouseMoved) { 120 | set_mouse_position(event.mouseMove.x, event.mouseMove.y); 121 | } else if (event.type == sf::Event::KeyPressed) { 122 | enqueue_key_pressed(event); 123 | } else if (event.type == sf::Event::MouseWheelMoved) { 124 | if (event.mouseWheel.delta < 0) { 125 | set_mouse_button_state(button::WHEEL_UP, true); 126 | } else if (event.mouseWheel.delta > 0) { 127 | set_mouse_button_state(button::WHEEL_DOWN, true); 128 | } 129 | } 130 | } 131 | } 132 | 133 | main_window->clear(); 134 | //if (main_detail::use_root_console) console->clear(); 135 | 136 | on_tick(duration_ms); 137 | 138 | if (main_detail::use_root_console) { 139 | console->render(*main_window); 140 | } else { 141 | gui->render(*main_window); 142 | } 143 | 144 | if (optional_display_hook) { 145 | main_window->pushGLStates(); 146 | main_window->resetGLStates(); 147 | optional_display_hook(); 148 | main_window->popGLStates(); 149 | } 150 | main_window->display(); 151 | if (main_detail::taking_screenshot) { 152 | sf::Image screen = rltk::get_window()->capture(); 153 | screen.saveToFile(main_detail::screenshot_filename); 154 | main_detail::screenshot_filename = ""; 155 | main_detail::taking_screenshot = false; 156 | } 157 | 158 | duration_ms = ((clock() - start_time) * 1000.0) / CLOCKS_PER_SEC; 159 | } 160 | } 161 | 162 | void request_screenshot(const std::string &filename) { 163 | main_detail::taking_screenshot = true; 164 | main_detail::screenshot_filename = filename; 165 | } 166 | 167 | } 168 | -------------------------------------------------------------------------------- /rltk/rltk.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* RLTK (RogueLike Tool Kit) 1.00 3 | * Copyright (c) 2016-Present, Bracket Productions. 4 | * Licensed under the MIT license - see LICENSE file. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include "font_manager.hpp" 11 | #include "texture_resources.hpp" 12 | #include "virtual_terminal.hpp" 13 | #include "colors.hpp" 14 | #include "rng.hpp" 15 | #include "geometry.hpp" 16 | #include "path_finding.hpp" 17 | #include "input_handler.hpp" 18 | #include "visibility.hpp" 19 | #include "gui.hpp" 20 | #include "ecs.hpp" 21 | #include "perlin_noise.hpp" 22 | #include "serialization_utils.hpp" 23 | #include "rexspeeder.hpp" 24 | #include "scaling.hpp" 25 | 26 | namespace rltk { 27 | 28 | /* 29 | * Defines a simple configuration to get most people started. 8x8 font, window size 1024x768. 30 | */ 31 | struct config_simple_px { 32 | config_simple_px(const std::string fontpath, const int width=1024, const int height=768, const std::string title="RLTK Roguelike", 33 | const std::string font="8x8", bool full_screen=false) : 34 | font_path(fontpath), width_px(width), height_px(height), window_title(title), root_font(font), fullscreen(full_screen) {} 35 | 36 | const std::string font_path; 37 | const int width_px; 38 | const int height_px; 39 | const std::string window_title; 40 | const std::string root_font; 41 | const bool fullscreen; 42 | }; 43 | 44 | /* 45 | * Defines a simple configuration to get most people started. 8x8 font, window size 128x96 (which happens to be 1024x768) 46 | */ 47 | struct config_simple { 48 | config_simple(const std::string fontpath, const int width_term=128, const int height_term=96, 49 | const std::string title="RLTK Roguelike", const std::string font="8x8", bool full_screen=false) : 50 | font_path(fontpath), width(width_term), height(height_term), window_title(title), root_font(font), fullscreen(full_screen) {} 51 | 52 | const std::string font_path; 53 | const int width; 54 | const int height; 55 | const std::string window_title; 56 | const std::string root_font; 57 | const bool fullscreen; 58 | }; 59 | 60 | /* 61 | * Defines an advanced configuration. No root console, so it is designed for the times you want to build your own GUI. 62 | */ 63 | struct config_advanced { 64 | config_advanced(const std::string fontpath, const int width=1024, const int height=768, const std::string title="RLTK Roguelike", 65 | bool full_screen=false) : 66 | font_path(fontpath), width_px(width), height_px(height), window_title(title), fullscreen(full_screen) {} 67 | 68 | const std::string font_path; 69 | const int width_px; 70 | const int height_px; 71 | const std::string window_title; 72 | const bool fullscreen; 73 | }; 74 | 75 | /* 76 | * Bootstrap the system with a simple configuration, specified in pixels. 77 | * This variant uses terminal coordinates - so specify 80x40 as size and it will scale with the terminal size. 78 | */ 79 | void init(const config_simple &config); 80 | 81 | /* 82 | * Bootstrap the system with a simple configuration, specified in pixels. 83 | * This variant uses screen coordinates. 84 | */ 85 | void init(const config_simple_px &config); 86 | 87 | /* 88 | * Bootstrap the system with a simple configuration, specified in pixels. 89 | * This variant doesn't set up much automatically; it's designed for the times you want to define your own GUI. 90 | */ 91 | void init(const config_advanced &config); 92 | 93 | /* 94 | * The main run loop. Calls on_tick each frame. Window can be initially defined with width/height/title, but these 95 | * have sane defaults to get you started. 96 | */ 97 | void run(std::function on_tick); 98 | 99 | /* 100 | * For rendering to the console 101 | */ 102 | extern std::unique_ptr console; 103 | 104 | /* 105 | * In case you want to do some SFML stuff yourself, this provides a pointer to the render window. 106 | */ 107 | sf::RenderWindow * get_window(); 108 | 109 | /* 110 | * For GUI manipulation 111 | */ 112 | extern std::unique_ptr gui; 113 | 114 | /* 115 | * Convenience function to quickly get a GUI layer 116 | */ 117 | inline layer_t * layer(const int &handle) { return gui->get_layer(handle); } 118 | inline virtual_terminal * term(const int &handle) { return gui->get_layer(handle)->console.get(); } 119 | inline virtual_terminal_sparse * sterm(const int &handle) { return gui->get_layer(handle)->sconsole.get(); } 120 | 121 | /* Request a screenshot */ 122 | void request_screenshot(const std::string &filename); 123 | 124 | /* Lifecycle hooks, for example to integrate ImGui with your application. */ 125 | extern std::function optional_event_hook; 126 | extern std::function optional_display_hook; 127 | } 128 | -------------------------------------------------------------------------------- /rltk/rng.cpp: -------------------------------------------------------------------------------- 1 | #include "rng.hpp" 2 | 3 | #include 4 | #include 5 | 6 | namespace rltk { 7 | 8 | int random_number_generator::fastrand() { 9 | g_seed = (214013 * g_seed + 2531011); 10 | return (g_seed >> 16) & 0x7FFF; 11 | } 12 | 13 | random_number_generator::random_number_generator() { 14 | initial_seed = time(nullptr); 15 | g_seed = initial_seed; 16 | } 17 | 18 | random_number_generator::random_number_generator(const int seed) { 19 | initial_seed = seed; 20 | g_seed = initial_seed; 21 | } 22 | 23 | random_number_generator::random_number_generator(const std::string seed) { 24 | std::hash hash_func; 25 | initial_seed = hash_func(seed); 26 | g_seed = initial_seed; 27 | } 28 | 29 | int random_number_generator::roll_dice(const int &n, const int &d) { 30 | int total = 0; 31 | for (int i = 0; i < n; ++i) { 32 | total += ((fastrand() % d)+1); 33 | } 34 | return total; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /rltk/rng.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* RLTK (RogueLike Tool Kit) 1.00 4 | * Copyright (c) 2016-Present, Bracket Productions. 5 | * Licensed under the MIT license - see LICENSE file. 6 | * 7 | * Random number generator class. 8 | */ 9 | 10 | #include 11 | 12 | namespace rltk 13 | { 14 | 15 | class random_number_generator 16 | { 17 | public: 18 | random_number_generator(); 19 | random_number_generator(const int seed); 20 | random_number_generator(const std::string seed); 21 | 22 | int roll_dice(const int &n, const int &d); 23 | int initial_seed; 24 | private: 25 | int fastrand(); 26 | int g_seed; 27 | }; 28 | 29 | } -------------------------------------------------------------------------------- /rltk/scaling.cpp: -------------------------------------------------------------------------------- 1 | #include "scaling.hpp" 2 | 3 | namespace rltk { 4 | float scale_factor = 1.0f; 5 | } -------------------------------------------------------------------------------- /rltk/scaling.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace rltk { 4 | extern float scale_factor; 5 | } -------------------------------------------------------------------------------- /rltk/texture.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* RLTK (RogueLike Tool Kit) 1.00 3 | * Copyright (c) 2016-Present, Bracket Productions. 4 | * Licensed under the MIT license - see LICENSE file. 5 | * 6 | * Provides RIAA wrapper for a texture. 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace rltk { 15 | 16 | struct texture { 17 | texture() {} 18 | texture(const std::string &filename) { 19 | tex = std::make_unique(); 20 | if (!tex->loadFromFile(filename)) 21 | { 22 | throw std::runtime_error("Unable to load texture from: " + filename); 23 | } 24 | } 25 | std::unique_ptr tex; 26 | }; 27 | 28 | } 29 | -------------------------------------------------------------------------------- /rltk/texture_resources.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "texture_resources.hpp" 4 | #include "texture.hpp" 5 | 6 | namespace rltk { 7 | 8 | namespace texture_detail { 9 | 10 | std::unordered_map atlas; 11 | 12 | } 13 | 14 | void register_texture(const std::string &filename, const std::string &tag) { 15 | auto finder = texture_detail::atlas.find(tag); 16 | if (finder != texture_detail::atlas.end()) { 17 | throw std::runtime_error("Duplicate resource tag: " + tag); 18 | } 19 | 20 | texture_detail::atlas[tag] = rltk::texture(filename); 21 | } 22 | 23 | sf::Texture * get_texture(const std::string &tag) { 24 | auto finder = texture_detail::atlas.find(tag); 25 | if (finder == texture_detail::atlas.end()) { 26 | throw std::runtime_error("Unable to find resource tag: " + tag); 27 | } else { 28 | return finder->second.tex.get(); 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /rltk/texture_resources.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* RLTK (RogueLike Tool Kit) 1.00 3 | * Copyright (c) 2016-Present, Bracket Productions. 4 | * Licensed under the MIT license - see LICENSE file. 5 | * 6 | * Provides a global texture atlas. 7 | */ 8 | 9 | #include 10 | #include 11 | 12 | namespace rltk { 13 | 14 | void register_texture(const std::string &filename, const std::string &tag); 15 | sf::Texture * get_texture(const std::string &tag); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /rltk/vchar.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* RLTK (RogueLike Tool Kit) 1.00 3 | * Copyright (c) 2016-Present, Bracket Productions. 4 | * Licensed under the MIT license - see LICENSE file. 5 | * 6 | * Virtual terminal character. 7 | */ 8 | 9 | #include "color_t.hpp" 10 | #include 11 | 12 | namespace rltk { 13 | /* 14 | * Represents a character on a virtual terminal. 15 | */ 16 | struct vchar { 17 | vchar() {} 18 | vchar(const uint32_t &g, const color_t &f, const color_t &b) : glyph(g), foreground(f), background(b) {} 19 | vchar(const int &g, const color_t &f, const color_t &b) : glyph(static_cast(g)), foreground(f), background(b) {} 20 | vchar(const uint32_t &gly, const uint8_t &fr, const uint8_t &fg, const uint8_t &fb, const uint8_t &br, const uint8_t &bg, const uint8_t &bb) : 21 | glyph(gly), foreground(color_t{fr, fg, fb}), background(color_t{br, bg, bb}) {} 22 | 23 | uint32_t glyph; 24 | color_t foreground; 25 | color_t background; 26 | 27 | template 28 | void serialize(Archive & archive) 29 | { 30 | archive( glyph, foreground, background ); // serialize things by passing them to the archive 31 | } 32 | }; 33 | } -------------------------------------------------------------------------------- /rltk/virtual_terminal.cpp: -------------------------------------------------------------------------------- 1 | #include "virtual_terminal.hpp" 2 | #include "texture_resources.hpp" 3 | #include "scaling.hpp" 4 | #include 5 | #include 6 | #include 7 | 8 | namespace rltk { 9 | 10 | void virtual_terminal::resize_pixels(const int width, const int height) noexcept { 11 | int w = static_cast(width/(font->character_size.first * scale_factor)); 12 | int h = static_cast(height/(font->character_size.second * scale_factor)); 13 | resize_chars(w,h); 14 | } 15 | 16 | void virtual_terminal::resize_chars(const int width, const int height) noexcept { 17 | dirty = true; 18 | const int num_chars = width*(height+1); 19 | buffer.resize(num_chars); 20 | term_width = width; 21 | term_height = height; 22 | backing.create(term_width * font->character_size.first, term_height * font->character_size.second); 23 | 24 | // Build the vertex buffer 25 | vertices.setPrimitiveType(sf::Quads); 26 | if (has_background) { 27 | vertices.resize(width * height * 8); 28 | } else { 29 | vertices.resize(width * height * 4); 30 | } 31 | 32 | if (has_background) { 33 | const int idx_half = width * height * 4; 34 | const int font_width = font->character_size.first; 35 | const int font_height = font->character_size.second; 36 | 37 | const int space_x = (219 % 16) * font_width; 38 | const int space_y = (219 / 16) * font_height; 39 | 40 | for (int y=0; y(x * font->character_size.first), static_cast(y * font->character_size.second)); 45 | vertices[idx+1].position = sf::Vector2f(static_cast((x+1) * font->character_size.first), static_cast(y * font->character_size.second)); 46 | vertices[idx+2].position = sf::Vector2f(static_cast((x+1) * font->character_size.first), static_cast((y+1) * font->character_size.second)); 47 | vertices[idx+3].position = sf::Vector2f(static_cast(x * font->character_size.first), static_cast((y+1) * font->character_size.second)); 48 | 49 | vertices[idx].texCoords = sf::Vector2f(static_cast(space_x), static_cast(space_y) ); 50 | vertices[idx+1].texCoords = sf::Vector2f(static_cast(space_x + font_width), static_cast(space_y) ); 51 | vertices[idx+2].texCoords = sf::Vector2f(static_cast(space_x + font_width), static_cast(space_y + font_height) ); 52 | vertices[idx+3].texCoords = sf::Vector2f(static_cast(space_x), static_cast(space_y + font_height )); 53 | 54 | vertices[idx2].position = sf::Vector2f(static_cast(x * font->character_size.first), static_cast(y * font->character_size.second)); 55 | vertices[idx2+1].position = sf::Vector2f(static_cast((x+1) * font->character_size.first), static_cast(y * font->character_size.second)); 56 | vertices[idx2+2].position = sf::Vector2f(static_cast((x+1) * font->character_size.first), static_cast((y+1) * font->character_size.second)); 57 | vertices[idx2+3].position = sf::Vector2f(static_cast(x * font->character_size.first), static_cast((y+1) * font->character_size.second)); 58 | } 59 | } 60 | } else { 61 | for (int y=0; y(x * font->character_size.first), static_cast(y * font->character_size.second)); 65 | vertices[idx+1].position = sf::Vector2f(static_cast((x+1) * font->character_size.first), static_cast(y * font->character_size.second)); 66 | vertices[idx+2].position = sf::Vector2f(static_cast((x+1) * font->character_size.first), static_cast((y+1) * font->character_size.second)); 67 | vertices[idx+3].position = sf::Vector2f(static_cast(x * font->character_size.first), static_cast((y+1) * font->character_size.second)); 68 | } 69 | } 70 | } 71 | } 72 | 73 | void virtual_terminal::clear() noexcept { 74 | dirty = true; 75 | std::fill(buffer.begin(), buffer.end(), vchar{ 32, {255,255,255}, {0,0,0} }); 76 | } 77 | 78 | void virtual_terminal::clear(const vchar &target) noexcept { 79 | dirty = true; 80 | std::fill(buffer.begin(), buffer.end(), target); 81 | } 82 | 83 | void virtual_terminal::set_char(const int idx, const vchar &target) noexcept { 84 | dirty = true; 85 | if (!(idx < 0 || idx > static_cast(buffer.size()))) { 86 | buffer[idx] = target; 87 | } 88 | } 89 | 90 | void virtual_terminal::print(const int x, const int y, const std::string &s, const color_t &fg, const color_t &bg) noexcept { 91 | int idx = at(x,y); 92 | for (std::size_t i=0; i((term_width/2) - (s.size()/2)), y, s, fg, bg); 100 | } 101 | 102 | void virtual_terminal::box(const int x, const int y, const int w, const int h, const color_t &fg, const color_t &bg, bool double_lines) noexcept { 103 | //std::cout << h << "\n"; 104 | for (int i=1; itexture_tag); 171 | } 172 | const int font_width = font->character_size.first; 173 | const int font_height = font->character_size.second; 174 | 175 | int vertex_idx = term_height * term_width * 4; 176 | if (!has_background) vertex_idx = 0; 177 | int bg_idx = 0; 178 | int idx=0; 179 | for (int y=0; y(texture_x), static_cast(texture_y) ); 199 | vertices[vertex_idx+1].texCoords = sf::Vector2f(static_cast(texture_x + font_width), static_cast(texture_y) ); 200 | vertices[vertex_idx+2].texCoords = sf::Vector2f(static_cast(texture_x + font_width), static_cast(texture_y + font_height) ); 201 | vertices[vertex_idx+3].texCoords = sf::Vector2f(static_cast(texture_x), static_cast(texture_y + font_height) ); 202 | 203 | sf::Color fgsfml = color_to_sfml(target.foreground); 204 | vertices[vertex_idx].color = fgsfml; 205 | vertices[vertex_idx+1].color = fgsfml; 206 | vertices[vertex_idx+2].color = fgsfml; 207 | vertices[vertex_idx+3].color = fgsfml; 208 | 209 | vertex_idx += 4; 210 | bg_idx += 4; 211 | ++idx; 212 | } 213 | } 214 | backing.clear(sf::Color(0,0,0,0)); 215 | backing.draw(vertices, tex); 216 | } 217 | 218 | backing.display(); 219 | sf::Sprite compositor(backing.getTexture()); 220 | compositor.move(static_cast(offset_x), static_cast(offset_y)); 221 | compositor.setColor(sf::Color(tint.r, tint.g, tint.b, alpha)); 222 | compositor.scale(scale_factor, scale_factor); 223 | window.draw(sf::Sprite(compositor)); 224 | dirty = false; 225 | } 226 | 227 | } 228 | -------------------------------------------------------------------------------- /rltk/virtual_terminal.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* RLTK (RogueLike Tool Kit) 1.00 3 | * Copyright (c) 2016-Present, Bracket Productions. 4 | * Licensed under the MIT license - see LICENSE file. 5 | * 6 | * Provides a virtual console 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include "font_manager.hpp" 13 | #include "texture.hpp" 14 | #include "color_t.hpp" 15 | #include "colors.hpp" 16 | #include "rexspeeder.hpp" 17 | #include "vchar.hpp" 18 | 19 | namespace rltk { 20 | 21 | /* 22 | * Combines the functions/data required to render a virtual terminal, either as root or 23 | * as a layer. This is generally initialized by the library, not the user. 24 | */ 25 | struct virtual_terminal { 26 | public: 27 | /* 28 | * Constructor to create a virtual terminal. Parameters: 29 | * fontt the tag of the bitmap font to use for the terminal. 30 | * x (defaults to 0); an offset at which to render the terminal. 31 | * y (defaults to 0); an offset at which to render the terminal. 32 | * background; if true, then solid backgrounds will be rendered. If false, then no background rendered. 33 | */ 34 | virtual_terminal(const std::string fontt, const int x=0, const int y=0, const bool background=true) : font_tag(fontt), offset_x(x), offset_y(y), has_background(background) { 35 | font = get_font(fontt); 36 | } 37 | 38 | /* 39 | * Resize the terminal to match width x height pixels. 40 | */ 41 | void resize_pixels(const int width, const int height) noexcept; 42 | 43 | /* 44 | * Resize the terminal to match width x height virtual characters. 45 | */ 46 | void resize_chars(const int width, const int height) noexcept; 47 | 48 | /* 49 | * Clears the virtual terminal to black spaces. 50 | */ 51 | void clear() noexcept; 52 | 53 | /* 54 | * Clears the virtual terminal to a user-provided character. 55 | * vchar; the character to which the terminal should be set. 56 | */ 57 | void clear(const vchar &target) noexcept; 58 | 59 | /* 60 | * Helper function that returns the index of a character location in the backing vector. 61 | */ 62 | inline int at(const int x, const int y) noexcept { return ( y * term_width) + x; } 63 | 64 | /* 65 | * Set a character at backing-vector idx (use "at" to calculate) to the specified target virtual 66 | * character. 67 | */ 68 | void set_char(const int idx, const vchar &target) noexcept; 69 | 70 | /* 71 | * Set a character at x/y to the specified target virtual character. 72 | */ 73 | inline void set_char(const int x, const int y, vchar target) noexcept { set_char(at(x,y), target); } 74 | 75 | /* 76 | * Print a string to the terminal, at location x/y, string s, with foreground and background of fg and bg. 77 | * If you don't specify colors, it will do white on black. 78 | */ 79 | void print(const int x, const int y, const std::string &s, const color_t &fg = colors::WHITE, const color_t &bg = colors::BLACK) noexcept; 80 | 81 | /* 82 | * Center a string (relative to the terminal size), and print it at location y. 83 | */ 84 | void print_center(const int y, const std::string &s, const color_t &fg = colors::WHITE, const color_t &bg = colors::BLACK) noexcept; 85 | 86 | /* 87 | * Draw a box taht encompasses the whole terminal boundary, in color fg/bg. If double_lines is set to true, then 88 | * it will use the two-line/thick box characters. 89 | */ 90 | inline void box(const color_t &fg = colors::WHITE, const color_t &bg = colors::BLACK, bool double_lines=false) noexcept { 91 | box (0, 0, term_width-1, term_height-1, fg, bg, double_lines); 92 | } 93 | 94 | /* 95 | * Draw a box at x/y of size w/h, in color fg/bg (or black/white if not specified). If double_lines is true, 96 | * it will use the two-line/thick box characters. 97 | */ 98 | void box(const int x, const int y, const int w, const int h, const color_t &fg = colors::WHITE, const color_t &bg = colors::BLACK, bool double_lines=false) noexcept; 99 | 100 | /* 101 | * Fill a region with a specified character 102 | */ 103 | void fill(const int left_x, const int top_y, const int right_x, const int bottom_y, const uint8_t glyph, const color_t &fg = colors::WHITE, const color_t &bg = colors::BLACK) noexcept; 104 | 105 | /* 106 | * Draw a REX sprite at the specified location. 107 | */ 108 | void draw_sprite(const int x, const int y, xp::rex_sprite &sprite); 109 | 110 | /* 111 | * Renders the terminal to the specified renderable. Don't call this directly - the toolkit will take care of it. 112 | */ 113 | void render(sf::RenderWindow &window); 114 | 115 | /* 116 | * Sets the global translucency level for the console. You can use this to make a translucent console layer on top of 117 | * other items. 118 | */ 119 | inline void set_alpha(const uint8_t new_alpha) noexcept { alpha = new_alpha; } 120 | 121 | /* 122 | * Use this to tint the entire rendering of the console. 123 | */ 124 | inline void set_tint(const color_t new_tint) noexcept { tint = new_tint; } 125 | 126 | /* 127 | * Use this to move the terminal in x/y screen pixels. 128 | */ 129 | inline void set_offset(int x, int y) noexcept { offset_x = x; offset_y = y; }; 130 | 131 | /* 132 | * This gets the current font size in pixels, first is width, second is height. 133 | */ 134 | inline std::pair get_font_size() noexcept { return font->character_size; } 135 | 136 | inline std::string get_font_tag() noexcept { return font->texture_tag; } 137 | 138 | int term_width; 139 | int term_height; 140 | bool visible = true; 141 | bool dirty = true; // Flag for requiring a re-draw 142 | 143 | private: 144 | sf::RenderTexture backing; 145 | sf::VertexArray vertices; 146 | std::string font_tag; 147 | int offset_x; 148 | int offset_y; 149 | uint8_t alpha = 255; 150 | color_t tint{255,255,255}; 151 | bool has_background; 152 | bitmap_font * font = nullptr; 153 | sf::Texture * tex = nullptr; 154 | std::vector buffer; 155 | 156 | }; 157 | 158 | } -------------------------------------------------------------------------------- /rltk/virtual_terminal_sparse.cpp: -------------------------------------------------------------------------------- 1 | #include "virtual_terminal_sparse.hpp" 2 | #include "texture_resources.hpp" 3 | #include "scaling.hpp" 4 | 5 | namespace rltk { 6 | 7 | void virtual_terminal_sparse::resize_pixels(const int width, const int height) { 8 | int w = static_cast(width/(font->character_size.first * scale_factor)); 9 | int h = static_cast(height/(font->character_size.second * scale_factor)); 10 | resize_chars(w,h); 11 | } 12 | 13 | void virtual_terminal_sparse::resize_chars(const int width, const int height) { 14 | dirty = true; 15 | const int num_chars = width*(height+1); 16 | buffer.resize(num_chars); 17 | term_width = width; 18 | term_height = height; 19 | backing.create(term_width * font->character_size.first, term_height * font->character_size.second); 20 | } 21 | 22 | void virtual_terminal_sparse::render(sf::RenderWindow &window) { 23 | if (!visible) return; 24 | 25 | if (dirty) { 26 | if (font == nullptr) { 27 | throw std::runtime_error("Font not loaded: " + font_tag); 28 | } 29 | if (tex == nullptr) { 30 | tex = get_texture(font->texture_tag); 31 | } 32 | const int font_width = font->character_size.first; 33 | const int font_height = font->character_size.second; 34 | const float fontW = static_cast(font_width); 35 | const float fontH = static_cast(font_height); 36 | const sf::Vector2f origin(fontW/2.0f, fontH/2.0f); 37 | 38 | const int space_x = (219 % 16) * font_width; 39 | const int space_y = (219 / 16) * font_height; 40 | 41 | // We want to iterate the display vector, and put sprites on the screen for each entry 42 | backing.clear(sf::Color(0,0,0,0)); 43 | for (const xchar &target : buffer) { 44 | 45 | const int texture_x = (target.glyph % 16) * font_width; 46 | const int texture_y = (target.glyph / 16) * font_height; 47 | sf::Vector2f position((target.x * fontW) + fontW/2.0f, (target.y * fontH) + fontH/2.0f); 48 | 49 | if (target.has_background) { 50 | sf::Color bgsfml = color_to_sfml(target.background); 51 | sf::Sprite sprite; 52 | sprite.setTexture(*tex); 53 | sprite.setTextureRect(sf::IntRect(space_x, space_y, font_width, font_height)); 54 | sprite.setColor(bgsfml); 55 | sprite.setOrigin(origin); 56 | sprite.setPosition(position); 57 | sprite.setRotation(static_cast(target.angle)); 58 | backing.draw(sprite); 59 | } 60 | sf::Color fgsfml = color_to_sfml(target.foreground); 61 | sf::Sprite sprite; 62 | sprite.setTexture(*tex); 63 | sprite.setTextureRect(sf::IntRect(texture_x, texture_y, font_width, font_height)); 64 | sprite.setColor(fgsfml); 65 | sprite.setOrigin(origin); 66 | sprite.setPosition(position); 67 | sprite.setRotation(static_cast(target.angle)); 68 | backing.draw(sprite); 69 | 70 | } 71 | } 72 | 73 | // Draw the backing 74 | backing.display(); 75 | sf::Sprite compositor(backing.getTexture()); 76 | compositor.move(static_cast(offset_x), static_cast(offset_y)); 77 | compositor.setColor(sf::Color(tint.r, tint.g, tint.b, alpha)); 78 | compositor.scale(scale_factor, scale_factor); 79 | window.draw(sf::Sprite(compositor)); 80 | dirty = false; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /rltk/virtual_terminal_sparse.hpp: -------------------------------------------------------------------------------- 1 | /* RLTK (RogueLike Tool Kit) 1.00 2 | * Copyright (c) 2016-Present, Bracket Productions. 3 | * Licensed under the MIT license - see LICENSE file. 4 | * 5 | * Provides a virtual console, in 'sparse' mode: it doesn't assume that you have a regular grid, 6 | * supports off-alignment rendering, rotation and similar. It is optimized for use with a few 7 | * characters, rather than a complete grid. A common usage would be as a layer for PCs/NPCs, 8 | * rendered over a regular console grid. 9 | */ 10 | 11 | #pragma once 12 | 13 | #include 14 | #include 15 | #include "font_manager.hpp" 16 | #include "texture.hpp" 17 | #include "color_t.hpp" 18 | #include "colors.hpp" 19 | 20 | namespace rltk { 21 | 22 | /* 23 | * Represents a character on a sparse virtual terminal. 24 | */ 25 | struct xchar { 26 | xchar() {} 27 | xchar(const int Glyph, const color_t fg, const float X, const float Y) : glyph(Glyph), foreground(fg), x(X), y(Y) {} 28 | xchar(const int Glyph, const color_t fg, const float X, const float Y, const int ANGLE) : glyph(Glyph), foreground(fg), x(X), y(Y), angle(ANGLE) {} 29 | int glyph; // Glyph to render 30 | color_t foreground; // Foreground color 31 | float x = 0.0f; // Provided as floats to allow for partial character movement/sliding 32 | float y = 0.0f; 33 | int angle = 0; // Rotation angle in degrees, defaults to 0 - not rotated 34 | unsigned char opacity = 255; 35 | bool has_background = false; 36 | color_t background; // If provided, a background is drawn 37 | }; 38 | 39 | struct virtual_terminal_sparse { 40 | 41 | virtual_terminal_sparse(const std::string fontt, const int x=0, const int y=0) : font_tag(fontt), offset_x(x), offset_y(y) { 42 | font = get_font(fontt); 43 | } 44 | 45 | void resize_pixels(const int width, const int height); 46 | void resize_chars(const int width, const int height); 47 | inline void clear() { 48 | dirty = true; 49 | buffer.clear(); 50 | } 51 | inline void add(const xchar target) { 52 | dirty = true; 53 | buffer.push_back(target); 54 | } 55 | void render(sf::RenderWindow &window); 56 | 57 | int term_width; 58 | int term_height; 59 | bool visible = true; 60 | bool dirty = true; // Flag for requiring a re-draw 61 | 62 | private: 63 | std::string font_tag; 64 | int offset_x; 65 | int offset_y; 66 | uint8_t alpha = 255; 67 | color_t tint{255,255,255}; 68 | bitmap_font * font = nullptr; 69 | sf::Texture * tex = nullptr; 70 | std::vector buffer; 71 | sf::RenderTexture backing; 72 | }; 73 | 74 | } 75 | -------------------------------------------------------------------------------- /rltk/visibility.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* RLTK (RogueLike Tool Kit) 1.00 3 | * Copyright (c) 2016-Present, Bracket Productions. 4 | * Licensed under the MIT license - see LICENSE file. 5 | * 6 | * Provides a wrapper for bitmap fonts. 7 | */ 8 | 9 | #include 10 | #include "geometry.hpp" 11 | 12 | namespace rltk { 13 | 14 | namespace visibility_private { 15 | 16 | /* 17 | * Benchmark results from example 6 (on my desktop): 18 | * - Using line_func, it averages 0.3625 uS per line. 19 | * - Using line_func_cancellable, it averages 0.25 uS per line. 20 | * - Using a naieve float based slope, it averages 0.2 uS per line. 21 | */ 22 | 23 | template 24 | void internal_2d_sweep(const location_t_ &position, const int &range, std::function set_visible, 25 | std::function is_opaque, const std::pair offset) 26 | { 27 | bool blocked = false; 28 | const int start_x = navigator_t::get_x(position); 29 | const int start_y = navigator_t::get_y(position); 30 | const int end_x = start_x + offset.first; 31 | const int end_y = start_y + offset.second; 32 | 33 | line_func_cancellable(start_x, start_y, end_x, end_y, [&blocked, &is_opaque, &set_visible, &range, &position] (int X, int Y) { 34 | if (blocked) return false; 35 | float distance = distance2d(static_cast(position.x), static_cast(position.y), X, Y); 36 | if (distance <= range) { 37 | location_t_ pos = navigator_t::get_xy(X,Y); 38 | if (!blocked) set_visible(pos); 39 | if (!is_opaque(pos)) blocked = true; 40 | } 41 | return true; 42 | }); 43 | } 44 | 45 | } 46 | 47 | /* Simple all-direction visibility sweep in 2 dimensions. This requires that your location_t utilize an x and y 48 | * component. Parameters: 49 | * position - where you are sweeping from. 50 | * range - the number of tiles you can traverse. 51 | * set_visible - a callback (such as bool set_visible(location_t & loc)) to say "this is visible" 52 | * is_opaque - a callback to ask your map if you can see through a tile. 53 | * 54 | * You must provide a navigator_t, just like for path finding. It must support get_x, get_y, and get_xy. 55 | */ 56 | template 57 | void visibility_sweep_2d(const location_t_ &position, const int &range, std::function set_visible, 58 | std::function is_opaque) 59 | { 60 | // You can always see yourself 61 | set_visible(position); 62 | 63 | // Box-sweep 64 | for (int i=0-range; i(position, range, set_visible, is_opaque, std::make_pair(i, 0-range)); 66 | visibility_private::internal_2d_sweep(position, range, set_visible, is_opaque, std::make_pair(i, range)); 67 | visibility_private::internal_2d_sweep(position, range, set_visible, is_opaque, std::make_pair(0-range, i)); 68 | visibility_private::internal_2d_sweep(position, range, set_visible, is_opaque, std::make_pair(range, i)); 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /rltk/xml.cpp: -------------------------------------------------------------------------------- 1 | #include "xml.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std::string_literals; 11 | 12 | namespace rltk { 13 | 14 | xml_node * xml_node::add_node(const std::string &name) { 15 | children.push_back(xml_node(name, depth+1)); 16 | return &children[children.size()-1]; 17 | } 18 | 19 | void xml_node::add_node(xml_node x) { 20 | children.push_back(x); 21 | } 22 | 23 | std::string xml_node::indent() const { 24 | std::string result; 25 | for (int i=0; i\n"; 31 | for (const auto & val : values) { 32 | lbfile << indent() << " <" << val.first << ":value>" << val.second << "\n"; 33 | } 34 | for (const xml_node &child : children) { 35 | child.save(lbfile); 36 | } 37 | lbfile << indent() << "\n"; 38 | } 39 | 40 | void xml_node::dump(std::stringstream &lbfile) const { 41 | lbfile << indent() << "<" << name << ">\n"; 42 | for (const auto & val : values) { 43 | lbfile << indent() << " <" << val.first << ":value>" << val.second << "\n"; 44 | } 45 | for (const xml_node &child : children) { 46 | child.dump(lbfile); 47 | } 48 | lbfile << indent() << "\n"; 49 | } 50 | 51 | std::string xml_node::dump() const { 52 | std::stringstream ss; 53 | dump(ss); 54 | return ss.str(); 55 | } 56 | 57 | void xml_node::add_value(const std::string &key, const std::string &val) { 58 | values.emplace_back(std::make_pair(key, val)); 59 | } 60 | 61 | static inline std::string <rim(std::string &s) { 62 | s.erase(s.begin(), std::find_if(s.begin(), s.end(), 63 | std::not1(std::ptr_fun(std::isspace)))); 64 | return s; 65 | } 66 | 67 | static inline void erase_all(std::string &s, const std::string &to_remove) { 68 | auto pos = s.find(to_remove); 69 | while (pos != std::string::npos) { 70 | s.erase(pos, to_remove.size()); 71 | pos = s.find(to_remove); 72 | } 73 | } 74 | 75 | static inline std::string remove_xml_braces(std::string s) { 76 | erase_all(s, "<"); 77 | erase_all(s, ">"); 78 | return s; 79 | } 80 | 81 | static inline std::string remove_colon_value(std::string s) { 82 | erase_all(s, ":value"); 83 | return s; 84 | } 85 | 86 | void xml_reader::load() { 87 | std::stack stack; 88 | 89 | bool first_line = true; 90 | std::string line; 91 | while (getline(*lbfile, line)) { 92 | std::string trimmed = ltrim(line); 93 | erase_all(trimmed, "\n"); 94 | 95 | if (first_line) { 96 | root.name = remove_xml_braces(trimmed); 97 | first_line = false; 98 | } else if (trimmed == ""s) { 99 | // We're at the end. 100 | } else if (stack.empty()) { 101 | // We're at the beginning, so we need to create a node. 102 | stack.push(xml_node(remove_xml_braces(trimmed))); 103 | } else if (trimmed == ""s ) { 104 | // We're at the end of the element 105 | if (stack.size() > 1) { 106 | xml_node current = stack.top(); 107 | stack.pop(); 108 | stack.top().add_node(current); 109 | } else { 110 | xml_node current = stack.top(); 111 | stack.pop(); 112 | root.add_node(current); 113 | } 114 | } else { 115 | // Are we looking at a new node or a value? 116 | if (trimmed.find(":value>")==std::string::npos) { 117 | // It's a new child 118 | stack.push(xml_node(remove_xml_braces(trimmed))); 119 | } else { 120 | // It's a value 121 | std::string key = remove_colon_value(remove_xml_braces(trimmed.substr(0,trimmed.find(">")))); 122 | std::string val = trimmed.substr(trimmed.find(">")+1); 123 | val = val.substr(0, val.find("<")); 124 | stack.top().add_value(key, val); 125 | } 126 | } 127 | } 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /rltk/xml.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "color_t.hpp" 11 | #include "vchar.hpp" 12 | #include "filesystem.hpp" 13 | 14 | namespace rltk { 15 | 16 | template 17 | inline T from_string(const std::string &val) { 18 | std::stringstream ss; 19 | ss << val; 20 | T result; 21 | ss >> result; 22 | return result; 23 | } 24 | template<> 25 | inline uint8_t from_string(const std::string &val) { 26 | std::stringstream ss; 27 | ss << val; 28 | int result; 29 | ss >> result; 30 | return static_cast(result); 31 | } 32 | template<> 33 | inline std::string from_string(const std::string &val) { 34 | return val; 35 | } 36 | 37 | struct xml_node { 38 | xml_node() {}; 39 | xml_node(const std::string &Name) : name(Name) {} 40 | xml_node(const std::string &Name, const int Depth) : name(Name), depth(Depth) {} 41 | 42 | xml_node * add_node(const std::string &name); 43 | void add_node(xml_node x); 44 | std::string indent() const; 45 | void save(std::ofstream &lbfile) const; 46 | void dump(std::stringstream &lbfile) const; 47 | std::string dump() const; 48 | void add_value(const std::string &key, const std::string &val); 49 | 50 | std::string name = ""; 51 | 52 | std::size_t count() { 53 | std::size_t count = 0; 54 | for (xml_node &node : children) { 55 | node.count(count); 56 | } 57 | return count; 58 | } 59 | 60 | void count(std::size_t &count) { 61 | ++count; 62 | for (xml_node &node : children) { 63 | node.count(count); 64 | } 65 | } 66 | 67 | xml_node * find(const std::string name) { 68 | for (xml_node &node : children) { 69 | if (node.name == name) return &node; 70 | } 71 | return nullptr; 72 | } 73 | 74 | template 75 | T val(const std::string &key) { 76 | for (const auto &val : values) { 77 | if (val.first == key) { 78 | return from_string(val.second); 79 | } 80 | } 81 | throw std::runtime_error(std::string("Key not found:") + key); 82 | } 83 | 84 | inline void iterate_child(const std::string &name, const std::function &func) { 85 | xml_node * vec = find(name); 86 | for (xml_node child : vec->children) { 87 | func(&child); 88 | } 89 | } 90 | 91 | inline color_t color(const std::string &name) { 92 | xml_node * c = find(name); 93 | return color_t{c->val("r"), c->val("g"), c->val("b")}; 94 | } 95 | 96 | inline rltk::vchar vchar() { 97 | rltk::vchar c; 98 | c.glyph = val("glyph"); 99 | c.foreground = color("foreground"); 100 | c.background = color("background"); 101 | return c; 102 | } 103 | 104 | const int depth = 0; 105 | std::vector children; 106 | std::vector> values; 107 | 108 | }; 109 | 110 | struct xml_writer { 111 | xml_writer(const std::string &fn, const std::string &root_name) : filename(fn), root(xml_node(root_name,0)) { 112 | if (exists(filename)) std::remove(filename.c_str()); 113 | lbfile = std::make_unique(filename, std::ios::out | std::ios::binary); 114 | } 115 | 116 | xml_writer(std::unique_ptr &&f, const std::string &root_name) : lbfile(std::move(f)), root(xml_node(root_name,0)) {} 117 | 118 | inline void commit() { 119 | root.save(*lbfile); 120 | } 121 | 122 | inline xml_node * add_node(const std::string &name) { 123 | return root.add_node(name); 124 | } 125 | 126 | private: 127 | std::unique_ptr lbfile; 128 | const std::string filename = ""; 129 | xml_node root; 130 | }; 131 | 132 | struct xml_reader { 133 | xml_reader(const std::string &fn) : filename(fn) { 134 | if (!exists(filename)) throw std::runtime_error(std::string("File not found: ") + filename); 135 | lbfile = std::make_unique(filename, std::ios::in | std::ios::binary); 136 | load(); 137 | } 138 | 139 | xml_reader(std::unique_ptr &&f) : lbfile(std::move(f)) { 140 | load(); 141 | } 142 | 143 | inline xml_node * get() { return &root; } 144 | 145 | private: 146 | std::unique_ptr lbfile; 147 | const std::string filename = ""; 148 | xml_node root; 149 | 150 | void load(); 151 | }; 152 | 153 | } 154 | -------------------------------------------------------------------------------- /tutorial_images/example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/rltk/58635419d2488dca8e8d7fd2e29b4ba144300cff/tutorial_images/example1.png -------------------------------------------------------------------------------- /tutorial_images/example10.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/rltk/58635419d2488dca8e8d7fd2e29b4ba144300cff/tutorial_images/example10.gif -------------------------------------------------------------------------------- /tutorial_images/example11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/rltk/58635419d2488dca8e8d7fd2e29b4ba144300cff/tutorial_images/example11.png -------------------------------------------------------------------------------- /tutorial_images/example2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/rltk/58635419d2488dca8e8d7fd2e29b4ba144300cff/tutorial_images/example2.gif -------------------------------------------------------------------------------- /tutorial_images/example3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/rltk/58635419d2488dca8e8d7fd2e29b4ba144300cff/tutorial_images/example3.gif -------------------------------------------------------------------------------- /tutorial_images/example4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/rltk/58635419d2488dca8e8d7fd2e29b4ba144300cff/tutorial_images/example4.gif -------------------------------------------------------------------------------- /tutorial_images/example5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/rltk/58635419d2488dca8e8d7fd2e29b4ba144300cff/tutorial_images/example5.gif -------------------------------------------------------------------------------- /tutorial_images/example6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/rltk/58635419d2488dca8e8d7fd2e29b4ba144300cff/tutorial_images/example6.gif -------------------------------------------------------------------------------- /tutorial_images/example7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/rltk/58635419d2488dca8e8d7fd2e29b4ba144300cff/tutorial_images/example7.png -------------------------------------------------------------------------------- /tutorial_images/example8.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/rltk/58635419d2488dca8e8d7fd2e29b4ba144300cff/tutorial_images/example8.gif -------------------------------------------------------------------------------- /tutorial_images/example9.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/rltk/58635419d2488dca8e8d7fd2e29b4ba144300cff/tutorial_images/example9.gif --------------------------------------------------------------------------------