Repository: thebracket/rltk Branch: master Commit: 58635419d248 Files: 64 Total size: 295.8 KB Directory structure: gitextract_vk2s8wpi/ ├── .gitignore ├── CMakeLists.txt ├── CMakeSettings.json ├── LICENSE ├── README.md ├── assets/ │ ├── fonts.txt │ └── nyan.xp ├── 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 ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ /build/ /.kdev4/ /bgame.kdev4 /build_eclipse/ .DS_Store x64 x84 /RltkMaster/packages /RltkMaster/rltk/Debug /RltkMaster/rltk/SFML-2.3.2 *.opendb /RltkMaster/.vs/RltkMaster/v14/.suo ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.1) project("rltk") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake_modules") set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED on) find_package(ZLIB REQUIRED) find_package(SFML 2 COMPONENTS system window graphics REQUIRED) find_package(cereal REQUIRED) add_library(rltk rltk/rltk.cpp rltk/texture_resources.cpp rltk/color_t.cpp rltk/virtual_terminal.cpp rltk/rng.cpp rltk/geometry.cpp rltk/input_handler.cpp rltk/font_manager.cpp rltk/gui.cpp rltk/layer_t.cpp rltk/gui_control_t.cpp rltk/virtual_terminal_sparse.cpp rltk/ecs.cpp rltk/xml.cpp rltk/perlin_noise.cpp rltk/rexspeeder.cpp rltk/scaling.cpp) target_include_directories(rltk PUBLIC "$" "$" "$" ) target_link_libraries(rltk PUBLIC ${ZLIB_LIBRARIES} ${SFML_LIBRARIES}) if(NOT MSVC) # Why was this here? I exempted the wierd linker flags target_compile_options(rltk PUBLIC -O3 -Wall -Wpedantic -march=native -mtune=native -g) else() target_compile_options(rltk PUBLIC /W3 /EHsc) endif() install (TARGETS rltk ARCHIVE DESTINATION lib LIBRARY DESTINATION lib RUNTIME DESTINATION bin) set(RLTK_HEADERS rltk/astar.hpp rltk/colors.hpp rltk/color_t.hpp rltk/ecs.hpp rltk/ecs_impl.hpp rltk/filesystem.hpp rltk/font_manager.hpp rltk/fsa.hpp rltk/geometry.hpp rltk/gui.hpp rltk/gui_control_t.hpp rltk/input_handler.hpp rltk/layer_t.hpp rltk/path_finding.hpp rltk/perlin_noise.hpp rltk/rexspeeder.hpp rltk/rltk.hpp rltk/rng.hpp rltk/scaling.hpp rltk/serialization_utils.hpp rltk/texture.hpp rltk/texture_resources.hpp rltk/vchar.hpp rltk/virtual_terminal.hpp rltk/virtual_terminal_sparse.hpp rltk/visibility.hpp rltk/xml.hpp) install(FILES ${RLTK_HEADERS} DESTINATION "include/rltk" ) # Examples # Add all of the example executables and their library dependency add_executable(ex1 examples/ex1/main.cpp) add_executable(ex2 examples/ex2/main.cpp) add_executable(ex3 examples/ex3/main.cpp) add_executable(ex4 examples/ex4/main.cpp) add_executable(ex5 examples/ex5/main.cpp) add_executable(ex6 examples/ex6/main.cpp) add_executable(ex7 examples/ex7/main.cpp) add_executable(ex8 examples/ex8/main.cpp) add_executable(ex9 examples/ex9/main.cpp) add_executable(ex10 examples/ex10/main.cpp) add_executable(ex11 examples/ex11/main.cpp) target_link_libraries(ex1 rltk) target_link_libraries(ex2 rltk) target_link_libraries(ex3 rltk) target_link_libraries(ex4 rltk) target_link_libraries(ex5 rltk) target_link_libraries(ex6 rltk) target_link_libraries(ex7 rltk) target_link_libraries(ex8 rltk) target_link_libraries(ex9 rltk) target_link_libraries(ex10 rltk) target_link_libraries(ex11 rltk) ================================================ FILE: CMakeSettings.json ================================================ { // See https://go.microsoft.com//fwlink//?linkid=834763 for more information about this file. "configurations": [ { "name": "x86-Debug", "generator": "Visual Studio 15 2017", "configurationType": "Debug", "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "-m -v:minimal", "ctestCommandArgs": "", "variables": [ { "name": "CMAKE_TOOLCHAIN_FILE", "value": "C:/Users/Herbert/Source/Repos/vcpkg/scripts/buildsystems/vcpkg.cmake" } ] }, { "name": "x86-Release", "generator": "Visual Studio 15 2017", "configurationType": "Release", "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "-m -v:minimal", "ctestCommandArgs": "", "variables": [ { "name": "CMAKE_TOOLCHAIN_FILE", "value": "C:/Users/Herbert/Source/Repos/vcpkg/scripts/buildsystems/vcpkg.cmake" } ] }, { "name": "x64-Debug", "generator": "Visual Studio 15 2017 Win64", "configurationType": "Debug", "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "-m -v:minimal", "ctestCommandArgs": "", "variables": [ { "name": "CMAKE_TOOLCHAIN_FILE", "value": "C:/Users/Herbert/Source/Repos/vcpkg/scripts/buildsystems/vcpkg.cmake" } ] }, { "name": "x64-Release", "generator": "Visual Studio 15 2017 Win64", "configurationType": "Release", "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "-m -v:minimal", "ctestCommandArgs": "", "variables": [ { "name": "CMAKE_TOOLCHAIN_FILE", "value": "C:/Users/Herbert/Source/Repos/vcpkg/scripts/buildsystems/vcpkg.cmake" } ] } ] } ================================================ FILE: LICENSE ================================================ Copyright (c) 2016 Bracket Productions (Herbert Wolverson) 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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. ================================================ FILE: README.md ================================================ # rltk : Roguelike Toolkit - Modern (C++14) SFML-based toolkit for creating roguelikes. 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 engine logic from game logic for my sanity; I realized that others might find the underlying engine code useful. Right now, it's a very fast ASCII (code-page 437) terminal renderer compatible with fonts from libtcod and Dwarf Fortress. Eventually, it will provide assistance with a number of game-related topics including path-finding, line-plotting, and probably some of the entity-component-system I've been enjoying. **Credit to Pyridine for the REXPaint format code. Original located at: [https://github.com/pyridine/REXSpeeder](https://github.com/pyridine/REXSpeeder)** ## Building from source 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). ## Building on Visual Studio 2017 * 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/) * Ensure that you've integrated it, with `vcpkg integrate install` (see [here](https://github.com/Microsoft/vcpkg/blob/master/docs/examples/using-sqlite.md)) * Install packages: `vcpkg install sfml` and `vcpkg install cereal`. These take a while. * 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. * 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. * You should now be able to select an output, and build/run it. ## Included Examples (with lots of comments!) 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 heavily as things solidify. I'm doing my best to include examples evolving towards a real roguelike. Currently, these are: ### Example 1: Hello World ![Hello World](https://raw.githubusercontent.com/thebracket/rltk/master/tutorial_images/example1.png "Hello World") [Example 1](https://github.com/thebracket/rltk/blob/master/examples/ex1/main.cpp): demonstrates a Hello World with frame-rate. ### Example 2: Randomly moving @ ![Randomly Moving @](https://raw.githubusercontent.com/thebracket/rltk/master/tutorial_images/example2.gif "Randomly Moving @") [Example 2](https://github.com/thebracket/rltk/blob/master/examples/ex2/main.cpp): a randomly moving yellow @ on a field of white dots. ### Example 3: Bresenham's Line Pathing ![Bresenham Line Pathing](https://raw.githubusercontent.com/thebracket/rltk/master/tutorial_images/example3.gif "Bresenham Line Pathing") [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. ### Example 4: A-Star Pathing ![A Star Pathing](https://raw.githubusercontent.com/thebracket/rltk/master/tutorial_images/example4.gif "A Star Pathing") [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! ### Example 5: Mouse Controlled Path-Finding ![Mouse Driven A Star Pathing](https://raw.githubusercontent.com/thebracket/rltk/master/tutorial_images/example5.gif "Mouse Driven A Star Pathing") [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 cursor. Click and he goes there. ### Example 6: Visibility ![Visibility](https://raw.githubusercontent.com/thebracket/rltk/master/tutorial_images/example6.gif "Visibility") [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. ### Example 7: Multi-font ASCII Layout ![ComplexGui](https://raw.githubusercontent.com/thebracket/rltk/master/tutorial_images/example7.png "Complex GUI") [Example 7](https://github.com/thebracket/rltk/blob/master/examples/ex7/main.cpp): A complex GUI with multiple fonts and layers, dynamically resizable. ### Example 8: Owner draw, and retained-mode GUI elements ![RetainedMode](https://raw.githubusercontent.com/thebracket/rltk/master/tutorial_images/example8.gif "Retained Mode") [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. ### Example 9: Sparse layer with effects ![Sparse](https://raw.githubusercontent.com/thebracket/rltk/master/tutorial_images/example9.gif "Sparse") [Example 9](https://github.com/thebracket/rltk/blob/master/examples/ex9/main.cpp): This demo uses a regular console layer to draw the map, and a "sparse" console layer for the character and traversal path. It uses sub-character alignment to smoothly move the @ around, and 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!). ### Example 10: The beginnings of a roguelike ![Sparse](https://raw.githubusercontent.com/thebracket/rltk/master/tutorial_images/example10.gif "RogueBeginnings") [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! ### Example 11: REXPaint support (http://www.gridsagegames.com/rexpaint/) ![RexPaint](https://raw.githubusercontent.com/thebracket/rltk/master/tutorial_images/example11.png "RexPaint") [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. ## Example The goal is to keep it simple from the user's point of view. The following code is enough to setup an ASCII terminal, and display **Hello World** with a frame-rate displayed (around 100 FPS on my workstation): ```c++ #include "../../rltk/rltk.hpp" #include using namespace rltk; using namespace rltk::colors; void tick(double duration_ms) { std::stringstream ss; ss << "Frame duration: " << duration_ms << " ms (" << (1000.0/duration_ms) << " FPS)."; console->print(1, 1, "Hello World", WHITE, BLACK); console->print(1, 2, ss.str(), YELLOW, BLUE); } int main() { init(config_simple_px("../assets")); run(tick); return 0; } ``` ================================================ FILE: assets/fonts.txt ================================================ 8x8,terminal8x8.png,8,8 16x16,terminal16x16.png,16,16 32x32,terminal32x32.png,32,32 8x16,VGA8x16.png,8,16 ================================================ FILE: cmake_modules/FindSFML.cmake ================================================ # This script locates the SFML library # ------------------------------------ # # Usage # ----- # # When you try to locate the SFML libraries, you must specify which modules you want to use (system, window, graphics, network, audio, main). # If none is given, the SFML_LIBRARIES variable will be empty and you'll end up linking to nothing. # example: # find_package(SFML COMPONENTS graphics window system) # find the graphics, window and system modules # # You can enforce a specific version, either MAJOR.MINOR or only MAJOR. # If nothing is specified, the version won't be checked (i.e. any version will be accepted). # example: # find_package(SFML COMPONENTS ...) # no specific version required # find_package(SFML 2 COMPONENTS ...) # any 2.x version # find_package(SFML 2.4 COMPONENTS ...) # version 2.4 or greater # # By default, the dynamic libraries of SFML will be found. To find the static ones instead, # you must set the SFML_STATIC_LIBRARIES variable to TRUE before calling find_package(SFML ...). # Since you have to link yourself all the SFML dependencies when you link it statically, the following # additional variables are defined: SFML_XXX_DEPENDENCIES and SFML_DEPENDENCIES (see their detailed # description below). # In case of static linking, the SFML_STATIC macro will also be defined by this script. # example: # set(SFML_STATIC_LIBRARIES TRUE) # find_package(SFML 2 COMPONENTS network system) # # On Mac OS X if SFML_STATIC_LIBRARIES is not set to TRUE then by default CMake will search for frameworks unless # CMAKE_FIND_FRAMEWORK is set to "NEVER" for example. Please refer to CMake documentation for more details. # Moreover, keep in mind that SFML frameworks are only available as release libraries unlike dylibs which # are available for both release and debug modes. # # If SFML is not installed in a standard path, you can use the SFML_ROOT CMake (or environment) variable # to tell CMake where SFML is. # # Output # ------ # # This script defines the following variables: # - For each specified module XXX (system, window, graphics, network, audio, main): # - SFML_XXX_LIBRARY_DEBUG: the name of the debug library of the xxx module (set to SFML_XXX_LIBRARY_RELEASE is no debug version is found) # - SFML_XXX_LIBRARY_RELEASE: the name of the release library of the xxx module (set to SFML_XXX_LIBRARY_DEBUG is no release version is found) # - SFML_XXX_LIBRARY: the name of the library to link to for the xxx module (includes both debug and optimized names if necessary) # - SFML_XXX_FOUND: true if either the debug or release library of the xxx module is found # - SFML_XXX_DEPENDENCIES: the list of libraries the module depends on, in case of static linking # - SFML_LIBRARIES: the list of all libraries corresponding to the required modules # - SFML_FOUND: true if all the required modules are found # - SFML_INCLUDE_DIR: the path where SFML headers are located (the directory containing the SFML/Config.hpp file) # - SFML_DEPENDENCIES: the list of libraries SFML depends on, in case of static linking # # example: # find_package(SFML 2 COMPONENTS system window graphics audio REQUIRED) # include_directories(${SFML_INCLUDE_DIR}) # add_executable(myapp ...) # target_link_libraries(myapp ${SFML_LIBRARIES}) # define the SFML_STATIC macro if static build was chosen if(SFML_STATIC_LIBRARIES) add_definitions(-DSFML_STATIC) endif() # define the list of search paths for headers and libraries set(FIND_SFML_PATHS ${SFML_ROOT} $ENV{SFML_ROOT} ~/Library/Frameworks /Library/Frameworks /usr/local /usr /sw /opt/local /opt/csw /opt) # find the SFML include directory find_path(SFML_INCLUDE_DIR SFML/Config.hpp PATH_SUFFIXES include PATHS ${FIND_SFML_PATHS}) # check the version number set(SFML_VERSION_OK TRUE) if(SFML_FIND_VERSION AND SFML_INCLUDE_DIR) # extract the major and minor version numbers from SFML/Config.hpp # we have to handle framework a little bit differently: if("${SFML_INCLUDE_DIR}" MATCHES "SFML.framework") set(SFML_CONFIG_HPP_INPUT "${SFML_INCLUDE_DIR}/Headers/Config.hpp") else() set(SFML_CONFIG_HPP_INPUT "${SFML_INCLUDE_DIR}/SFML/Config.hpp") endif() FILE(READ "${SFML_CONFIG_HPP_INPUT}" SFML_CONFIG_HPP_CONTENTS) STRING(REGEX REPLACE ".*#define SFML_VERSION_MAJOR ([0-9]+).*" "\\1" SFML_VERSION_MAJOR "${SFML_CONFIG_HPP_CONTENTS}") STRING(REGEX REPLACE ".*#define SFML_VERSION_MINOR ([0-9]+).*" "\\1" SFML_VERSION_MINOR "${SFML_CONFIG_HPP_CONTENTS}") STRING(REGEX REPLACE ".*#define SFML_VERSION_PATCH ([0-9]+).*" "\\1" SFML_VERSION_PATCH "${SFML_CONFIG_HPP_CONTENTS}") if (NOT "${SFML_VERSION_PATCH}" MATCHES "^[0-9]+$") set(SFML_VERSION_PATCH 0) endif() math(EXPR SFML_REQUESTED_VERSION "${SFML_FIND_VERSION_MAJOR} * 10000 + ${SFML_FIND_VERSION_MINOR} * 100 + ${SFML_FIND_VERSION_PATCH}") # if we could extract them, compare with the requested version number if (SFML_VERSION_MAJOR) # transform version numbers to an integer math(EXPR SFML_VERSION "${SFML_VERSION_MAJOR} * 10000 + ${SFML_VERSION_MINOR} * 100 + ${SFML_VERSION_PATCH}") # compare them if(SFML_VERSION LESS SFML_REQUESTED_VERSION) set(SFML_VERSION_OK FALSE) endif() else() # SFML version is < 2.0 if (SFML_REQUESTED_VERSION GREATER 10900) set(SFML_VERSION_OK FALSE) set(SFML_VERSION_MAJOR 1) set(SFML_VERSION_MINOR x) set(SFML_VERSION_PATCH x) endif() endif() endif() # find the requested modules set(SFML_FOUND TRUE) # will be set to false if one of the required modules is not found foreach(FIND_SFML_COMPONENT ${SFML_FIND_COMPONENTS}) string(TOLOWER ${FIND_SFML_COMPONENT} FIND_SFML_COMPONENT_LOWER) string(TOUPPER ${FIND_SFML_COMPONENT} FIND_SFML_COMPONENT_UPPER) set(FIND_SFML_COMPONENT_NAME sfml-${FIND_SFML_COMPONENT_LOWER}) # no suffix for sfml-main, it is always a static library if(FIND_SFML_COMPONENT_LOWER STREQUAL "main") # release library find_library(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE NAMES ${FIND_SFML_COMPONENT_NAME} PATH_SUFFIXES lib64 lib PATHS ${FIND_SFML_PATHS}) # debug library find_library(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG NAMES ${FIND_SFML_COMPONENT_NAME}-d PATH_SUFFIXES lib64 lib PATHS ${FIND_SFML_PATHS}) else() # static release library find_library(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_STATIC_RELEASE NAMES ${FIND_SFML_COMPONENT_NAME}-s PATH_SUFFIXES lib64 lib PATHS ${FIND_SFML_PATHS}) # static debug library find_library(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_STATIC_DEBUG NAMES ${FIND_SFML_COMPONENT_NAME}-s-d PATH_SUFFIXES lib64 lib PATHS ${FIND_SFML_PATHS}) # dynamic release library find_library(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DYNAMIC_RELEASE NAMES ${FIND_SFML_COMPONENT_NAME} PATH_SUFFIXES lib64 lib PATHS ${FIND_SFML_PATHS}) # dynamic debug library find_library(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DYNAMIC_DEBUG NAMES ${FIND_SFML_COMPONENT_NAME}-d PATH_SUFFIXES lib64 lib PATHS ${FIND_SFML_PATHS}) # choose the entries that fit the requested link type if(SFML_STATIC_LIBRARIES) if(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_STATIC_RELEASE) set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_STATIC_RELEASE}) endif() if(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_STATIC_DEBUG) set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_STATIC_DEBUG}) endif() else() if(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DYNAMIC_RELEASE) set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DYNAMIC_RELEASE}) endif() if(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DYNAMIC_DEBUG) set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DYNAMIC_DEBUG}) endif() endif() endif() if (SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG OR SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE) # library found set(SFML_${FIND_SFML_COMPONENT_UPPER}_FOUND TRUE) # if both are found, set SFML_XXX_LIBRARY to contain both if (SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG AND SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE) set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY debug ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG} optimized ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE}) endif() # if only one debug/release variant is found, set the other to be equal to the found one if (SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG AND NOT SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE) # debug and not release set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG}) set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG}) endif() if (SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE AND NOT SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG) # release and not debug set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE}) set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE}) endif() else() # library not found set(SFML_FOUND FALSE) set(SFML_${FIND_SFML_COMPONENT_UPPER}_FOUND FALSE) set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY "") set(FIND_SFML_MISSING "${FIND_SFML_MISSING} SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY") endif() # mark as advanced MARK_AS_ADVANCED(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_STATIC_RELEASE SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_STATIC_DEBUG SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DYNAMIC_RELEASE SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DYNAMIC_DEBUG) # add to the global list of libraries set(SFML_LIBRARIES ${SFML_LIBRARIES} "${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY}") endforeach() # in case of static linking, we must also define the list of all the dependencies of SFML libraries if(SFML_STATIC_LIBRARIES) # detect the OS if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") set(FIND_SFML_OS_WINDOWS 1) elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") set(FIND_SFML_OS_LINUX 1) elseif(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") set(FIND_SFML_OS_FREEBSD 1) elseif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") set(FIND_SFML_OS_MACOSX 1) endif() # start with an empty list set(SFML_DEPENDENCIES) set(FIND_SFML_DEPENDENCIES_NOTFOUND) # macro that searches for a 3rd-party library macro(find_sfml_dependency output friendlyname) # No lookup in environment variables (PATH on Windows), as they may contain wrong library versions find_library(${output} NAMES ${ARGN} PATHS ${FIND_SFML_PATHS} PATH_SUFFIXES lib NO_SYSTEM_ENVIRONMENT_PATH) if(${${output}} STREQUAL "${output}-NOTFOUND") unset(output) set(FIND_SFML_DEPENDENCIES_NOTFOUND "${FIND_SFML_DEPENDENCIES_NOTFOUND} ${friendlyname}") endif() endmacro() # sfml-system list(FIND SFML_FIND_COMPONENTS "system" FIND_SFML_SYSTEM_COMPONENT) if(NOT ${FIND_SFML_SYSTEM_COMPONENT} EQUAL -1) # update the list -- these are only system libraries, no need to find them if(FIND_SFML_OS_LINUX OR FIND_SFML_OS_FREEBSD OR FIND_SFML_OS_MACOSX) set(SFML_SYSTEM_DEPENDENCIES "pthread") endif() if(FIND_SFML_OS_LINUX) set(SFML_SYSTEM_DEPENDENCIES ${SFML_SYSTEM_DEPENDENCIES} "rt") endif() if(FIND_SFML_OS_WINDOWS) set(SFML_SYSTEM_DEPENDENCIES "winmm") endif() set(SFML_DEPENDENCIES ${SFML_SYSTEM_DEPENDENCIES} ${SFML_DEPENDENCIES}) endif() # sfml-network list(FIND SFML_FIND_COMPONENTS "network" FIND_SFML_NETWORK_COMPONENT) if(NOT ${FIND_SFML_NETWORK_COMPONENT} EQUAL -1) # update the list -- these are only system libraries, no need to find them if(FIND_SFML_OS_WINDOWS) set(SFML_NETWORK_DEPENDENCIES "ws2_32") endif() set(SFML_DEPENDENCIES ${SFML_NETWORK_DEPENDENCIES} ${SFML_DEPENDENCIES}) endif() # sfml-window list(FIND SFML_FIND_COMPONENTS "window" FIND_SFML_WINDOW_COMPONENT) if(NOT ${FIND_SFML_WINDOW_COMPONENT} EQUAL -1) # find libraries if(FIND_SFML_OS_LINUX OR FIND_SFML_OS_FREEBSD) find_sfml_dependency(X11_LIBRARY "X11" X11) find_sfml_dependency(XRANDR_LIBRARY "Xrandr" Xrandr) endif() if(FIND_SFML_OS_LINUX) find_sfml_dependency(UDEV_LIBRARIES "UDev" udev libudev) endif() # update the list if(FIND_SFML_OS_WINDOWS) set(SFML_WINDOW_DEPENDENCIES ${SFML_WINDOW_DEPENDENCIES} "opengl32" "winmm" "gdi32") elseif(FIND_SFML_OS_LINUX) set(SFML_WINDOW_DEPENDENCIES ${SFML_WINDOW_DEPENDENCIES} "GL" ${X11_LIBRARY} ${XRANDR_LIBRARY} ${UDEV_LIBRARIES}) elseif(FIND_SFML_OS_FREEBSD) set(SFML_WINDOW_DEPENDENCIES ${SFML_WINDOW_DEPENDENCIES} "GL" ${X11_LIBRARY} ${XRANDR_LIBRARY} "usbhid") elseif(FIND_SFML_OS_MACOSX) set(SFML_WINDOW_DEPENDENCIES ${SFML_WINDOW_DEPENDENCIES} "-framework OpenGL -framework Foundation -framework AppKit -framework IOKit -framework Carbon") endif() set(SFML_DEPENDENCIES ${SFML_WINDOW_DEPENDENCIES} ${SFML_DEPENDENCIES}) endif() # sfml-graphics list(FIND SFML_FIND_COMPONENTS "graphics" FIND_SFML_GRAPHICS_COMPONENT) if(NOT ${FIND_SFML_GRAPHICS_COMPONENT} EQUAL -1) # find libraries find_sfml_dependency(FREETYPE_LIBRARY "FreeType" freetype) find_sfml_dependency(JPEG_LIBRARY "libjpeg" jpeg) # update the list set(SFML_GRAPHICS_DEPENDENCIES ${FREETYPE_LIBRARY} ${JPEG_LIBRARY}) set(SFML_DEPENDENCIES ${SFML_GRAPHICS_DEPENDENCIES} ${SFML_DEPENDENCIES}) endif() # sfml-audio list(FIND SFML_FIND_COMPONENTS "audio" FIND_SFML_AUDIO_COMPONENT) if(NOT ${FIND_SFML_AUDIO_COMPONENT} EQUAL -1) # find libraries find_sfml_dependency(OPENAL_LIBRARY "OpenAL" openal openal32) find_sfml_dependency(OGG_LIBRARY "Ogg" ogg) find_sfml_dependency(VORBIS_LIBRARY "Vorbis" vorbis) find_sfml_dependency(VORBISFILE_LIBRARY "VorbisFile" vorbisfile) find_sfml_dependency(VORBISENC_LIBRARY "VorbisEnc" vorbisenc) find_sfml_dependency(FLAC_LIBRARY "FLAC" FLAC) # update the list set(SFML_AUDIO_DEPENDENCIES ${OPENAL_LIBRARY} ${FLAC_LIBRARY} ${VORBISENC_LIBRARY} ${VORBISFILE_LIBRARY} ${VORBIS_LIBRARY} ${OGG_LIBRARY}) set(SFML_DEPENDENCIES ${SFML_DEPENDENCIES} ${SFML_AUDIO_DEPENDENCIES}) endif() endif() # handle errors if(NOT SFML_VERSION_OK) # SFML version not ok set(FIND_SFML_ERROR "SFML found but version too low (requested: ${SFML_FIND_VERSION}, found: ${SFML_VERSION_MAJOR}.${SFML_VERSION_MINOR}.${SFML_VERSION_PATCH})") set(SFML_FOUND FALSE) elseif(SFML_STATIC_LIBRARIES AND FIND_SFML_DEPENDENCIES_NOTFOUND) set(FIND_SFML_ERROR "SFML found but some of its dependencies are missing (${FIND_SFML_DEPENDENCIES_NOTFOUND})") set(SFML_FOUND FALSE) elseif(NOT SFML_FOUND) # include directory or library not found set(FIND_SFML_ERROR "Could NOT find SFML (missing: ${FIND_SFML_MISSING})") endif() if (NOT SFML_FOUND) if(SFML_FIND_REQUIRED) # fatal error message(FATAL_ERROR ${FIND_SFML_ERROR}) elseif(NOT SFML_FIND_QUIETLY) # error but continue message("${FIND_SFML_ERROR}") endif() endif() # handle success if(SFML_FOUND AND NOT SFML_FIND_QUIETLY) message(STATUS "Found SFML ${SFML_VERSION_MAJOR}.${SFML_VERSION_MINOR}.${SFML_VERSION_PATCH} in ${SFML_INCLUDE_DIR}") endif() ================================================ FILE: cmake_modules/Findcereal.cmake ================================================ # - Try to find Cereal lib # # This sets the following variables: # CEREAL_FOUND - True if Cereal was found. # CEREAL_INCLUDE_DIRS - Directories containing the Cereal include files. find_path(CEREAL_INCLUDE_DIRS cereal HINTS "$ENV{CMAKE_SOURCE_DIR}/include" "/usr/include" "$ENV{CMAKE_BINARY_DIR}/cereal/include") set(CEREAL_INCLUDE_DIRS ${CEREAL_INCLUDE_DIRS}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Cereal DEFAULT_MSG CEREAL_INCLUDE_DIRS) mark_as_advanced(CEREAL_INCLUDE_DIRS) if(CEREAL_FOUND) MESSAGE(STATUS "Found Cereal: ${CEREAL_INCLUDE_DIRS}") endif(CEREAL_FOUND) ================================================ FILE: examples/ex1/main.cpp ================================================ /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * Example 1: A truly minimal hello world root console, demonstrating * how to get started with RLTK. */ // You need to include the RLTK header #include "../../rltk/rltk.hpp" // We're using a stringstream to build the hello world message. #include // For convenience, import the whole rltk namespace. You may not want to do this // in larger projects, to avoid naming collisions. using namespace rltk; using namespace rltk::colors; // Tick is called every frame. The parameter specifies how many ms have elapsed // since the last time it was called. void tick(double duration_ms) { // In this case, we just want to print "Hello World" in white on black. if (console->dirty) { console->clear(); console->print(1,1,"Hello World", WHITE, BLACK); } } // Your main function int main() { // Initialize the library. Here, we are providing plenty of into so you can see what is // available. There's also the option to use config_simple_px to configure by pixels // rather than characters. // The first parameter is the path to the font files. // The second and third parameters specify the desired console size in screen characters (80x25). // The fourth parameter is the window title. // 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! // The final parameter controls whether or not we want to go full screen. init(config_simple("../assets", 80, 25, "RLTK Hello World", "8x16", false)); // Enter the main loop. "tick" is the function we wrote above. run(tick); return 0; } ================================================ FILE: examples/ex10/main.cpp ================================================ /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * Example 10: Not really an example yet, playing with getting the ECS working. */ // You need to include the RLTK header #include "../../rltk/rltk.hpp" // We're using a stringstream to build the hello world message. #include #include // For convenience, import the whole rltk namespace. You may not want to do this // in larger projects, to avoid naming collisions. using namespace rltk; using namespace rltk::colors; using std::size_t; constexpr int map_width = 100; constexpr int map_height = 100; constexpr int map_size = map_width * map_height; int map_idx(const int x, const int y) { return (y * map_width) + x; } std::vector map_tiles; std::vector visible; std::vector revealed; random_number_generator rng; size_t player_id; struct position { position() {} position(const int X, const int Y) : x(X), y(Y) {} int x, y; void bounds_check() { if (x < 0) x = 0; if (x > map_width) x = map_width; if (y < 0) y = 0; if (y > map_height) y = map_height; } }; struct renderable { renderable() {} renderable(const char Glyph, const color_t foreground) : glyph(Glyph), fg(foreground) {} int glyph; color_t fg=colors::WHITE; color_t bg=colors::BLACK; }; struct navigator_helper { static int get_x(const position &loc) { return loc.x; } static int get_y(const position &loc) { return loc.y; } static position get_xy(const int &x, const int &y) { return position{x,y}; } }; // Clipping info int left_x, right_x, top_y, bottom_y; struct actor_moved_message : base_message_t { actor_moved_message() {} actor_moved_message(entity_t * ACTOR, const int fx, const int fy, const int dx, const int dy) : mover(ACTOR), from_x(fx), from_y(fy), destination_x(dx), destination_y(dy) {} entity_t * mover; int from_x, from_y, destination_x, destination_y; }; struct player_moved_message : base_message_t {}; struct camera_system : public base_system { virtual void configure() override { system_name = "Camera System"; // Create the player auto player = create_entity() ->assign(position{map_width/2, map_height/2}) ->assign(renderable('@', colors::YELLOW)); player_id = player->id; } virtual void update(const double duration_ms) override { if (console->dirty) { console->clear(); // Find the camera auto camera_loc = entity(player_id)->component(); left_x = camera_loc->x - (console->term_width / 2); right_x = camera_loc->x + (console->term_width / 2); top_y = camera_loc->y - (console->term_height/2); bottom_y = camera_loc->y + (console->term_height/2)+1; for (int y=top_y; y= 0 && x < map_width && y >= 0 && y < map_height) { vchar map_char{'.', color_t(0,0,64), colors::BLACK}; const int map_index = map_idx(x,y); if (revealed[map_index]) { switch (map_tiles[map_index]) { case 0 : map_char.glyph = '.'; break; case 1 : map_char.glyph = '#'; break; default : map_char.glyph = 'E'; // This shouldn't happen } map_char.foreground = color_t(96,96,96); } if (visible[map_index] > 0) { uint8_t brightness =(visible[map_index]*16) + 127; map_char.foreground = color_t(brightness, brightness, brightness); } else { if (map_tiles[map_index] == 0) map_char.glyph = ' '; } console->set_char(x-left_x, y-top_y, map_char); } } } } } }; struct actor_render_system : public base_system { virtual void configure() override { system_name = "Actor Render System"; } virtual void update(const double duration_ms) override { each([] (entity_t &entity, position &pos, renderable &render) { const int map_index = map_idx(pos.x, pos.y); if (visible[map_index]) { console->set_char(pos.x-left_x, pos.y-top_y, vchar{ render.glyph, render.fg, render.bg }); } }); } }; struct player_system : public base_system { virtual void configure() override { system_name = "Player System"; subscribe([this](actor_moved_message &msg) { if (map_tiles[map_idx(msg.destination_x, msg.destination_y)] == 0) { msg.mover->component()->x = msg.destination_x; msg.mover->component()->y = msg.destination_y; msg.mover->component()->bounds_check(); console->dirty = true; emit(player_moved_message{}); } }); subscribe_mbox(); } virtual void update(const double duration_ms) override { auto camera_loc = entity(player_id)->component(); // Loop through the keyboard input list std::queue * messages = mbox(); while (!messages->empty()) { key_pressed_t e = messages->front(); messages->pop(); 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 }); 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 }); 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 }); 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 }); if (e.event.key.code == sf::Keyboard::F1) { std::string timings = ecs_profile_dump(); std::cout << timings << "\n"; } } } }; struct visibility_system : public base_system { virtual void configure() override { system_name = "Visibility System"; subscribe([this](player_moved_message &msg) { auto camera_loc = entity(player_id)->component(); position camera_loc_deref = *camera_loc; std::fill(visible.begin(), visible.end(), 0); visibility_sweep_2d(camera_loc_deref, 10, [](position reveal) { reveal.bounds_check(); const int idx = map_idx(reveal.x, reveal.y); ++visible[idx]; if (visible[idx] > 8) visible[idx] = 8; revealed[idx] = true; }, [](position visibility_check) { visibility_check.bounds_check(); return (map_tiles[map_idx(visibility_check.x, visibility_check.y)] == 0); }); }); } bool firstrun = true; virtual void update(const double duration_ms) override { if (firstrun) { emit(player_moved_message{}); firstrun = false; } } }; // Tick is called every frame. The parameter specifies how many ms have elapsed // since the last time it was called. void tick(double duration_ms) { ecs_tick(duration_ms); } // Your main function int main() { // Initialize the library. Here, we are providing plenty of into so you can see what is // available. There's also the option to use config_simple_px to configure by pixels // rather than characters. // The first parameter is the path to the font files. // The second and third parameters specify the desired console size in screen characters (80x25). // The fourth parameter is the window title. // 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! init(config_simple("../assets", 80, 50, "RLTK Hello World", "8x8")); // Zero the map other than the edges map_tiles.resize(map_size); visible.resize(map_size); revealed.resize(map_size); std::fill(map_tiles.begin(), map_tiles.end(), 0); std::fill(visible.begin(), visible.end(), false); std::fill(revealed.begin(), revealed.end(), false); for (int i=0; i(); add_system(); add_system(); add_system(); // Enter the main loop. "tick" is the function we wrote above. ecs_configure(); run(tick); return 0; } ================================================ FILE: examples/ex11/main.cpp ================================================ /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * Example 11: Hello World and a REX Paint object */ // You need to include the RLTK header #include "../../rltk/rltk.hpp" // We're using a stringstream to build the hello world message. #include // For convenience, import the whole rltk namespace. You may not want to do this // in larger projects, to avoid naming collisions. using namespace rltk; using namespace rltk::colors; rltk::xp::rex_sprite nyan_cat("../assets/nyan.xp"); // Tick is called every frame. The parameter specifies how many ms have elapsed // since the last time it was called. void tick(double duration_ms) { // In this case, we just want to print "Hello World" in white on black. if (console->dirty) { console->clear(); console->print(1,1,"Hello World", WHITE, BLACK); console->draw_sprite(1,3,nyan_cat); } } // Your main function int main() { // Initialize the library. Here, we are providing plenty of into so you can see what is // available. There's also the option to use config_simple_px to configure by pixels // rather than characters. // The first parameter is the path to the font files. // The second and third parameters specify the desired console size in screen characters (80x25). // The fourth parameter is the window title. // 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! init(config_simple("../assets", 80, 25, "RLTK Hello World", "8x16")); // Enter the main loop. "tick" is the function we wrote above. run(tick); return 0; } ================================================ FILE: examples/ex2/main.cpp ================================================ /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * Example 2: A wandering @ dude, demonstrating the random number generator, * character rendering, directed screen clearing, and frame-rate independent * tick lengths. */ // You need to include the RLTK header #include "../../rltk/rltk.hpp" // For convenience, import the whole rltk namespace. You may not want to do this // in larger projects, to avoid naming collisions. using namespace rltk; using namespace rltk::colors; // For now, we always want our "dude" to be a yellow @ - so he's constexpr const vchar dude{'@', YELLOW, BLACK}; // The dude's location in X/Y terms int dude_x = 10; int dude_y = 10; // A default-defined random number generator. You can specify a seed to get // the same results each time, but for now we're keeping it simple. random_number_generator rng; // We want to keep the game running at a steady pace, rather than however // fast our super graphics card wants to go! To do this, we set a constant // duration for a "tick" and only process after that much time has elapsed. // Most roguelikes are turn-based and won't actually use this, but that's // for a later example when we get to input. constexpr double tick_duration = 5.0; double tick_time = 0.0; // Tick is called every frame. The parameter specifies how many ms have elapsed // since the last time it was called. void tick(double duration_ms) { // Rather than clearing the screen to black, we set it to all white dots. We only want // to do this if something has forced the screen to re-render (such as re-sizing) if (console->dirty) console->clear(vchar{'.', GREY, BLACK}); // Increase the tick time by the frame duration. If it has exceeded // the tick duration, then we move the @. tick_time += duration_ms; if (tick_time > tick_duration) { // Using the RNG's handy roll_dice function, we roll 1d4. Each option // represents a possible move in this case. The function is just like D&D's // venerable XdY system - you can roll 10d12 with roll_dice(10,12). You // aren't limited to dice sizes that exist or make sense. int direction = rng.roll_dice(1,4); switch (direction) { case 1 : --dude_x; break; case 2 : ++dude_x; break; case 3 : --dude_y; break; case 4 : ++dude_y; break; } // Important: we clear the tick count after the update. tick_time = 0.0; // Clear the console, which has the nice side effect of setting the terminal // to dirty. console->clear(vchar{'.', GREY, BLACK}); // Clipping: keep the dude on the screen. Why are we doing this here, and not // after an update? For now, we aren't handling the concept of a map that is larger // than the screen - so if the window resizes, the @ gets clipped to a visible area. if (dude_x < 0) dude_x = 0; if (dude_x > console->term_width) dude_x = console->term_width; if (dude_y < 0) dude_y = 0; if (dude_y > console->term_height) dude_x = console->term_height; // Finally, we render the @ symbol. dude_x and dude_y are in terminal coordinates. console->set_char(console->at(dude_x, dude_y), dude); } } // Your main function int main() { // Initialize with defaults. init(config_simple_px("../assets")); // Enter the main loop. "tick" is the function we wrote above. run(tick); return 0; } ================================================ FILE: examples/ex3/main.cpp ================================================ /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * Example 3: A wandering @ dude, this time using Bresenham's line functions to * plot his path through the world. We play around a bit, rendering the destination * and path as well as the simple "world" our little @ lives in. */ // You need to include the RLTK header #include "../../rltk/rltk.hpp" // We're using a deque to represent our path #include // For convenience, import the whole rltk namespace. You may not want to do this // in larger projects, to avoid naming collisions. using namespace rltk; using namespace rltk::colors; // For now, we always want our "dude" to be a yellow @ - so he's constexpr const vchar dude{'@', YELLOW, BLACK}; // We're also going to render our destination as a pink heart. Aww. const vchar destination{3, MAGENTA, BLACK}; // We'll also render our planned path ahead as a series of stars const vchar star{'*', GREEN, BLACK}; // The dude's location in X/Y terms int dude_x = 10; int dude_y = 10; // Where the dude would like to go; we'll start with him being happy where he is. int destination_x = 10; int destination_y = 10; // We'll store the path to the goal as a simple queue of x/y (represented as an std::pair of ints). // A queue naturally represents the task - each step, in order. A deque has the added property // of being iteratable - so we are using it. std::deque> path; // A default-defined random number generator. You can specify a seed to get // the same results each time, but for now we're keeping it simple. random_number_generator rng; // We want to keep the game running at a steady pace, rather than however // fast our super graphics card wants to go! To do this, we set a constant // duration for a "tick" and only process after that much time has elapsed. // Most roguelikes are turn-based and won't actually use this, but that's // for a later example when we get to input. // Note that this is faster than previous examples; I liked it better that way! constexpr double tick_duration = 5.0; double tick_time = 0.0; // Tick is called every frame. The parameter specifies how many ms have elapsed // since the last time it was called. void tick(double duration_ms) { // Rather than clearing the screen to black, we set it to all white dots. console->clear(vchar{'.', GREY, BLACK}); // Increase the tick time by the frame duration. If it has exceeded // the tick duration, then we move the @. tick_time += duration_ms; if (tick_time > tick_duration) { // If we're at our destination, we need a new one! if ((destination_x == dude_x && destination_y == dude_y) || path.empty()) { // We use the RNG to determine where we want to go destination_x = rng.roll_dice(1, console->term_width)-1; destination_y = rng.roll_dice(1, console->term_height)-1; // Now we use "line_func". The prototype for this is: // void line_func(int x1, int y1, const int x2, const int y2, std::function func); // What this means in practice is line_func(from_x, from_y, to_x, to_y, callback function for each step). // We'll use a lambda for the callback, to keep it inline and tight. line_func(dude_x, dude_y, destination_x, destination_y, [] (int nx, int ny) { // Simply add the next step to the path path.push_back(std::make_pair(nx,ny)); }); } else { // We aren't there yet, so we follow our path. We take the first element on the list, // and then use pop_back to remove it. // std::tie is a handy way to extract two parts of an std::pair (or tuple) in one fell swoop. std::tie(dude_x, dude_y) = path.front(); path.pop_front(); } // Important: we clear the tick count after the update. tick_time = 0.0; } // Clipping: keep the dude on the screen. Why are we doing this here, and not // after an update? For now, we aren't handling the concept of a map that is larger // than the screen - so if the window resizes, the @ gets clipped to a visible area. if (dude_x < 0) dude_x = 0; if (dude_x > console->term_width) dude_x = console->term_width; if (dude_y < 0) dude_y = 0; if (dude_y > console->term_height) dude_x = console->term_height; // Render our planned path. We're using auto and a range-for to avoid typing all // the iterator stuff for (auto step : path) { console->set_char(console->at(step.first, step.second), star); } // Render our destination console->set_char(console->at(destination_x, destination_y), destination); // Finally, we render the @ symbol. dude_x and dude_y are in terminal coordinates. console->set_char(console->at(dude_x, dude_y), dude); } // Your main function int main() { // Initialize with defaults. init(config_simple_px("../assets")); // Enter the main loop. "tick" is the function we wrote above. run(tick); return 0; } ================================================ FILE: examples/ex4/main.cpp ================================================ #include /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * Example 4: Now we implement a basic map, and use A* to find our way around it. * This example is a bit more in-depth, since it demonstrates the library's ability * to use templates to specialize itself around your map design - we won't force a * map type on you! */ // You need to include the RLTK header #include "../../rltk/rltk.hpp" // We're using a vector to represent the map #include // We're also going to be using a shared_ptr to a map. Why shared? Because the library // hands it off to you and it's up to you to use it; this provides some safety that it // will be disposed when you are done with it. #include // For convenience, import the whole rltk namespace. You may not want to do this // in larger projects, to avoid naming collisions. using namespace rltk; using namespace rltk::colors; // A default-defined random number generator. You can specify a seed to get // the same results each time, but for now we're keeping it simple. random_number_generator rng; // For now, we always want our "dude" to be a yellow @ - so he's constexpr const vchar dude{'@', YELLOW, BLACK}; // We're also going to render our destination as a pink heart. Aww. const vchar destination_glyph{3, MAGENTA, BLACK}; // We now need to represent walls and floors, too const vchar wall_tile{'#', WHITE, BLACK}; const vchar floor_tile{'.', GREY, BLACK}; // Note that "floor" is taken as a name in C++! // Now we define a structure to represent a location. In this case, it's a simple // x/y coordinate. struct location_t { int x=-1; // I like to set uninitialized values to something invalid for help with debugging int y=-1; // For convenience, we're overriding the quality operator. This gives a very // quick and natural looking way to say "are these locations the same?" bool operator==(location_t &rhs) { return (x==rhs.x && y==rhs.y); } location_t() {} location_t(const int X, const int Y) : x(X), y(Y) {} }; // Now we define our basic map. Why a struct? Because a struct is just a class with // everything public in it! struct map_t { map_t(const int &w, const int &h) : width(w), height(h) { // Resize the vector to hold the whole map; this way it won't reallocate walkable.resize(w*h); // Set the entire map to walkable std::fill(walkable.begin(), walkable.end(), true); // We want the perimeter to be solid for (int x=0; x walkable; }; // The A* library returns a navigation path with a template specialization to our location_t. // Store the path here. Normally, you'd use "auto" for this type, it is a lot less typing! std::shared_ptr> path; // We're using 1024x768, with 8 pixel wide chars. That gives a console grid of // 128 x 96. We'll go with that for the map, even though in reality the screen // might change. Worrying about that is for a future example! constexpr int MAP_WIDTH = 128; constexpr int MAP_HEIGHT = 96; map_t map(MAP_WIDTH, MAP_HEIGHT); // Instead of raw ints, we'll use the location structure to represent where our // dude is. Using C++14 initialization, it's nice and clean. location_t dude_position {10,10}; // We'll also use a location_t to represent the intended destination. location_t destination {10,10}; // The A* library also requires a helper class to understand your map format. struct navigator { // This lets you define a distance heuristic. Manhattan distance works really well, but // for now we'll just use a simple euclidian distance squared. // The geometry system defines one for us. static float get_distance_estimate(location_t &pos, location_t &goal) { float d = distance2d_squared(pos.x, pos.y, goal.x, goal.y); return d; } // Heuristic to determine if we've reached our destination? In some cases, you'd not want // this to be a simple comparison with the goal - for example, if you just want to be // adjacent to (or even a preferred distance from) the goal. In this case, // we're trying to get to the goal rather than near it. static bool is_goal(location_t &pos, location_t &goal) { return pos == goal; } // This is where we calculate where you can go from a given tile. In this case, we check // all 8 directions, and if the destination is walkable return it as an option. static bool get_successors(location_t pos, std::vector &successors) { //std::cout << pos.x << "/" << pos.y << "\n"; if (map.walkable[map.at(pos.x-1, pos.y-1)]) successors.push_back(location_t(pos.x-1, pos.y-1)); if (map.walkable[map.at(pos.x, pos.y-1)]) successors.push_back(location_t(pos.x, pos.y-1)); if (map.walkable[map.at(pos.x+1, pos.y-1)]) successors.push_back(location_t(pos.x+1, pos.y-1)); if (map.walkable[map.at(pos.x-1, pos.y)]) successors.push_back(location_t(pos.x-1, pos.y)); if (map.walkable[map.at(pos.x+1, pos.y)]) successors.push_back(location_t(pos.x+1, pos.y)); if (map.walkable[map.at(pos.x-1, pos.y+1)]) successors.push_back(location_t(pos.x-1, pos.y+1)); if (map.walkable[map.at(pos.x, pos.y+1)]) successors.push_back(location_t(pos.x, pos.y+1)); if (map.walkable[map.at(pos.x+1, pos.y+1)]) successors.push_back(location_t(pos.x+1, pos.y+1)); return true; } // This function lets you set a cost on a tile transition. For now, we'll always use a cost of 1.0. static float get_cost(location_t &position, location_t &successor) { return 1.0f; } // This is a simple comparison to determine if two locations are the same. It just passes // through to the location_t's equality operator in this instance (we didn't do that automatically) // because there are times you might want to behave differently. static bool is_same_state(location_t &lhs, location_t &rhs) { return lhs == rhs; } }; // Lets go really fast! constexpr double tick_duration = 1.0; double tick_time = 0.0; // Tick is called every frame. The parameter specifies how many ms have elapsed // since the last time it was called. void tick(double duration_ms) { // Iterate over the whole map, rendering as appropriate for (int y=0; yset_char(console->at(x,y), floor_tile); } else { console->set_char(console->at(x,y), wall_tile); } } } // Increase the tick time by the frame duration. If it has exceeded // the tick duration, then we move the @. tick_time += duration_ms; if (tick_time > tick_duration) { // Are we there yet? if (dude_position == destination) { // We are there! We need to pick a new destination. destination.x = rng.roll_dice(1, MAP_WIDTH-1); destination.y = rng.roll_dice(1, MAP_HEIGHT-1); // Lets make sure that the destination is walkable while (map.walkable[map.at(destination.x,destination.y)] == false) { destination.x = rng.roll_dice(1, MAP_WIDTH-1); destination.y = rng.roll_dice(1, MAP_HEIGHT-1); } // Now determine how to get there if (path) path.reset(); path = find_path(dude_position, destination); if (!path->success) { destination = dude_position; std::cout << "RESET: THIS ISN'T MEANT TO HAPPEN!\n"; } } else { // Follow the breadcrumbs! location_t next_step = path->steps.front(); dude_position.x = next_step.x; dude_position.y = next_step.y; path->steps.pop_front(); } // Important: we clear the tick count after the update. tick_time = 0.0; } // Render our planned path. We're using auto and a range-for to avoid typing all // the iterator stuff if (path) { // We're going to show off a bit and "lerp" the color along the path; the red // lightens as it approaches the destination. This is a preview of some of the // color functions. const float n_steps = static_cast(path->steps.size()); float i = 0; for (auto step : path->steps) { const float lerp_amount = i / n_steps; vchar highlight{ 177, lerp(DARK_GREEN, LIGHTEST_GREEN, lerp_amount), BLACK }; console->set_char(console->at(step.x, step.y), highlight); ++i; } } // Render our destination console->set_char(console->at(destination.x, destination.y), destination_glyph); // Finally, we render the @ symbol. dude_x and dude_y are in terminal coordinates. console->set_char(console->at(dude_position.x, dude_position.y), dude); } // Your main function int main() { // Initialize with defaults init(config_simple_px("../assets")); // Enter the main loop. "tick" is the function we wrote above. run(tick); return 0; } ================================================ FILE: examples/ex5/main.cpp ================================================ #include /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * Example 5: Extend example 4 to use the mouse to guide player movement. */ // You need to include the RLTK header #include "../../rltk/rltk.hpp" // We're using a vector to represent the map #include // We're also going to be using a shared_ptr to a map. Why shared? Because the library // hands it off to you and it's up to you to use it; this provides some safety that it // will be disposed when you are done with it. #include // For convenience, import the whole rltk namespace. You may not want to do this // in larger projects, to avoid naming collisions. using namespace rltk; using namespace rltk::colors; // A default-defined random number generator. You can specify a seed to get // the same results each time, but for now we're keeping it simple. random_number_generator rng; // For now, we always want our "dude" to be a yellow @ - so he's constexpr const vchar dude{'@', YELLOW, BLACK}; // We're also going to render our destination as a pink heart. Aww. const vchar destination_glyph{3, MAGENTA, BLACK}; // We now need to represent walls and floors, too const vchar wall_tile{'#', WHITE, BLACK}; const vchar floor_tile{'.', GREY, BLACK}; // Note that "floor" is taken as a name in C++! // Now we define a structure to represent a location. In this case, it's a simple // x/y coordinate. struct location_t { int x=-1; // I like to set uninitialized values to something invalid for help with debugging int y=-1; // For convenience, we're overriding the quality operator. This gives a very // quick and natural looking way to say "are these locations the same?" bool operator==(location_t &rhs) { return (x==rhs.x && y==rhs.y); } location_t() {} location_t(const int X, const int Y) : x(X), y(Y) {} }; // Now we define our basic map. Why a struct? Because a struct is just a class with // everything public in it! struct map_t { map_t(const int &w, const int &h) : width(w), height(h) { // Resize the vector to hold the whole map; this way it won't reallocate walkable.resize(w*h); // Set the entire map to walkable std::fill(walkable.begin(), walkable.end(), true); // We want the perimeter to be solid for (int x=0; x walkable; }; // The A* library returns a navigation path with a template specialization to our location_t. // Store the path here. Normally, you'd use "auto" for this type, it is a lot less typing! std::shared_ptr> path; // We're using 1024x768, with 8 pixel wide chars. That gives a console grid of // 128 x 96. We'll go with that for the map, even though in reality the screen // might change. Worrying about that is for a future example! constexpr int MAP_WIDTH = 128; constexpr int MAP_HEIGHT = 96; map_t map(MAP_WIDTH, MAP_HEIGHT); // Instead of raw ints, we'll use the location structure to represent where our // dude is. Using C++14 initialization, it's nice and clean. location_t dude_position {10,10}; // We'll also use a location_t to represent the intended destination. location_t destination {10,10}; // The A* library also requires a helper class to understand your map format. struct navigator { // This lets you define a distance heuristic. Manhattan distance works really well, but // for now we'll just use a simple euclidian distance squared. // The geometry system defines one for us. static float get_distance_estimate(location_t &pos, location_t &goal) { float d = distance2d_squared(pos.x, pos.y, goal.x, goal.y); return d; } // Heuristic to determine if we've reached our destination? In some cases, you'd not want // this to be a simple comparison with the goal - for example, if you just want to be // adjacent to (or even a preferred distance from) the goal. In this case, // we're trying to get to the goal rather than near it. static bool is_goal(location_t &pos, location_t &goal) { return pos == goal; } // This is where we calculate where you can go from a given tile. In this case, we check // all 8 directions, and if the destination is walkable return it as an option. static bool get_successors(location_t pos, std::vector &successors) { //std::cout << pos.x << "/" << pos.y << "\n"; if (map.walkable[map.at(pos.x-1, pos.y-1)]) successors.push_back(location_t(pos.x-1, pos.y-1)); if (map.walkable[map.at(pos.x, pos.y-1)]) successors.push_back(location_t(pos.x, pos.y-1)); if (map.walkable[map.at(pos.x+1, pos.y-1)]) successors.push_back(location_t(pos.x+1, pos.y-1)); if (map.walkable[map.at(pos.x-1, pos.y)]) successors.push_back(location_t(pos.x-1, pos.y)); if (map.walkable[map.at(pos.x+1, pos.y)]) successors.push_back(location_t(pos.x+1, pos.y)); if (map.walkable[map.at(pos.x-1, pos.y+1)]) successors.push_back(location_t(pos.x-1, pos.y+1)); if (map.walkable[map.at(pos.x, pos.y+1)]) successors.push_back(location_t(pos.x, pos.y+1)); if (map.walkable[map.at(pos.x+1, pos.y+1)]) successors.push_back(location_t(pos.x+1, pos.y+1)); return true; } // This function lets you set a cost on a tile transition. For now, we'll always use a cost of 1.0. static float get_cost(location_t &position, location_t &successor) { return 1.0f; } // This is a simple comparison to determine if two locations are the same. It just passes // through to the location_t's equality operator in this instance (we didn't do that automatically) // because there are times you might want to behave differently. static bool is_same_state(location_t &lhs, location_t &rhs) { return lhs == rhs; } }; // Lets go really fast! constexpr double tick_duration = 1.0; double tick_time = 0.0; // Tick is called every frame. The parameter specifies how many ms have elapsed // since the last time it was called. void tick(double duration_ms) { // Iterate over the whole map, rendering as appropriate for (int y=0; yset_char(console->at(x,y), floor_tile); } else { console->set_char(console->at(x,y), wall_tile); } } } // Increase the tick time by the frame duration. If it has exceeded // the tick duration, then we move the @. tick_time += duration_ms; if (tick_time > tick_duration) { // Are we there yet? if (dude_position == destination) { // Now we poll the mouse to determine where we want to go int mouse_x, mouse_y; std::tie(mouse_x, mouse_y) = get_mouse_position(); const int terminal_x = mouse_x / 8; const int terminal_y = mouse_y / 8; const bool walkable = map.walkable[map.at(terminal_x, terminal_y)]; if (walkable && get_mouse_button_state(rltk::button::LEFT)) { destination.x = terminal_x; destination.y = terminal_y; // Now determine how to get there if (path) path.reset(); path = find_path(dude_position, destination); if (!path->success) { destination = dude_position; std::cout << "RESET: THIS ISN'T MEANT TO HAPPEN!\n"; } } else if (walkable) { if (path) path.reset(); path = find_path(dude_position, location_t{terminal_x, terminal_y}); } } else { // Follow the breadcrumbs! if (path) { location_t next_step = path->steps.front(); dude_position.x = next_step.x; dude_position.y = next_step.y; path->steps.pop_front(); } } // Important: we clear the tick count after the update. tick_time = 0.0; } // Render our planned path. We're using auto and a range-for to avoid typing all // the iterator stuff if (path) { // We're going to show off a bit and "lerp" the color along the path; the red // lightens as it approaches the destination. This is a preview of some of the // color functions. const float n_steps = static_cast(path->steps.size()); float i = 0; for (auto step : path->steps) { const float lerp_amount = i / n_steps; vchar highlight; if (dude_position == destination) { highlight = { 177, lerp(DARK_GREEN, LIGHTEST_GREEN, lerp_amount), BLACK }; } else { highlight = { 177, lerp(DARK_RED, LIGHTEST_RED, lerp_amount), BLACK }; } console->set_char(console->at(step.x, step.y), highlight); ++i; } } // Render our destination console->set_char(console->at(destination.x, destination.y), destination_glyph); // Finally, we render the @ symbol. dude_x and dude_y are in terminal coordinates. console->set_char(console->at(dude_position.x, dude_position.y), dude); } // Your main function int main() { // Initialize with defaults init(config_simple_px("../assets")); // Enter the main loop. "tick" is the function we wrote above. run(tick); return 0; } ================================================ FILE: examples/ex6/main.cpp ================================================ #include /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * Example 6: Extend example 5 to include visibility and map revealed status. */ // You need to include the RLTK header #include "../../rltk/rltk.hpp" // We're using a vector to represent the map #include // We're also going to be using a shared_ptr to a map. Why shared? Because the library // hands it off to you and it's up to you to use it; this provides some safety that it // will be disposed when you are done with it. #include // For convenience, import the whole rltk namespace. You may not want to do this // in larger projects, to avoid naming collisions. using namespace rltk; using namespace rltk::colors; // A default-defined random number generator. You can specify a seed to get // the same results each time, but for now we're keeping it simple. random_number_generator rng; // For now, we always want our "dude" to be a yellow @ - so he's constexpr const vchar dude{'@', YELLOW, BLACK}; // We're also going to render our destination as a pink heart. Aww. const vchar destination_glyph{3, MAGENTA, BLACK}; // Now we define a structure to represent a location. In this case, it's a simple // x/y coordinate. struct location_t { int x=-1; // I like to set uninitialized values to something invalid for help with debugging int y=-1; // For convenience, we're overriding the quality operator. This gives a very // quick and natural looking way to say "are these locations the same?" bool operator==(location_t &rhs) { return (x==rhs.x && y==rhs.y); } location_t() {} location_t(const int X, const int Y) : x(X), y(Y) {} }; // Now we define our basic map. Why a struct? Because a struct is just a class with // everything public in it! struct map_t { map_t(const int &w, const int &h) : width(w), height(h) { // Resize the vectors to hold the whole map; this way it won't reallocate walkable.resize(w*h); revealed.resize(w*h); visible.resize(w*h); // Set the entire map to walkable, not visible and not revealed std::fill(walkable.begin(), walkable.end(), true); std::fill(revealed.begin(), revealed.end(), false); std::fill(visible.begin(), visible.end(), false); // We want the perimeter to be solid for (int x=0; x walkable; // Revealed: has a tile been shown yet? std::vector revealed; // Visible: is a tile currently visible? std::vector visible; }; // The A* library returns a navigation path with a template specialization to our location_t. // Store the path here. Normally, you'd use "auto" for this type, it is a lot less typing! std::shared_ptr> path; // We're using 1024x768, with 8 pixel wide chars. That gives a console grid of // 128 x 96. We'll go with that for the map, even though in reality the screen // might change. Worrying about that is for a future example! constexpr int MAP_WIDTH = 128; constexpr int MAP_HEIGHT = 96; map_t map(MAP_WIDTH, MAP_HEIGHT); // Instead of raw ints, we'll use the location structure to represent where our // dude is. Using C++14 initialization, it's nice and clean. location_t dude_position {10,10}; // We'll also use a location_t to represent the intended destination. location_t destination {10,10}; // The A* library also requires a helper class to understand your map format. struct navigator { // This lets you define a distance heuristic. Manhattan distance works really well, but // for now we'll just use a simple euclidian distance squared. // The geometry system defines one for us. static float get_distance_estimate(location_t &pos, location_t &goal) { float d = distance2d_squared(pos.x, pos.y, goal.x, goal.y); return d; } // Heuristic to determine if we've reached our destination? In some cases, you'd not want // this to be a simple comparison with the goal - for example, if you just want to be // adjacent to (or even a preferred distance from) the goal. In this case, // we're trying to get to the goal rather than near it. static bool is_goal(location_t &pos, location_t &goal) { return pos == goal; } // This is where we calculate where you can go from a given tile. In this case, we check // all 8 directions, and if the destination is walkable return it as an option. static bool get_successors(location_t pos, std::vector &successors) { //std::cout << pos.x << "/" << pos.y << "\n"; if (map.walkable[map.at(pos.x-1, pos.y-1)]) successors.push_back(location_t(pos.x-1, pos.y-1)); if (map.walkable[map.at(pos.x, pos.y-1)]) successors.push_back(location_t(pos.x, pos.y-1)); if (map.walkable[map.at(pos.x+1, pos.y-1)]) successors.push_back(location_t(pos.x+1, pos.y-1)); if (map.walkable[map.at(pos.x-1, pos.y)]) successors.push_back(location_t(pos.x-1, pos.y)); if (map.walkable[map.at(pos.x+1, pos.y)]) successors.push_back(location_t(pos.x+1, pos.y)); if (map.walkable[map.at(pos.x-1, pos.y+1)]) successors.push_back(location_t(pos.x-1, pos.y+1)); if (map.walkable[map.at(pos.x, pos.y+1)]) successors.push_back(location_t(pos.x, pos.y+1)); if (map.walkable[map.at(pos.x+1, pos.y+1)]) successors.push_back(location_t(pos.x+1, pos.y+1)); return true; } // This function lets you set a cost on a tile transition. For now, we'll always use a cost of 1.0. static float get_cost(location_t &position, location_t &successor) { return 1.0f; } // This is a simple comparison to determine if two locations are the same. It just passes // through to the location_t's equality operator in this instance (we didn't do that automatically) // because there are times you might want to behave differently. static bool is_same_state(location_t &lhs, location_t &rhs) { return lhs == rhs; } // We're using the Bresneham's line optimization for pathing this time, which requires a few extra // static methods. These are designed to translate between your map format and co-ordinates used by // the library (we don't want to force you to structure things a certain way). static int get_x(const location_t &loc) { return loc.x; } static int get_y(const location_t &loc) { return loc.y; } static location_t get_xy(const int &x, const int &y) { return location_t{x,y}; } static bool is_walkable(const location_t &loc) { return map.walkable[map.at(loc.x, loc.y)]; } }; // Lets go really fast! constexpr double tick_duration = 0.0; double tick_time = 0.0; // Helper function: calls the RLTK visibility sweep 2D algorithm with lambdas to // assist in understanding our map format. inline void visibility_sweep() { visibility_sweep_2d(dude_position, 10, [] (location_t reveal) { map.revealed[map.at(reveal.x, reveal.y)] = true; map.visible[map.at(reveal.x, reveal.y)] = true; }, [] (auto test_visibility) { return map.walkable[map.at(test_visibility.x, test_visibility.y)]; } ); } // Tick is called every frame. The parameter specifies how many ms have elapsed // since the last time it was called. void tick(double duration_ms) { // Iterate over the whole map, rendering as appropriate for (int y=0; yset_char(map_idx, vchar{'.', GREEN, BLACK}); } else if (map.revealed[map_idx]) { // Revealed tile: render grey console->set_char(map_idx, vchar{'.', LIGHTER_GREY, BLACK}); } else { // We haven't seen it yet - darkest gray console->set_char(map_idx, vchar{'.', DARK_GREY, BLACK}); } } else { if (map.visible[map_idx]) { // Visible tile: render full color console->set_char(map_idx, vchar{'#', CYAN, BLACK}); } else if (map.revealed[map_idx]) { // Revealed tile: render grey console->set_char(map_idx, vchar{'#', GREY, BLACK}); } else { // We haven't seen it yet - darkest gray console->set_char(map_idx, vchar{'#', DARKER_GREY, BLACK}); } } } } // Increase the tick time by the frame duration. If it has exceeded // the tick duration, then we move the @. tick_time += duration_ms; if (tick_time > tick_duration) { // Are we there yet? if (dude_position == destination) { // Now we poll the mouse to determine where we want to go // This requests the mouse position in PIXELS, and ties it into our mouse_x/mouse_y variables. int mouse_x, mouse_y; std::tie(mouse_x, mouse_y) = get_mouse_position(); // Since we're using an 8x8, it's just a matter of dividing by 8 to find the terminal-character // coordinates. There will be a helper function for this once we get into retained GUIs. const int terminal_x = mouse_x / 8; const int terminal_y = mouse_y / 8; // If the mouse is pointing at a walkable location, and the left button is down - path to the mouse. const bool walkable = map.walkable[map.at(terminal_x, terminal_y)]; if (walkable && get_mouse_button_state(rltk::button::LEFT)) { destination.x = terminal_x; destination.y = terminal_y; // Now determine how to get there if (path) path.reset(); path = find_path(dude_position, destination); if (!path->success) { destination = dude_position; std::cout << "RESET: THIS ISN'T MEANT TO HAPPEN!\n"; } } else if (walkable) { // If the mouse is not clicked, then path to the mouse cursor for display only if (path) path.reset(); path = find_path_2d(dude_position, location_t{terminal_x, terminal_y}); } } else { // Follow the breadcrumbs! if (path) { location_t next_step = path->steps.front(); dude_position.x = next_step.x; dude_position.y = next_step.y; path->steps.pop_front(); // Update the map visibility std::fill(map.visible.begin(), map.visible.end(), false); visibility_sweep(); } } // Important: we clear the tick count after the update. tick_time = 0.0; } // Render our planned path. We're using auto and a range-for to avoid typing all // the iterator stuff if (path) { // We're going to show off a bit and "lerp" the color along the path; the red // lightens as it approaches the destination. This is a preview of some of the // color functions. const float n_steps = static_cast(path->steps.size()); float i = 0; for (auto step : path->steps) { const float lerp_amount = i / n_steps; vchar highlight; // If we're at our destination, we are showing possible paths - highlight green; // otherwise, highlight red to indicate that we are en route. if (dude_position == destination) { highlight = { 177, lerp(DARK_GREEN, LIGHTEST_GREEN, lerp_amount), BLACK }; } else { highlight = { 177, lerp(DARK_RED, LIGHTEST_RED, lerp_amount), BLACK }; } console->set_char(console->at(step.x, step.y), highlight); ++i; } } // Render our destination console->set_char(console->at(destination.x, destination.y), destination_glyph); // Finally, we render the @ symbol. dude_x and dude_y are in terminal coordinates. console->set_char(console->at(dude_position.x, dude_position.y), dude); } // Your main function int main() { // Initialize with defaults init(config_simple_px("../assets")); // We do a visibility sweep to start, so your starting position is revealed visibility_sweep(); // Enter the main loop. "tick" is the function we wrote above. run(tick); return 0; } ================================================ FILE: examples/ex7/main.cpp ================================================ /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * Example 7: Introduction to complex GUIs. This example demonstrates how you can create multiple layers, * and use call-backs to resize them as the window adjusts. It also displays a layer on top of another, * with alpha transparency (useful for effects). */ // You need to include the RLTK header #include "../../rltk/rltk.hpp" #include #include // For convenience, import the whole rltk namespace. You may not want to do this // in larger projects, to avoid naming collisions. using namespace rltk; using namespace rltk::colors; // For convenience, we'll define our GUI section handles here. These are just ID numbers. constexpr int TITLE_LAYER = 0; constexpr int MAIN_LAYER = 1; constexpr int LOG_LAYER = 2; constexpr int OVERLAY_LAYER = 3; // Tick is called every frame. The parameter specifies how many ms have elapsed // since the last time it was called. void tick(double duration_ms) { term(TITLE_LAYER)->clear(vchar{' ', YELLOW, BLUE}); term(TITLE_LAYER)->print_center(0, "Big 32x32 Title", YELLOW, BLUE); term(MAIN_LAYER)->clear(vchar{'.', GREY, BLACK}); term(MAIN_LAYER)->box(GREY, BLACK, true); term(MAIN_LAYER)->set_char(10, 10, vchar{'@', YELLOW, BLACK}); term(LOG_LAYER)->clear(vchar{' ', WHITE, DARKEST_GREEN}); term(LOG_LAYER)->box(DARKEST_GREEN, BLACK); term(LOG_LAYER)->print(1,1, "Log Entry", LIGHT_GREEN, DARKEST_GREEN); term(LOG_LAYER)->print(1,2, "More text!", LIGHT_GREEN, DARKEST_GREEN); term(LOG_LAYER)->print(1,3, "Even more...", LIGHT_GREEN, DARKEST_GREEN); term(LOG_LAYER)->print(1,4, "... goes here", LIGHT_GREEN, DARKEST_GREEN); term(OVERLAY_LAYER)->clear(); term(OVERLAY_LAYER)->set_char(11, 10, vchar{17, LIGHT_GREEN, BLACK}); // Draw a left arrow term(OVERLAY_LAYER)->print(12, 10, "Translucent Tool-tip", LIGHT_GREEN, BLACK); std::stringstream ss; ss << std::setiosflags(std::ios::fixed) << std::setprecision(0) << (1000.0/duration_ms) << " FPS"; term(LOG_LAYER)->print(1,6, ss.str(), WHITE, DARKEST_GREEN); } // This is called when the screen resizes, to allow the GUI to redefine itself. void resize_title(layer_t * l, int w, int h) { // Simply set the width to the whole window width l->w = w; l->h = 32; // Not really necessary - here for clarity } // This is called when the screen resizes, to allow the GUI to redefine itself. void resize_main(layer_t * l, int w, int h) { // Simply set the width to the whole window width, and the whole window minus 16 pixels (for the heading) l->w = w - 160; l->h = h - 32; if (l->w < 0) l->w = 160; // If the width is too small with the log window, display anyway. } // This is called when the screen resizes, to allow the GUI to redefine itself. void resize_log(layer_t * l, int w, int h) { // Simply set the width to the whole window width, and the whole window minus 16 pixels (for the heading) l->w = w - 160; l->h = h - 32; // If the log window would take up the whole screen, hide it if (l->w < 0) { l->console->visible = false; } else { l->console->visible = true; } l->x = w - 160; } // Your main function int main() { // This time, we're using a full initialization: width, height, window title, and "false" meaning we don't // want an automatically generated root console. This is necessary when you want to use the complex layout // functions. init(config_advanced("../assets")); gui->add_layer(TITLE_LAYER, 0, 0, 1024, 32, "32x32", resize_title); gui->add_layer(MAIN_LAYER, 0, 32, 1024-160, 768-32, "8x8", resize_main); gui->add_layer(LOG_LAYER, 864, 32, 160, 768-32, "8x16", resize_log); 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 term(OVERLAY_LAYER)->set_alpha(196); // Make the overlay translucent run(tick); return 0; } ================================================ FILE: examples/ex8/main.cpp ================================================ /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * Example 7: Advanced GUI with retained-mode GUI elements and an owner-draw background. */ // You need to include the RLTK header #include "../../rltk/rltk.hpp" #include #include // For convenience, import the whole rltk namespace. You may not want to do this // in larger projects, to avoid naming collisions. using namespace rltk; using namespace rltk::colors; random_number_generator rng; constexpr int BACKDROP_LAYER = 1; constexpr int LOG_LAYER = 2; constexpr int RETAINED_TEST_LAYER = 3; constexpr int TEST_BOUNDARY_BOX = 1; constexpr int TEST_STATIC_TEST = 2; constexpr int TEST_MOUSE_HOVER = 3; constexpr int TEST_CHECKBOX = 4; constexpr int TEST_RADIOSET = 5; constexpr int TEST_HBAR = 6; constexpr int TEST_VBAR = 7; constexpr int TEST_LISTBOX = 8; void resize_bg(layer_t * l, int w, int h) { // Use the whole window l->w = w; l->h = h; } void draw_bg(layer_t * l, sf::RenderTexture &window) { sf::Texture * bg = get_texture("backdrop"); sf::Sprite backdrop(*bg); window.draw(backdrop); } void resize_log(layer_t * l, int w, int h) { // Simply set the width to the whole window width, and the whole window minus 16 pixels (for the heading) l->w = w - 160; l->h = h - 32; // If the log window would take up the whole screen, hide it if (l->w < 0) { l->console->visible = false; } else { l->console->visible = true; } l->x = w - 160; } void resize_retained(layer_t * l, int w, int h) { // Do nothing - we'll just keep on rendering away. l->x = 100; l->y = 100; l->w = 400; l->h = 200; } // Tick is called every frame. The parameter specifies how many ms have elapsed // since the last time it was called. void tick(double duration_ms) { term(LOG_LAYER)->clear(vchar{' ', WHITE, DARKEST_GREEN}); term(LOG_LAYER)->box(DARKEST_GREEN, BLACK); layer_t * retained = layer(RETAINED_TEST_LAYER); if ( retained->control(TEST_CHECKBOX)->checked ) { term(LOG_LAYER)->print(1,1, "Checked", LIGHTEST_GREEN, DARKEST_GREEN); } else { term(LOG_LAYER)->print(1,1, "Not Checked", DARK_GREEN, DARKEST_GREEN); } std::stringstream radio_ss; radio_ss << "Option: " << retained->control(TEST_RADIOSET)->selected_value; term(LOG_LAYER)->print(1,2, radio_ss.str(), LIGHT_GREEN, DARKEST_GREEN); std::stringstream list_ss; list_ss << "List: " << retained->control(TEST_LISTBOX)->selected_value; term(LOG_LAYER)->print(1,3, list_ss.str(), LIGHT_GREEN, DARKEST_GREEN); term(LOG_LAYER)->print(1,4, "... goes here", LIGHT_GREEN, DARKEST_GREEN); std::stringstream ss; ss << std::setiosflags(std::ios::fixed) << std::setprecision(0) << (1000.0/duration_ms) << " FPS"; term(LOG_LAYER)->print(1,6, ss.str(), WHITE, DARKEST_GREEN); if (rng.roll_dice(1,20)==1) { retained->control(TEST_HBAR)->value = rng.roll_dice(1,100); retained->control(TEST_VBAR)->value = rng.roll_dice(1,100); } } // Your main function int main() { // This time, we're using a full initialization: width, height, window title, and "false" meaning we don't // want an automatically generated root console. This is necessary when you want to use the complex layout // functions. init(config_advanced("../assets")); // We're going to be using a bitmap, so we need to load it. The library makes this easy: register_texture("../assets/background_image.png", "backdrop"); // Now we add an owner-draw background layer. "Owner-draw" means that it the library will ask it to // draw itself with a call-back function. gui->add_owner_layer(BACKDROP_LAYER, 0, 0, 1024, 768, resize_bg, draw_bg); gui->add_layer(LOG_LAYER, 864, 32, 160, 768-32, "8x16", resize_log); term(LOG_LAYER)->set_alpha(196); // Make the overlay translucent gui->add_layer(RETAINED_TEST_LAYER, 100, 100, 400, 400, "8x16", resize_retained); // To reduce typing, grab a pointer to the retained layer: layer_t * retained = layer(RETAINED_TEST_LAYER); // Now we build some retained-mode controls. These don't require additional work during rendering // Note that we are providing a handle to the control. That lets us access it later with // layer(layerhandle)->control(controlhandle). It's up to you to store the handles; they can be any // int. retained->add_boundary_box(TEST_BOUNDARY_BOX, true, DARK_GREY, BLACK); retained->add_static_text(TEST_STATIC_TEST, 1, 1, "Retained Mode Static Text", YELLOW, BLACK); // For this control, we'll define an on-mouse-over. We're using a lambda, but it could be any function // with that takes a gui_control_t * as a parameter. We'll also use "on render start" to define a function // run when the control rendering starts. retained->add_static_text(TEST_MOUSE_HOVER, 1, 2, "Hover the mouse over me!", WHITE, BLACK); retained->control(TEST_MOUSE_HOVER)->on_render_start = [] (gui_control_t * control) { auto static_text = static_cast(control); static_text->background = BLACK; static_text->text = "Hover the mouse over me!"; }; retained->control(TEST_MOUSE_HOVER)->on_mouse_over = [] (gui_control_t * control, int terminal_x, int terminal_y) { auto static_text = static_cast(control); static_text->background = RED; static_text->text = "Why Hello There! "; }; // A checkbox retained->add_checkbox(TEST_CHECKBOX, 1, 3, "I'm a checkbox - click me!", false, LIGHT_GREEN, BLACK); // A radioset retained->add_radioset(TEST_RADIOSET, 1, 5, "Test radio buttons", CYAN, BLACK, { {true, "Option A", 0}, {false, "Option B", 1}, {false, "Option C", 2} }); // Add a horizontal and vertical color bar (e.g. health) 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: "); 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: "); // Listbox 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); // Main loop - calls the 'tick' function you defined for each frame. run(tick); return 0; } ================================================ FILE: examples/ex9/main.cpp ================================================ #include /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * Example 9: This is example 6, but using two consoles. One for the map, and one sparse. This allows * for some trickery to speed up map rendering (we're only redrawing when we need to). We also use smooth * movement, which many people may or may not like - but is an important feature to offer. Finally, we're * 'bouncing' the @ left and right to demonstrate rotation. */ // You need to include the RLTK header #include "../../rltk/rltk.hpp" // We're using a vector to represent the map #include // We're also going to be using a shared_ptr to a map. Why shared? Because the library // hands it off to you and it's up to you to use it; this provides some safety that it // will be disposed when you are done with it. #include // For convenience, import the whole rltk namespace. You may not want to do this // in larger projects, to avoid naming collisions. using namespace rltk; using namespace rltk::colors; // A default-defined random number generator. You can specify a seed to get // the same results each time, but for now we're keeping it simple. random_number_generator rng; // For now, we always want our "dude" to be a yellow @ - so he's constexpr const vchar dude{'@', YELLOW, BLACK}; // We're also going to render our destination as a pink heart. Aww. const vchar destination_glyph{3, MAGENTA, BLACK}; // Now we define a structure to represent a location. In this case, it's a simple // x/y coordinate. struct location_t { float x=-1.0f; // I like to set uninitialized values to something invalid for help with debugging float y=-1.0f; // For convenience, we're overriding the quality operator. This gives a very // quick and natural looking way to say "are these locations the same?" bool operator==(location_t &rhs) { return (std::floor(x)==std::floor(rhs.x) && std::floor(y)==std::floor(rhs.y)); } location_t() {} location_t(const int X, const int Y) : x(static_cast(X)), y(static_cast(Y)) {} }; // Now we define our basic map. Why a struct? Because a struct is just a class with // everything public in it! struct map_t { map_t(const int &w, const int &h) : width(w), height(h) { // Resize the vectors to hold the whole map; this way it won't reallocate walkable.resize(w*h); revealed.resize(w*h); visible.resize(w*h); // Set the entire map to walkable, not visible and not revealed std::fill(walkable.begin(), walkable.end(), true); std::fill(revealed.begin(), revealed.end(), false); std::fill(visible.begin(), visible.end(), false); // We want the perimeter to be solid for (int x=0; x walkable; // Revealed: has a tile been shown yet? std::vector revealed; // Visible: is a tile currently visible? std::vector visible; }; // The A* library returns a navigation path with a template specialization to our location_t. // Store the path here. Normally, you'd use "auto" for this type, it is a lot less typing! std::shared_ptr> path; // We're using 1024x768, with 8 pixel wide chars. That gives a console grid of // 128 x 96. We'll go with that for the map, even though in reality the screen // might change. Worrying about that is for a future example! constexpr int MAP_WIDTH = 128; constexpr int MAP_HEIGHT = 96; map_t map(MAP_WIDTH, MAP_HEIGHT); // Instead of raw ints, we'll use the location structure to represent where our // dude is. Using C++14 initialization, it's nice and clean. location_t dude_position {10,10}; // We'll also use a location_t to represent the intended destination. location_t destination {10,10}; // The A* library also requires a helper class to understand your map format. struct navigator { // This lets you define a distance heuristic. Manhattan distance works really well, but // for now we'll just use a simple euclidian distance squared. // The geometry system defines one for us. static float get_distance_estimate(location_t &pos, location_t &goal) { float d = distance2d_squared(static_cast(pos.x), static_cast(pos.y), static_cast(goal.x), static_cast(goal.y)); return d; } // Heuristic to determine if we've reached our destination? In some cases, you'd not want // this to be a simple comparison with the goal - for example, if you just want to be // adjacent to (or even a preferred distance from) the goal. In this case, // we're trying to get to the goal rather than near it. static bool is_goal(location_t &pos, location_t &goal) { return pos == goal; } // This is where we calculate where you can go from a given tile. In this case, we check // all 8 directions, and if the destination is walkable return it as an option. static bool get_successors(location_t pos, std::vector &successors) { //std::cout << pos.x << "/" << pos.y << "\n"; if (map.walkable[map.at(static_cast(pos.x-1), static_cast(pos.y-1))]) successors.push_back(location_t(static_cast(pos.x-1), static_cast(pos.y-1))); if (map.walkable[map.at(static_cast(pos.x), static_cast(pos.y-1))]) successors.push_back(location_t(static_cast(pos.x), static_cast(pos.y-1))); if (map.walkable[map.at(static_cast(pos.x+1), static_cast(pos.y-1))]) successors.push_back(location_t(static_cast(pos.x+1), static_cast(pos.y-1))); if (map.walkable[map.at(static_cast(pos.x-1), static_cast(pos.y))]) successors.push_back(location_t(static_cast(pos.x-1), static_cast(pos.y))); if (map.walkable[map.at(static_cast(pos.x+1), static_cast(pos.y))]) successors.push_back(location_t(static_cast(pos.x+1), static_cast(pos.y))); if (map.walkable[map.at(static_cast(pos.x-1), static_cast(pos.y+1))]) successors.push_back(location_t(static_cast(pos.x-1), static_cast(pos.y+1))); if (map.walkable[map.at(static_cast(pos.x), static_cast(pos.y+1))]) successors.push_back(location_t(static_cast(pos.x), static_cast(pos.y+1))); if (map.walkable[map.at(static_cast(pos.x+1), static_cast(pos.y+1))]) successors.push_back(location_t(static_cast(pos.x+1), static_cast(pos.y+1))); return true; } // This function lets you set a cost on a tile transition. For now, we'll always use a cost of 1.0. static float get_cost(location_t &position, location_t &successor) { return 1.0f; } // This is a simple comparison to determine if two locations are the same. It just passes // through to the location_t's equality operator in this instance (we didn't do that automatically) // because there are times you might want to behave differently. static bool is_same_state(location_t &lhs, location_t &rhs) { return lhs == rhs; } // We're using the Bresneham's line optimization for pathing this time, which requires a few extra // static methods. These are designed to translate between your map format and co-ordinates used by // the library (we don't want to force you to structure things a certain way). static int get_x(const location_t &loc) { return static_cast(loc.x); } static int get_y(const location_t &loc) { return static_cast(loc.y); } static location_t get_xy(const int &x, const int &y) { return location_t{x,y}; } static bool is_walkable(const location_t &loc) { return map.walkable[map.at(static_cast(loc.x), static_cast(loc.y))]; } }; // Lets go really fast! constexpr double tick_duration = 0.0; double tick_time = 0.0; // Helper function: calls the RLTK visibility sweep 2D algorithm with lambdas to // assist in understanding our map format. inline void visibility_sweep() { visibility_sweep_2d(dude_position, 10, [] (location_t reveal) { map.revealed[map.at(static_cast(reveal.x), static_cast(reveal.y))] = true; map.visible[map.at(static_cast(reveal.x), static_cast(reveal.y))] = true; }, [] (auto test_visibility) { return map.walkable[map.at(static_cast(test_visibility.x), static_cast(test_visibility.y))]; } ); } // Tick is called every frame. The parameter specifies how many ms have elapsed // since the last time it was called. void tick(double duration_ms) { int angle = 0; // Increase the tick time by the frame duration. If it has exceeded // the tick duration, then we move the @. tick_time += duration_ms; if (tick_time > tick_duration) { // Are we there yet? if (dude_position == destination) { // Now we poll the mouse to determine where we want to go // This requests the mouse position in PIXELS, and ties it into our mouse_x/mouse_y variables. int mouse_x, mouse_y; std::tie(mouse_x, mouse_y) = get_mouse_position(); // Since we're using an 8x8, it's just a matter of dividing by 8 to find the terminal-character // coordinates. There will be a helper function for this once we get into retained GUIs. const int terminal_x = mouse_x / 8; const int terminal_y = mouse_y / 8; // If the mouse is pointing at a walkable location, and the left button is down - path to the mouse. const bool walkable = map.walkable[map.at(terminal_x, terminal_y)]; if (walkable && get_mouse_button_state(rltk::button::LEFT)) { destination.x = static_cast(terminal_x); destination.y = static_cast(terminal_y); // Now determine how to get there if (path) path.reset(); path = find_path(dude_position, destination); if (!path->success) { destination = dude_position; std::cout << "RESET: THIS ISN'T MEANT TO HAPPEN!\n"; } } else if (walkable) { // If the mouse is not clicked, then path to the mouse cursor for display only if (path) path.reset(); path = find_path_2d(dude_position, location_t{terminal_x, terminal_y}); } } else { // Follow the breadcrumbs! if (path) { location_t next_step = path->steps.front(); //dude_position.x = next_step.x; //dude_position.y = next_step.y; if (dude_position.x > next_step.x) { dude_position.x -= 0.25f; angle = 315; } if (dude_position.x < next_step.x) { dude_position.x += 0.25f; angle = 45; } if (dude_position.y > next_step.y) dude_position.y -= 0.25f; if (dude_position.y < next_step.y) dude_position.y += 0.25f; if (std::floor(dude_position.x) == next_step.x && std::floor(dude_position.y) == next_step.y) path->steps.pop_front(); // Update the map visibility std::fill(map.visible.begin(), map.visible.end(), false); visibility_sweep(); term(1)->dirty = true; } } // Important: we clear the tick count after the update. tick_time = 0.0; } // Render our planned path. We're using auto and a range-for to avoid typing all // the iterator stuff sterm(2)->clear(); if (path) { // We're going to show off a bit and "lerp" the color along the path; the red // lightens as it approaches the destination. This is a preview of some of the // color functions. const float n_steps = static_cast(path->steps.size()); float i = 0; for (auto step : path->steps) { const float lerp_amount = i / n_steps; vchar highlight; // If we're at our destination, we are showing possible paths - highlight green; // otherwise, highlight red to indicate that we are en route. if (dude_position == destination) { highlight = { 177, lerp(DARK_GREEN, LIGHTEST_GREEN, lerp_amount), BLACK }; } else { highlight = { 177, lerp(DARK_RED, LIGHTEST_RED, lerp_amount), BLACK }; } sterm(2)->add(xchar( 177, highlight.foreground, static_cast(step.x), static_cast(step.y) )); ++i; } } // Render our destination term(1)->set_char(term(1)->at(static_cast(destination.x), static_cast(destination.y)), destination_glyph); // Finally, we render the @ symbol. dude_x and dude_y are in terminal coordinates. //term(1)->set_char(term(1)->at(dude_position.x, dude_position.y), dude); sterm(2)->add(xchar( '@', YELLOW, static_cast(dude_position.x), static_cast(dude_position.y), angle )); // Iterate over the whole map, rendering as appropriate if (term(1)->dirty) { for (int y=0; yset_char(map_idx, vchar{'.', GREEN, BLACK}); } else if (map.revealed[map_idx]) { // Revealed tile: render grey term(1)->set_char(map_idx, vchar{'.', LIGHTER_GREY, BLACK}); } else { // We haven't seen it yet - darkest gray term(1)->set_char(map_idx, vchar{'.', DARK_GREY, BLACK}); } } else { if (map.visible[map_idx]) { // Visible tile: render full color term(1)->set_char(map_idx, vchar{'#', CYAN, BLACK}); } else if (map.revealed[map_idx]) { // Revealed tile: render grey term(1)->set_char(map_idx, vchar{'#', GREY, BLACK}); } else { // We haven't seen it yet - darkest gray term(1)->set_char(map_idx, vchar{'#', DARKER_GREY, BLACK}); } } } } } } void resize_map(layer_t * l, int w, int h) { // Use the whole window l->w = w; l->h = h; } // Your main function int main() { // Initialize as a 1024x768 window with a title init(config_advanced("../assets", 1020, 768, "RLTK - Example 9", false)); // Add a regular layer gui->add_layer(1, 0, 0, 1024, 768, "8x8", resize_map); // Add a spare layer so we can animate the @ symbol bending over gui->add_sparse_layer(2, 0, 0, 1024, 768, "8x8", resize_map); // Our sparse layer // We do a visibility sweep to start, so your starting position is revealed visibility_sweep(); // Enter the main loop. "tick" is the function we wrote above. run(tick); return 0; } ================================================ FILE: rltk/astar.hpp ================================================ /* A* Algorithm Implementation using STL is Copyright (C)2001-2005 Justin Heyes-Jones Permission is given by the author to freely redistribute and include this code in any program as long as this credit is given where due. COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. Use at your own risk! */ #pragma once // used for text debugging #include #include //#include #include // stl includes #include #include #include #include using std::vector; // fast fixed size memory allocator, used for fast node memory management #include "fsa.hpp" // Fixed size memory allocator can be disabled to compare performance // Uses std new and delete instead if you turn it off #define USE_FSA_MEMORY 1 // disable warning that debugging information has lines that are truncated // occurs in stl headers #if defined(WIN32) && defined(_WINDOWS) #pragma warning( disable : 4786 ) #endif template class AStarState; // The AStar search class. UserState is the users state space type template class AStarSearch { public: // data enum { SEARCH_STATE_NOT_INITIALISED, SEARCH_STATE_SEARCHING, SEARCH_STATE_SUCCEEDED, SEARCH_STATE_FAILED, SEARCH_STATE_OUT_OF_MEMORY, SEARCH_STATE_INVALID }; // A node represents a possible state in the search // The user provided state type is included inside this type public: class Node { public: Node *parent; // used during the search to record the parent of successor nodes Node *child; // used after the search for the application to view the search in reverse float g; // cost of this node + it's predecessors float h; // heuristic estimate of distance to goal float f; // sum of cumulative cost of predecessors and self and heuristic Node() : parent(0), child(0), g(0.0f), h(0.0f), f(0.0f) { } UserState m_UserState; }; // For sorting the heap the STL needs compare function that lets us compare // the f value of two nodes class HeapCompare_f { public: bool operator()(const Node *x, const Node *y) const { return x->f > y->f; } }; public: // methods // constructor just initialises private data AStarSearch() : m_State(SEARCH_STATE_NOT_INITIALISED), m_CurrentSolutionNode( NULL), #if USE_FSA_MEMORY m_FixedSizeAllocator(10000), #endif m_AllocateNodeCount(0), m_CancelRequest(false) { } AStarSearch(int MaxNodes) : m_State(SEARCH_STATE_NOT_INITIALISED), m_CurrentSolutionNode( NULL), #if USE_FSA_MEMORY m_FixedSizeAllocator(MaxNodes), #endif m_AllocateNodeCount(0), m_CancelRequest(false) { } // call at any time to cancel the search and free up all the memory void CancelSearch() { m_CancelRequest = true; } // Set Start and goal states void SetStartAndGoalStates(UserState &Start, UserState &Goal) { m_CancelRequest = false; m_Start = AllocateNode(); m_Goal = AllocateNode(); assert((m_Start != NULL && m_Goal != NULL)); m_Start->m_UserState = Start; m_Goal->m_UserState = Goal; m_State = SEARCH_STATE_SEARCHING; // Initialise the AStar specific parts of the Start Node // The user only needs fill out the state information m_Start->g = 0; m_Start->h = m_Start->m_UserState.GoalDistanceEstimate( m_Goal->m_UserState); m_Start->f = m_Start->g + m_Start->h; m_Start->parent = m_Start; // Push the start node on the Open list m_OpenList.push_back(m_Start); // heap now unsorted // Sort back element into heap push_heap(m_OpenList.begin(), m_OpenList.end(), HeapCompare_f()); // Initialise counter for search steps m_Steps = 0; } // Advances search one step unsigned int SearchStep() { // Firstly break if the user has not initialised the search assert((m_State > SEARCH_STATE_NOT_INITIALISED) && (m_State < SEARCH_STATE_INVALID)); // Next I want it to be safe to do a searchstep once the search has succeeded... if ((m_State == SEARCH_STATE_SUCCEEDED) || (m_State == SEARCH_STATE_FAILED)) { return m_State; } // Failure is defined as emptying the open list as there is nothing left to // search... // New: Allow user abort if (m_OpenList.empty() || m_CancelRequest) { FreeAllNodes(); m_State = SEARCH_STATE_FAILED; return m_State; } // Incremement step count m_Steps++; // Pop the best node (the one with the lowest f) Node *n = m_OpenList.front(); // get pointer to the node pop_heap(m_OpenList.begin(), m_OpenList.end(), HeapCompare_f()); m_OpenList.pop_back(); // Check for the goal, once we pop that we're done if (n->m_UserState.IsGoal(m_Goal->m_UserState)) { // The user is going to use the Goal Node he passed in // so copy the parent pointer of n m_Goal->parent = n->parent; m_Goal->g = n->g; // A special case is that the goal was passed in as the start state // so handle that here if (false == n->m_UserState.IsSameState(m_Start->m_UserState)) { FreeNode(n); // set the child pointers in each node (except Goal which has no child) Node *nodeChild = m_Goal; Node *nodeParent = m_Goal->parent; do { nodeParent->child = nodeChild; nodeChild = nodeParent; nodeParent = nodeParent->parent; } while (nodeChild != m_Start); // Start is always the first node by definition } // delete nodes that aren't needed for the solution FreeUnusedNodes(); m_State = SEARCH_STATE_SUCCEEDED; return m_State; } else // not goal { // We now need to generate the successors of this node // The user helps us to do this, and we keep the new nodes in // m_Successors ... m_Successors.clear(); // empty vector of successor nodes to n // User provides this functions and uses AddSuccessor to add each successor of // node 'n' to m_Successors bool ret = n->m_UserState.GetSuccessors(this, n->parent ? &n->parent->m_UserState : NULL); if (!ret) { typename vector::iterator successor; // free the nodes that may previously have been added for (successor = m_Successors.begin(); successor != m_Successors.end(); successor++) { FreeNode((*successor)); } m_Successors.clear(); // empty vector of successor nodes to n // free up everything else we allocated FreeAllNodes(); m_State = SEARCH_STATE_OUT_OF_MEMORY; return m_State; } // Now handle each successor to the current node ... for (typename vector::iterator successor = m_Successors.begin(); successor != m_Successors.end(); successor++) { // The g value for this successor ... float newg = n->g + n->m_UserState.GetCost((*successor)->m_UserState); // Now we need to find whether the node is on the open or closed lists // If it is but the node that is already on them is better (lower g) // then we can forget about this successor // First linear search of open list to find node typename vector::iterator openlist_result; for (openlist_result = m_OpenList.begin(); openlist_result != m_OpenList.end(); openlist_result++) { if ((*openlist_result)->m_UserState.IsSameState( (*successor)->m_UserState)) { break; } } if (openlist_result != m_OpenList.end()) { // we found this state on open if ((*openlist_result)->g <= newg) { FreeNode((*successor)); // the one on Open is cheaper than this one continue; } } typename vector::iterator closedlist_result; for (closedlist_result = m_ClosedList.begin(); closedlist_result != m_ClosedList.end(); closedlist_result++) { if ((*closedlist_result)->m_UserState.IsSameState( (*successor)->m_UserState)) { break; } } if (closedlist_result != m_ClosedList.end()) { // we found this state on closed if ((*closedlist_result)->g <= newg) { // the one on Closed is cheaper than this one FreeNode((*successor)); continue; } } // This node is the best node so far with this particular state // so lets keep it and set up its AStar specific data ... (*successor)->parent = n; (*successor)->g = newg; (*successor)->h = (*successor)->m_UserState.GoalDistanceEstimate( m_Goal->m_UserState); (*successor)->f = (*successor)->g + (*successor)->h; // Remove successor from closed if it was on it if (closedlist_result != m_ClosedList.end()) { // remove it from Closed FreeNode((*closedlist_result)); m_ClosedList.erase(closedlist_result); // Fix thanks to ... // Greg Douglas // who noticed that this code path was incorrect // Here we have found a new state which is already CLOSED // anus } // Update old version of this node if (openlist_result != m_OpenList.end()) { FreeNode((*openlist_result)); m_OpenList.erase(openlist_result); // re-make the heap // make_heap rather than sort_heap is an essential bug fix // thanks to Mike Ryynanen for pointing this out and then explaining // it in detail. sort_heap called on an invalid heap does not work make_heap(m_OpenList.begin(), m_OpenList.end(), HeapCompare_f()); } // heap now unsorted m_OpenList.push_back((*successor)); // sort back element into heap push_heap(m_OpenList.begin(), m_OpenList.end(), HeapCompare_f()); } // push n onto Closed, as we have expanded it now m_ClosedList.push_back(n); } // end else (not goal so expand) return m_State; // Succeeded bool is false at this point. } // User calls this to add a successor to a list of successors // when expanding the search frontier bool AddSuccessor(UserState &State) { Node *node = AllocateNode(); if (node) { node->m_UserState = State; m_Successors.push_back(node); return true; } return false; } // Free the solution nodes // This is done to clean up all used Node memory when you are done with the // search void FreeSolutionNodes() { Node *n = m_Start; if (m_Start->child) { do { Node *del = n; n = n->child; FreeNode(del); del = NULL; } while (n != m_Goal); FreeNode(n); // Delete the goal } else { // if the start node is the solution we need to just delete the start and goal // nodes FreeNode(m_Start); FreeNode(m_Goal); } } // Functions for traversing the solution // Get start node UserState *GetSolutionStart() { m_CurrentSolutionNode = m_Start; if (m_Start) { return &m_Start->m_UserState; } else { return NULL; } } // Get next node UserState *GetSolutionNext() { if (m_CurrentSolutionNode) { if (m_CurrentSolutionNode->child) { Node *child = m_CurrentSolutionNode->child; m_CurrentSolutionNode = m_CurrentSolutionNode->child; return &child->m_UserState; } } return NULL; } // Get end node UserState *GetSolutionEnd() { m_CurrentSolutionNode = m_Goal; if (m_Goal) { return &m_Goal->m_UserState; } else { return NULL; } } // Step solution iterator backwards UserState *GetSolutionPrev() { if (m_CurrentSolutionNode) { if (m_CurrentSolutionNode->parent) { Node *parent = m_CurrentSolutionNode->parent; m_CurrentSolutionNode = m_CurrentSolutionNode->parent; return &parent->m_UserState; } } return NULL; } // Get final cost of solution // Returns FLT_MAX if goal is not defined or there is no solution float GetSolutionCost() { if (m_Goal && m_State == SEARCH_STATE_SUCCEEDED) { return m_Goal->g; } else { return FLT_MAX; } } // For educational use and debugging it is useful to be able to view // the open and closed list at each step, here are two functions to allow that. UserState *GetOpenListStart() { float f, g, h; return GetOpenListStart(f, g, h); } UserState *GetOpenListStart(float &f, float &g, float &h) { iterDbgOpen = m_OpenList.begin(); if (iterDbgOpen != m_OpenList.end()) { f = (*iterDbgOpen)->f; g = (*iterDbgOpen)->g; h = (*iterDbgOpen)->h; return &(*iterDbgOpen)->m_UserState; } return NULL; } UserState *GetOpenListNext() { float f, g, h; return GetOpenListNext(f, g, h); } UserState *GetOpenListNext(float &f, float &g, float &h) { iterDbgOpen++; if (iterDbgOpen != m_OpenList.end()) { f = (*iterDbgOpen)->f; g = (*iterDbgOpen)->g; h = (*iterDbgOpen)->h; return &(*iterDbgOpen)->m_UserState; } return NULL; } UserState *GetClosedListStart() { float f, g, h; return GetClosedListStart(f, g, h); } UserState *GetClosedListStart(float &f, float &g, float &h) { iterDbgClosed = m_ClosedList.begin(); if (iterDbgClosed != m_ClosedList.end()) { f = (*iterDbgClosed)->f; g = (*iterDbgClosed)->g; h = (*iterDbgClosed)->h; return &(*iterDbgClosed)->m_UserState; } return NULL; } UserState *GetClosedListNext() { float f, g, h; return GetClosedListNext(f, g, h); } UserState *GetClosedListNext(float &f, float &g, float &h) { iterDbgClosed++; if (iterDbgClosed != m_ClosedList.end()) { f = (*iterDbgClosed)->f; g = (*iterDbgClosed)->g; h = (*iterDbgClosed)->h; return &(*iterDbgClosed)->m_UserState; } return NULL; } // Get the number of steps int GetStepCount() { return m_Steps; } void EnsureMemoryFreed() { #if USE_FSA_MEMORY assert(m_AllocateNodeCount == 0); #endif } private: // methods // This is called when a search fails or is cancelled to free all used // memory void FreeAllNodes() { // iterate open list and delete all nodes typename vector::iterator iterOpen = m_OpenList.begin(); while (iterOpen != m_OpenList.end()) { Node *n = (*iterOpen); FreeNode(n); iterOpen++; } m_OpenList.clear(); // iterate closed list and delete unused nodes typename vector::iterator iterClosed; for (iterClosed = m_ClosedList.begin(); iterClosed != m_ClosedList.end(); iterClosed++) { Node *n = (*iterClosed); FreeNode(n); } m_ClosedList.clear(); // delete the goal FreeNode(m_Goal); } // This call is made by the search class when the search ends. A lot of nodes may be // created that are still present when the search ends. They will be deleted by this // routine once the search ends void FreeUnusedNodes() { // iterate open list and delete unused nodes typename vector::iterator iterOpen = m_OpenList.begin(); while (iterOpen != m_OpenList.end()) { Node *n = (*iterOpen); if (!n->child) { FreeNode(n); n = NULL; } iterOpen++; } m_OpenList.clear(); // iterate closed list and delete unused nodes typename vector::iterator iterClosed; for (iterClosed = m_ClosedList.begin(); iterClosed != m_ClosedList.end(); iterClosed++) { Node *n = (*iterClosed); if (!n->child) { FreeNode(n); n = NULL; } } m_ClosedList.clear(); } // Node memory management Node *AllocateNode() { #if !USE_FSA_MEMORY Node *p = new Node; return p; #else Node *address = m_FixedSizeAllocator.alloc(); if (!address) { return NULL; } m_AllocateNodeCount++; Node *p = new (address) Node; return p; #endif } void FreeNode(Node *node) { m_AllocateNodeCount--; #if !USE_FSA_MEMORY delete node; #else node->~Node(); m_FixedSizeAllocator.free(node); #endif } private: // data // Heap (simple vector but used as a heap, cf. Steve Rabin's game gems article) vector m_OpenList; // Closed list is a vector. vector m_ClosedList; // Successors is a vector filled out by the user each type successors to a node // are generated vector m_Successors; // State unsigned int m_State; // Counts steps int m_Steps; // Start and goal state pointers Node *m_Start; Node *m_Goal; Node *m_CurrentSolutionNode; #if USE_FSA_MEMORY // Memory FixedSizeAllocator m_FixedSizeAllocator; #endif //Debug : need to keep these two iterators around // for the user Dbg functions typename vector::iterator iterDbgOpen; typename vector::iterator iterDbgClosed; // debugging : count memory allocation and free's int m_AllocateNodeCount; bool m_CancelRequest; }; template class AStarState { public: virtual ~AStarState() { } virtual float GoalDistanceEstimate(T &nodeGoal) = 0; // Heuristic function which computes the estimated cost to the goal node virtual bool IsGoal(T &nodeGoal) = 0; // Returns true if this node is the goal node virtual bool GetSuccessors(AStarSearch *astarsearch, T *parent_node) = 0; // Retrieves all successors to this node and adds them via astarsearch.addSuccessor() virtual float GetCost(T &successor) = 0; // Computes the cost of traveling from this node to the successor node virtual bool IsSameState(T &rhs) = 0; // Returns true if this node is the same as the rhs node }; ================================================ FILE: rltk/color_t.cpp ================================================ #include "color_t.hpp" #include using std::min; using std::max; using std::fmod; namespace rltk { // Credit: https://gist.github.com/fairlight1337/4935ae72bcbcc1ba5c72 std::tuple color_to_hsv(const color_t &col) { float fR = (col.r / 255.0f); float fG = (col.g / 255.0f); float fB = (col.b / 255.0f); float fH=0.0f, fS=0.0f, fV=0.0f; float fCMax = max(max(fR, fG), fB); float fCMin = min(min(fR, fG), fB); float fDelta = fCMax - fCMin; if(fDelta > 0) { if(fCMax == fR) { fH = 60.0f * (fmodf(((fG - fB) / fDelta), 6.0f)); } else if(fCMax == fG) { fH = 60.0f * (((fB - fR) / fDelta) + 2.0f); } else if(fCMax == fB) { fH = 60.0f * (((fR - fG) / fDelta) + 4.0f); } if(fCMax > 0) { fS = fDelta / fCMax; } else { fS = 0; } fV = fCMax; } else { fH = 0; fS = 0; fV = fCMax; } if(fH < 0) { fH = 360 + fH; } return std::make_tuple(fH, fS, fV); } // Credit: https://gist.github.com/fairlight1337/4935ae72bcbcc1ba5c72 std::tuple color_from_hsv(const float hue, const float saturation, const float value) { float fH = hue; float fS = saturation; float fV = value; float fR, fG, fB; float fC = fV * fS; // Chroma float fHPrime = fmodf(fH / 60.0f, 6.0f); float fX = fC * (1.0f - fabsf(fmodf(fHPrime, 2.0f) - 1.0f)); float fM = fV - fC; if(0 <= fHPrime && fHPrime < 1) { fR = fC; fG = fX; fB = 0; } else if(1 <= fHPrime && fHPrime < 2) { fR = fX; fG = fC; fB = 0; } else if(2 <= fHPrime && fHPrime < 3) { fR = 0; fG = fC; fB = fX; } else if(3 <= fHPrime && fHPrime < 4) { fR = 0; fG = fX; fB = fC; } else if(4 <= fHPrime && fHPrime < 5) { fR = fX; fG = 0; fB = fC; } else if(5 <= fHPrime && fHPrime < 6) { fR = fC; fG = 0; fB = fX; } else { fR = 0; fG = 0; fB = 0; } fR += fM; fG += fM; fB += fM; return std::make_tuple(static_cast(fR*255.0), static_cast(fG*255.0), static_cast(fB*255.0)); } /* * Calculates the luminance of a color, and converts it to grey-scale. */ color_t greyscale(const color_t &col) { unsigned char red = col.r; unsigned char green = col.g; unsigned char blue = col.b; float RED = red / 255.0F; float GREEN = green / 255.0F; float BLUE = blue / 255.0F; float luminance = 0.299f * RED + 0.587f * GREEN + 0.114f * BLUE; red = static_cast(luminance * 255.0F); green = static_cast(luminance * 255.0F); blue = static_cast(luminance * 255.0F); return color_t(red, green, blue); } /* * Darkens a color by the specified amount. */ color_t darken(const int &amount, const color_t &col) { unsigned char red = col.r; unsigned char green = col.g; unsigned char blue = col.b; if (red > amount) { red -= amount; } else { red = 0; } if (green > amount) { green -= amount; } else { green = 0; } if (blue > amount) { blue -= amount; } else { blue = 0; } return color_t(red, green, blue); } /* Applies colored lighting effect; colors that don't exist remain dark. Lights are from 0.0 to 1.0. */ color_t apply_colored_light(const color_t &col, const std::tuple &light) { unsigned char red = col.r; unsigned char green = col.g; unsigned char blue = col.b; float RED = red / 255.0F; float GREEN = green / 255.0F; float BLUE = blue / 255.0F; RED *= std::get<0>(light); GREEN *= std::get<1>(light); BLUE *= std::get<2>(light); if (RED > 1.0) RED = 1.0; if (RED < 0.0) RED = 0.0; if (GREEN > 1.0) GREEN = 1.0; if (GREEN < 0.0) GREEN = 0.0; if (BLUE > 1.0) BLUE = 1.0; if (BLUE < 0.0) BLUE = 0.0; red = static_cast(RED * 255.0F); green = static_cast(GREEN * 255.0F); blue = static_cast(BLUE * 255.0F); return color_t(red, green, blue); } color_t lerp(const color_t &first, const color_t &second, float amount) { const float r1 = first.r; const float g1 = first.g; const float b1 = first.b; const float r2 = second.r; const float g2 = second.g; const float b2 = second.b; const float rdiff = r2 - r1; const float gdiff = g2 - g1; const float bdiff = b2 - b1; float red = r1 + (rdiff * amount); float green = g1 + (gdiff * amount); float blue = b1 + (bdiff * amount); if (red > 255.0F) red = 255.0F; if (green > 255.0F) green = 255.0F; if (blue > 255.0F) blue = 255.0F; if (red < 0.0F) red = 0.0F; if (green < 0.0F) green = 0.0F; if (blue < 0.0F) blue = 0.0F; const int r = static_cast(red); const int g = static_cast(green); const int b = static_cast(blue); return color_t(r,g,b); } } ================================================ FILE: rltk/color_t.hpp ================================================ #pragma once /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * Color functions */ #include #include #include namespace rltk { /* Converts HSV into an RGB tuple */ extern std::tuple color_from_hsv(const float hue, const float saturation, const float value); struct color_t { /* Default empty constructor */ color_t() {} /* Convenience constructor from red/green/blue; accepts ints and casts them */ color_t(const int R, const int G, const int B) : r(static_cast(R)), g(static_cast(G)), b(static_cast(B)) {} /* Construct from red/green/blue, in the range of 0-255 per component. */ color_t(const uint8_t R, const uint8_t G, const uint8_t B) : r(R), g(G), b(B) {} /* Construct from HSV, in the range of 0-1.0 as floats. */ color_t(const float hue, const float saturation, const float value) { std::tie(r,g,b) = color_from_hsv(hue, saturation, value); } /* Construct from an RGB tuple */ color_t(const std::tuple &c) { std::tie(r,g,b) = c; } /* You can add colors together, for example as a quick blend/lighten */ color_t operator+(const color_t &other) { int red = r + other.r; int green = g + other.g; int blue = b + other.b; if (red > 255) red = 255; if (green > 255) green = 255; if (blue > 255) blue = 255; return color_t(red, green, blue); } /* You can subtract colors */ color_t operator-(const color_t &other) { int red = r - other.r; int green = g - other.g; int blue = b - other.b; if (red < 0) red = 0; if (green < 0) green = 0; if (blue < 0) blue = 0; return color_t(red, green, blue); } /* You can multiply colors */ color_t operator*(const color_t &other) { int red = r * other.r; int green = g * other.g; int blue = b * other.b; if (red < 0) red = 0; if (green < 0) green = 0; if (blue < 0) blue = 0; if (red > 255) red = 255; if (green > 255) green = 255; if (blue > 255) blue = 255; return color_t(red, green, blue); } /* You can compare colors */ bool operator==(const color_t &other) const { if (other.r == r && other.g == g && other.b == b) { return true; } else { return false; } } /* RGB storage */ uint8_t r,g,b; template void serialize(Archive & archive) { archive( r, g, b ); // serialize things by passing them to the archive } }; /* Converts a color_t to an SFML color */ inline sf::Color color_to_sfml(const color_t &col) { return sf::Color(col.r, col.g, col.b); } /* Converts a color_t to an RGB tuple */ inline std::tuple color_to_rgb(const color_t &col) { return std::make_tuple(col.r, col.g, col.b); } /* Converts a color_t to an HSV tuple */ extern std::tuple color_to_hsv(const color_t &col); /* Calculates the luminance of a color, and converts it to grey-scale. */ extern color_t greyscale(const color_t &col); /* Darkens a color by the specified amount. */ color_t darken(const int &amount, const color_t &col); /* Applies colored lighting effect; colors that don't exist remain dark. Lights are from 0.0 to 1.0. */ color_t apply_colored_light(const color_t &col, const std::tuple &light); /* Calculates an intermediate color on a linear RGB color ramp. Amount is from 0 to 1 */ extern color_t lerp(const color_t &first, const color_t &second, float amount); } ================================================ FILE: rltk/colors.hpp ================================================ #pragma once #include "color_t.hpp" namespace rltk { namespace colors { const color_t AliceBlue(240, 248, 255); const color_t AntiqueWhite(250, 235, 215); const color_t Aqua(0, 255, 255); const color_t Aquamarine(127, 255, 212); const color_t Beige(245, 245, 220); const color_t Black(0, 0, 0); const color_t BlanchedAlmond(255, 235, 205); const color_t Blue(0, 0, 255); const color_t BlueViolet(138, 43, 226); const color_t Brown(165, 42, 42); const color_t BurlyWood(222, 184, 135); const color_t CadetBlue(95, 158, 160); const color_t Chartreuse(127, 255, 0); const color_t Chocolate(210, 105, 20); const color_t Coral(255, 127, 80); const color_t CornflowerBlue(100, 149, 237); const color_t Cornsilk(255, 248, 220); const color_t Crimson(220, 20, 60); const color_t Cyan(0, 0, 255); const color_t DarkBlue(0, 0, 139); const color_t DarkCyan(0, 139, 139); const color_t DarkGoldenRod(184, 134, 11); const color_t DarkGray(169, 169, 169); const color_t DarkGreen(0, 100, 0); const color_t DarkKhaki(189, 183, 107); const color_t DarkMagenta(139, 0, 139); const color_t DarkOliveGreen(85, 107, 47); const color_t Darkorange(255, 140, 0); const color_t DarkOrchid(255, 140, 0); const color_t DarkRed(139, 0, 0); const color_t DarkSalmon(233, 150, 122); const color_t DarkSeaGreen(143, 188, 143); const color_t DarkSlateBlue(72, 61, 139); const color_t DarkSlateGray(47, 79, 79); const color_t DarkTurquoise(0, 206, 209); const color_t DarkViolet(148, 0, 211); const color_t DeepPink(255, 20, 147); const color_t DeepSkyBlue(0, 191, 255); const color_t DimGray(105, 105, 105); const color_t DodgerBlue(30, 144, 255); const color_t FireBrick(178, 34, 34); const color_t FloralWhite(255, 250, 240); const color_t ForestGreen(34, 139, 34); const color_t Fuchsia(255, 0, 255); const color_t Gainsboro(220, 220, 220); const color_t GhostWhite(248, 248, 255); const color_t Gold(255, 215, 0); const color_t GoldenRod(218, 165, 32); const color_t Grey(128, 128, 128); const color_t Green(0, 128, 0); const color_t GreenYellow(173, 255, 47); const color_t HoneyDew(240, 255, 240); const color_t HotPink(255, 105, 180); const color_t IndianRed(205, 92, 92); const color_t Indigo(72, 0, 130); const color_t Ivory(255, 255, 240); const color_t Khaki(240, 230, 140); const color_t Lavender(230, 230, 250); const color_t LavenderBlush(255, 240, 245); const color_t LawnGreen(124, 252, 0); const color_t LemonChiffon(255, 250, 205); const color_t LightBlue(173, 216, 203); const color_t LightCoral(240, 128, 128); const color_t LightCyan(240, 128, 128); const color_t LightGoldenRodYellow(250, 250, 210); const color_t LightGrey(211, 211, 211); const color_t LightGreen(144, 238, 144); const color_t LightPink(255, 182, 193); const color_t LightSalmon(255, 160, 122); const color_t LightSeaGreen(32, 178, 170); const color_t LightSkyBlue(135, 206, 250); const color_t LightSlateGrey(119, 136, 153); const color_t LightSteelBlue(176, 196, 222); const color_t LightYellow(255, 255, 224); const color_t Lime(0, 255, 0); const color_t LimeGreen(50, 205, 50); const color_t Linen(250, 240, 230); const color_t Magenta(255, 0, 255); const color_t Maroon(128, 0, 0); const color_t MediumAquaMarine(102, 205, 170); const color_t MediumBlue(0, 0, 205); const color_t MediumOrchid(186, 85, 211); const color_t MediumPurple(147, 112, 219); const color_t MediumSeaGreen(60, 179, 113); const color_t MediumSlateBlue(123, 104, 238); const color_t MediumSpringGreen(0, 250, 154); const color_t MediumTurquoise(72, 209, 204); const color_t MediumVioletRed(199, 21, 133); const color_t MidnightBlue(25, 25, 112); const color_t MintCream(245, 255, 250); const color_t MistyRose(255, 228, 225); const color_t Moccasin(255, 228, 181); const color_t NavajoWhite(255, 222, 173); const color_t Navy(0, 0, 128); const color_t OldLace(253, 245, 230); const color_t Olive(128, 128, 0); const color_t OliveDrab(107, 142, 35); const color_t Orange(255, 165, 0); const color_t OrangeRed(255, 69, 0); const color_t Orchid(218, 112, 214); const color_t PaleGoldenRod(238, 232, 170); const color_t PaleGreen(152, 251, 152); const color_t PaleTurquoise(175, 238, 238); const color_t PaleVioletRed(219, 112, 147); const color_t PapayaWhip(225, 239, 213); const color_t PeachPuff(225, 218, 185); const color_t Peru(205, 133, 63); const color_t Pink(255, 192, 203); const color_t Plum(221, 160, 221); const color_t PowderBlue(176, 224, 230); const color_t Purple(128, 0, 128); const color_t Red(255, 0, 0); const color_t RosyBrown(188, 143, 143); const color_t RoyalBlue(65, 105, 225); const color_t SaddleBrown(139, 69, 19); const color_t Salmon(250, 128, 114); const color_t SandyBrown(244, 164, 96); const color_t SeaGreen(46, 139, 87); const color_t SeaShell(255, 245, 238); const color_t Sienna(160, 82, 45); const color_t Silver(192, 192, 192); const color_t SkyBlue(135, 206, 235); const color_t SlateBlue(106, 90, 205); const color_t SlateGrey(112, 128, 144); const color_t Snow(255, 250, 250); const color_t SpringGreen(0, 255, 127); const color_t SteelBlue(70, 130, 180); const color_t Tan(210, 180, 140); const color_t Teal(0, 128, 128); const color_t Thistle(216, 191, 216); const color_t Tomato(255, 99, 71); const color_t Turquoise(64, 224, 208); const color_t Violet(238, 130, 238); const color_t Wheat(245, 222, 179); const color_t White(255, 255, 255); const color_t WhiteSmoke(245, 245, 245); const color_t Yellow(255, 0, 0); const color_t YellowGreen(154, 205, 50); const color_t BLACK(0,0,0); const color_t WHITE(255,255,255); const color_t DESATURATED_RED(128,64,64); const color_t LIGHTEST_RED(255,191,191); const color_t LIGHTER_RED(255,166,166); const color_t LIGHT_RED(255,115,115); const color_t RED(255,0,0); const color_t DARK_RED(191,0,0); const color_t DARKER_RED(128,0,0); const color_t DARKEST_RED(64,0,0); const color_t FLAME(255,63,0); const color_t ORANGE(255,127,0); const color_t AMBER(255,191,0); const color_t YELLOW(255,255,0); const color_t LIME(191,255,0); const color_t CHARTREUSE(127,255,0); const color_t DESATURATED_GREEN(64,128,64); const color_t LIGHTEST_GREEN(191,255,191); const color_t LIGHTER_GREEN(166,255,166); const color_t LIGHT_GREEN(115,255,115); const color_t GREEN(0,255,0); const color_t DARK_GREEN(0,191,0); const color_t DARKER_GREEN(0,128,0); const color_t DARKEST_GREEN(0,64,0); const color_t SEA(0,255,127); const color_t TURQUOISE(0,255,191); const color_t CYAN(0,255,255); const color_t SKY(0,191,255); const color_t AZURE(0,127,255); const color_t BLUE(0,0,255); const color_t HAN(63,0,255); const color_t VIOLET(127,0,255); const color_t PURPLE(191,0,255); const color_t FUCHSIA(255,0,191); const color_t MAGENTA(255,0,255); const color_t PINK(255,0,127); const color_t CRIMSON(255,0,63); const color_t BRASS(191,151,96); const color_t COPPER(200,117,51); const color_t GOLD(229,191,0); const color_t SILVER(203,203,203); const color_t CELADON(172,255,171); const color_t PEACH(255,159,127); const color_t LIGHTEST_GREY(223,223,223); const color_t LIGHTER_GREY(191,191,191); const color_t LIGHT_GREY(159,159,159); const color_t GREY(127,127,127); const color_t DARK_GREY(95,95,95); const color_t DARKER_GREY(63,63,63); const color_t DARKEST_GREY(31,31,31); const color_t LIGHTEST_SEPIA(222,211,195); const color_t LIGHTER_SEPIA(191,171,143); const color_t LIGHT_SEPIA(158,134,100); const color_t SEPIA(127,101,63); const color_t DARK_SEPIA(94,75,47); const color_t DARKER_SEPIA(63,50,31); const color_t DARKEST_SEPIA(31,24,15); } } ================================================ FILE: rltk/ecs.cpp ================================================ #include "ecs.hpp" #include #include namespace rltk { std::size_t impl::base_component_t::type_counter = 1; std::size_t base_message_t::type_counter = 1; std::size_t entity_t::entity_counter{1}; // Not using zero since it is used as null so often ecs default_ecs; entity_t * ecs::entity(const std::size_t id) noexcept { entity_t * result = nullptr; auto finder = entity_store.find(id); if (finder == entity_store.end()) return result; if (finder->second.deleted) return result; result = &finder->second; return result; } entity_t * ecs::create_entity() { entity_t new_entity; while (entity_store.find(new_entity.id) != entity_store.end()) { ++entity_t::entity_counter; new_entity.id = entity_t::entity_counter; } //std::cout << "New Entity ID#: " << new_entity.id << "\n"; entity_store.emplace(new_entity.id, new_entity); return entity(new_entity.id); } entity_t * ecs::create_entity(const std::size_t new_id) { entity_t new_entity(new_id); if (entity_store.find(new_entity.id) != entity_store.end()) { throw std::runtime_error("WARNING: Duplicate entity ID. Odd things will happen\n"); } entity_store.emplace(new_entity.id, new_entity); return entity(new_entity.id); } void ecs::each(std::function &&func) { for (auto it=entity_store.begin(); it!=entity_store.end(); ++it) { if (!it->second.deleted) { func(it->second); } } } void ecs::delete_all_systems() { system_store.clear(); system_profiling.clear(); pubsub_holder.clear(); } void ecs::ecs_configure() { for (std::unique_ptr & sys : system_store) { sys->configure(); } } void ecs::ecs_tick(const double duration_ms) { std::size_t count = 0; for (std::unique_ptr & sys : system_store) { std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now(); sys->update(duration_ms); deliver_messages(); std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now(); double duration = static_cast(std::chrono::duration_cast( t2 - t1 ).count()); system_profiling[count].last = duration; if (duration > system_profiling[count].worst) system_profiling[count].worst = duration; if (duration < system_profiling[count].best) system_profiling[count].best = duration; ++count; } ecs_garbage_collect(); } void ecs::ecs_save(std::unique_ptr &lbfile) { cereal::BinaryOutputArchive oarchive(*lbfile); oarchive(*this); } void ecs::ecs_load(std::unique_ptr &lbfile) { entity_store.clear(); component_store.clear(); cereal::BinaryInputArchive iarchive(*lbfile); iarchive(*this); std::cout << "Loaded " << entity_store.size() << " entities, and " << component_store.size() << " component types.\n"; } std::string ecs::ecs_profile_dump() { std::stringstream ss; ss.precision(3); ss << std::fixed; ss << "SYSTEMS PERFORMANCE IN MICROSECONDS:\n"; ss << std::setw(20) << "System" << std::setw(20) << "Last" << std::setw(20) << "Best" << std::setw(20) << "Worst\n"; for (std::size_t i=0; isystem_name << std::setw(20) << system_profiling[i].last << std::setw(20) << system_profiling[i].best << std::setw(20) << system_profiling[i].worst << "\n"; } return ss.str(); } } ================================================ FILE: rltk/ecs.hpp ================================================ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "serialization_utils.hpp" #include "xml.hpp" #include #include "ecs_impl.hpp" namespace rltk { /* Public interface to allow existing calls to continue to work */ extern ecs default_ecs; inline entity_t * entity(ecs &ECS, const std::size_t id) noexcept { return ECS.entity(id); } inline entity_t * entity(const std::size_t id) noexcept { return entity(default_ecs, id); } inline entity_t * create_entity(ecs &ECS) { return ECS.create_entity(); } inline entity_t * create_entity() { return create_entity(default_ecs); } inline entity_t * create_entity(ecs &ECS, const std::size_t new_id) { return ECS.create_entity(new_id); } inline entity_t * create_entity(const std::size_t new_id) { return create_entity(default_ecs, new_id); } inline void delete_entity(ecs &ECS, const std::size_t id) noexcept { ECS.delete_entity(id); } inline void delete_entity(const std::size_t id) noexcept { delete_entity(default_ecs, id); } inline void delete_entity(ecs &ECS, entity_t &e) noexcept { ECS.delete_entity(e); } inline void delete_entity(entity_t &e) noexcept { delete_entity(default_ecs, e); } inline void delete_all_entities(ecs &ECS) noexcept { ECS.delete_all_entities(); } inline void delete_all_entities() noexcept { delete_all_entities(default_ecs); } template inline void delete_component(ecs &ECS, const std::size_t entity_id, bool delete_entity_if_empty=false) noexcept { ECS.delete_component(entity_id, delete_entity_if_empty); } template inline void delete_component(const std::size_t entity_id, bool delete_entity_if_empty=false) noexcept { delete_component(default_ecs, entity_id, delete_entity_if_empty); } template inline std::vector entities_with_component(ecs &ECS) { return ECS.entities_with_component(); } template inline std::vector entities_with_component() { return entities_with_component(default_ecs); } template inline void all_components(ecs &ECS, typename std::function func) { ECS.all_components(func); } template inline void all_components(typename std::function func) { all_components(default_ecs, func); } template inline void each(ecs &ECS, F callback) { ECS.each(callback); } template inline void each(F callback) { each(default_ecs, callback); } template inline void each_if(ecs &ECS, P&& predicate, F callback) { ECS.each_if(predicate, callback); } template inline void each_if(P&& predicate, F callback) { each_if(default_ecs, predicate, callback); } inline void ecs_garbage_collect(ecs &ECS) { ECS.ecs_garbage_collect(); } inline void ecs_garbage_collect() { ecs_garbage_collect(default_ecs); } template inline void emit(ecs &ECS, MSG message) { ECS.emit(message); } template inline void emit(MSG message) { default_ecs.emit(message); } template inline void emit_deferred(ecs &ECS, MSG message) { ECS.emit_deferred(message); } template inline void emit_deferred(MSG message) { emit_deferred(default_ecs, message); } template inline void add_system( ecs &ECS, Args && ... args ) { ECS.add_system(args...); } template inline void add_system( Args && ... args ) { add_system(default_ecs, args...); } inline void delete_all_systems(ecs &ECS) { ECS.delete_all_systems(); } inline void delete_all_systems() { delete_all_systems(default_ecs); } inline void ecs_configure(ecs &ECS) { ECS.ecs_configure(); } inline void ecs_configure() { ecs_configure(default_ecs); } inline void ecs_tick(ecs &ECS, const double duration_ms) { ECS.ecs_tick(duration_ms); } inline void ecs_tick(const double duration_ms) { ecs_tick(default_ecs, duration_ms); } inline void ecs_save(ecs &ECS, std::unique_ptr &lbfile) { ECS.ecs_save(lbfile); } inline void ecs_save(std::unique_ptr &lbfile) { ecs_save(default_ecs, lbfile); } inline void ecs_load(ecs &ECS, std::unique_ptr &lbfile) { ECS.ecs_load(lbfile); } inline void ecs_load(std::unique_ptr &lbfile) { ecs_load(default_ecs, lbfile); } inline std::string ecs_profile_dump(ecs &ECS) { return ECS.ecs_profile_dump(); } inline std::string ecs_profile_dump() { return ecs_profile_dump(default_ecs); } } ================================================ FILE: rltk/ecs_impl.hpp ================================================ #pragma once #include #include #include #include #include namespace rltk { // Forward declarations class ecs; struct entity_t; struct base_system; extern ecs default_ecs; namespace impl { template inline void assign(ecs &ECS, entity_t &E, C component); template inline C * component(ecs &ECS, entity_t &E) noexcept; template inline void subscribe(ecs &ECS, base_system &B, std::function destination); template inline void subscribe_mbox(ecs &ECS, base_system &B); inline void unset_component_mask(ecs &ECS, const std::size_t id, const std::size_t family_id, bool delete_if_empty=false); } /* * Base class from which all messages must derive. */ struct base_message_t { static std::size_t type_counter; }; /* Class for storing profile data */ struct system_profiling_t { double last = 0.0; double best = 1000000.0; double worst = 0.0; }; struct base_system; namespace impl { /* * Constant defining the maximum number of components to support. This sizes the bitsets, * so we don't want it to be much bigger than needed. */ constexpr std::size_t MAX_COMPONENTS = 128; /* * If the current component set does not support serialization, this will become true. */ static bool ecs_supports_serialization = true; /* * Base type for component handles. Exists so that we can have a vector of pointers to * derived classes. entity_id is included to allow a quick reference without a static cast. * type_counter is used as a static member, referenced from component_t - the handle class. */ struct base_component_t { static std::size_t type_counter; std::size_t entity_id; bool deleted = false; template void serialize(Archive & archive) { archive( entity_id, deleted ); // serialize things by passing them to the archive } }; /* Extracts xml_identity from a class, or uses the RTTI name if one isn't available */ template< class T > struct has_to_xml_identity { typedef char(&YesType)[1]; typedef char(&NoType)[2]; template< class, class > struct Sfinae; template< class T2 > static YesType Test( Sfinae().xml_identity)> *); template< class T2 > static NoType Test( ... ); static const bool value = sizeof(Test(0))==sizeof(YesType); }; template struct _calc_xml_identity { template typename std::enable_if< has_to_xml_identity::value, void >::type test(T &data, std::string &id) { id = data.xml_identity; } template typename std::enable_if< !has_to_xml_identity::value, void >::type test(T &data, std::string &id) { id = typeid(data).name(); } }; template struct _ecs_check_for_to_xml { template typename std::enable_if< serial::has_to_xml_method::value, void >::type test(xml_node * c, T &data) { data.to_xml(c); } template typename std::enable_if< !serial::has_to_xml_method::value, void >::type test(xml_node * c, T &data) { ecs_supports_serialization = false; } }; /* * component_t is a handle class for components. It inherits from base_component, allowing * the component store to have vectors of base_component_t *, where each type is a concrete * specialized class containing the component data. It does some magic with a static type_counter * to ensure that each instance with the same template type will have a unique family_id - this is * then used to reference the correct component store. */ template struct component_t : public base_component_t { component_t() { data = C{}; family(); } component_t(C comp) : data(comp) { family(); } std::size_t family_id; C data; template void serialize(Archive & archive) { archive( cereal::base_class(this), family_id, data ); // serialize things by passing them to the archive } inline void family() { static std::size_t family_id_tmp = base_component_t::type_counter++; family_id = family_id_tmp; } inline std::string xml_identity() { std::string id; _calc_xml_identity().test(data, id); return id; } inline void to_xml(xml_node * c) { _ecs_check_for_to_xml serial; serial.test(c, data); } }; /* * Base class for the component store. Concrete component stores derive from this. */ struct base_component_store { virtual void erase_by_entity_id(ecs &ECS, const std::size_t &id)=0; virtual void really_delete()=0; virtual void save(xml_node * xml)=0; virtual std::size_t size()=0; template void serialize(Archive & archive) { } }; /* * Component stores are just a vector of type C (the component). They inherit from * base_component_store, to allow for a vector of base_component_store*, with each * casting to a concrete vector of that type. The types are indexed by the family_id * created for a type with component_t. This guarantees that each component type * is stored in a big contiguous vector, with only one de-reference required to find * the right store. */ template struct component_store_t : public base_component_store { std::vector components; virtual void erase_by_entity_id(ecs &ECS, const std::size_t &id) override final { for (auto &item : components) { if (item.entity_id == id) { item.deleted=true; impl::unset_component_mask(ECS, id, item.family_id); } } } virtual void really_delete() override final { components.erase(std::remove_if(components.begin(), components.end(), [] (auto x) { return x.deleted; }), components.end()); } virtual void save(xml_node * xml) override final { for (auto &item : components) { xml_node * body = xml->add_node(item.xml_identity()); item.to_xml(body); body->add_value("entity_id", rltk::serial::to_string(item.entity_id)); } } virtual std::size_t size() override final { return components.size(); } template void serialize(Archive & archive) { archive( cereal::base_class(this), components ); // serialize things by passing them to the archive } }; /* * Handle class for messages */ template struct message_t : public base_message_t { message_t() { C empty; data = empty; family(); } message_t(C comp) : data(comp) { family(); } std::size_t family_id; C data; inline void family() { static std::size_t family_id_tmp = base_message_t::type_counter++; family_id = family_id_tmp; } }; /* * Base class for storing subscriptions to messages */ struct subscription_base_t { virtual void deliver_messages()=0; }; /* Base class for subscription mailboxes */ struct subscription_mailbox_t { }; /* Implementation class for mailbox subscriptions; stores a queue */ template struct mailbox_t : subscription_mailbox_t { std::queue messages; }; /* * Class that holds subscriptions, and determines delivery mechanism. */ template struct subscription_holder_t : subscription_base_t { std::queue delivery_queue; std::mutex delivery_mutex; std::vector,base_system *>> subscriptions; virtual void deliver_messages() override { std::lock_guard guard(delivery_mutex); while (!delivery_queue.empty()) { C message = delivery_queue.front(); delivery_queue.pop(); message_t handle(message); for (auto &func : subscriptions) { if (std::get<0>(func) && std::get<1>(func)) { std::get<1>(func)(message); } else { // It is destined for the system's mailbox queue. auto finder = std::get<2>(func)->mailboxes.find(handle.family_id); if (finder != std::get<2>(func)->mailboxes.end()) { static_cast *>(finder->second.get())->messages.push(message); } } } } } }; } // End impl namespace /* * All entities are of type entity_t. They should be created with create_entity (below). */ struct entity_t { /* * Default constructor - use the next available entity id #. */ entity_t() { ++entity_t::entity_counter; id = entity_t::entity_counter; } /* * Construct with a specified entity #. Moves the next available entity # to this id+1. */ entity_t(const std::size_t ID) { entity_t::entity_counter = ID+1; id = ID; } /* * Static ID counter - used to ensure that entity IDs are unique. This would need to be atomic * in a threaded app. */ static std::size_t entity_counter; /* * The entities ID number. Used to identify the entity. These should be unique. */ std::size_t id; /* * Overload == and != to allow entities to be compared for likeness. */ bool operator == (const entity_t &other) const { return other.id == id; } bool operator != (const entity_t &other) const { return other.id != id; } bool deleted = false; /* * A bitset storing whether or not an entity has each component type. These are set with the family_id * determined in the component_t system above. */ std::bitset component_mask; /* * Assign a component to this entity. Determines the family_id of the component type, sets the bitmask to * include the component, marks the component as belonging to the entity, and puts it in the appropriate * component store. */ template inline entity_t * assign(ecs &ECS, C component) { if (deleted) throw std::runtime_error("Cannot assign to a deleted entity"); impl::assign(ECS, *this, component); return this; } template inline entity_t * assign(C component) { return assign(default_ecs, component); } /* * Find a component of the specified type that belongs to the entity. */ template inline C * component(ecs &ECS) noexcept { return impl::component(ECS, *this); } template inline C * component() noexcept { return component(default_ecs); } template void serialize(Archive & archive) { archive( component_mask, id, deleted ); // serialize things by passing them to the archive } }; /* * Systems should inherit from this class. */ struct base_system { virtual void configure() {} virtual void update(const double duration_ms)=0; std::string system_name = "Unnamed System"; std::unordered_map> mailboxes; template void subscribe(ecs &ECS, std::function destination) { impl::subscribe(ECS, *this, destination); } template void subscribe(std::function destination) { subscribe(default_ecs, destination); } template void subscribe_mbox(ecs &ECS) { impl::subscribe_mbox(ECS, *this); } template void subscribe_mbox() { subscribe_mbox(default_ecs); } template std::queue * mbox() { impl::message_t handle(MSG{}); auto finder = mailboxes.find(handle.family_id); if (finder != mailboxes.end()) { return &static_cast *>(finder->second.get())->messages; } else { return nullptr; } } template void each_mbox(const std::function &func) { std::queue * mailbox = mbox(); while (!mailbox->empty()) { MSG msg = mailbox->front(); mailbox->pop(); func(msg); } } }; /* A pre-implemented simple system for systems that only handle messages. */ template struct mailbox_system : public base_system { virtual void configure() override final { subscribe_mbox(); } virtual void update(const double duration_ms) override final { std::queue * mailbox = base_system::mbox(); while (!mailbox->empty()) { MSG msg = mailbox->front(); mailbox->pop(); on_message(msg); } } virtual void on_message(const MSG &msg)=0; }; /* * Class that holds an entity-component-system. This was moved to a class to allow for multiple instances. */ class ecs { public: /* * entity(ID) is used to reference an entity. So you can, for example, do: * entity(12)->component()->x = 3; */ entity_t * entity(const std::size_t id) noexcept; /* * Creates an entity with a new ID #. Returns a pointer to the entity, to enable * call chaining. For example create_entity()->assign(foo)->assign(bar) */ entity_t * create_entity(); /* * Creates an entity with a specified ID #. You generally only do this during loading. */ entity_t * create_entity(const std::size_t new_id); /* * Marks an entity (specified by ID#) as deleted. */ inline void delete_entity(const std::size_t id) noexcept { auto e = entity(id); if (!e) return; e->deleted = true; for (auto &store : component_store) { if (store) store->erase_by_entity_id(*this, id); } } /* * Marks an entity as deleted. */ inline void delete_entity(entity_t &e) noexcept { delete_entity(e.id); } /* * Deletes all entities */ inline void delete_all_entities() noexcept { for (auto it=entity_store.begin(); it!=entity_store.end(); ++it) { delete_entity(it->first); } } /* * Marks an entity's component as deleted. */ template inline void delete_component(const std::size_t entity_id, bool delete_entity_if_empty=false) noexcept { auto eptr = entity(entity_id); if (!eptr) return; entity_t e = *entity(entity_id); C empty_component; impl::component_t temp(empty_component); if (!e.component_mask.test(temp.family_id)) return; for (impl::component_t &component : static_cast> *>(component_store[temp.family_id].get())->components) { if (component.entity_id == entity_id) { component.deleted = true; unset_component_mask(entity_id, temp.family_id, delete_entity_if_empty); } } } /* * Finds all entities that have a component of the type specified, and returns a * vector of pointers to the entities. It does not check for component deletion. */ template inline std::vector entities_with_component() { C empty_component; std::vector result; impl::component_t temp(empty_component); for (auto it=entity_store.begin(); it!=entity_store.end(); ++it) { if (!it->second.deleted && it->second.component_mask.test(temp.family_id)) { result.push_back(&it->second); } } return result; } /* * all_components takes a component type, and calls the provided function/lambda on * every component, alongside it's owning entity. For example, * all_components([] (entity_t &e, position &p) {...}) would execute the * function body (...) for every entity/component position pair. */ template inline void all_components(typename std::function func) { C empty_component; impl::component_t temp(empty_component); for (impl::component_t &component : static_cast> *>(component_store[temp.family_id].get())->components) { entity_t e = *entity(component.entity_id); if (!e.deleted && !component.deleted) { func(e, component.data); } } } /* * each, overloaded with a function/lambda that accepts an entity, will call the provided * function on _every_ entity in the system. */ void each(std::function &&func); /* * Variadic each. Use this to call a function for all entities having a discrete set of components. For example, * each([] (entity_t &e, position &pos, ai &brain) { ... code ... }); */ template inline void each(F callback) { std::array family_ids{ {impl::component_t{}.family_id...} }; for (auto it=entity_store.begin(); it!=entity_store.end(); ++it) { if (!it->second.deleted) { bool matches = true; for (const std::size_t &compare : family_ids) { if (!it->second.component_mask.test(compare)) { matches = false; break; } } if (matches) { // Call the functor callback(it->second, *it->second.component()...); } } } } /* * Variadic each_if. Use this to call a function for all entities having a discrete set of components. For example, * each([] (entity_t &e, position &pos, ai &brain) { ... code returns true if needs processing ... }, * [] (entity_t &e, position &pos, ai &brain) { ... code ... }); */ template inline void each_if(P&& predicate, F callback) { std::array family_ids{ {impl::component_t{}.family_id...} }; for (auto it=entity_store.begin(); it!=entity_store.end(); ++it) { if (!it->second.deleted) { bool matches = true; for (const std::size_t &compare : family_ids) { if (!it->second.component_mask.test(compare)) { matches = false; break; } } if (matches && predicate(it->second, *it->second.component()...)) { // Call the functor callback(it->second, *it->second.component()...); } } } } /* * This should be called periodically to actually erase all entities and components that are marked as deleted. */ inline void ecs_garbage_collect() { std::unordered_set entities_to_delete; // Ensure that components are marked as deleted, and list out entities for erasure for (auto it=entity_store.begin(); it!=entity_store.end(); ++it) { if (it->second.deleted) { for (std::unique_ptr &store : component_store) { if (store) store->erase_by_entity_id(*this, it->second.id); } entities_to_delete.insert(it->second.id); } } // Actually delete entities for (const std::size_t &id : entities_to_delete) entity_store.erase(id); // Now we erase components for (std::unique_ptr &store : component_store) { if (store) store->really_delete(); } } /* * Submits a message for delivery. It will be delivered to every system that has issued a subscribe or subscribe_mbox * call. */ template inline void emit(MSG message) { impl::message_t handle(message); if (pubsub_holder.size() > handle.family_id) { for (auto &func : static_cast *>(pubsub_holder[handle.family_id].get())->subscriptions) { if (std::get<0>(func) && std::get<1>(func)) { std::get<1>(func)(message); } else { // It is destined for the system's mailbox queue. auto finder = std::get<2>(func)->mailboxes.find(handle.family_id); if (finder != std::get<2>(func)->mailboxes.end()) { static_cast *>(finder->second.get())->messages.push(message); } } } } } /* * Submits a message for delivery. It will be delivered to every system that has issued a subscribe or subscribe_mbox * call at the end of the next system execution. This is thead-safe, so you can emit_defer from within a parallel_each. */ template inline void emit_deferred(MSG message) { impl::message_t handle(message); if (pubsub_holder.size() > handle.family_id) { auto * subholder = static_cast *>(pubsub_holder[handle.family_id].get()); std::lock_guard postlock(subholder->delivery_mutex); subholder->delivery_queue.push(message); } } /* Add a system to the mix */ template inline void add_system( Args && ... args ) { system_store.push_back(std::make_unique( std::forward(args) ... )); system_profiling.push_back(system_profiling_t{}); } void delete_all_systems(); void ecs_configure(); void ecs_tick(const double duration_ms); void ecs_save(std::unique_ptr &lbfile); void ecs_load(std::unique_ptr &lbfile); std::string ecs_profile_dump(); // The ECS component store std::vector> component_store; // The ECS entity store std::unordered_map entity_store; // Mailbox system std::vector> pubsub_holder; // Storage of systems std::vector> system_store; // Profile data storage std::vector system_profiling; // Helpers inline void unset_component_mask(const std::size_t id, const std::size_t family_id, bool delete_if_empty) { auto finder = entity_store.find(id); if (finder != entity_store.end()) { finder->second.component_mask.reset(family_id); if (delete_if_empty && finder->second.component_mask.none()) finder->second.deleted = true; } } /* Delivers the queue; called at the end of each system call */ inline void deliver_messages() { for (auto &holder : pubsub_holder) { if (holder) holder->deliver_messages(); } } /* * Cereal support for save/load */ template void serialize(Archive & archive) { archive( entity_store, component_store, entity_t::entity_counter, impl::base_component_t::type_counter ); // serialize things by passing them to the archive } }; namespace impl { template inline void assign(ecs &ECS, entity_t &E, C component) { impl::component_t temp(component); temp.entity_id = E.id; if (ECS.component_store.size() < temp.family_id+1) { ECS.component_store.resize(temp.family_id+1); } if (!ECS.component_store[temp.family_id]) ECS.component_store[temp.family_id] = std::move(std::make_unique>>()); static_cast> *>(ECS.component_store[temp.family_id].get())->components.push_back(temp); E.component_mask.set(temp.family_id); } template inline C * component(ecs &ECS, entity_t &E) noexcept { C * result = nullptr; if (E.deleted) return result; C empty_component; impl::component_t temp(empty_component); if (!E.component_mask.test(temp.family_id)) return result; for (impl::component_t &component : static_cast> *>(ECS.component_store[temp.family_id].get())->components) { if (component.entity_id == E.id) { result = &component.data; return result; } } return result; } template inline void subscribe(ecs &ECS, base_system &B, std::function destination) { MSG empty_message{}; impl::message_t handle(empty_message); if (ECS.pubsub_holder.size() < handle.family_id + 1) { ECS.pubsub_holder.resize(handle.family_id + 1); } if (!ECS.pubsub_holder[handle.family_id]) { ECS.pubsub_holder[handle.family_id] = std::move(std::make_unique>()); } static_cast *>(ECS.pubsub_holder[handle.family_id].get())->subscriptions.push_back(std::make_tuple(true,destination,nullptr)); } template inline void subscribe_mbox(ecs &ECS, base_system &B) { MSG empty_message{}; impl::message_t handle(empty_message); if (ECS.pubsub_holder.size() < handle.family_id + 1) { ECS.pubsub_holder.resize(handle.family_id + 1); } if (!ECS.pubsub_holder[handle.family_id]) { ECS.pubsub_holder[handle.family_id] = std::move(std::make_unique>()); } std::function destination; // Deliberately empty static_cast *>(ECS.pubsub_holder[handle.family_id].get())->subscriptions.push_back(std::make_tuple(false,destination,&B)); B.mailboxes[handle.family_id] = std::make_unique>(); } inline void unset_component_mask(ecs &ECS, const std::size_t id, const std::size_t family_id, bool delete_if_empty) { ECS.unset_component_mask(id, family_id, delete_if_empty); } } } // End RLTK namespace CEREAL_REGISTER_ARCHIVE(rltk::ecs) ================================================ FILE: rltk/filesystem.hpp ================================================ /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * Minimal filesystem tools */ #pragma once #include #include namespace rltk { inline bool exists(const std::string &filename) noexcept { struct stat buffer; return (stat (filename.c_str(), &buffer) == 0); } } ================================================ FILE: rltk/font_manager.cpp ================================================ #include "font_manager.hpp" #include "texture_resources.hpp" #include #include #include #include #include #include "filesystem.hpp" namespace rltk { namespace font_detail { std::vector split ( const std::string &str, const char &delimiter ) { std::vector internal; std::stringstream ss ( str ); // Turn the string into a stream. std::string tok; while ( getline ( ss, tok, delimiter ) ) { internal.push_back ( tok ); } return internal; } std::unordered_map atlas; } bitmap_font * get_font(const std::string font_tag) { auto finder = font_detail::atlas.find(font_tag); if (finder == font_detail::atlas.end()) { throw std::runtime_error("Unable to locate bitmap font with tag " + font_tag); } else { return &finder->second; } } inline void check_for_duplicate_font(const std::string &tag) { auto finder = font_detail::atlas.find(tag); if (finder != font_detail::atlas.end()) { throw std::runtime_error("Attempting to insert duplicate font with tag " + tag); } } inline void check_texture_exists(const std::string &texture_tag) { if (get_texture(texture_tag) == nullptr) { throw std::runtime_error("No such texture resource: " + texture_tag); } } void register_font(const std::string font_tag, const std::string filename, int width, int height) { const std::string texture_tag = "font_tex_" + filename; check_for_duplicate_font(font_tag); register_texture(filename, texture_tag); check_texture_exists(texture_tag); font_detail::atlas.emplace(std::make_pair(font_tag, bitmap_font(texture_tag, width, height))); } void register_font_directory(const std::string path) { if (!exists(path)) throw std::runtime_error("Font directory does not exist."); const std::string info_file = path + "/fonts.txt"; if (!exists(info_file)) throw std::runtime_error("No fonts.txt file in font directory."); std::ifstream f(info_file); std::string line; while (getline(f, line)) { auto split = font_detail::split(line, ','); if (split.size() == 4) { register_font(split[0], path + "/" + split[1], std::stoi(split[2]), std::stoi(split[3])); } } /* ptree font_tree; read_json(info_file, font_tree); ptree::const_iterator end = font_tree.get_child("fonts").end(); for (ptree::const_iterator it = font_tree.get_child("fonts").begin(); it != end; ++it) { const std::string font_name = it->first; const std::string font_tree_path = "fonts." + font_name + "."; const std::string font_file = font_tree.get(font_tree_path + "file"); const int width = font_tree.get(font_tree_path + "width"); const int height = font_tree.get(font_tree_path + "height"); register_font(font_name, path + "/" + font_file, width, height); }*/ } } ================================================ FILE: rltk/font_manager.hpp ================================================ /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * Font manager */ #pragma once #include #include namespace rltk { struct bitmap_font { bitmap_font(const std::string tag, const int width, const int height) : texture_tag(tag), character_size({width,height}) {} const std::string texture_tag; const std::pair character_size; }; void register_font_directory(const std::string path); bitmap_font * get_font(const std::string font_tag); void register_font(const std::string font_tag, const std::string filename, int width=8, int height=8); } ================================================ FILE: rltk/fsa.hpp ================================================ /* A* Algorithm Implementation using STL is Copyright (C)2001-2005 Justin Heyes-Jones Permission is given by the author to freely redistribute and include this code in any program as long as this credit is given where due. COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. Use at your own risk! FixedSizeAllocator class Copyright 2001 Justin Heyes-Jones This class is a constant time O(1) memory manager for objects of a specified type. The type is specified using a template class. Memory is allocated from a fixed size buffer which you can specify in the class constructor or use the default. Using GetFirst and GetNext it is possible to iterate through the elements one by one, and this would be the most common use for the class. I would suggest using this class when you want O(1) add and delete and you don't do much searching, which would be O(n). Structures such as binary trees can be used instead to get O(logn) access time. */ #pragma once #include #include template class FixedSizeAllocator { public: // Constants enum { FSA_DEFAULT_SIZE = 100 }; // This class enables us to transparently manage the extra data // needed to enable the user class to form part of the double-linked // list class struct FSA_ELEMENT { USER_TYPE UserType; FSA_ELEMENT *pPrev; FSA_ELEMENT *pNext; }; public: // methods FixedSizeAllocator(unsigned int MaxElements = FSA_DEFAULT_SIZE) : m_pFirstUsed(NULL), m_MaxElements(MaxElements) { // Allocate enough memory for the maximum number of elements char *pMem = new char[m_MaxElements * sizeof(FSA_ELEMENT)]; m_pMemory = (FSA_ELEMENT *) pMem; // Set the free list first pointer m_pFirstFree = m_pMemory; // Clear the memory memset(m_pMemory, 0, sizeof(FSA_ELEMENT) * m_MaxElements); // Point at first element FSA_ELEMENT *pElement = m_pFirstFree; // Set the double linked free list for (unsigned int i = 0; i < m_MaxElements; i++) { pElement->pPrev = pElement - 1; pElement->pNext = pElement + 1; pElement++; } // first element should have a null prev m_pFirstFree->pPrev = NULL; // last element should have a null next (pElement - 1)->pNext = NULL; } ~FixedSizeAllocator() { // Free up the memory delete[] (char *) m_pMemory; } // Allocate a new USER_TYPE and return a pointer to it USER_TYPE *alloc() { FSA_ELEMENT *pNewNode = NULL; if (!m_pFirstFree) { return NULL; } else { pNewNode = m_pFirstFree; m_pFirstFree = pNewNode->pNext; // if the new node points to another free node then // change that nodes prev free pointer... if (pNewNode->pNext) { pNewNode->pNext->pPrev = NULL; } // node is now on the used list pNewNode->pPrev = NULL; // the allocated node is always first in the list if (m_pFirstUsed == NULL) { pNewNode->pNext = NULL; // no other nodes } else { m_pFirstUsed->pPrev = pNewNode; // insert this at the head of the used list pNewNode->pNext = m_pFirstUsed; } m_pFirstUsed = pNewNode; } return reinterpret_cast(pNewNode); } // Free the given user type // For efficiency I don't check whether the user_data is a valid // pointer that was allocated. I may add some debug only checking // (To add the debug check you'd need to make sure the pointer is in // the m_pMemory area and is pointing at the start of a node) void free(USER_TYPE *user_data) { FSA_ELEMENT *pNode = reinterpret_cast(user_data); // manage used list, remove this node from it if (pNode->pPrev) { pNode->pPrev->pNext = pNode->pNext; } else { // this handles the case that we delete the first node in the used list m_pFirstUsed = pNode->pNext; } if (pNode->pNext) { pNode->pNext->pPrev = pNode->pPrev; } // add to free list if (m_pFirstFree == NULL) { // free list was empty m_pFirstFree = pNode; pNode->pPrev = NULL; pNode->pNext = NULL; } else { // Add this node at the start of the free list m_pFirstFree->pPrev = pNode; pNode->pNext = m_pFirstFree; m_pFirstFree = pNode; } } // For debugging this displays both lists (using the prev/next list pointers) void Debug() { printf("free list "); FSA_ELEMENT *p = m_pFirstFree; while (p) { printf("%x!%x ", p->pPrev, p->pNext); p = p->pNext; } printf("\n"); printf("used list "); p = m_pFirstUsed; while (p) { printf("%x!%x ", p->pPrev, p->pNext); p = p->pNext; } printf("\n"); } // Iterators USER_TYPE *GetFirst() { return reinterpret_cast(m_pFirstUsed); } USER_TYPE *GetNext(USER_TYPE *node) { return reinterpret_cast((reinterpret_cast(node))->pNext); } public: // data private: // methods private: // data FSA_ELEMENT *m_pFirstFree; FSA_ELEMENT *m_pFirstUsed; unsigned int m_MaxElements; FSA_ELEMENT *m_pMemory; }; ================================================ FILE: rltk/geometry.cpp ================================================ #include "geometry.hpp" #include #include namespace rltk { /* * From a given point x/y, project forward radius units (generally tiles) at an angle of degrees_radians degrees * (in radians). */ std::pair project_angle(const int &x, const int &y, const double &radius, const double °rees_radians) noexcept { return std::make_pair(static_cast(x + radius * std::cos(degrees_radians)), static_cast(y + radius * std::sin(degrees_radians))); } } ================================================ FILE: rltk/geometry.hpp ================================================ #pragma once /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * Random number generator class. */ #include #include #include namespace rltk { /* * From a given point x/y, project forward radius units (generally tiles) at an angle of degrees_radians degrees * (in radians). */ std::pair project_angle(const int &x, const int &y, const double &radius, const double °rees_radians) noexcept; /* * Provides a correct 2D distance between two points. */ inline float distance2d(const int &x1, const int &y1, const int &x2, const int &y2) noexcept { const float dx = (float)x1 - (float)x2; const float dy = (float)y1 - (float)y2; return std::sqrt((dx*dx) + (dy*dy)); } /* * Provides a fast 2D distance between two points, omitting the square-root; compare * with other squared distances. */ inline float distance2d_squared(const int &x1, const int &y1, const int &x2, const int &y2) noexcept { const float dx = (float)x1 - (float)x2; const float dy = (float)y1 - (float)y2; return (dx*dx) + (dy*dy); } /* * Provides 2D Manhattan distance between two points. */ inline float distance2d_manhattan(const int &x1, const int &y1, const int &x2, const int &y2) noexcept { const float dx = (float)x1 - (float)x2; const float dy = (float)y1 - (float)y2; return std::abs(dx) + std::abs(dy); } /* * Provides a correct 3D distance between two points. */ inline float distance3d(const int &x1, const int &y1, const int &z1, const int &x2, const int &y2, const int &z2) noexcept { const float dx = (float)x1 - (float)x2; const float dy = (float)y1 - (float)y2; const float dz = (float)z1 - (float)z2; return std::sqrt((dx*dx) + (dy*dy) + (dz*dz)); } /* * Provides a fast 3D distance between two points, omitting the square-root; compare * with other squared distances. */ inline float distance3d_squared(const int &x1, const int &y1, const int &z1, const int &x2, const int &y2, const int &z2) noexcept { float dx = (float)x1 - (float)x2; float dy = (float)y1 - (float)y2; float dz = (float)z1 - (float)z2; return (dx*dx) + (dy*dy) + (dz*dz); } /* * Provides Manhattan distance between two 3D points. */ inline float distance3d_manhattan(const int &x1, const int &y1, const int &z1, const int &x2, const int &y2, const int &z2) noexcept { const float dx = (float)x1 - (float)x2; const float dy = (float)y1 - (float)y2; const float dz = (float)z1 - (float)z2; return std::abs(dx) + std::abs(dy) + std::abs(dz); } /* * Perform a function for each line element between x1/y1 and x2/y2. We used to use Bresenham's line, * but benchmarking showed a simple float-based setup to be faster. */ inline void line_func(const int &x1, const int &y1, const int &x2, const int &y2, std::function &&func) noexcept { float x = static_cast(x1) + 0.5F; float y = static_cast(y1) + 0.5F; const float dest_x = static_cast(x2); const float dest_y = static_cast(y2); const float n_steps = distance2d(x1,y1,x2,y2); const int steps = static_cast(std::floor(n_steps) + 1); const float slope_x = (dest_x - x) / n_steps; const float slope_y = (dest_y - y) / n_steps; for (int i = 0; i < steps; ++i) { func(static_cast(x), static_cast(y)); x += slope_x; y += slope_y; } } /* * Perform a function for each line element between x1/y1/z1 and x2/y2/z2. Uses a 3D * implementation of Bresenham's line algorithm. * https://gist.github.com/yamamushi/5823518 */ template 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 { float x = static_cast(x1)+0.5F; float y = static_cast(y1)+0.5F; float z = static_cast(z1)+0.5F; float length = distance3d(x1, y1, z1, x2, y2, z2); int steps = static_cast(std::floor(length)); float x_step = (x - x2) / length; float y_step = (y - y2) / length; float z_step = (z - z2) / length; for (int i=0; i(std::floor(x)), static_cast(std::floor(y)), static_cast(std::floor(z))); } } /* * Perform a function for each line element between x1/y1 and x2/y2. We used to use Bresenham's algorithm, * but benchmarking showed that a simple float based vector was faster. */ template inline void line_func_cancellable(const int &x1, const int &y1, const int &x2, const int &y2, F &&func) noexcept { float x = static_cast(x1) + 0.5F; float y = static_cast(y1) + 0.5F; const float dest_x = static_cast(x2); const float dest_y = static_cast(y2); const float n_steps = distance2d(x1,y1,x2,y2); const int steps = static_cast(std::floor(n_steps) + 1); const float slope_x = (dest_x - x) / n_steps; const float slope_y = (dest_y - y) / n_steps; for (int i = 0; i < steps; ++i) { if (!func(static_cast(x), static_cast(y))) return; x += slope_x; y += slope_y; } } /* * Perform a function for each line element between x1/y1/z1 and x2/y2/z2. Uses a 3D * implementation of Bresenham's line algorithm. * https://gist.github.com/yamamushi/5823518 */ template 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 { float x = static_cast(x1)+0.5F; float y = static_cast(y1)+0.5F; float z = static_cast(z1)+0.5F; float length = distance3d(x1, y1, z1, x2, y2, z2); int steps = static_cast(std::floor(length)); float x_step = (x - x2) / length; float y_step = (y - y2) / length; float z_step = (z - z2) / length; for (int i=0; i(std::floor(x)), static_cast(std::floor(y)), static_cast(std::floor(z))); if (!keep_going) return; } } } ================================================ FILE: rltk/gui.cpp ================================================ #include "gui.hpp" #include #include #include namespace rltk { namespace gui_detail { std::vector> render_order; } void gui_t::on_resize(const int w, const int h) { screen_width = w; screen_height = h; for (auto it = layers.begin(); it != layers.end(); ++it) { it->second.on_resize(w, h); } } void gui_t::render(sf::RenderWindow &window) { for (auto l : gui_detail::render_order) { l.second->render(window); } } void gui_t::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, int order) { check_handle_uniqueness(handle); layers.emplace(std::make_pair(handle, layer_t(X, Y, W, H, font_name, resize_fun, has_background))); if (order == -1) { order = render_order; ++render_order; } gui_detail::render_order.push_back(std::make_pair(order, get_layer(handle))); std::sort(gui_detail::render_order.begin(), gui_detail::render_order.end(), [] (std::pair a, std::pair b) { return a.first < b.first; } ); } void gui_t::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) { check_handle_uniqueness(handle); layers.emplace(std::make_pair(handle, layer_t(true, X, Y, W, H, font_name, resize_fun))); if (order == -1) { order = render_order; ++render_order; } gui_detail::render_order.push_back(std::make_pair(order, get_layer(handle))); std::sort(gui_detail::render_order.begin(), gui_detail::render_order.end(), [] (std::pair a, std::pair b) { return a.first < b.first; } ); } void gui_t::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) { check_handle_uniqueness(handle); layers.emplace(std::make_pair(handle, layer_t(X, Y, W, H, resize_fun, owner_draw_fun))); if (order == -1) { order = render_order; ++render_order; } gui_detail::render_order.push_back(std::make_pair(order, get_layer(handle))); std::sort(gui_detail::render_order.begin(), gui_detail::render_order.end(), [] (std::pair a, std::pair b) { return a.first < b.first; } ); } void gui_t::delete_layer(const int handle) { gui_detail::render_order.erase(std::remove_if(gui_detail::render_order.begin(), gui_detail::render_order.end(), [&handle, this] (std::pair a) { if (a.second == this->get_layer(handle)) return true; return false; } ), gui_detail::render_order.end()); layers.erase(handle); } layer_t * gui_t::get_layer(const int handle) { auto finder = layers.find(handle); if (finder == layers.end()) throw std::runtime_error("Unknown layer handle: " + std::to_string(handle)); return &(finder->second); } } ================================================ FILE: rltk/gui.hpp ================================================ /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * Provides support for complicated GUIs. */ #pragma once #include #include #include #include #include #include "layer_t.hpp" namespace rltk { /* * The overall GUI - holds layers and handles render calls. Access via rltk::gui */ struct gui_t { public: gui_t(const int w, const int h) : screen_width(w), screen_height(h) {} void on_resize(const int w, const int h); void render(sf::RenderWindow &window); // Specialization for adding console layers 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); // Specialization for sparse layers 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); // Specialization for adding owner-draw layers 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); void delete_layer(const int handle); layer_t * get_layer(const int handle); private: int screen_width; int screen_height; int render_order = 0; std::unordered_map layers; inline void check_handle_uniqueness(const int handle) { auto finder = layers.find(handle); if (finder != layers.end()) throw std::runtime_error("Adding a duplicate layer handle: " + std::to_string(handle)); } }; } ================================================ FILE: rltk/gui_control_t.cpp ================================================ #include "gui_control_t.hpp" #include #include namespace rltk { void gui_static_text_t::render(virtual_terminal * console) { console->print(x, y, text, foreground, background); } void gui_border_box_t::render(virtual_terminal * console) { console->box(foreground, background, is_double); } void gui_checkbox_t::render(virtual_terminal * console) { console->set_char(x, y, vchar{'[', foreground, background}); if (checked) { console->set_char(x+1, y, vchar{'X', foreground, background}); } else { console->set_char(x+1, y, vchar{' ', foreground, background}); } console->print(x+2, y, "] " + label, foreground, background); } void gui_radiobuttons_t::render(virtual_terminal * console) { console->print(x, y, caption, foreground, background); int current_y = y+1; for (const radio &r : options) { console->set_char(x, current_y, vchar{'(', foreground, background}); if (r.checked) { console->set_char(x+1, current_y, vchar{'*', foreground, background}); } else { console->set_char(x+1, current_y, vchar{' ', foreground, background}); } console->print(x+2, current_y, ") " + r.label, foreground, background); ++current_y; } } void gui_hbar_t::render(virtual_terminal * console) { float fullness = (float)(value - min) / (float)max; float full_w_f = fullness * (float)w; std::size_t full_w = static_cast(full_w_f); std::stringstream ss; for (std::size_t i=0; i((w/2) - (tmp.size() / 2)); for (std::size_t i=0; i < std::min(tmp.size(),w); ++i) { s[i + start] = tmp[i]; } for (std::size_t i=0; iset_char(x+i, y, vchar{s[i], text_color, lerp(full_start, full_end, pct)}); } else { console->set_char(x+i, y, vchar{s[i], text_color, lerp(empty_start, empty_end, pct)}); } } } void gui_vbar_t::render(virtual_terminal * console) { float fullness = (float)(value - min) / (float)max; float full_h_f = fullness * (float)h; std::size_t full_h = static_cast(full_h_f); std::stringstream ss; for (std::size_t i=0; i((h/2) - (tmp.size() / 2)); for (std::size_t i=0; i < std::min(tmp.size(), h); ++i) { s[i + start] = tmp[i]; } for (std::size_t i=0; iset_char(x, y+i, vchar{s[i], text_color, lerp(full_start, full_end, pct)}); } else { console->set_char(x, y+i, vchar{s[i], text_color, lerp(empty_start, empty_end, pct)}); } } } void gui_listbox_t::render(virtual_terminal * console) { console->box(x, y, w+3, static_cast(items.size())+1, caption_fg, caption_bg, false); console->print(x+3, y, caption, caption_fg, caption_bg); console->set_char(x+1, y, vchar{180, caption_fg, caption_bg}); console->set_char(x+2, y, vchar{' ', caption_fg, caption_bg}); console->set_char(x+w, y, vchar{' ', caption_fg, caption_bg}); console->set_char(x+w+1, y, vchar{195, caption_fg, caption_bg}); int current_y = y+1; for (const list_item &item : items) { if (item.value == selected_value) { console->print(x+2, current_y, item.label, selected_fg, selected_bg); } else { console->print(x+2, current_y, item.label, item_fg, item_bg); } ++current_y; } } } ================================================ FILE: rltk/gui_control_t.hpp ================================================ /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * Retained mode GUI controls */ #pragma once #include "virtual_terminal.hpp" #include #include #include #include // TODO: Text box, display rich text, combo box, slider, spinner namespace rltk { /* * Base type for retained-mode GUI controls. */ struct gui_control_t { virtual void render(virtual_terminal * console)=0; virtual bool mouse_in_control(const int tx, const int ty) { return false; } // Callbacks std::function on_render_start = nullptr; std::function on_mouse_over = nullptr; std::function on_mouse_down = nullptr; std::function on_mouse_up = nullptr; }; struct gui_static_text_t : public gui_control_t { gui_static_text_t(const int X, const int Y, const std::string txt, const color_t fg, const color_t bg) : text(txt), x(X), y(Y), foreground(fg), background(bg) {} std::string text = ""; int x=0; int y=0; color_t foreground; color_t background; virtual void render(virtual_terminal * console) override; virtual bool mouse_in_control(const int tx, const int ty) override { return (tx >= x && tx <= x + (static_cast(text.size())) && ty==y); } }; struct gui_border_box_t : public gui_control_t { gui_border_box_t(const bool double_lines, const color_t fg, const color_t bg) : is_double(double_lines), foreground(fg), background(bg) {} bool is_double = false; color_t foreground; color_t background; virtual void render(virtual_terminal * console) override; }; struct gui_checkbox_t : public gui_control_t { 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) : checked(is_checked), label(text), foreground(fg), background(bg), x(X), y(Y) { on_mouse_down = [] (gui_control_t * ctrl, int tx, int ty) { gui_checkbox_t * me = static_cast(ctrl); me->click_started = true; }; on_mouse_up = [] (gui_control_t * ctrl, int tx, int ty) { gui_checkbox_t * me = static_cast(ctrl); if (me->click_started) { me->checked = !me->checked; } me->click_started = false; }; } bool checked = false; std::string label; color_t foreground; color_t background; int x=0; int y=0; bool click_started = false; virtual void render(virtual_terminal * console) override; virtual bool mouse_in_control(const int tx, const int ty) override { return (tx >= x && tx <= x + (static_cast(label.size())+4) && ty==y); } }; struct radio { bool checked; std::string label; int value; }; struct gui_radiobuttons_t : public gui_control_t { gui_radiobuttons_t(const int X, const int Y, const std::string heading, const color_t fg, const color_t bg, std::vector opts) : caption(heading), foreground(fg), background(bg), options(opts), x(X), y(Y) { width = static_cast(caption.size()); for (const radio &r : options) { if (width < static_cast(r.label.size())) width = static_cast(r.label.size()); if (r.checked) selected_value = r.value; } height = static_cast(options.size()) + 1; on_mouse_down = [] (gui_control_t * ctrl, int tx, int ty) { gui_radiobuttons_t * me = static_cast(ctrl); me->click_started = true; }; on_mouse_up = [] (gui_control_t * ctrl, int tx, int ty) { gui_radiobuttons_t * me = static_cast(ctrl); if (me->click_started) { const int option_number = (ty - me->y) -1; if (option_number >= 0 && option_number <= static_cast(me->options.size())) { me->selected_value = me->options[option_number].value; for (auto &r : me->options) { if (r.value == me->selected_value) { r.checked = true; } else { r.checked = false; } } } } me->click_started = false; }; } std::string caption; color_t foreground; color_t background; std::vector options; int x; int y; int width; int height; bool click_started = false; int selected_value = -1; virtual void render(virtual_terminal * console) override; virtual bool mouse_in_control(const int tx, const int ty) override { if (tx >= x && tx <= (x + width) && ty >= y && ty <= (y + height)) { return true; } return false; } }; struct gui_hbar_t : public gui_control_t { gui_hbar_t(const int X, const int Y, const int W, const int MIN, const int MAX, const int VAL, 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, std::string PREFIX = "") : x(X), y(Y), w(W), min(MIN), max(MAX), value(VAL), full_start(FULL_START), full_end(FULL_END), empty_start(EMPTY_START), empty_end(EMPTY_END), text_color(TEXT_COL), prefix(PREFIX) {} int x; int y; std::size_t w; int min; int max; int value; color_t full_start; color_t full_end; color_t empty_start; color_t empty_end; color_t text_color; std::string prefix; virtual void render(virtual_terminal * console) override; }; struct gui_vbar_t : public gui_control_t { gui_vbar_t(const int X, const int Y, const int H, const int MIN, const int MAX, const int VAL, 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, std::string PREFIX = "") : x(X), y(Y), h(H), min(MIN), max(MAX), value(VAL), full_start(FULL_START), full_end(FULL_END), empty_start(EMPTY_START), empty_end(EMPTY_END), text_color(TEXT_COL), prefix(PREFIX) {} int x; int y; std::size_t h; int min; int max; int value; color_t full_start; color_t full_end; color_t empty_start; color_t empty_end; color_t text_color; std::string prefix; virtual void render(virtual_terminal * console) override; }; struct list_item { int value; std::string label; }; struct gui_listbox_t : public gui_control_t { gui_listbox_t(const int X, const int Y, const int VAL, std::vector options, std::string label, const color_t label_fg, const color_t label_bg, const color_t ITEM_FG, const color_t ITEM_BG, const color_t sel_fg, const color_t sel_bg) : x(X), y(Y), selected_value(VAL), items(options), caption_fg(label_fg), caption_bg(label_bg), item_fg(ITEM_FG), item_bg(ITEM_BG), selected_fg(sel_fg), selected_bg(sel_bg) { caption = label; w = static_cast(caption.size()) + 2; for (const list_item &item : items) { if (w < static_cast(item.label.size())) w = static_cast(item.label.size()); } on_mouse_down = [] (gui_control_t * ctrl, int tx, int ty) { gui_listbox_t * me = static_cast(ctrl); me->click_started = true; }; on_mouse_up = [] (gui_control_t * ctrl, int tx, int ty) { gui_listbox_t * me = static_cast(ctrl); if (me->click_started) { const int option_number = (ty - me->y) -1; if (option_number >= 0 && option_number <= static_cast(me->items.size())) { me->selected_value = me->items[option_number].value; } } me->click_started = false; }; } int x; int y; int selected_value; std::vector items; std::string caption; color_t caption_fg; color_t caption_bg; color_t item_fg; color_t item_bg; color_t selected_fg; color_t selected_bg; int w = 0; bool click_started = false; virtual void render(virtual_terminal * console) override; virtual bool mouse_in_control(const int tx, const int ty) override { if (tx >= x && tx <= (x + w) && ty >= y && ty <= (y + static_cast(items.size())+1)) { return true; } return false; } }; } ================================================ FILE: rltk/input_handler.cpp ================================================ #include "input_handler.hpp" #include "scaling.hpp" #include #include namespace rltk { namespace state { bool window_focused = true; std::array mouse_button_pressed; int mouse_x = 0; int mouse_y = 0; } bool is_window_focused() { return rltk::state::window_focused; } void set_window_focus_state(const bool &s) { rltk::state::window_focused = s; } void reset_mouse_state() { std::fill(rltk::state::mouse_button_pressed.begin(), rltk::state::mouse_button_pressed.end(), false); rltk::state::mouse_x = 0; rltk::state::mouse_y = 0; } void set_mouse_button_state(const int button, const bool state) { rltk::state::mouse_button_pressed[button] = state; } bool get_mouse_button_state(const int button) { return rltk::state::mouse_button_pressed[button]; } void set_mouse_position(const int x, const int y) { rltk::state::mouse_x = static_cast(x / scale_factor); rltk::state::mouse_y = static_cast(y / scale_factor); } std::pair get_mouse_position() { return std::make_pair(rltk::state::mouse_x, rltk::state::mouse_y); } void enqueue_key_pressed(sf::Event &event) { emit(key_pressed_t{event}); } } ================================================ FILE: rltk/input_handler.hpp ================================================ #pragma once /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * Class for providing a simple input interface. * Provides functions that can be queried for the current state of * the input system. * */ #include #include #include "ecs.hpp" namespace rltk { /* Helper constants to represent mouse buttons */ namespace button { constexpr int LEFT = 0; constexpr int RIGHT = 1; constexpr int MIDDLE = 2; constexpr int SIDE1 = 3; constexpr int SIDE2 = 4; constexpr int WHEEL_UP = 5; constexpr int WHEEL_DOWN = 6; } /* Does the game window currently have focus? You might want to pause if it doesn't. */ extern bool is_window_focused(); /* Setter function for window focus */ extern void set_window_focus_state(const bool &s); /* Mouse state reset: clears all mouse state */ extern void reset_mouse_state(); /* Update the stored mouse position. Does not actually move the mouse. */ extern void set_mouse_position(const int x, const int y); extern std::pair get_mouse_position(); /* Mouse button state */ extern void set_mouse_button_state(const int button, const bool state); extern bool get_mouse_button_state(const int button); /* Keyboard queue */ extern void enqueue_key_pressed(sf::Event &event); struct key_pressed_t : base_message_t { public: key_pressed_t() {} key_pressed_t(sf::Event ev) : event(ev) {} sf::Event event; }; } ================================================ FILE: rltk/layer_t.cpp ================================================ #include "layer_t.hpp" #include "input_handler.hpp" namespace rltk { void layer_t::make_owner_draw_backing() { if (!backing) { backing = std::make_unique(); } backing->create(w, h); } void layer_t::on_resize(const int width, const int height) { resize_func(this, width, height); if (console && console->visible) { console->set_offset(x,y); console->resize_pixels(w, h); console->dirty = true; } else { make_owner_draw_backing(); } } void layer_t::render(sf::RenderWindow &window) { if (console) { if (!controls.empty()) { // Render start events for (auto it=controls.begin(); it != controls.end(); ++it) { if (it->second->on_render_start) { auto callfunc = it->second->on_render_start; callfunc(it->second.get()); } } int mouse_x, mouse_y; std::tie(mouse_x, mouse_y) = get_mouse_position(); if (mouse_x >= x && mouse_x <= (x+w) && mouse_y >= y && mouse_y <= (y+h)) { // Mouse over in here is possible. auto font_dimensions = console->get_font_size(); const int terminal_x = (mouse_x - x) / font_dimensions.first; const int terminal_y = (mouse_y - y) / font_dimensions.second; for (auto it=controls.begin(); it != controls.end(); ++it) { // Mouse over if (it->second->mouse_in_control(terminal_x, terminal_y)) { if (it->second->on_mouse_over) { auto callfunc = it->second->on_mouse_over; callfunc(it->second.get(), terminal_x, terminal_y); } // Mouse down and up if (get_mouse_button_state(button::LEFT) && it->second->on_mouse_down) { auto callfunc = it->second->on_mouse_down; callfunc(it->second.get(), terminal_x, terminal_y); } if (!get_mouse_button_state(button::LEFT) && it->second->on_mouse_up) { auto callfunc = it->second->on_mouse_up; callfunc(it->second.get(), terminal_x, terminal_y); } } } } for (auto it=controls.begin(); it != controls.end(); ++it) { it->second->render(console.get()); } } console->render(window); } else if (sconsole) { sconsole->render(window); } else { if (!backing) make_owner_draw_backing(); backing->clear(sf::Color(0,0,0,0)); owner_draw_func(this, *backing); backing->display(); sf::Sprite compositor(backing->getTexture()); compositor.move(static_cast(x), static_cast(y)); window.draw(sf::Sprite(compositor)); } } void resize_fullscreen(rltk::layer_t * l, int w, int h) { // Use the whole window l->w = w; l->h = h; } } ================================================ FILE: rltk/layer_t.hpp ================================================ /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * Layer type used by the GUI */ #pragma once #include "virtual_terminal.hpp" #include "virtual_terminal_sparse.hpp" #include "gui_control_t.hpp" #include namespace rltk { /* * A renderable layer. You won't use this type directly. */ struct layer_t { /* This specialization is for generic consoles */ 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) : x(X), y(Y), w(W), h(H), font(font_name), resize_func(resize_fun), has_background(render_background) { console = std::make_unique(font_name, x, y, has_background); console->resize_pixels(w, h); } /* This specialization is for sparse consoles */ layer_t(bool sparse, const int X, const int Y, const int W, const int H, std::string font_name, std::function resize_fun) : x(X), y(Y), w(W), h(H), font(font_name), resize_func(resize_fun) { // Sparse is unusued, but is there to differentiate the signature. sconsole = std::make_unique(font_name, x, y); sconsole->resize_pixels(w, h); } /* This specialization is for owner-draw panels */ layer_t(const int X, const int Y, const int W, const int H, std::function resize_fun, std::function owner_draw_fun) : x(X), y(Y), w(W), h(H), resize_func(resize_fun), owner_draw_func(owner_draw_fun) { } // The bounding box of the layer int x; int y; int w; int h; // Font tag - used only if there is a console std::string font; // Callbacks: // resize_func is called when the window changes size. It receives the WINDOW dimensions - it's up to you to // determine how to lay things out. std::function resize_func; // Passed through to virtual console; if false then no background will be rendered (helpful for text overlays) bool has_background; // owner_draw_func is used only for owner draw layers, and is called at render time. std::function owner_draw_func; // If a console is present, this is it. std::unique_ptr console; // If it has a sparse console, this is it. std::unique_ptr sconsole; // If retained-mode controls are present, they are in here. std::unordered_map> controls; // Used for owner-draw layers. We need to render to texture and then compose to: // a) permit threading, should you so wish (so there is a single composite run) // b) allow the future "effects" engine to run. std::unique_ptr backing; // Used by the owner-draw code to ensure that a texture is available for use void make_owner_draw_backing(); // Called by GUI when a resize event occurs. void on_resize(const int width, const int height); // Called by GUI when a render event occurs. void render(sf::RenderWindow &window); // Retained Mode Controls template T * control(const int handle) { auto finder = controls.find(handle); if (finder == controls.end()) throw std::runtime_error("Unknown GUI control handle: " + std::to_string(handle)); return static_cast(finder->second.get()); } gui_control_t * control(const int handle) { auto finder = controls.find(handle); if (finder == controls.end()) throw std::runtime_error("Unknown GUI control handle: " + std::to_string(handle)); return finder->second.get(); } inline void remove_control(const int handle) { controls.erase(handle); } inline void check_handle_uniqueness(const int handle) { auto finder = controls.find(handle); if (finder != controls.end()) throw std::runtime_error("Adding a duplicate control handle: " + std::to_string(handle)); } 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) { check_handle_uniqueness(handle); controls.emplace(handle, std::make_unique(x, y, text, fg, bg)); } inline void add_boundary_box(const int handle, const bool double_lines, const color_t fg, const color_t bg) { check_handle_uniqueness(handle); controls.emplace(handle, std::make_unique(double_lines, fg, bg)); } 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) { check_handle_uniqueness(handle); controls.emplace(handle, std::make_unique(x, y, checked, label, fg, bg)); } 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) { check_handle_uniqueness(handle); controls.emplace(handle, std::make_unique(x, y, caption, fg, bg, opts)); } 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, 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="") { check_handle_uniqueness(handle); controls.emplace(handle, std::make_unique(X, Y, W, MIN, MAX, VAL, FULL_START, FULL_END, EMPTY_START, EMPTY_END, TEXT_COL, prefix)); } 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, 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="") { check_handle_uniqueness(handle); controls.emplace(handle, std::make_unique(X, Y, H, MIN, MAX, VAL, FULL_START, FULL_END, EMPTY_START, EMPTY_END, TEXT_COL, prefix)); } inline void add_listbox(const int handle, const int X, const int Y, const int VAL, std::vector options, std::string label, const color_t label_fg, const color_t label_bg, const color_t ITEM_FG, const color_t ITEM_BG, const color_t sel_fg, const color_t sel_bg) { check_handle_uniqueness(handle); controls.emplace(handle, std::make_unique(X, Y, VAL, options, label, label_fg, label_bg, ITEM_FG, ITEM_BG, sel_fg, sel_bg)); } inline void clear_gui() { controls.clear(); } }; // Convenience: ready-made function to resize to use the whole screen. extern void resize_fullscreen(rltk::layer_t * l, int w, int h); } ================================================ FILE: rltk/path_finding.hpp ================================================ #pragma once /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * Path finding - interface to the A-Star system */ #include "astar.hpp" #include "geometry.hpp" #include #include #include #include namespace rltk { // Template class used to forward to specialize the algorithm to the user's map format and // and behaviors defined in navigator_t. This avoids the library mandating what your map // looks like. template class map_search_node { public: location_t pos; map_search_node() {} map_search_node(location_t loc) : pos(loc) {} float GoalDistanceEstimate(map_search_node &goal) { float result = navigator_t::get_distance_estimate(pos, goal.pos); //std::cout << "GoalDistanceEstimate called (" << result << ").\n"; return result; } bool IsGoal(map_search_node &node_goal) { bool result = navigator_t::is_goal(pos, node_goal.pos); //std::cout << "IsGoal called (" << result << ").\n"; return result; } bool GetSuccessors(AStarSearch> * a_star_search, map_search_node * parent_node) { //std::cout << "GetSuccessors called.\n"; std::vector successors; if (parent_node != nullptr) { navigator_t::get_successors(parent_node->pos, successors); } else { throw std::runtime_error("Null parent error."); } for (location_t loc : successors) { map_search_node tmp(loc); //std::cout << " --> " << loc.x << "/" << loc.y << "\n"; a_star_search->AddSuccessor( tmp ); } return true; } float GetCost(map_search_node &successor) { float result = navigator_t::get_cost(pos, successor.pos); //std::cout << "GetCost called (" << result << ").\n"; return result; } bool IsSameState(map_search_node &rhs) { bool result = navigator_t::is_same_state(pos, rhs.pos); //std::cout << "IsSameState called (" << result << ").\n"; return result; } }; // Template class used to define what a navigation path looks like template struct navigation_path { bool success = false; location_t destination; std::deque steps; }; /* * find_path_3d implements A*, and provides an optimization that scans a 3D Bresenham line at the beginning * to check for a simple line-of-sight (and paths along it). * * We jump through a few hoops to make sure that it will work with whatever map format you choose to use, * hence: it requires that the navigator_t class provide: * - get_x, get_y_, get_z - to translate X/Y/Z into whatever name the user wishes to utilize. * - get_xyz - returns a location_t given X/Y/Z co-ordinates. */ template std::shared_ptr> find_path_3d(const location_t start, const location_t end) { { std::shared_ptr> result = std::shared_ptr>(new navigation_path()); result->success = true; 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) { location_t step = navigator_t::get_xyz(X,Y, Z); if (result->success and navigator_t::is_walkable(step)) { result->steps.push_back(step); } else { result->success = false; } }); if (result->success) { return result; } } AStarSearch> a_star_search; map_search_node a_start(start); map_search_node a_end(end); a_star_search.SetStartAndGoalStates(a_start, a_end); unsigned int search_state; unsigned int search_steps = 0; do { search_state = a_star_search.SearchStep(); ++search_steps; } while (search_state == AStarSearch>::SEARCH_STATE_SEARCHING); if (search_state == AStarSearch>::SEARCH_STATE_SUCCEEDED) { std::shared_ptr> result = std::shared_ptr>(new navigation_path()); result->destination = end; map_search_node * node = a_star_search.GetSolutionStart(); for (;;) { node = a_star_search.GetSolutionNext(); if (!node) break; result->steps.push_back(node->pos); } a_star_search.FreeSolutionNodes(); a_star_search.EnsureMemoryFreed(); result->success = true; return result; } std::shared_ptr> result = std::make_shared>(); a_star_search.EnsureMemoryFreed(); return result; } /* * find_path_2d implements A*, and provides an optimization that scans a 2D Bresenham line at the beginning * to check for a simple line-of-sight (and paths along it). * * We jump through a few hoops to make sure that it will work with whatever map format you choose to use, * hence: it requires that the navigator_t class provide: * - get_x, get_y - to translate X/Y/Z into whatever name the user wishes to utilize. * - get_xy - returns a location_t given X/Y/Z co-ordinates. */ template std::shared_ptr> find_path_2d(const location_t start, const location_t end) { { std::shared_ptr> result = std::shared_ptr>(new navigation_path()); result->success = true; 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) { location_t step = navigator_t::get_xy(X,Y); if (result->success && navigator_t::is_walkable(step)) { result->steps.push_back(step); } else { result->success = false; } }); if (result->success) { return result; } } AStarSearch> a_star_search; map_search_node a_start(start); map_search_node a_end(end); a_star_search.SetStartAndGoalStates(a_start, a_end); unsigned int search_state; unsigned int search_steps = 0; do { search_state = a_star_search.SearchStep(); ++search_steps; } while (search_state == AStarSearch>::SEARCH_STATE_SEARCHING); if (search_state == AStarSearch>::SEARCH_STATE_SUCCEEDED) { std::shared_ptr> result = std::shared_ptr>(new navigation_path()); result->destination = end; map_search_node * node = a_star_search.GetSolutionStart(); for (;;) { node = a_star_search.GetSolutionNext(); if (!node) break; result->steps.push_back(node->pos); } a_star_search.FreeSolutionNodes(); a_star_search.EnsureMemoryFreed(); result->success = true; return result; } std::shared_ptr> result = std::make_shared>(); a_star_search.EnsureMemoryFreed(); return result; } /* * Implements a simple A-Star path, with no line-search optimization. This has the benefit of avoiding * requiring as much additional translation between the template and your preferred map format, at the * expense of being potentially slower for some paths. */ template std::shared_ptr> find_path(const location_t start, const location_t end) { AStarSearch> a_star_search; map_search_node a_start(start); map_search_node a_end(end); a_star_search.SetStartAndGoalStates(a_start, a_end); unsigned int search_state; std::size_t search_steps = 0; do { search_state = a_star_search.SearchStep(); ++search_steps; } while (search_state == AStarSearch>::SEARCH_STATE_SEARCHING); if (search_state == AStarSearch>::SEARCH_STATE_SUCCEEDED) { std::shared_ptr> result = std::shared_ptr>(new navigation_path()); result->destination = end; map_search_node * node = a_star_search.GetSolutionStart(); for (;;) { node = a_star_search.GetSolutionNext(); if (!node) break; result->steps.push_back(node->pos); } a_star_search.FreeSolutionNodes(); a_star_search.EnsureMemoryFreed(); result->success = true; return result; } std::shared_ptr> result = std::make_shared>(); a_star_search.EnsureMemoryFreed(); return result; } } ================================================ FILE: rltk/perlin_noise.cpp ================================================ #include "perlin_noise.hpp" #include #include #include #include #include namespace rltk { // Initialize with the reference values for the permutation vector perlin_noise::perlin_noise() { // Initialize the permutation vector with the reference values p = { 151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142, 8,99,37,240,21,10,23,190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117, 35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71, 134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41, 55,46,245,40,244,102,143,54, 65,25,63,161,1,216,80,73,209,76,132,187,208, 89, 18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226, 250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182, 189,28,42,223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246, 97,228,251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239, 107,49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254, 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180 }; // Duplicate the permutation vector p.insert(p.end(), p.begin(), p.end()); } // Generate a new permutation vector based on the value of seed perlin_noise::perlin_noise(unsigned int seed) { p.resize(256); // Fill p with values from 0 to 255 std::iota(p.begin(), p.end(), 0); // Initialize a random engine with seed std::default_random_engine engine(seed); // Suffle using the above random engine std::shuffle(p.begin(), p.end(), engine); // Duplicate the permutation vector p.insert(p.end(), p.begin(), p.end()); } double perlin_noise::noise(double x, double y, double z) const noexcept { // Find the unit cube that contains the point int X = (int) floor(x) & 255; int Y = (int) floor(y) & 255; int Z = (int) floor(z) & 255; // Find relative x, y,z of point in cube x -= floor(x); y -= floor(y); z -= floor(z); // Compute fade curves for each of x, y, z double u = fade(x); double v = fade(y); double w = fade(z); // Hash coordinates of the 8 cube corners int A = p[X] + Y; int AA = p[A] + Z; int AB = p[A + 1] + Z; int B = p[X + 1] + Y; int BA = p[B] + Z; int BB = p[B + 1] + Z; // Add blended results from 8 corners of cube 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)))); return (res + 1.0)/2.0; } double perlin_noise::noise_octaves(double x, double y, double z, int octaves, double persistence, double frequency) const noexcept { double total = 0; double amplitude = 1; double maxValue = 0; // Used for normalizing result to 0.0 - 1.0 for(int i=0;i namespace rltk { class perlin_noise { // The permutation vector std::vector p; public: // Initialize with the reference values for the permutation vector perlin_noise(); // Generate a new permutation vector based on the value of seed perlin_noise(unsigned int seed); // Get a noise value, for 2D images z can have any value double noise(double x, double y, double z) const noexcept; double noise_octaves(double x, double y, double z, int octaves, double persistence, double frequency) const noexcept; private: double fade(double t) const noexcept; double lerp(double t, double a, double b) const noexcept; double grad(int hash, double x, double y, double z) const noexcept; }; } ================================================ FILE: rltk/rexspeeder.cpp ================================================ #include "rexspeeder.hpp" extern "C" { #include } #include namespace rltk { //===========================================================================================================// // Safe I/O (where "safe" means "will throw errors") // // // // These functions will throw an error message from gzerror, and set errno to the error code. // //===========================================================================================================// inline std::runtime_error make_rexception(gzFile g) { /*The exception creation is a bit verbose.*/ int errnum = 0; const char* errstr = gzerror(g, &errnum); return std::runtime_error(std::to_string(errnum) + std::string(" ") + std::string(errstr)); } static void s_gzread(gzFile g, voidp buf, unsigned int len) { if (gzread(g, buf, len) > 0) return; /*We expect to read past the end of the file after the last layer.*/ if (gzeof(g)) return; throw make_rexception(g); } static void s_gzwrite(gzFile g, voidp buf, unsigned int len) { if (gzwrite(g, buf, len) > 0) return; throw make_rexception(g); } static gzFile s_gzopen(const std::string filename, const char* permissions) { gzFile g = gzopen(filename.c_str(), permissions); if (g != Z_NULL) return g; int err = 0; const char* errstr = gzerror(g, &err); if (err == 0) { /*Assume the file simply didn't exist.*/ std::string s("File " + filename + " does not exist."); throw std::runtime_error(s); } throw std::runtime_error(std::string(errstr)); } namespace xp { //===========================================================================================================// // Loading an xp file // //===========================================================================================================// rex_sprite::rex_sprite(std::string const & filename) { typedef void* vp; //Number of bytes in a tile. Not equal to sizeof(RexTile) due to padding. const int tileLen = 10; gzFile gz; try { gz = s_gzopen(filename.c_str(), "rb"); s_gzread(gz, (vp)&version, sizeof(version)); s_gzread(gz, (vp)&num_layers, sizeof(num_layers)); s_gzread(gz, (vp)&width, sizeof(width)); s_gzread(gz, (vp)&height, sizeof(height)); layers.resize(num_layers); for (int i = 0; i < num_layers; i++) layers[i] = rex_layer(width, height); for (int layer_index = 0; layer_index < num_layers; layer_index++) { for (int i = 0; i < width*height; ++i) s_gzread(gz, get_tile(layer_index, i), tileLen); //The layer and height information is repeated. //This is expected to read off the end after the last layer. s_gzread(gz, (vp)&width, sizeof(width)); s_gzread(gz, (vp)&height, sizeof(height)); } } catch (...) { throw; } gzclose(gz); } //===========================================================================================================// // Saving an xp file // //===========================================================================================================// void rex_sprite::save(std::string const & filename) { typedef void* vp; //Number of bytes in a tile. Not equal to sizeof(RexTile) due to padding. const int tileLen = 10; try { gzFile gz = s_gzopen(filename.c_str(), "wb"); s_gzwrite(gz, (vp)&version, sizeof(version)); s_gzwrite(gz, (vp)&num_layers, sizeof(num_layers)); for (int layer = 0; layer < num_layers; ++layer) { s_gzwrite(gz, (vp)&width, sizeof(width)); s_gzwrite(gz, (vp)&height, sizeof(height)); for (int i = 0; i < width*height; ++i) //Note: not "sizeof(RexTile)" because of padding. s_gzwrite(gz, (vp)get_tile(layer,i), tileLen); } gzflush(gz, Z_FULL_FLUSH); gzclose(gz); } catch (...) { throw; } } //===========================================================================================================// // Constructors / Destructors // //===========================================================================================================// rex_sprite::rex_sprite(int _version, int _width, int _height, int _num_layers) :version(_version), width(_width), height(_height), num_layers(_num_layers) { layers.resize(num_layers); //All layers above the first are set transparent. for (int l = 1; l < num_layers; l++) { for (int i = 0; i < width*height; ++i) { rltk::vchar t = transparent_tile(); set_tile(l, i, t); } } } //===========================================================================================================// // Utility Functions // //===========================================================================================================// void rex_sprite::flatten() { if (num_layers == 1) return; //Paint the last layer onto the second-to-last for (int i = 0; i < width*height; ++i) { rltk::vchar* overlay = get_tile(num_layers - 1, i); if (!is_transparent(overlay)) { *get_tile(num_layers - 2, i) = *overlay; } } //Remove the last layer --num_layers; //Recurse flatten(); } } } ================================================ FILE: rltk/rexspeeder.hpp ================================================ #pragma once /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * REXPaint 1.02 file reader. Credit to https://github.com/pyridine/REXSpeeder for the original. */ /*For version 1.02 of REXPaint*/ #include #include #include #include #include "vchar.hpp" namespace rltk { namespace xp { //REXpaint identifies transparent tiles by setting their background color to 255,0,255. //You may want to check this for each tile before drawing or converting a RexFile. //(By default, no tile in the first layer is transaprent). inline bool is_transparent(const rltk::vchar * tile) { //This might be faster than comparing with transparentTile(), despite it being a constexpr return (tile->background.r == 255 && tile->background.g == 0 && tile->background.b == 255); } //Returns a transparent tile. inline rltk::vchar transparent_tile() { return rltk::vchar{0, 0, 0, 0, 255, 0, 255}; } struct rex_layer { rex_layer() {} rex_layer(int width, int height) { tiles.resize(width * height); } ~rex_layer() { tiles.clear(); } std::vector tiles; }; class rex_sprite { public: //Load an .xp file into a new RexFile. //Note: May throw a const char* error message and set errno. //Both the error message and the value of errno may be as gzopen or gzread set them. //It may also throw an error with code REXSPEEDER_FILE_DOES_NOT_EXIST. //Will not throw an error if the file specified by `filename` is not zlib compressed. rex_sprite(std::string const& filename); //Save this RexFile into a valid .xp file that RexPaint can load (if the ".xp" suffix is present). //Note: May throw a const char* error message and set errno. //Both the error message and the value of errno may be as gzopen or gzwrite set them. void save(std::string const& filename); //Create a blank RexFile with the specified attributes. //Layers above the first will be made of transparent tiles. rex_sprite(int _version, int _width, int _height, int _num_layers); //Image attributes inline int get_version() { return version; }; inline int get_width() { return width; }; inline int get_height() { return height; }; inline int get_num_layers() { return num_layers; }; //Returns a pointer to a single tile specified by layer, x coordinate, y coordinate. //0,0 is the top-left corner. inline rltk::vchar* get_tile(int layer, int x, int y) { return &layers[layer].tiles[y + (x * height)]; }; //Returns a pointer to a single tile specified by layer and the actual index into the array. //Useful for iterating through a whole layer in one go for coordinate-nonspecific tasks. inline rltk::vchar* get_tile(int layer, int index) { return &layers[layer].tiles[index]; }; //Replaces the data for a tile. Not super necessary, but might save you a couple lines. inline void set_tile(int layer, int x, int y, rltk::vchar& val) { *get_tile(layer, x, y) = val; }; //Replaces the data for a tile. Not super necessary, but might save you a couple lines. inline void set_tile(int layer, int i, rltk::vchar& val) { *get_tile(layer, i) = val; }; //Combines all the layers of the image into one layer. //Respects transparency. void flatten(); private: //Image properties int version; int width, height, num_layers; std::vector layers; //layers[0] is the first layer. //Forbid default construction. rex_sprite(); }; } } ================================================ FILE: rltk/rltk.cpp ================================================ #include "rltk.hpp" #include "texture.hpp" #include namespace rltk { std::unique_ptr main_window; std::unique_ptr console; std::unique_ptr gui; namespace main_detail { bool use_root_console; bool taking_screenshot = false; std::string screenshot_filename = ""; } sf::RenderWindow * get_window() { return main_window.get(); } gui_t * get_gui() { return gui.get(); } void init(const config_simple &config) { register_font_directory(config.font_path); bitmap_font * font = get_font(config.root_font); if (!config.fullscreen) { main_window = std::make_unique(sf::VideoMode(config.width * font->character_size.first, config.height * font->character_size.second), config.window_title, sf::Style::Titlebar | sf::Style::Resize | sf::Style::Close); } else { sf::VideoMode desktop = sf::VideoMode::getDesktopMode(); main_window = std::make_unique(sf::VideoMode(desktop.width, desktop.height, desktop.bitsPerPixel) , config.window_title, sf::Style::Fullscreen); } main_window->setVerticalSyncEnabled(true); main_detail::use_root_console = true; console = std::make_unique(config.root_font, 0, 0); sf::Vector2u size_pixels = main_window->getSize(); console->resize_pixels(size_pixels.x, size_pixels.y); } void init(const config_simple_px &config) { register_font_directory(config.font_path); if (!config.fullscreen) { main_window = std::make_unique(sf::VideoMode(config.width_px, config.height_px), config.window_title, sf::Style::Titlebar | sf::Style::Resize | sf::Style::Close); } else { sf::VideoMode desktop = sf::VideoMode::getDesktopMode(); main_window = std::make_unique(sf::VideoMode(desktop.width, desktop.height, desktop.bitsPerPixel) , config.window_title, sf::Style::Fullscreen); } main_window->setVerticalSyncEnabled(true); main_detail::use_root_console = true; console = std::make_unique(config.root_font, 0, 0); sf::Vector2u size_pixels = main_window->getSize(); console->resize_pixels(size_pixels.x, size_pixels.y); } void init(const config_advanced &config) { register_font_directory(config.font_path); sf::ContextSettings settings; settings.antialiasingLevel = 4; settings.depthBits = 32; settings.stencilBits = 8; if (!config.fullscreen) { main_window = std::make_unique(sf::VideoMode(config.width_px, config.height_px), config.window_title, sf::Style::Titlebar | sf::Style::Resize | sf::Style::Close, settings); } else { sf::VideoMode desktop = sf::VideoMode::getDesktopMode(); main_window = std::make_unique(sf::VideoMode(desktop.width, desktop.height, desktop.bitsPerPixel) , config.window_title, sf::Style::Fullscreen, settings); } main_window->setVerticalSyncEnabled(true); main_detail::use_root_console = false; gui = std::make_unique(config.width_px, config.height_px); } std::function optional_event_hook = nullptr; std::function optional_display_hook = nullptr; void run(std::function on_tick) { reset_mouse_state(); double duration_ms = 0.0; while (main_window->isOpen()) { clock_t start_time = clock(); sf::Event event; while (main_window->pollEvent(event)) { bool handle_events = true; if (optional_event_hook) { handle_events = optional_event_hook(event); } if (handle_events) { if (event.type == sf::Event::Closed) { main_window->close(); } else if (event.type == sf::Event::Resized) { main_window->setView(sf::View(sf::FloatRect(0.f, 0.f, static_cast(event.size.width), static_cast(event.size.height)))); if (main_detail::use_root_console) console->resize_pixels(event.size.width, event.size.height); if (gui) gui->on_resize(event.size.width, event.size.height); } else if (event.type == sf::Event::LostFocus) { set_window_focus_state(false); } else if (event.type == sf::Event::GainedFocus) { set_window_focus_state(true); } else if (event.type == sf::Event::MouseButtonPressed) { set_mouse_button_state(event.mouseButton.button, true); } else if (event.type == sf::Event::MouseButtonReleased) { set_mouse_button_state(event.mouseButton.button, false); } else if (event.type == sf::Event::MouseMoved) { set_mouse_position(event.mouseMove.x, event.mouseMove.y); } else if (event.type == sf::Event::KeyPressed) { enqueue_key_pressed(event); } else if (event.type == sf::Event::MouseWheelMoved) { if (event.mouseWheel.delta < 0) { set_mouse_button_state(button::WHEEL_UP, true); } else if (event.mouseWheel.delta > 0) { set_mouse_button_state(button::WHEEL_DOWN, true); } } } } main_window->clear(); //if (main_detail::use_root_console) console->clear(); on_tick(duration_ms); if (main_detail::use_root_console) { console->render(*main_window); } else { gui->render(*main_window); } if (optional_display_hook) { main_window->pushGLStates(); main_window->resetGLStates(); optional_display_hook(); main_window->popGLStates(); } main_window->display(); if (main_detail::taking_screenshot) { sf::Image screen = rltk::get_window()->capture(); screen.saveToFile(main_detail::screenshot_filename); main_detail::screenshot_filename = ""; main_detail::taking_screenshot = false; } duration_ms = ((clock() - start_time) * 1000.0) / CLOCKS_PER_SEC; } } void request_screenshot(const std::string &filename) { main_detail::taking_screenshot = true; main_detail::screenshot_filename = filename; } } ================================================ FILE: rltk/rltk.hpp ================================================ #pragma once /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. */ #include #include #include #include "font_manager.hpp" #include "texture_resources.hpp" #include "virtual_terminal.hpp" #include "colors.hpp" #include "rng.hpp" #include "geometry.hpp" #include "path_finding.hpp" #include "input_handler.hpp" #include "visibility.hpp" #include "gui.hpp" #include "ecs.hpp" #include "perlin_noise.hpp" #include "serialization_utils.hpp" #include "rexspeeder.hpp" #include "scaling.hpp" namespace rltk { /* * Defines a simple configuration to get most people started. 8x8 font, window size 1024x768. */ struct config_simple_px { config_simple_px(const std::string fontpath, const int width=1024, const int height=768, const std::string title="RLTK Roguelike", const std::string font="8x8", bool full_screen=false) : font_path(fontpath), width_px(width), height_px(height), window_title(title), root_font(font), fullscreen(full_screen) {} const std::string font_path; const int width_px; const int height_px; const std::string window_title; const std::string root_font; const bool fullscreen; }; /* * Defines a simple configuration to get most people started. 8x8 font, window size 128x96 (which happens to be 1024x768) */ struct config_simple { config_simple(const std::string fontpath, const int width_term=128, const int height_term=96, const std::string title="RLTK Roguelike", const std::string font="8x8", bool full_screen=false) : font_path(fontpath), width(width_term), height(height_term), window_title(title), root_font(font), fullscreen(full_screen) {} const std::string font_path; const int width; const int height; const std::string window_title; const std::string root_font; const bool fullscreen; }; /* * Defines an advanced configuration. No root console, so it is designed for the times you want to build your own GUI. */ struct config_advanced { config_advanced(const std::string fontpath, const int width=1024, const int height=768, const std::string title="RLTK Roguelike", bool full_screen=false) : font_path(fontpath), width_px(width), height_px(height), window_title(title), fullscreen(full_screen) {} const std::string font_path; const int width_px; const int height_px; const std::string window_title; const bool fullscreen; }; /* * Bootstrap the system with a simple configuration, specified in pixels. * This variant uses terminal coordinates - so specify 80x40 as size and it will scale with the terminal size. */ void init(const config_simple &config); /* * Bootstrap the system with a simple configuration, specified in pixels. * This variant uses screen coordinates. */ void init(const config_simple_px &config); /* * Bootstrap the system with a simple configuration, specified in pixels. * This variant doesn't set up much automatically; it's designed for the times you want to define your own GUI. */ void init(const config_advanced &config); /* * The main run loop. Calls on_tick each frame. Window can be initially defined with width/height/title, but these * have sane defaults to get you started. */ void run(std::function on_tick); /* * For rendering to the console */ extern std::unique_ptr console; /* * In case you want to do some SFML stuff yourself, this provides a pointer to the render window. */ sf::RenderWindow * get_window(); /* * For GUI manipulation */ extern std::unique_ptr gui; /* * Convenience function to quickly get a GUI layer */ inline layer_t * layer(const int &handle) { return gui->get_layer(handle); } inline virtual_terminal * term(const int &handle) { return gui->get_layer(handle)->console.get(); } inline virtual_terminal_sparse * sterm(const int &handle) { return gui->get_layer(handle)->sconsole.get(); } /* Request a screenshot */ void request_screenshot(const std::string &filename); /* Lifecycle hooks, for example to integrate ImGui with your application. */ extern std::function optional_event_hook; extern std::function optional_display_hook; } ================================================ FILE: rltk/rng.cpp ================================================ #include "rng.hpp" #include #include namespace rltk { int random_number_generator::fastrand() { g_seed = (214013 * g_seed + 2531011); return (g_seed >> 16) & 0x7FFF; } random_number_generator::random_number_generator() { initial_seed = time(nullptr); g_seed = initial_seed; } random_number_generator::random_number_generator(const int seed) { initial_seed = seed; g_seed = initial_seed; } random_number_generator::random_number_generator(const std::string seed) { std::hash hash_func; initial_seed = hash_func(seed); g_seed = initial_seed; } int random_number_generator::roll_dice(const int &n, const int &d) { int total = 0; for (int i = 0; i < n; ++i) { total += ((fastrand() % d)+1); } return total; } } ================================================ FILE: rltk/rng.hpp ================================================ #pragma once /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * Random number generator class. */ #include namespace rltk { class random_number_generator { public: random_number_generator(); random_number_generator(const int seed); random_number_generator(const std::string seed); int roll_dice(const int &n, const int &d); int initial_seed; private: int fastrand(); int g_seed; }; } ================================================ FILE: rltk/scaling.cpp ================================================ #include "scaling.hpp" namespace rltk { float scale_factor = 1.0f; } ================================================ FILE: rltk/scaling.hpp ================================================ #pragma once namespace rltk { extern float scale_factor; } ================================================ FILE: rltk/serialization_utils.hpp ================================================ #pragma once #include #include #include #include #include #include #include "color_t.hpp" #include "xml.hpp" #include "vchar.hpp" namespace rltk { template inline void component_to_xml(xml_node * c, const T... args); namespace serial { // Forward declarations template inline void component_to_xml(xml_node * c, const T... args); template void _component_to_xml(xml_node * c, First arg, Rest... args); // Final render to string template inline std::string to_string(T val) { std::stringstream ss; ss << val; return ss.str(); } template <> inline std::string to_string(uint8_t val) { std::stringstream ss; ss << static_cast(val); return ss.str(); } template void __component_to_xml(xml_node *c, T arg) { c->add_value(arg.first, rltk::serial::to_string(arg.second)); } template< class T > struct has_to_xml_method { typedef char(&YesType)[1]; typedef char(&NoType)[2]; template< class, class > struct Sfinae; template< class T2 > static YesType Test( Sfinae().to_xml( std::declval() ))> * ); template< class T2 > static NoType Test( ... ); static const bool value = sizeof(Test(0))==sizeof(YesType); }; /* Uses enable_if to determine if it should call a struct's load method, or just pass through to * a more specific Deserialize */ template struct _component_to_xml_check_for_to_xml { template typename std::enable_if< has_to_xml_method::value, void >::type test(xml_node * c, std::pair &arg) { arg.second.to_xml(c); } template typename std::enable_if< !has_to_xml_method::value, void >::type test(xml_node * c, std::pair &arg) { __component_to_xml(c, arg); } }; template inline void _component_to_xml(xml_node * c, std::pair arg) { _component_to_xml_check_for_to_xml temp; temp.test(c, arg); } template<> inline void _component_to_xml(xml_node * c, std::pair arg) { // Serialize r/g/b components xml_node * ch = c->add_node(arg.first); ch->add_value("r", std::to_string(arg.second.r)); ch->add_value("g", std::to_string(arg.second.g)); ch->add_value("b", std::to_string(arg.second.b)); } template<> inline void _component_to_xml(xml_node * c, std::pair arg) { // Serialize r/g/b components xml_node * ch = c->add_node(arg.first); ch->add_value("glyph", std::to_string(arg.second.glyph)); _component_to_xml(ch, std::make_pair("foreground", arg.second.foreground)); _component_to_xml(ch, std::make_pair("background", arg.second.background)); } template inline void _component_to_xml(xml_node *c, std::pair> arg) { xml_node * vec = c->add_node(arg.first); for (T item : arg.second) { rltk::component_to_xml(vec, std::make_pair(arg.first, item)); } } template inline void _component_to_xml(xml_node *c, std::pair>> arg) { xml_node * vec = c->add_node(arg.first); for (auto item : arg.second) { xml_node * e = vec->add_node(arg.first); xml_node * i = e->add_node(std::string(arg.first) + std::string("_first")); rltk::component_to_xml(i, std::make_pair(arg.first, item.first)); xml_node * i2 = e->add_node(std::string(arg.first) + std::string("_second")); rltk::component_to_xml(i2, std::make_pair(arg.first, item.second)); } } template inline void _component_to_xml(xml_node *c, std::pair> arg) { xml_node * set = c->add_node(arg.first); for (auto it = arg.second.begin(); it != arg.second.end(); ++it) { xml_node * key = set->add_node("key"); rltk::component_to_xml(key, std::make_pair("k", *it)); } } template inline void _component_to_xml(xml_node *c, std::pair> arg) { xml_node * map = c->add_node(arg.first); for (auto it = arg.second.begin(); it != arg.second.end(); ++it) { xml_node * entry = map->add_node(arg.first); entry->add_value("key", to_string(it->first)); rltk::component_to_xml(entry, std::make_pair("v", it->second)); } } template inline void _component_to_xml(xml_node * c, std::pair> arg) { xml_node * optional = c->add_node(arg.first); if (!arg.second) { optional->add_value("initialized", "no"); } else { optional->add_value("initialized", "yes"); rltk::component_to_xml(optional, std::make_pair(arg.first, *arg.second)); } } template void _component_to_xml(xml_node * c, First arg, Rest... args) { _component_to_xml(c, arg); _component_to_xml(c, args...); } } // End serial sub-namespace template inline void component_to_xml(xml_node * c, const T... args) { serial::_component_to_xml(c, args...); } /* Binary file helpers */ template inline void serialize(std::ostream &lbfile, const T &target) { lbfile.write(reinterpret_cast(&target), sizeof(target)); } template<> inline void serialize(std::ostream &lbfile, const std::string &target) { std::size_t size = target.size(); serialize(lbfile, size); for (std::size_t i = 0; i < size; ++i) { serialize(lbfile, target[i]); } } template<> inline void serialize(std::ostream &lbfile, const rltk::color_t &col) { serialize(lbfile, col.r); serialize(lbfile, col.g); serialize(lbfile, col.b); } template inline void serialize(std::ostream &lbfile, const std::vector &vec) { std::size_t sz = vec.size(); serialize(lbfile, sz); for (std::size_t i=0; i inline void serialize(std::ostream &lbfile, const std::vector &vec) { std::size_t sz = vec.size(); serialize(lbfile, sz); for (std::size_t i=0; i inline void deserialize(std::istream &lbfile, T &target) { lbfile.read(reinterpret_cast(&target), sizeof(target)); } template<> inline void deserialize(std::istream &lbfile, std::string &target) { std::string result; std::size_t size = 0; deserialize(lbfile, size); for (std::size_t i = 0; i < size; ++i) { char c; deserialize(lbfile, c); result += c; } target = result; } template<> inline void deserialize(std::istream &lbfile, rltk::color_t &target) { deserialize(lbfile, target.r); deserialize(lbfile, target.g); deserialize(lbfile, target.b); } template inline void deserialize(std::istream &lbfile, std::vector &vec) { std::size_t size; deserialize(lbfile, size); vec.resize(size); for (std::size_t i=0; i inline void serialize(const T &target) { if (gzwrite(file, reinterpret_cast(&target), sizeof(target))) return; throw_gzip_exception(file); } template inline void serialize(const std::string &target) { std::size_t size = target.size(); serialize(size); for (std::size_t i = 0; i < size; ++i) { serialize(target[i]); } } template inline void serialize(const rltk::color_t &col) { serialize(col.r); serialize(col.g); serialize(col.b); } inline void serialize_vector_bool(const std::vector &vec) { std::size_t sz = vec.size(); serialize(sz); for (std::size_t i=0; i(b); } } template inline void serialize(const std::vector &vec) { std::size_t sz = vec.size(); serialize(sz); for (std::size_t i=0; i inline void deserialize(T &target) { if (gzread(file, reinterpret_cast(&target), sizeof(target))) return; throw_gzip_exception(file); } template inline void deserialize(std::string &target) { std::string result; std::size_t size = 0; deserialize(size); for (std::size_t i = 0; i < size; ++i) { char c; deserialize(c); result += c; } target = result; } template inline void deserialize(rltk::color_t &target) { deserialize(target.r); deserialize(target.g); deserialize(target.b); } template inline void deserialize(std::vector &vec) { std::size_t size; deserialize(size); vec.resize(size); for (std::size_t i=0; i #include #include #include namespace rltk { struct texture { texture() {} texture(const std::string &filename) { tex = std::make_unique(); if (!tex->loadFromFile(filename)) { throw std::runtime_error("Unable to load texture from: " + filename); } } std::unique_ptr tex; }; } ================================================ FILE: rltk/texture_resources.cpp ================================================ #include #include #include "texture_resources.hpp" #include "texture.hpp" namespace rltk { namespace texture_detail { std::unordered_map atlas; } void register_texture(const std::string &filename, const std::string &tag) { auto finder = texture_detail::atlas.find(tag); if (finder != texture_detail::atlas.end()) { throw std::runtime_error("Duplicate resource tag: " + tag); } texture_detail::atlas[tag] = rltk::texture(filename); } sf::Texture * get_texture(const std::string &tag) { auto finder = texture_detail::atlas.find(tag); if (finder == texture_detail::atlas.end()) { throw std::runtime_error("Unable to find resource tag: " + tag); } else { return finder->second.tex.get(); } } } ================================================ FILE: rltk/texture_resources.hpp ================================================ #pragma once /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * Provides a global texture atlas. */ #include #include namespace rltk { void register_texture(const std::string &filename, const std::string &tag); sf::Texture * get_texture(const std::string &tag); } ================================================ FILE: rltk/vchar.hpp ================================================ #pragma once /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * Virtual terminal character. */ #include "color_t.hpp" #include namespace rltk { /* * Represents a character on a virtual terminal. */ struct vchar { vchar() {} vchar(const uint32_t &g, const color_t &f, const color_t &b) : glyph(g), foreground(f), background(b) {} vchar(const int &g, const color_t &f, const color_t &b) : glyph(static_cast(g)), foreground(f), background(b) {} 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) : glyph(gly), foreground(color_t{fr, fg, fb}), background(color_t{br, bg, bb}) {} uint32_t glyph; color_t foreground; color_t background; template void serialize(Archive & archive) { archive( glyph, foreground, background ); // serialize things by passing them to the archive } }; } ================================================ FILE: rltk/virtual_terminal.cpp ================================================ #include "virtual_terminal.hpp" #include "texture_resources.hpp" #include "scaling.hpp" #include #include #include namespace rltk { void virtual_terminal::resize_pixels(const int width, const int height) noexcept { int w = static_cast(width/(font->character_size.first * scale_factor)); int h = static_cast(height/(font->character_size.second * scale_factor)); resize_chars(w,h); } void virtual_terminal::resize_chars(const int width, const int height) noexcept { dirty = true; const int num_chars = width*(height+1); buffer.resize(num_chars); term_width = width; term_height = height; backing.create(term_width * font->character_size.first, term_height * font->character_size.second); // Build the vertex buffer vertices.setPrimitiveType(sf::Quads); if (has_background) { vertices.resize(width * height * 8); } else { vertices.resize(width * height * 4); } if (has_background) { const int idx_half = width * height * 4; const int font_width = font->character_size.first; const int font_height = font->character_size.second; const int space_x = (219 % 16) * font_width; const int space_y = (219 / 16) * font_height; for (int y=0; y(x * font->character_size.first), static_cast(y * font->character_size.second)); vertices[idx+1].position = sf::Vector2f(static_cast((x+1) * font->character_size.first), static_cast(y * font->character_size.second)); vertices[idx+2].position = sf::Vector2f(static_cast((x+1) * font->character_size.first), static_cast((y+1) * font->character_size.second)); vertices[idx+3].position = sf::Vector2f(static_cast(x * font->character_size.first), static_cast((y+1) * font->character_size.second)); vertices[idx].texCoords = sf::Vector2f(static_cast(space_x), static_cast(space_y) ); vertices[idx+1].texCoords = sf::Vector2f(static_cast(space_x + font_width), static_cast(space_y) ); vertices[idx+2].texCoords = sf::Vector2f(static_cast(space_x + font_width), static_cast(space_y + font_height) ); vertices[idx+3].texCoords = sf::Vector2f(static_cast(space_x), static_cast(space_y + font_height )); vertices[idx2].position = sf::Vector2f(static_cast(x * font->character_size.first), static_cast(y * font->character_size.second)); vertices[idx2+1].position = sf::Vector2f(static_cast((x+1) * font->character_size.first), static_cast(y * font->character_size.second)); vertices[idx2+2].position = sf::Vector2f(static_cast((x+1) * font->character_size.first), static_cast((y+1) * font->character_size.second)); vertices[idx2+3].position = sf::Vector2f(static_cast(x * font->character_size.first), static_cast((y+1) * font->character_size.second)); } } } else { for (int y=0; y(x * font->character_size.first), static_cast(y * font->character_size.second)); vertices[idx+1].position = sf::Vector2f(static_cast((x+1) * font->character_size.first), static_cast(y * font->character_size.second)); vertices[idx+2].position = sf::Vector2f(static_cast((x+1) * font->character_size.first), static_cast((y+1) * font->character_size.second)); vertices[idx+3].position = sf::Vector2f(static_cast(x * font->character_size.first), static_cast((y+1) * font->character_size.second)); } } } } void virtual_terminal::clear() noexcept { dirty = true; std::fill(buffer.begin(), buffer.end(), vchar{ 32, {255,255,255}, {0,0,0} }); } void virtual_terminal::clear(const vchar &target) noexcept { dirty = true; std::fill(buffer.begin(), buffer.end(), target); } void virtual_terminal::set_char(const int idx, const vchar &target) noexcept { dirty = true; if (!(idx < 0 || idx > static_cast(buffer.size()))) { buffer[idx] = target; } } void virtual_terminal::print(const int x, const int y, const std::string &s, const color_t &fg, const color_t &bg) noexcept { int idx = at(x,y); for (std::size_t i=0; i((term_width/2) - (s.size()/2)), y, s, fg, bg); } 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 { //std::cout << h << "\n"; for (int i=1; itexture_tag); } const int font_width = font->character_size.first; const int font_height = font->character_size.second; int vertex_idx = term_height * term_width * 4; if (!has_background) vertex_idx = 0; int bg_idx = 0; int idx=0; for (int y=0; y(texture_x), static_cast(texture_y) ); vertices[vertex_idx+1].texCoords = sf::Vector2f(static_cast(texture_x + font_width), static_cast(texture_y) ); vertices[vertex_idx+2].texCoords = sf::Vector2f(static_cast(texture_x + font_width), static_cast(texture_y + font_height) ); vertices[vertex_idx+3].texCoords = sf::Vector2f(static_cast(texture_x), static_cast(texture_y + font_height) ); sf::Color fgsfml = color_to_sfml(target.foreground); vertices[vertex_idx].color = fgsfml; vertices[vertex_idx+1].color = fgsfml; vertices[vertex_idx+2].color = fgsfml; vertices[vertex_idx+3].color = fgsfml; vertex_idx += 4; bg_idx += 4; ++idx; } } backing.clear(sf::Color(0,0,0,0)); backing.draw(vertices, tex); } backing.display(); sf::Sprite compositor(backing.getTexture()); compositor.move(static_cast(offset_x), static_cast(offset_y)); compositor.setColor(sf::Color(tint.r, tint.g, tint.b, alpha)); compositor.scale(scale_factor, scale_factor); window.draw(sf::Sprite(compositor)); dirty = false; } } ================================================ FILE: rltk/virtual_terminal.hpp ================================================ #pragma once /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * Provides a virtual console */ #include #include #include #include "font_manager.hpp" #include "texture.hpp" #include "color_t.hpp" #include "colors.hpp" #include "rexspeeder.hpp" #include "vchar.hpp" namespace rltk { /* * Combines the functions/data required to render a virtual terminal, either as root or * as a layer. This is generally initialized by the library, not the user. */ struct virtual_terminal { public: /* * Constructor to create a virtual terminal. Parameters: * fontt the tag of the bitmap font to use for the terminal. * x (defaults to 0); an offset at which to render the terminal. * y (defaults to 0); an offset at which to render the terminal. * background; if true, then solid backgrounds will be rendered. If false, then no background rendered. */ 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) { font = get_font(fontt); } /* * Resize the terminal to match width x height pixels. */ void resize_pixels(const int width, const int height) noexcept; /* * Resize the terminal to match width x height virtual characters. */ void resize_chars(const int width, const int height) noexcept; /* * Clears the virtual terminal to black spaces. */ void clear() noexcept; /* * Clears the virtual terminal to a user-provided character. * vchar; the character to which the terminal should be set. */ void clear(const vchar &target) noexcept; /* * Helper function that returns the index of a character location in the backing vector. */ inline int at(const int x, const int y) noexcept { return ( y * term_width) + x; } /* * Set a character at backing-vector idx (use "at" to calculate) to the specified target virtual * character. */ void set_char(const int idx, const vchar &target) noexcept; /* * Set a character at x/y to the specified target virtual character. */ inline void set_char(const int x, const int y, vchar target) noexcept { set_char(at(x,y), target); } /* * Print a string to the terminal, at location x/y, string s, with foreground and background of fg and bg. * If you don't specify colors, it will do white on black. */ 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; /* * Center a string (relative to the terminal size), and print it at location y. */ void print_center(const int y, const std::string &s, const color_t &fg = colors::WHITE, const color_t &bg = colors::BLACK) noexcept; /* * Draw a box taht encompasses the whole terminal boundary, in color fg/bg. If double_lines is set to true, then * it will use the two-line/thick box characters. */ inline void box(const color_t &fg = colors::WHITE, const color_t &bg = colors::BLACK, bool double_lines=false) noexcept { box (0, 0, term_width-1, term_height-1, fg, bg, double_lines); } /* * 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, * it will use the two-line/thick box characters. */ 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; /* * Fill a region with a specified character */ 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; /* * Draw a REX sprite at the specified location. */ void draw_sprite(const int x, const int y, xp::rex_sprite &sprite); /* * Renders the terminal to the specified renderable. Don't call this directly - the toolkit will take care of it. */ void render(sf::RenderWindow &window); /* * Sets the global translucency level for the console. You can use this to make a translucent console layer on top of * other items. */ inline void set_alpha(const uint8_t new_alpha) noexcept { alpha = new_alpha; } /* * Use this to tint the entire rendering of the console. */ inline void set_tint(const color_t new_tint) noexcept { tint = new_tint; } /* * Use this to move the terminal in x/y screen pixels. */ inline void set_offset(int x, int y) noexcept { offset_x = x; offset_y = y; }; /* * This gets the current font size in pixels, first is width, second is height. */ inline std::pair get_font_size() noexcept { return font->character_size; } inline std::string get_font_tag() noexcept { return font->texture_tag; } int term_width; int term_height; bool visible = true; bool dirty = true; // Flag for requiring a re-draw private: sf::RenderTexture backing; sf::VertexArray vertices; std::string font_tag; int offset_x; int offset_y; uint8_t alpha = 255; color_t tint{255,255,255}; bool has_background; bitmap_font * font = nullptr; sf::Texture * tex = nullptr; std::vector buffer; }; } ================================================ FILE: rltk/virtual_terminal_sparse.cpp ================================================ #include "virtual_terminal_sparse.hpp" #include "texture_resources.hpp" #include "scaling.hpp" namespace rltk { void virtual_terminal_sparse::resize_pixels(const int width, const int height) { int w = static_cast(width/(font->character_size.first * scale_factor)); int h = static_cast(height/(font->character_size.second * scale_factor)); resize_chars(w,h); } void virtual_terminal_sparse::resize_chars(const int width, const int height) { dirty = true; const int num_chars = width*(height+1); buffer.resize(num_chars); term_width = width; term_height = height; backing.create(term_width * font->character_size.first, term_height * font->character_size.second); } void virtual_terminal_sparse::render(sf::RenderWindow &window) { if (!visible) return; if (dirty) { if (font == nullptr) { throw std::runtime_error("Font not loaded: " + font_tag); } if (tex == nullptr) { tex = get_texture(font->texture_tag); } const int font_width = font->character_size.first; const int font_height = font->character_size.second; const float fontW = static_cast(font_width); const float fontH = static_cast(font_height); const sf::Vector2f origin(fontW/2.0f, fontH/2.0f); const int space_x = (219 % 16) * font_width; const int space_y = (219 / 16) * font_height; // We want to iterate the display vector, and put sprites on the screen for each entry backing.clear(sf::Color(0,0,0,0)); for (const xchar &target : buffer) { const int texture_x = (target.glyph % 16) * font_width; const int texture_y = (target.glyph / 16) * font_height; sf::Vector2f position((target.x * fontW) + fontW/2.0f, (target.y * fontH) + fontH/2.0f); if (target.has_background) { sf::Color bgsfml = color_to_sfml(target.background); sf::Sprite sprite; sprite.setTexture(*tex); sprite.setTextureRect(sf::IntRect(space_x, space_y, font_width, font_height)); sprite.setColor(bgsfml); sprite.setOrigin(origin); sprite.setPosition(position); sprite.setRotation(static_cast(target.angle)); backing.draw(sprite); } sf::Color fgsfml = color_to_sfml(target.foreground); sf::Sprite sprite; sprite.setTexture(*tex); sprite.setTextureRect(sf::IntRect(texture_x, texture_y, font_width, font_height)); sprite.setColor(fgsfml); sprite.setOrigin(origin); sprite.setPosition(position); sprite.setRotation(static_cast(target.angle)); backing.draw(sprite); } } // Draw the backing backing.display(); sf::Sprite compositor(backing.getTexture()); compositor.move(static_cast(offset_x), static_cast(offset_y)); compositor.setColor(sf::Color(tint.r, tint.g, tint.b, alpha)); compositor.scale(scale_factor, scale_factor); window.draw(sf::Sprite(compositor)); dirty = false; } } ================================================ FILE: rltk/virtual_terminal_sparse.hpp ================================================ /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * Provides a virtual console, in 'sparse' mode: it doesn't assume that you have a regular grid, * supports off-alignment rendering, rotation and similar. It is optimized for use with a few * characters, rather than a complete grid. A common usage would be as a layer for PCs/NPCs, * rendered over a regular console grid. */ #pragma once #include #include #include "font_manager.hpp" #include "texture.hpp" #include "color_t.hpp" #include "colors.hpp" namespace rltk { /* * Represents a character on a sparse virtual terminal. */ struct xchar { xchar() {} xchar(const int Glyph, const color_t fg, const float X, const float Y) : glyph(Glyph), foreground(fg), x(X), y(Y) {} 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) {} int glyph; // Glyph to render color_t foreground; // Foreground color float x = 0.0f; // Provided as floats to allow for partial character movement/sliding float y = 0.0f; int angle = 0; // Rotation angle in degrees, defaults to 0 - not rotated unsigned char opacity = 255; bool has_background = false; color_t background; // If provided, a background is drawn }; struct virtual_terminal_sparse { virtual_terminal_sparse(const std::string fontt, const int x=0, const int y=0) : font_tag(fontt), offset_x(x), offset_y(y) { font = get_font(fontt); } void resize_pixels(const int width, const int height); void resize_chars(const int width, const int height); inline void clear() { dirty = true; buffer.clear(); } inline void add(const xchar target) { dirty = true; buffer.push_back(target); } void render(sf::RenderWindow &window); int term_width; int term_height; bool visible = true; bool dirty = true; // Flag for requiring a re-draw private: std::string font_tag; int offset_x; int offset_y; uint8_t alpha = 255; color_t tint{255,255,255}; bitmap_font * font = nullptr; sf::Texture * tex = nullptr; std::vector buffer; sf::RenderTexture backing; }; } ================================================ FILE: rltk/visibility.hpp ================================================ #pragma once /* RLTK (RogueLike Tool Kit) 1.00 * Copyright (c) 2016-Present, Bracket Productions. * Licensed under the MIT license - see LICENSE file. * * Provides a wrapper for bitmap fonts. */ #include #include "geometry.hpp" namespace rltk { namespace visibility_private { /* * Benchmark results from example 6 (on my desktop): * - Using line_func, it averages 0.3625 uS per line. * - Using line_func_cancellable, it averages 0.25 uS per line. * - Using a naieve float based slope, it averages 0.2 uS per line. */ template void internal_2d_sweep(const location_t_ &position, const int &range, std::function set_visible, std::function is_opaque, const std::pair offset) { bool blocked = false; const int start_x = navigator_t::get_x(position); const int start_y = navigator_t::get_y(position); const int end_x = start_x + offset.first; const int end_y = start_y + offset.second; line_func_cancellable(start_x, start_y, end_x, end_y, [&blocked, &is_opaque, &set_visible, &range, &position] (int X, int Y) { if (blocked) return false; float distance = distance2d(static_cast(position.x), static_cast(position.y), X, Y); if (distance <= range) { location_t_ pos = navigator_t::get_xy(X,Y); if (!blocked) set_visible(pos); if (!is_opaque(pos)) blocked = true; } return true; }); } } /* Simple all-direction visibility sweep in 2 dimensions. This requires that your location_t utilize an x and y * component. Parameters: * position - where you are sweeping from. * range - the number of tiles you can traverse. * set_visible - a callback (such as bool set_visible(location_t & loc)) to say "this is visible" * is_opaque - a callback to ask your map if you can see through a tile. * * You must provide a navigator_t, just like for path finding. It must support get_x, get_y, and get_xy. */ template void visibility_sweep_2d(const location_t_ &position, const int &range, std::function set_visible, std::function is_opaque) { // You can always see yourself set_visible(position); // Box-sweep for (int i=0-range; i(position, range, set_visible, is_opaque, std::make_pair(i, 0-range)); visibility_private::internal_2d_sweep(position, range, set_visible, is_opaque, std::make_pair(i, range)); visibility_private::internal_2d_sweep(position, range, set_visible, is_opaque, std::make_pair(0-range, i)); visibility_private::internal_2d_sweep(position, range, set_visible, is_opaque, std::make_pair(range, i)); } } } ================================================ FILE: rltk/xml.cpp ================================================ #include "xml.hpp" #include #include #include #include #include #include #include using namespace std::string_literals; namespace rltk { xml_node * xml_node::add_node(const std::string &name) { children.push_back(xml_node(name, depth+1)); return &children[children.size()-1]; } void xml_node::add_node(xml_node x) { children.push_back(x); } std::string xml_node::indent() const { std::string result; for (int i=0; i\n"; for (const auto & val : values) { lbfile << indent() << " <" << val.first << ":value>" << val.second << "\n"; } for (const xml_node &child : children) { child.save(lbfile); } lbfile << indent() << "\n"; } void xml_node::dump(std::stringstream &lbfile) const { lbfile << indent() << "<" << name << ">\n"; for (const auto & val : values) { lbfile << indent() << " <" << val.first << ":value>" << val.second << "\n"; } for (const xml_node &child : children) { child.dump(lbfile); } lbfile << indent() << "\n"; } std::string xml_node::dump() const { std::stringstream ss; dump(ss); return ss.str(); } void xml_node::add_value(const std::string &key, const std::string &val) { values.emplace_back(std::make_pair(key, val)); } static inline std::string <rim(std::string &s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); return s; } static inline void erase_all(std::string &s, const std::string &to_remove) { auto pos = s.find(to_remove); while (pos != std::string::npos) { s.erase(pos, to_remove.size()); pos = s.find(to_remove); } } static inline std::string remove_xml_braces(std::string s) { erase_all(s, "<"); erase_all(s, ">"); return s; } static inline std::string remove_colon_value(std::string s) { erase_all(s, ":value"); return s; } void xml_reader::load() { std::stack stack; bool first_line = true; std::string line; while (getline(*lbfile, line)) { std::string trimmed = ltrim(line); erase_all(trimmed, "\n"); if (first_line) { root.name = remove_xml_braces(trimmed); first_line = false; } else if (trimmed == ""s) { // We're at the end. } else if (stack.empty()) { // We're at the beginning, so we need to create a node. stack.push(xml_node(remove_xml_braces(trimmed))); } else if (trimmed == ""s ) { // We're at the end of the element if (stack.size() > 1) { xml_node current = stack.top(); stack.pop(); stack.top().add_node(current); } else { xml_node current = stack.top(); stack.pop(); root.add_node(current); } } else { // Are we looking at a new node or a value? if (trimmed.find(":value>")==std::string::npos) { // It's a new child stack.push(xml_node(remove_xml_braces(trimmed))); } else { // It's a value std::string key = remove_colon_value(remove_xml_braces(trimmed.substr(0,trimmed.find(">")))); std::string val = trimmed.substr(trimmed.find(">")+1); val = val.substr(0, val.find("<")); stack.top().add_value(key, val); } } } } } ================================================ FILE: rltk/xml.hpp ================================================ #pragma once #include #include #include #include #include #include #include #include "color_t.hpp" #include "vchar.hpp" #include "filesystem.hpp" namespace rltk { template inline T from_string(const std::string &val) { std::stringstream ss; ss << val; T result; ss >> result; return result; } template<> inline uint8_t from_string(const std::string &val) { std::stringstream ss; ss << val; int result; ss >> result; return static_cast(result); } template<> inline std::string from_string(const std::string &val) { return val; } struct xml_node { xml_node() {}; xml_node(const std::string &Name) : name(Name) {} xml_node(const std::string &Name, const int Depth) : name(Name), depth(Depth) {} xml_node * add_node(const std::string &name); void add_node(xml_node x); std::string indent() const; void save(std::ofstream &lbfile) const; void dump(std::stringstream &lbfile) const; std::string dump() const; void add_value(const std::string &key, const std::string &val); std::string name = ""; std::size_t count() { std::size_t count = 0; for (xml_node &node : children) { node.count(count); } return count; } void count(std::size_t &count) { ++count; for (xml_node &node : children) { node.count(count); } } xml_node * find(const std::string name) { for (xml_node &node : children) { if (node.name == name) return &node; } return nullptr; } template T val(const std::string &key) { for (const auto &val : values) { if (val.first == key) { return from_string(val.second); } } throw std::runtime_error(std::string("Key not found:") + key); } inline void iterate_child(const std::string &name, const std::function &func) { xml_node * vec = find(name); for (xml_node child : vec->children) { func(&child); } } inline color_t color(const std::string &name) { xml_node * c = find(name); return color_t{c->val("r"), c->val("g"), c->val("b")}; } inline rltk::vchar vchar() { rltk::vchar c; c.glyph = val("glyph"); c.foreground = color("foreground"); c.background = color("background"); return c; } const int depth = 0; std::vector children; std::vector> values; }; struct xml_writer { xml_writer(const std::string &fn, const std::string &root_name) : filename(fn), root(xml_node(root_name,0)) { if (exists(filename)) std::remove(filename.c_str()); lbfile = std::make_unique(filename, std::ios::out | std::ios::binary); } xml_writer(std::unique_ptr &&f, const std::string &root_name) : lbfile(std::move(f)), root(xml_node(root_name,0)) {} inline void commit() { root.save(*lbfile); } inline xml_node * add_node(const std::string &name) { return root.add_node(name); } private: std::unique_ptr lbfile; const std::string filename = ""; xml_node root; }; struct xml_reader { xml_reader(const std::string &fn) : filename(fn) { if (!exists(filename)) throw std::runtime_error(std::string("File not found: ") + filename); lbfile = std::make_unique(filename, std::ios::in | std::ios::binary); load(); } xml_reader(std::unique_ptr &&f) : lbfile(std::move(f)) { load(); } inline xml_node * get() { return &root; } private: std::unique_ptr lbfile; const std::string filename = ""; xml_node root; void load(); }; }