From 59ca86f5334f4bb82ca6d40ed759dfc330e08321 Mon Sep 17 00:00:00 2001 From: UnknownXXX000 <59707969+UnknownXXX000@users.noreply.github.com> Date: Sun, 28 Apr 2024 18:20:12 +0300 Subject: [PATCH 1/5] Update networking and arg parser --- CMakeLists.txt | 24 ++++++++--- include/main.h | 10 ++--- src/main.cpp | 114 ++++++++++++++++++++++++++++++++++--------------- 3 files changed, 102 insertions(+), 46 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f5b8bb..64b2fba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,21 +21,35 @@ set(SOURCES # Add header files set (HEADERS - "include/main.h" -) - -set(CMAKE_CXX_STANDARD 20) + "include/includes.h" + "include/net_message.h" + "include/net_tsqueue.h" + "include/net_connection.h" + "include/net_server.h" + "include/net_client.h" + "include/net_includes.h" + "include/net_common.h" + "include/game_client.h" + "include/game_server.h" + "include/game_structs.h" + "argparse/argparse.hpp") + +include_directories("${PROJECT_SOURCE_DIR}/include") +include_directories("${PROJECT_SOURCE_DIR}/argparse") + +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Add the executable -add_executable(ConsolePacmanProject ${SOURCES} ${HEADERS}) +add_executable(ConsolePacmanProject ${SOURCES} ${HEADERS} "include/game_structs.h") # Check and include NCurses or PDCurses if(LINUX OR MACOS) find_package(Curses REQUIRED) include_directories(${CURSES_INCLUDE_DIR}) target_link_libraries(ConsolePacmanProject ${CURSES_LIBRARIES}) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DASIO_STANDALONE -lncurses -DNCURSES_NOMACROS -lpthread") elseif(WINDOWS) set(CURSES_NEED_WIDE TRUE) set(PDCurses_INCLUDE_DIRS "${PROJECT_SOURCE_DIR}/include") diff --git a/include/main.h b/include/main.h index 5e43948..d2b7734 100644 --- a/include/main.h +++ b/include/main.h @@ -11,11 +11,6 @@ #define ASIO_STANDALONE #endif -#include -#include -#include -#include -#include // TODO: установите здесь ссылки на дополнительные заголовки, требующиеся для программы. @@ -27,4 +22,7 @@ #include #endif -#include \ No newline at end of file +#include +#include + +#include "net_includes.h" \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index d610ae6..43814e8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,57 +3,101 @@ #include "main.h" + using namespace std::chrono; +// using namespace std::chrono_literals; +//std::vector vBuffer(10 * 1024); +// +//void GrabSomeData(asio::ip::tcp::socket& socket) +//{ +// // socket.async_read_some(asio::buffer(vBuffer.data(), vBuffer.size())); +//} -bool game_exit = false; -struct vector2di -{ - int x = 0; - int y = 0; -}; -int main() +int main(int argc, char * argv[]) { - asio::error_code ec; - //Create a "context" - essentially the platform specific interface - asio::io_context context; + argparse::ArgumentParser program("PacmanConsole"); + + program.add_argument("-t", "--type").help("decide if a program is a client or a server").default_value(std::string("client")).nargs(1); + program.add_argument("-i", "--ip").help("if server, then decide the ip address the server will be running on. if client, then connect to a server with specific ip address").default_value(std::string("127.0.0.1")).nargs(1); + program.add_argument("-p", "--port").help("the server will be listening on specific port, or a client will connect to a server running on a specific port").default_value(60000).scan <'d', uint16_t>().nargs(1); + + try + { + program.parse_args(argc, argv); + } + catch (const std::exception& err) + { + std::cerr << "Argparse failed. Error: " << err.what() << std::endl; + std::cerr << program; + return -1; + } + + const auto program_type = program.get("-t"); + const auto program_ip = program.get("-i"); + const auto program_port = program.get("-p"); - //Get the address of somewhere we wish to connect to - asio::ip::tcp::endpoint endpoint(asio::ip::make_address("93.184.216.34", ec), 80); + std::cout << "Program type is " << program_type << ", ip address is " << program_ip << " and port is " << program_port << std::endl; - asio::ip::tcp::socket socket(context); + //net::message msg; - socket.connect(endpoint, ec); + //int a = 50, b = 40, c = 30; - if (!ec) - std::cout << "Connected\n"; - else - std::cout << "Fail " << ec.message() << '\n'; + //std::cout << a << b << c << '\n'; - if (socket.is_open()) - { - std::string sRequest = - "GET /index.html HTTP/1.1\r\n" - "Host: example.com\r\n" - "Connection: close\r\n\r\n"; + //msg << a << b << c; - socket.write_some(asio::buffer(sRequest.data(), sRequest.size()), ec); + //a = b = c = 0; - const size_t bytes = socket.available(); - std::cout << "Bytes available: " << bytes << '\n'; + //std::cout << a << b << c << '\n'; - if (bytes > 0) - { - std::vector vBuffer(bytes); - socket.read_some(asio::buffer(vBuffer.data(), vBuffer.size()), ec); + //msg >> c >> b >> a; - for (const auto& c : vBuffer) - std::cout << c; - } - } + //std::cout << a << b << c << '\n'; + + //asio::error_code ec; + + ////Create a "context" - essentially the platform specific interface + //asio::io_context context; + + ////Get the address of somewhere we wish to connect to + //asio::ip::tcp::endpoint endpoint(asio::ip::make_address("51.38.81.49", ec), 80); + + //asio::ip::tcp::socket socket(context); + + //socket.connect(endpoint, ec); + + //if (!ec) + // std::cout << "Connected\n"; + //else + // std::cout << "Fail " << ec.message() << '\n'; + + //if (socket.is_open()) + //{ + // std::string sRequest = + // "GET /index.html HTTP/1.1\r\n" + // "Host: example.com\r\n" + // "Connection: close\r\n\r\n"; + + // std::cout << "Number of bytes sent: " << socket.write_some(asio::buffer(sRequest.data(), sRequest.size()), ec) << '\n'; + + // socket.wait(socket.wait_read); + + // const size_t bytes = socket.available(); + // std::cout << "Bytes available: " << bytes << '\n'; + + // if (bytes > 0) + // { + // std::vector vBuffer(bytes); + // socket.read_some(asio::buffer(vBuffer.data(), vBuffer.size()), ec); + + // for (const auto& c : vBuffer) + // std::cout << c; + // } + //} return 0; From b9fb8980144883c69a10fc94bf626f04529014ca Mon Sep 17 00:00:00 2001 From: UnknownXXX000 <59707969+UnknownXXX000@users.noreply.github.com> Date: Sun, 28 Apr 2024 18:20:41 +0300 Subject: [PATCH 2/5] Updated networking and arg parser --- CMakeLists.txt | 10 +- {include => PDCurses/include}/curses.h | 0 {lib => PDCurses/lib}/pdcurses.lib | Bin argparse/include/argparse.hpp | 2513 ++++++++++++++++++++++++ include/game_client.h | 30 + include/game_server.h | 60 + include/game_structs.h | 109 + include/includes.h | 40 + include/main.h | 28 - include/net_client.h | 120 ++ include/net_common.h | 27 + include/net_connection.h | 365 ++++ include/net_includes.h | 8 + include/net_message.h | 109 + include/net_server.h | 246 +++ include/net_tsqueue.h | 114 ++ src/main.cpp | 3 +- 17 files changed, 3747 insertions(+), 35 deletions(-) rename {include => PDCurses/include}/curses.h (100%) rename {lib => PDCurses/lib}/pdcurses.lib (100%) create mode 100644 argparse/include/argparse.hpp create mode 100644 include/game_client.h create mode 100644 include/game_server.h create mode 100644 include/game_structs.h create mode 100644 include/includes.h delete mode 100644 include/main.h create mode 100644 include/net_client.h create mode 100644 include/net_common.h create mode 100644 include/net_connection.h create mode 100644 include/net_includes.h create mode 100644 include/net_message.h create mode 100644 include/net_server.h create mode 100644 include/net_tsqueue.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 64b2fba..d0a44dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,17 +32,17 @@ set (HEADERS "include/game_client.h" "include/game_server.h" "include/game_structs.h" - "argparse/argparse.hpp") + "argparse/include/argparse.hpp") include_directories("${PROJECT_SOURCE_DIR}/include") -include_directories("${PROJECT_SOURCE_DIR}/argparse") +include_directories("${PROJECT_SOURCE_DIR}/argparse/include") set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Add the executable -add_executable(ConsolePacmanProject ${SOURCES} ${HEADERS} "include/game_structs.h") +add_executable(ConsolePacmanProject ${SOURCES} ${HEADERS}) # Check and include NCurses or PDCurses if(LINUX OR MACOS) @@ -52,8 +52,8 @@ if(LINUX OR MACOS) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DASIO_STANDALONE -lncurses -DNCURSES_NOMACROS -lpthread") elseif(WINDOWS) set(CURSES_NEED_WIDE TRUE) - set(PDCurses_INCLUDE_DIRS "${PROJECT_SOURCE_DIR}/include") - set(PDCurses_LIBRARIES "${PROJECT_SOURCE_DIR}/lib/pdcurses.lib") + set(PDCurses_INCLUDE_DIRS "${PROJECT_SOURCE_DIR}/PDCurses/include") + set(PDCurses_LIBRARIES "${PROJECT_SOURCE_DIR}/PDCurses/lib/pdcurses.lib") include_directories(${PDCurses_INCLUDE_DIRS}) target_link_libraries(ConsolePacmanProject ${PDCurses_LIBRARIES}) endif() diff --git a/include/curses.h b/PDCurses/include/curses.h similarity index 100% rename from include/curses.h rename to PDCurses/include/curses.h diff --git a/lib/pdcurses.lib b/PDCurses/lib/pdcurses.lib similarity index 100% rename from lib/pdcurses.lib rename to PDCurses/lib/pdcurses.lib diff --git a/argparse/include/argparse.hpp b/argparse/include/argparse.hpp new file mode 100644 index 0000000..118e214 --- /dev/null +++ b/argparse/include/argparse.hpp @@ -0,0 +1,2513 @@ +/* + __ _ _ __ __ _ _ __ __ _ _ __ ___ ___ + / _` | '__/ _` | '_ \ / _` | '__/ __|/ _ \ Argument Parser for Modern C++ +| (_| | | | (_| | |_) | (_| | | \__ \ __/ http://github.com/p-ranav/argparse + \__,_|_| \__, | .__/ \__,_|_| |___/\___| + |___/|_| + +Licensed under the MIT License . +SPDX-License-Identifier: MIT +Copyright (c) 2019-2022 Pranav Srinivas Kumar +and other contributors. + +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. +*/ +#pragma once + +#include + +#ifndef ARGPARSE_MODULE_USE_STD_MODULE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#ifndef ARGPARSE_CUSTOM_STRTOF +#define ARGPARSE_CUSTOM_STRTOF strtof +#endif + +#ifndef ARGPARSE_CUSTOM_STRTOD +#define ARGPARSE_CUSTOM_STRTOD strtod +#endif + +#ifndef ARGPARSE_CUSTOM_STRTOLD +#define ARGPARSE_CUSTOM_STRTOLD strtold +#endif + +namespace argparse { + +namespace details { // namespace for helper methods + +template +struct HasContainerTraits : std::false_type {}; + +template <> struct HasContainerTraits : std::false_type {}; + +template <> struct HasContainerTraits : std::false_type {}; + +template +struct HasContainerTraits< + T, std::void_t().begin()), + decltype(std::declval().end()), + decltype(std::declval().size())>> : std::true_type {}; + +template +inline constexpr bool IsContainer = HasContainerTraits::value; + +template +struct HasStreamableTraits : std::false_type {}; + +template +struct HasStreamableTraits< + T, + std::void_t() << std::declval())>> + : std::true_type {}; + +template +inline constexpr bool IsStreamable = HasStreamableTraits::value; + +constexpr std::size_t repr_max_container_size = 5; + +template std::string repr(T const &val) { + if constexpr (std::is_same_v) { + return val ? "true" : "false"; + } else if constexpr (std::is_convertible_v) { + return '"' + std::string{std::string_view{val}} + '"'; + } else if constexpr (IsContainer) { + std::stringstream out; + out << "{"; + const auto size = val.size(); + if (size > 1) { + out << repr(*val.begin()); + std::for_each( + std::next(val.begin()), + std::next( + val.begin(), + static_cast( + std::min(size, repr_max_container_size) - 1)), + [&out](const auto &v) { out << " " << repr(v); }); + if (size <= repr_max_container_size) { + out << " "; + } else { + out << "..."; + } + } + if (size > 0) { + out << repr(*std::prev(val.end())); + } + out << "}"; + return out.str(); + } else if constexpr (IsStreamable) { + std::stringstream out; + out << val; + return out.str(); + } else { + return ""; + } +} + +namespace { + +template constexpr bool standard_signed_integer = false; +template <> constexpr bool standard_signed_integer = true; +template <> constexpr bool standard_signed_integer = true; +template <> constexpr bool standard_signed_integer = true; +template <> constexpr bool standard_signed_integer = true; +template <> constexpr bool standard_signed_integer = true; + +template constexpr bool standard_unsigned_integer = false; +template <> constexpr bool standard_unsigned_integer = true; +template <> constexpr bool standard_unsigned_integer = true; +template <> constexpr bool standard_unsigned_integer = true; +template <> constexpr bool standard_unsigned_integer = true; +template <> +constexpr bool standard_unsigned_integer = true; + +} // namespace + +constexpr int radix_2 = 2; +constexpr int radix_8 = 8; +constexpr int radix_10 = 10; +constexpr int radix_16 = 16; + +template +constexpr bool standard_integer = + standard_signed_integer || standard_unsigned_integer; + +template +constexpr decltype(auto) +apply_plus_one_impl(F &&f, Tuple &&t, Extra &&x, + std::index_sequence /*unused*/) { + return std::invoke(std::forward(f), std::get(std::forward(t))..., + std::forward(x)); +} + +template +constexpr decltype(auto) apply_plus_one(F &&f, Tuple &&t, Extra &&x) { + return details::apply_plus_one_impl( + std::forward(f), std::forward(t), std::forward(x), + std::make_index_sequence< + std::tuple_size_v>>{}); +} + +constexpr auto pointer_range(std::string_view s) noexcept { + return std::tuple(s.data(), s.data() + s.size()); +} + +template +constexpr bool starts_with(std::basic_string_view prefix, + std::basic_string_view s) noexcept { + return s.substr(0, prefix.size()) == prefix; +} + +enum class chars_format { + scientific = 0xf1, + fixed = 0xf2, + hex = 0xf4, + binary = 0xf8, + general = fixed | scientific +}; + +struct ConsumeBinaryPrefixResult { + bool is_binary; + std::string_view rest; +}; + +constexpr auto consume_binary_prefix(std::string_view s) + -> ConsumeBinaryPrefixResult { + if (starts_with(std::string_view{"0b"}, s) || + starts_with(std::string_view{"0B"}, s)) { + s.remove_prefix(2); + return {true, s}; + } + return {false, s}; +} + +struct ConsumeHexPrefixResult { + bool is_hexadecimal; + std::string_view rest; +}; + +using namespace std::literals; + +constexpr auto consume_hex_prefix(std::string_view s) + -> ConsumeHexPrefixResult { + if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) { + s.remove_prefix(2); + return {true, s}; + } + return {false, s}; +} + +template +inline auto do_from_chars(std::string_view s) -> T { + T x; + auto [first, last] = pointer_range(s); + auto [ptr, ec] = std::from_chars(first, last, x, Param); + if (ec == std::errc()) { + if (ptr == last) { + return x; + } + throw std::invalid_argument{"pattern '" + std::string(s) + + "' does not match to the end"}; + } + if (ec == std::errc::invalid_argument) { + throw std::invalid_argument{"pattern '" + std::string(s) + "' not found"}; + } + if (ec == std::errc::result_out_of_range) { + throw std::range_error{"'" + std::string(s) + "' not representable"}; + } + return x; // unreachable +} + +template struct parse_number { + auto operator()(std::string_view s) -> T { + return do_from_chars(s); + } +}; + +template struct parse_number { + auto operator()(std::string_view s) -> T { + if (auto [ok, rest] = consume_binary_prefix(s); ok) { + return do_from_chars(rest); + } + throw std::invalid_argument{"pattern not found"}; + } +}; + +template struct parse_number { + auto operator()(std::string_view s) -> T { + if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) { + if (auto [ok, rest] = consume_hex_prefix(s); ok) { + try { + return do_from_chars(rest); + } catch (const std::invalid_argument &err) { + throw std::invalid_argument("Failed to parse '" + std::string(s) + + "' as hexadecimal: " + err.what()); + } catch (const std::range_error &err) { + throw std::range_error("Failed to parse '" + std::string(s) + + "' as hexadecimal: " + err.what()); + } + } + } else { + // Allow passing hex numbers without prefix + // Shape 'x' already has to be specified + try { + return do_from_chars(s); + } catch (const std::invalid_argument &err) { + throw std::invalid_argument("Failed to parse '" + std::string(s) + + "' as hexadecimal: " + err.what()); + } catch (const std::range_error &err) { + throw std::range_error("Failed to parse '" + std::string(s) + + "' as hexadecimal: " + err.what()); + } + } + + throw std::invalid_argument{"pattern '" + std::string(s) + + "' not identified as hexadecimal"}; + } +}; + +template struct parse_number { + auto operator()(std::string_view s) -> T { + auto [ok, rest] = consume_hex_prefix(s); + if (ok) { + try { + return do_from_chars(rest); + } catch (const std::invalid_argument &err) { + throw std::invalid_argument("Failed to parse '" + std::string(s) + + "' as hexadecimal: " + err.what()); + } catch (const std::range_error &err) { + throw std::range_error("Failed to parse '" + std::string(s) + + "' as hexadecimal: " + err.what()); + } + } + + auto [ok_binary, rest_binary] = consume_binary_prefix(s); + if (ok_binary) { + try { + return do_from_chars(rest_binary); + } catch (const std::invalid_argument &err) { + throw std::invalid_argument("Failed to parse '" + std::string(s) + + "' as binary: " + err.what()); + } catch (const std::range_error &err) { + throw std::range_error("Failed to parse '" + std::string(s) + + "' as binary: " + err.what()); + } + } + + if (starts_with("0"sv, s)) { + try { + return do_from_chars(rest); + } catch (const std::invalid_argument &err) { + throw std::invalid_argument("Failed to parse '" + std::string(s) + + "' as octal: " + err.what()); + } catch (const std::range_error &err) { + throw std::range_error("Failed to parse '" + std::string(s) + + "' as octal: " + err.what()); + } + } + + try { + return do_from_chars(rest); + } catch (const std::invalid_argument &err) { + throw std::invalid_argument("Failed to parse '" + std::string(s) + + "' as decimal integer: " + err.what()); + } catch (const std::range_error &err) { + throw std::range_error("Failed to parse '" + std::string(s) + + "' as decimal integer: " + err.what()); + } + } +}; + +namespace { + +template inline const auto generic_strtod = nullptr; +template <> inline const auto generic_strtod = ARGPARSE_CUSTOM_STRTOF; +template <> inline const auto generic_strtod = ARGPARSE_CUSTOM_STRTOD; +template <> +inline const auto generic_strtod = ARGPARSE_CUSTOM_STRTOLD; + +} // namespace + +template inline auto do_strtod(std::string const &s) -> T { + if (isspace(static_cast(s[0])) || s[0] == '+') { + throw std::invalid_argument{"pattern '" + s + "' not found"}; + } + + auto [first, last] = pointer_range(s); + char *ptr; + + errno = 0; + auto x = generic_strtod(first, &ptr); + if (errno == 0) { + if (ptr == last) { + return x; + } + throw std::invalid_argument{"pattern '" + s + + "' does not match to the end"}; + } + if (errno == ERANGE) { + throw std::range_error{"'" + s + "' not representable"}; + } + return x; // unreachable +} + +template struct parse_number { + auto operator()(std::string const &s) -> T { + if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { + throw std::invalid_argument{ + "chars_format::general does not parse hexfloat"}; + } + if (auto r = consume_binary_prefix(s); r.is_binary) { + throw std::invalid_argument{ + "chars_format::general does not parse binfloat"}; + } + + try { + return do_strtod(s); + } catch (const std::invalid_argument &err) { + throw std::invalid_argument("Failed to parse '" + s + + "' as number: " + err.what()); + } catch (const std::range_error &err) { + throw std::range_error("Failed to parse '" + s + + "' as number: " + err.what()); + } + } +}; + +template struct parse_number { + auto operator()(std::string const &s) -> T { + if (auto r = consume_hex_prefix(s); !r.is_hexadecimal) { + throw std::invalid_argument{"chars_format::hex parses hexfloat"}; + } + if (auto r = consume_binary_prefix(s); r.is_binary) { + throw std::invalid_argument{"chars_format::hex does not parse binfloat"}; + } + + try { + return do_strtod(s); + } catch (const std::invalid_argument &err) { + throw std::invalid_argument("Failed to parse '" + s + + "' as hexadecimal: " + err.what()); + } catch (const std::range_error &err) { + throw std::range_error("Failed to parse '" + s + + "' as hexadecimal: " + err.what()); + } + } +}; + +template struct parse_number { + auto operator()(std::string const &s) -> T { + if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { + throw std::invalid_argument{ + "chars_format::binary does not parse hexfloat"}; + } + if (auto r = consume_binary_prefix(s); !r.is_binary) { + throw std::invalid_argument{"chars_format::binary parses binfloat"}; + } + + return do_strtod(s); + } +}; + +template struct parse_number { + auto operator()(std::string const &s) -> T { + if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { + throw std::invalid_argument{ + "chars_format::scientific does not parse hexfloat"}; + } + if (auto r = consume_binary_prefix(s); r.is_binary) { + throw std::invalid_argument{ + "chars_format::scientific does not parse binfloat"}; + } + if (s.find_first_of("eE") == std::string::npos) { + throw std::invalid_argument{ + "chars_format::scientific requires exponent part"}; + } + + try { + return do_strtod(s); + } catch (const std::invalid_argument &err) { + throw std::invalid_argument("Failed to parse '" + s + + "' as scientific notation: " + err.what()); + } catch (const std::range_error &err) { + throw std::range_error("Failed to parse '" + s + + "' as scientific notation: " + err.what()); + } + } +}; + +template struct parse_number { + auto operator()(std::string const &s) -> T { + if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { + throw std::invalid_argument{ + "chars_format::fixed does not parse hexfloat"}; + } + if (auto r = consume_binary_prefix(s); r.is_binary) { + throw std::invalid_argument{ + "chars_format::fixed does not parse binfloat"}; + } + if (s.find_first_of("eE") != std::string::npos) { + throw std::invalid_argument{ + "chars_format::fixed does not parse exponent part"}; + } + + try { + return do_strtod(s); + } catch (const std::invalid_argument &err) { + throw std::invalid_argument("Failed to parse '" + s + + "' as fixed notation: " + err.what()); + } catch (const std::range_error &err) { + throw std::range_error("Failed to parse '" + s + + "' as fixed notation: " + err.what()); + } + } +}; + +template +std::string join(StrIt first, StrIt last, const std::string &separator) { + if (first == last) { + return ""; + } + std::stringstream value; + value << *first; + ++first; + while (first != last) { + value << separator << *first; + ++first; + } + return value.str(); +} + +template struct can_invoke_to_string { + template + static auto test(int) + -> decltype(std::to_string(std::declval()), std::true_type{}); + + template static auto test(...) -> std::false_type; + + static constexpr bool value = decltype(test(0))::value; +}; + +template struct IsChoiceTypeSupported { + using CleanType = typename std::decay::type; + static const bool value = std::is_integral::value || + std::is_same::value || + std::is_same::value || + std::is_same::value; +}; + +template +std::size_t get_levenshtein_distance(const StringType &s1, + const StringType &s2) { + std::vector> dp( + s1.size() + 1, std::vector(s2.size() + 1, 0)); + + for (std::size_t i = 0; i <= s1.size(); ++i) { + for (std::size_t j = 0; j <= s2.size(); ++j) { + if (i == 0) { + dp[i][j] = j; + } else if (j == 0) { + dp[i][j] = i; + } else if (s1[i - 1] == s2[j - 1]) { + dp[i][j] = dp[i - 1][j - 1]; + } else { + dp[i][j] = 1 + std::min({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]}); + } + } + } + + return dp[s1.size()][s2.size()]; +} + +template +std::string get_most_similar_string(const std::map &map, + const std::string &input) { + std::string most_similar{}; + std::size_t min_distance = std::numeric_limits::max(); + + for (const auto &entry : map) { + std::size_t distance = get_levenshtein_distance(entry.first, input); + if (distance < min_distance) { + min_distance = distance; + most_similar = entry.first; + } + } + + return most_similar; +} + +} // namespace details + +enum class nargs_pattern { optional, any, at_least_one }; + +enum class default_arguments : unsigned int { + none = 0, + help = 1, + version = 2, + all = help | version, +}; + +inline default_arguments operator&(const default_arguments &a, + const default_arguments &b) { + return static_cast( + static_cast::type>(a) & + static_cast::type>(b)); +} + +class ArgumentParser; + +class Argument { + friend class ArgumentParser; + friend auto operator<<(std::ostream &stream, const ArgumentParser &parser) + -> std::ostream &; + + template + explicit Argument(std::string_view prefix_chars, + std::array &&a, + std::index_sequence /*unused*/) + : m_accepts_optional_like_value(false), + m_is_optional((is_optional(a[I], prefix_chars) || ...)), + m_is_required(false), m_is_repeatable(false), m_is_used(false), + m_is_hidden(false), m_prefix_chars(prefix_chars) { + ((void)m_names.emplace_back(a[I]), ...); + std::sort( + m_names.begin(), m_names.end(), [](const auto &lhs, const auto &rhs) { + return lhs.size() == rhs.size() ? lhs < rhs : lhs.size() < rhs.size(); + }); + } + +public: + template + explicit Argument(std::string_view prefix_chars, + std::array &&a) + : Argument(prefix_chars, std::move(a), std::make_index_sequence{}) {} + + Argument &help(std::string help_text) { + m_help = std::move(help_text); + return *this; + } + + Argument &metavar(std::string metavar) { + m_metavar = std::move(metavar); + return *this; + } + + template Argument &default_value(T &&value) { + m_num_args_range = NArgsRange{0, m_num_args_range.get_max()}; + m_default_value_repr = details::repr(value); + + if constexpr (std::is_convertible_v) { + m_default_value_str = std::string{std::string_view{value}}; + } else if constexpr (details::can_invoke_to_string::value) { + m_default_value_str = std::to_string(value); + } + + m_default_value = std::forward(value); + return *this; + } + + Argument &default_value(const char *value) { + return default_value(std::string(value)); + } + + Argument &required() { + m_is_required = true; + return *this; + } + + Argument &implicit_value(std::any value) { + m_implicit_value = std::move(value); + m_num_args_range = NArgsRange{0, 0}; + return *this; + } + + // This is shorthand for: + // program.add_argument("foo") + // .default_value(false) + // .implicit_value(true) + Argument &flag() { + default_value(false); + implicit_value(true); + return *this; + } + + template + auto action(F &&callable, Args &&... bound_args) + -> std::enable_if_t, + Argument &> { + using action_type = std::conditional_t< + std::is_void_v>, + void_action, valued_action>; + if constexpr (sizeof...(Args) == 0) { + m_action.emplace(std::forward(callable)); + } else { + m_action.emplace( + [f = std::forward(callable), + tup = std::make_tuple(std::forward(bound_args)...)]( + std::string const &opt) mutable { + return details::apply_plus_one(f, tup, opt); + }); + } + return *this; + } + + auto &store_into(bool &var) { + flag(); + if (m_default_value.has_value()) { + var = std::any_cast(m_default_value); + } + action([&var](const auto & /*unused*/) { var = true; }); + return *this; + } + + template ::value>::type * = nullptr> + auto &store_into(T &var) { + if (m_default_value.has_value()) { + var = std::any_cast(m_default_value); + } + action([&var](const auto &s) { + var = details::parse_number()(s); + }); + return *this; + } + + auto &store_into(double &var) { + if (m_default_value.has_value()) { + var = std::any_cast(m_default_value); + } + action([&var](const auto &s) { + var = details::parse_number()(s); + }); + return *this; + } + + auto &store_into(std::string &var) { + if (m_default_value.has_value()) { + var = std::any_cast(m_default_value); + } + action([&var](const std::string &s) { var = s; }); + return *this; + } + + auto &store_into(std::vector &var) { + if (m_default_value.has_value()) { + var = std::any_cast>(m_default_value); + } + action([this, &var](const std::string &s) { + if (!m_is_used) { + var.clear(); + } + m_is_used = true; + var.push_back(s); + }); + return *this; + } + + auto &store_into(std::vector &var) { + if (m_default_value.has_value()) { + var = std::any_cast>(m_default_value); + } + action([this, &var](const std::string &s) { + if (!m_is_used) { + var.clear(); + } + m_is_used = true; + var.push_back(details::parse_number()(s)); + }); + return *this; + } + + auto &append() { + m_is_repeatable = true; + return *this; + } + + // Cause the argument to be invisible in usage and help + auto &hidden() { + m_is_hidden = true; + return *this; + } + + template + auto scan() -> std::enable_if_t, Argument &> { + static_assert(!(std::is_const_v || std::is_volatile_v), + "T should not be cv-qualified"); + auto is_one_of = [](char c, auto... x) constexpr { + return ((c == x) || ...); + }; + + if constexpr (is_one_of(Shape, 'd') && details::standard_integer) { + action(details::parse_number()); + } else if constexpr (is_one_of(Shape, 'i') && + details::standard_integer) { + action(details::parse_number()); + } else if constexpr (is_one_of(Shape, 'u') && + details::standard_unsigned_integer) { + action(details::parse_number()); + } else if constexpr (is_one_of(Shape, 'b') && + details::standard_unsigned_integer) { + action(details::parse_number()); + } else if constexpr (is_one_of(Shape, 'o') && + details::standard_unsigned_integer) { + action(details::parse_number()); + } else if constexpr (is_one_of(Shape, 'x', 'X') && + details::standard_unsigned_integer) { + action(details::parse_number()); + } else if constexpr (is_one_of(Shape, 'a', 'A') && + std::is_floating_point_v) { + action(details::parse_number()); + } else if constexpr (is_one_of(Shape, 'e', 'E') && + std::is_floating_point_v) { + action(details::parse_number()); + } else if constexpr (is_one_of(Shape, 'f', 'F') && + std::is_floating_point_v) { + action(details::parse_number()); + } else if constexpr (is_one_of(Shape, 'g', 'G') && + std::is_floating_point_v) { + action(details::parse_number()); + } else { + static_assert(alignof(T) == 0, "No scan specification for T"); + } + + return *this; + } + + Argument &nargs(std::size_t num_args) { + m_num_args_range = NArgsRange{num_args, num_args}; + return *this; + } + + Argument &nargs(std::size_t num_args_min, std::size_t num_args_max) { + m_num_args_range = NArgsRange{num_args_min, num_args_max}; + return *this; + } + + Argument &nargs(nargs_pattern pattern) { + switch (pattern) { + case nargs_pattern::optional: + m_num_args_range = NArgsRange{0, 1}; + break; + case nargs_pattern::any: + m_num_args_range = + NArgsRange{0, (std::numeric_limits::max)()}; + break; + case nargs_pattern::at_least_one: + m_num_args_range = + NArgsRange{1, (std::numeric_limits::max)()}; + break; + } + return *this; + } + + Argument &remaining() { + m_accepts_optional_like_value = true; + return nargs(nargs_pattern::any); + } + + template void add_choice(T &&choice) { + static_assert(details::IsChoiceTypeSupported::value, + "Only string or integer type supported for choice"); + static_assert(std::is_convertible_v || + details::can_invoke_to_string::value, + "Choice is not convertible to string_type"); + if (!m_choices.has_value()) { + m_choices = std::vector{}; + } + + if constexpr (std::is_convertible_v) { + m_choices.value().push_back( + std::string{std::string_view{std::forward(choice)}}); + } else if constexpr (details::can_invoke_to_string::value) { + m_choices.value().push_back(std::to_string(std::forward(choice))); + } + } + + Argument &choices() { + if (!m_choices.has_value()) { + throw std::runtime_error("Zero choices provided"); + } + return *this; + } + + template + Argument &choices(T &&first, U &&... rest) { + add_choice(std::forward(first)); + choices(std::forward(rest)...); + return *this; + } + + void find_default_value_in_choices_or_throw() const { + + const auto &choices = m_choices.value(); + + if (m_default_value.has_value()) { + if (std::find(choices.begin(), choices.end(), m_default_value_str) == + choices.end()) { + // provided arg not in list of allowed choices + // report error + + std::string choices_as_csv = + std::accumulate(choices.begin(), choices.end(), std::string(), + [](const std::string &a, const std::string &b) { + return a + (a.empty() ? "" : ", ") + b; + }); + + throw std::runtime_error( + std::string{"Invalid default value "} + m_default_value_repr + + " - allowed options: {" + choices_as_csv + "}"); + } + } + } + + template + void find_value_in_choices_or_throw(Iterator it) const { + + const auto &choices = m_choices.value(); + + if (std::find(choices.begin(), choices.end(), *it) == choices.end()) { + // provided arg not in list of allowed choices + // report error + + std::string choices_as_csv = + std::accumulate(choices.begin(), choices.end(), std::string(), + [](const std::string &a, const std::string &b) { + return a + (a.empty() ? "" : ", ") + b; + }); + + throw std::runtime_error(std::string{"Invalid argument "} + + details::repr(*it) + " - allowed options: {" + + choices_as_csv + "}"); + } + } + + /* The dry_run parameter can be set to true to avoid running the actions, + * and setting m_is_used. This may be used by a pre-processing step to do + * a first iteration over arguments. + */ + template + Iterator consume(Iterator start, Iterator end, + std::string_view used_name = {}, bool dry_run = false) { + if (!m_is_repeatable && m_is_used) { + throw std::runtime_error( + std::string("Duplicate argument ").append(used_name)); + } + m_used_name = used_name; + + if (m_choices.has_value()) { + // Check each value in (start, end) and make sure + // it is in the list of allowed choices/options + std::size_t i = 0; + auto max_number_of_args = m_num_args_range.get_max(); + for (auto it = start; it != end; ++it) { + if (i == max_number_of_args) { + break; + } + find_value_in_choices_or_throw(it); + i += 1; + } + } + + const auto num_args_max = m_num_args_range.get_max(); + const auto num_args_min = m_num_args_range.get_min(); + std::size_t dist = 0; + if (num_args_max == 0) { + if (!dry_run) { + m_values.emplace_back(m_implicit_value); + std::visit([](const auto &f) { f({}); }, m_action); + m_is_used = true; + } + return start; + } + if ((dist = static_cast(std::distance(start, end))) >= + num_args_min) { + if (num_args_max < dist) { + end = std::next(start, static_cast( + num_args_max)); + } + if (!m_accepts_optional_like_value) { + end = std::find_if( + start, end, + std::bind(is_optional, std::placeholders::_1, m_prefix_chars)); + dist = static_cast(std::distance(start, end)); + if (dist < num_args_min) { + throw std::runtime_error("Too few arguments"); + } + } + + struct ActionApply { + void operator()(valued_action &f) { + std::transform(first, last, std::back_inserter(self.m_values), f); + } + + void operator()(void_action &f) { + std::for_each(first, last, f); + if (!self.m_default_value.has_value()) { + if (!self.m_accepts_optional_like_value) { + self.m_values.resize( + static_cast(std::distance(first, last))); + } + } + } + + Iterator first, last; + Argument &self; + }; + if (!dry_run) { + std::visit(ActionApply{start, end, *this}, m_action); + m_is_used = true; + } + return end; + } + if (m_default_value.has_value()) { + if (!dry_run) { + m_is_used = true; + } + return start; + } + throw std::runtime_error("Too few arguments for '" + + std::string(m_used_name) + "'."); + } + + /* + * @throws std::runtime_error if argument values are not valid + */ + void validate() const { + if (m_is_optional) { + // TODO: check if an implicit value was programmed for this argument + if (!m_is_used && !m_default_value.has_value() && m_is_required) { + throw_required_arg_not_used_error(); + } + if (m_is_used && m_is_required && m_values.empty()) { + throw_required_arg_no_value_provided_error(); + } + } else { + if (!m_num_args_range.contains(m_values.size()) && + !m_default_value.has_value()) { + throw_nargs_range_validation_error(); + } + } + + if (m_choices.has_value()) { + // Make sure the default value (if provided) + // is in the list of choices + find_default_value_in_choices_or_throw(); + } + } + + std::string get_names_csv(char separator = ',') const { + return std::accumulate( + m_names.begin(), m_names.end(), std::string{""}, + [&](const std::string &result, const std::string &name) { + return result.empty() ? name : result + separator + name; + }); + } + + std::string get_usage_full() const { + std::stringstream usage; + + usage << get_names_csv('/'); + const std::string metavar = !m_metavar.empty() ? m_metavar : "VAR"; + if (m_num_args_range.get_max() > 0) { + usage << " " << metavar; + if (m_num_args_range.get_max() > 1) { + usage << "..."; + } + } + return usage.str(); + } + + std::string get_inline_usage() const { + std::stringstream usage; + // Find the longest variant to show in the usage string + std::string longest_name = m_names.front(); + for (const auto &s : m_names) { + if (s.size() > longest_name.size()) { + longest_name = s; + } + } + if (!m_is_required) { + usage << "["; + } + usage << longest_name; + const std::string metavar = !m_metavar.empty() ? m_metavar : "VAR"; + if (m_num_args_range.get_max() > 0) { + usage << " " << metavar; + if (m_num_args_range.get_max() > 1 && + m_metavar.find("> <") == std::string::npos) { + usage << "..."; + } + } + if (!m_is_required) { + usage << "]"; + } + if (m_is_repeatable) { + usage << "..."; + } + return usage.str(); + } + + std::size_t get_arguments_length() const { + + std::size_t names_size = std::accumulate( + std::begin(m_names), std::end(m_names), std::size_t(0), + [](const auto &sum, const auto &s) { return sum + s.size(); }); + + if (is_positional(m_names.front(), m_prefix_chars)) { + // A set metavar means this replaces the names + if (!m_metavar.empty()) { + // Indent and metavar + return 2 + m_metavar.size(); + } + + // Indent and space-separated + return 2 + names_size + (m_names.size() - 1); + } + // Is an option - include both names _and_ metavar + // size = text + (", " between names) + std::size_t size = names_size + 2 * (m_names.size() - 1); + if (!m_metavar.empty() && m_num_args_range == NArgsRange{1, 1}) { + size += m_metavar.size() + 1; + } + return size + 2; // indent + } + + friend std::ostream &operator<<(std::ostream &stream, + const Argument &argument) { + std::stringstream name_stream; + name_stream << " "; // indent + if (argument.is_positional(argument.m_names.front(), + argument.m_prefix_chars)) { + if (!argument.m_metavar.empty()) { + name_stream << argument.m_metavar; + } else { + name_stream << details::join(argument.m_names.begin(), + argument.m_names.end(), " "); + } + } else { + name_stream << details::join(argument.m_names.begin(), + argument.m_names.end(), ", "); + // If we have a metavar, and one narg - print the metavar + if (!argument.m_metavar.empty() && + argument.m_num_args_range == NArgsRange{1, 1}) { + name_stream << " " << argument.m_metavar; + } + else if (!argument.m_metavar.empty() && + argument.m_num_args_range.get_min() == argument.m_num_args_range.get_max() && + argument.m_metavar.find("> <") != std::string::npos) { + name_stream << " " << argument.m_metavar; + } + } + + // align multiline help message + auto stream_width = stream.width(); + auto name_padding = std::string(name_stream.str().size(), ' '); + auto pos = std::string::size_type{}; + auto prev = std::string::size_type{}; + auto first_line = true; + auto hspace = " "; // minimal space between name and help message + stream << name_stream.str(); + std::string_view help_view(argument.m_help); + while ((pos = argument.m_help.find('\n', prev)) != std::string::npos) { + auto line = help_view.substr(prev, pos - prev + 1); + if (first_line) { + stream << hspace << line; + first_line = false; + } else { + stream.width(stream_width); + stream << name_padding << hspace << line; + } + prev += pos - prev + 1; + } + if (first_line) { + stream << hspace << argument.m_help; + } else { + auto leftover = help_view.substr(prev, argument.m_help.size() - prev); + if (!leftover.empty()) { + stream.width(stream_width); + stream << name_padding << hspace << leftover; + } + } + + // print nargs spec + if (!argument.m_help.empty()) { + stream << " "; + } + stream << argument.m_num_args_range; + + bool add_space = false; + if (argument.m_default_value.has_value() && + argument.m_num_args_range != NArgsRange{0, 0}) { + stream << "[default: " << argument.m_default_value_repr << "]"; + add_space = true; + } else if (argument.m_is_required) { + stream << "[required]"; + add_space = true; + } + if (argument.m_is_repeatable) { + if (add_space) { + stream << " "; + } + stream << "[may be repeated]"; + } + stream << "\n"; + return stream; + } + + template bool operator!=(const T &rhs) const { + return !(*this == rhs); + } + + /* + * Compare to an argument value of known type + * @throws std::logic_error in case of incompatible types + */ + template bool operator==(const T &rhs) const { + if constexpr (!details::IsContainer) { + return get() == rhs; + } else { + using ValueType = typename T::value_type; + auto lhs = get(); + return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs), + std::end(rhs), [](const auto &a, const auto &b) { + return std::any_cast(a) == b; + }); + } + } + + /* + * positional: + * _empty_ + * '-' + * '-' decimal-literal + * !'-' anything + */ + static bool is_positional(std::string_view name, + std::string_view prefix_chars) { + auto first = lookahead(name); + + if (first == eof) { + return true; + } + if (prefix_chars.find(static_cast(first)) != + std::string_view::npos) { + name.remove_prefix(1); + if (name.empty()) { + return true; + } + return is_decimal_literal(name); + } + return true; + } + +private: + class NArgsRange { + std::size_t m_min; + std::size_t m_max; + + public: + NArgsRange(std::size_t minimum, std::size_t maximum) + : m_min(minimum), m_max(maximum) { + if (minimum > maximum) { + throw std::logic_error("Range of number of arguments is invalid"); + } + } + + bool contains(std::size_t value) const { + return value >= m_min && value <= m_max; + } + + bool is_exact() const { return m_min == m_max; } + + bool is_right_bounded() const { + return m_max < (std::numeric_limits::max)(); + } + + std::size_t get_min() const { return m_min; } + + std::size_t get_max() const { return m_max; } + + // Print help message + friend auto operator<<(std::ostream &stream, const NArgsRange &range) + -> std::ostream & { + if (range.m_min == range.m_max) { + if (range.m_min != 0 && range.m_min != 1) { + stream << "[nargs: " << range.m_min << "] "; + } + } else { + if (range.m_max == (std::numeric_limits::max)()) { + stream << "[nargs: " << range.m_min << " or more] "; + } else { + stream << "[nargs=" << range.m_min << ".." << range.m_max << "] "; + } + } + return stream; + } + + bool operator==(const NArgsRange &rhs) const { + return rhs.m_min == m_min && rhs.m_max == m_max; + } + + bool operator!=(const NArgsRange &rhs) const { return !(*this == rhs); } + }; + + void throw_nargs_range_validation_error() const { + std::stringstream stream; + if (!m_used_name.empty()) { + stream << m_used_name << ": "; + } else { + stream << m_names.front() << ": "; + } + if (m_num_args_range.is_exact()) { + stream << m_num_args_range.get_min(); + } else if (m_num_args_range.is_right_bounded()) { + stream << m_num_args_range.get_min() << " to " + << m_num_args_range.get_max(); + } else { + stream << m_num_args_range.get_min() << " or more"; + } + stream << " argument(s) expected. " << m_values.size() << " provided."; + throw std::runtime_error(stream.str()); + } + + void throw_required_arg_not_used_error() const { + std::stringstream stream; + stream << m_names.front() << ": required."; + throw std::runtime_error(stream.str()); + } + + void throw_required_arg_no_value_provided_error() const { + std::stringstream stream; + stream << m_used_name << ": no value provided."; + throw std::runtime_error(stream.str()); + } + + static constexpr int eof = std::char_traits::eof(); + + static auto lookahead(std::string_view s) -> int { + if (s.empty()) { + return eof; + } + return static_cast(static_cast(s[0])); + } + + /* + * decimal-literal: + * '0' + * nonzero-digit digit-sequence_opt + * integer-part fractional-part + * fractional-part + * integer-part '.' exponent-part_opt + * integer-part exponent-part + * + * integer-part: + * digit-sequence + * + * fractional-part: + * '.' post-decimal-point + * + * post-decimal-point: + * digit-sequence exponent-part_opt + * + * exponent-part: + * 'e' post-e + * 'E' post-e + * + * post-e: + * sign_opt digit-sequence + * + * sign: one of + * '+' '-' + */ + static bool is_decimal_literal(std::string_view s) { + auto is_digit = [](auto c) constexpr { + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return true; + default: + return false; + } + }; + + // precondition: we have consumed or will consume at least one digit + auto consume_digits = [=](std::string_view sd) { + // NOLINTNEXTLINE(readability-qualified-auto) + auto it = std::find_if_not(std::begin(sd), std::end(sd), is_digit); + return sd.substr(static_cast(it - std::begin(sd))); + }; + + switch (lookahead(s)) { + case '0': { + s.remove_prefix(1); + if (s.empty()) { + return true; + } + goto integer_part; + } + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + s = consume_digits(s); + if (s.empty()) { + return true; + } + goto integer_part_consumed; + } + case '.': { + s.remove_prefix(1); + goto post_decimal_point; + } + default: + return false; + } + + integer_part: + s = consume_digits(s); + integer_part_consumed: + switch (lookahead(s)) { + case '.': { + s.remove_prefix(1); + if (is_digit(lookahead(s))) { + goto post_decimal_point; + } else { + goto exponent_part_opt; + } + } + case 'e': + case 'E': { + s.remove_prefix(1); + goto post_e; + } + default: + return false; + } + + post_decimal_point: + if (is_digit(lookahead(s))) { + s = consume_digits(s); + goto exponent_part_opt; + } + return false; + + exponent_part_opt: + switch (lookahead(s)) { + case eof: + return true; + case 'e': + case 'E': { + s.remove_prefix(1); + goto post_e; + } + default: + return false; + } + + post_e: + switch (lookahead(s)) { + case '-': + case '+': + s.remove_prefix(1); + } + if (is_digit(lookahead(s))) { + s = consume_digits(s); + return s.empty(); + } + return false; + } + + static bool is_optional(std::string_view name, + std::string_view prefix_chars) { + return !is_positional(name, prefix_chars); + } + + /* + * Get argument value given a type + * @throws std::logic_error in case of incompatible types + */ + template T get() const { + if (!m_values.empty()) { + if constexpr (details::IsContainer) { + return any_cast_container(m_values); + } else { + return std::any_cast(m_values.front()); + } + } + if (m_default_value.has_value()) { + return std::any_cast(m_default_value); + } + if constexpr (details::IsContainer) { + if (!m_accepts_optional_like_value) { + return any_cast_container(m_values); + } + } + + throw std::logic_error("No value provided for '" + m_names.back() + "'."); + } + + /* + * Get argument value given a type. + * @pre The object has no default value. + * @returns The stored value if any, std::nullopt otherwise. + */ + template auto present() const -> std::optional { + if (m_default_value.has_value()) { + throw std::logic_error("Argument with default value always presents"); + } + if (m_values.empty()) { + return std::nullopt; + } + if constexpr (details::IsContainer) { + return any_cast_container(m_values); + } + return std::any_cast(m_values.front()); + } + + template + static auto any_cast_container(const std::vector &operand) -> T { + using ValueType = typename T::value_type; + + T result; + std::transform( + std::begin(operand), std::end(operand), std::back_inserter(result), + [](const auto &value) { return std::any_cast(value); }); + return result; + } + + void set_usage_newline_counter(int i) { m_usage_newline_counter = i; } + + void set_group_idx(std::size_t i) { m_group_idx = i; } + + std::vector m_names; + std::string_view m_used_name; + std::string m_help; + std::string m_metavar; + std::any m_default_value; + std::string m_default_value_repr; + std::optional + m_default_value_str; // used for checking default_value against choices + std::any m_implicit_value; + std::optional> m_choices{std::nullopt}; + using valued_action = std::function; + using void_action = std::function; + std::variant m_action{ + std::in_place_type, + [](const std::string &value) { return value; }}; + std::vector m_values; + NArgsRange m_num_args_range{1, 1}; + // Bit field of bool values. Set default value in ctor. + bool m_accepts_optional_like_value : 1; + bool m_is_optional : 1; + bool m_is_required : 1; + bool m_is_repeatable : 1; + bool m_is_used : 1; + bool m_is_hidden : 1; // if set, does not appear in usage or help + std::string_view m_prefix_chars; // ArgumentParser has the prefix_chars + int m_usage_newline_counter = 0; + std::size_t m_group_idx = 0; +}; + +class ArgumentParser { +public: + explicit ArgumentParser(std::string program_name = {}, + std::string version = "1.0", + default_arguments add_args = default_arguments::all, + bool exit_on_default_arguments = true, + std::ostream &os = std::cout) + : m_program_name(std::move(program_name)), m_version(std::move(version)), + m_exit_on_default_arguments(exit_on_default_arguments), + m_parser_path(m_program_name) { + if ((add_args & default_arguments::help) == default_arguments::help) { + add_argument("-h", "--help") + .action([&](const auto & /*unused*/) { + os << help().str(); + if (m_exit_on_default_arguments) { + std::exit(0); + } + }) + .default_value(false) + .help("shows help message and exits") + .implicit_value(true) + .nargs(0); + } + if ((add_args & default_arguments::version) == default_arguments::version) { + add_argument("-v", "--version") + .action([&](const auto & /*unused*/) { + os << m_version << std::endl; + if (m_exit_on_default_arguments) { + std::exit(0); + } + }) + .default_value(false) + .help("prints version information and exits") + .implicit_value(true) + .nargs(0); + } + } + + ~ArgumentParser() = default; + + // ArgumentParser is meant to be used in a single function. + // Setup everything and parse arguments in one place. + // + // ArgumentParser internally uses std::string_views, + // references, iterators, etc. + // Many of these elements become invalidated after a copy or move. + ArgumentParser(const ArgumentParser &other) = delete; + ArgumentParser &operator=(const ArgumentParser &other) = delete; + ArgumentParser(ArgumentParser &&) noexcept = delete; + ArgumentParser &operator=(ArgumentParser &&) = delete; + + explicit operator bool() const { + auto arg_used = std::any_of(m_argument_map.cbegin(), m_argument_map.cend(), + [](auto &it) { return it.second->m_is_used; }); + auto subparser_used = + std::any_of(m_subparser_used.cbegin(), m_subparser_used.cend(), + [](auto &it) { return it.second; }); + + return m_is_parsed && (arg_used || subparser_used); + } + + // Parameter packing + // Call add_argument with variadic number of string arguments + template Argument &add_argument(Targs... f_args) { + using array_of_sv = std::array; + auto argument = + m_optional_arguments.emplace(std::cend(m_optional_arguments), + m_prefix_chars, array_of_sv{f_args...}); + + if (!argument->m_is_optional) { + m_positional_arguments.splice(std::cend(m_positional_arguments), + m_optional_arguments, argument); + } + argument->set_usage_newline_counter(m_usage_newline_counter); + argument->set_group_idx(m_group_names.size()); + + index_argument(argument); + return *argument; + } + + class MutuallyExclusiveGroup { + friend class ArgumentParser; + + public: + MutuallyExclusiveGroup() = delete; + + explicit MutuallyExclusiveGroup(ArgumentParser &parent, + bool required = false) + : m_parent(parent), m_required(required), m_elements({}) {} + + MutuallyExclusiveGroup(const MutuallyExclusiveGroup &other) = delete; + MutuallyExclusiveGroup & + operator=(const MutuallyExclusiveGroup &other) = delete; + + MutuallyExclusiveGroup(MutuallyExclusiveGroup &&other) noexcept + : m_parent(other.m_parent), m_required(other.m_required), + m_elements(std::move(other.m_elements)) { + other.m_elements.clear(); + } + + template Argument &add_argument(Targs... f_args) { + auto &argument = m_parent.add_argument(std::forward(f_args)...); + m_elements.push_back(&argument); + argument.set_usage_newline_counter(m_parent.m_usage_newline_counter); + argument.set_group_idx(m_parent.m_group_names.size()); + return argument; + } + + private: + ArgumentParser &m_parent; + bool m_required{false}; + std::vector m_elements{}; + }; + + MutuallyExclusiveGroup &add_mutually_exclusive_group(bool required = false) { + m_mutually_exclusive_groups.emplace_back(*this, required); + return m_mutually_exclusive_groups.back(); + } + + // Parameter packed add_parents method + // Accepts a variadic number of ArgumentParser objects + template + ArgumentParser &add_parents(const Targs &... f_args) { + for (const ArgumentParser &parent_parser : {std::ref(f_args)...}) { + for (const auto &argument : parent_parser.m_positional_arguments) { + auto it = m_positional_arguments.insert( + std::cend(m_positional_arguments), argument); + index_argument(it); + } + for (const auto &argument : parent_parser.m_optional_arguments) { + auto it = m_optional_arguments.insert(std::cend(m_optional_arguments), + argument); + index_argument(it); + } + } + return *this; + } + + // Ask for the next optional arguments to be displayed on a separate + // line in usage() output. Only effective if set_usage_max_line_width() is + // also used. + ArgumentParser &add_usage_newline() { + ++m_usage_newline_counter; + return *this; + } + + // Ask for the next optional arguments to be displayed in a separate section + // in usage() and help (<< *this) output. + // For usage(), this is only effective if set_usage_max_line_width() is + // also used. + ArgumentParser &add_group(std::string group_name) { + m_group_names.emplace_back(std::move(group_name)); + return *this; + } + + ArgumentParser &add_description(std::string description) { + m_description = std::move(description); + return *this; + } + + ArgumentParser &add_epilog(std::string epilog) { + m_epilog = std::move(epilog); + return *this; + } + + // Add a un-documented/hidden alias for an argument. + // Ideally we'd want this to be a method of Argument, but Argument + // does not own its owing ArgumentParser. + ArgumentParser &add_hidden_alias_for(Argument &arg, std::string_view alias) { + for (auto it = m_optional_arguments.begin(); + it != m_optional_arguments.end(); ++it) { + if (&(*it) == &arg) { + m_argument_map.insert_or_assign(std::string(alias), it); + return *this; + } + } + throw std::logic_error( + "Argument is not an optional argument of this parser"); + } + + /* Getter for arguments and subparsers. + * @throws std::logic_error in case of an invalid argument or subparser name + */ + template T &at(std::string_view name) { + if constexpr (std::is_same_v) { + return (*this)[name]; + } else { + std::string str_name(name); + auto subparser_it = m_subparser_map.find(str_name); + if (subparser_it != m_subparser_map.end()) { + return subparser_it->second->get(); + } + throw std::logic_error("No such subparser: " + str_name); + } + } + + ArgumentParser &set_prefix_chars(std::string prefix_chars) { + m_prefix_chars = std::move(prefix_chars); + return *this; + } + + ArgumentParser &set_assign_chars(std::string assign_chars) { + m_assign_chars = std::move(assign_chars); + return *this; + } + + /* Call parse_args_internal - which does all the work + * Then, validate the parsed arguments + * This variant is used mainly for testing + * @throws std::runtime_error in case of any invalid argument + */ + void parse_args(const std::vector &arguments) { + parse_args_internal(arguments); + // Check if all arguments are parsed + for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) { + argument->validate(); + } + + // Check each mutually exclusive group and make sure + // there are no constraint violations + for (const auto &group : m_mutually_exclusive_groups) { + auto mutex_argument_used{false}; + Argument *mutex_argument_it{nullptr}; + for (Argument *arg : group.m_elements) { + if (!mutex_argument_used && arg->m_is_used) { + mutex_argument_used = true; + mutex_argument_it = arg; + } else if (mutex_argument_used && arg->m_is_used) { + // Violation + throw std::runtime_error("Argument '" + arg->get_usage_full() + + "' not allowed with '" + + mutex_argument_it->get_usage_full() + "'"); + } + } + + if (!mutex_argument_used && group.m_required) { + // at least one argument from the group is + // required + std::string argument_names{}; + std::size_t i = 0; + std::size_t size = group.m_elements.size(); + for (Argument *arg : group.m_elements) { + if (i + 1 == size) { + // last + argument_names += "'" + arg->get_usage_full() + "' "; + } else { + argument_names += "'" + arg->get_usage_full() + "' or "; + } + i += 1; + } + throw std::runtime_error("One of the arguments " + argument_names + + "is required"); + } + } + } + + /* Call parse_known_args_internal - which does all the work + * Then, validate the parsed arguments + * This variant is used mainly for testing + * @throws std::runtime_error in case of any invalid argument + */ + std::vector + parse_known_args(const std::vector &arguments) { + auto unknown_arguments = parse_known_args_internal(arguments); + // Check if all arguments are parsed + for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) { + argument->validate(); + } + return unknown_arguments; + } + + /* Main entry point for parsing command-line arguments using this + * ArgumentParser + * @throws std::runtime_error in case of any invalid argument + */ + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) + void parse_args(int argc, const char *const argv[]) { + parse_args({argv, argv + argc}); + } + + /* Main entry point for parsing command-line arguments using this + * ArgumentParser + * @throws std::runtime_error in case of any invalid argument + */ + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) + auto parse_known_args(int argc, const char *const argv[]) { + return parse_known_args({argv, argv + argc}); + } + + /* Getter for options with default values. + * @throws std::logic_error if parse_args() has not been previously called + * @throws std::logic_error if there is no such option + * @throws std::logic_error if the option has no value + * @throws std::bad_any_cast if the option is not of type T + */ + template T get(std::string_view arg_name) const { + if (!m_is_parsed) { + throw std::logic_error("Nothing parsed, no arguments are available."); + } + return (*this)[arg_name].get(); + } + + /* Getter for options without default values. + * @pre The option has no default value. + * @throws std::logic_error if there is no such option + * @throws std::bad_any_cast if the option is not of type T + */ + template + auto present(std::string_view arg_name) const -> std::optional { + return (*this)[arg_name].present(); + } + + /* Getter that returns true for user-supplied options. Returns false if not + * user-supplied, even with a default value. + */ + auto is_used(std::string_view arg_name) const { + return (*this)[arg_name].m_is_used; + } + + /* Getter that returns true if a subcommand is used. + */ + auto is_subcommand_used(std::string_view subcommand_name) const { + return m_subparser_used.at(std::string(subcommand_name)); + } + + /* Getter that returns true if a subcommand is used. + */ + auto is_subcommand_used(const ArgumentParser &subparser) const { + return is_subcommand_used(subparser.m_program_name); + } + + /* Indexing operator. Return a reference to an Argument object + * Used in conjunction with Argument.operator== e.g., parser["foo"] == true + * @throws std::logic_error in case of an invalid argument name + */ + Argument &operator[](std::string_view arg_name) const { + std::string name(arg_name); + auto it = m_argument_map.find(name); + if (it != m_argument_map.end()) { + return *(it->second); + } + if (!is_valid_prefix_char(arg_name.front())) { + const auto legal_prefix_char = get_any_valid_prefix_char(); + const auto prefix = std::string(1, legal_prefix_char); + + // "-" + arg_name + name = prefix + name; + it = m_argument_map.find(name); + if (it != m_argument_map.end()) { + return *(it->second); + } + // "--" + arg_name + name = prefix + name; + it = m_argument_map.find(name); + if (it != m_argument_map.end()) { + return *(it->second); + } + } + throw std::logic_error("No such argument: " + std::string(arg_name)); + } + + // Print help message + friend auto operator<<(std::ostream &stream, const ArgumentParser &parser) + -> std::ostream & { + stream.setf(std::ios_base::left); + + auto longest_arg_length = parser.get_length_of_longest_argument(); + + stream << parser.usage() << "\n\n"; + + if (!parser.m_description.empty()) { + stream << parser.m_description << "\n\n"; + } + + const bool has_visible_positional_args = std::find_if( + parser.m_positional_arguments.begin(), + parser.m_positional_arguments.end(), + [](const auto &argument) { + return !argument.m_is_hidden; }) != + parser.m_positional_arguments.end(); + if (has_visible_positional_args) { + stream << "Positional arguments:\n"; + } + + for (const auto &argument : parser.m_positional_arguments) { + if (!argument.m_is_hidden) { + stream.width(static_cast(longest_arg_length)); + stream << argument; + } + } + + if (!parser.m_optional_arguments.empty()) { + stream << (!has_visible_positional_args ? "" : "\n") + << "Optional arguments:\n"; + } + + for (const auto &argument : parser.m_optional_arguments) { + if (argument.m_group_idx == 0 && !argument.m_is_hidden) { + stream.width(static_cast(longest_arg_length)); + stream << argument; + } + } + + for (size_t i_group = 0; i_group < parser.m_group_names.size(); ++i_group) { + stream << "\n" << parser.m_group_names[i_group] << " (detailed usage):\n"; + for (const auto &argument : parser.m_optional_arguments) { + if (argument.m_group_idx == i_group + 1 && !argument.m_is_hidden) { + stream.width(static_cast(longest_arg_length)); + stream << argument; + } + } + } + + bool has_visible_subcommands = std::any_of( + parser.m_subparser_map.begin(), parser.m_subparser_map.end(), + [](auto &p) { return !p.second->get().m_suppress; }); + + if (has_visible_subcommands) { + stream << (parser.m_positional_arguments.empty() + ? (parser.m_optional_arguments.empty() ? "" : "\n") + : "\n") + << "Subcommands:\n"; + for (const auto &[command, subparser] : parser.m_subparser_map) { + if (subparser->get().m_suppress) { + continue; + } + + stream << std::setw(2) << " "; + stream << std::setw(static_cast(longest_arg_length - 2)) + << command; + stream << " " << subparser->get().m_description << "\n"; + } + } + + if (!parser.m_epilog.empty()) { + stream << '\n'; + stream << parser.m_epilog << "\n\n"; + } + + return stream; + } + + // Format help message + auto help() const -> std::stringstream { + std::stringstream out; + out << *this; + return out; + } + + // Sets the maximum width for a line of the Usage message + ArgumentParser &set_usage_max_line_width(size_t w) { + this->m_usage_max_line_width = w; + return *this; + } + + // Asks to display arguments of mutually exclusive group on separate lines in + // the Usage message + ArgumentParser &set_usage_break_on_mutex() { + this->m_usage_break_on_mutex = true; + return *this; + } + + // Format usage part of help only + auto usage() const -> std::string { + std::stringstream stream; + + std::string curline("Usage: "); + curline += this->m_program_name; + const bool multiline_usage = + this->m_usage_max_line_width < std::numeric_limits::max(); + const size_t indent_size = curline.size(); + + const auto deal_with_options_of_group = [&](std::size_t group_idx) { + bool found_options = false; + // Add any options inline here + const MutuallyExclusiveGroup *cur_mutex = nullptr; + int usage_newline_counter = -1; + for (const auto &argument : this->m_optional_arguments) { + if (argument.m_is_hidden) { + continue; + } + if (multiline_usage) { + if (argument.m_group_idx != group_idx) { + continue; + } + if (usage_newline_counter != argument.m_usage_newline_counter) { + if (usage_newline_counter >= 0) { + if (curline.size() > indent_size) { + stream << curline << std::endl; + curline = std::string(indent_size, ' '); + } + } + usage_newline_counter = argument.m_usage_newline_counter; + } + } + found_options = true; + const std::string arg_inline_usage = argument.get_inline_usage(); + const MutuallyExclusiveGroup *arg_mutex = + get_belonging_mutex(&argument); + if ((cur_mutex != nullptr) && (arg_mutex == nullptr)) { + curline += ']'; + if (this->m_usage_break_on_mutex) { + stream << curline << std::endl; + curline = std::string(indent_size, ' '); + } + } else if ((cur_mutex == nullptr) && (arg_mutex != nullptr)) { + if ((this->m_usage_break_on_mutex && curline.size() > indent_size) || + curline.size() + 3 + arg_inline_usage.size() > + this->m_usage_max_line_width) { + stream << curline << std::endl; + curline = std::string(indent_size, ' '); + } + curline += " ["; + } else if ((cur_mutex != nullptr) && (arg_mutex != nullptr)) { + if (cur_mutex != arg_mutex) { + curline += ']'; + if (this->m_usage_break_on_mutex || + curline.size() + 3 + arg_inline_usage.size() > + this->m_usage_max_line_width) { + stream << curline << std::endl; + curline = std::string(indent_size, ' '); + } + curline += " ["; + } else { + curline += '|'; + } + } + cur_mutex = arg_mutex; + if (curline.size() + 1 + arg_inline_usage.size() > + this->m_usage_max_line_width) { + stream << curline << std::endl; + curline = std::string(indent_size, ' '); + curline += " "; + } else if (cur_mutex == nullptr) { + curline += " "; + } + curline += arg_inline_usage; + } + if (cur_mutex != nullptr) { + curline += ']'; + } + return found_options; + }; + + const bool found_options = deal_with_options_of_group(0); + + if (found_options && multiline_usage && + !this->m_positional_arguments.empty()) { + stream << curline << std::endl; + curline = std::string(indent_size, ' '); + } + // Put positional arguments after the optionals + for (const auto &argument : this->m_positional_arguments) { + if (argument.m_is_hidden) { + continue; + } + const std::string pos_arg = !argument.m_metavar.empty() + ? argument.m_metavar + : argument.m_names.front(); + if (curline.size() + 1 + pos_arg.size() > this->m_usage_max_line_width) { + stream << curline << std::endl; + curline = std::string(indent_size, ' '); + } + curline += " "; + if (argument.m_num_args_range.get_min() == 0 && + !argument.m_num_args_range.is_right_bounded()) { + curline += "["; + curline += pos_arg; + curline += "]..."; + } else if (argument.m_num_args_range.get_min() == 1 && + !argument.m_num_args_range.is_right_bounded()) { + curline += pos_arg; + curline += "..."; + } else { + curline += pos_arg; + } + } + + if (multiline_usage) { + // Display options of other groups + for (std::size_t i = 0; i < m_group_names.size(); ++i) { + stream << curline << std::endl << std::endl; + stream << m_group_names[i] << ":" << std::endl; + curline = std::string(indent_size, ' '); + deal_with_options_of_group(i + 1); + } + } + + stream << curline; + + // Put subcommands after positional arguments + if (!m_subparser_map.empty()) { + stream << " {"; + std::size_t i{0}; + for (const auto &[command, subparser] : m_subparser_map) { + if (subparser->get().m_suppress) { + continue; + } + + if (i == 0) { + stream << command; + } else { + stream << "," << command; + } + ++i; + } + stream << "}"; + } + + return stream.str(); + } + + // Printing the one and only help message + // I've stuck with a simple message format, nothing fancy. + [[deprecated("Use cout << program; instead. See also help().")]] std::string + print_help() const { + auto out = help(); + std::cout << out.rdbuf(); + return out.str(); + } + + void add_subparser(ArgumentParser &parser) { + parser.m_parser_path = m_program_name + " " + parser.m_program_name; + auto it = m_subparsers.emplace(std::cend(m_subparsers), parser); + m_subparser_map.insert_or_assign(parser.m_program_name, it); + m_subparser_used.insert_or_assign(parser.m_program_name, false); + } + + void set_suppress(bool suppress) { m_suppress = suppress; } + +protected: + const MutuallyExclusiveGroup *get_belonging_mutex(const Argument *arg) const { + for (const auto &mutex : m_mutually_exclusive_groups) { + if (std::find(mutex.m_elements.begin(), mutex.m_elements.end(), arg) != + mutex.m_elements.end()) { + return &mutex; + } + } + return nullptr; + } + + bool is_valid_prefix_char(char c) const { + return m_prefix_chars.find(c) != std::string::npos; + } + + char get_any_valid_prefix_char() const { return m_prefix_chars[0]; } + + /* + * Pre-process this argument list. Anything starting with "--", that + * contains an =, where the prefix before the = has an entry in the + * options table, should be split. + */ + std::vector + preprocess_arguments(const std::vector &raw_arguments) const { + std::vector arguments{}; + for (const auto &arg : raw_arguments) { + + const auto argument_starts_with_prefix_chars = + [this](const std::string &a) -> bool { + if (!a.empty()) { + + const auto legal_prefix = [this](char c) -> bool { + return m_prefix_chars.find(c) != std::string::npos; + }; + + // Windows-style + // if '/' is a legal prefix char + // then allow single '/' followed by argument name, followed by an + // assign char, e.g., ':' e.g., 'test.exe /A:Foo' + const auto windows_style = legal_prefix('/'); + + if (windows_style) { + if (legal_prefix(a[0])) { + return true; + } + } else { + // Slash '/' is not a legal prefix char + // For all other characters, only support long arguments + // i.e., the argument must start with 2 prefix chars, e.g, + // '--foo' e,g, './test --foo=Bar -DARG=yes' + if (a.size() > 1) { + return (legal_prefix(a[0]) && legal_prefix(a[1])); + } + } + } + return false; + }; + + // Check that: + // - We don't have an argument named exactly this + // - The argument starts with a prefix char, e.g., "--" + // - The argument contains an assign char, e.g., "=" + auto assign_char_pos = arg.find_first_of(m_assign_chars); + + if (m_argument_map.find(arg) == m_argument_map.end() && + argument_starts_with_prefix_chars(arg) && + assign_char_pos != std::string::npos) { + // Get the name of the potential option, and check it exists + std::string opt_name = arg.substr(0, assign_char_pos); + if (m_argument_map.find(opt_name) != m_argument_map.end()) { + // This is the name of an option! Split it into two parts + arguments.push_back(std::move(opt_name)); + arguments.push_back(arg.substr(assign_char_pos + 1)); + continue; + } + } + // If we've fallen through to here, then it's a standard argument + arguments.push_back(arg); + } + return arguments; + } + + /* + * @throws std::runtime_error in case of any invalid argument + */ + void parse_args_internal(const std::vector &raw_arguments) { + auto arguments = preprocess_arguments(raw_arguments); + if (m_program_name.empty() && !arguments.empty()) { + m_program_name = arguments.front(); + } + auto end = std::end(arguments); + auto positional_argument_it = std::begin(m_positional_arguments); + for (auto it = std::next(std::begin(arguments)); it != end;) { + const auto ¤t_argument = *it; + if (Argument::is_positional(current_argument, m_prefix_chars)) { + if (positional_argument_it == std::end(m_positional_arguments)) { + + // Check sub-parsers + auto subparser_it = m_subparser_map.find(current_argument); + if (subparser_it != m_subparser_map.end()) { + + // build list of remaining args + const auto unprocessed_arguments = + std::vector(it, end); + + // invoke subparser + m_is_parsed = true; + m_subparser_used[current_argument] = true; + return subparser_it->second->get().parse_args( + unprocessed_arguments); + } + + if (m_positional_arguments.empty()) { + + // Ask the user if they argument they provided was a typo + // for some sub-parser, + // e.g., user provided `git totes` instead of `git notes` + if (!m_subparser_map.empty()) { + throw std::runtime_error( + "Failed to parse '" + current_argument + "', did you mean '" + + std::string{details::get_most_similar_string( + m_subparser_map, current_argument)} + + "'"); + } + + // Ask the user if they meant to use a specific optional argument + if (!m_optional_arguments.empty()) { + for (const auto &opt : m_optional_arguments) { + if (!opt.m_implicit_value.has_value()) { + // not a flag, requires a value + if (!opt.m_is_used) { + throw std::runtime_error( + "Zero positional arguments expected, did you mean " + + opt.get_usage_full()); + } + } + } + + throw std::runtime_error("Zero positional arguments expected"); + } else { + throw std::runtime_error("Zero positional arguments expected"); + } + } else { + throw std::runtime_error("Maximum number of positional arguments " + "exceeded, failed to parse '" + + current_argument + "'"); + } + } + auto argument = positional_argument_it++; + + // Deal with the situation of ... + if (argument->m_num_args_range.get_min() == 1 && + argument->m_num_args_range.get_max() == (std::numeric_limits::max)() && + positional_argument_it != std::end(m_positional_arguments) && + std::next(positional_argument_it) == std::end(m_positional_arguments) && + positional_argument_it->m_num_args_range.get_min() == 1 && + positional_argument_it->m_num_args_range.get_max() == 1 ) { + if (std::next(it) != end) { + positional_argument_it->consume(std::prev(end), end); + end = std::prev(end); + } else { + throw std::runtime_error("Missing " + positional_argument_it->m_names.front()); + } + } + + it = argument->consume(it, end); + continue; + } + + auto arg_map_it = m_argument_map.find(current_argument); + if (arg_map_it != m_argument_map.end()) { + auto argument = arg_map_it->second; + it = argument->consume(std::next(it), end, arg_map_it->first); + } else if (const auto &compound_arg = current_argument; + compound_arg.size() > 1 && + is_valid_prefix_char(compound_arg[0]) && + !is_valid_prefix_char(compound_arg[1])) { + ++it; + for (std::size_t j = 1; j < compound_arg.size(); j++) { + auto hypothetical_arg = std::string{'-', compound_arg[j]}; + auto arg_map_it2 = m_argument_map.find(hypothetical_arg); + if (arg_map_it2 != m_argument_map.end()) { + auto argument = arg_map_it2->second; + it = argument->consume(it, end, arg_map_it2->first); + } else { + throw std::runtime_error("Unknown argument: " + current_argument); + } + } + } else { + throw std::runtime_error("Unknown argument: " + current_argument); + } + } + m_is_parsed = true; + } + + /* + * Like parse_args_internal but collects unused args into a vector + */ + std::vector + parse_known_args_internal(const std::vector &raw_arguments) { + auto arguments = preprocess_arguments(raw_arguments); + + std::vector unknown_arguments{}; + + if (m_program_name.empty() && !arguments.empty()) { + m_program_name = arguments.front(); + } + auto end = std::end(arguments); + auto positional_argument_it = std::begin(m_positional_arguments); + for (auto it = std::next(std::begin(arguments)); it != end;) { + const auto ¤t_argument = *it; + if (Argument::is_positional(current_argument, m_prefix_chars)) { + if (positional_argument_it == std::end(m_positional_arguments)) { + + // Check sub-parsers + auto subparser_it = m_subparser_map.find(current_argument); + if (subparser_it != m_subparser_map.end()) { + + // build list of remaining args + const auto unprocessed_arguments = + std::vector(it, end); + + // invoke subparser + m_is_parsed = true; + m_subparser_used[current_argument] = true; + return subparser_it->second->get().parse_known_args_internal( + unprocessed_arguments); + } + + // save current argument as unknown and go to next argument + unknown_arguments.push_back(current_argument); + ++it; + } else { + // current argument is the value of a positional argument + // consume it + auto argument = positional_argument_it++; + it = argument->consume(it, end); + } + continue; + } + + auto arg_map_it = m_argument_map.find(current_argument); + if (arg_map_it != m_argument_map.end()) { + auto argument = arg_map_it->second; + it = argument->consume(std::next(it), end, arg_map_it->first); + } else if (const auto &compound_arg = current_argument; + compound_arg.size() > 1 && + is_valid_prefix_char(compound_arg[0]) && + !is_valid_prefix_char(compound_arg[1])) { + ++it; + for (std::size_t j = 1; j < compound_arg.size(); j++) { + auto hypothetical_arg = std::string{'-', compound_arg[j]}; + auto arg_map_it2 = m_argument_map.find(hypothetical_arg); + if (arg_map_it2 != m_argument_map.end()) { + auto argument = arg_map_it2->second; + it = argument->consume(it, end, arg_map_it2->first); + } else { + unknown_arguments.push_back(current_argument); + break; + } + } + } else { + // current argument is an optional-like argument that is unknown + // save it and move to next argument + unknown_arguments.push_back(current_argument); + ++it; + } + } + m_is_parsed = true; + return unknown_arguments; + } + + // Used by print_help. + std::size_t get_length_of_longest_argument() const { + if (m_argument_map.empty()) { + return 0; + } + std::size_t max_size = 0; + for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) { + max_size = + std::max(max_size, argument->get_arguments_length()); + } + for ([[maybe_unused]] const auto &[command, unused] : m_subparser_map) { + max_size = std::max(max_size, command.size()); + } + return max_size; + } + + using argument_it = std::list::iterator; + using mutex_group_it = std::vector::iterator; + using argument_parser_it = + std::list>::iterator; + + void index_argument(argument_it it) { + for (const auto &name : std::as_const(it->m_names)) { + m_argument_map.insert_or_assign(name, it); + } + } + + std::string m_program_name; + std::string m_version; + std::string m_description; + std::string m_epilog; + bool m_exit_on_default_arguments = true; + std::string m_prefix_chars{"-"}; + std::string m_assign_chars{"="}; + bool m_is_parsed = false; + std::list m_positional_arguments; + std::list m_optional_arguments; + std::map m_argument_map; + std::string m_parser_path; + std::list> m_subparsers; + std::map m_subparser_map; + std::map m_subparser_used; + std::vector m_mutually_exclusive_groups; + bool m_suppress = false; + std::size_t m_usage_max_line_width = std::numeric_limits::max(); + bool m_usage_break_on_mutex = false; + int m_usage_newline_counter = 0; + std::vector m_group_names; +}; + +} // namespace argparse diff --git a/include/game_client.h b/include/game_client.h new file mode 100644 index 0000000..8181c94 --- /dev/null +++ b/include/game_client.h @@ -0,0 +1,30 @@ +#pragma once + +#include "net_client.h" +#include "game_structs.h" + +class GameClient : public net::client_interface +{ +public: + + GameClient() = default; + ~GameClient() = default; + + void PingServer() + { + net::message msg; + msg.header.id = PTypes::SERVER_PING; + + std::chrono::system_clock::time_point timeNow = std::chrono::system_clock::now(); + + msg << timeNow; + Send(msg); + } + + void MessageAll() + { + net::message msg; + msg.header.id = PTypes::CLIENT_CONNECT; + Send(msg); + } +}; \ No newline at end of file diff --git a/include/game_server.h b/include/game_server.h new file mode 100644 index 0000000..e7eb56b --- /dev/null +++ b/include/game_server.h @@ -0,0 +1,60 @@ +#pragma once + +#include "net_connection.h" +#include "net_server.h" +#include "game_structs.h" + +class GameServer : public net::server_interface +{ +public: + + GameServer(uint16_t port) : net::server_interface(port) + { + } +protected: + + bool OnClientConnect(std::shared_ptr> client) override + { + net::message msg; + msg.header.id = PTypes::SERVER_ACCEPT; + + client->Send(msg); + return true; + } + + void OnClientDisconnect(std::shared_ptr> client) override + { + std::cout << "Removing client [" << client->GetID() << "]\n"; + } + + // Called when a message arrives + void OnMessage(std::shared_ptr> client, net::message& msg) override + { + switch (msg.header.id) + { + case PTypes::SERVER_PING: + { + std::cout << "[" << client->GetID() << "]: Server Ping\n"; + + // Simply bounce message back to client + client->Send(msg); + } + break; + + case PTypes::CLIENT_CONNECT: + { + std::cout << "[" << client->GetID() << "]: Client connected\n"; + + // Construct a new message and send it to all clients + net::message msg; + msg.header.id = PTypes::CLIENT_CONNECT; + msg << client->GetID(); + MessageAllClients(msg, client); + + } + break; + } + } + + +}; \ No newline at end of file diff --git a/include/game_structs.h b/include/game_structs.h new file mode 100644 index 0000000..31bf48c --- /dev/null +++ b/include/game_structs.h @@ -0,0 +1,109 @@ +#pragma once + +enum class PTypes : uint32_t +{ + CLIENT_CONNECT = 0x01, + SERVER_SEND_MAP = 0x10, + CLIENT_READY = 0x02, + SERVER_GAME_START = 0x20, + CLIENT_SEND_KEY = 0x00, + SERVER_SEND_KEY_TO_OTHERS = 0xffffffff, + SERVER_PING = 0x50, + SERVER_ACCEPT = 0x60, +}; + +enum class PlayerMoves : uint8_t +{ + UP = 0, + RIGHT = 1, + DOWN = 2, + LEFT = 4 +}; + +enum class CellType : uint8_t +{ + EMPTY = 0x00, + WALL = 0xff, + FOOD = 0xaa, + PLAYER = 0x22 +}; + +template +class GameField +{ +private: + CellType field[WIDTH][HEIGHT] = {0}; +public: + constexpr GameField() = default; + ~GameField() = default; + + GameField(const GameField&) = default; + GameField(GameField&&) noexcept = default; + + [[nodiscard]] bool operator==(const GameField& other) const noexcept + { + return (std::memcmp(&field, &other.field, WIDTH * HEIGHT) == 0); + } + + [[nodiscard]] bool operator!=(const GameField& other) const noexcept + { + return !(*this == other); + } + + GameField& operator=(const GameField& other) + { + std::memcpy(&field, &other.field, WIDTH * HEIGHT); + return *this; + } + + GameField& operator=(GameField&& other) noexcept + { + std::memcpy(&field, &other.field, WIDTH * HEIGHT); + return *this; + } + + CellType& operator()(size_t x, size_t y) + { + static_assert(x < WIDTH && y < HEIGHT); + return field[x][y]; + } + + const CellType& at(const size_t& x, const size_t& y) const + { + static_assert(x < WIDTH && y < HEIGHT); + return field[x][y]; + } + + constexpr void fill(CellType value) + { + std::memset(&field[0], value, WIDTH * HEIGHT); + } + + [[nodiscard]] constexpr size_t GetWidth() const noexcept + { + return WIDTH; + } + + [[nodiscard]] constexpr size_t GetHeight() const noexcept + { + return HEIGHT; + } + + void InvertVertical() + { + static_assert(HEIGHT % 2 == 0); + + for (size_t i = 0; i < WIDTH; i++) + for (size_t j = 0; j < HEIGHT / 2; j++) + std::swap(field[i][j], field[i][HEIGHT - j]); + } + + void InvertHorizontal() + { + static_assert(WIDTH % 2 == 0); + + for (size_t i = 0; i < WIDTH / 2; i++) + for (size_t j = 0; j < HEIGHT; j++) + std::swap(field[i][j], field[WIDTH - i][j]); + } +}; \ No newline at end of file diff --git a/include/includes.h b/include/includes.h new file mode 100644 index 0000000..658b9cf --- /dev/null +++ b/include/includes.h @@ -0,0 +1,40 @@ +#pragma once + +#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) +/* UNIX-style OS. ------------------------------------------- */ +#define IS_UNIX 1 +#elif defined(_WIN32) || defined(WIN32) || defined(__WIN32__) +// Windows +#define IS_WIN 1 +#endif + +#ifdef IS_WIN +#define _WIN32_WINNT 0x0A00 +#endif + +#ifndef ASIO_STANDALONE +#define ASIO_STANDALONE +#endif + +#include +#include +#include +#include +#include + +// TODO: , . + +#ifdef IS_UNIX + /* UNIX-style OS. ------------------------------------------- */ +#include +#elif defined(IS_WIN) +// Windows +#include "../PDCurses/include/curses.h" +#endif + +#include +#include + +#include +#include +#include \ No newline at end of file diff --git a/include/main.h b/include/main.h deleted file mode 100644 index d2b7734..0000000 --- a/include/main.h +++ /dev/null @@ -1,28 +0,0 @@ -// PacmanConsole.h : включаемый файл для стандартных системных включаемых файлов -// или включаемые файлы для конкретного проекта. - -#pragma once - -#if defined(_WIN32) || defined(WIN32) || defined(__WIN32__) -#define _WIN32_WINNT 0x0A00 -#endif - -#ifndef ASIO_STANDALONE -#define ASIO_STANDALONE -#endif - - -// TODO: установите здесь ссылки на дополнительные заголовки, требующиеся для программы. - -#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) - /* UNIX-style OS. ------------------------------------------- */ -#include -#elif defined(_WIN32) || defined(WIN32) || defined(__WIN32__) -// Windows -#include -#endif - -#include -#include - -#include "net_includes.h" \ No newline at end of file diff --git a/include/net_client.h b/include/net_client.h new file mode 100644 index 0000000..1ccd4a8 --- /dev/null +++ b/include/net_client.h @@ -0,0 +1,120 @@ +#pragma once + +#include "net_common.h" + +#include "net_message.h" +#include "net_tsqueue.h" +#include "net_connection.h" + +namespace net +{ + template + class client_interface + { + public: + client_interface(const client_interface& other) = delete; + + client_interface(client_interface&& other) noexcept = default; + + client_interface& operator=(const client_interface& other) = delete; + + client_interface& operator=(client_interface&& other) noexcept + { + m_context = std::move(other.m_context); + thrContext = std::move(other.thrContext); + m_connection = std::move(other.m_connection); + m_qMessagesIn = std::move(other.m_qMessagesIn); + return *this; + } + + + client_interface() + { + } + + virtual ~client_interface() + { + Disconnect(); + } + + // Connect to server with hostname/ip-address and port + bool Connect(const std::string& host, const uint16_t port) + { + try + { + // Resolve hostname/ip-address into tangiable physical address + asio::ip::tcp::resolver resolver(m_context); + asio::ip::tcp::resolver::results_type endpoints = resolver.resolve(host, std::to_string(port)); + + // Create connection + m_connection = std::make_unique>(connection::owner::client, m_context, asio::ip::tcp::socket(m_context), m_qMessagesIn); + + // Tell the connection object to connect to server + m_connection->ConnectToServer(endpoints); + + // Start Context Thread + thrContext = std::thread([this]() { m_context.run(); }); + } + catch (std::exception& e) + { + std::cerr << "Client Exception: " << e.what() << "\n"; + return false; + } + return true; + } + + // Disconnect from server + void Disconnect() + { + // If connection exists, and it's connected then... + if (IsConnected()) + { + // ...disconnect from server gracefully + m_connection->Disconnect(); + } + + // Either way, we're also done with the asio context... + m_context.stop(); + // ...and its thread + if (thrContext.joinable()) + thrContext.join(); + + // Destroy the connection object + m_connection.release(); + } + + + [[nodiscard]] bool IsConnected() const noexcept + { + if (m_connection) + return m_connection->IsConnected(); + + return false; + } + + tsqueue>& Incoming() + { + return m_qMessagesIn; + } + + // Send message to server + void Send(const message& msg) + { + if (IsConnected()) + m_connection->Send(msg); + } + + protected: + // asio context handles the data transfer... + asio::io_context m_context; + // ...but needs a thread of its own to execute its work commands + std::thread thrContext; + + // The client has a single instance of a "connection" object, which handles data transfer + std::unique_ptr> m_connection; + + private: + // This is the thread safe queue of incoming messages from server + tsqueue> m_qMessagesIn; + }; +} \ No newline at end of file diff --git a/include/net_common.h b/include/net_common.h new file mode 100644 index 0000000..9a6dc65 --- /dev/null +++ b/include/net_common.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#ifdef _WIN32 +#define _WIN32_WINNT 0x0A00 +#endif + +#define ASIO_STANDALONE + +#include +#include +#include \ No newline at end of file diff --git a/include/net_connection.h b/include/net_connection.h new file mode 100644 index 0000000..c6609b8 --- /dev/null +++ b/include/net_connection.h @@ -0,0 +1,365 @@ +#pragma once + +#include "net_tsqueue.h" +#include "net_message.h" + +namespace net +{ + template + class server_interface; + + template + class connection : public std::enable_shared_from_this> + { + public: + enum class owner : bool + { + server, + client + }; + + connection(owner parent, + asio::io_context& asioContext, + asio::ip::tcp::socket socket, + tsqueue>& qIn) + : m_nOwnerType(parent), + m_asioContext(asioContext), + m_socket(std::move(socket)), + m_qMessagesIn(qIn) + { + if (m_nOwnerType == owner::server) + { + m_nHandshakeOut = static_cast(std::chrono::system_clock::now().time_since_epoch().count()); + + m_nHandshakeCheck = scramble(m_nHandshakeOut); + } + else + { + m_nHandshakeIn = 0; + m_nHandshakeOut = 0; + } + + } + + virtual ~connection() + { + + } + + void ConnectToServer(const asio::ip::tcp::resolver::results_type& endpoints) + { + // Only clients can connect to servers + if (m_nOwnerType == owner::client) + { + // Request asio attempts to connect to an endpoint + asio::async_connect(m_socket, endpoints, + [this](std::error_code ec, asio::ip::tcp::endpoint endpoint) + { + if (!ec) + { + // ReadHeader(); + + ReadValidation(); + } + }); + } + } + + void Disconnect() + { + if (IsConnected()) + asio::post(m_asioContext, [this]() { m_socket.close(); }); + } + + [[nodiscard]] bool IsConnected() const noexcept + { + return m_socket.is_open(); + } + + [[nodiscard]] uint32_t GetID() const noexcept + { + return id; + } + + void Send(const message& msg) + { + asio::post(m_asioContext, + [this, msg]() + { + const bool bWritingMessage = !m_qMessagesOut.empty(); + m_qMessagesOut.push_back(msg); + if (!bWritingMessage) + WriteHeader(); + } + ); + } + + void ConnectToClient(net::server_interface* server, uint32_t uid = 0) + { + if (m_nOwnerType == owner::server) + { + if (m_socket.is_open()) + { + id = uid; + + // ReadHeader(); + + WriteValidation(); + + ReadValidation(server); + } + } + } + + private: + + // ASYNC - Prime context to write a message header + void WriteHeader() + { + // If this function is called, we know the outgoing message queue must have + // at least one message to send. So allocate a transmission buffer to hold + // the message, and issue the work - asio, send these bytes + asio::async_write(m_socket, asio::buffer(&m_qMessagesOut.front().header, sizeof(message_header)), + [this](std::error_code ec, std::size_t length) + { + // asio has now sent the bytes - if there was a problem + // an error would be available... + if (!ec) + { + // ... no error, so check if the message header just sent also + // has a message body... + if (m_qMessagesOut.front().body.size() > 0) + { + // ...it does, so issue the task to write the body bytes + WriteBody(); + } + else + { + // ...it didnt, so we are done with this message. Remove it from + // the outgoing message queue + m_qMessagesOut.pop_front(); + + // If the queue is not empty, there are more messages to send, so + // make this happen by issuing the task to send the next header. + if (!m_qMessagesOut.empty()) + { + WriteHeader(); + } + } + } + else + { + // ...asio failed to write the message, we could analyse why but + // for now simply assume the connection has died by closing the + // socket. When a future attempt to write to this client fails due + // to the closed socket, it will be tidied up. + std::cout << '[' << id << "] Write Header Fail.\n"; + m_socket.close(); + } + }); + } + + // ASYNC - Prime context to write a message body + void WriteBody() + { + // If this function is called, a header has just been sent, and that header + // indicated a body existed for this message. Fill a transmission buffer + // with the body data, and send it! + asio::async_write(m_socket, asio::buffer(m_qMessagesOut.front().body.data(), m_qMessagesOut.front().body.size()), + [this](std::error_code ec, std::size_t length) + { + if (!ec) + { + // Sending was successful, so we are done with the message + // and remove it from the queue + m_qMessagesOut.pop_front(); + + // If the queue still has messages in it, then issue the task to + // send the next messages' header. + if (!m_qMessagesOut.empty()) + { + WriteHeader(); + } + } + else + { + // Sending failed, see WriteHeader() equivalent for description :P + std::cout << '[' << id << "] Write Body Fail.\n"; + m_socket.close(); + } + }); + } + + // ASYNC - Prime context ready to read a message header + void ReadHeader() + { + // If this function is called, we are expecting asio to wait until it receives + // enough bytes to form a header of a message. We know the headers are a fixed + // size, so allocate a transmission buffer large enough to store it. In fact, + // we will construct the message in a "temporary" message object as it's + // convenient to work with. + asio::async_read(m_socket, asio::buffer(&m_msgTemporaryIn.header, sizeof(message_header)), + [this](std::error_code ec, std::size_t length) + { + if (!ec) + { + // A complete message header has been read, check if this message + // has a body to follow... + if (m_msgTemporaryIn.header.size > 0) + { + // ...it does, so allocate enough space in the messages' body + // vector, and issue asio with the task to read the body. + m_msgTemporaryIn.body.resize(m_msgTemporaryIn.header.size); + ReadBody(); + } + else + { + // it doesn't, so add this bodyless message to the connections + // incoming message queue + AddToIncomingMessageQueue(); + } + } + else + { + // Reading form the client went wrong, most likely a disconnect + // has occurred. Close the socket and let the system tidy it up later. + std::cout << '[' << id << "] Read Header Fail.\n"; + m_socket.close(); + } + }); + } + + // ASYNC - Prime context ready to read a message body + void ReadBody() + { + // If this function is called, a header has already been read, and that header + // request we read a body, The space for that body has already been allocated + // in the temporary message object, so just wait for the bytes to arrive... + asio::async_read(m_socket, asio::buffer(m_msgTemporaryIn.body.data(), m_msgTemporaryIn.body.size()), + [this](std::error_code ec, std::size_t length) + { + if (!ec) + { + // ...and they have! The message is now complete, so add + // the whole message to incoming queue + AddToIncomingMessageQueue(); + } + else + { + // As above! + std::cout << '[' << id << "] Read Body Fail.\n"; + m_socket.close(); + } + }); + } + + // Once a full message is received, add it to the incoming queue + void AddToIncomingMessageQueue() + { + // Shove it in queue, converting it to an "owned message", by initialising + // with the a shared pointer from this connection object + if (m_nOwnerType == owner::server) + m_qMessagesIn.push_back({ this->shared_from_this(), m_msgTemporaryIn }); + else + m_qMessagesIn.push_back({ nullptr, m_msgTemporaryIn }); + + // We must now prime the asio context to receive the next message. It + // wil just sit and wait for bytes to arrive, and the message construction + // process repeats itself. Clever huh? + ReadHeader(); + } + + static uint64_t scramble(const uint64_t& nInput) + { + uint64_t out = nInput ^ 0xDEADBEEFC0DECAFE; + out = (out & 0xF0F0F0F0F0F0F0) >> 4 | (out & 0x0F0F0F0F0F0F0F) << 4; + return out ^ 0xC0DEFACE12345678; + } + + + // ASYNC - Used by both client and server to write validation data + void WriteValidation() + { + asio::async_write(m_socket, asio::buffer(&m_nHandshakeOut, sizeof(uint64_t)), + [this](std::error_code ec, std::size_t length) + { + if (!ec) + { + if (m_nOwnerType == owner::client) + ReadHeader(); + } + else + { + m_socket.close(); + } + } + ); + } + + void ReadValidation(net::server_interface* server = nullptr) + { + asio::async_read(m_socket, asio::buffer(&m_nHandshakeIn, sizeof(uint64_t)), + [this, server](std::error_code ec, std::size_t length) + { + if (!ec) + { + if (m_nOwnerType == owner::server) + { + if (m_nHandshakeIn == m_nHandshakeCheck) + { + std::cout << "Client validated\n"; + server->OnClientValidated(this->shared_from_this()); + + ReadHeader(); + } + else + { + std::cout << "Client Disconnected (Fail Validation)\n"; + m_socket.close(); + } + } + else + { + m_nHandshakeOut = scramble(m_nHandshakeIn); + + WriteValidation(); + } + } + else + { + std::cout << "Client disconnected (Read Validation)\n"; + m_socket.close(); + } + } + ); + } + + protected: + + // The "owner" decides how some of the connection behaves + owner m_nOwnerType = owner::server; + + // This context is shared with the whole asio instance + asio::io_context& m_asioContext; + + // Each connection has a unique socket to a remote + asio::ip::tcp::socket m_socket; + + // This queue holds all messages to be sent to the remote side + // of this connection + tsqueue> m_qMessagesOut; + + // This references the incoming queue of the parent object + tsqueue>& m_qMessagesIn; + + // Incoming messages are constructed asynchronously, so we will +// store the part assembled message here, until it is ready + message m_msgTemporaryIn; + + uint32_t id = 0; + + uint64_t m_nHandshakeOut = 0; + uint64_t m_nHandshakeIn = 0; + uint64_t m_nHandshakeCheck = 0; + }; +} \ No newline at end of file diff --git a/include/net_includes.h b/include/net_includes.h new file mode 100644 index 0000000..cbeb3f9 --- /dev/null +++ b/include/net_includes.h @@ -0,0 +1,8 @@ +#pragma once + +#include "net_common.h" +#include "net_message.h" +#include "net_tsqueue.h" +#include "net_connection.h" +#include "net_client.h" +#include "net_server.h" \ No newline at end of file diff --git a/include/net_message.h b/include/net_message.h new file mode 100644 index 0000000..761ea15 --- /dev/null +++ b/include/net_message.h @@ -0,0 +1,109 @@ +#pragma once + +#include "net_common.h" + +constexpr static uint32_t MAGIC = 0xabcdfe01; + +namespace net +{ + + template + class connection; + + + template + struct message_header + { + uint32_t magic = MAGIC; + T ptype{}; + uint32_t datasize = 0; + }; + + template + struct message + { + message_header header{}; + std::vector data; + + // returns size of entire message packet in bytes + [[nodiscard]] size_t size() const noexcept + { + return sizeof(message_header) + data.size(); + } + + // Override for std::cout compatibility - produces friendly description of message + friend std::ostream& operator<<(std::ostream& os, const message& msg) + { + os << "PType: " << static_cast(msg.header.ptype) << " Size: " << msg.header.datasize; + return os; + } + + // Convenience Operator overloads - These allow us to add and remove stuff from + // the body vector as if it were a stack, so First in, Last Out. These are a + // template in itself, because we dont know what data type the user is pushing or + // popping, so lets allow them all. NOTE: It assumes the data type is fundamentally + // Plain Old Data (POD). TLDR: Serialise & Deserialise into/from a vector + + // Pushes any POD-like data into the message buffer + template + friend message& operator << (message& msg, const DataType& data) + { + // Check that the type of the data being pushed is trivially copyable + static_assert(std::is_standard_layout_v, "Data is too complex to be pushed into vector"); + + // Cache current size of vector, as this will be the point we insert the data + const size_t i = msg.data.size(); + + // Resize the vector by the size of the data being pushed + msg.data.resize(i + sizeof(DataType)); + + // Physically copy the data into the newly allocated vector space + std::memcpy(msg.data.data() + i, &data, sizeof(DataType)); + + // Recalculate the message size + msg.header.size = msg.size(); + + // Return the target message so it can be "chained" + return msg; + } + + // Pulls any POD-like data form the message buffer + template + friend message& operator >> (message& msg, DataType& data) + { + // Check that the type of the data being pushed is trivially copyable + static_assert(std::is_standard_layout_v, "Data is too complex to be pulled from vector"); + + // Cache the location towards the end of the vector where the pulled data starts + const size_t i = msg.data.size() - sizeof(DataType); + + // Physically copy the data from the vector into the user variable + std::memcpy(&data, msg.data.data() + i, sizeof(DataType)); + + // Shrink the vector to remove read bytes, and reset end position + msg.data.resize(i); + + // Recalculate the message size + msg.header.size = msg.size(); + + // Return the target message so it can be "chained" + return msg; + } + }; + + template + struct owned_message + { + std::shared_ptr> remote = nullptr; + message msg; + + // Again, a friendly string maker + friend std::ostream& operator<<(std::ostream& os, const owned_message& msg_) + { + os << msg_.msg; + return os; + } + }; + + +} \ No newline at end of file diff --git a/include/net_server.h b/include/net_server.h new file mode 100644 index 0000000..4504bdd --- /dev/null +++ b/include/net_server.h @@ -0,0 +1,246 @@ +#pragma once + +#include "net_tsqueue.h" +#include "net_message.h" +#include "net_connection.h" + +namespace net +{ + template + class server_interface + { + public: + + server_interface() = delete; + + // Create a server, ready to listen on specified port + server_interface(uint16_t port) + : m_asioAcceptor(m_asioContext, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)) + { + + } + + virtual ~server_interface() + { + // May as well try and tidy up + Stop(); + } + + // Starts the server! + bool Start() + { + try + { + // Issue a task to the asio context - This is important + // as it will prime the context with "work", and stop it + // from exiting immediately. Since this is a server, we + // want it primed ready to handle clients trying to + // connect. + WaitForClientConnection(); + + // Launch the asio context in its own thread + m_threadContext = std::thread([this]() { m_asioContext.run(); }); + } + catch (std::exception& e) + { + // Something prohibited the server from listening + std::cerr << "[SERVER] Exception: " << e.what() << "\n"; + return false; + } + + std::cout << "[SERVER] Started!\n"; + return true; + } + + // Stops the server! + void Stop() + { + // Request the context to close + m_asioContext.stop(); + + // Tidy up the context thread + if (m_threadContext.joinable()) m_threadContext.join(); + + // Inform someone, anybody, if they care... + std::cout << "[SERVER] Stopped!\n"; + } + + // ASYNC - Instruct asio to wait for connection + void WaitForClientConnection() + { + // Prime context with an instruction to wait until a socket connects. This + // is the purpose of an "acceptor" object. It will provide a unique socket + // for each incoming connection attempt + m_asioAcceptor.async_accept( + [this](std::error_code ec, asio::ip::tcp::socket socket) + { + // Triggered by incoming connection request + if (!ec) + { + // Display some useful(?) information + std::cout << "[SERVER] New Connection: " << socket.remote_endpoint() << "\n"; + + // Create a new connection to handle this client + std::shared_ptr> newconn = + std::make_shared>(connection::owner::server, + m_asioContext, std::move(socket), m_qMessagesIn); + + // Give the user server a chance to deny connection + if (OnClientConnect(newconn)) + { + // Connection allowed, so add to container of new connections + m_deqConnections.push_back(std::move(newconn)); + + // And very important! Issue a task to the connection's + // asio context to sit and wait for bytes to arrive! + m_deqConnections.back()->ConnectToClient(this, nIDCounter++); + + std::cout << "[" << m_deqConnections.back()->GetID() << "] Connection Approved\n"; + } + else + { + std::cout << "[-----] Connection Denied\n"; + + // Connection will go out of scope with no pending tasks, so will + // get destroyed automagically due to the wonder of smart pointers + } + } + else + { + // Error has occurred during acceptance + std::cout << "[SERVER] New Connection Error: " << ec.message() << "\n"; + } + + // Prime the asio context with more work - again simply wait for + // another connection... + WaitForClientConnection(); + }); + } + + // Send a message to a specific client + void MessageClient(std::shared_ptr> client, const message& msg) + { + // Check client is legitimate... + if (client && client->IsConnected()) + { + // ...and post the message via the connection + client->Send(msg); + } + else + { + // If we cant communicate with client then we may as + // well remove the client - let the server know, it may + // be tracking it somehow + OnClientDisconnect(client); + + // Off you go now, bye bye! + client.reset(); + + // Then physically remove it from the container + m_deqConnections.erase( + std::remove(m_deqConnections.begin(), m_deqConnections.end(), client), m_deqConnections.end()); + } + } + + // Send message to all clients + void MessageAllClients(const message& msg, std::shared_ptr> pIgnoreClient = nullptr) + { + bool bInvalidClientExists = false; + + // Iterate through all clients in container + for (auto& client : m_deqConnections) + { + // Check client is connected... + if (client && client->IsConnected()) + { + // ..it is! + if (client != pIgnoreClient) + client->Send(msg); + } + else + { + // The client couldnt be contacted, so assume it has + // disconnected. + OnClientDisconnect(client); + client.reset(); + + // Set this flag to then remove dead clients from container + bInvalidClientExists = true; + } + } + + // Remove dead clients, all in one go - this way, we dont invalidate the + // container as we iterated through it. + if (bInvalidClientExists) + m_deqConnections.erase( + std::remove(m_deqConnections.begin(), m_deqConnections.end(), nullptr), m_deqConnections.end()); + } + + // Force server to respond to incoming messages + void Update(size_t nMaxMessages = -1, bool bWait = false) + { + if (bWait) m_qMessagesIn.wait(); + + // Process as many messages as you can up to the value + // specified + size_t nMessageCount = 0; + while (nMessageCount < nMaxMessages && !m_qMessagesIn.empty()) + { + // Grab the front message + auto msg = m_qMessagesIn.pop_front(); + + // Pass to message handler + OnMessage(msg.remote, msg.msg); + + nMessageCount++; + } + } + + protected: + // This server class should override thse functions to implement + // customised functionality + + // Called when a client connects, you can veto the connection by returning false + virtual bool OnClientConnect(std::shared_ptr> client) + { + return false; + } + + // Called when a client appears to have disconnected + virtual void OnClientDisconnect(std::shared_ptr> client) + { + + } + + // Called when a message arrives + virtual void OnMessage(std::shared_ptr> client, message& msg) + { + + } + + public: + + virtual void OnClientValidated(std::shared_ptr> client) + { + + } + + + protected: + // Thread Safe Queue for incoming message packets + tsqueue> m_qMessagesIn; + + // Container of active validated connections + std::deque>> m_deqConnections; + + // Order of declaration is important - it is also the order of initialisation + asio::io_context m_asioContext; + std::thread m_threadContext; + + // These things need an asio context + asio::ip::tcp::acceptor m_asioAcceptor; // Handles new incoming connection attempts... + + // Clients will be identified in the "wider system" via an ID + uint32_t nIDCounter = 10000; + }; +} \ No newline at end of file diff --git a/include/net_tsqueue.h b/include/net_tsqueue.h new file mode 100644 index 0000000..d955d86 --- /dev/null +++ b/include/net_tsqueue.h @@ -0,0 +1,114 @@ +#pragma once + +#include "net_common.h" + +namespace net +{ + template + class tsqueue + { + public: + tsqueue() = default; + tsqueue(const tsqueue&) = delete; + tsqueue& operator=(const tsqueue&) = delete; + tsqueue(tsqueue&&) noexcept = default; + + tsqueue& operator=(tsqueue&& other) noexcept + { + muxQueue = std::move(other.muxQueue); + deqQueue = std::move(other.deqQueue); + return *this; + } + + virtual ~tsqueue() { clear(); } + + public: + // Returns and maintains item at front of Queue + const T& front() + { + std::scoped_lock lock(muxQueue); + return deqQueue.front(); + } + + // Returns and maintains item at back of Queue + const T& back() + { + std::scoped_lock lock(muxQueue); + return deqQueue.back(); + } + + // Removes and returns item from front of Queue + T pop_front() + { + std::scoped_lock lock(muxQueue); + auto t = std::move(deqQueue.front()); + deqQueue.pop_front(); + return t; + } + + // Removes and returns item from back of Queue + T pop_back() + { + std::scoped_lock lock(muxQueue); + auto t = std::move(deqQueue.back()); + deqQueue.pop_back(); + return t; + } + + // Adds an item to back of Queue + void push_back(const T& item) + { + std::scoped_lock lock(muxQueue); + deqQueue.emplace_back(std::move(item)); + + std::unique_lock ul(muxBlocking); + cvBlocking.notify_one(); + } + + // Adds an item to front of Queue + void push_front(const T& item) + { + std::scoped_lock lock(muxQueue); + deqQueue.emplace_front(std::move(item)); + + std::unique_lock ul(muxBlocking); + cvBlocking.notify_one(); + } + + // Returns true if Queue has no items + bool empty() + { + std::scoped_lock lock(muxQueue); + return deqQueue.empty(); + } + + // Returns number of items in Queue + size_t count() + { + std::scoped_lock lock(muxQueue); + return deqQueue.size(); + } + + // Clears Queue + void clear() + { + std::scoped_lock lock(muxQueue); + deqQueue.clear(); + } + + void wait() + { + while (empty()) + { + std::unique_lock ul(muxBlocking); + cvBlocking.wait(ul); + } + } + + protected: + std::mutex muxQueue; + std::deque deqQueue; + std::condition_variable cvBlocking; + std::mutex muxBlocking; + }; +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 43814e8..fe94dc4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,7 @@ // PacmanConsole.cpp: определяет точку входа для приложения. // -#include "main.h" - +#include using namespace std::chrono; // using namespace std::chrono_literals; From 50bff0ee9163d2c875dd385a0ac7c119dc13a19a Mon Sep 17 00:00:00 2001 From: UnknownXXX000 <59707969+UnknownXXX000@users.noreply.github.com> Date: Thu, 30 May 2024 10:54:27 +0300 Subject: [PATCH 3/5] change byte order --- include/game_structs.h | 422 ++++++++++++++++++++-- include/net_client.h | 3 + include/net_common.h | 57 ++- include/net_connection.h | 8 + include/net_message.h | 145 +++++++- include/net_server.h | 6 + src/main.cpp | 748 ++++++++++++++++++++++++++++++++++++++- 7 files changed, 1355 insertions(+), 34 deletions(-) diff --git a/include/game_structs.h b/include/game_structs.h index 31bf48c..53da116 100644 --- a/include/game_structs.h +++ b/include/game_structs.h @@ -1,38 +1,50 @@ #pragma once -enum class PTypes : uint32_t -{ - CLIENT_CONNECT = 0x01, - SERVER_SEND_MAP = 0x10, - CLIENT_READY = 0x02, - SERVER_GAME_START = 0x20, - CLIENT_SEND_KEY = 0x00, - SERVER_SEND_KEY_TO_OTHERS = 0xffffffff, - SERVER_PING = 0x50, - SERVER_ACCEPT = 0x60, -}; +#include +#include "net_common.h" +// #include -enum class PlayerMoves : uint8_t -{ - UP = 0, - RIGHT = 1, - DOWN = 2, - LEFT = 4 -}; +constexpr static unsigned int MAX_PLAYER_NAME_LEN = 256; +constexpr static unsigned int MAP_PART_SIZE = 20 * 15; -enum class CellType : uint8_t +//#ifndef OLC_PGE_APPLICATION +//#define OLC_PGE_APPLICATION +//#endif +// +//#include +// +//#ifndef OLC_PGEX_TRANSFORMEDVIEW +//#define OLC_PGEX_TRANSFORMEDVIEW +//#endif +// +//#include + +inline uint8_t convert(const CellType& c) { - EMPTY = 0x00, - WALL = 0xff, - FOOD = 0xaa, - PLAYER = 0x22 -}; + switch (c) + { + case CellType::EMPTY: + return 0; + case CellType::PLAYER: + return 0x22; + case CellType::FOOD: + return 0xaa; + case CellType::WALL: + return 0xff; + } +} template class GameField { private: - CellType field[WIDTH][HEIGHT] = {0}; + union { + CellType field[WIDTH][HEIGHT]; + uint8_t bytes[HEIGHT * WIDTH]; + + // Correct: field[HEIGHT][WIDTH]; + }; + // std::mutex Mutex; public: constexpr GameField() = default; ~GameField() = default; @@ -102,8 +114,364 @@ class GameField { static_assert(WIDTH % 2 == 0); - for (size_t i = 0; i < WIDTH / 2; i++) + for (size_t j = 0; j < HEIGHT; j++) + for (size_t i = 0; i < WIDTH / 2; i++) + { + const auto temp = field[i][j]; + // std::swap(field[i][j], field[WIDTH - i][j]); + field[i][j] = field[WIDTH - i - 1][j]; + field[WIDTH - i - 1][j] = temp; + } + } + + template + void InsertToAnother(GameField& other, const size_t offset_x, const size_t offset_y) + { + assert(offset_x + WIDTH <= OTHER_WIDTH && offset_y + HEIGHT <= OTHER_HEIGHT); + + for (size_t j = 0; j < HEIGHT; j++) + for (size_t i = 0; i < WIDTH; i++) + other(i + offset_x, j + offset_y) = field[i][j]; + } + + public: + friend std::ostream& operator<<(std::ostream& os, const GameField& f) + { + for (size_t j = 0; j < f.GetHeight(); j++) + { + for (size_t i = 0; i < f.GetWidth(); i++) + { + char c = '\0'; + switch (f.at(i, j)) + { + case CellType::EMPTY: + c = ' '; + break; + case CellType::WALL: + c = '*'; + break; + case CellType::FOOD: + c = '0'; + break; + case CellType::PLAYER: + c = '+'; + break; + default: + break; + } + os << c; + } + os << '\n'; + } + return os; + } + + void GetStartPosition(uint32_t& x, uint32_t& y) const + { + bool bDone = false; + // // std::scoped_lock lock(Mutex); + for (size_t i = 0; i < WIDTH; i++) + { for (size_t j = 0; j < HEIGHT; j++) - std::swap(field[i][j], field[WIDTH - i][j]); + { + if (field[i][j] == CellType::EMPTY || field[i][j] == CellType::FOOD) + { + x = i; + y = j; + bDone = true; + break; + } + } + if (bDone) break; + } + } + + public: + std::array ToBytes() const noexcept + { + // std::scoped_lock lock(Mutex); + std::array byteArray; + std::memcpy(byteArray.data(), &bytes[0], WIDTH * HEIGHT); + return byteArray; + } + + void FromBytes(const std::array& byteArray) + { + // std::scoped_lock lock(Mutex); + std::memcpy(&bytes[0], byteArray.data(), WIDTH * HEIGHT); + } + + void WriteToCArray(uint8_t(&array)[WIDTH * HEIGHT]) + { + // std::scoped_lock lock(Mutex); + std::memcpy(&array[0], &bytes[0], WIDTH * HEIGHT); + } + + void ReadFromCArray(uint8_t(&array)[WIDTH * HEIGHT]) + { + // std::scoped_lock lock(Mutex); + std::memcpy(&bytes[0], &array[0], WIDTH * HEIGHT); + } + + void WriteToCArrayFixed(uint8_t(&array)[WIDTH * HEIGHT]) + { + size_t index = 0; + // CellType field[WIDTH][HEIGHT]; + + for (size_t i = 0; i < HEIGHT; i++) + { + for (size_t j = 0; j < WIDTH; j++) + { + array[index] = static_cast(field[j][i]); + index++; + } + } + } + + void ReadFromCArrayFixed(uint8_t(&array)[WIDTH * HEIGHT]) + { + size_t index = 0; + // CellType field[WIDTH][HEIGHT]; + + for (size_t i = 0; i < HEIGHT; i++) + { + for (size_t j = 0; j < WIDTH; j++) + { + field[j][i] = static_cast(array[index]); + index++; + } + } + } + + uint8_t* data() const noexcept + { + // std::scoped_lock lock(Mutex); + return &bytes[0]; + } + + void GenerateMap() + { + MazeGenerator(*this); + } + + template + void GenerateSymmetricMap(GameField& other) + { + assert(WIDTH * 2 == OTHER_WIDTH && HEIGHT * 2 == OTHER_HEIGHT); + + this->InsertToAnother(other, 0, 0); + this->InvertVertical(); + this->InsertToAnother(other, 0, HEIGHT); + this->InvertHorizontal(); + this->InsertToAnother(other, WIDTH, HEIGHT); + this->InvertVertical(); + this->InsertToAnother(other, WIDTH, 0); + } +}; + +template +class MazeGenerator +{ + friend GameField; + +private: + + GameField& field; + + const int NUM_OBSTACLES = 5; + + // define the tetris shapes + const int TETRIS_SHAPES[7][4][2] = { + {{0,0}, {1,0}, {0,1}, {1,1}}, // square + {{0,0}, {1,0}, {2,0}, {3,0}}, // horizontal line + {{0,0}, {0,1}, {0,2}, {0,3}}, // vertical line + {{0,0}, {1,0}, {1,1}, {2,1}}, // L shape + {{0,0}, {1,0}, {1,1}, {1,2}}, // inverted L shape + {{0,0}, {1,0}, {1,1}, {2,0}}, // T shape + {{0,0}, {1,0}, {1,1}, {2,1}} // S shape + }; + +public: + MazeGenerator() = delete; + ~MazeGenerator() = default; + + MazeGenerator(const MazeGenerator&) = delete; + MazeGenerator(MazeGenerator&&) noexcept = delete; + MazeGenerator& operator=(const MazeGenerator&) = delete; + MazeGenerator& operator=(MazeGenerator&&) noexcept = delete; + +private: + MazeGenerator(GameField& f) : field(f) + { + srand((unsigned)time(NULL)); + generate_obstacle_field(); + } + +private: + + // check if a cell is inside the field + inline bool is_inside(int x, int y) { + return x >= 0 && x < WIDTH && y >= 0 && y < HEIGHT; + } + + // check if a cell is an obstacle + inline bool is_obstacle(int x, int y) { + return field(x, y) == CellType::WALL; + } + + // check if a cell is empty + inline bool is_empty(int x, int y) { + return field(x, y) == CellType::FOOD || field(x, y) == CellType::EMPTY; + } + + int non_obstacle_neighbors(int x, int y) { + int count = 0; + const bool cell_inside = is_inside(x, y); + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + if (dx == 0 && dy == 0) continue; + if ((is_inside(x + dx, y + dy) && is_empty(x + dx, y + dy)) || (!is_inside(x + dx, y + dy) && cell_inside)) { + count++; + } + } + } + return count; + } + + // check if a cell has at least 2 non-obstacle neighbors + bool has_non_obstacle_neighbors(int x, int y) { + int count = 0; + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + if (dx == 0 && dy == 0) continue; + if (is_inside(x + dx, y + dy) && is_empty(x + dx, y + dy)) { + count++; + } + } + } + return count >= 2; + } + + // check if a tetris shape can be placed at a given position without intersecting with any obstacle + bool can_place_tetris_shape(int x, int y, int shape_index) { + for (int i = 0; i < 4; i++) { + int dx = TETRIS_SHAPES[shape_index][i][0]; + int dy = TETRIS_SHAPES[shape_index][i][1]; + if (!is_inside(x + dx, y + dy) || is_obstacle(x + dx, y + dy)) { + return false; + } + if (non_obstacle_neighbors(x + dx, y + dy) < 8) return false; + } + return true; + } + + // place a tetris shape at a given position + void place_tetris_shape(int x, int y, int shape_index) { + for (int i = 0; i < 4; i++) { + int dx = TETRIS_SHAPES[shape_index][i][0]; + int dy = TETRIS_SHAPES[shape_index][i][1]; + field(x + dx, y + dy) = CellType::WALL; + } + } + + // generate a random tetris shape + int random_tetris_shape() { + return rand() % 7; + } + + // generate a random position for a tetris shape + void random_position(int& x, int& y) { + x = rand() % WIDTH; + y = rand() % HEIGHT; + } + + // check if two tetris shapes are colliding + bool are_colliding(int x1, int y1, int shape_index1, int x2, int y2, int shape_index2) { + for (int i = 0; i < 4; i++) { + int dx1 = TETRIS_SHAPES[shape_index1][i][0]; + int dy1 = TETRIS_SHAPES[shape_index1][i][1]; + for (int j = 0; j < 4; j++) { + int dx2 = TETRIS_SHAPES[shape_index2][j][0]; + int dy2 = TETRIS_SHAPES[shape_index2][j][1]; + if (x1 + dx1 == x2 + dx2 && y1 + dy1 == y2 + dy2) { + return true; + } + } + } + return false; + } + + + void generate_obstacle_field() { + for (int i = 0; i < WIDTH; i++) { + for (int j = 0; j < HEIGHT; j++) { + field(i, j) = CellType::FOOD; + } + } + + for (int i = 0; i < NUM_OBSTACLES; i++) { + int shape_index1 = random_tetris_shape(); + int x1, y1; + do { + random_position(x1, y1); + } while (!can_place_tetris_shape(x1, y1, shape_index1)); + place_tetris_shape(x1, y1, shape_index1); + + // check for collisions with other obstacles + for (int j = 0; j < i; j++) { + int shape_index2 = random_tetris_shape(); + int x2, y2; + do { + random_position(x2, y2); + } while (!can_place_tetris_shape(x2, y2, shape_index2) || are_colliding(x1, y1, shape_index1, x2, y2, shape_index2)); + place_tetris_shape(x2, y2, shape_index2); + } + } + + for (int x = 0; x < WIDTH; x++) { + for (int y = 0; y < HEIGHT; y++) { + if (is_empty(x, y) && !has_non_obstacle_neighbors(x, y)) { + field(x, y) = CellType::WALL; + } + } + } + } +}; + +typedef GameField<20, 15> FieldPart; +typedef GameField<40, 30> FieldMap; + +struct CppPlayer +{ + uint32_t start_x; + uint32_t start_y; + PlayerMoves start_direction; + std::string name; + + player ToCPlayer() const + { + player p = { 0 }; + p.start_x = start_x; + p.start_y = start_y; + p.start_direction = static_cast(start_direction); + p.player_name_len = name.size(); + std::copy(name.begin(), name.end(), p.player_name); + p.player_name[name.size()] = '\0'; + + return p; + } + + void FromCPlayer(const player& p) + { + start_x = p.start_x; + start_y = p.start_y; + start_direction = static_cast(p.start_direction); + name = std::string(p.player_name, p.player_name_len); + } + + friend std::ostream& operator<<(std::ostream& os, const CppPlayer& cp) + { + os << "{ start_x = " << cp.start_x << ", start_y = " << cp.start_y << ", direction = " << static_cast(cp.start_direction) << ", name = " << cp.name << " }"; + return os; } }; \ No newline at end of file diff --git a/include/net_client.h b/include/net_client.h index 1ccd4a8..7f703b2 100644 --- a/include/net_client.h +++ b/include/net_client.h @@ -101,7 +101,10 @@ namespace net void Send(const message& msg) { if (IsConnected()) + { + // msg.SwapEndianness(); m_connection->Send(msg); + } } protected: diff --git a/include/net_common.h b/include/net_common.h index 9a6dc65..e13cd63 100644 --- a/include/net_common.h +++ b/include/net_common.h @@ -24,4 +24,59 @@ #include #include -#include \ No newline at end of file +#include + +enum class PTypes : uint32_t +{ + CLIENT_SEND_KEY = 0x00, + CLIENT_CONNECT = 0x01, + CLIENT_READY = 0x02, + SERVER_SEND_MAP = 0x10, + SERVER_GAME_START = 0x20, + SERVER_SEND_KEY_TO_OTHERS = 0xffffffff, + // SERVER_PING = 0x50, +}; + +// typedef net::message GameMessage; + +enum class PlayerMoves : uint8_t +{ + UP = 0, + RIGHT = 1, + DOWN = 2, + LEFT = 3 +}; + +enum class CellType : uint8_t +{ + EMPTY = 0x00, + PLAYER = 0x22, + FOOD = 0xaa, + WALL = 0xff +}; + +struct player { + uint32_t start_x; + uint32_t start_y; + uint32_t start_direction; + uint32_t player_name_len; + char player_name[256]; +}; + + + +//template || (std::is_enum_v && std::is_same_v, uint32_t>)), T>* = nullptr> +template, T>* = nullptr> +constexpr inline void ChangeEndian(T& value) +{ + if constexpr (sizeof(T) > 1u) + { + //auto val = value; + std::array valueBytes; + std::memcpy(valueBytes.data(), &value, sizeof(T)); + std::reverse(valueBytes.begin(), valueBytes.end()); + std::memcpy(&value, valueBytes.data(), sizeof(T)); + //return val; + } + //return value; +} \ No newline at end of file diff --git a/include/net_connection.h b/include/net_connection.h index c6609b8..ecccb87 100644 --- a/include/net_connection.h +++ b/include/net_connection.h @@ -83,6 +83,10 @@ namespace net void Send(const message& msg) { + // Reverse msg byte order + + //const_cast&>(msg).ReverseHeader(); + asio::post(m_asioContext, [this, msg]() { @@ -203,8 +207,12 @@ namespace net { if (!ec) { + // Change endianness + ChangeEndian(m_msgTemporaryIn.header.size); + // A complete message header has been read, check if this message // has a body to follow... + if (m_msgTemporaryIn.header.size > 0) { // ...it does, so allocate enough space in the messages' body diff --git a/include/net_message.h b/include/net_message.h index 761ea15..798f031 100644 --- a/include/net_message.h +++ b/include/net_message.h @@ -2,7 +2,34 @@ #include "net_common.h" -constexpr static uint32_t MAGIC = 0xabcdfe01; +constexpr static uint32_t MAGIC = 0xabcdfe01; +constexpr static uint32_t MAGIC_LITTLE = 0x01fecdab; + +//template, T>* = nullptr> +//constexpr inline void ChangeEndian(T& value); + +struct player; + +/* +template && !(std::is_unsigned_v || std::is_enum_v), T>* = nullptr> +void foo(const T& data) +{ + // static_assert(std::is_standard_layout::value, "Data too complex"); + std::cout << typeid(data).name() << " main called\n"; +} + +template, T>* = nullptr> +void foo(const T& data) +{ + std::cout << typeid(data).name() << "unsigned called\n"; +} + +template && std::is_unsigned_v>,T>* = nullptr> +void foo(const T& data) +{ + std::cout << typeid(std::underlying_type_t).name() << " " << typeid(T).name() << " called\n"; +} +*/ namespace net { @@ -45,11 +72,11 @@ namespace net // Plain Old Data (POD). TLDR: Serialise & Deserialise into/from a vector // Pushes any POD-like data into the message buffer - template + template && !(std::is_unsigned_v && sizeof(DataType) == 4u) && !std::is_same_v, DataType>* = nullptr> friend message& operator << (message& msg, const DataType& data) { // Check that the type of the data being pushed is trivially copyable - static_assert(std::is_standard_layout_v, "Data is too complex to be pushed into vector"); + //static_assert(std::is_standard_layout_v, "Data is too complex to be pushed into vector"); // Cache current size of vector, as this will be the point we insert the data const size_t i = msg.data.size(); @@ -68,18 +95,118 @@ namespace net } // Pulls any POD-like data form the message buffer - template + template && !(std::is_unsigned_v && sizeof(DataType) == 4u) && !std::is_same_v, DataType>* = nullptr> friend message& operator >> (message& msg, DataType& data) { // Check that the type of the data being pushed is trivially copyable - static_assert(std::is_standard_layout_v, "Data is too complex to be pulled from vector"); + // static_assert(std::is_standard_layout_v, "Data is too complex to be pulled from vector"); + // Cache the location towards the end of the vector where the pulled data starts + size_t i = msg.body.size() - sizeof(DataType); + + // Physically copy the data from the vector into the user variable + std::memcpy(&data, msg.body.data() + i, sizeof(DataType)); + + // Shrink the vector to remove read bytes, and reset end position + msg.body.resize(i); + + // Recalculate the message size + msg.header.size = msg.size(); + + // Return the target message so it can be "chained" + return msg; + } + + // For unsigned 4 byte values + template && sizeof(DataType) == 4u), DataType>* = nullptr> + friend message& operator << (message& msg, const DataType& data) + { + auto dataCopy = data; + ChangeEndian(dataCopy); + // Cache current size of vector, as this will be the point we insert the data + size_t i = msg.body.size(); + + // Resize the vector by the size of the data being pushed + msg.body.resize(msg.body.size() + sizeof(DataType)); + + // Physically copy the data into the newly allocated vector space + std::memcpy(msg.body.data() + i, &dataCopy, sizeof(DataType)); + + // Recalculate the message size + msg.header.size = msg.size(); + + // Return the target message so it can be "chained" + return msg; + } + + // For unsigned 4 byte values + template && sizeof(DataType) == 4u), DataType>* = nullptr> + friend message& operator >> (message& msg, DataType& data) + { // Cache the location towards the end of the vector where the pulled data starts const size_t i = msg.data.size() - sizeof(DataType); // Physically copy the data from the vector into the user variable std::memcpy(&data, msg.data.data() + i, sizeof(DataType)); + ChangeEndian(data); + + // Shrink the vector to remove read bytes, and reset end position + msg.body.resize(i); + + // Recalculate the message size + msg.header.size = msg.size(); + + // Return the target message so it can be "chained" + return msg; + } + + // For player struct + friend message& operator << (message& msg, const player& data) + { + // Check that the type of the data being pushed is trivially copyable + //static_assert(std::is_standard_layout_v, "Data is too complex to be pushed into vector"); + + auto playerCopy = data; + + ChangeEndian(playerCopy.start_x); + ChangeEndian(playerCopy.start_y); + ChangeEndian(playerCopy.start_direction); + ChangeEndian(playerCopy.player_name_len); + + // Cache current size of vector, as this will be the point we insert the data + size_t i = msg.body.size(); + + // Resize the vector by the size of the data being pushed + msg.body.resize(msg.body.size() + sizeof(player)); + + // Physically copy the data into the newly allocated vector space + std::memcpy(msg.body.data() + i, &playerCopy, sizeof(player)); + + // Recalculate the message size + msg.header.size = msg.size(); + + // Return the target message so it can be "chained" + return msg; + } + + // For player struct + friend message& operator >> (message& msg, player& data) + { + // Check that the type of the data being pushed is trivially copyable + // static_assert(std::is_standard_layout_v, "Data is too complex to be pulled from vector"); + + // Cache the location towards the end of the vector where the pulled data starts + size_t i = msg.body.size() - sizeof(player); + + // Physically copy the data from the vector into the user variable + std::memcpy(&data, msg.body.data() + i, sizeof(player)); + + ChangeEndian(data.start_x); + ChangeEndian(data.start_y); + ChangeEndian(data.start_direction); + ChangeEndian(data.player_name_len); + // Shrink the vector to remove read bytes, and reset end position msg.data.resize(i); @@ -89,6 +216,14 @@ namespace net // Return the target message so it can be "chained" return msg; } + + // Reverse byte order of header + void ReverseHeader() + { + ChangeEndian(header.magic); + if constexpr (std::is_unsigned_v && sizeof(T) == 4u) ChangeEndian(header.id); + ChangeEndian(header.size); + } }; template diff --git a/include/net_server.h b/include/net_server.h index 4504bdd..061f2d3 100644 --- a/include/net_server.h +++ b/include/net_server.h @@ -120,6 +120,7 @@ namespace net // Send a message to a specific client void MessageClient(std::shared_ptr> client, const message& msg) { + // msg.SwapEndianness(); // Check client is legitimate... if (client && client->IsConnected()) { @@ -145,6 +146,7 @@ namespace net // Send message to all clients void MessageAllClients(const message& msg, std::shared_ptr> pIgnoreClient = nullptr) { + // msg.SwapEndianness(); bool bInvalidClientExists = false; // Iterate through all clients in container @@ -189,6 +191,10 @@ namespace net // Grab the front message auto msg = m_qMessagesIn.pop_front(); + // Restore byte order + + //msg.msg.ReverseHeader(); + // Pass to message handler OnMessage(msg.remote, msg.msg); diff --git a/src/main.cpp b/src/main.cpp index fe94dc4..c914a52 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,7 +3,753 @@ #include -using namespace std::chrono; +//#include "game_client.h" +//#include "game_server.h" + +using namespace std::chrono_literals; + +class GameClient : public olc::PixelGameEngine, net::client_interface +{ +public: + GameClient() + { + sAppName = "PacmanConsole"; + myName = std::string("Default") + std::to_string(static_cast(std::chrono::system_clock::now().time_since_epoch().count())); + serverIP = "127.0.0.1"; + serverPort = 60000; + // myDirection = static_cast(myName.size() % 4); + // bRegisterMsgSent = false; + + if (myName.size() > 256u) myName.resize(256u); + } + GameClient(const std::string& name, const std::string& ip, const uint16_t& port) : + myName(name), serverIP(ip), serverPort(port) + { + sAppName = "PacmanConsole"; + // bRegisterMsgSent = false; + + if (myName.size() > 256u) myName.resize(256u); + } + +private: + + inline void SendClientReady() + { + net::message clientReadyMsg; + clientReadyMsg.header.id = PTypes::CLIENT_READY; + // clientReadyMsg.SwapEndianness(); + + clientReadyMsg.ReverseHeader(); + Send(clientReadyMsg); + } + + inline void SendClientConnect() + { + // if (bRegisterMsgSent) return; + + net::message registerMsg; + + registerMsg.header.id = PTypes::CLIENT_CONNECT; + char name[MAX_PLAYER_NAME_LEN]; + std::copy(myName.begin(), myName.end(), name); + name[myName.size()] = '\0'; + registerMsg << name; + + registerMsg.ReverseHeader(); + // registerMsg.SwapEndianness(); + Send(registerMsg); + std::cout << "Register message sent\n"; + + // bRegisterMsgSent = true; + // bRegisterMsgSent = true; + } + + void SendKeyInput() + { + const auto endTime = std::chrono::system_clock::now(); + + if (std::chrono::duration_cast(endTime - m_StartTime).count() < 100ll) return; + + PlayerMoves dir = localPlayer->start_direction; + + int offset_x = 0, offset_y = 0; + + if (GetKey(olc::Key::W).bHeld) dir = PlayerMoves::UP; + if (GetKey(olc::Key::S).bHeld) dir = PlayerMoves::DOWN; + if (GetKey(olc::Key::A).bHeld) dir = PlayerMoves::LEFT; + if (GetKey(olc::Key::D).bHeld) dir = PlayerMoves::RIGHT; + + switch (dir) + { + case PlayerMoves::UP: + offset_y--; + break; + case PlayerMoves::DOWN: + offset_y++; + break; + case PlayerMoves::LEFT: + offset_x--; + break; + case PlayerMoves::RIGHT: + offset_x++; + break; + } + + offset_x += static_cast(localPlayer->start_x); + offset_y += static_cast(localPlayer->start_y); + + if (offset_x >= 0 && offset_x < GameMap.GetWidth() && offset_y >= 0 && offset_y < GameMap.GetHeight() && + (GameMap.at(offset_x, offset_y) == CellType::FOOD || GameMap.at(offset_x, offset_y) == CellType::EMPTY)) + { + localPlayer->start_direction = dir; + + if (GameMap.at(offset_x, offset_y) == CellType::FOOD) + { + score++; + foodLeft--; + } + + GameMap(localPlayer->start_x, localPlayer->start_y) = CellType::EMPTY; + + localPlayer->start_x = static_cast(offset_x); + localPlayer->start_y = static_cast(offset_y); + + GameMap(localPlayer->start_x, localPlayer->start_y) = CellType::PLAYER; + + + net::message sendKeyMsg; + sendKeyMsg.header.id = PTypes::CLIENT_SEND_KEY; + + sendKeyMsg << dir; + + sendKeyMsg.ReverseHeader(); + // sendKeyMsg.SwapEndianness(); + Send(sendKeyMsg); + + // std::this_thread::sleep_for(100ms); + m_StartTime = std::chrono::system_clock::now(); + } + + } + +public: + bool OnUserCreate() override + { + tv = olc::TileTransformedView({ ScreenWidth(), ScreenHeight() }, { 8, 8 }); + if (Connect(serverIP, serverPort)) + { + SendClientConnect(); + + return true; + } + return false; + } + + bool OnUserUpdate(float fElapsedTime) override + { + + if (!bGameOver && !IsConnected()) + { + SendClientConnect(); + // std::this_thread::sleep_for(1s); + } + + if (foodLeft == 0) bGameOver = true; + if (bGameOver) + { + std::cout << "Your score: " << score << std::endl; + return false; + } + + // std::cout << "On user update\n"; + if (IsConnected()) + { + while (!Incoming().empty()) + { + auto msg = Incoming().pop_front().msg; + + msg.ReverseHeader(); + + // std::cout << std::hex << msg << '\n'; + + if (msg.header.magic != MAGIC) + continue; + + switch (msg.header.id) + { + case PTypes::SERVER_SEND_MAP: + { + bWaitingForRegistration = false; + + // if (!bRegisterMsgSent) bRegisterMsgSent = true; + uint8_t arr[MAP_PART_SIZE]; + msg >> arr; + FieldPart part; + //part.ReadFromCArray(arr); + part.ReadFromCArrayFixed(arr); + + std::cout << "Received map part:\n" << part << '\n'; + part.GenerateSymmetricMap(GameMap); + + SendClientReady(); + break; + } + case PTypes::SERVER_GAME_START: + { + bWaitingForRegistration = false; + if (!bReady) + { + m_StartTime = std::chrono::system_clock::now(); + bReady = true; + } + + uint32_t frame_timeout = 0; + size_t player_count = 4; + + msg >> frame_timeout; + + frame_timeout = static_cast(std::chrono::system_clock::now().time_since_epoch().count()) - frame_timeout; + + msg >> player_count; + + std::cout << "Frame timeout = " << frame_timeout << '\n'; + + playerList.resize(player_count); + + for (size_t i = 0; i < player_count; i++) + { + player p; + msg >> p; + + playerList[i].FromCPlayer(p); + std::cout << playerList[i] << '\n'; + + if (myName == playerList[i].name) + localPlayer = &playerList[i]; + + GameMap(playerList[i].start_x, playerList[i].start_y) = CellType::PLAYER; + } + + foodLeft = GameMap.countFood(); + + score = 0; + + break; + } + + case PTypes::SERVER_SEND_KEY_TO_OTHERS: + { + PlayerMoves OtherDir; + CppPlayer* playerSent = nullptr; + char name[MAX_PLAYER_NAME_LEN]; + + msg >> name; + msg >> OtherDir; + + const std::string FromName(name); + + // std::cout << "Received direction " << static_cast(OtherDir) << " from player " << FromName << '\n'; + + for (auto& player : playerList) + { + if (player.name == FromName) + { + playerSent = &player; + break; + } + } + + if (playerSent == nullptr) break; + + GameMap(playerSent->start_x, playerSent->start_y) = CellType::EMPTY; + + if (OtherDir == PlayerMoves::UP) playerSent->start_y--; + else if (OtherDir == PlayerMoves::DOWN) playerSent->start_y++; + else if (OtherDir == PlayerMoves::LEFT) playerSent->start_x--; + else if (OtherDir == PlayerMoves::RIGHT) playerSent->start_x++; + + if (GameMap.at(playerSent->start_x, playerSent->start_y) == CellType::FOOD) + foodLeft--; + + GameMap(playerSent->start_x, playerSent->start_y) = CellType::PLAYER; + + break; + } + + default: + break; + } + } + + if (bWaitingForRegistration) + { + Clear(olc::DARK_BLUE); + DrawString({ 10,10 }, "Waiting To Be Registered...", olc::WHITE); + return true; + } + + if (bReady) SendKeyInput(); + + // Update players on map + + Clear(olc::BLACK); + + // Draw World + olc::vi2d vTL = tv.GetTopLeftTile().max({ 0,0 }); + olc::vi2d vBR = tv.GetBottomRightTile().min(vWorldSize); + olc::vi2d vTile; + for (vTile.y = vTL.y; vTile.y < vBR.y; vTile.y++) + for (vTile.x = vTL.x; vTile.x < vBR.x; vTile.x++) + { + const auto& currentTile = GameMap.at(vTile.x, vTile.y); + switch (currentTile) + { + case CellType::WALL: + { + tv.FillRect(vTile, { 1.0f, 1.0f }); + // tv.DrawRect(olc::vf2d(vTile) + olc::vf2d(0.1f, 0.1f), { 0.8f, 0.8f }); + break; + } + case CellType::FOOD: + { + tv.FillCircle(olc::vf2d(vTile) + olc::vf2d(0.5f, 0.5f), 0.25f, olc::YELLOW); + break; + } + case CellType::PLAYER: + { + if (vTile.x == localPlayer->start_x && vTile.y == localPlayer->start_y) + tv.FillRect(olc::vf2d(vTile) + olc::vf2d(0.2f, 0.2f), {0.8f, 0.8f}, olc::GREEN); + else + tv.FillRect(olc::vf2d(vTile) + olc::vf2d(0.2f, 0.2f), { 0.8f, 0.8f }, olc::RED); + break; + } + case CellType::EMPTY: + { + break; + } + + } + } + + // Draw other info + + DrawString({ 10, static_cast(static_cast(ScreenHeight()) * 0.8f) }, "Your score: " + std::to_string(score)); + DrawString({ 10, static_cast(static_cast(ScreenHeight()) * 0.7f) }, "Food left: " + std::to_string(foodLeft)); + + } + return true; + } + +private: + olc::TileTransformedView tv; + olc::vi2d vWorldSize = { 40, 30 }; + + FieldMap GameMap; + std::vector playerList; + std::string myName; + + std::string serverIP; + uint16_t serverPort; + + // PlayerMoves myDirection; + bool bWaitingForRegistration = true; + bool bReady = false; + bool bGameOver = false; + + CppPlayer* localPlayer = nullptr; + uint32_t score = 0; + size_t foodLeft = UINT32_MAX; + + std::chrono::time_point m_StartTime; + // std::chrono::time_point m_EndTime; +}; + + +class GameServer : public net::server_interface +{ +private: + const uint16_t PlayerCount; + std::unordered_map PlayerList; + std::vector BannedIDs; + bool MapGenerated = false; + FieldPart GenMap; + uint32_t PlayerStartX; + uint32_t PlayerStartY; + std::unordered_set ReadyClients; + net::message SendMapMessage; + // net::message SendPlayerListMessage; +public: + + GameServer(uint16_t port) : net::server_interface(port), PlayerCount(2), PlayerStartX(0), PlayerStartY(0) + { + PlayerList.reserve(PlayerCount); + if (!MapGenerated) + { + GenMap.GenerateMap(); + FormSendMapMessage(); + GenMap.GetStartPosition(PlayerStartX, PlayerStartY); + MapGenerated = true; + } + } + + GameServer(uint16_t port, uint32_t player_count) : net::server_interface(port), PlayerCount(player_count), PlayerStartX(0), PlayerStartY(0) + { + PlayerList.reserve(PlayerCount); + if (!MapGenerated) + { + GenMap.GenerateMap(); + FormSendMapMessage(); + GenMap.GetStartPosition(PlayerStartX, PlayerStartY); + MapGenerated = true; + } + } + +private: + inline void FormSendMapMessage() + { + // net::message mapMsg; + SendMapMessage.header.id = PTypes::SERVER_SEND_MAP; + + uint8_t bytes[MAP_PART_SIZE]; + + std::cout << "[GameServer] Generated map:\n" << GenMap << '\n'; + + // GenMap.WriteToCArray(bytes); + GenMap.WriteToCArrayFixed(bytes); + + SendMapMessage << bytes; + + SendMapMessage.ReverseHeader(); + + // SendMapMessage.SwapEndianness(); + //const auto bytes = GenMap.ToBytes(); + + //for (const auto& byte_ : bytes) + // SendMapMessage << byte_; + + // return mapMsg; + } + + inline void FormSendPlayerListMessage() + { + // net::message playerListMsg; + net::message SendPlayerListMessage; + SendPlayerListMessage.header.id = PTypes::SERVER_GAME_START; + + for (const auto& [playerID, playerData] : PlayerList) + { + player p = playerData.ToCPlayer(); + SendPlayerListMessage << p; + } + + SendPlayerListMessage << PlayerList.size(); + + uint32_t frame_timeout = static_cast(std::chrono::system_clock::now().time_since_epoch().count()); + + SendPlayerListMessage << frame_timeout; + + SendPlayerListMessage.ReverseHeader(); + // SendPlayerListMessage.SwapEndianness(); + + MessageAllClients(SendPlayerListMessage); + // return playerListMsg; + } + + void OnGameReady() + { + RemoveDisconnectedClients(); + + if (ReadyClients.size() == PlayerCount) + { + std::cout << "[GameServer] Game is ready. Sending player list\n"; + + FormSendPlayerListMessage(); + + // auto msg = FormSendPlayerListMessage(); + + // MessageAllClients(msg); + } + } + +protected: + + bool OnClientConnect(std::shared_ptr> client) override + { + // RemoveDisconnectedClients(); + + if ((PlayerList.size() + 1u > PlayerCount) || (std::find(BannedIDs.begin(), BannedIDs.end(), client->GetID()) != BannedIDs.end())) + { + DisconnectClient(client); + return false; + } + + std::cout << "[GameServer] client " << client->GetID() << " connected\n"; + + //else + //{ + // std::cout << "[GameServer] client " << client->GetID() << " allowed\n"; + // PlayerList[client->GetID()] = { 0, 0, PlayerMoves::UP, "Unnamed" }; + //} + + std::cout << "[GameServer] Sending map to player " << client->GetID() << '\n'; + MessageClient(client, SendMapMessage); + + + //net::message msg; + //msg.header.ptype = PTypes::SERVER_SEND_MAP; + + //uint8_t MapBytes[20 * 15]; + + //GenMap.CopyToCArray(&MapBytes[0]); + + //msg << MapBytes; + + //client->Send(msg); + return true; + } + + void OnClientDisconnect(std::shared_ptr> client) override + { + if (client) + { + std::cout << "[GameServer] client " << client->GetID() << " disconnected\n"; + + const bool bBanned = std::find(BannedIDs.begin(), BannedIDs.end(), client->GetID()) != BannedIDs.end(); + + if (bBanned || PlayerList.find(client->GetID()) == PlayerList.end()) + { + // client never added to list, so just let it disappear + if (bBanned) + std::cout << "[GameServer] client " << client->GetID() << " is banned.\n"; + } + + else + { + auto& pd = PlayerList[client->GetID()]; + std::cout << "[GameServer] removed " << pd.name << '\n'; + PlayerList.erase(client->GetID()); + // PlayerCount--; + // BannedIDs.push_back(client->GetID()); + } + + if (ReadyClients.find(client->GetID()) != ReadyClients.end()) + { + std::cout << "[GameServer] client " << client->GetID() << " removed from ready clients\n"; + ReadyClients.erase(client->GetID()); + } + } + } + + // Called when a message arrives + void OnMessage(std::shared_ptr> client, net::message& msg) override + { + msg.ReverseHeader(); + // msg.SwapEndianness(); + // RemoveDisconnectedClients(); + + // std::cout << "[GameServer] client: " << client->GetID() << ", message info: " << msg << '\n'; + + //if (!BannedIDs.empty()) + //{ + // for (const auto& pid : BannedIDs) + // { + // // olc::net::message m; + // // m.header.id = GameMsg::Game_RemovePlayer; + // // m << pid; + // std::cout << "Removing " << pid << '\n'; + // // MessageAllClients(m); + // } + // BannedIDs.clear(); + //} + + if (msg.header.magic != MAGIC) + { + DisconnectClient(client); + return; + } + + switch (msg.header.id) + { + //case PTypes::SERVER_PING: + //{ + // std::cout << '[' << client->GetID() << "]: Server Ping\n"; + + // // Simply bounce message back to client + // client->Send(msg); + //} + //break; + + case PTypes::CLIENT_CONNECT: + { + if (PlayerList.find(client->GetID()) != PlayerList.end() || std::find(BannedIDs.begin(), BannedIDs.end(), client->GetID()) != BannedIDs.end()) + break; + + std::cout << "[GameServer] [" << client->GetID() << "]: Client connected\n"; + + auto& newPlayer = PlayerList[client->GetID()]; + // PlayerCount++; + + char tempName[MAX_PLAYER_NAME_LEN]; + + msg >> tempName; + + newPlayer.name = std::string(tempName); + + if (newPlayer.name == "Server") + { + const auto ID = client->GetID(); + DisconnectClient(client); + BannedIDs.push_back(ID); + break; + } + + bool bDone = false; + + for (const auto& [id, player] : PlayerList) + { + if (id != client->GetID() && newPlayer.name == player.name) + { + bDone = true; + const auto ID = client->GetID(); + DisconnectClient(client); + BannedIDs.push_back(ID); + break; + } + } + + if (bDone) break; + + std::cout << "[GameServer] player id: " << client->GetID() << ", name: " << newPlayer.name << '\n'; + + newPlayer.start_direction = static_cast(newPlayer.name.size() % 4u); + + switch (PlayerList.size()) + { + case 1u: + { + newPlayer.start_x = PlayerStartX; + newPlayer.start_y = PlayerStartY; + break; + } + case 2u: + { + newPlayer.start_x = 2u * GenMap.GetWidth() - PlayerStartX - 1u; + newPlayer.start_y = PlayerStartY; + break; + } + case 3u: + { + newPlayer.start_x = 2u * GenMap.GetWidth() - PlayerStartX - 1u; + newPlayer.start_y = 2u * GenMap.GetHeight() - PlayerStartY - 1u; + break; + } + case 4u: + { + newPlayer.start_x = PlayerStartX; + newPlayer.start_y = 2u * GenMap.GetHeight() - PlayerStartY - 1u; + break; + } + default: + newPlayer.start_x = 0u; + newPlayer.start_y = 0u; + break; + } + + // const auto mapMsg = FormSendMapMessage(); + + + //std::cout << "[GameServer] Sending map to player " << client->GetID() << '\n'; + //MessageClient(client, SendMapMessage); + + //if (PlayerList.size() == PlayerCount) + // OnGameReady(); + + // SendMap(client); + + // Construct a new message and send it to all clients + //net::message msg; + //msg.header.ptype = PTypes::CLIENT_CONNECT; + //msg << client->GetID(); + //MessageAllClients(msg, client); + break; + } + + case PTypes::CLIENT_READY: + { + std::cout << "[GameServer] player " << client->GetID() << ' ' << PlayerList.at(client->GetID()).name << " is ready!\n"; + // NumOfClientsReady++; + ReadyClients.insert(client->GetID()); + if (ReadyClients.size() == PlayerCount) + OnGameReady(); + break; + } + + case PTypes::CLIENT_SEND_KEY: + { + PlayerMoves dir; + msg >> dir; + + net::message SendKeyToOthersMsg; + SendKeyToOthersMsg.header.id = PTypes::SERVER_SEND_KEY_TO_OTHERS; + + SendKeyToOthersMsg << dir; + + char name[MAX_PLAYER_NAME_LEN]; + + std::copy(PlayerList.at(client->GetID()).name.begin(), PlayerList.at(client->GetID()).name.end(), name); + name[PlayerList.at(client->GetID()).name.size()] = '\0'; + + SendKeyToOthersMsg << name; + + SendKeyToOthersMsg.ReverseHeader(); + + // SendKeyToOthersMsg.SwapEndianness(); + MessageAllClients(SendKeyToOthersMsg, client); + + break; + } + + default: + { + std::cout << "[GameServer] [" << client->GetID() << "] wrong packet id. Removing client\n"; + DisconnectClient(client); + break; + } + + } + } + + +}; + +//// Override base class with your custom functionality +//class Example : public olc::PixelGameEngine +//{ +//public: +// Example() +// { +// // Name your application +// sAppName = "Example"; +// } +// +//public: +// bool OnUserCreate() override +// { +// // Called once at the start, so create things here +// return true; +// } +// +// bool OnUserUpdate(float fElapsedTime) override +// { +// // Called once per frame, draws random coloured pixels +// for (int x = 0; x < ScreenWidth(); x++) +// for (int y = 0; y < ScreenHeight(); y++) +// Draw(x, y, olc::Pixel(rand() % 256, rand() % 256, rand() % 256)); +// return true; +// } +//}; + + // using namespace std::chrono_literals; //std::vector vBuffer(10 * 1024); From 7c932fc6c0cd22ab1511daf6cb4d5e2832d62c7e Mon Sep 17 00:00:00 2001 From: UnknownXXX000 <59707969+UnknownXXX000@users.noreply.github.com> Date: Thu, 30 May 2024 10:57:34 +0300 Subject: [PATCH 4/5] change to network byte order --- CMakeLists.txt | 33 +- PDCurses/include/curses.h | 1406 ---- PDCurses/lib/pdcurses.lib | Bin 288632 -> 0 bytes include/game_client.h | 30 - include/game_server.h | 60 - include/game_structs.h | 136 +- include/includes.h | 33 +- include/net_client.h | 2 +- include/net_common.h | 8 + include/net_connection.h | 189 +- include/net_message.h | 54 +- include/net_server.h | 60 +- .../include/olcPGEX_TransformedView.h | 770 ++ .../include/olcPixelGameEngine.h | 6751 +++++++++++++++++ src/SnakeGame.cpp | 246 - src/main.cpp | 196 +- 16 files changed, 7983 insertions(+), 1991 deletions(-) delete mode 100644 PDCurses/include/curses.h delete mode 100644 PDCurses/lib/pdcurses.lib delete mode 100644 include/game_client.h delete mode 100644 include/game_server.h create mode 100644 olcPixelGameEngine/include/olcPGEX_TransformedView.h create mode 100644 olcPixelGameEngine/include/olcPixelGameEngine.h delete mode 100644 src/SnakeGame.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d0a44dd..8ba2276 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,13 +29,17 @@ set (HEADERS "include/net_client.h" "include/net_includes.h" "include/net_common.h" - "include/game_client.h" - "include/game_server.h" + # "include/game_client.h" + # "include/game_server.h" "include/game_structs.h" - "argparse/include/argparse.hpp") + # "include/stl_container_check.h" + "argparse/include/argparse.hpp" + "olcPixelGameEngine/include/olcPixelGameEngine.h" + "olcPixelGameEngine/include/olcPGEX_TransformedView.h") include_directories("${PROJECT_SOURCE_DIR}/include") include_directories("${PROJECT_SOURCE_DIR}/argparse/include") +include_directories("${PROJECT_SOURCE_DIR}/olcPixelGameEngine/include") set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -44,18 +48,21 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) # Add the executable add_executable(ConsolePacmanProject ${SOURCES} ${HEADERS}) -# Check and include NCurses or PDCurses +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DASIO_STANDALONE") + if(LINUX OR MACOS) - find_package(Curses REQUIRED) - include_directories(${CURSES_INCLUDE_DIR}) - target_link_libraries(ConsolePacmanProject ${CURSES_LIBRARIES}) - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DASIO_STANDALONE -lncurses -DNCURSES_NOMACROS -lpthread") + # find_package(Curses REQUIRED) + # include_directories(${CURSES_INCLUDE_DIR}) + # target_link_libraries(ConsolePacmanProject ${CURSES_LIBRARIES}) + # SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -lncurses -DNCURSES_NOMACROS") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -lpthread -lX11 -lGL -lpng -lstdc++fs") + target_link_libraries(ConsolePacmanProject pthread X11 GL png stdc++fs) elseif(WINDOWS) - set(CURSES_NEED_WIDE TRUE) - set(PDCurses_INCLUDE_DIRS "${PROJECT_SOURCE_DIR}/PDCurses/include") - set(PDCurses_LIBRARIES "${PROJECT_SOURCE_DIR}/PDCurses/lib/pdcurses.lib") - include_directories(${PDCurses_INCLUDE_DIRS}) - target_link_libraries(ConsolePacmanProject ${PDCurses_LIBRARIES}) + # set(CURSES_NEED_WIDE TRUE) + # set(PDCurses_INCLUDE_DIRS "${PROJECT_SOURCE_DIR}/PDCurses/include") + # set(PDCurses_LIBRARIES "${PROJECT_SOURCE_DIR}/PDCurses/lib/pdcurses.lib") + # include_directories(${PDCurses_INCLUDE_DIRS}) + # target_link_libraries(ConsolePacmanProject ${PDCurses_LIBRARIES}) endif() # Check and include ASIO diff --git a/PDCurses/include/curses.h b/PDCurses/include/curses.h deleted file mode 100644 index b5b91fd..0000000 --- a/PDCurses/include/curses.h +++ /dev/null @@ -1,1406 +0,0 @@ -/*----------------------------------------------------------------------* - * PDCurses * - *----------------------------------------------------------------------*/ - -#ifndef __PDCURSES__ -#define __PDCURSES__ 1 - -/*man-start************************************************************** - -Define before inclusion (only those needed): - - XCURSES True if compiling for X11. - PDC_RGB True if you want to use RGB color definitions - (Red = 1, Green = 2, Blue = 4) instead of BGR. - PDC_WIDE True if building wide-character support. - PDC_DLL_BUILD True if building a Windows DLL. - PDC_NCMOUSE Use the ncurses mouse API instead - of PDCurses' traditional mouse API. - -Defined by this header: - - PDCURSES Enables access to PDCurses-only routines. - PDC_BUILD Defines API build version. - PDC_VER_MAJOR Major version number - PDC_VER_MINOR Minor version number - PDC_VERDOT Version string - -**man-end****************************************************************/ - -#define PDCURSES 1 -#define PDC_BUILD 3900 -#define PDC_VER_MAJOR 3 -#define PDC_VER_MINOR 9 -#define PDC_VERDOT "3.9" - -#define CHTYPE_LONG 1 /* chtype >= 32 bits */ - -#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L -# define PDC_99 1 -#endif - -#if defined(__cplusplus) && __cplusplus >= 199711L -# define PDC_PP98 1 -#endif - -/*----------------------------------------------------------------------*/ - -#include -#include -#include - -#ifdef PDC_WIDE -# include -#endif - -#if defined(PDC_99) && !defined(__bool_true_false_are_defined) -# include -#endif - -#ifdef __cplusplus -extern "C" -{ -# ifndef PDC_PP98 -# define bool _bool -# endif -#endif - -/*---------------------------------------------------------------------- - * - * Constants and Types - * - */ - -#undef FALSE -#define FALSE 0 - -#undef TRUE -#define TRUE 1 - -#undef ERR -#define ERR (-1) - -#undef OK -#define OK 0 - -#if !defined(PDC_PP98) && !defined(__bool_true_false_are_defined) -typedef unsigned char bool; -#endif - -#if _LP64 -typedef unsigned int chtype; -#else -typedef unsigned long chtype; /* 16-bit attr + 16-bit char */ -#endif - -#ifdef PDC_WIDE -typedef chtype cchar_t; -#endif - -typedef chtype attr_t; - -/*---------------------------------------------------------------------- - * - * Version Info - * - */ - -/* Use this structure with PDC_get_version() for run-time info about the - way the library was built, in case it doesn't match the header. */ - -typedef struct -{ - short flags; /* flags OR'd together (see below) */ - short build; /* PDC_BUILD at compile time */ - unsigned char major; /* PDC_VER_MAJOR */ - unsigned char minor; /* PDC_VER_MINOR */ - unsigned char csize; /* sizeof chtype */ - unsigned char bsize; /* sizeof bool */ -} PDC_VERSION; - -enum -{ - PDC_VFLAG_DEBUG = 1, /* set if built with -DPDCDEBUG */ - PDC_VFLAG_WIDE = 2, /* -DPDC_WIDE */ - PDC_VFLAG_UTF8 = 4, /* -DPDC_FORCE_UTF8 */ - PDC_VFLAG_DLL = 8, /* -DPDC_DLL_BUILD */ - PDC_VFLAG_RGB = 16 /* -DPDC_RGB */ -}; - -/*---------------------------------------------------------------------- - * - * Mouse Interface - * - */ - -#if _LP64 -typedef unsigned int mmask_t; -#else -typedef unsigned long mmask_t; -#endif - -typedef struct -{ - int x; /* absolute column, 0 based, measured in characters */ - int y; /* absolute row, 0 based, measured in characters */ - short button[3]; /* state of each button */ - int changes; /* flags indicating what has changed with the mouse */ -} MOUSE_STATUS; - -#define BUTTON_RELEASED 0x0000 -#define BUTTON_PRESSED 0x0001 -#define BUTTON_CLICKED 0x0002 -#define BUTTON_DOUBLE_CLICKED 0x0003 -#define BUTTON_TRIPLE_CLICKED 0x0004 -#define BUTTON_MOVED 0x0005 /* PDCurses */ -#define WHEEL_SCROLLED 0x0006 /* PDCurses */ -#define BUTTON_ACTION_MASK 0x0007 /* PDCurses */ - -#define PDC_BUTTON_SHIFT 0x0008 /* PDCurses */ -#define PDC_BUTTON_CONTROL 0x0010 /* PDCurses */ -#define PDC_BUTTON_ALT 0x0020 /* PDCurses */ -#define BUTTON_MODIFIER_MASK 0x0038 /* PDCurses */ - -#define MOUSE_X_POS (Mouse_status.x) -#define MOUSE_Y_POS (Mouse_status.y) - -/* - * Bits associated with the .changes field: - * 3 2 1 0 - * 210987654321098765432109876543210 - * 1 <- button 1 has changed - * 10 <- button 2 has changed - * 100 <- button 3 has changed - * 1000 <- mouse has moved - * 10000 <- mouse position report - * 100000 <- mouse wheel up - * 1000000 <- mouse wheel down - * 10000000 <- mouse wheel left - * 100000000 <- mouse wheel right - */ - -#define PDC_MOUSE_MOVED 0x0008 -#define PDC_MOUSE_POSITION 0x0010 -#define PDC_MOUSE_WHEEL_UP 0x0020 -#define PDC_MOUSE_WHEEL_DOWN 0x0040 -#define PDC_MOUSE_WHEEL_LEFT 0x0080 -#define PDC_MOUSE_WHEEL_RIGHT 0x0100 - -#define A_BUTTON_CHANGED (Mouse_status.changes & 7) -#define MOUSE_MOVED (Mouse_status.changes & PDC_MOUSE_MOVED) -#define MOUSE_POS_REPORT (Mouse_status.changes & PDC_MOUSE_POSITION) -#define BUTTON_CHANGED(x) (Mouse_status.changes & (1 << ((x) - 1))) -#define BUTTON_STATUS(x) (Mouse_status.button[(x) - 1]) -#define MOUSE_WHEEL_UP (Mouse_status.changes & PDC_MOUSE_WHEEL_UP) -#define MOUSE_WHEEL_DOWN (Mouse_status.changes & PDC_MOUSE_WHEEL_DOWN) -#define MOUSE_WHEEL_LEFT (Mouse_status.changes & PDC_MOUSE_WHEEL_LEFT) -#define MOUSE_WHEEL_RIGHT (Mouse_status.changes & PDC_MOUSE_WHEEL_RIGHT) - -/* mouse bit-masks */ - -#define BUTTON1_RELEASED 0x00000001L -#define BUTTON1_PRESSED 0x00000002L -#define BUTTON1_CLICKED 0x00000004L -#define BUTTON1_DOUBLE_CLICKED 0x00000008L -#define BUTTON1_TRIPLE_CLICKED 0x00000010L -#define BUTTON1_MOVED 0x00000010L /* PDCurses */ - -#define BUTTON2_RELEASED 0x00000020L -#define BUTTON2_PRESSED 0x00000040L -#define BUTTON2_CLICKED 0x00000080L -#define BUTTON2_DOUBLE_CLICKED 0x00000100L -#define BUTTON2_TRIPLE_CLICKED 0x00000200L -#define BUTTON2_MOVED 0x00000200L /* PDCurses */ - -#define BUTTON3_RELEASED 0x00000400L -#define BUTTON3_PRESSED 0x00000800L -#define BUTTON3_CLICKED 0x00001000L -#define BUTTON3_DOUBLE_CLICKED 0x00002000L -#define BUTTON3_TRIPLE_CLICKED 0x00004000L -#define BUTTON3_MOVED 0x00004000L /* PDCurses */ - -/* For the ncurses-compatible functions only, BUTTON4_PRESSED and - BUTTON5_PRESSED are returned for mouse scroll wheel up and down; - otherwise PDCurses doesn't support buttons 4 and 5 */ - -#define BUTTON4_RELEASED 0x00008000L -#define BUTTON4_PRESSED 0x00010000L -#define BUTTON4_CLICKED 0x00020000L -#define BUTTON4_DOUBLE_CLICKED 0x00040000L -#define BUTTON4_TRIPLE_CLICKED 0x00080000L - -#define BUTTON5_RELEASED 0x00100000L -#define BUTTON5_PRESSED 0x00200000L -#define BUTTON5_CLICKED 0x00400000L -#define BUTTON5_DOUBLE_CLICKED 0x00800000L -#define BUTTON5_TRIPLE_CLICKED 0x01000000L - -#define MOUSE_WHEEL_SCROLL 0x02000000L /* PDCurses */ -#define BUTTON_MODIFIER_SHIFT 0x04000000L /* PDCurses */ -#define BUTTON_MODIFIER_CONTROL 0x08000000L /* PDCurses */ -#define BUTTON_MODIFIER_ALT 0x10000000L /* PDCurses */ - -#define ALL_MOUSE_EVENTS 0x1fffffffL -#define REPORT_MOUSE_POSITION 0x20000000L - -/* ncurses mouse interface */ - -typedef struct -{ - short id; /* unused, always 0 */ - int x, y, z; /* x, y same as MOUSE_STATUS; z unused */ - mmask_t bstate; /* equivalent to changes + button[], but - in the same format as used for mousemask() */ -} MEVENT; - -#if defined(PDC_NCMOUSE) && !defined(NCURSES_MOUSE_VERSION) -# define NCURSES_MOUSE_VERSION 2 -#endif - -#ifdef NCURSES_MOUSE_VERSION -# define BUTTON_SHIFT BUTTON_MODIFIER_SHIFT -# define BUTTON_CONTROL BUTTON_MODIFIER_CONTROL -# define BUTTON_CTRL BUTTON_MODIFIER_CONTROL -# define BUTTON_ALT BUTTON_MODIFIER_ALT -#else -# define BUTTON_SHIFT PDC_BUTTON_SHIFT -# define BUTTON_CONTROL PDC_BUTTON_CONTROL -# define BUTTON_ALT PDC_BUTTON_ALT -#endif - -/*---------------------------------------------------------------------- - * - * Window and Screen Structures - * - */ - -typedef struct _win /* definition of a window */ -{ - int _cury; /* current pseudo-cursor */ - int _curx; - int _maxy; /* max window coordinates */ - int _maxx; - int _begy; /* origin on screen */ - int _begx; - int _flags; /* window properties */ - chtype _attrs; /* standard attributes and colors */ - chtype _bkgd; /* background, normally blank */ - bool _clear; /* causes clear at next refresh */ - bool _leaveit; /* leaves cursor where it is */ - bool _scroll; /* allows window scrolling */ - bool _nodelay; /* input character wait flag */ - bool _immed; /* immediate update flag */ - bool _sync; /* synchronise window ancestors */ - bool _use_keypad; /* flags keypad key mode active */ - chtype **_y; /* pointer to line pointer array */ - int *_firstch; /* first changed character in line */ - int *_lastch; /* last changed character in line */ - int _tmarg; /* top of scrolling region */ - int _bmarg; /* bottom of scrolling region */ - int _delayms; /* milliseconds of delay for getch() */ - int _parx, _pary; /* coords relative to parent (0,0) */ - struct _win *_parent; /* subwin's pointer to parent win */ -} WINDOW; - -/* Avoid using the SCREEN struct directly -- use the corresponding - functions if possible. This struct may eventually be made private. */ - -typedef struct -{ - bool alive; /* if initscr() called, and not endwin() */ - bool autocr; /* if cr -> lf */ - bool cbreak; /* if terminal unbuffered */ - bool echo; /* if terminal echo */ - bool raw_inp; /* raw input mode (v. cooked input) */ - bool raw_out; /* raw output mode (7 v. 8 bits) */ - bool audible; /* FALSE if the bell is visual */ - bool mono; /* TRUE if current screen is mono */ - bool resized; /* TRUE if TERM has been resized */ - bool orig_attr; /* TRUE if we have the original colors */ - short orig_fore; /* original screen foreground color */ - short orig_back; /* original screen foreground color */ - int cursrow; /* position of physical cursor */ - int curscol; /* position of physical cursor */ - int visibility; /* visibility of cursor */ - int orig_cursor; /* original cursor size */ - int lines; /* new value for LINES */ - int cols; /* new value for COLS */ - mmask_t _trap_mbe; /* trap these mouse button events */ - int mouse_wait; /* time to wait (in ms) for a - button release after a press, in - order to count it as a click */ - int slklines; /* lines in use by slk_init() */ - WINDOW *slk_winptr; /* window for slk */ - int linesrippedoff; /* lines ripped off via ripoffline() */ - int linesrippedoffontop; /* lines ripped off on - top via ripoffline() */ - int delaytenths; /* 1/10ths second to wait block - getch() for */ - bool _preserve; /* TRUE if screen background - to be preserved */ - int _restore; /* specifies if screen background - to be restored, and how */ - unsigned long key_modifiers; /* key modifiers (SHIFT, CONTROL, etc.) - on last key press */ - bool return_key_modifiers; /* TRUE if modifier keys are - returned as "real" keys */ - bool key_code; /* TRUE if last key is a special key; - used internally by get_wch() */ - MOUSE_STATUS mouse_status; /* last returned mouse status */ -#ifdef XCURSES - bool sb_on; - int sb_viewport_y; - int sb_viewport_x; - int sb_total_y; - int sb_total_x; - int sb_cur_y; - int sb_cur_x; -#endif - short line_color; /* color of line attributes - default -1 */ - attr_t termattrs; /* attribute capabilities */ - WINDOW *lastscr; /* the last screen image */ - FILE *dbfp; /* debug trace file pointer */ - bool color_started; /* TRUE after start_color() */ - bool dirty; /* redraw on napms() after init_color() */ - int sel_start; /* start of selection (y * COLS + x) */ - int sel_end; /* end of selection */ -} SCREEN; - -/*---------------------------------------------------------------------- - * - * External Variables - * - */ - -#ifdef PDC_DLL_BUILD -# ifdef CURSES_LIBRARY -# define PDCEX __declspec(dllexport) extern -# else -# define PDCEX __declspec(dllimport) -# endif -#else -# define PDCEX extern -#endif - -PDCEX int LINES; /* terminal height */ -PDCEX int COLS; /* terminal width */ -PDCEX WINDOW *stdscr; /* the default screen window */ -PDCEX WINDOW *curscr; /* the current screen image */ -PDCEX SCREEN *SP; /* curses variables */ -PDCEX MOUSE_STATUS Mouse_status; -PDCEX int COLORS; -PDCEX int COLOR_PAIRS; -PDCEX int TABSIZE; -PDCEX chtype acs_map[]; /* alternate character set map */ -PDCEX char ttytype[]; /* terminal name/description */ - -/*man-start************************************************************** - -Text Attributes -=============== - -PDCurses uses a 32-bit integer for its chtype: - - +--------------------------------------------------------------------+ - |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|..| 2| 1| 0| - +--------------------------------------------------------------------+ - color pair | modifiers | character eg 'a' - -There are 256 color pairs (8 bits), 8 bits for modifiers, and 16 bits -for character data. The modifiers are bold, underline, right-line, -left-line, italic, reverse and blink, plus the alternate character set -indicator. - -**man-end****************************************************************/ - -/*** Video attribute macros ***/ - -#define A_NORMAL (chtype)0 - -#define A_ALTCHARSET (chtype)0x00010000 -#define A_RIGHT (chtype)0x00020000 -#define A_LEFT (chtype)0x00040000 -#define A_ITALIC (chtype)0x00080000 -#define A_UNDERLINE (chtype)0x00100000 -#define A_REVERSE (chtype)0x00200000 -#define A_BLINK (chtype)0x00400000 -#define A_BOLD (chtype)0x00800000 - -#define A_ATTRIBUTES (chtype)0xffff0000 -#define A_CHARTEXT (chtype)0x0000ffff -#define A_COLOR (chtype)0xff000000 - -#define PDC_COLOR_SHIFT 24 - -#define A_LEFTLINE A_LEFT -#define A_RIGHTLINE A_RIGHT -#define A_STANDOUT (A_REVERSE | A_BOLD) /* X/Open */ - -#define A_DIM A_NORMAL -#define A_INVIS A_NORMAL -#define A_PROTECT A_NORMAL - -#define A_HORIZONTAL A_NORMAL -#define A_LOW A_NORMAL -#define A_TOP A_NORMAL -#define A_VERTICAL A_NORMAL - -#define CHR_MSK A_CHARTEXT /* Obsolete */ -#define ATR_MSK A_ATTRIBUTES /* Obsolete */ -#define ATR_NRM A_NORMAL /* Obsolete */ - -/* For use with attr_t -- X/Open says, "these shall be distinct", so - this is a non-conforming implementation. */ - -#define WA_NORMAL A_NORMAL - -#define WA_ALTCHARSET A_ALTCHARSET -#define WA_BLINK A_BLINK -#define WA_BOLD A_BOLD -#define WA_DIM A_DIM -#define WA_INVIS A_INVIS -#define WA_ITALIC A_ITALIC -#define WA_LEFT A_LEFT -#define WA_PROTECT A_PROTECT -#define WA_REVERSE A_REVERSE -#define WA_RIGHT A_RIGHT -#define WA_STANDOUT A_STANDOUT -#define WA_UNDERLINE A_UNDERLINE - -#define WA_HORIZONTAL A_HORIZONTAL -#define WA_LOW A_LOW -#define WA_TOP A_TOP -#define WA_VERTICAL A_VERTICAL - -#define WA_ATTRIBUTES A_ATTRIBUTES - -/*** Alternate character set macros ***/ - -#define PDC_ACS(w) ((chtype)w | A_ALTCHARSET) - -/* VT100-compatible symbols -- box chars */ - -#define ACS_ULCORNER PDC_ACS('l') -#define ACS_LLCORNER PDC_ACS('m') -#define ACS_URCORNER PDC_ACS('k') -#define ACS_LRCORNER PDC_ACS('j') -#define ACS_RTEE PDC_ACS('u') -#define ACS_LTEE PDC_ACS('t') -#define ACS_BTEE PDC_ACS('v') -#define ACS_TTEE PDC_ACS('w') -#define ACS_HLINE PDC_ACS('q') -#define ACS_VLINE PDC_ACS('x') -#define ACS_PLUS PDC_ACS('n') - -/* VT100-compatible symbols -- other */ - -#define ACS_S1 PDC_ACS('o') -#define ACS_S9 PDC_ACS('s') -#define ACS_DIAMOND PDC_ACS('`') -#define ACS_CKBOARD PDC_ACS('a') -#define ACS_DEGREE PDC_ACS('f') -#define ACS_PLMINUS PDC_ACS('g') -#define ACS_BULLET PDC_ACS('~') - -/* Teletype 5410v1 symbols -- these are defined in SysV curses, but - are not well-supported by most terminals. Stick to VT100 characters - for optimum portability. */ - -#define ACS_LARROW PDC_ACS(',') -#define ACS_RARROW PDC_ACS('+') -#define ACS_DARROW PDC_ACS('.') -#define ACS_UARROW PDC_ACS('-') -#define ACS_BOARD PDC_ACS('h') -#define ACS_LANTERN PDC_ACS('i') -#define ACS_BLOCK PDC_ACS('0') - -/* That goes double for these -- undocumented SysV symbols. Don't use - them. */ - -#define ACS_S3 PDC_ACS('p') -#define ACS_S7 PDC_ACS('r') -#define ACS_LEQUAL PDC_ACS('y') -#define ACS_GEQUAL PDC_ACS('z') -#define ACS_PI PDC_ACS('{') -#define ACS_NEQUAL PDC_ACS('|') -#define ACS_STERLING PDC_ACS('}') - -/* Box char aliases */ - -#define ACS_BSSB ACS_ULCORNER -#define ACS_SSBB ACS_LLCORNER -#define ACS_BBSS ACS_URCORNER -#define ACS_SBBS ACS_LRCORNER -#define ACS_SBSS ACS_RTEE -#define ACS_SSSB ACS_LTEE -#define ACS_SSBS ACS_BTEE -#define ACS_BSSS ACS_TTEE -#define ACS_BSBS ACS_HLINE -#define ACS_SBSB ACS_VLINE -#define ACS_SSSS ACS_PLUS - -/* cchar_t aliases */ - -#ifdef PDC_WIDE -# define WACS_ULCORNER (&(acs_map['l'])) -# define WACS_LLCORNER (&(acs_map['m'])) -# define WACS_URCORNER (&(acs_map['k'])) -# define WACS_LRCORNER (&(acs_map['j'])) -# define WACS_RTEE (&(acs_map['u'])) -# define WACS_LTEE (&(acs_map['t'])) -# define WACS_BTEE (&(acs_map['v'])) -# define WACS_TTEE (&(acs_map['w'])) -# define WACS_HLINE (&(acs_map['q'])) -# define WACS_VLINE (&(acs_map['x'])) -# define WACS_PLUS (&(acs_map['n'])) - -# define WACS_S1 (&(acs_map['o'])) -# define WACS_S9 (&(acs_map['s'])) -# define WACS_DIAMOND (&(acs_map['`'])) -# define WACS_CKBOARD (&(acs_map['a'])) -# define WACS_DEGREE (&(acs_map['f'])) -# define WACS_PLMINUS (&(acs_map['g'])) -# define WACS_BULLET (&(acs_map['~'])) - -# define WACS_LARROW (&(acs_map[','])) -# define WACS_RARROW (&(acs_map['+'])) -# define WACS_DARROW (&(acs_map['.'])) -# define WACS_UARROW (&(acs_map['-'])) -# define WACS_BOARD (&(acs_map['h'])) -# define WACS_LANTERN (&(acs_map['i'])) -# define WACS_BLOCK (&(acs_map['0'])) - -# define WACS_S3 (&(acs_map['p'])) -# define WACS_S7 (&(acs_map['r'])) -# define WACS_LEQUAL (&(acs_map['y'])) -# define WACS_GEQUAL (&(acs_map['z'])) -# define WACS_PI (&(acs_map['{'])) -# define WACS_NEQUAL (&(acs_map['|'])) -# define WACS_STERLING (&(acs_map['}'])) - -# define WACS_BSSB WACS_ULCORNER -# define WACS_SSBB WACS_LLCORNER -# define WACS_BBSS WACS_URCORNER -# define WACS_SBBS WACS_LRCORNER -# define WACS_SBSS WACS_RTEE -# define WACS_SSSB WACS_LTEE -# define WACS_SSBS WACS_BTEE -# define WACS_BSSS WACS_TTEE -# define WACS_BSBS WACS_HLINE -# define WACS_SBSB WACS_VLINE -# define WACS_SSSS WACS_PLUS -#endif - -/*** Color macros ***/ - -#define COLOR_BLACK 0 - -#ifdef PDC_RGB /* RGB */ -# define COLOR_RED 1 -# define COLOR_GREEN 2 -# define COLOR_BLUE 4 -#else /* BGR */ -# define COLOR_BLUE 1 -# define COLOR_GREEN 2 -# define COLOR_RED 4 -#endif - -#define COLOR_CYAN (COLOR_BLUE | COLOR_GREEN) -#define COLOR_MAGENTA (COLOR_RED | COLOR_BLUE) -#define COLOR_YELLOW (COLOR_RED | COLOR_GREEN) - -#define COLOR_WHITE 7 - -/*---------------------------------------------------------------------- - * - * Function and Keypad Key Definitions - * Many are just for compatibility - * - */ - -#define KEY_CODE_YES 0x100 /* If get_wch() gives a key code */ - -#define KEY_BREAK 0x101 /* Not on PC KBD */ -#define KEY_DOWN 0x102 /* Down arrow key */ -#define KEY_UP 0x103 /* Up arrow key */ -#define KEY_LEFT 0x104 /* Left arrow key */ -#define KEY_RIGHT 0x105 /* Right arrow key */ -#define KEY_HOME 0x106 /* home key */ -#define KEY_BACKSPACE 0x107 /* not on pc */ -#define KEY_F0 0x108 /* function keys; 64 reserved */ - -#define KEY_DL 0x148 /* delete line */ -#define KEY_IL 0x149 /* insert line */ -#define KEY_DC 0x14a /* delete character */ -#define KEY_IC 0x14b /* insert char or enter ins mode */ -#define KEY_EIC 0x14c /* exit insert char mode */ -#define KEY_CLEAR 0x14d /* clear screen */ -#define KEY_EOS 0x14e /* clear to end of screen */ -#define KEY_EOL 0x14f /* clear to end of line */ -#define KEY_SF 0x150 /* scroll 1 line forward */ -#define KEY_SR 0x151 /* scroll 1 line back (reverse) */ -#define KEY_NPAGE 0x152 /* next page */ -#define KEY_PPAGE 0x153 /* previous page */ -#define KEY_STAB 0x154 /* set tab */ -#define KEY_CTAB 0x155 /* clear tab */ -#define KEY_CATAB 0x156 /* clear all tabs */ -#define KEY_ENTER 0x157 /* enter or send (unreliable) */ -#define KEY_SRESET 0x158 /* soft/reset (partial/unreliable) */ -#define KEY_RESET 0x159 /* reset/hard reset (unreliable) */ -#define KEY_PRINT 0x15a /* print/copy */ -#define KEY_LL 0x15b /* home down/bottom (lower left) */ -#define KEY_ABORT 0x15c /* abort/terminate key (any) */ -#define KEY_SHELP 0x15d /* short help */ -#define KEY_LHELP 0x15e /* long help */ -#define KEY_BTAB 0x15f /* Back tab key */ -#define KEY_BEG 0x160 /* beg(inning) key */ -#define KEY_CANCEL 0x161 /* cancel key */ -#define KEY_CLOSE 0x162 /* close key */ -#define KEY_COMMAND 0x163 /* cmd (command) key */ -#define KEY_COPY 0x164 /* copy key */ -#define KEY_CREATE 0x165 /* create key */ -#define KEY_END 0x166 /* end key */ -#define KEY_EXIT 0x167 /* exit key */ -#define KEY_FIND 0x168 /* find key */ -#define KEY_HELP 0x169 /* help key */ -#define KEY_MARK 0x16a /* mark key */ -#define KEY_MESSAGE 0x16b /* message key */ -#define KEY_MOVE 0x16c /* move key */ -#define KEY_NEXT 0x16d /* next object key */ -#define KEY_OPEN 0x16e /* open key */ -#define KEY_OPTIONS 0x16f /* options key */ -#define KEY_PREVIOUS 0x170 /* previous object key */ -#define KEY_REDO 0x171 /* redo key */ -#define KEY_REFERENCE 0x172 /* ref(erence) key */ -#define KEY_REFRESH 0x173 /* refresh key */ -#define KEY_REPLACE 0x174 /* replace key */ -#define KEY_RESTART 0x175 /* restart key */ -#define KEY_RESUME 0x176 /* resume key */ -#define KEY_SAVE 0x177 /* save key */ -#define KEY_SBEG 0x178 /* shifted beginning key */ -#define KEY_SCANCEL 0x179 /* shifted cancel key */ -#define KEY_SCOMMAND 0x17a /* shifted command key */ -#define KEY_SCOPY 0x17b /* shifted copy key */ -#define KEY_SCREATE 0x17c /* shifted create key */ -#define KEY_SDC 0x17d /* shifted delete char key */ -#define KEY_SDL 0x17e /* shifted delete line key */ -#define KEY_SELECT 0x17f /* select key */ -#define KEY_SEND 0x180 /* shifted end key */ -#define KEY_SEOL 0x181 /* shifted clear line key */ -#define KEY_SEXIT 0x182 /* shifted exit key */ -#define KEY_SFIND 0x183 /* shifted find key */ -#define KEY_SHOME 0x184 /* shifted home key */ -#define KEY_SIC 0x185 /* shifted input key */ - -#define KEY_SLEFT 0x187 /* shifted left arrow key */ -#define KEY_SMESSAGE 0x188 /* shifted message key */ -#define KEY_SMOVE 0x189 /* shifted move key */ -#define KEY_SNEXT 0x18a /* shifted next key */ -#define KEY_SOPTIONS 0x18b /* shifted options key */ -#define KEY_SPREVIOUS 0x18c /* shifted prev key */ -#define KEY_SPRINT 0x18d /* shifted print key */ -#define KEY_SREDO 0x18e /* shifted redo key */ -#define KEY_SREPLACE 0x18f /* shifted replace key */ -#define KEY_SRIGHT 0x190 /* shifted right arrow */ -#define KEY_SRSUME 0x191 /* shifted resume key */ -#define KEY_SSAVE 0x192 /* shifted save key */ -#define KEY_SSUSPEND 0x193 /* shifted suspend key */ -#define KEY_SUNDO 0x194 /* shifted undo key */ -#define KEY_SUSPEND 0x195 /* suspend key */ -#define KEY_UNDO 0x196 /* undo key */ - -/* PDCurses-specific key definitions -- PC only */ - -#define ALT_0 0x197 -#define ALT_1 0x198 -#define ALT_2 0x199 -#define ALT_3 0x19a -#define ALT_4 0x19b -#define ALT_5 0x19c -#define ALT_6 0x19d -#define ALT_7 0x19e -#define ALT_8 0x19f -#define ALT_9 0x1a0 -#define ALT_A 0x1a1 -#define ALT_B 0x1a2 -#define ALT_C 0x1a3 -#define ALT_D 0x1a4 -#define ALT_E 0x1a5 -#define ALT_F 0x1a6 -#define ALT_G 0x1a7 -#define ALT_H 0x1a8 -#define ALT_I 0x1a9 -#define ALT_J 0x1aa -#define ALT_K 0x1ab -#define ALT_L 0x1ac -#define ALT_M 0x1ad -#define ALT_N 0x1ae -#define ALT_O 0x1af -#define ALT_P 0x1b0 -#define ALT_Q 0x1b1 -#define ALT_R 0x1b2 -#define ALT_S 0x1b3 -#define ALT_T 0x1b4 -#define ALT_U 0x1b5 -#define ALT_V 0x1b6 -#define ALT_W 0x1b7 -#define ALT_X 0x1b8 -#define ALT_Y 0x1b9 -#define ALT_Z 0x1ba - -#define CTL_LEFT 0x1bb /* Control-Left-Arrow */ -#define CTL_RIGHT 0x1bc -#define CTL_PGUP 0x1bd -#define CTL_PGDN 0x1be -#define CTL_HOME 0x1bf -#define CTL_END 0x1c0 - -#define KEY_A1 0x1c1 /* upper left on Virtual keypad */ -#define KEY_A2 0x1c2 /* upper middle on Virt. keypad */ -#define KEY_A3 0x1c3 /* upper right on Vir. keypad */ -#define KEY_B1 0x1c4 /* middle left on Virt. keypad */ -#define KEY_B2 0x1c5 /* center on Virt. keypad */ -#define KEY_B3 0x1c6 /* middle right on Vir. keypad */ -#define KEY_C1 0x1c7 /* lower left on Virt. keypad */ -#define KEY_C2 0x1c8 /* lower middle on Virt. keypad */ -#define KEY_C3 0x1c9 /* lower right on Vir. keypad */ - -#define PADSLASH 0x1ca /* slash on keypad */ -#define PADENTER 0x1cb /* enter on keypad */ -#define CTL_PADENTER 0x1cc /* ctl-enter on keypad */ -#define ALT_PADENTER 0x1cd /* alt-enter on keypad */ -#define PADSTOP 0x1ce /* stop on keypad */ -#define PADSTAR 0x1cf /* star on keypad */ -#define PADMINUS 0x1d0 /* minus on keypad */ -#define PADPLUS 0x1d1 /* plus on keypad */ -#define CTL_PADSTOP 0x1d2 /* ctl-stop on keypad */ -#define CTL_PADCENTER 0x1d3 /* ctl-enter on keypad */ -#define CTL_PADPLUS 0x1d4 /* ctl-plus on keypad */ -#define CTL_PADMINUS 0x1d5 /* ctl-minus on keypad */ -#define CTL_PADSLASH 0x1d6 /* ctl-slash on keypad */ -#define CTL_PADSTAR 0x1d7 /* ctl-star on keypad */ -#define ALT_PADPLUS 0x1d8 /* alt-plus on keypad */ -#define ALT_PADMINUS 0x1d9 /* alt-minus on keypad */ -#define ALT_PADSLASH 0x1da /* alt-slash on keypad */ -#define ALT_PADSTAR 0x1db /* alt-star on keypad */ -#define ALT_PADSTOP 0x1dc /* alt-stop on keypad */ -#define CTL_INS 0x1dd /* ctl-insert */ -#define ALT_DEL 0x1de /* alt-delete */ -#define ALT_INS 0x1df /* alt-insert */ -#define CTL_UP 0x1e0 /* ctl-up arrow */ -#define CTL_DOWN 0x1e1 /* ctl-down arrow */ -#define CTL_TAB 0x1e2 /* ctl-tab */ -#define ALT_TAB 0x1e3 -#define ALT_MINUS 0x1e4 -#define ALT_EQUAL 0x1e5 -#define ALT_HOME 0x1e6 -#define ALT_PGUP 0x1e7 -#define ALT_PGDN 0x1e8 -#define ALT_END 0x1e9 -#define ALT_UP 0x1ea /* alt-up arrow */ -#define ALT_DOWN 0x1eb /* alt-down arrow */ -#define ALT_RIGHT 0x1ec /* alt-right arrow */ -#define ALT_LEFT 0x1ed /* alt-left arrow */ -#define ALT_ENTER 0x1ee /* alt-enter */ -#define ALT_ESC 0x1ef /* alt-escape */ -#define ALT_BQUOTE 0x1f0 /* alt-back quote */ -#define ALT_LBRACKET 0x1f1 /* alt-left bracket */ -#define ALT_RBRACKET 0x1f2 /* alt-right bracket */ -#define ALT_SEMICOLON 0x1f3 /* alt-semi-colon */ -#define ALT_FQUOTE 0x1f4 /* alt-forward quote */ -#define ALT_COMMA 0x1f5 /* alt-comma */ -#define ALT_STOP 0x1f6 /* alt-stop */ -#define ALT_FSLASH 0x1f7 /* alt-forward slash */ -#define ALT_BKSP 0x1f8 /* alt-backspace */ -#define CTL_BKSP 0x1f9 /* ctl-backspace */ -#define PAD0 0x1fa /* keypad 0 */ - -#define CTL_PAD0 0x1fb /* ctl-keypad 0 */ -#define CTL_PAD1 0x1fc -#define CTL_PAD2 0x1fd -#define CTL_PAD3 0x1fe -#define CTL_PAD4 0x1ff -#define CTL_PAD5 0x200 -#define CTL_PAD6 0x201 -#define CTL_PAD7 0x202 -#define CTL_PAD8 0x203 -#define CTL_PAD9 0x204 - -#define ALT_PAD0 0x205 /* alt-keypad 0 */ -#define ALT_PAD1 0x206 -#define ALT_PAD2 0x207 -#define ALT_PAD3 0x208 -#define ALT_PAD4 0x209 -#define ALT_PAD5 0x20a -#define ALT_PAD6 0x20b -#define ALT_PAD7 0x20c -#define ALT_PAD8 0x20d -#define ALT_PAD9 0x20e - -#define CTL_DEL 0x20f /* clt-delete */ -#define ALT_BSLASH 0x210 /* alt-back slash */ -#define CTL_ENTER 0x211 /* ctl-enter */ - -#define SHF_PADENTER 0x212 /* shift-enter on keypad */ -#define SHF_PADSLASH 0x213 /* shift-slash on keypad */ -#define SHF_PADSTAR 0x214 /* shift-star on keypad */ -#define SHF_PADPLUS 0x215 /* shift-plus on keypad */ -#define SHF_PADMINUS 0x216 /* shift-minus on keypad */ -#define SHF_UP 0x217 /* shift-up on keypad */ -#define SHF_DOWN 0x218 /* shift-down on keypad */ -#define SHF_IC 0x219 /* shift-insert on keypad */ -#define SHF_DC 0x21a /* shift-delete on keypad */ - -#define KEY_MOUSE 0x21b /* "mouse" key */ -#define KEY_SHIFT_L 0x21c /* Left-shift */ -#define KEY_SHIFT_R 0x21d /* Right-shift */ -#define KEY_CONTROL_L 0x21e /* Left-control */ -#define KEY_CONTROL_R 0x21f /* Right-control */ -#define KEY_ALT_L 0x220 /* Left-alt */ -#define KEY_ALT_R 0x221 /* Right-alt */ -#define KEY_RESIZE 0x222 /* Window resize */ -#define KEY_SUP 0x223 /* Shifted up arrow */ -#define KEY_SDOWN 0x224 /* Shifted down arrow */ - -#define KEY_MIN KEY_BREAK /* Minimum curses key value */ -#define KEY_MAX KEY_SDOWN /* Maximum curses key */ - -#define KEY_F(n) (KEY_F0 + (n)) - -/*---------------------------------------------------------------------- - * - * Functions - * - */ - -/* Standard */ - -PDCEX int addch(const chtype); -PDCEX int addchnstr(const chtype *, int); -PDCEX int addchstr(const chtype *); -PDCEX int addnstr(const char *, int); -PDCEX int addstr(const char *); -PDCEX int attroff(chtype); -PDCEX int attron(chtype); -PDCEX int attrset(chtype); -PDCEX int attr_get(attr_t *, short *, void *); -PDCEX int attr_off(attr_t, void *); -PDCEX int attr_on(attr_t, void *); -PDCEX int attr_set(attr_t, short, void *); -PDCEX int baudrate(void); -PDCEX int beep(void); -PDCEX int bkgd(chtype); -PDCEX void bkgdset(chtype); -PDCEX int border(chtype, chtype, chtype, chtype, - chtype, chtype, chtype, chtype); -PDCEX int box(WINDOW *, chtype, chtype); -PDCEX bool can_change_color(void); -PDCEX int cbreak(void); -PDCEX int chgat(int, attr_t, short, const void *); -PDCEX int clearok(WINDOW *, bool); -PDCEX int clear(void); -PDCEX int clrtobot(void); -PDCEX int clrtoeol(void); -PDCEX int color_content(short, short *, short *, short *); -PDCEX int color_set(short, void *); -PDCEX int copywin(const WINDOW *, WINDOW *, int, int, int, - int, int, int, int); -PDCEX int curs_set(int); -PDCEX int def_prog_mode(void); -PDCEX int def_shell_mode(void); -PDCEX int delay_output(int); -PDCEX int delch(void); -PDCEX int deleteln(void); -PDCEX void delscreen(SCREEN *); -PDCEX int delwin(WINDOW *); -PDCEX WINDOW *derwin(WINDOW *, int, int, int, int); -PDCEX int doupdate(void); -PDCEX WINDOW *dupwin(WINDOW *); -PDCEX int echochar(const chtype); -PDCEX int echo(void); -PDCEX int endwin(void); -PDCEX char erasechar(void); -PDCEX int erase(void); -PDCEX void filter(void); -PDCEX int flash(void); -PDCEX int flushinp(void); -PDCEX chtype getbkgd(WINDOW *); -PDCEX int getnstr(char *, int); -PDCEX int getstr(char *); -PDCEX WINDOW *getwin(FILE *); -PDCEX int halfdelay(int); -PDCEX bool has_colors(void); -PDCEX bool has_ic(void); -PDCEX bool has_il(void); -PDCEX int hline(chtype, int); -PDCEX void idcok(WINDOW *, bool); -PDCEX int idlok(WINDOW *, bool); -PDCEX void immedok(WINDOW *, bool); -PDCEX int inchnstr(chtype *, int); -PDCEX int inchstr(chtype *); -PDCEX chtype inch(void); -PDCEX int init_color(short, short, short, short); -PDCEX int init_pair(short, short, short); -PDCEX WINDOW *initscr(void); -PDCEX int innstr(char *, int); -PDCEX int insch(chtype); -PDCEX int insdelln(int); -PDCEX int insertln(void); -PDCEX int insnstr(const char *, int); -PDCEX int insstr(const char *); -PDCEX int instr(char *); -PDCEX int intrflush(WINDOW *, bool); -PDCEX bool isendwin(void); -PDCEX bool is_linetouched(WINDOW *, int); -PDCEX bool is_wintouched(WINDOW *); -PDCEX char *keyname(int); -PDCEX int keypad(WINDOW *, bool); -PDCEX char killchar(void); -PDCEX int leaveok(WINDOW *, bool); -PDCEX char *longname(void); -PDCEX int meta(WINDOW *, bool); -PDCEX int move(int, int); -PDCEX int mvaddch(int, int, const chtype); -PDCEX int mvaddchnstr(int, int, const chtype *, int); -PDCEX int mvaddchstr(int, int, const chtype *); -PDCEX int mvaddnstr(int, int, const char *, int); -PDCEX int mvaddstr(int, int, const char *); -PDCEX int mvchgat(int, int, int, attr_t, short, const void *); -PDCEX int mvcur(int, int, int, int); -PDCEX int mvdelch(int, int); -PDCEX int mvderwin(WINDOW *, int, int); -PDCEX int mvgetch(int, int); -PDCEX int mvgetnstr(int, int, char *, int); -PDCEX int mvgetstr(int, int, char *); -PDCEX int mvhline(int, int, chtype, int); -PDCEX chtype mvinch(int, int); -PDCEX int mvinchnstr(int, int, chtype *, int); -PDCEX int mvinchstr(int, int, chtype *); -PDCEX int mvinnstr(int, int, char *, int); -PDCEX int mvinsch(int, int, chtype); -PDCEX int mvinsnstr(int, int, const char *, int); -PDCEX int mvinsstr(int, int, const char *); -PDCEX int mvinstr(int, int, char *); -PDCEX int mvprintw(int, int, const char *, ...); -PDCEX int mvscanw(int, int, const char *, ...); -PDCEX int mvvline(int, int, chtype, int); -PDCEX int mvwaddchnstr(WINDOW *, int, int, const chtype *, int); -PDCEX int mvwaddchstr(WINDOW *, int, int, const chtype *); -PDCEX int mvwaddch(WINDOW *, int, int, const chtype); -PDCEX int mvwaddnstr(WINDOW *, int, int, const char *, int); -PDCEX int mvwaddstr(WINDOW *, int, int, const char *); -PDCEX int mvwchgat(WINDOW *, int, int, int, attr_t, short, const void *); -PDCEX int mvwdelch(WINDOW *, int, int); -PDCEX int mvwgetch(WINDOW *, int, int); -PDCEX int mvwgetnstr(WINDOW *, int, int, char *, int); -PDCEX int mvwgetstr(WINDOW *, int, int, char *); -PDCEX int mvwhline(WINDOW *, int, int, chtype, int); -PDCEX int mvwinchnstr(WINDOW *, int, int, chtype *, int); -PDCEX int mvwinchstr(WINDOW *, int, int, chtype *); -PDCEX chtype mvwinch(WINDOW *, int, int); -PDCEX int mvwinnstr(WINDOW *, int, int, char *, int); -PDCEX int mvwinsch(WINDOW *, int, int, chtype); -PDCEX int mvwinsnstr(WINDOW *, int, int, const char *, int); -PDCEX int mvwinsstr(WINDOW *, int, int, const char *); -PDCEX int mvwinstr(WINDOW *, int, int, char *); -PDCEX int mvwin(WINDOW *, int, int); -PDCEX int mvwprintw(WINDOW *, int, int, const char *, ...); -PDCEX int mvwscanw(WINDOW *, int, int, const char *, ...); -PDCEX int mvwvline(WINDOW *, int, int, chtype, int); -PDCEX int napms(int); -PDCEX WINDOW *newpad(int, int); -PDCEX SCREEN *newterm(const char *, FILE *, FILE *); -PDCEX WINDOW *newwin(int, int, int, int); -PDCEX int nl(void); -PDCEX int nocbreak(void); -PDCEX int nodelay(WINDOW *, bool); -PDCEX int noecho(void); -PDCEX int nonl(void); -PDCEX void noqiflush(void); -PDCEX int noraw(void); -PDCEX int notimeout(WINDOW *, bool); -PDCEX int overlay(const WINDOW *, WINDOW *); -PDCEX int overwrite(const WINDOW *, WINDOW *); -PDCEX int pair_content(short, short *, short *); -PDCEX int pechochar(WINDOW *, chtype); -PDCEX int pnoutrefresh(WINDOW *, int, int, int, int, int, int); -PDCEX int prefresh(WINDOW *, int, int, int, int, int, int); -PDCEX int printw(const char *, ...); -PDCEX int putwin(WINDOW *, FILE *); -PDCEX void qiflush(void); -PDCEX int raw(void); -PDCEX int redrawwin(WINDOW *); -PDCEX int refresh(void); -PDCEX int reset_prog_mode(void); -PDCEX int reset_shell_mode(void); -PDCEX int resetty(void); -PDCEX int ripoffline(int, int (*)(WINDOW *, int)); -PDCEX int savetty(void); -PDCEX int scanw(const char *, ...); -PDCEX int scr_dump(const char *); -PDCEX int scr_init(const char *); -PDCEX int scr_restore(const char *); -PDCEX int scr_set(const char *); -PDCEX int scrl(int); -PDCEX int scroll(WINDOW *); -PDCEX int scrollok(WINDOW *, bool); -PDCEX SCREEN *set_term(SCREEN *); -PDCEX int setscrreg(int, int); -PDCEX int slk_attroff(const chtype); -PDCEX int slk_attr_off(const attr_t, void *); -PDCEX int slk_attron(const chtype); -PDCEX int slk_attr_on(const attr_t, void *); -PDCEX int slk_attrset(const chtype); -PDCEX int slk_attr_set(const attr_t, short, void *); -PDCEX int slk_clear(void); -PDCEX int slk_color(short); -PDCEX int slk_init(int); -PDCEX char *slk_label(int); -PDCEX int slk_noutrefresh(void); -PDCEX int slk_refresh(void); -PDCEX int slk_restore(void); -PDCEX int slk_set(int, const char *, int); -PDCEX int slk_touch(void); -PDCEX int standend(void); -PDCEX int standout(void); -PDCEX int start_color(void); -PDCEX WINDOW *subpad(WINDOW *, int, int, int, int); -PDCEX WINDOW *subwin(WINDOW *, int, int, int, int); -PDCEX int syncok(WINDOW *, bool); -PDCEX chtype termattrs(void); -PDCEX attr_t term_attrs(void); -PDCEX char *termname(void); -PDCEX void timeout(int); -PDCEX int touchline(WINDOW *, int, int); -PDCEX int touchwin(WINDOW *); -PDCEX int typeahead(int); -PDCEX int untouchwin(WINDOW *); -PDCEX void use_env(bool); -PDCEX int vidattr(chtype); -PDCEX int vid_attr(attr_t, short, void *); -PDCEX int vidputs(chtype, int (*)(int)); -PDCEX int vid_puts(attr_t, short, void *, int (*)(int)); -PDCEX int vline(chtype, int); -PDCEX int vw_printw(WINDOW *, const char *, va_list); -PDCEX int vwprintw(WINDOW *, const char *, va_list); -PDCEX int vw_scanw(WINDOW *, const char *, va_list); -PDCEX int vwscanw(WINDOW *, const char *, va_list); -PDCEX int waddchnstr(WINDOW *, const chtype *, int); -PDCEX int waddchstr(WINDOW *, const chtype *); -PDCEX int waddch(WINDOW *, const chtype); -PDCEX int waddnstr(WINDOW *, const char *, int); -PDCEX int waddstr(WINDOW *, const char *); -PDCEX int wattroff(WINDOW *, chtype); -PDCEX int wattron(WINDOW *, chtype); -PDCEX int wattrset(WINDOW *, chtype); -PDCEX int wattr_get(WINDOW *, attr_t *, short *, void *); -PDCEX int wattr_off(WINDOW *, attr_t, void *); -PDCEX int wattr_on(WINDOW *, attr_t, void *); -PDCEX int wattr_set(WINDOW *, attr_t, short, void *); -PDCEX void wbkgdset(WINDOW *, chtype); -PDCEX int wbkgd(WINDOW *, chtype); -PDCEX int wborder(WINDOW *, chtype, chtype, chtype, chtype, - chtype, chtype, chtype, chtype); -PDCEX int wchgat(WINDOW *, int, attr_t, short, const void *); -PDCEX int wclear(WINDOW *); -PDCEX int wclrtobot(WINDOW *); -PDCEX int wclrtoeol(WINDOW *); -PDCEX int wcolor_set(WINDOW *, short, void *); -PDCEX void wcursyncup(WINDOW *); -PDCEX int wdelch(WINDOW *); -PDCEX int wdeleteln(WINDOW *); -PDCEX int wechochar(WINDOW *, const chtype); -PDCEX int werase(WINDOW *); -PDCEX int wgetch(WINDOW *); -PDCEX int wgetnstr(WINDOW *, char *, int); -PDCEX int wgetstr(WINDOW *, char *); -PDCEX int whline(WINDOW *, chtype, int); -PDCEX int winchnstr(WINDOW *, chtype *, int); -PDCEX int winchstr(WINDOW *, chtype *); -PDCEX chtype winch(WINDOW *); -PDCEX int winnstr(WINDOW *, char *, int); -PDCEX int winsch(WINDOW *, chtype); -PDCEX int winsdelln(WINDOW *, int); -PDCEX int winsertln(WINDOW *); -PDCEX int winsnstr(WINDOW *, const char *, int); -PDCEX int winsstr(WINDOW *, const char *); -PDCEX int winstr(WINDOW *, char *); -PDCEX int wmove(WINDOW *, int, int); -PDCEX int wnoutrefresh(WINDOW *); -PDCEX int wprintw(WINDOW *, const char *, ...); -PDCEX int wredrawln(WINDOW *, int, int); -PDCEX int wrefresh(WINDOW *); -PDCEX int wscanw(WINDOW *, const char *, ...); -PDCEX int wscrl(WINDOW *, int); -PDCEX int wsetscrreg(WINDOW *, int, int); -PDCEX int wstandend(WINDOW *); -PDCEX int wstandout(WINDOW *); -PDCEX void wsyncdown(WINDOW *); -PDCEX void wsyncup(WINDOW *); -PDCEX void wtimeout(WINDOW *, int); -PDCEX int wtouchln(WINDOW *, int, int, int); -PDCEX int wvline(WINDOW *, chtype, int); - -/* Wide-character functions */ - -#ifdef PDC_WIDE -PDCEX int addnwstr(const wchar_t *, int); -PDCEX int addwstr(const wchar_t *); -PDCEX int add_wch(const cchar_t *); -PDCEX int add_wchnstr(const cchar_t *, int); -PDCEX int add_wchstr(const cchar_t *); -PDCEX int bkgrnd(const cchar_t *); -PDCEX void bkgrndset(const cchar_t *); -PDCEX int border_set(const cchar_t *, const cchar_t *, const cchar_t *, - const cchar_t *, const cchar_t *, const cchar_t *, - const cchar_t *, const cchar_t *); -PDCEX int box_set(WINDOW *, const cchar_t *, const cchar_t *); -PDCEX int echo_wchar(const cchar_t *); -PDCEX int erasewchar(wchar_t *); -PDCEX int getbkgrnd(cchar_t *); -PDCEX int getcchar(const cchar_t *, wchar_t *, attr_t *, short *, void *); -PDCEX int getn_wstr(wint_t *, int); -PDCEX int get_wch(wint_t *); -PDCEX int get_wstr(wint_t *); -PDCEX int hline_set(const cchar_t *, int); -PDCEX int innwstr(wchar_t *, int); -PDCEX int ins_nwstr(const wchar_t *, int); -PDCEX int ins_wch(const cchar_t *); -PDCEX int ins_wstr(const wchar_t *); -PDCEX int inwstr(wchar_t *); -PDCEX int in_wch(cchar_t *); -PDCEX int in_wchnstr(cchar_t *, int); -PDCEX int in_wchstr(cchar_t *); -PDCEX char *key_name(wchar_t); -PDCEX int killwchar(wchar_t *); -PDCEX int mvaddnwstr(int, int, const wchar_t *, int); -PDCEX int mvaddwstr(int, int, const wchar_t *); -PDCEX int mvadd_wch(int, int, const cchar_t *); -PDCEX int mvadd_wchnstr(int, int, const cchar_t *, int); -PDCEX int mvadd_wchstr(int, int, const cchar_t *); -PDCEX int mvgetn_wstr(int, int, wint_t *, int); -PDCEX int mvget_wch(int, int, wint_t *); -PDCEX int mvget_wstr(int, int, wint_t *); -PDCEX int mvhline_set(int, int, const cchar_t *, int); -PDCEX int mvinnwstr(int, int, wchar_t *, int); -PDCEX int mvins_nwstr(int, int, const wchar_t *, int); -PDCEX int mvins_wch(int, int, const cchar_t *); -PDCEX int mvins_wstr(int, int, const wchar_t *); -PDCEX int mvinwstr(int, int, wchar_t *); -PDCEX int mvin_wch(int, int, cchar_t *); -PDCEX int mvin_wchnstr(int, int, cchar_t *, int); -PDCEX int mvin_wchstr(int, int, cchar_t *); -PDCEX int mvvline_set(int, int, const cchar_t *, int); -PDCEX int mvwaddnwstr(WINDOW *, int, int, const wchar_t *, int); -PDCEX int mvwaddwstr(WINDOW *, int, int, const wchar_t *); -PDCEX int mvwadd_wch(WINDOW *, int, int, const cchar_t *); -PDCEX int mvwadd_wchnstr(WINDOW *, int, int, const cchar_t *, int); -PDCEX int mvwadd_wchstr(WINDOW *, int, int, const cchar_t *); -PDCEX int mvwgetn_wstr(WINDOW *, int, int, wint_t *, int); -PDCEX int mvwget_wch(WINDOW *, int, int, wint_t *); -PDCEX int mvwget_wstr(WINDOW *, int, int, wint_t *); -PDCEX int mvwhline_set(WINDOW *, int, int, const cchar_t *, int); -PDCEX int mvwinnwstr(WINDOW *, int, int, wchar_t *, int); -PDCEX int mvwins_nwstr(WINDOW *, int, int, const wchar_t *, int); -PDCEX int mvwins_wch(WINDOW *, int, int, const cchar_t *); -PDCEX int mvwins_wstr(WINDOW *, int, int, const wchar_t *); -PDCEX int mvwin_wch(WINDOW *, int, int, cchar_t *); -PDCEX int mvwin_wchnstr(WINDOW *, int, int, cchar_t *, int); -PDCEX int mvwin_wchstr(WINDOW *, int, int, cchar_t *); -PDCEX int mvwinwstr(WINDOW *, int, int, wchar_t *); -PDCEX int mvwvline_set(WINDOW *, int, int, const cchar_t *, int); -PDCEX int pecho_wchar(WINDOW *, const cchar_t*); -PDCEX int setcchar(cchar_t*, const wchar_t*, const attr_t, - short, const void*); -PDCEX int slk_wset(int, const wchar_t *, int); -PDCEX int unget_wch(const wchar_t); -PDCEX int vline_set(const cchar_t *, int); -PDCEX int waddnwstr(WINDOW *, const wchar_t *, int); -PDCEX int waddwstr(WINDOW *, const wchar_t *); -PDCEX int wadd_wch(WINDOW *, const cchar_t *); -PDCEX int wadd_wchnstr(WINDOW *, const cchar_t *, int); -PDCEX int wadd_wchstr(WINDOW *, const cchar_t *); -PDCEX int wbkgrnd(WINDOW *, const cchar_t *); -PDCEX void wbkgrndset(WINDOW *, const cchar_t *); -PDCEX int wborder_set(WINDOW *, const cchar_t *, const cchar_t *, - const cchar_t *, const cchar_t *, const cchar_t *, - const cchar_t *, const cchar_t *, const cchar_t *); -PDCEX int wecho_wchar(WINDOW *, const cchar_t *); -PDCEX int wgetbkgrnd(WINDOW *, cchar_t *); -PDCEX int wgetn_wstr(WINDOW *, wint_t *, int); -PDCEX int wget_wch(WINDOW *, wint_t *); -PDCEX int wget_wstr(WINDOW *, wint_t *); -PDCEX int whline_set(WINDOW *, const cchar_t *, int); -PDCEX int winnwstr(WINDOW *, wchar_t *, int); -PDCEX int wins_nwstr(WINDOW *, const wchar_t *, int); -PDCEX int wins_wch(WINDOW *, const cchar_t *); -PDCEX int wins_wstr(WINDOW *, const wchar_t *); -PDCEX int winwstr(WINDOW *, wchar_t *); -PDCEX int win_wch(WINDOW *, cchar_t *); -PDCEX int win_wchnstr(WINDOW *, cchar_t *, int); -PDCEX int win_wchstr(WINDOW *, cchar_t *); -PDCEX wchar_t *wunctrl(cchar_t *); -PDCEX int wvline_set(WINDOW *, const cchar_t *, int); -#endif - -/* Quasi-standard */ - -PDCEX chtype getattrs(WINDOW *); -PDCEX int getbegx(WINDOW *); -PDCEX int getbegy(WINDOW *); -PDCEX int getmaxx(WINDOW *); -PDCEX int getmaxy(WINDOW *); -PDCEX int getparx(WINDOW *); -PDCEX int getpary(WINDOW *); -PDCEX int getcurx(WINDOW *); -PDCEX int getcury(WINDOW *); -PDCEX void traceoff(void); -PDCEX void traceon(void); -PDCEX char *unctrl(chtype); - -PDCEX int crmode(void); -PDCEX int nocrmode(void); -PDCEX int draino(int); -PDCEX int resetterm(void); -PDCEX int fixterm(void); -PDCEX int saveterm(void); -PDCEX void setsyx(int, int); - -PDCEX int mouse_set(mmask_t); -PDCEX int mouse_on(mmask_t); -PDCEX int mouse_off(mmask_t); -PDCEX int request_mouse_pos(void); -PDCEX void wmouse_position(WINDOW *, int *, int *); -PDCEX mmask_t getmouse(void); - -/* ncurses */ - -PDCEX int assume_default_colors(int, int); -PDCEX const char *curses_version(void); -PDCEX bool has_key(int); -PDCEX bool is_keypad(const WINDOW *); -PDCEX bool is_leaveok(const WINDOW *); -PDCEX bool is_pad(const WINDOW *); -PDCEX int set_tabsize(int); -PDCEX int use_default_colors(void); -PDCEX int wresize(WINDOW *, int, int); - -PDCEX bool has_mouse(void); -PDCEX int mouseinterval(int); -PDCEX mmask_t mousemask(mmask_t, mmask_t *); -PDCEX bool mouse_trafo(int *, int *, bool); -PDCEX int nc_getmouse(MEVENT *); -PDCEX int ungetmouse(MEVENT *); -PDCEX bool wenclose(const WINDOW *, int, int); -PDCEX bool wmouse_trafo(const WINDOW *, int *, int *, bool); - -/* PDCurses */ - -PDCEX int addrawch(chtype); -PDCEX int insrawch(chtype); -PDCEX bool is_termresized(void); -PDCEX int mvaddrawch(int, int, chtype); -PDCEX int mvdeleteln(int, int); -PDCEX int mvinsertln(int, int); -PDCEX int mvinsrawch(int, int, chtype); -PDCEX int mvwaddrawch(WINDOW *, int, int, chtype); -PDCEX int mvwdeleteln(WINDOW *, int, int); -PDCEX int mvwinsertln(WINDOW *, int, int); -PDCEX int mvwinsrawch(WINDOW *, int, int, chtype); -PDCEX int raw_output(bool); -PDCEX int resize_term(int, int); -PDCEX WINDOW *resize_window(WINDOW *, int, int); -PDCEX int waddrawch(WINDOW *, chtype); -PDCEX int winsrawch(WINDOW *, chtype); -PDCEX char wordchar(void); - -#ifdef PDC_WIDE -PDCEX wchar_t *slk_wlabel(int); -#endif - -PDCEX void PDC_debug(const char *, ...); -PDCEX void PDC_get_version(PDC_VERSION *); -PDCEX int PDC_ungetch(int); -PDCEX int PDC_set_blink(bool); -PDCEX int PDC_set_bold(bool); -PDCEX int PDC_set_line_color(short); -PDCEX void PDC_set_title(const char *); - -PDCEX int PDC_clearclipboard(void); -PDCEX int PDC_freeclipboard(char *); -PDCEX int PDC_getclipboard(char **, long *); -PDCEX int PDC_setclipboard(const char *, long); - -PDCEX unsigned long PDC_get_input_fd(void); -PDCEX unsigned long PDC_get_key_modifiers(void); -PDCEX int PDC_return_key_modifiers(bool); - -#ifdef XCURSES -PDCEX WINDOW *Xinitscr(int, char **); -PDCEX void XCursesExit(void); -PDCEX int sb_init(void); -PDCEX int sb_set_horz(int, int, int); -PDCEX int sb_set_vert(int, int, int); -PDCEX int sb_get_horz(int *, int *, int *); -PDCEX int sb_get_vert(int *, int *, int *); -PDCEX int sb_refresh(void); -#endif - -/* NetBSD */ - -PDCEX int touchoverlap(const WINDOW *, WINDOW *); -PDCEX int underend(void); -PDCEX int underscore(void); -PDCEX int wunderend(WINDOW *); -PDCEX int wunderscore(WINDOW *); - -/*** Functions defined as macros ***/ - -/* getch() and ungetch() conflict with some DOS libraries */ - -#define getch() wgetch(stdscr) -#define ungetch(ch) PDC_ungetch(ch) - -#define COLOR_PAIR(n) (((chtype)(n) << PDC_COLOR_SHIFT) & A_COLOR) -#define PAIR_NUMBER(n) (((n) & A_COLOR) >> PDC_COLOR_SHIFT) - -/* These will _only_ work as macros */ - -#define getbegyx(w, y, x) (y = getbegy(w), x = getbegx(w)) -#define getmaxyx(w, y, x) (y = getmaxy(w), x = getmaxx(w)) -#define getparyx(w, y, x) (y = getpary(w), x = getparx(w)) -#define getyx(w, y, x) (y = getcury(w), x = getcurx(w)) - -#define getsyx(y, x) { if (curscr->_leaveit) (y)=(x)=-1; \ - else getyx(curscr,(y),(x)); } - -#ifdef NCURSES_MOUSE_VERSION -# define getmouse(x) nc_getmouse(x) -#endif - -/* Deprecated */ - -#define PDC_save_key_modifiers(x) (OK) - -/* return codes from PDC_getclipboard() and PDC_setclipboard() calls */ - -#define PDC_CLIP_SUCCESS 0 -#define PDC_CLIP_ACCESS_ERROR 1 -#define PDC_CLIP_EMPTY 2 -#define PDC_CLIP_MEMORY_ERROR 3 - -/* PDCurses key modifier masks */ - -#define PDC_KEY_MODIFIER_SHIFT 1 -#define PDC_KEY_MODIFIER_CONTROL 2 -#define PDC_KEY_MODIFIER_ALT 4 -#define PDC_KEY_MODIFIER_NUMLOCK 8 - -#ifdef __cplusplus -# ifndef PDC_PP98 -# undef bool -# endif -} -#endif - -#endif /* __PDCURSES__ */ diff --git a/PDCurses/lib/pdcurses.lib b/PDCurses/lib/pdcurses.lib deleted file mode 100644 index c0bff422a94dd8899a6b474b6b2475520f6037ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 288632 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 w;5b2j5ehk|PWg}rei--X -{ -public: - - GameClient() = default; - ~GameClient() = default; - - void PingServer() - { - net::message msg; - msg.header.id = PTypes::SERVER_PING; - - std::chrono::system_clock::time_point timeNow = std::chrono::system_clock::now(); - - msg << timeNow; - Send(msg); - } - - void MessageAll() - { - net::message msg; - msg.header.id = PTypes::CLIENT_CONNECT; - Send(msg); - } -}; \ No newline at end of file diff --git a/include/game_server.h b/include/game_server.h deleted file mode 100644 index e7eb56b..0000000 --- a/include/game_server.h +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once - -#include "net_connection.h" -#include "net_server.h" -#include "game_structs.h" - -class GameServer : public net::server_interface -{ -public: - - GameServer(uint16_t port) : net::server_interface(port) - { - } -protected: - - bool OnClientConnect(std::shared_ptr> client) override - { - net::message msg; - msg.header.id = PTypes::SERVER_ACCEPT; - - client->Send(msg); - return true; - } - - void OnClientDisconnect(std::shared_ptr> client) override - { - std::cout << "Removing client [" << client->GetID() << "]\n"; - } - - // Called when a message arrives - void OnMessage(std::shared_ptr> client, net::message& msg) override - { - switch (msg.header.id) - { - case PTypes::SERVER_PING: - { - std::cout << "[" << client->GetID() << "]: Server Ping\n"; - - // Simply bounce message back to client - client->Send(msg); - } - break; - - case PTypes::CLIENT_CONNECT: - { - std::cout << "[" << client->GetID() << "]: Client connected\n"; - - // Construct a new message and send it to all clients - net::message msg; - msg.header.id = PTypes::CLIENT_CONNECT; - msg << client->GetID(); - MessageAllClients(msg, client); - - } - break; - } - } - - -}; \ No newline at end of file diff --git a/include/game_structs.h b/include/game_structs.h index 53da116..3799ae3 100644 --- a/include/game_structs.h +++ b/include/game_structs.h @@ -34,9 +34,14 @@ inline uint8_t convert(const CellType& c) } } +template +class MazeGenerator; + template class GameField { + // friend MazeGenerator; + private: union { CellType field[WIDTH][HEIGHT]; @@ -46,18 +51,32 @@ class GameField }; // std::mutex Mutex; public: - constexpr GameField() = default; + GameField() + { + fill(CellType::EMPTY); + //fill(CellType::WALL); + //for (size_t i = 0; i < WIDTH; i++) + // for (size_t j = 0; j < HEIGHT; j++) + // if ((i + j) % 2 == 0) + // field[i][j] = CellType::FOOD; + + //field[0][1] = CellType::PLAYER; + //field[1][2] = CellType::PLAYER; + //field[2][3] = CellType::PLAYER; + + } ~GameField() = default; GameField(const GameField&) = default; GameField(GameField&&) noexcept = default; - [[nodiscard]] bool operator==(const GameField& other) const noexcept + bool operator==(const GameField& other) const { + // std::scoped_lock lock(Mutex); return (std::memcmp(&field, &other.field, WIDTH * HEIGHT) == 0); } - [[nodiscard]] bool operator!=(const GameField& other) const noexcept + bool operator!=(const GameField& other) const { return !(*this == other); } @@ -65,54 +84,101 @@ class GameField GameField& operator=(const GameField& other) { std::memcpy(&field, &other.field, WIDTH * HEIGHT); + // Mutex = other.Mutex; return *this; } GameField& operator=(GameField&& other) noexcept { std::memcpy(&field, &other.field, WIDTH * HEIGHT); + // Mutex = std::move(other.Mutex); return *this; } CellType& operator()(size_t x, size_t y) { - static_assert(x < WIDTH && y < HEIGHT); + // std::scoped_lock lock(Mutex); + assert(x < WIDTH && y < HEIGHT); return field[x][y]; } const CellType& at(const size_t& x, const size_t& y) const { - static_assert(x < WIDTH && y < HEIGHT); + // std::scoped_lock lock(Mutex); + assert(x < WIDTH && y < HEIGHT); return field[x][y]; } - constexpr void fill(CellType value) + bool isEmpty(size_t x, size_t y) const { - std::memset(&field[0], value, WIDTH * HEIGHT); + // std::scoped_lock lock(Mutex); + assert(x < WIDTH && y < HEIGHT); + return field[x][y] == CellType::EMPTY || field[x][y] == CellType::FOOD; } - [[nodiscard]] constexpr size_t GetWidth() const noexcept + size_t countFood() const { + // std::scoped_lock lock(Mutex); + size_t cnt = 0; + + for (size_t i = 0; i < WIDTH; i++) + for (size_t j = 0; j < HEIGHT; j++) + if (field[i][j] == CellType::FOOD) + cnt++; + + return cnt; + } + +private: + void fill(CellType value) + { + std::memset(&field[0], static_cast(value), WIDTH * HEIGHT); + } + +public: + size_t GetWidth() const noexcept + { + // // std::scoped_lock lock(Mutex); return WIDTH; } - [[nodiscard]] constexpr size_t GetHeight() const noexcept + size_t GetHeight() const noexcept { + // // std::scoped_lock lock(Mutex); + return HEIGHT; + } + + constexpr const size_t ConstexprWidth() const noexcept + { + // std::scoped_lock(Mutex); + return WIDTH; + } + + constexpr const size_t ConstexprHeight() const noexcept + { + // std::scoped_lock(Mutex); return HEIGHT; } +private: void InvertVertical() { - static_assert(HEIGHT % 2 == 0); + // static_assert(HEIGHT % 2 == 0, "Incompatible"); - for (size_t i = 0; i < WIDTH; i++) - for (size_t j = 0; j < HEIGHT / 2; j++) - std::swap(field[i][j], field[i][HEIGHT - j]); + for (size_t j = 0; j < HEIGHT / 2; j++) + for (size_t i = 0; i < WIDTH; i++) + { + // std::cout << "exchange cell (" << i << ", " << j << ") with cell (" << i << ", " << HEIGHT - j - 1 << ")\n"; + const auto temp = field[i][j]; + // std::swap(field[i][j], field[i][HEIGHT - j]); + field[i][j] = field[i][HEIGHT - j - 1]; + field[i][HEIGHT - j - 1] = temp; + } } void InvertHorizontal() { - static_assert(WIDTH % 2 == 0); + // static_assert(WIDTH % 2 == 0, "Incompatible"); for (size_t j = 0; j < HEIGHT; j++) for (size_t i = 0; i < WIDTH / 2; i++) @@ -134,7 +200,7 @@ class GameField other(i + offset_x, j + offset_y) = field[i][j]; } - public: +public: friend std::ostream& operator<<(std::ostream& os, const GameField& f) { for (size_t j = 0; j < f.GetHeight(); j++) @@ -144,20 +210,20 @@ class GameField char c = '\0'; switch (f.at(i, j)) { - case CellType::EMPTY: - c = ' '; - break; - case CellType::WALL: - c = '*'; - break; - case CellType::FOOD: - c = '0'; - break; - case CellType::PLAYER: - c = '+'; - break; - default: - break; + case CellType::EMPTY: + c = ' '; + break; + case CellType::WALL: + c = '*'; + break; + case CellType::FOOD: + c = '0'; + break; + case CellType::PLAYER: + c = '+'; + break; + default: + break; } os << c; } @@ -186,16 +252,16 @@ class GameField } } - public: - std::array ToBytes() const noexcept +public: + std::array ToBytes() const noexcept { // std::scoped_lock lock(Mutex); - std::array byteArray; + std::array byteArray; std::memcpy(byteArray.data(), &bytes[0], WIDTH * HEIGHT); return byteArray; } - void FromBytes(const std::array& byteArray) + void FromBytes(const std::array& byteArray) { // std::scoped_lock lock(Mutex); std::memcpy(&bytes[0], byteArray.data(), WIDTH * HEIGHT); @@ -290,11 +356,11 @@ class MazeGenerator {{0,0}, {1,0}, {1,1}, {2,0}}, // T shape {{0,0}, {1,0}, {1,1}, {2,1}} // S shape }; - + public: MazeGenerator() = delete; ~MazeGenerator() = default; - + MazeGenerator(const MazeGenerator&) = delete; MazeGenerator(MazeGenerator&&) noexcept = delete; MazeGenerator& operator=(const MazeGenerator&) = delete; diff --git a/include/includes.h b/include/includes.h index 658b9cf..2d8ad8f 100644 --- a/include/includes.h +++ b/include/includes.h @@ -1,5 +1,10 @@ #pragma once +#include +#include +#include +#include + #if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) /* UNIX-style OS. ------------------------------------------- */ #define IS_UNIX 1 @@ -8,33 +13,21 @@ #define IS_WIN 1 #endif -#ifdef IS_WIN -#define _WIN32_WINNT 0x0A00 -#endif - #ifndef ASIO_STANDALONE #define ASIO_STANDALONE #endif -#include -#include -#include -#include -#include // TODO: , . -#ifdef IS_UNIX - /* UNIX-style OS. ------------------------------------------- */ -#include -#elif defined(IS_WIN) -// Windows -#include "../PDCurses/include/curses.h" -#endif +// #ifdef IS_UNIX +// /* UNIX-style OS. ------------------------------------------- */ +// #include +// #elif defined(IS_WIN) +// // Windows +// #include "../PDCurses/include/curses.h" +// #endif #include -#include -#include -#include -#include \ No newline at end of file +#include "net_includes.h" \ No newline at end of file diff --git a/include/net_client.h b/include/net_client.h index 7f703b2..0e3fe39 100644 --- a/include/net_client.h +++ b/include/net_client.h @@ -84,7 +84,7 @@ namespace net } - [[nodiscard]] bool IsConnected() const noexcept + bool IsConnected() const noexcept { if (m_connection) return m_connection->IsConnected(); diff --git a/include/net_common.h b/include/net_common.h index e13cd63..3a9a843 100644 --- a/include/net_common.h +++ b/include/net_common.h @@ -17,10 +17,18 @@ #ifdef _WIN32 +#ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0A00 #endif +#endif + +#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#endif +#ifndef ASIO_STANDALONE #define ASIO_STANDALONE +#endif #include #include diff --git a/include/net_connection.h b/include/net_connection.h index ecccb87..321dd29 100644 --- a/include/net_connection.h +++ b/include/net_connection.h @@ -3,8 +3,11 @@ #include "net_tsqueue.h" #include "net_message.h" + namespace net { + // Connection + // Forward declare template class server_interface; @@ -12,38 +15,45 @@ namespace net class connection : public std::enable_shared_from_this> { public: - enum class owner : bool + // A connection is "owned" by either a server or a client, and its + // behaviour is slightly different bewteen the two. + enum class owner { server, client }; - connection(owner parent, - asio::io_context& asioContext, - asio::ip::tcp::socket socket, - tsqueue>& qIn) - : m_nOwnerType(parent), - m_asioContext(asioContext), - m_socket(std::move(socket)), - m_qMessagesIn(qIn) + public: + // Constructor: Specify Owner, connect to context, transfer the socket + // Provide reference to incoming message queue + connection(owner parent, asio::io_context& asioContext, asio::ip::tcp::socket socket, tsqueue>& qIn) + : m_asioContext(asioContext), m_socket(std::move(socket)), m_qMessagesIn(qIn), pServer(nullptr) { - if (m_nOwnerType == owner::server) - { - m_nHandshakeOut = static_cast(std::chrono::system_clock::now().time_since_epoch().count()); - - m_nHandshakeCheck = scramble(m_nHandshakeOut); - } - else - { - m_nHandshakeIn = 0; - m_nHandshakeOut = 0; - } - + m_nOwnerType = parent; } virtual ~connection() + {} + + // This ID is used system wide - its how clients will understand other clients + // exist across the whole system. + uint32_t GetID() const { + return id; + } + public: + void ConnectToClient(net::server_interface* serverPtr, uint32_t uid = 0) + { + if (m_nOwnerType == owner::server) + { + if (m_socket.is_open()) + { + pServer = serverPtr; + id = uid; + ReadHeader(); + } + } } void ConnectToServer(const asio::ip::tcp::resolver::results_type& endpoints) @@ -57,30 +67,33 @@ namespace net { if (!ec) { - // ReadHeader(); - - ReadValidation(); + ReadHeader(); } }); } } - + + void Disconnect() { if (IsConnected()) asio::post(m_asioContext, [this]() { m_socket.close(); }); } - [[nodiscard]] bool IsConnected() const noexcept + bool IsConnected() const { return m_socket.is_open(); } - [[nodiscard]] uint32_t GetID() const noexcept + // Prime the connection to wait for incoming messages + void StartListening() { - return id; + } + public: + // ASYNC - Send a message, connections are one-to-one so no need to specifiy + // the target, for a client, the target is the server and vice versa void Send(const message& msg) { // Reverse msg byte order @@ -90,33 +103,23 @@ namespace net asio::post(m_asioContext, [this, msg]() { - const bool bWritingMessage = !m_qMessagesOut.empty(); + // If the queue has a message in it, then we must + // assume that it is in the process of asynchronously being written. + // Either way add the message to the queue to be output. If no messages + // were available to be written, then start the process of writing the + // message at the front of the queue. + bool bWritingMessage = !m_qMessagesOut.empty(); m_qMessagesOut.push_back(msg); if (!bWritingMessage) + { WriteHeader(); - } - ); + } + }); } - void ConnectToClient(net::server_interface* server, uint32_t uid = 0) - { - if (m_nOwnerType == owner::server) - { - if (m_socket.is_open()) - { - id = uid; - - // ReadHeader(); - - WriteValidation(); - ReadValidation(server); - } - } - } private: - // ASYNC - Prime context to write a message header void WriteHeader() { @@ -157,7 +160,8 @@ namespace net // for now simply assume the connection has died by closing the // socket. When a future attempt to write to this client fails due // to the closed socket, it will be tidied up. - std::cout << '[' << id << "] Write Header Fail.\n"; + std::cout << "[" << id << "] Write Header Fail.\n"; + if (m_nOwnerType == owner::server && pServer != nullptr) pServer->OnClientFail(this->shared_from_this()); m_socket.close(); } }); @@ -188,7 +192,8 @@ namespace net else { // Sending failed, see WriteHeader() equivalent for description :P - std::cout << '[' << id << "] Write Body Fail.\n"; + std::cout << "[" << id << "] Write Body Fail.\n"; + if (m_nOwnerType == owner::server && pServer != nullptr) pServer->OnClientFail(this->shared_from_this()); m_socket.close(); } }); @@ -232,6 +237,7 @@ namespace net // Reading form the client went wrong, most likely a disconnect // has occurred. Close the socket and let the system tidy it up later. std::cout << '[' << id << "] Read Header Fail.\n"; + if (m_nOwnerType == owner::server && pServer != nullptr) pServer->OnClientFail(this->shared_from_this()); m_socket.close(); } }); @@ -256,6 +262,7 @@ namespace net { // As above! std::cout << '[' << id << "] Read Body Fail.\n"; + if (m_nOwnerType == owner::server && pServer != nullptr) pServer->OnClientFail(this->shared_from_this()); m_socket.close(); } }); @@ -277,82 +284,13 @@ namespace net ReadHeader(); } - static uint64_t scramble(const uint64_t& nInput) - { - uint64_t out = nInput ^ 0xDEADBEEFC0DECAFE; - out = (out & 0xF0F0F0F0F0F0F0) >> 4 | (out & 0x0F0F0F0F0F0F0F) << 4; - return out ^ 0xC0DEFACE12345678; - } - - - // ASYNC - Used by both client and server to write validation data - void WriteValidation() - { - asio::async_write(m_socket, asio::buffer(&m_nHandshakeOut, sizeof(uint64_t)), - [this](std::error_code ec, std::size_t length) - { - if (!ec) - { - if (m_nOwnerType == owner::client) - ReadHeader(); - } - else - { - m_socket.close(); - } - } - ); - } - - void ReadValidation(net::server_interface* server = nullptr) - { - asio::async_read(m_socket, asio::buffer(&m_nHandshakeIn, sizeof(uint64_t)), - [this, server](std::error_code ec, std::size_t length) - { - if (!ec) - { - if (m_nOwnerType == owner::server) - { - if (m_nHandshakeIn == m_nHandshakeCheck) - { - std::cout << "Client validated\n"; - server->OnClientValidated(this->shared_from_this()); - - ReadHeader(); - } - else - { - std::cout << "Client Disconnected (Fail Validation)\n"; - m_socket.close(); - } - } - else - { - m_nHandshakeOut = scramble(m_nHandshakeIn); - - WriteValidation(); - } - } - else - { - std::cout << "Client disconnected (Read Validation)\n"; - m_socket.close(); - } - } - ); - } - protected: - - // The "owner" decides how some of the connection behaves - owner m_nOwnerType = owner::server; + // Each connection has a unique socket to a remote + asio::ip::tcp::socket m_socket; // This context is shared with the whole asio instance asio::io_context& m_asioContext; - // Each connection has a unique socket to a remote - asio::ip::tcp::socket m_socket; - // This queue holds all messages to be sent to the remote side // of this connection tsqueue> m_qMessagesOut; @@ -361,13 +299,16 @@ namespace net tsqueue>& m_qMessagesIn; // Incoming messages are constructed asynchronously, so we will -// store the part assembled message here, until it is ready + // store the part assembled message here, until it is ready message m_msgTemporaryIn; + // The "owner" decides how some of the connection behaves + owner m_nOwnerType = owner::server; + uint32_t id = 0; - uint64_t m_nHandshakeOut = 0; - uint64_t m_nHandshakeIn = 0; - uint64_t m_nHandshakeCheck = 0; + // If the connection is owned by server, then provide it with pointer to the server interface + + net::server_interface* pServer = nullptr; }; } \ No newline at end of file diff --git a/include/net_message.h b/include/net_message.h index 798f031..a04b8f1 100644 --- a/include/net_message.h +++ b/include/net_message.h @@ -1,9 +1,10 @@ #pragma once #include "net_common.h" +// #include "stl_container_check.h" -constexpr static uint32_t MAGIC = 0xabcdfe01; -constexpr static uint32_t MAGIC_LITTLE = 0x01fecdab; +constexpr static uint32_t MAGIC = 0xabcdfe01; +constexpr static uint32_t MAGIC_LITTLE = 0x01fecdab; //template, T>* = nullptr> //constexpr inline void ChangeEndian(T& value); @@ -33,7 +34,6 @@ void foo(const T& data) namespace net { - template class connection; @@ -41,27 +41,31 @@ namespace net template struct message_header { - uint32_t magic = MAGIC; - T ptype{}; - uint32_t datasize = 0; + uint32_t magic = 0xabcdfe01; + T id{}; + uint32_t size = 0; }; - template + // Message Body contains a header and a std::vector, containing raw bytes + // of infomation. This way the message can be variable length, but the size + // in the header must be updated. + template struct message { + // Header & Body vector message_header header{}; - std::vector data; + std::vector body; // returns size of entire message packet in bytes - [[nodiscard]] size_t size() const noexcept + size_t size() const { - return sizeof(message_header) + data.size(); + return body.size(); } // Override for std::cout compatibility - produces friendly description of message - friend std::ostream& operator<<(std::ostream& os, const message& msg) + friend std::ostream& operator << (std::ostream& os, const message& msg) { - os << "PType: " << static_cast(msg.header.ptype) << " Size: " << msg.header.datasize; + os << "ID:" << static_cast(msg.header.id) << " Size:" << msg.header.size; return os; } @@ -79,13 +83,13 @@ namespace net //static_assert(std::is_standard_layout_v, "Data is too complex to be pushed into vector"); // Cache current size of vector, as this will be the point we insert the data - const size_t i = msg.data.size(); + size_t i = msg.body.size(); // Resize the vector by the size of the data being pushed - msg.data.resize(i + sizeof(DataType)); + msg.body.resize(msg.body.size() + sizeof(DataType)); // Physically copy the data into the newly allocated vector space - std::memcpy(msg.data.data() + i, &data, sizeof(DataType)); + std::memcpy(msg.body.data() + i, &data, sizeof(DataType)); // Recalculate the message size msg.header.size = msg.size(); @@ -144,10 +148,10 @@ namespace net friend message& operator >> (message& msg, DataType& data) { // Cache the location towards the end of the vector where the pulled data starts - const size_t i = msg.data.size() - sizeof(DataType); + size_t i = msg.body.size() - sizeof(DataType); // Physically copy the data from the vector into the user variable - std::memcpy(&data, msg.data.data() + i, sizeof(DataType)); + std::memcpy(&data, msg.body.data() + i, sizeof(DataType)); ChangeEndian(data); @@ -208,7 +212,7 @@ namespace net ChangeEndian(data.player_name_len); // Shrink the vector to remove read bytes, and reset end position - msg.data.resize(i); + msg.body.resize(i); // Recalculate the message size msg.header.size = msg.size(); @@ -226,6 +230,15 @@ namespace net } }; + + // An "owned" message is identical to a regular message, but it is associated with + // a connection. On a server, the owner would be the client that sent the message, + // on a client the owner would be the server. + + // Forward declare the connection + template + class connection; + template struct owned_message { @@ -233,12 +246,11 @@ namespace net message msg; // Again, a friendly string maker - friend std::ostream& operator<<(std::ostream& os, const owned_message& msg_) + friend std::ostream& operator<<(std::ostream& os, const owned_message& msg) { - os << msg_.msg; + os << msg.msg; return os; } }; - } \ No newline at end of file diff --git a/include/net_server.h b/include/net_server.h index 061f2d3..9d06522 100644 --- a/include/net_server.h +++ b/include/net_server.h @@ -10,9 +10,6 @@ namespace net class server_interface { public: - - server_interface() = delete; - // Create a server, ready to listen on specified port server_interface(uint16_t port) : m_asioAcceptor(m_asioContext, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)) @@ -20,6 +17,8 @@ namespace net } + server_interface() = delete; + virtual ~server_interface() { // May as well try and tidy up @@ -85,6 +84,8 @@ namespace net std::make_shared>(connection::owner::server, m_asioContext, std::move(socket), m_qMessagesIn); + + // Give the user server a chance to deny connection if (OnClientConnect(newconn)) { @@ -117,6 +118,49 @@ namespace net }); } + void OnClientFail(std::shared_ptr> client) + { + std::cout << "Client " << client->GetID() << " has failed\n"; + DisconnectClient(client); + } + + void RemoveDisconnectedClients() + { + for (auto it = m_deqConnections.begin(); it != m_deqConnections.end(); ) + { + if (!((*it) && (*it)->IsConnected())) + { + // std::cout << "Removed client " << (*it)->GetID() << '\n'; + + OnClientDisconnect(*it); + (*it).reset(); + it = m_deqConnections.erase(it); + } + else + { + ++it; + } + } + } + + // Disconnect client by force + void DisconnectClient(std::shared_ptr> client) + { + // If we cant communicate with client then we may as + // well remove the client - let the server know, it may + // be tracking it somehow + OnClientDisconnect(client); + + // client->Disconnect(); + + // Off you go now, bye bye! + client.reset(); + + // Then physically remove it from the container + m_deqConnections.erase( + std::remove(m_deqConnections.begin(), m_deqConnections.end(), client), m_deqConnections.end()); + } + // Send a message to a specific client void MessageClient(std::shared_ptr> client, const message& msg) { @@ -188,6 +232,7 @@ namespace net size_t nMessageCount = 0; while (nMessageCount < nMaxMessages && !m_qMessagesIn.empty()) { + // Grab the front message auto msg = m_qMessagesIn.pop_front(); @@ -199,6 +244,8 @@ namespace net OnMessage(msg.remote, msg.msg); nMessageCount++; + + // RemoveDisconnectedClients(); } } @@ -224,13 +271,6 @@ namespace net } - public: - - virtual void OnClientValidated(std::shared_ptr> client) - { - - } - protected: // Thread Safe Queue for incoming message packets diff --git a/olcPixelGameEngine/include/olcPGEX_TransformedView.h b/olcPixelGameEngine/include/olcPGEX_TransformedView.h new file mode 100644 index 0000000..7bef5f5 --- /dev/null +++ b/olcPixelGameEngine/include/olcPGEX_TransformedView.h @@ -0,0 +1,770 @@ +/* + olcPGEX_TransformedView.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine Extension | + | Transformed View v1.09 | + +-------------------------------------------------------------+ + + NOTE: UNDER ACTIVE DEVELOPMENT - THERE ARE BUGS/GLITCHES + + What is this? + ~~~~~~~~~~~~~ + This extension provides drawing routines that are compatible with + changeable world and screen spaces. For example you can pan and + zoom, and all PGE drawing routines will automatically adopt the current + world scales and offsets. + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2024 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, OneLoneCoder 2019, 2020, 2021, 2022, 2023, 2024 + + Revisions: + 1.00: Initial Release + 1.01: Fix for rounding error when scaling to screen + 1.02: Added DrawLineDecal for convenience + 1.03: Removed std::floor from WorldToScreen() + Added HandlePanAndZoom(...) convenience function + Removed unused "range" facility in TileTransformView + 1.04: Added DrawPolygonDecal() for arbitrary polygons + 1.05: Clipped DrawSprite() to visible area, massive performance increase + 1.06: Fixed error in DrawLine() - Thanks CraisyDaisyRecords (& Fern)! + 1.07: +DrawRectDecal() + +GetPGE() + 1.08: +DrawPolygonDecal() with tint overload, akin to PGE + 1.09: +SetScaleExtents() - Sets range that world scale can exist within + +EnableScaleClamp() - Applies a range that scaling is clamped to + These are both useful for having zoom clamped between a min and max + without weird panning artefacts occuring +*/ + +#pragma once +#ifndef OLC_PGEX_TRANSFORMEDVIEW_H +#define OLC_PGEX_TRANSFORMEDVIEW_H + +#include "olcPixelGameEngine.h" + + + +namespace olc +{ + class TransformedView : public olc::PGEX + { + public: + TransformedView() = default; + virtual void Initialise(const olc::vi2d& vViewArea, const olc::vf2d& vPixelScale = { 1.0f, 1.0f }); + + olc::PixelGameEngine* GetPGE(); + + public: + void SetWorldOffset(const olc::vf2d& vOffset); + void MoveWorldOffset(const olc::vf2d& vDeltaOffset); + void SetWorldScale(const olc::vf2d& vScale); + void SetViewArea(const olc::vi2d& vViewArea); + olc::vf2d GetWorldTL() const; + olc::vf2d GetWorldBR() const; + olc::vf2d GetWorldVisibleArea() const; + void ZoomAtScreenPos(const float fDeltaZoom, const olc::vi2d& vPos); + void SetZoom(const float fZoom, const olc::vf2d& vPos); + void StartPan(const olc::vi2d& vPos); + void UpdatePan(const olc::vi2d& vPos); + void EndPan(const olc::vi2d& vPos); + const olc::vf2d& GetWorldOffset() const; + const olc::vf2d& GetWorldScale() const; + virtual olc::vf2d WorldToScreen(const olc::vf2d& vWorldPos) const; + virtual olc::vf2d ScreenToWorld(const olc::vf2d& vScreenPos) const; + virtual olc::vf2d ScaleToWorld(const olc::vf2d& vScreenSize) const; + virtual olc::vf2d ScaleToScreen(const olc::vf2d& vWorldSize) const; + virtual bool IsPointVisible(const olc::vf2d& vPos) const; + virtual bool IsRectVisible(const olc::vf2d& vPos, const olc::vf2d& vSize) const; + virtual void HandlePanAndZoom(const int nMouseButton = 2, const float fZoomRate = 0.1f, const bool bPan = true, const bool bZoom = true); + void SetScaleExtents(const olc::vf2d& vScaleMin, const olc::vf2d& vScaleMax); + void EnableScaleClamp(const bool bEnable); + + protected: + olc::vf2d m_vWorldOffset = { 0.0f, 0.0f }; + olc::vf2d m_vWorldScale = { 1.0f, 1.0f }; + olc::vf2d m_vRecipPixel = { 1.0f, 1.0f }; + olc::vf2d m_vPixelScale = { 1.0f, 1.0f }; + bool m_bPanning = false; + olc::vf2d m_vStartPan = { 0.0f, 0.0f }; + olc::vi2d m_vViewArea; + bool m_bZoomClamp = false; + olc::vf2d m_vMaxScale = { 0.0f, 0.0f }; + olc::vf2d m_vMinScale = { 0.0f, 0.0f }; + + public: // Hopefully, these should look familiar! + // Plots a single point + virtual bool Draw(float x, float y, olc::Pixel p = olc::WHITE); + bool Draw(const olc::vf2d& pos, olc::Pixel p = olc::WHITE); + // Draws a line from (x1,y1) to (x2,y2) + void DrawLine(float x1, float y1, float x2, float y2, olc::Pixel p = olc::WHITE, uint32_t pattern = 0xFFFFFFFF); + void DrawLine(const olc::vf2d& pos1, const olc::vf2d& pos2, olc::Pixel p = olc::WHITE, uint32_t pattern = 0xFFFFFFFF); + // Draws a circle located at (x,y) with radius + void DrawCircle(float x, float y, float radius, olc::Pixel p = olc::WHITE, uint8_t mask = 0xFF); + void DrawCircle(const olc::vf2d& pos, float radius, olc::Pixel p = olc::WHITE, uint8_t mask = 0xFF); + // Fills a circle located at (x,y) with radius + void FillCircle(float x, float y, float radius, olc::Pixel p = olc::WHITE); + void FillCircle(const olc::vf2d& pos, float radius, olc::Pixel p = olc::WHITE); + // Draws a rectangle at (x,y) to (x+w,y+h) + void DrawRect(float x, float y, float w, float h, olc::Pixel p = olc::WHITE); + void DrawRect(const olc::vf2d& pos, const olc::vf2d& size, olc::Pixel p = olc::WHITE); + // Fills a rectangle at (x,y) to (x+w,y+h) + void FillRect(float x, float y, float w, float h, olc::Pixel p = olc::WHITE); + void FillRect(const olc::vf2d& pos, const olc::vf2d& size, olc::Pixel p = olc::WHITE); + // Draws a triangle between points (x1,y1), (x2,y2) and (x3,y3) + void DrawTriangle(float x1, float y1, float x2, float y2, float x3, float y3, olc::Pixel p = olc::WHITE); + void DrawTriangle(const olc::vf2d& pos1, const olc::vf2d& pos2, const olc::vf2d& pos3, olc::Pixel p = olc::WHITE); + // Flat fills a triangle between points (x1,y1), (x2,y2) and (x3,y3) + void FillTriangle(float x1, float y1, float x2, float y2, float x3, float y3, olc::Pixel p = olc::WHITE); + void FillTriangle(const olc::vf2d& pos1, const olc::vf2d& pos2, const olc::vf2d& pos3, olc::Pixel p = olc::WHITE); + // Draws an entire sprite at location (x,y) + void DrawSprite(float x, float y, olc::Sprite* sprite, float scalex = 1, float scaley = 1, uint8_t flip = olc::Sprite::NONE); + void DrawSprite(const olc::vf2d& pos, olc::Sprite* sprite, const olc::vf2d& scale = { 1.0f, 1.0f }, uint8_t flip = olc::Sprite::NONE); + // Draws an area of a sprite at location (x,y), where the + // selected area is (ox,oy) to (ox+w,oy+h) + void DrawPartialSprite(float x, float y, Sprite* sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, float scalex = 1, float scaley = 1, uint8_t flip = olc::Sprite::NONE); + void DrawPartialSprite(const olc::vf2d& pos, Sprite* sprite, const olc::vi2d& sourcepos, const olc::vi2d& size, const olc::vf2d& scale = { 1.0f, 1.0f }, uint8_t flip = olc::Sprite::NONE); + void DrawString(float x, float y, const std::string& sText, Pixel col, const olc::vf2d& scale); + void DrawString(const olc::vf2d& pos, const std::string& sText, const Pixel col, const olc::vf2d& scale); + + + // Draws a whole decal, with optional scale and tinting + void DrawDecal(const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& scale = { 1.0f,1.0f }, const olc::Pixel& tint = olc::WHITE); + // Draws a region of a decal, with optional scale and tinting + void DrawPartialDecal(const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::vf2d& scale = { 1.0f,1.0f }, const olc::Pixel& tint = olc::WHITE); + void DrawPartialDecal(const olc::vf2d& pos, const olc::vf2d& size, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); + // Draws fully user controlled 4 vertices, pos(pixels), uv(pixels), colours + void DrawExplicitDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::vf2d* uv, const olc::Pixel* col, uint32_t elements = 4); + //// Draws a decal with 4 arbitrary points, warping the texture to look "correct" + void DrawWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::Pixel& tint = olc::WHITE); + void DrawWarpedDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::Pixel& tint = olc::WHITE); + void DrawWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::Pixel& tint = olc::WHITE); + //// As above, but you can specify a region of a decal source sprite + void DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); + void DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); + void DrawPartialWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); + //// Draws a decal rotated to specified angle, wit point of rotation offset + void DrawRotatedDecal(const olc::vf2d& pos, olc::Decal* decal, const float fAngle, const olc::vf2d& center = { 0.0f, 0.0f }, const olc::vf2d& scale = { 1.0f,1.0f }, const olc::Pixel& tint = olc::WHITE); + void DrawPartialRotatedDecal(const olc::vf2d& pos, olc::Decal* decal, const float fAngle, const olc::vf2d& center, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::vf2d& scale = { 1.0f, 1.0f }, const olc::Pixel& tint = olc::WHITE); + // Draws a multiline string as a decal, with tiniting and scaling + void DrawStringDecal(const olc::vf2d& pos, const std::string& sText, const olc::Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }); + void DrawStringPropDecal(const olc::vf2d& pos, const std::string& sText, const olc::Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }); + // Draws a single shaded filled rectangle as a decal + void FillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col = olc::WHITE); + void DrawRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col = olc::WHITE); + + // Draws a corner shaded rectangle as a decal + void GradientFillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel colTR); + // Draws an arbitrary convex textured polygon using GPU + void DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const olc::Pixel tint = olc::WHITE); + void DrawLineDecal(const olc::vf2d& pos1, const olc::vf2d& pos2, Pixel p = olc::WHITE); + void DrawPolygonDecal(olc::Decal* decal, const std::vector&pos, const std::vector&uv, const std::vector &tint); + void DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const std::vector& colours, const olc::Pixel tint); + + +#if defined(OLC_PGEX_SHADER) + // Shader Specific + void DrawDecal(olc::Shade& shader, const olc::vf2d & pos, olc::Decal * decal, const olc::vf2d & scale = { 1.0f,1.0f }, const olc::Pixel & tint = olc::WHITE); + void DrawPartialDecal(olc::Shade& shader, const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::vf2d& scale = { 1.0f,1.0f }, const olc::Pixel& tint = olc::WHITE); + void DrawPartialDecal(olc::Shade& shader, const olc::vf2d& pos, const olc::vf2d& size, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); +#endif + + + + }; + + class TileTransformedView : public TransformedView + { + public: + TileTransformedView() = default; + TileTransformedView(const olc::vi2d& vViewArea, const olc::vi2d& vTileSize); + + public: + olc::vi2d GetTopLeftTile() const; + olc::vi2d GetBottomRightTile() const; + olc::vi2d GetVisibleTiles() const; + olc::vi2d GetTileUnderScreenPos(const olc::vi2d& vPos) const; + const olc::vi2d GetTileOffset() const; + + }; +} + +#ifdef OLC_PGEX_TRANSFORMEDVIEW +#undef OLC_PGEX_TRANSFORMEDVIEW + +namespace olc +{ + olc::PixelGameEngine* TransformedView::GetPGE() + { + return pge; + } + + void TransformedView::Initialise(const olc::vi2d& vViewArea, const olc::vf2d& vPixelScale) + { + SetViewArea(vViewArea); + SetWorldScale(vPixelScale); + m_vPixelScale = vPixelScale; + m_vRecipPixel = 1.0f / m_vPixelScale; + } + + void TransformedView::SetWorldOffset(const olc::vf2d& vOffset) + { + m_vWorldOffset = vOffset; + } + + void TransformedView::MoveWorldOffset(const olc::vf2d& vDeltaOffset) + { + m_vWorldOffset += vDeltaOffset; + } + + void TransformedView::SetWorldScale(const olc::vf2d& vScale) + { + m_vWorldScale = vScale; + if (m_bZoomClamp) m_vWorldScale = m_vWorldScale.clamp(m_vMinScale, m_vMaxScale); + } + + void TransformedView::SetViewArea(const olc::vi2d& vViewArea) + { + m_vViewArea = vViewArea; + } + + olc::vf2d TransformedView::GetWorldTL() const + { + return TransformedView::ScreenToWorld({ 0,0 }); + } + + olc::vf2d TransformedView::GetWorldBR() const + { + return TransformedView::ScreenToWorld(m_vViewArea); + } + + olc::vf2d TransformedView::GetWorldVisibleArea() const + { + return GetWorldBR() - GetWorldTL(); + } + + void TransformedView::SetScaleExtents(const olc::vf2d& vScaleMin, const olc::vf2d& vScaleMax) + { + m_vMaxScale = vScaleMax; + m_vMinScale = vScaleMin; + } + + void TransformedView::EnableScaleClamp(const bool bEnable) + { + m_bZoomClamp = bEnable; + } + + void TransformedView::ZoomAtScreenPos(const float fDeltaZoom, const olc::vi2d& vPos) + { + olc::vf2d vOffsetBeforeZoom = ScreenToWorld(vPos); + m_vWorldScale *= fDeltaZoom; + if (m_bZoomClamp) m_vWorldScale = m_vWorldScale.clamp(m_vMinScale, m_vMaxScale); + olc::vf2d vOffsetAfterZoom = ScreenToWorld(vPos); + m_vWorldOffset += vOffsetBeforeZoom - vOffsetAfterZoom; + } + + void TransformedView::SetZoom(const float fZoom, const olc::vf2d& vPos) + { + olc::vf2d vOffsetBeforeZoom = ScreenToWorld(vPos); + m_vWorldScale = { fZoom, fZoom }; + if (m_bZoomClamp) m_vWorldScale = m_vWorldScale.clamp(m_vMinScale, m_vMaxScale); + olc::vf2d vOffsetAfterZoom = ScreenToWorld(vPos); + m_vWorldOffset += vOffsetBeforeZoom - vOffsetAfterZoom; + } + + void TransformedView::StartPan(const olc::vi2d& vPos) + { + m_bPanning = true; + m_vStartPan = olc::vf2d(vPos); + } + + void TransformedView::UpdatePan(const olc::vi2d& vPos) + { + if (m_bPanning) + { + m_vWorldOffset -= (olc::vf2d(vPos) - m_vStartPan) / m_vWorldScale; + m_vStartPan = olc::vf2d(vPos); + } + } + + void TransformedView::EndPan(const olc::vi2d& vPos) + { + UpdatePan(vPos); + m_bPanning = false; + } + + const olc::vf2d& TransformedView::GetWorldOffset() const + { + return m_vWorldOffset; + } + + const olc::vf2d& TransformedView::GetWorldScale() const + { + return m_vWorldScale; + } + + olc::vf2d TransformedView::WorldToScreen(const olc::vf2d& vWorldPos) const + { + olc::vf2d vFloat = ((vWorldPos - m_vWorldOffset) * m_vWorldScale); + //vFloat = { std::floor(vFloat.x + 0.5f), std::floor(vFloat.y + 0.5f) }; + return vFloat; + } + + olc::vf2d TransformedView::ScreenToWorld(const olc::vf2d& vScreenPos) const + { + return (olc::vf2d(vScreenPos) / m_vWorldScale) + m_vWorldOffset; + } + + olc::vf2d TransformedView::ScaleToWorld(const olc::vf2d& vScreenSize) const + { + return (olc::vf2d(vScreenSize) / m_vWorldScale); + } + + olc::vf2d TransformedView::ScaleToScreen(const olc::vf2d& vWorldSize) const + { + //olc::vf2d vFloat = (vWorldSize * m_vWorldScale) + olc::vf2d(0.5f, 0.5f); + //return vFloat.floor(); + return (vWorldSize * m_vWorldScale); + } + + bool TransformedView::IsPointVisible(const olc::vf2d & vPos) const + { + olc::vi2d vScreen = WorldToScreen(vPos); + return vScreen.x >= 0 && vScreen.x < m_vViewArea.x&& vScreen.y >= 0 && vScreen.y < m_vViewArea.y; + } + + bool TransformedView::IsRectVisible(const olc::vf2d& vPos, const olc::vf2d& vSize) const + { + olc::vi2d vScreenPos = WorldToScreen(vPos); + olc::vi2d vScreenSize = vSize * m_vWorldScale; + return (vScreenPos.x < 0 + m_vViewArea.x && vScreenPos.x + vScreenSize.x > 0 && vScreenPos.y < m_vViewArea.y&& vScreenPos.y + vScreenSize.y > 0); + } + + void TransformedView::HandlePanAndZoom(const int nMouseButton, const float fZoomRate, const bool bPan, const bool bZoom) + { + const auto& vMousePos = pge->GetMousePos(); + if (bPan) + { + if (pge->GetMouse(nMouseButton).bPressed) StartPan(vMousePos); + if (pge->GetMouse(nMouseButton).bHeld) UpdatePan(vMousePos); + if (pge->GetMouse(nMouseButton).bReleased) EndPan(vMousePos); + } + + if (bZoom) + { + if (pge->GetMouseWheel() > 0) ZoomAtScreenPos(1.0f + fZoomRate, vMousePos); + if (pge->GetMouseWheel() < 0) ZoomAtScreenPos(1.0f - fZoomRate, vMousePos); + } + } + + bool TransformedView::Draw(float x, float y, olc::Pixel p) + { + return Draw({ x, y }, p); + } + + bool TransformedView::Draw(const olc::vf2d & pos, olc::Pixel p) + { + return pge->Draw(WorldToScreen(pos), p); + } + + void TransformedView::DrawLine(float x1, float y1, float x2, float y2, olc::Pixel p, uint32_t pattern) + { + DrawLine({ x1, y1 }, { x2, y2 }, p, pattern); + } + + void TransformedView::DrawLine(const olc::vf2d & pos1, const olc::vf2d & pos2, olc::Pixel p, uint32_t pattern) + { + pge->DrawLine(WorldToScreen(pos1), WorldToScreen(pos2), p, pattern); + } + + void TransformedView::DrawCircle(float x, float y, float radius, olc::Pixel p, uint8_t mask) + { + DrawCircle({ x,y }, radius, p, mask); + } + + void TransformedView::DrawCircle(const olc::vf2d & pos, float radius, olc::Pixel p, uint8_t mask) + { + pge->DrawCircle(WorldToScreen(pos), int32_t(radius * m_vWorldScale.x), p, mask); + } + + void TransformedView::FillCircle(float x, float y, float radius, olc::Pixel p) + { + FillCircle({ x,y }, radius, p); + } + + void TransformedView::FillCircle(const olc::vf2d & pos, float radius, olc::Pixel p) + { + pge->FillCircle(WorldToScreen(pos), int32_t(radius * m_vWorldScale.x), p); + } + + void TransformedView::DrawRect(float x, float y, float w, float h, olc::Pixel p) + { + DrawRect({ x, y }, { w, h }, p); + } + + void TransformedView::DrawRect(const olc::vf2d & pos, const olc::vf2d & size, olc::Pixel p) + { + pge->DrawRect(WorldToScreen(pos), ((size * m_vWorldScale) + olc::vf2d(0.5f, 0.5f)).floor(), p); + } + + void TransformedView::FillRect(float x, float y, float w, float h, olc::Pixel p) + { + FillRect({ x, y }, { w, h }, p); + } + + void TransformedView::FillRect(const olc::vf2d & pos, const olc::vf2d & size, olc::Pixel p) + { + pge->FillRect(WorldToScreen(pos), size * m_vWorldScale, p); + } + + void TransformedView::DrawTriangle(float x1, float y1, float x2, float y2, float x3, float y3, olc::Pixel p) + { + DrawTriangle({ x1, y1 }, { x2, y2 }, { x3, y3 }, p); + } + + void TransformedView::DrawTriangle(const olc::vf2d & pos1, const olc::vf2d & pos2, const olc::vf2d & pos3, olc::Pixel p) + { + pge->DrawTriangle(WorldToScreen(pos1), WorldToScreen(pos2), WorldToScreen(pos3), p); + } + + void TransformedView::FillTriangle(float x1, float y1, float x2, float y2, float x3, float y3, olc::Pixel p) + { + FillTriangle({ x1, y1 }, { x2, y2 }, { x3, y3 }, p); + } + + void TransformedView::FillTriangle(const olc::vf2d & pos1, const olc::vf2d & pos2, const olc::vf2d & pos3, olc::Pixel p) + { + pge->FillTriangle(WorldToScreen(pos1), WorldToScreen(pos2), WorldToScreen(pos3), p); + } + + void TransformedView::DrawSprite(float x, float y, olc::Sprite* sprite, float scalex, float scaley, uint8_t flip) + { + DrawSprite({ x, y }, sprite, { scalex, scaley }, flip); + } + + void TransformedView::DrawSprite(const olc::vf2d & pos, olc::Sprite * sprite, const olc::vf2d & scale, uint8_t flip) + { + olc::vf2d vSpriteSize = olc::vf2d(float(sprite->width), float(sprite->height)); + if (IsRectVisible(pos, vSpriteSize * scale)) + { + olc::vf2d vSpriteScaledSize = vSpriteSize * m_vRecipPixel * m_vWorldScale * scale; + olc::vi2d vPixel; + olc::vi2d vSpritePixelStart = WorldToScreen(pos); + olc::vi2d vSpritePixelEnd = WorldToScreen((vSpriteSize * scale) + pos); + + olc::vi2d vScreenPixelStart = (vSpritePixelStart).max({0,0}); + olc::vi2d vScreenPixelEnd = (vSpritePixelEnd).min({ pge->ScreenWidth(),pge->ScreenHeight() }); + + olc::vf2d vPixelStep = 1.0f / vSpriteScaledSize; + + for (vPixel.y = vScreenPixelStart.y; vPixel.y < vScreenPixelEnd.y; vPixel.y++) + { + for (vPixel.x = vScreenPixelStart.x; vPixel.x < vScreenPixelEnd.x; vPixel.x++) + { + olc::vf2d vSample = olc::vf2d(vPixel - vSpritePixelStart) * vPixelStep; + pge->Draw(vPixel, sprite->Sample(vSample.x, vSample.y)); + } + } + } + } + + + void TransformedView::DrawPartialSprite(float x, float y, Sprite* sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, float scalex, float scaley, uint8_t flip) + { + DrawPartialSprite({ x,y }, sprite, { ox,oy }, { w, h }, { scalex, scaley }, flip); + } + + void TransformedView::DrawPartialSprite(const olc::vf2d& pos, Sprite* sprite, const olc::vi2d& sourcepos, const olc::vi2d& size, const olc::vf2d& scale, uint8_t flip) + { + olc::vf2d vSpriteSize = size; + if (IsRectVisible(pos, size * scale)) + { + olc::vf2d vSpriteScaledSize = olc::vf2d(size) * m_vRecipPixel * m_vWorldScale * scale; + olc::vf2d vSpritePixelStep = 1.0f / olc::vf2d(float(sprite->width), float(sprite->height)); + olc::vi2d vPixel, vStart = WorldToScreen(pos), vEnd = vSpriteScaledSize + vStart; + olc::vf2d vScreenPixelStep = 1.0f / vSpriteScaledSize; + + for (vPixel.y = vStart.y; vPixel.y < vEnd.y; vPixel.y++) + { + for (vPixel.x = vStart.x; vPixel.x < vEnd.x; vPixel.x++) + { + olc::vf2d vSample = ((olc::vf2d(vPixel - vStart) * vScreenPixelStep) * size * vSpritePixelStep) + olc::vf2d(sourcepos) * vSpritePixelStep; + pge->Draw(vPixel, sprite->Sample(vSample.x, vSample.y)); + } + } + } + } + + void TransformedView::DrawString(float x, float y, const std::string& sText, Pixel col, const olc::vf2d& scale) + { + DrawString({ x, y }, sText, col, scale); + } + + void TransformedView::DrawString(const olc::vf2d& pos, const std::string& sText, const Pixel col, const olc::vf2d& scale) + { + olc::vf2d vOffset = { 0.0f, 0.0f }; + Pixel::Mode m = pge->GetPixelMode(); + + auto StringPlot = [&col](const int x, const int y, const olc::Pixel& pSource, const olc::Pixel& pDest) + { + return pSource.r > 1 ? col : pDest; + }; + + pge->SetPixelMode(StringPlot); + + for (auto c : sText) + { + if (c == '\n') + { + vOffset.x = 0.0f; vOffset.y += 8.0f * m_vRecipPixel.y * scale.y; + } + else + { + int32_t ox = ((c - 32) % 16) * 8; + int32_t oy = ((c - 32) / 16) * 8; + DrawPartialSprite(pos + vOffset, pge->GetFontSprite(), { ox, oy }, { 8, 8 }, scale); + vOffset.x += 8.0f * m_vRecipPixel.x * scale.x; + } + } + pge->SetPixelMode(m); + } + + + void TransformedView::DrawDecal(const olc::vf2d & pos, olc::Decal * decal, const olc::vf2d & scale, const olc::Pixel & tint) + { + pge->DrawDecal(WorldToScreen(pos), decal, scale * m_vWorldScale * m_vRecipPixel, tint); + } + + void TransformedView::DrawPartialDecal(const olc::vf2d & pos, olc::Decal * decal, const olc::vf2d & source_pos, const olc::vf2d & source_size, const olc::vf2d & scale, const olc::Pixel & tint) + { + pge->DrawPartialDecal(WorldToScreen(pos), decal, source_pos, source_size, scale * m_vWorldScale * m_vRecipPixel, tint); + } + + void TransformedView::DrawPartialDecal(const olc::vf2d & pos, const olc::vf2d & size, olc::Decal * decal, const olc::vf2d & source_pos, const olc::vf2d & source_size, const olc::Pixel & tint) + { + pge->DrawPartialDecal(WorldToScreen(pos), size * m_vWorldScale * m_vRecipPixel, decal, source_pos, source_size, tint); + } + + void TransformedView::DrawExplicitDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::vf2d* uv, const olc::Pixel* col, uint32_t elements) + { + std::vector vTransformed(elements); + for (uint32_t n = 0; n < elements; n++) + vTransformed[n] = WorldToScreen(pos[n]); + pge->DrawExplicitDecal(decal, vTransformed.data(), uv, col, elements); + } + + void TransformedView::DrawWarpedDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::Pixel& tint) + { + std::array vTransformed = + { { + WorldToScreen(pos[0]), WorldToScreen(pos[1]), + WorldToScreen(pos[2]), WorldToScreen(pos[3]), + } }; + + pge->DrawWarpedDecal(decal, vTransformed, tint); + } + + void TransformedView::DrawWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::Pixel& tint) + { + DrawWarpedDecal(decal, &pos[0], tint); + } + + void TransformedView::DrawWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::Pixel& tint) + { + DrawWarpedDecal(decal, pos.data(), tint); + } + + void TransformedView::DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint) + { + DrawPartialWarpedDecal(decal, &pos[0], source_pos, source_size, tint); + } + + void TransformedView::DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint) + { + std::array vTransformed = + { { + WorldToScreen(pos[0]), WorldToScreen(pos[1]), + WorldToScreen(pos[2]), WorldToScreen(pos[3]), + } }; + + pge->DrawPartialWarpedDecal(decal, vTransformed, source_pos, source_size, tint); + } + + void TransformedView::DrawPartialWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint) + { + DrawPartialWarpedDecal(decal, pos.data(), source_pos, source_size, tint); + } + + void TransformedView::DrawRotatedDecal(const olc::vf2d & pos, olc::Decal * decal, const float fAngle, const olc::vf2d & center, const olc::vf2d & scale, const olc::Pixel & tint) + { + pge->DrawRotatedDecal(WorldToScreen(pos), decal, fAngle, center, scale * m_vWorldScale * m_vRecipPixel, tint); + } + + void TransformedView::DrawPartialRotatedDecal(const olc::vf2d & pos, olc::Decal * decal, const float fAngle, const olc::vf2d & center, const olc::vf2d & source_pos, const olc::vf2d & source_size, const olc::vf2d & scale, const olc::Pixel & tint) + { + pge->DrawPartialRotatedDecal(WorldToScreen(pos), decal, fAngle, center, source_pos, source_size, scale * m_vWorldScale * m_vRecipPixel, tint); + } + + void TransformedView::DrawStringDecal(const olc::vf2d & pos, const std::string & sText, const olc::Pixel col, const olc::vf2d & scale) + { + pge->DrawStringDecal(WorldToScreen(pos), sText, col, scale * m_vWorldScale * m_vRecipPixel); + } + + void TransformedView::DrawStringPropDecal(const olc::vf2d & pos, const std::string & sText, const olc::Pixel col, const olc::vf2d & scale ) + { + pge->DrawStringPropDecal(WorldToScreen(pos), sText, col, scale * m_vWorldScale * m_vRecipPixel); + } + + void TransformedView::FillRectDecal(const olc::vf2d & pos, const olc::vf2d & size, const olc::Pixel col) + { + pge->FillRectDecal(WorldToScreen(pos), (size * m_vWorldScale).ceil(), col); + } + + void TransformedView::DrawRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) + { + pge->DrawRectDecal(WorldToScreen(pos), (size * m_vWorldScale).ceil(), col); + } + + void TransformedView::DrawLineDecal(const olc::vf2d& pos1, const olc::vf2d& pos2, Pixel p) + { + pge->DrawLineDecal(WorldToScreen(pos1), WorldToScreen(pos2), p); + } + + void TransformedView::GradientFillRectDecal(const olc::vf2d & pos, const olc::vf2d & size, const olc::Pixel colTL, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel colTR) + { + pge->GradientFillRectDecal(WorldToScreen(pos), size * m_vWorldScale, colTL, colBL, colBR, colTR); + } + + void TransformedView::DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const olc::Pixel tint) + { + std::vector vTransformed(pos.size()); + for (uint32_t n = 0; n < pos.size(); n++) + vTransformed[n] = WorldToScreen(pos[n]); + pge->DrawPolygonDecal(decal, vTransformed, uv, tint); + } + + void TransformedView::DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const std::vector &tint) + { + std::vector vTransformed(pos.size()); + for (uint32_t n = 0; n < pos.size(); n++) + vTransformed[n] = WorldToScreen(pos[n]); + pge->DrawPolygonDecal(decal, vTransformed, uv, tint); + } + + void TransformedView::DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const std::vector& colours, const olc::Pixel tint) + { + std::vector vTransformed(pos.size()); + for (uint32_t n = 0; n < pos.size(); n++) + vTransformed[n] = WorldToScreen(pos[n]); + pge->DrawPolygonDecal(decal, vTransformed, uv, colours, tint); + } + + + +#if defined (OLC_PGEX_SHADER) + + void TransformedView::DrawDecal(olc::Shade &shade, const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& scale, const olc::Pixel& tint) + { + shade.DrawDecal(WorldToScreen(pos), decal, scale * m_vWorldScale * m_vRecipPixel, tint); + } + + void TransformedView::DrawPartialDecal(olc::Shade& shade, const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::vf2d& scale, const olc::Pixel& tint) + { + shade.DrawPartialDecal(WorldToScreen(pos), decal, source_pos, source_size, scale * m_vWorldScale * m_vRecipPixel, tint); + } + + void TransformedView::DrawPartialDecal(olc::Shade& shade, const olc::vf2d& pos, const olc::vf2d& size, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint) + { + shade.DrawPartialDecal(WorldToScreen(pos), size * m_vWorldScale * m_vRecipPixel, decal, source_pos, source_size, tint); + } + +#endif + + + + + + + + ///////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////// + + + TileTransformedView::TileTransformedView(const olc::vi2d& vViewArea, const olc::vi2d& vTileSize) + { + Initialise(vViewArea, vTileSize); + } + + + olc::vi2d TileTransformedView::GetTopLeftTile() const + { + return ScreenToWorld({ 0,0 }).floor(); + } + + olc::vi2d TileTransformedView::GetBottomRightTile() const + { + return ScreenToWorld(m_vViewArea).ceil(); + } + + olc::vi2d TileTransformedView::GetVisibleTiles() const + { + return GetBottomRightTile() - GetTopLeftTile(); + } + + olc::vi2d TileTransformedView::GetTileUnderScreenPos(const olc::vi2d& vPos) const + { + return ScreenToWorld(vPos).floor(); + } + + const olc::vi2d TileTransformedView::GetTileOffset() const + { + return { int32_t((m_vWorldOffset.x - std::floor(m_vWorldOffset.x)) * m_vWorldScale.x), + int32_t((m_vWorldOffset.y - std::floor(m_vWorldOffset.y)) * m_vWorldScale.y) }; + } +} + +#endif +#endif diff --git a/olcPixelGameEngine/include/olcPixelGameEngine.h b/olcPixelGameEngine/include/olcPixelGameEngine.h new file mode 100644 index 0000000..e3beb79 --- /dev/null +++ b/olcPixelGameEngine/include/olcPixelGameEngine.h @@ -0,0 +1,6751 @@ +#pragma region license_and_help +/* + olcPixelGameEngine.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine v2.25 | + | "What do you need? Pixels... Lots of Pixels..." - javidx9 | + +-------------------------------------------------------------+ + + What is this? + ~~~~~~~~~~~~~ + olc::PixelGameEngine is a single file, cross platform graphics and userinput + framework used for games, visualisations, algorithm exploration and learning. + It was developed by YouTuber "javidx9" as an assistive tool for many of his + videos. The goal of this project is to provide high speed graphics with + minimal project setup complexity, to encourage new programmers, younger people, + and anyone else that wants to make fun things. + + However, olc::PixelGameEngine is not a toy! It is a powerful and fast utility + capable of delivering high resolution, high speed, high quality applications + which behave the same way regardless of the operating system or platform. + + This file provides the core utility set of the olc::PixelGameEngine, including + window creation, keyboard/mouse input, main game thread, timing, pixel drawing + routines, image/sprite loading and drawing routines, and a bunch of utility + types to make rapid development of games/visualisations possible. + + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2022 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions or derivations of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce the above + copyright notice. This list of conditions and the following disclaimer must be + reproduced in the documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its contributors may + be used to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + Patreon: https://www.patreon.com/javidx9 + Community: https://community.onelonecoder.com + + + + Compiling in Linux + ~~~~~~~~~~~~~~~~~~ + You will need a modern C++ compiler, so update yours! + To compile use the command: + + g++ -o YourProgName YourSource.cpp -lX11 -lGL -lpthread -lpng -lstdc++fs -std=c++17 + + On some Linux configurations, the frame rate is locked to the refresh + rate of the monitor. This engine tries to unlock it but may not be + able to, in which case try launching your program like this: + + vblank_mode=0 ./YourProgName + + + + Compiling in Code::Blocks on Windows + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Well I wont judge you, but make sure your Code::Blocks installation + is really up to date - you may even consider updating your C++ toolchain + to use MinGW32-W64. + + Guide for installing recent GCC for Windows: + https://www.msys2.org/ + Guide for configuring code::blocks: + https://solarianprogrammer.com/2019/11/05/install-gcc-windows/ + https://solarianprogrammer.com/2019/11/16/install-codeblocks-gcc-windows-build-c-cpp-fortran-programs/ + + Add these libraries to "Linker Options": + user32 gdi32 opengl32 gdiplus Shlwapi dwmapi stdc++fs + + Set these compiler options: -std=c++17 + + + + Compiling on Mac - EXPERIMENTAL! PROBABLY HAS BUGS + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Yes yes, people use Macs for C++ programming! Who knew? Anyway, enough + arguing, thanks to Mumflr the PGE is now supported on Mac. Now I know nothing + about Mac, so if you need support, I suggest checking out the instructions + here: https://github.com/MumflrFumperdink/olcPGEMac + + clang++ -arch x86_64 -std=c++17 -mmacosx-version-min=10.15 -Wall -framework OpenGL + -framework GLUT -framework Carbon -lpng YourSource.cpp -o YourProgName + + + + Compiling with Emscripten (New & Experimental) + ~~~~~~~~~~~~~~~~~~~~~~~~~ + Emscripten compiler will turn your awesome C++ PixelGameEngine project into WASM! + This means you can run your application in teh browser, great for distributing + and submission in to jams and things! It's a bit new at the moment. + + em++ -std=c++17 -O2 -s ALLOW_MEMORY_GROWTH=1 -s MAX_WEBGL_VERSION=2 -s MIN_WEBGL_VERSION=2 -s USE_LIBPNG=1 ./YourSource.cpp -o pge.html + + + + Using stb_image.h + ~~~~~~~~~~~~~~~~~ + The PGE will load png images by default (with help from libpng on non-windows systems). + However, the excellent "stb_image.h" can be used instead, supporting a variety of + image formats, and has no library dependence - something we like at OLC studios ;) + To use stb_image.h, make sure it's in your code base, and simply: + + #define OLC_IMAGE_STB + + Before including the olcPixelGameEngine.h header file. stb_image.h works on many systems + and can be downloaded here: https://github.com/nothings/stb/blob/master/stb_image.h + + + + Multiple cpp file projects? + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + As a single header solution, the OLC_PGE_APPLICATION definition is used to + insert the engine implementation at a project location of your choosing. + The simplest way to setup multifile projects is to create a file called + "olcPixelGameEngine.cpp" which includes the following: + + #define OLC_PGE_APPLICATION + #include "olcPixelGameEngine.h" + + That's all it should include. You can also include PGEX includes and + defines in here too. With this in place, you dont need to + #define OLC_PGE_APPLICATION anywhere, and can simply include this + header file as an when you need to. + + + + Ports + ~~~~~ + olc::PixelGameEngine has been ported and tested with varying degrees of + success to: WinXP, Win7, Win8, Win10, Various Linux, Raspberry Pi, + Chromebook, Playstation Portable (PSP) and Nintendo Switch. If you are + interested in the details of these ports, come and visit the Discord! + + + + Thanks + ~~~~~~ + I'd like to extend thanks to Ian McKay, Bispoo, Eremiell, slavka, Kwizatz77, gurkanctn, Phantim, + IProgramInCPP, JackOJC, KrossX, Huhlig, Dragoneye, Appa, JustinRichardsMusic, SliceNDice, + dandistine, Ralakus, Gorbit99, raoul, joshinils, benedani, Moros1138, Alexio, SaladinAkara + & MagetzUb for advice, ideas and testing, and I'd like to extend my appreciation to the + 250K YouTube followers, 80+ Patreons, 4.8K Twitch followers and 10K Discord server members + who give me the motivation to keep going with all this :D + + Significant Contributors: @Moros1138, @SaladinAkara, @MaGetzUb, @slavka, + @Dragoneye, @Gorbit99, @dandistine & @Mumflr + + Special thanks to those who bring gifts! + GnarGnarHead.......Domina + Gorbit99...........Bastion, Ori & The Blind Forest, Terraria, Spelunky 2, Skully + Marti Morta........Gris + Danicron...........Terraria + SaladinAkara.......Aseprite, Inside, Quern: Undying Thoughts, Outer Wilds + AlterEgo...........Final Fantasy XII - The Zodiac Age + SlicEnDicE.........Noita, Inside + TGD................Voucher Gift + Dragoneye..........Lucas Arts Adventure Game Pack + Anonymous Pirate...Return To Monkey Island + + Special thanks to my Patreons too - I wont name you on here, but I've + certainly enjoyed my tea and flapjacks :D + + - In Memory of SaladinAkara 25.06.2023 - + + Author + ~~~~~~ + David Barr, aka javidx9, (c) OneLoneCoder 2018, 2019, 2020, 2021, 2022, 2023, 2024 +*/ +#pragma endregion + +#pragma region version_history +/* + 2.01: Made renderer and platform static for multifile projects + 2.02: Added Decal destructor, optimised Pixel constructor + 2.03: Added FreeBSD flags, Added DrawStringDecal() + 2.04: Windows Full-Screen bug fixed + 2.05: +DrawPartialWarpedDecal() - draws a warped decal from a subset image + +DrawPartialRotatedDecal() - draws a rotated decal from a subset image + 2.06: +GetTextSize() - returns area occupied by multiline string + +GetWindowSize() - returns actual window size + +GetElapsedTime() - returns last calculated fElapsedTime + +GetWindowMouse() - returns actual mouse location in window + +DrawExplicitDecal() - bow-chikka-bow-bow + +DrawPartialDecal(pos, size) - draws a partial decal to specified area + +FillRectDecal() - draws a flat shaded rectangle as a decal + +GradientFillRectDecal() - draws a rectangle, with unique colour corners + +Modified DrawCircle() & FillCircle() - Thanks IanM-Matrix1 (#PR121) + +Gone someway to appeasing pedants + 2.07: +GetPixelSize() - returns user specified pixel size + +GetScreenPixelSize() - returns actual size in monitor pixels + +Pixel Cohesion Mode (flag in Construct()) - disallows arbitrary window scaling + +Working VSYNC in Windows windowed application - now much smoother + +Added string conversion for olc::vectors + +Added comparator operators for olc::vectors + +Added DestroyWindow() on windows platforms for serial PGE launches + +Added GetMousePos() to stop TarriestPython whinging + 2.08: Fix SetScreenSize() aspect ratio pre-calculation + Fix DrawExplicitDecal() - stupid oversight with multiple decals + Disabled olc::Sprite copy constructor + +olc::Sprite Duplicate() - produces a new clone of the sprite + +olc::Sprite Duplicate(pos, size) - produces a new sprite from the region defined + +Unary operators for vectors + +More pedant mollification - Thanks TheLandfill + +ImageLoader modules - user selectable image handling core, gdi+, libpng, stb_image + +Mac Support via GLUT - thanks Mumflr! + 2.09: Fix olc::Renderable Image load error - Thanks MaGetzUb & Zij-IT for finding and moaning about it + Fix file rejection in image loaders when using resource packs + Tidied Compiler defines per platform - Thanks slavka + +Pedant fixes, const correctness in parts + +DecalModes - Normal, Additive, Multiplicative blend modes + +Pixel Operators & Lerping + +Filtered Decals - If you hate pixels, then erase this file + +DrawStringProp(), GetTextSizeProp(), DrawStringPropDecal() - Draws non-monospaced font + 2.10: Fix PixelLerp() - oops my bad, lerped the wrong way :P + Fix "Shader" support for strings - thanks Megarev for crying about it + Fix GetTextSizeProp() - Height was just plain wrong... + +vec2d operator overloads (element wise *=, /=) + +vec2d comparison operators... :| yup... hmmmm... + +vec2d ceil(), floor(), min(), max() functions - surprising how often I do it manually + +DrawExplicitDecal(... uint32_t elements) - complete control over convex polygons and lines + +DrawPolygonDecal() - to keep Bispoo happy, required significant rewrite of EVERYTHING, but hey ho + +Complete rewrite of decal renderer + +OpenGL 3.3 Renderer (also supports Raspberry Pi) + +PGEX Break-In Hooks - with a push from Dandistine + +Wireframe Decal Mode - For debug overlays + 2.11: Made PGEX hooks optional - (provide true to super constructor) + 2.12: Fix for MinGW compiler non-compliance :( - why is its sdk structure different?? why??? + 2.13: +GetFontSprite() - allows access to font data + 2.14: Fix WIN32 Definition reshuffle + Fix DrawPartialDecal() - messed up dimension during renderer experiment, didnt remove junk code, thanks Alexio + Fix? Strange error regarding GDI+ Image Loader not knowing about COM, SDK change? + 2.15: Big Reformat + +WASM Platform (via Emscripten) - Big Thanks to OLC Community - See Platform for details + +Sample Mode for Decals + +Made olc_ConfigureSystem() accessible + +Added OLC_----_CUSTOM_EX for externalised platforms, renderers and image loaders + =Refactored olc::Sprite pixel data store + -Deprecating LoadFromPGESprFile() + -Deprecating SaveToPGESprFile() + Fix Pixel -= operator (thanks Au Lit) + 2.16: FIX Emscripten JS formatting in VS IDE (thanks Moros) + +"Headless" Mode + +DrawLineDecal() + +Mouse Button Constants + +Move Constructor for olc::Renderable + +Polar/Cartesian conversion for v2d_generic + +DrawRotatedStringDecal()/DrawRotatedStringPropDecal() (thanks Oso-Grande/Sopadeoso (PR #209)) + =Using olc::Renderable for layer surface + +Major Mac and GLUT Update (thanks Mumflr) + 2.17: +Clipping for DrawLine() functions + +Reintroduced sub-pixel decals + +Modified DrawPartialDecal() to quantise and correctly sample from tile atlasses + +olc::Sprite::GetPixel() - Clamp Mode + 2.18: +Option to not "dirty" layers with SetDrawTarget() - Thanks TerasKasi! + =Detection for Mac M1, fix for scroll wheel interrogation - Thanks ruarq! + 2.19: Textual Input(of)course Edition! + =Built in font is now olc::Renderable + +EnablePixelTransfer() - Gate if layer content transfers occur (speedup in decal only apps) + +TextEntryEnable() - Enables/Disables text entry mode + +TextEntryGetString() - Gets the current accumulated string in text entry mode + +TextEntryGetCursor() - Gets the current cursor position in text entry mode + +IsTextEntryEnabled() - Returns true if text entry mode is activated + +OnTextEntryComplete() - Override is called when user presses "ENTER" in text entry mode + +Potential for regional keyboard mappings - needs volunteers to do this + +ConsoleShow() - Opens built in command console + +ConsoleClear() - Clears built in command console output + +ConsoleOut() - Stream strings to command console output + +ConsoleCaptureStdOut() - Capture std::cout by redirecting to built-in console + +OnConsoleCommand() - Override is called when command is entered into built in console + 2.20: +DrawRectDecal() - Keeps OneSketchyGuy quiet + +GetScreenSize() + +olc::Sprite::Size() - returns size of sprite in vector format + 2.21: Emscripten Overhaul - Thanks Moros! + +DrawPolygonDecal() tint overload, can now tint a polygon accounting for vertex colours + +Multiplicative Pixel overload + +v2d_generic clamp() + +v2d_generic lerp() + +GetDroppedFiles() - returns files dropped onto engine window for that frame (MSW only) + +GetDroppedFilesPoint() - returns location of dropped files (MSW only) + +Exposed OpenGL33 Loader interface so the typedefs can be shared with PGEX & user + +Fix OGL33 DecalStructure types - wow, how did that one get missed?? lol + +FillTexturedTriangle() - Software rasterizes a textured, coloured, triangle + +FillTexturedPolygon() - Hijacks DecalStructure for configuration + +olc::vf2d arguments for Sprite::Sample() functions + 2.22: = Fix typo on dragged file buffers for unicode builds + 2.23: Fixed Emscripten host sizing errors - Thanks Moros + Fixed v2d_generic.clamp() function + 2.24: Fix FillTexturedTriangle() to remove const-ref + 2.25: +DrawPolygonDecal(pos, tex, w, col) + + !! Apple Platforms will not see these updates immediately - Sorry, I dont have a mac to test... !! + !! Volunteers willing to help appreciated, though PRs are manually integrated with credit !! +*/ +#pragma endregion + +#pragma region hello_world_example +// O------------------------------------------------------------------------------O +// | Example "Hello World" Program (main.cpp) | +// O------------------------------------------------------------------------------O +/* + +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" + +// Override base class with your custom functionality +class Example : public olc::PixelGameEngine +{ +public: + Example() + { + // Name your application + sAppName = "Example"; + } + +public: + bool OnUserCreate() override + { + // Called once at the start, so create things here + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + // Called once per frame, draws random coloured pixels + for (int x = 0; x < ScreenWidth(); x++) + for (int y = 0; y < ScreenHeight(); y++) + Draw(x, y, olc::Pixel(rand() % 256, rand() % 256, rand() % 256)); + return true; + } +}; + +int main() +{ + Example demo; + if (demo.Construct(256, 240, 4, 4)) + demo.Start(); + return 0; +} + +*/ +#pragma endregion + +#ifndef OLC_PGE_DEF +#define OLC_PGE_DEF + +#pragma region std_includes +// O------------------------------------------------------------------------------O +// | STANDARD INCLUDES | +// O------------------------------------------------------------------------------O +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#pragma endregion + +#define PGE_VER 225 + +// O------------------------------------------------------------------------------O +// | COMPILER CONFIGURATION ODDITIES | +// O------------------------------------------------------------------------------O +#pragma region compiler_config +#define USE_EXPERIMENTAL_FS +#if defined(_WIN32) + #if _MSC_VER >= 1920 && _MSVC_LANG >= 201703L + #undef USE_EXPERIMENTAL_FS + #endif +#endif +#if defined(__linux__) || defined(__MINGW32__) || defined(__EMSCRIPTEN__) || defined(__FreeBSD__) || defined(__APPLE__) + #if __cplusplus >= 201703L + #undef USE_EXPERIMENTAL_FS + #endif +#endif + +#if !defined(OLC_KEYBOARD_UK) + #define OLC_KEYBOARD_UK +#endif + + +#if defined(USE_EXPERIMENTAL_FS) || defined(FORCE_EXPERIMENTAL_FS) + // C++14 + #define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING + #include + namespace _gfs = std::experimental::filesystem::v1; +#else + // C++17 + #include + namespace _gfs = std::filesystem; +#endif + +#if defined(UNICODE) || defined(_UNICODE) + #define olcT(s) L##s +#else + #define olcT(s) s +#endif + +#define UNUSED(x) (void)(x) + +// O------------------------------------------------------------------------------O +// | PLATFORM SELECTION CODE, Thanks slavka! | +// O------------------------------------------------------------------------------O + +#if defined(OLC_PGE_HEADLESS) + #define OLC_PLATFORM_HEADLESS + #define OLC_GFX_HEADLESS + #if !defined(OLC_IMAGE_STB) && !defined(OLC_IMAGE_GDI) && !defined(OLC_IMAGE_LIBPNG) + #define OLC_IMAGE_HEADLESS + #endif +#endif + +// Platform +#if !defined(OLC_PLATFORM_WINAPI) && !defined(OLC_PLATFORM_X11) && !defined(OLC_PLATFORM_GLUT) && !defined(OLC_PLATFORM_EMSCRIPTEN) && !defined(OLC_PLATFORM_HEADLESS) + #if !defined(OLC_PLATFORM_CUSTOM_EX) + #if defined(_WIN32) + #define OLC_PLATFORM_WINAPI + #endif + #if defined(__linux__) || defined(__FreeBSD__) + #define OLC_PLATFORM_X11 + #endif + #if defined(__APPLE__) + #define GL_SILENCE_DEPRECATION + #define OLC_PLATFORM_GLUT + #endif + #if defined(__EMSCRIPTEN__) + #define OLC_PLATFORM_EMSCRIPTEN + #endif + #endif +#endif + +// Start Situation +#if defined(OLC_PLATFORM_GLUT) || defined(OLC_PLATFORM_EMSCRIPTEN) + #define PGE_USE_CUSTOM_START +#endif + + + +// Renderer +#if !defined(OLC_GFX_OPENGL10) && !defined(OLC_GFX_OPENGL33) && !defined(OLC_GFX_DIRECTX10) && !defined(OLC_GFX_HEADLESS) + #if !defined(OLC_GFX_CUSTOM_EX) + #if defined(OLC_PLATFORM_EMSCRIPTEN) + #define OLC_GFX_OPENGL33 + #else + #define OLC_GFX_OPENGL10 + #endif + #endif +#endif + +// Image loader +#if !defined(OLC_IMAGE_STB) && !defined(OLC_IMAGE_GDI) && !defined(OLC_IMAGE_LIBPNG) && !defined(OLC_IMAGE_HEADLESS) + #if !defined(OLC_IMAGE_CUSTOM_EX) + #if defined(_WIN32) + #define OLC_IMAGE_GDI + #endif + #if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__EMSCRIPTEN__) + #define OLC_IMAGE_LIBPNG + #endif + #endif +#endif + + +// O------------------------------------------------------------------------------O +// | PLATFORM-SPECIFIC DEPENDENCIES | +// O------------------------------------------------------------------------------O +#if !defined(OLC_PGE_HEADLESS) +#if defined(OLC_PLATFORM_WINAPI) + #define _WINSOCKAPI_ // Thanks Cornchipss + #if !defined(VC_EXTRALEAN) + #define VC_EXTRALEAN + #endif + #if !defined(NOMINMAX) + #define NOMINMAX + #endif + + // In Code::Blocks + #if !defined(_WIN32_WINNT) + #ifdef HAVE_MSMF + #define _WIN32_WINNT 0x0600 // Windows Vista + #else + #define _WIN32_WINNT 0x0500 // Windows 2000 + #endif + #endif + + #include + #undef _WINSOCKAPI_ +#endif + +#if defined(OLC_PLATFORM_X11) + namespace X11 + { + #include + #include + } +#endif + +#if defined(OLC_PLATFORM_GLUT) + #if defined(__linux__) + #include + #include + #endif + #if defined(__APPLE__) + #include + #include + #include + #endif +#endif +#endif + +#if defined(OLC_PGE_HEADLESS) +#if defined max +#undef max +#endif +#if defined min +#undef min +#endif +#endif +#pragma endregion + +// O------------------------------------------------------------------------------O +// | olcPixelGameEngine INTERFACE DECLARATION | +// O------------------------------------------------------------------------------O +#pragma region pge_declaration +namespace olc +{ + class PixelGameEngine; + class Sprite; + + // Pixel Game Engine Advanced Configuration + constexpr uint8_t nMouseButtons = 5; + constexpr uint8_t nDefaultAlpha = 0xFF; + constexpr uint32_t nDefaultPixel = (nDefaultAlpha << 24); + constexpr uint8_t nTabSizeInSpaces = 4; + constexpr size_t OLC_MAX_VERTS = 128; + enum rcode { FAIL = 0, OK = 1, NO_FILE = -1 }; + + // O------------------------------------------------------------------------------O + // | olc::Pixel - Represents a 32-Bit RGBA colour | + // O------------------------------------------------------------------------------O +#if !defined(OLC_IGNORE_PIXEL) + struct Pixel + { + union + { + uint32_t n = nDefaultPixel; + struct { uint8_t r; uint8_t g; uint8_t b; uint8_t a; }; + }; + + enum Mode { NORMAL, MASK, ALPHA, CUSTOM }; + + Pixel(); + Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = nDefaultAlpha); + Pixel(uint32_t p); + Pixel& operator = (const Pixel& v) = default; + bool operator ==(const Pixel& p) const; + bool operator !=(const Pixel& p) const; + Pixel operator * (const float i) const; + Pixel operator / (const float i) const; + Pixel& operator *=(const float i); + Pixel& operator /=(const float i); + Pixel operator + (const Pixel& p) const; + Pixel operator - (const Pixel& p) const; + Pixel& operator +=(const Pixel& p); + Pixel& operator -=(const Pixel& p); + Pixel operator * (const Pixel& p) const; + Pixel& operator *=(const Pixel& p); + Pixel inv() const; + }; + + Pixel PixelF(float red, float green, float blue, float alpha = 1.0f); + Pixel PixelLerp(const olc::Pixel& p1, const olc::Pixel& p2, float t); + + + // O------------------------------------------------------------------------------O + // | USEFUL CONSTANTS | + // O------------------------------------------------------------------------------O + static const Pixel + GREY(192, 192, 192), DARK_GREY(128, 128, 128), VERY_DARK_GREY(64, 64, 64), + RED(255, 0, 0), DARK_RED(128, 0, 0), VERY_DARK_RED(64, 0, 0), + YELLOW(255, 255, 0), DARK_YELLOW(128, 128, 0), VERY_DARK_YELLOW(64, 64, 0), + GREEN(0, 255, 0), DARK_GREEN(0, 128, 0), VERY_DARK_GREEN(0, 64, 0), + CYAN(0, 255, 255), DARK_CYAN(0, 128, 128), VERY_DARK_CYAN(0, 64, 64), + BLUE(0, 0, 255), DARK_BLUE(0, 0, 128), VERY_DARK_BLUE(0, 0, 64), + MAGENTA(255, 0, 255), DARK_MAGENTA(128, 0, 128), VERY_DARK_MAGENTA(64, 0, 64), + WHITE(255, 255, 255), BLACK(0, 0, 0), BLANK(0, 0, 0, 0); +#endif + // Thanks to scripticuk and others for updating the key maps + // NOTE: The GLUT platform will need updating, open to contributions ;) + enum Key + { + NONE, + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + K0, K1, K2, K3, K4, K5, K6, K7, K8, K9, + F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + UP, DOWN, LEFT, RIGHT, + SPACE, TAB, SHIFT, CTRL, INS, DEL, HOME, END, PGUP, PGDN, + BACK, ESCAPE, RETURN, ENTER, PAUSE, SCROLL, + NP0, NP1, NP2, NP3, NP4, NP5, NP6, NP7, NP8, NP9, + NP_MUL, NP_DIV, NP_ADD, NP_SUB, NP_DECIMAL, PERIOD, + EQUALS, COMMA, MINUS, + OEM_1, OEM_2, OEM_3, OEM_4, OEM_5, OEM_6, OEM_7, OEM_8, + CAPS_LOCK, ENUM_END + }; + + namespace Mouse + { + static constexpr int32_t LEFT = 0; + static constexpr int32_t RIGHT = 1; + static constexpr int32_t MIDDLE = 2; + }; + + // O------------------------------------------------------------------------------O + // | olc::HWButton - Represents the state of a hardware button (mouse/key/joy) | + // O------------------------------------------------------------------------------O + struct HWButton + { + bool bPressed = false; // Set once during the frame the event occurs + bool bReleased = false; // Set once during the frame the event occurs + bool bHeld = false; // Set true for all frames between pressed and released events + }; + + + + + // O------------------------------------------------------------------------------O + // | olc::vX2d - A generic 2D vector type | + // O------------------------------------------------------------------------------O +#if !defined(OLC_IGNORE_VEC2D) + template + struct v2d_generic + { + T x = 0; + T y = 0; + v2d_generic() : x(0), y(0) {} + v2d_generic(T _x, T _y) : x(_x), y(_y) {} + v2d_generic(const v2d_generic& v) : x(v.x), y(v.y) {} + v2d_generic& operator=(const v2d_generic& v) = default; + T mag() const { return T(std::sqrt(x * x + y * y)); } + T mag2() const { return x * x + y * y; } + v2d_generic norm() const { T r = 1 / mag(); return v2d_generic(x * r, y * r); } + v2d_generic perp() const { return v2d_generic(-y, x); } + v2d_generic floor() const { return v2d_generic(std::floor(x), std::floor(y)); } + v2d_generic ceil() const { return v2d_generic(std::ceil(x), std::ceil(y)); } + v2d_generic max(const v2d_generic& v) const { return v2d_generic(std::max(x, v.x), std::max(y, v.y)); } + v2d_generic min(const v2d_generic& v) const { return v2d_generic(std::min(x, v.x), std::min(y, v.y)); } + v2d_generic cart() { return { std::cos(y) * x, std::sin(y) * x }; } + v2d_generic polar() { return { mag(), std::atan2(y, x) }; } + v2d_generic clamp(const v2d_generic& v1, const v2d_generic& v2) const { return this->max(v1).min(v2); } + v2d_generic lerp(const v2d_generic& v1, const double t) { return this->operator*(T(1.0 - t)) + (v1 * T(t)); } + T dot(const v2d_generic& rhs) const { return this->x * rhs.x + this->y * rhs.y; } + T cross(const v2d_generic& rhs) const { return this->x * rhs.y - this->y * rhs.x; } + v2d_generic operator + (const v2d_generic& rhs) const { return v2d_generic(this->x + rhs.x, this->y + rhs.y); } + v2d_generic operator - (const v2d_generic& rhs) const { return v2d_generic(this->x - rhs.x, this->y - rhs.y); } + v2d_generic operator * (const T& rhs) const { return v2d_generic(this->x * rhs, this->y * rhs); } + v2d_generic operator * (const v2d_generic& rhs) const { return v2d_generic(this->x * rhs.x, this->y * rhs.y); } + v2d_generic operator / (const T& rhs) const { return v2d_generic(this->x / rhs, this->y / rhs); } + v2d_generic operator / (const v2d_generic& rhs) const { return v2d_generic(this->x / rhs.x, this->y / rhs.y); } + v2d_generic& operator += (const v2d_generic& rhs) { this->x += rhs.x; this->y += rhs.y; return *this; } + v2d_generic& operator -= (const v2d_generic& rhs) { this->x -= rhs.x; this->y -= rhs.y; return *this; } + v2d_generic& operator *= (const T& rhs) { this->x *= rhs; this->y *= rhs; return *this; } + v2d_generic& operator /= (const T& rhs) { this->x /= rhs; this->y /= rhs; return *this; } + v2d_generic& operator *= (const v2d_generic& rhs) { this->x *= rhs.x; this->y *= rhs.y; return *this; } + v2d_generic& operator /= (const v2d_generic& rhs) { this->x /= rhs.x; this->y /= rhs.y; return *this; } + v2d_generic operator + () const { return { +x, +y }; } + v2d_generic operator - () const { return { -x, -y }; } + bool operator == (const v2d_generic& rhs) const { return (this->x == rhs.x && this->y == rhs.y); } + bool operator != (const v2d_generic& rhs) const { return (this->x != rhs.x || this->y != rhs.y); } + const std::string str() const { return std::string("(") + std::to_string(this->x) + "," + std::to_string(this->y) + ")"; } + friend std::ostream& operator << (std::ostream& os, const v2d_generic& rhs) { os << rhs.str(); return os; } + operator v2d_generic() const { return { static_cast(this->x), static_cast(this->y) }; } + operator v2d_generic() const { return { static_cast(this->x), static_cast(this->y) }; } + operator v2d_generic() const { return { static_cast(this->x), static_cast(this->y) }; } + }; + + // Note: joshinils has some good suggestions here, but they are complicated to implement at this moment, + // however they will appear in a future version of PGE + template inline v2d_generic operator * (const float& lhs, const v2d_generic& rhs) + { return v2d_generic((T)(lhs * (float)rhs.x), (T)(lhs * (float)rhs.y)); } + template inline v2d_generic operator * (const double& lhs, const v2d_generic& rhs) + { return v2d_generic((T)(lhs * (double)rhs.x), (T)(lhs * (double)rhs.y)); } + template inline v2d_generic operator * (const int& lhs, const v2d_generic& rhs) + { return v2d_generic((T)(lhs * (int)rhs.x), (T)(lhs * (int)rhs.y)); } + template inline v2d_generic operator / (const float& lhs, const v2d_generic& rhs) + { return v2d_generic((T)(lhs / (float)rhs.x), (T)(lhs / (float)rhs.y)); } + template inline v2d_generic operator / (const double& lhs, const v2d_generic& rhs) + { return v2d_generic((T)(lhs / (double)rhs.x), (T)(lhs / (double)rhs.y)); } + template inline v2d_generic operator / (const int& lhs, const v2d_generic& rhs) + { return v2d_generic((T)(lhs / (int)rhs.x), (T)(lhs / (int)rhs.y)); } + + // To stop dandistine crying... + template inline bool operator < (const v2d_generic& lhs, const v2d_generic& rhs) + { return lhs.y < rhs.y || (lhs.y == rhs.y && lhs.x < rhs.x); } + template inline bool operator > (const v2d_generic& lhs, const v2d_generic& rhs) + { return lhs.y > rhs.y || (lhs.y == rhs.y && lhs.x > rhs.x); } + + typedef v2d_generic vi2d; + typedef v2d_generic vu2d; + typedef v2d_generic vf2d; + typedef v2d_generic vd2d; +#endif + + + + + + + // O------------------------------------------------------------------------------O + // | olc::ResourcePack - A virtual scrambled filesystem to pack your assets into | + // O------------------------------------------------------------------------------O + struct ResourceBuffer : public std::streambuf + { + ResourceBuffer(std::ifstream& ifs, uint32_t offset, uint32_t size); + std::vector vMemory; + }; + + class ResourcePack : public std::streambuf + { + public: + ResourcePack(); + ~ResourcePack(); + bool AddFile(const std::string& sFile); + bool LoadPack(const std::string& sFile, const std::string& sKey); + bool SavePack(const std::string& sFile, const std::string& sKey); + ResourceBuffer GetFileBuffer(const std::string& sFile); + bool Loaded(); + private: + struct sResourceFile { uint32_t nSize; uint32_t nOffset; }; + std::map mapFiles; + std::ifstream baseFile; + std::vector scramble(const std::vector& data, const std::string& key); + std::string makeposix(const std::string& path); + }; + + + class ImageLoader + { + public: + ImageLoader() = default; + virtual ~ImageLoader() = default; + virtual olc::rcode LoadImageResource(olc::Sprite* spr, const std::string& sImageFile, olc::ResourcePack* pack) = 0; + virtual olc::rcode SaveImageResource(olc::Sprite* spr, const std::string& sImageFile) = 0; + }; + + + // O------------------------------------------------------------------------------O + // | olc::Sprite - An image represented by a 2D array of olc::Pixel | + // O------------------------------------------------------------------------------O + class Sprite + { + public: + Sprite(); + Sprite(const std::string& sImageFile, olc::ResourcePack* pack = nullptr); + Sprite(int32_t w, int32_t h); + Sprite(const olc::Sprite&) = delete; + ~Sprite(); + + public: + olc::rcode LoadFromFile(const std::string& sImageFile, olc::ResourcePack* pack = nullptr); + + public: + int32_t width = 0; + int32_t height = 0; + enum Mode { NORMAL, PERIODIC, CLAMP }; + enum Flip { NONE = 0, HORIZ = 1, VERT = 2 }; + + public: + void SetSampleMode(olc::Sprite::Mode mode = olc::Sprite::Mode::NORMAL); + Pixel GetPixel(int32_t x, int32_t y) const; + bool SetPixel(int32_t x, int32_t y, Pixel p); + Pixel GetPixel(const olc::vi2d& a) const; + bool SetPixel(const olc::vi2d& a, Pixel p); + Pixel Sample(float x, float y) const; + Pixel Sample(const olc::vf2d& uv) const; + Pixel SampleBL(float u, float v) const; + Pixel SampleBL(const olc::vf2d& uv) const; + Pixel* GetData(); + olc::Sprite* Duplicate(); + olc::Sprite* Duplicate(const olc::vi2d& vPos, const olc::vi2d& vSize); + olc::vi2d Size() const; + std::vector pColData; + Mode modeSample = Mode::NORMAL; + + static std::unique_ptr loader; + }; + + // O------------------------------------------------------------------------------O + // | olc::Decal - A GPU resident storage of an olc::Sprite | + // O------------------------------------------------------------------------------O + class Decal + { + public: + Decal(olc::Sprite* spr, bool filter = false, bool clamp = true); + Decal(const uint32_t nExistingTextureResource, olc::Sprite* spr); + virtual ~Decal(); + void Update(); + void UpdateSprite(); + + public: // But dont touch + int32_t id = -1; + olc::Sprite* sprite = nullptr; + olc::vf2d vUVScale = { 1.0f, 1.0f }; + }; + + enum class DecalMode + { + NORMAL, + ADDITIVE, + MULTIPLICATIVE, + STENCIL, + ILLUMINATE, + WIREFRAME, + }; + + enum class DecalStructure + { + LINE, + FAN, + STRIP, + LIST + }; + + // O------------------------------------------------------------------------------O + // | olc::Renderable - Convenience class to keep a sprite and decal together | + // O------------------------------------------------------------------------------O + class Renderable + { + public: + Renderable() = default; + Renderable(Renderable&& r) : pSprite(std::move(r.pSprite)), pDecal(std::move(r.pDecal)) {} + Renderable(const Renderable&) = delete; + olc::rcode Load(const std::string& sFile, ResourcePack* pack = nullptr, bool filter = false, bool clamp = true); + void Create(uint32_t width, uint32_t height, bool filter = false, bool clamp = true); + olc::Decal* Decal() const; + olc::Sprite* Sprite() const; + + private: + std::unique_ptr pSprite = nullptr; + std::unique_ptr pDecal = nullptr; + }; + + + // O------------------------------------------------------------------------------O + // | Auxilliary components internal to engine | + // O------------------------------------------------------------------------------O + + struct DecalInstance + { + olc::Decal* decal = nullptr; + std::vector pos; + std::vector uv; + std::vector w; + std::vector z; + std::vector tint; + olc::DecalMode mode = olc::DecalMode::NORMAL; + olc::DecalStructure structure = olc::DecalStructure::FAN; + uint32_t points = 0; + bool depth = false; + }; + + struct LayerDesc + { + olc::vf2d vOffset = { 0, 0 }; + olc::vf2d vScale = { 1, 1 }; + bool bShow = false; + bool bUpdate = false; + olc::Renderable pDrawTarget; + uint32_t nResID = 0; + std::vector vecDecalInstance; + olc::Pixel tint = olc::WHITE; + std::function funcHook = nullptr; + }; + + class Renderer + { + public: + virtual ~Renderer() = default; + virtual void PrepareDevice() = 0; + virtual olc::rcode CreateDevice(std::vector params, bool bFullScreen, bool bVSYNC) = 0; + virtual olc::rcode DestroyDevice() = 0; + virtual void DisplayFrame() = 0; + virtual void PrepareDrawing() = 0; + virtual void SetDecalMode(const olc::DecalMode& mode) = 0; + virtual void DrawLayerQuad(const olc::vf2d& offset, const olc::vf2d& scale, const olc::Pixel tint) = 0; + virtual void DrawDecal(const olc::DecalInstance& decal) = 0; + virtual uint32_t CreateTexture(const uint32_t width, const uint32_t height, const bool filtered = false, const bool clamp = true) = 0; + virtual void UpdateTexture(uint32_t id, olc::Sprite* spr) = 0; + virtual void ReadTexture(uint32_t id, olc::Sprite* spr) = 0; + virtual uint32_t DeleteTexture(const uint32_t id) = 0; + virtual void ApplyTexture(uint32_t id) = 0; + virtual void UpdateViewport(const olc::vi2d& pos, const olc::vi2d& size) = 0; + virtual void ClearBuffer(olc::Pixel p, bool bDepth) = 0; + static olc::PixelGameEngine* ptrPGE; + }; + + class Platform + { + public: + virtual ~Platform() = default; + virtual olc::rcode ApplicationStartUp() = 0; + virtual olc::rcode ApplicationCleanUp() = 0; + virtual olc::rcode ThreadStartUp() = 0; + virtual olc::rcode ThreadCleanUp() = 0; + virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) = 0; + virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d& vWindowSize, bool bFullScreen) = 0; + virtual olc::rcode SetWindowTitle(const std::string& s) = 0; + virtual olc::rcode StartSystemEventLoop() = 0; + virtual olc::rcode HandleSystemEvent() = 0; + static olc::PixelGameEngine* ptrPGE; + }; + + class PGEX; + + // The Static Twins (plus one) + static std::unique_ptr renderer; + static std::unique_ptr platform; + static std::map mapKeys; + + // O------------------------------------------------------------------------------O + // | olc::PixelGameEngine - The main BASE class for your application | + // O------------------------------------------------------------------------------O + class PixelGameEngine + { + public: + PixelGameEngine(); + virtual ~PixelGameEngine(); + public: + olc::rcode Construct(int32_t screen_w, int32_t screen_h, int32_t pixel_w, int32_t pixel_h, + bool full_screen = false, bool vsync = false, bool cohesion = false); + olc::rcode Start(); + + public: // User Override Interfaces + // Called once on application startup, use to load your resources + virtual bool OnUserCreate(); + // Called every frame, and provides you with a time per frame value + virtual bool OnUserUpdate(float fElapsedTime); + // Called once on application termination, so you can be one clean coder + virtual bool OnUserDestroy(); + + // Called when a text entry is confirmed with "enter" key + virtual void OnTextEntryComplete(const std::string& sText); + // Called when a console command is executed + virtual bool OnConsoleCommand(const std::string& sCommand); + + + public: // Hardware Interfaces + // Returns true if window is currently in focus + bool IsFocused() const; + // Get the state of a specific keyboard button + HWButton GetKey(Key k) const; + // Get the state of a specific mouse button + HWButton GetMouse(uint32_t b) const; + // Get Mouse X coordinate in "pixel" space + int32_t GetMouseX() const; + // Get Mouse Y coordinate in "pixel" space + int32_t GetMouseY() const; + // Get Mouse Wheel Delta + int32_t GetMouseWheel() const; + // Get the mouse in window space + const olc::vi2d& GetWindowMouse() const; + // Gets the mouse as a vector to keep Tarriest happy + const olc::vi2d& GetMousePos() const; + + static const std::map& GetKeyMap() { return mapKeys; } + + public: // Utility + // Returns the width of the screen in "pixels" + int32_t ScreenWidth() const; + // Returns the height of the screen in "pixels" + int32_t ScreenHeight() const; + // Returns the width of the currently selected drawing target in "pixels" + int32_t GetDrawTargetWidth() const; + // Returns the height of the currently selected drawing target in "pixels" + int32_t GetDrawTargetHeight() const; + // Returns the currently active draw target + olc::Sprite* GetDrawTarget() const; + // Resize the primary screen sprite + void SetScreenSize(int w, int h); + // Specify which Sprite should be the target of drawing functions, use nullptr + // to specify the primary screen + void SetDrawTarget(Sprite* target); + // Gets the current Frames Per Second + uint32_t GetFPS() const; + // Gets last update of elapsed time + float GetElapsedTime() const; + // Gets Actual Window size + const olc::vi2d& GetWindowSize() const; + // Gets pixel scale + const olc::vi2d& GetPixelSize() const; + // Gets actual pixel scale + const olc::vi2d& GetScreenPixelSize() const; + // Gets "screen" size + const olc::vi2d& GetScreenSize() const; + // Gets any files dropped this frame + const std::vector& GetDroppedFiles() const; + const olc::vi2d& GetDroppedFilesPoint() const; + + public: // CONFIGURATION ROUTINES + // Layer targeting functions + void SetDrawTarget(uint8_t layer, bool bDirty = true); + void EnableLayer(uint8_t layer, bool b); + void SetLayerOffset(uint8_t layer, const olc::vf2d& offset); + void SetLayerOffset(uint8_t layer, float x, float y); + void SetLayerScale(uint8_t layer, const olc::vf2d& scale); + void SetLayerScale(uint8_t layer, float x, float y); + void SetLayerTint(uint8_t layer, const olc::Pixel& tint); + void SetLayerCustomRenderFunction(uint8_t layer, std::function f); + + std::vector& GetLayers(); + uint32_t CreateLayer(); + + // Change the pixel mode for different optimisations + // olc::Pixel::NORMAL = No transparency + // olc::Pixel::MASK = Transparent if alpha is < 255 + // olc::Pixel::ALPHA = Full transparency + void SetPixelMode(Pixel::Mode m); + Pixel::Mode GetPixelMode(); + // Use a custom blend function + void SetPixelMode(std::function pixelMode); + // Change the blend factor from between 0.0f to 1.0f; + void SetPixelBlend(float fBlend); + + + + public: // DRAWING ROUTINES + // Draws a single Pixel + virtual bool Draw(int32_t x, int32_t y, Pixel p = olc::WHITE); + bool Draw(const olc::vi2d& pos, Pixel p = olc::WHITE); + // Draws a line from (x1,y1) to (x2,y2) + void DrawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, Pixel p = olc::WHITE, uint32_t pattern = 0xFFFFFFFF); + void DrawLine(const olc::vi2d& pos1, const olc::vi2d& pos2, Pixel p = olc::WHITE, uint32_t pattern = 0xFFFFFFFF); + // Draws a circle located at (x,y) with radius + void DrawCircle(int32_t x, int32_t y, int32_t radius, Pixel p = olc::WHITE, uint8_t mask = 0xFF); + void DrawCircle(const olc::vi2d& pos, int32_t radius, Pixel p = olc::WHITE, uint8_t mask = 0xFF); + // Fills a circle located at (x,y) with radius + void FillCircle(int32_t x, int32_t y, int32_t radius, Pixel p = olc::WHITE); + void FillCircle(const olc::vi2d& pos, int32_t radius, Pixel p = olc::WHITE); + // Draws a rectangle at (x,y) to (x+w,y+h) + void DrawRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p = olc::WHITE); + void DrawRect(const olc::vi2d& pos, const olc::vi2d& size, Pixel p = olc::WHITE); + // Fills a rectangle at (x,y) to (x+w,y+h) + void FillRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p = olc::WHITE); + void FillRect(const olc::vi2d& pos, const olc::vi2d& size, Pixel p = olc::WHITE); + // Draws a triangle between points (x1,y1), (x2,y2) and (x3,y3) + void DrawTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p = olc::WHITE); + void DrawTriangle(const olc::vi2d& pos1, const olc::vi2d& pos2, const olc::vi2d& pos3, Pixel p = olc::WHITE); + // Flat fills a triangle between points (x1,y1), (x2,y2) and (x3,y3) + void FillTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p = olc::WHITE); + void FillTriangle(const olc::vi2d& pos1, const olc::vi2d& pos2, const olc::vi2d& pos3, Pixel p = olc::WHITE); + // Fill a textured and coloured triangle + void FillTexturedTriangle(std::vector vPoints, std::vector vTex, std::vector vColour, olc::Sprite* sprTex); + void FillTexturedPolygon(const std::vector& vPoints, const std::vector& vTex, const std::vector& vColour, olc::Sprite* sprTex, olc::DecalStructure structure = olc::DecalStructure::LIST); + // Draws an entire sprite at location (x,y) + void DrawSprite(int32_t x, int32_t y, Sprite* sprite, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE); + void DrawSprite(const olc::vi2d& pos, Sprite* sprite, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE); + // Draws an area of a sprite at location (x,y), where the + // selected area is (ox,oy) to (ox+w,oy+h) + void DrawPartialSprite(int32_t x, int32_t y, Sprite* sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE); + void DrawPartialSprite(const olc::vi2d& pos, Sprite* sprite, const olc::vi2d& sourcepos, const olc::vi2d& size, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE); + // Draws a single line of text - traditional monospaced + void DrawString(int32_t x, int32_t y, const std::string& sText, Pixel col = olc::WHITE, uint32_t scale = 1); + void DrawString(const olc::vi2d& pos, const std::string& sText, Pixel col = olc::WHITE, uint32_t scale = 1); + olc::vi2d GetTextSize(const std::string& s); + // Draws a single line of text - non-monospaced + void DrawStringProp(int32_t x, int32_t y, const std::string& sText, Pixel col = olc::WHITE, uint32_t scale = 1); + void DrawStringProp(const olc::vi2d& pos, const std::string& sText, Pixel col = olc::WHITE, uint32_t scale = 1); + olc::vi2d GetTextSizeProp(const std::string& s); + + // Decal Quad functions + void SetDecalMode(const olc::DecalMode& mode); + void SetDecalStructure(const olc::DecalStructure& structure); + // Draws a whole decal, with optional scale and tinting + void DrawDecal(const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& scale = { 1.0f,1.0f }, const olc::Pixel& tint = olc::WHITE); + // Draws a region of a decal, with optional scale and tinting + void DrawPartialDecal(const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::vf2d& scale = { 1.0f,1.0f }, const olc::Pixel& tint = olc::WHITE); + void DrawPartialDecal(const olc::vf2d& pos, const olc::vf2d& size, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); + // Draws fully user controlled 4 vertices, pos(pixels), uv(pixels), colours + void DrawExplicitDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::vf2d* uv, const olc::Pixel* col, uint32_t elements = 4); + // Draws a decal with 4 arbitrary points, warping the texture to look "correct" + void DrawWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::Pixel& tint = olc::WHITE); + void DrawWarpedDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::Pixel& tint = olc::WHITE); + void DrawWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::Pixel& tint = olc::WHITE); + // As above, but you can specify a region of a decal source sprite + void DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); + void DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); + void DrawPartialWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); + // Draws a decal rotated to specified angle, wit point of rotation offset + void DrawRotatedDecal(const olc::vf2d& pos, olc::Decal* decal, const float fAngle, const olc::vf2d& center = { 0.0f, 0.0f }, const olc::vf2d& scale = { 1.0f,1.0f }, const olc::Pixel& tint = olc::WHITE); + void DrawPartialRotatedDecal(const olc::vf2d& pos, olc::Decal* decal, const float fAngle, const olc::vf2d& center, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::vf2d& scale = { 1.0f, 1.0f }, const olc::Pixel& tint = olc::WHITE); + // Draws a multiline string as a decal, with tiniting and scaling + void DrawStringDecal(const olc::vf2d& pos, const std::string& sText, const Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }); + void DrawStringPropDecal(const olc::vf2d& pos, const std::string& sText, const Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }); + // Draws a single shaded filled rectangle as a decal + void DrawRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col = olc::WHITE); + void FillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col = olc::WHITE); + // Draws a corner shaded rectangle as a decal + void GradientFillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel colTR); + // Draws an arbitrary convex textured polygon using GPU + void DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const olc::Pixel tint = olc::WHITE); + void DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& depth, const std::vector& uv, const olc::Pixel tint = olc::WHITE); + void DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const std::vector& tint); + void DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const std::vector& colours, const olc::Pixel tint); + void DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& depth, const std::vector& uv, const std::vector& colours, const olc::Pixel tint); + + // Draws a line in Decal Space + void DrawLineDecal(const olc::vf2d& pos1, const olc::vf2d& pos2, Pixel p = olc::WHITE); + void DrawRotatedStringDecal(const olc::vf2d& pos, const std::string& sText, const float fAngle, const olc::vf2d& center = { 0.0f, 0.0f }, const olc::Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }); + void DrawRotatedStringPropDecal(const olc::vf2d& pos, const std::string& sText, const float fAngle, const olc::vf2d& center = { 0.0f, 0.0f }, const olc::Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }); + // Clears entire draw target to Pixel + void Clear(Pixel p); + // Clears the rendering back buffer + void ClearBuffer(Pixel p, bool bDepth = true); + // Returns the font image + olc::Sprite* GetFontSprite(); + + // Clip a line segment to visible area + bool ClipLineToScreen(olc::vi2d& in_p1, olc::vi2d& in_p2); + + // Dont allow PGE to mark layers as dirty, so pixel graphics don't update + void EnablePixelTransfer(const bool bEnable = true); + + // Command Console Routines + void ConsoleShow(const olc::Key &keyExit, bool bSuspendTime = true); + bool IsConsoleShowing() const; + void ConsoleClear(); + std::stringstream& ConsoleOut(); + void ConsoleCaptureStdOut(const bool bCapture); + + // Text Entry Routines + void TextEntryEnable(const bool bEnable, const std::string& sText = ""); + std::string TextEntryGetString() const; + int32_t TextEntryGetCursor() const; + bool IsTextEntryEnabled() const; + + + + private: + void UpdateTextEntry(); + void UpdateConsole(); + + public: + + // Experimental Lightweight 3D Routines ================ +#ifdef OLC_ENABLE_EXPERIMENTAL + // Set Manual View Matrix + void LW3D_View(const std::array& m); + // Set Manual World Matrix + void LW3D_World(const std::array& m); + // Set Manual Projection Matrix + void LW3D_Projection(const std::array& m); + + // Draws a vector of vertices, interprted as individual triangles + void LW3D_DrawTriangles(olc::Decal* decal, const std::vector>& pos, const std::vector& tex, const std::vector& col); + void LW3D_DrawWarpedDecal(olc::Decal* decal, const std::vector>& pos, const olc::Pixel& tint); + + void LW3D_ModelTranslate(const float x, const float y, const float z); + + // Camera convenience functions + void LW3D_SetCameraAtTarget(const float fEyeX, const float fEyeY, const float fEyeZ, + const float fTargetX, const float fTargetY, const float fTargetZ, + const float fUpX = 0.0f, const float fUpY = 1.0f, const float fUpZ = 0.0f); + void LW3D_SetCameraAlongDirection(const float fEyeX, const float fEyeY, const float fEyeZ, + const float fDirX, const float fDirY, const float fDirZ, + const float fUpX = 0.0f, const float fUpY = 1.0f, const float fUpZ = 0.0f); + + // 3D Rendering Flags + void LW3D_EnableDepthTest(const bool bEnableDepth); + void LW3D_EnableBackfaceCulling(const bool bEnableCull); +#endif + public: // Branding + std::string sAppName; + + private: // Inner mysterious workings + olc::Sprite* pDrawTarget = nullptr; + Pixel::Mode nPixelMode = Pixel::NORMAL; + float fBlendFactor = 1.0f; + olc::vi2d vScreenSize = { 256, 240 }; + olc::vf2d vInvScreenSize = { 1.0f / 256.0f, 1.0f / 240.0f }; + olc::vi2d vPixelSize = { 4, 4 }; + olc::vi2d vScreenPixelSize = { 4, 4 }; + olc::vi2d vMousePos = { 0, 0 }; + int32_t nMouseWheelDelta = 0; + olc::vi2d vMousePosCache = { 0, 0 }; + olc::vi2d vMouseWindowPos = { 0, 0 }; + int32_t nMouseWheelDeltaCache = 0; + olc::vi2d vWindowSize = { 0, 0 }; + olc::vi2d vViewPos = { 0, 0 }; + olc::vi2d vViewSize = { 0,0 }; + bool bFullScreen = false; + olc::vf2d vPixel = { 1.0f, 1.0f }; + bool bHasInputFocus = false; + bool bHasMouseFocus = false; + bool bEnableVSYNC = false; + float fFrameTimer = 1.0f; + float fLastElapsed = 0.0f; + int nFrameCount = 0; + bool bSuspendTextureTransfer = false; + Renderable fontRenderable; + std::vector vLayers; + uint8_t nTargetLayer = 0; + uint32_t nLastFPS = 0; + bool bPixelCohesion = false; + DecalMode nDecalMode = DecalMode::NORMAL; + DecalStructure nDecalStructure = DecalStructure::FAN; + std::function funcPixelMode; + std::chrono::time_point m_tp1, m_tp2; + std::vector vFontSpacing; + std::vector vDroppedFiles; + std::vector vDroppedFilesCache; + olc::vi2d vDroppedFilesPoint; + olc::vi2d vDroppedFilesPointCache; + + // Command Console Specific + bool bConsoleShow = false; + bool bConsoleSuspendTime = false; + olc::Key keyConsoleExit = olc::Key::F1; + std::stringstream ssConsoleOutput; + std::streambuf* sbufOldCout = nullptr; + olc::vi2d vConsoleSize; + olc::vi2d vConsoleCursor = { 0,0 }; + olc::vf2d vConsoleCharacterScale = { 1.0f, 2.0f }; + std::vector sConsoleLines; + std::list sCommandHistory; + std::list::iterator sCommandHistoryIt; + + // Text Entry Specific + bool bTextEntryEnable = false; + std::string sTextEntryString = ""; + int32_t nTextEntryCursor = 0; + std::vector> vKeyboardMap; + + + + // State of keyboard + bool pKeyNewState[256] = { 0 }; + bool pKeyOldState[256] = { 0 }; + HWButton pKeyboardState[256] = { 0 }; + + // State of mouse + bool pMouseNewState[nMouseButtons] = { 0 }; + bool pMouseOldState[nMouseButtons] = { 0 }; + HWButton pMouseState[nMouseButtons] = { 0 }; + + // The main engine thread + void EngineThread(); + + + // If anything sets this flag to false, the engine + // "should" shut down gracefully + static std::atomic bAtomActive; + + public: + // "Break In" Functions + void olc_UpdateMouse(int32_t x, int32_t y); + void olc_UpdateMouseWheel(int32_t delta); + void olc_UpdateWindowSize(int32_t x, int32_t y); + void olc_UpdateViewport(); + void olc_ConstructFontSheet(); + void olc_CoreUpdate(); + void olc_PrepareEngine(); + void olc_UpdateMouseState(int32_t button, bool state); + void olc_UpdateKeyState(int32_t key, bool state); + void olc_UpdateMouseFocus(bool state); + void olc_UpdateKeyFocus(bool state); + void olc_Terminate(); + void olc_DropFiles(int32_t x, int32_t y, const std::vector& vFiles); + void olc_Reanimate(); + bool olc_IsRunning(); + + // At the very end of this file, chooses which + // components to compile + virtual void olc_ConfigureSystem(); + + // NOTE: Items Here are to be deprecated, I have left them in for now + // in case you are using them, but they will be removed. + // olc::vf2d vSubPixelOffset = { 0.0f, 0.0f }; + + public: // PGEX Stuff + friend class PGEX; + void pgex_Register(olc::PGEX* pgex); + + private: + std::vector vExtensions; + }; + + + + // O------------------------------------------------------------------------------O + // | PGE EXTENSION BASE CLASS - Permits access to PGE functions from extension | + // O------------------------------------------------------------------------------O + class PGEX + { + friend class olc::PixelGameEngine; + public: + PGEX(bool bHook = false); + + protected: + virtual void OnBeforeUserCreate(); + virtual void OnAfterUserCreate(); + virtual bool OnBeforeUserUpdate(float &fElapsedTime); + virtual void OnAfterUserUpdate(float fElapsedTime); + + protected: + static PixelGameEngine* pge; + }; +} + +#pragma endregion + + +#pragma region opengl33_iface +// In order to facilitate more advanced graphics features, some PGEX +// will rely on shaders. Instead of having each PGEX responsible for +// managing this, for convenience, this interface exists. + +#if defined(OLC_GFX_OPENGL33) + + #if defined(OLC_PLATFORM_WINAPI) + #include + #define CALLSTYLE __stdcall + #endif + + #if defined(__linux__) || defined(__FreeBSD__) + #include + #endif + + #if defined(OLC_PLATFORM_X11) + namespace X11 { + #include + } + #define CALLSTYLE + #endif + + #if defined(__APPLE__) + #define GL_SILENCE_DEPRECATION + #include + #include + #include + #endif + + #if defined(OLC_PLATFORM_EMSCRIPTEN) + #include + #include + #define GL_GLEXT_PROTOTYPES + #include + #include + #define CALLSTYLE + #define GL_CLAMP GL_CLAMP_TO_EDGE + #endif + +namespace olc +{ + typedef char GLchar; + typedef ptrdiff_t GLsizeiptr; + + typedef GLuint CALLSTYLE locCreateShader_t(GLenum type); + typedef GLuint CALLSTYLE locCreateProgram_t(void); + typedef void CALLSTYLE locDeleteShader_t(GLuint shader); + typedef void CALLSTYLE locCompileShader_t(GLuint shader); + typedef void CALLSTYLE locLinkProgram_t(GLuint program); + typedef void CALLSTYLE locDeleteProgram_t(GLuint program); + typedef void CALLSTYLE locAttachShader_t(GLuint program, GLuint shader); + typedef void CALLSTYLE locBindBuffer_t(GLenum target, GLuint buffer); + typedef void CALLSTYLE locBufferData_t(GLenum target, GLsizeiptr size, const void* data, GLenum usage); + typedef void CALLSTYLE locGenBuffers_t(GLsizei n, GLuint* buffers); + typedef void CALLSTYLE locVertexAttribPointer_t(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer); + typedef void CALLSTYLE locEnableVertexAttribArray_t(GLuint index); + typedef void CALLSTYLE locUseProgram_t(GLuint program); + typedef void CALLSTYLE locBindVertexArray_t(GLuint array); + typedef void CALLSTYLE locGenVertexArrays_t(GLsizei n, GLuint* arrays); + typedef void CALLSTYLE locGetShaderInfoLog_t(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog); + typedef GLint CALLSTYLE locGetUniformLocation_t(GLuint program, const GLchar* name); + typedef void CALLSTYLE locUniform1f_t(GLint location, GLfloat v0); + typedef void CALLSTYLE locUniform1i_t(GLint location, GLint v0); + typedef void CALLSTYLE locUniform2fv_t(GLint location, GLsizei count, const GLfloat* value); + typedef void CALLSTYLE locActiveTexture_t(GLenum texture); + typedef void CALLSTYLE locGenFrameBuffers_t(GLsizei n, GLuint* ids); + typedef void CALLSTYLE locBindFrameBuffer_t(GLenum target, GLuint fb); + typedef GLenum CALLSTYLE locCheckFrameBufferStatus_t(GLenum target); + typedef void CALLSTYLE locDeleteFrameBuffers_t(GLsizei n, const GLuint* fbs); + typedef void CALLSTYLE locFrameBufferTexture2D_t(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); + typedef void CALLSTYLE locDrawBuffers_t(GLsizei n, const GLenum* bufs); + typedef void CALLSTYLE locBlendFuncSeparate_t(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); + +#if defined(OLC_PLATFORM_WINAPI) + typedef void __stdcall locSwapInterval_t(GLsizei n); +#endif + +#if defined(OLC_PLATFORM_X11) + typedef int(locSwapInterval_t)(X11::Display* dpy, X11::GLXDrawable drawable, int interval); +#endif + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + typedef void CALLSTYLE locShaderSource_t(GLuint shader, GLsizei count, const GLchar* const* string, const GLint* length); + typedef EGLBoolean(locSwapInterval_t)(EGLDisplay display, EGLint interval); +#else + typedef void CALLSTYLE locShaderSource_t(GLuint shader, GLsizei count, const GLchar** string, const GLint* length); +#endif + +} // olc namespace +#endif // OpenGL33 Definitions +#pragma endregion + + +#endif // OLC_PGE_DEF + + +// O------------------------------------------------------------------------------O +// | START OF OLC_PGE_APPLICATION | +// O------------------------------------------------------------------------------O +#ifdef OLC_PGE_APPLICATION +#undef OLC_PGE_APPLICATION + +// O------------------------------------------------------------------------------O +// | olcPixelGameEngine INTERFACE IMPLEMENTATION (CORE) | +// | Note: The core implementation is platform independent | +// O------------------------------------------------------------------------------O +#pragma region pge_implementation +namespace olc +{ + // O------------------------------------------------------------------------------O + // | olc::Pixel IMPLEMENTATION | + // O------------------------------------------------------------------------------O +#if !defined(OLC_IGNORE_PIXEL) + Pixel::Pixel() + { r = 0; g = 0; b = 0; a = nDefaultAlpha; } + + Pixel::Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) + { n = red | (green << 8) | (blue << 16) | (alpha << 24); } // Thanks jarekpelczar + + Pixel::Pixel(uint32_t p) + { n = p; } + + bool Pixel::operator==(const Pixel& p) const + { return n == p.n; } + + bool Pixel::operator!=(const Pixel& p) const + { return n != p.n; } + + Pixel Pixel::operator * (const float i) const + { + float fR = std::min(255.0f, std::max(0.0f, float(r) * i)); + float fG = std::min(255.0f, std::max(0.0f, float(g) * i)); + float fB = std::min(255.0f, std::max(0.0f, float(b) * i)); + return Pixel(uint8_t(fR), uint8_t(fG), uint8_t(fB), a); + } + + Pixel Pixel::operator / (const float i) const + { + float fR = std::min(255.0f, std::max(0.0f, float(r) / i)); + float fG = std::min(255.0f, std::max(0.0f, float(g) / i)); + float fB = std::min(255.0f, std::max(0.0f, float(b) / i)); + return Pixel(uint8_t(fR), uint8_t(fG), uint8_t(fB), a); + } + + Pixel& Pixel::operator *=(const float i) + { + this->r = uint8_t(std::min(255.0f, std::max(0.0f, float(r) * i))); + this->g = uint8_t(std::min(255.0f, std::max(0.0f, float(g) * i))); + this->b = uint8_t(std::min(255.0f, std::max(0.0f, float(b) * i))); + return *this; + } + + Pixel& Pixel::operator /=(const float i) + { + this->r = uint8_t(std::min(255.0f, std::max(0.0f, float(r) / i))); + this->g = uint8_t(std::min(255.0f, std::max(0.0f, float(g) / i))); + this->b = uint8_t(std::min(255.0f, std::max(0.0f, float(b) / i))); + return *this; + } + + Pixel Pixel::operator + (const Pixel& p) const + { + uint8_t nR = uint8_t(std::min(255, std::max(0, int(r) + int(p.r)))); + uint8_t nG = uint8_t(std::min(255, std::max(0, int(g) + int(p.g)))); + uint8_t nB = uint8_t(std::min(255, std::max(0, int(b) + int(p.b)))); + return Pixel(nR, nG, nB, a); + } + + Pixel Pixel::operator - (const Pixel& p) const + { + uint8_t nR = uint8_t(std::min(255, std::max(0, int(r) - int(p.r)))); + uint8_t nG = uint8_t(std::min(255, std::max(0, int(g) - int(p.g)))); + uint8_t nB = uint8_t(std::min(255, std::max(0, int(b) - int(p.b)))); + return Pixel(nR, nG, nB, a); + } + + Pixel& Pixel::operator += (const Pixel& p) + { + this->r = uint8_t(std::min(255, std::max(0, int(r) + int(p.r)))); + this->g = uint8_t(std::min(255, std::max(0, int(g) + int(p.g)))); + this->b = uint8_t(std::min(255, std::max(0, int(b) + int(p.b)))); + return *this; + } + + Pixel& Pixel::operator -= (const Pixel& p) // Thanks Au Lit + { + this->r = uint8_t(std::min(255, std::max(0, int(r) - int(p.r)))); + this->g = uint8_t(std::min(255, std::max(0, int(g) - int(p.g)))); + this->b = uint8_t(std::min(255, std::max(0, int(b) - int(p.b)))); + return *this; + } + + Pixel Pixel::operator * (const Pixel& p) const + { + uint8_t nR = uint8_t(std::min(255.0f, std::max(0.0f, float(r) * float(p.r) / 255.0f))); + uint8_t nG = uint8_t(std::min(255.0f, std::max(0.0f, float(g) * float(p.g) / 255.0f))); + uint8_t nB = uint8_t(std::min(255.0f, std::max(0.0f, float(b) * float(p.b) / 255.0f))); + uint8_t nA = uint8_t(std::min(255.0f, std::max(0.0f, float(a) * float(p.a) / 255.0f))); + return Pixel(nR, nG, nB, nA); + } + + Pixel& Pixel::operator *=(const Pixel& p) + { + this->r = uint8_t(std::min(255.0f, std::max(0.0f, float(r) * float(p.r) / 255.0f))); + this->g = uint8_t(std::min(255.0f, std::max(0.0f, float(g) * float(p.g) / 255.0f))); + this->b = uint8_t(std::min(255.0f, std::max(0.0f, float(b) * float(p.b) / 255.0f))); + this->a = uint8_t(std::min(255.0f, std::max(0.0f, float(a) * float(p.a) / 255.0f))); + return *this; + } + + Pixel Pixel::inv() const + { + uint8_t nR = uint8_t(std::min(255, std::max(0, 255 - int(r)))); + uint8_t nG = uint8_t(std::min(255, std::max(0, 255 - int(g)))); + uint8_t nB = uint8_t(std::min(255, std::max(0, 255 - int(b)))); + return Pixel(nR, nG, nB, a); + } + + Pixel PixelF(float red, float green, float blue, float alpha) + { return Pixel(uint8_t(red * 255.0f), uint8_t(green * 255.0f), uint8_t(blue * 255.0f), uint8_t(alpha * 255.0f)); } + + Pixel PixelLerp(const olc::Pixel& p1, const olc::Pixel& p2, float t) + { return (p2 * t) + p1 * (1.0f - t); } +#endif + // O------------------------------------------------------------------------------O + // | olc::Sprite IMPLEMENTATION | + // O------------------------------------------------------------------------------O + Sprite::Sprite() + { width = 0; height = 0; } + + Sprite::Sprite(const std::string& sImageFile, olc::ResourcePack* pack) + { LoadFromFile(sImageFile, pack); } + + Sprite::Sprite(int32_t w, int32_t h) + { + width = w; height = h; + pColData.resize(width * height); + pColData.resize(width * height, nDefaultPixel); + } + + Sprite::~Sprite() + { pColData.clear(); } + + void Sprite::SetSampleMode(olc::Sprite::Mode mode) + { modeSample = mode; } + + Pixel Sprite::GetPixel(const olc::vi2d& a) const + { return GetPixel(a.x, a.y); } + + bool Sprite::SetPixel(const olc::vi2d& a, Pixel p) + { return SetPixel(a.x, a.y, p); } + + Pixel Sprite::GetPixel(int32_t x, int32_t y) const + { + if (modeSample == olc::Sprite::Mode::NORMAL) + { + if (x >= 0 && x < width && y >= 0 && y < height) + return pColData[y * width + x]; + else + return Pixel(0, 0, 0, 0); + } + else + { + if (modeSample == olc::Sprite::Mode::PERIODIC) + return pColData[abs(y % height) * width + abs(x % width)]; + else + return pColData[std::max(0, std::min(y, height-1)) * width + std::max(0, std::min(x, width-1))]; + } + } + + bool Sprite::SetPixel(int32_t x, int32_t y, Pixel p) + { + if (x >= 0 && x < width && y >= 0 && y < height) + { + pColData[y * width + x] = p; + return true; + } + else + return false; + } + + Pixel Sprite::Sample(float x, float y) const + { + int32_t sx = std::min((int32_t)((x * (float)width)), width - 1); + int32_t sy = std::min((int32_t)((y * (float)height)), height - 1); + return GetPixel(sx, sy); + } + + Pixel Sprite::Sample(const olc::vf2d& uv) const + { + return Sample(uv.x, uv.y); + } + + Pixel Sprite::SampleBL(float u, float v) const + { + u = u * width - 0.5f; + v = v * height - 0.5f; + int x = (int)floor(u); // cast to int rounds toward zero, not downward + int y = (int)floor(v); // Thanks @joshinils + float u_ratio = u - x; + float v_ratio = v - y; + float u_opposite = 1 - u_ratio; + float v_opposite = 1 - v_ratio; + + olc::Pixel p1 = GetPixel(std::max(x, 0), std::max(y, 0)); + olc::Pixel p2 = GetPixel(std::min(x + 1, (int)width - 1), std::max(y, 0)); + olc::Pixel p3 = GetPixel(std::max(x, 0), std::min(y + 1, (int)height - 1)); + olc::Pixel p4 = GetPixel(std::min(x + 1, (int)width - 1), std::min(y + 1, (int)height - 1)); + + return olc::Pixel( + (uint8_t)((p1.r * u_opposite + p2.r * u_ratio) * v_opposite + (p3.r * u_opposite + p4.r * u_ratio) * v_ratio), + (uint8_t)((p1.g * u_opposite + p2.g * u_ratio) * v_opposite + (p3.g * u_opposite + p4.g * u_ratio) * v_ratio), + (uint8_t)((p1.b * u_opposite + p2.b * u_ratio) * v_opposite + (p3.b * u_opposite + p4.b * u_ratio) * v_ratio)); + } + + Pixel Sprite::SampleBL(const olc::vf2d& uv) const + { + return SampleBL(uv.x, uv.y); + } + + Pixel* Sprite::GetData() + { return pColData.data(); } + + + olc::rcode Sprite::LoadFromFile(const std::string& sImageFile, olc::ResourcePack* pack) + { + UNUSED(pack); + return loader->LoadImageResource(this, sImageFile, pack); + } + + olc::Sprite* Sprite::Duplicate() + { + olc::Sprite* spr = new olc::Sprite(width, height); + std::memcpy(spr->GetData(), GetData(), width * height * sizeof(olc::Pixel)); + spr->modeSample = modeSample; + return spr; + } + + olc::Sprite* Sprite::Duplicate(const olc::vi2d& vPos, const olc::vi2d& vSize) + { + olc::Sprite* spr = new olc::Sprite(vSize.x, vSize.y); + for (int y = 0; y < vSize.y; y++) + for (int x = 0; x < vSize.x; x++) + spr->SetPixel(x, y, GetPixel(vPos.x + x, vPos.y + y)); + return spr; + } + + olc::vi2d olc::Sprite::Size() const + { + return { width, height }; + } + + // O------------------------------------------------------------------------------O + // | olc::Decal IMPLEMENTATION | + // O------------------------------------------------------------------------------O + Decal::Decal(olc::Sprite* spr, bool filter, bool clamp) + { + id = -1; + if (spr == nullptr) return; + sprite = spr; + id = renderer->CreateTexture(sprite->width, sprite->height, filter, clamp); + Update(); + } + + Decal::Decal(const uint32_t nExistingTextureResource, olc::Sprite* spr) + { + if (spr == nullptr) return; + id = nExistingTextureResource; + } + + void Decal::Update() + { + if (sprite == nullptr) return; + vUVScale = { 1.0f / float(sprite->width), 1.0f / float(sprite->height) }; + renderer->ApplyTexture(id); + renderer->UpdateTexture(id, sprite); + } + + void Decal::UpdateSprite() + { + if (sprite == nullptr) return; + renderer->ApplyTexture(id); + renderer->ReadTexture(id, sprite); + } + + Decal::~Decal() + { + if (id != -1) + { + renderer->DeleteTexture(id); + id = -1; + } + } + + void Renderable::Create(uint32_t width, uint32_t height, bool filter, bool clamp) + { + pSprite = std::make_unique(width, height); + pDecal = std::make_unique(pSprite.get(), filter, clamp); + } + + olc::rcode Renderable::Load(const std::string& sFile, ResourcePack* pack, bool filter, bool clamp) + { + pSprite = std::make_unique(); + if (pSprite->LoadFromFile(sFile, pack) == olc::rcode::OK) + { + pDecal = std::make_unique(pSprite.get(), filter, clamp); + return olc::rcode::OK; + } + else + { + pSprite.release(); + pSprite = nullptr; + return olc::rcode::NO_FILE; + } + } + + olc::Decal* Renderable::Decal() const + { return pDecal.get(); } + + olc::Sprite* Renderable::Sprite() const + { return pSprite.get(); } + + // O------------------------------------------------------------------------------O + // | olc::ResourcePack IMPLEMENTATION | + // O------------------------------------------------------------------------------O + + + //============================================================= + // Resource Packs - Allows you to store files in one large + // scrambled file - Thanks MaGetzUb for debugging a null char in std::stringstream bug + ResourceBuffer::ResourceBuffer(std::ifstream& ifs, uint32_t offset, uint32_t size) + { + vMemory.resize(size); + ifs.seekg(offset); ifs.read(vMemory.data(), vMemory.size()); + setg(vMemory.data(), vMemory.data(), vMemory.data() + size); + } + + ResourcePack::ResourcePack() { } + ResourcePack::~ResourcePack() { baseFile.close(); } + + bool ResourcePack::AddFile(const std::string& sFile) + { + const std::string file = makeposix(sFile); + + if (_gfs::exists(file)) + { + sResourceFile e; + e.nSize = (uint32_t)_gfs::file_size(file); + e.nOffset = 0; // Unknown at this stage + mapFiles[file] = e; + return true; + } + return false; + } + + bool ResourcePack::LoadPack(const std::string& sFile, const std::string& sKey) + { + // Open the resource file + baseFile.open(sFile, std::ifstream::binary); + if (!baseFile.is_open()) return false; + + // 1) Read Scrambled index + uint32_t nIndexSize = 0; + baseFile.read((char*)&nIndexSize, sizeof(uint32_t)); + + std::vector buffer(nIndexSize); + for (uint32_t j = 0; j < nIndexSize; j++) + buffer[j] = baseFile.get(); + + std::vector decoded = scramble(buffer, sKey); + size_t pos = 0; + auto read = [&decoded, &pos](char* dst, size_t size) { + memcpy((void*)dst, (const void*)(decoded.data() + pos), size); + pos += size; + }; + + auto get = [&read]() -> int { char c; read(&c, 1); return c; }; + + // 2) Read Map + uint32_t nMapEntries = 0; + read((char*)&nMapEntries, sizeof(uint32_t)); + for (uint32_t i = 0; i < nMapEntries; i++) + { + uint32_t nFilePathSize = 0; + read((char*)&nFilePathSize, sizeof(uint32_t)); + + std::string sFileName(nFilePathSize, ' '); + for (uint32_t j = 0; j < nFilePathSize; j++) + sFileName[j] = get(); + + sResourceFile e; + read((char*)&e.nSize, sizeof(uint32_t)); + read((char*)&e.nOffset, sizeof(uint32_t)); + mapFiles[sFileName] = e; + } + + // Don't close base file! we will provide a stream + // pointer when the file is requested + return true; + } + + bool ResourcePack::SavePack(const std::string& sFile, const std::string& sKey) + { + // Create/Overwrite the resource file + std::ofstream ofs(sFile, std::ofstream::binary); + if (!ofs.is_open()) return false; + + // Iterate through map + uint32_t nIndexSize = 0; // Unknown for now + ofs.write((char*)&nIndexSize, sizeof(uint32_t)); + uint32_t nMapSize = uint32_t(mapFiles.size()); + ofs.write((char*)&nMapSize, sizeof(uint32_t)); + for (auto& e : mapFiles) + { + // Write the path of the file + size_t nPathSize = e.first.size(); + ofs.write((char*)&nPathSize, sizeof(uint32_t)); + ofs.write(e.first.c_str(), nPathSize); + + // Write the file entry properties + ofs.write((char*)&e.second.nSize, sizeof(uint32_t)); + ofs.write((char*)&e.second.nOffset, sizeof(uint32_t)); + } + + // 2) Write the individual Data + std::streampos offset = ofs.tellp(); + nIndexSize = (uint32_t)offset; + for (auto& e : mapFiles) + { + // Store beginning of file offset within resource pack file + e.second.nOffset = (uint32_t)offset; + + // Load the file to be added + std::vector vBuffer(e.second.nSize); + std::ifstream i(e.first, std::ifstream::binary); + i.read((char*)vBuffer.data(), e.second.nSize); + i.close(); + + // Write the loaded file into resource pack file + ofs.write((char*)vBuffer.data(), e.second.nSize); + offset += e.second.nSize; + } + + // 3) Scramble Index + std::vector stream; + auto write = [&stream](const char* data, size_t size) { + size_t sizeNow = stream.size(); + stream.resize(sizeNow + size); + memcpy(stream.data() + sizeNow, data, size); + }; + + // Iterate through map + write((char*)&nMapSize, sizeof(uint32_t)); + for (auto& e : mapFiles) + { + // Write the path of the file + size_t nPathSize = e.first.size(); + write((char*)&nPathSize, sizeof(uint32_t)); + write(e.first.c_str(), nPathSize); + + // Write the file entry properties + write((char*)&e.second.nSize, sizeof(uint32_t)); + write((char*)&e.second.nOffset, sizeof(uint32_t)); + } + std::vector sIndexString = scramble(stream, sKey); + uint32_t nIndexStringLen = uint32_t(sIndexString.size()); + // 4) Rewrite Map (it has been updated with offsets now) + // at start of file + ofs.seekp(0, std::ios::beg); + ofs.write((char*)&nIndexStringLen, sizeof(uint32_t)); + ofs.write(sIndexString.data(), nIndexStringLen); + ofs.close(); + return true; + } + + ResourceBuffer ResourcePack::GetFileBuffer(const std::string& sFile) + { return ResourceBuffer(baseFile, mapFiles[sFile].nOffset, mapFiles[sFile].nSize); } + + bool ResourcePack::Loaded() + { return baseFile.is_open(); } + + std::vector ResourcePack::scramble(const std::vector& data, const std::string& key) + { + if (key.empty()) return data; + std::vector o; + size_t c = 0; + for (auto s : data) o.push_back(s ^ key[(c++) % key.size()]); + return o; + }; + + std::string ResourcePack::makeposix(const std::string& path) + { + std::string o; + for (auto s : path) o += std::string(1, s == '\\' ? '/' : s); + return o; + }; + + // O------------------------------------------------------------------------------O + // | olc::PixelGameEngine IMPLEMENTATION | + // O------------------------------------------------------------------------------O + PixelGameEngine::PixelGameEngine() + { + sAppName = "Undefined"; + olc::PGEX::pge = this; + + // Bring in relevant Platform & Rendering systems depending + // on compiler parameters + olc_ConfigureSystem(); + } + + PixelGameEngine::~PixelGameEngine() + {} + + + olc::rcode PixelGameEngine::Construct(int32_t screen_w, int32_t screen_h, int32_t pixel_w, int32_t pixel_h, bool full_screen, bool vsync, bool cohesion) + { + bPixelCohesion = cohesion; + vScreenSize = { screen_w, screen_h }; + vInvScreenSize = { 1.0f / float(screen_w), 1.0f / float(screen_h) }; + vPixelSize = { pixel_w, pixel_h }; + vWindowSize = vScreenSize * vPixelSize; + bFullScreen = full_screen; + bEnableVSYNC = vsync; + vPixel = 2.0f / vScreenSize; + + if (vPixelSize.x <= 0 || vPixelSize.y <= 0 || vScreenSize.x <= 0 || vScreenSize.y <= 0) + return olc::FAIL; + return olc::OK; + } + + + void PixelGameEngine::SetScreenSize(int w, int h) + { + vScreenSize = { w, h }; + vInvScreenSize = { 1.0f / float(w), 1.0f / float(h) }; + for (auto& layer : vLayers) + { + layer.pDrawTarget.Create(vScreenSize.x, vScreenSize.y); + layer.bUpdate = true; + } + SetDrawTarget(nullptr); + renderer->ClearBuffer(olc::BLACK, true); + renderer->DisplayFrame(); + renderer->ClearBuffer(olc::BLACK, true); + renderer->UpdateViewport(vViewPos, vViewSize); + } + +#if !defined(PGE_USE_CUSTOM_START) + olc::rcode PixelGameEngine::Start() + { + if (platform->ApplicationStartUp() != olc::OK) return olc::FAIL; + + // Construct the window + if (platform->CreateWindowPane({ 30,30 }, vWindowSize, bFullScreen) != olc::OK) return olc::FAIL; + olc_UpdateWindowSize(vWindowSize.x, vWindowSize.y); + + // Start the thread + bAtomActive = true; + std::thread t = std::thread(&PixelGameEngine::EngineThread, this); + + // Some implementations may form an event loop here + platform->StartSystemEventLoop(); + + // Wait for thread to be exited + t.join(); + + if (platform->ApplicationCleanUp() != olc::OK) return olc::FAIL; + + return olc::OK; + } +#endif + + void PixelGameEngine::SetDrawTarget(Sprite* target) + { + if (target) + { + pDrawTarget = target; + } + else + { + nTargetLayer = 0; + pDrawTarget = vLayers[0].pDrawTarget.Sprite(); + } + } + + void PixelGameEngine::SetDrawTarget(uint8_t layer, bool bDirty) + { + if (layer < vLayers.size()) + { + pDrawTarget = vLayers[layer].pDrawTarget.Sprite(); + vLayers[layer].bUpdate = bDirty; + nTargetLayer = layer; + } + } + + void PixelGameEngine::EnableLayer(uint8_t layer, bool b) + { if (layer < vLayers.size()) vLayers[layer].bShow = b; } + + void PixelGameEngine::SetLayerOffset(uint8_t layer, const olc::vf2d& offset) + { SetLayerOffset(layer, offset.x, offset.y); } + + void PixelGameEngine::SetLayerOffset(uint8_t layer, float x, float y) + { if (layer < vLayers.size()) vLayers[layer].vOffset = { x, y }; } + + void PixelGameEngine::SetLayerScale(uint8_t layer, const olc::vf2d& scale) + { SetLayerScale(layer, scale.x, scale.y); } + + void PixelGameEngine::SetLayerScale(uint8_t layer, float x, float y) + { if (layer < vLayers.size()) vLayers[layer].vScale = { x, y }; } + + void PixelGameEngine::SetLayerTint(uint8_t layer, const olc::Pixel& tint) + { if (layer < vLayers.size()) vLayers[layer].tint = tint; } + + void PixelGameEngine::SetLayerCustomRenderFunction(uint8_t layer, std::function f) + { if (layer < vLayers.size()) vLayers[layer].funcHook = f; } + + std::vector& PixelGameEngine::GetLayers() + { return vLayers; } + + uint32_t PixelGameEngine::CreateLayer() + { + LayerDesc ld; + ld.pDrawTarget.Create(vScreenSize.x, vScreenSize.y); + vLayers.push_back(std::move(ld)); + return uint32_t(vLayers.size()) - 1; + } + + Sprite* PixelGameEngine::GetDrawTarget() const + { return pDrawTarget; } + + int32_t PixelGameEngine::GetDrawTargetWidth() const + { + if (pDrawTarget) + return pDrawTarget->width; + else + return 0; + } + + int32_t PixelGameEngine::GetDrawTargetHeight() const + { + if (pDrawTarget) + return pDrawTarget->height; + else + return 0; + } + + uint32_t PixelGameEngine::GetFPS() const + { return nLastFPS; } + + bool PixelGameEngine::IsFocused() const + { return bHasInputFocus; } + + HWButton PixelGameEngine::GetKey(Key k) const + { return pKeyboardState[k]; } + + HWButton PixelGameEngine::GetMouse(uint32_t b) const + { return pMouseState[b]; } + + int32_t PixelGameEngine::GetMouseX() const + { return vMousePos.x; } + + int32_t PixelGameEngine::GetMouseY() const + { return vMousePos.y; } + + const olc::vi2d& PixelGameEngine::GetMousePos() const + { return vMousePos; } + + int32_t PixelGameEngine::GetMouseWheel() const + { return nMouseWheelDelta; } + + int32_t PixelGameEngine::ScreenWidth() const + { return vScreenSize.x; } + + int32_t PixelGameEngine::ScreenHeight() const + { return vScreenSize.y; } + + float PixelGameEngine::GetElapsedTime() const + { return fLastElapsed; } + + const olc::vi2d& PixelGameEngine::GetWindowSize() const + { return vWindowSize; } + + const olc::vi2d& PixelGameEngine::GetPixelSize() const + { return vPixelSize; } + + const olc::vi2d& PixelGameEngine::GetScreenPixelSize() const + { return vScreenPixelSize; } + + const olc::vi2d& PixelGameEngine::GetScreenSize() const + { return vScreenSize; } + + const olc::vi2d& PixelGameEngine::GetWindowMouse() const + { return vMouseWindowPos; } + + bool PixelGameEngine::Draw(const olc::vi2d& pos, Pixel p) + { return Draw(pos.x, pos.y, p); } + + // This is it, the critical function that plots a pixel + bool PixelGameEngine::Draw(int32_t x, int32_t y, Pixel p) + { + if (!pDrawTarget) return false; + + if (nPixelMode == Pixel::NORMAL) + { + return pDrawTarget->SetPixel(x, y, p); + } + + if (nPixelMode == Pixel::MASK) + { + if (p.a == 255) + return pDrawTarget->SetPixel(x, y, p); + } + + if (nPixelMode == Pixel::ALPHA) + { + Pixel d = pDrawTarget->GetPixel(x, y); + float a = (float)(p.a / 255.0f) * fBlendFactor; + float c = 1.0f - a; + float r = a * (float)p.r + c * (float)d.r; + float g = a * (float)p.g + c * (float)d.g; + float b = a * (float)p.b + c * (float)d.b; + return pDrawTarget->SetPixel(x, y, Pixel((uint8_t)r, (uint8_t)g, (uint8_t)b/*, (uint8_t)(p.a * fBlendFactor)*/)); + } + + if (nPixelMode == Pixel::CUSTOM) + { + return pDrawTarget->SetPixel(x, y, funcPixelMode(x, y, p, pDrawTarget->GetPixel(x, y))); + } + + return false; + } + + + void PixelGameEngine::DrawLine(const olc::vi2d& pos1, const olc::vi2d& pos2, Pixel p, uint32_t pattern) + { DrawLine(pos1.x, pos1.y, pos2.x, pos2.y, p, pattern); } + + void PixelGameEngine::DrawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, Pixel p, uint32_t pattern) + { + int x, y, dx, dy, dx1, dy1, px, py, xe, ye, i; + dx = x2 - x1; dy = y2 - y1; + + auto rol = [&](void) { pattern = (pattern << 1) | (pattern >> 31); return pattern & 1; }; + + olc::vi2d p1(x1, y1), p2(x2, y2); + if (!ClipLineToScreen(p1, p2)) + return; + x1 = p1.x; y1 = p1.y; + x2 = p2.x; y2 = p2.y; + + // straight lines idea by gurkanctn + if (dx == 0) // Line is vertical + { + if (y2 < y1) std::swap(y1, y2); + for (y = y1; y <= y2; y++) if (rol()) Draw(x1, y, p); + return; + } + + if (dy == 0) // Line is horizontal + { + if (x2 < x1) std::swap(x1, x2); + for (x = x1; x <= x2; x++) if (rol()) Draw(x, y1, p); + return; + } + + // Line is Funk-aye + dx1 = abs(dx); dy1 = abs(dy); + px = 2 * dy1 - dx1; py = 2 * dx1 - dy1; + if (dy1 <= dx1) + { + if (dx >= 0) + { + x = x1; y = y1; xe = x2; + } + else + { + x = x2; y = y2; xe = x1; + } + + if (rol()) Draw(x, y, p); + + for (i = 0; x < xe; i++) + { + x = x + 1; + if (px < 0) + px = px + 2 * dy1; + else + { + if ((dx < 0 && dy < 0) || (dx > 0 && dy > 0)) y = y + 1; else y = y - 1; + px = px + 2 * (dy1 - dx1); + } + if (rol()) Draw(x, y, p); + } + } + else + { + if (dy >= 0) + { + x = x1; y = y1; ye = y2; + } + else + { + x = x2; y = y2; ye = y1; + } + + if (rol()) Draw(x, y, p); + + for (i = 0; y < ye; i++) + { + y = y + 1; + if (py <= 0) + py = py + 2 * dx1; + else + { + if ((dx < 0 && dy < 0) || (dx > 0 && dy > 0)) x = x + 1; else x = x - 1; + py = py + 2 * (dx1 - dy1); + } + if (rol()) Draw(x, y, p); + } + } + } + + void PixelGameEngine::DrawCircle(const olc::vi2d& pos, int32_t radius, Pixel p, uint8_t mask) + { DrawCircle(pos.x, pos.y, radius, p, mask); } + + void PixelGameEngine::DrawCircle(int32_t x, int32_t y, int32_t radius, Pixel p, uint8_t mask) + { // Thanks to IanM-Matrix1 #PR121 + if (radius < 0 || x < -radius || y < -radius || x - GetDrawTargetWidth() > radius || y - GetDrawTargetHeight() > radius) + return; + + if (radius > 0) + { + int x0 = 0; + int y0 = radius; + int d = 3 - 2 * radius; + + while (y0 >= x0) // only formulate 1/8 of circle + { + // Draw even octants + if (mask & 0x01) Draw(x + x0, y - y0, p);// Q6 - upper right right + if (mask & 0x04) Draw(x + y0, y + x0, p);// Q4 - lower lower right + if (mask & 0x10) Draw(x - x0, y + y0, p);// Q2 - lower left left + if (mask & 0x40) Draw(x - y0, y - x0, p);// Q0 - upper upper left + if (x0 != 0 && x0 != y0) + { + if (mask & 0x02) Draw(x + y0, y - x0, p);// Q7 - upper upper right + if (mask & 0x08) Draw(x + x0, y + y0, p);// Q5 - lower right right + if (mask & 0x20) Draw(x - y0, y + x0, p);// Q3 - lower lower left + if (mask & 0x80) Draw(x - x0, y - y0, p);// Q1 - upper left left + } + + if (d < 0) + d += 4 * x0++ + 6; + else + d += 4 * (x0++ - y0--) + 10; + } + } + else + Draw(x, y, p); + } + + void PixelGameEngine::FillCircle(const olc::vi2d& pos, int32_t radius, Pixel p) + { FillCircle(pos.x, pos.y, radius, p); } + + void PixelGameEngine::FillCircle(int32_t x, int32_t y, int32_t radius, Pixel p) + { // Thanks to IanM-Matrix1 #PR121 + if (radius < 0 || x < -radius || y < -radius || x - GetDrawTargetWidth() > radius || y - GetDrawTargetHeight() > radius) + return; + + if (radius > 0) + { + int x0 = 0; + int y0 = radius; + int d = 3 - 2 * radius; + + auto drawline = [&](int sx, int ex, int y) + { + for (int x = sx; x <= ex; x++) + Draw(x, y, p); + }; + + while (y0 >= x0) + { + drawline(x - y0, x + y0, y - x0); + if (x0 > 0) drawline(x - y0, x + y0, y + x0); + + if (d < 0) + d += 4 * x0++ + 6; + else + { + if (x0 != y0) + { + drawline(x - x0, x + x0, y - y0); + drawline(x - x0, x + x0, y + y0); + } + d += 4 * (x0++ - y0--) + 10; + } + } + } + else + Draw(x, y, p); + } + + void PixelGameEngine::DrawRect(const olc::vi2d& pos, const olc::vi2d& size, Pixel p) + { DrawRect(pos.x, pos.y, size.x, size.y, p); } + + void PixelGameEngine::DrawRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p) + { + DrawLine(x, y, x + w, y, p); + DrawLine(x + w, y, x + w, y + h, p); + DrawLine(x + w, y + h, x, y + h, p); + DrawLine(x, y + h, x, y, p); + } + + void PixelGameEngine::Clear(Pixel p) + { + int pixels = GetDrawTargetWidth() * GetDrawTargetHeight(); + Pixel* m = GetDrawTarget()->GetData(); + for (int i = 0; i < pixels; i++) m[i] = p; + } + + void PixelGameEngine::ClearBuffer(Pixel p, bool bDepth) + { renderer->ClearBuffer(p, bDepth); } + + olc::Sprite* PixelGameEngine::GetFontSprite() + { return fontRenderable.Sprite(); } + + bool PixelGameEngine::ClipLineToScreen(olc::vi2d& in_p1, olc::vi2d& in_p2) + { + // https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm + static constexpr int SEG_I = 0b0000, SEG_L = 0b0001, SEG_R = 0b0010, SEG_B = 0b0100, SEG_T = 0b1000; + auto Segment = [&vScreenSize = vScreenSize](const olc::vi2d& v) + { + int i = SEG_I; + if (v.x < 0) i |= SEG_L; else if (v.x > vScreenSize.x) i |= SEG_R; + if (v.y < 0) i |= SEG_B; else if (v.y > vScreenSize.y) i |= SEG_T; + return i; + }; + + int s1 = Segment(in_p1), s2 = Segment(in_p2); + + while (true) + { + if (!(s1 | s2)) return true; + else if (s1 & s2) return false; + else + { + int s3 = s2 > s1 ? s2 : s1; + olc::vi2d n; + if (s3 & SEG_T) { n.x = in_p1.x + (in_p2.x - in_p1.x) * (vScreenSize.y - in_p1.y) / (in_p2.y - in_p1.y); n.y = vScreenSize.y; } + else if (s3 & SEG_B) { n.x = in_p1.x + (in_p2.x - in_p1.x) * (0 - in_p1.y) / (in_p2.y - in_p1.y); n.y = 0; } + else if (s3 & SEG_R) { n.x = vScreenSize.x; n.y = in_p1.y + (in_p2.y - in_p1.y) * (vScreenSize.x - in_p1.x) / (in_p2.x - in_p1.x); } + else if (s3 & SEG_L) { n.x = 0; n.y = in_p1.y + (in_p2.y - in_p1.y) * (0 - in_p1.x) / (in_p2.x - in_p1.x); } + if (s3 == s1) { in_p1 = n; s1 = Segment(in_p1); } + else { in_p2 = n; s2 = Segment(in_p2); } + } + } + return true; + } + + void PixelGameEngine::EnablePixelTransfer(const bool bEnable) + { + bSuspendTextureTransfer = !bEnable; + } + + + void PixelGameEngine::FillRect(const olc::vi2d& pos, const olc::vi2d& size, Pixel p) + { FillRect(pos.x, pos.y, size.x, size.y, p); } + + void PixelGameEngine::FillRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p) + { + int32_t x2 = x + w; + int32_t y2 = y + h; + + if (x < 0) x = 0; + if (x >= (int32_t)GetDrawTargetWidth()) x = (int32_t)GetDrawTargetWidth(); + if (y < 0) y = 0; + if (y >= (int32_t)GetDrawTargetHeight()) y = (int32_t)GetDrawTargetHeight(); + + if (x2 < 0) x2 = 0; + if (x2 >= (int32_t)GetDrawTargetWidth()) x2 = (int32_t)GetDrawTargetWidth(); + if (y2 < 0) y2 = 0; + if (y2 >= (int32_t)GetDrawTargetHeight()) y2 = (int32_t)GetDrawTargetHeight(); + + for (int i = x; i < x2; i++) + for (int j = y; j < y2; j++) + Draw(i, j, p); + } + + void PixelGameEngine::DrawTriangle(const olc::vi2d& pos1, const olc::vi2d& pos2, const olc::vi2d& pos3, Pixel p) + { DrawTriangle(pos1.x, pos1.y, pos2.x, pos2.y, pos3.x, pos3.y, p); } + + void PixelGameEngine::DrawTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p) + { + DrawLine(x1, y1, x2, y2, p); + DrawLine(x2, y2, x3, y3, p); + DrawLine(x3, y3, x1, y1, p); + } + + void PixelGameEngine::FillTriangle(const olc::vi2d& pos1, const olc::vi2d& pos2, const olc::vi2d& pos3, Pixel p) + { FillTriangle(pos1.x, pos1.y, pos2.x, pos2.y, pos3.x, pos3.y, p); } + + // https://www.avrfreaks.net/sites/default/files/triangles.c + void PixelGameEngine::FillTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p) + { + auto drawline = [&](int sx, int ex, int ny) { for (int i = sx; i <= ex; i++) Draw(i, ny, p); }; + + int t1x, t2x, y, minx, maxx, t1xp, t2xp; + bool changed1 = false; + bool changed2 = false; + int signx1, signx2, dx1, dy1, dx2, dy2; + int e1, e2; + // Sort vertices + if (y1 > y2) { std::swap(y1, y2); std::swap(x1, x2); } + if (y1 > y3) { std::swap(y1, y3); std::swap(x1, x3); } + if (y2 > y3) { std::swap(y2, y3); std::swap(x2, x3); } + + t1x = t2x = x1; y = y1; // Starting points + dx1 = (int)(x2 - x1); + if (dx1 < 0) { dx1 = -dx1; signx1 = -1; } + else signx1 = 1; + dy1 = (int)(y2 - y1); + + dx2 = (int)(x3 - x1); + if (dx2 < 0) { dx2 = -dx2; signx2 = -1; } + else signx2 = 1; + dy2 = (int)(y3 - y1); + + if (dy1 > dx1) { std::swap(dx1, dy1); changed1 = true; } + if (dy2 > dx2) { std::swap(dy2, dx2); changed2 = true; } + + e2 = (int)(dx2 >> 1); + // Flat top, just process the second half + if (y1 == y2) goto next; + e1 = (int)(dx1 >> 1); + + for (int i = 0; i < dx1;) { + t1xp = 0; t2xp = 0; + if (t1x < t2x) { minx = t1x; maxx = t2x; } + else { minx = t2x; maxx = t1x; } + // process first line until y value is about to change + while (i < dx1) { + i++; + e1 += dy1; + while (e1 >= dx1) { + e1 -= dx1; + if (changed1) t1xp = signx1;//t1x += signx1; + else goto next1; + } + if (changed1) break; + else t1x += signx1; + } + // Move line + next1: + // process second line until y value is about to change + while (1) { + e2 += dy2; + while (e2 >= dx2) { + e2 -= dx2; + if (changed2) t2xp = signx2;//t2x += signx2; + else goto next2; + } + if (changed2) break; + else t2x += signx2; + } + next2: + if (minx > t1x) minx = t1x; + if (minx > t2x) minx = t2x; + if (maxx < t1x) maxx = t1x; + if (maxx < t2x) maxx = t2x; + drawline(minx, maxx, y); // Draw line from min to max points found on the y + // Now increase y + if (!changed1) t1x += signx1; + t1x += t1xp; + if (!changed2) t2x += signx2; + t2x += t2xp; + y += 1; + if (y == y2) break; + } + next: + // Second half + dx1 = (int)(x3 - x2); if (dx1 < 0) { dx1 = -dx1; signx1 = -1; } + else signx1 = 1; + dy1 = (int)(y3 - y2); + t1x = x2; + + if (dy1 > dx1) { // swap values + std::swap(dy1, dx1); + changed1 = true; + } + else changed1 = false; + + e1 = (int)(dx1 >> 1); + + for (int i = 0; i <= dx1; i++) { + t1xp = 0; t2xp = 0; + if (t1x < t2x) { minx = t1x; maxx = t2x; } + else { minx = t2x; maxx = t1x; } + // process first line until y value is about to change + while (i < dx1) { + e1 += dy1; + while (e1 >= dx1) { + e1 -= dx1; + if (changed1) { t1xp = signx1; break; }//t1x += signx1; + else goto next3; + } + if (changed1) break; + else t1x += signx1; + if (i < dx1) i++; + } + next3: + // process second line until y value is about to change + while (t2x != x3) { + e2 += dy2; + while (e2 >= dx2) { + e2 -= dx2; + if (changed2) t2xp = signx2; + else goto next4; + } + if (changed2) break; + else t2x += signx2; + } + next4: + + if (minx > t1x) minx = t1x; + if (minx > t2x) minx = t2x; + if (maxx < t1x) maxx = t1x; + if (maxx < t2x) maxx = t2x; + drawline(minx, maxx, y); + if (!changed1) t1x += signx1; + t1x += t1xp; + if (!changed2) t2x += signx2; + t2x += t2xp; + y += 1; + if (y > y3) return; + } + } + + void PixelGameEngine::FillTexturedTriangle(std::vector vPoints, std::vector vTex, std::vector vColour, olc::Sprite* sprTex) + { + olc::vi2d p1 = vPoints[0]; + olc::vi2d p2 = vPoints[1]; + olc::vi2d p3 = vPoints[2]; + + if (p2.y < p1.y){std::swap(p1.y, p2.y); std::swap(p1.x, p2.x); std::swap(vTex[0].x, vTex[1].x); std::swap(vTex[0].y, vTex[1].y); std::swap(vColour[0], vColour[1]);} + if (p3.y < p1.y){std::swap(p1.y, p3.y); std::swap(p1.x, p3.x); std::swap(vTex[0].x, vTex[2].x); std::swap(vTex[0].y, vTex[2].y); std::swap(vColour[0], vColour[2]);} + if (p3.y < p2.y){std::swap(p2.y, p3.y); std::swap(p2.x, p3.x); std::swap(vTex[1].x, vTex[2].x); std::swap(vTex[1].y, vTex[2].y); std::swap(vColour[1], vColour[2]);} + + olc::vi2d dPos1 = p2 - p1; + olc::vf2d dTex1 = vTex[1] - vTex[0]; + int dcr1 = vColour[1].r - vColour[0].r; + int dcg1 = vColour[1].g - vColour[0].g; + int dcb1 = vColour[1].b - vColour[0].b; + int dca1 = vColour[1].a - vColour[0].a; + + olc::vi2d dPos2 = p3 - p1; + olc::vf2d dTex2 = vTex[2] - vTex[0]; + int dcr2 = vColour[2].r - vColour[0].r; + int dcg2 = vColour[2].g - vColour[0].g; + int dcb2 = vColour[2].b - vColour[0].b; + int dca2 = vColour[2].a - vColour[0].a; + + float dax_step = 0, dbx_step = 0, dcr1_step = 0, dcr2_step = 0, dcg1_step = 0, dcg2_step = 0, dcb1_step = 0, dcb2_step = 0, dca1_step = 0, dca2_step = 0; + olc::vf2d vTex1Step, vTex2Step; + + if (dPos1.y) + { + dax_step = dPos1.x / (float)abs(dPos1.y); + vTex1Step = dTex1 / (float)abs(dPos1.y); + dcr1_step = dcr1 / (float)abs(dPos1.y); + dcg1_step = dcg1 / (float)abs(dPos1.y); + dcb1_step = dcb1 / (float)abs(dPos1.y); + dca1_step = dca1 / (float)abs(dPos1.y); + } + + if (dPos2.y) + { + dbx_step = dPos2.x / (float)abs(dPos2.y); + vTex2Step = dTex2 / (float)abs(dPos2.y); + dcr2_step = dcr2 / (float)abs(dPos2.y); + dcg2_step = dcg2 / (float)abs(dPos2.y); + dcb2_step = dcb2 / (float)abs(dPos2.y); + dca2_step = dca2 / (float)abs(dPos2.y); + } + + olc::vi2d vStart; + olc::vi2d vEnd; + int vStartIdx; + + for (int pass = 0; pass < 2; pass++) + { + if (pass == 0) + { + vStart = p1; vEnd = p2; vStartIdx = 0; + } + else + { + dPos1 = p3 - p2; + dTex1 = vTex[2] - vTex[1]; + dcr1 = vColour[2].r - vColour[1].r; + dcg1 = vColour[2].g - vColour[1].g; + dcb1 = vColour[2].b - vColour[1].b; + dca1 = vColour[2].a - vColour[1].a; + dcr1_step = 0; dcg1_step = 0; dcb1_step = 0; dca1_step = 0; + + if (dPos2.y) dbx_step = dPos2.x / (float)abs(dPos2.y); + if (dPos1.y) + { + dax_step = dPos1.x / (float)abs(dPos1.y); + vTex1Step = dTex1 / (float)abs(dPos1.y); + dcr1_step = dcr1 / (float)abs(dPos1.y); + dcg1_step = dcg1 / (float)abs(dPos1.y); + dcb1_step = dcb1 / (float)abs(dPos1.y); + dca1_step = dca1 / (float)abs(dPos1.y); + } + + vStart = p2; vEnd = p3; vStartIdx = 1; + } + + if (dPos1.y) + { + for (int i = vStart.y; i <= vEnd.y; i++) + { + int ax = int(vStart.x + (float)(i - vStart.y) * dax_step); + int bx = int(p1.x + (float)(i - p1.y) * dbx_step); + + olc::vf2d tex_s(vTex[vStartIdx].x + (float)(i - vStart.y) * vTex1Step.x, vTex[vStartIdx].y + (float)(i - vStart.y) * vTex1Step.y); + olc::vf2d tex_e(vTex[0].x + (float)(i - p1.y) * vTex2Step.x, vTex[0].y + (float)(i - p1.y) * vTex2Step.y); + + olc::Pixel col_s(vColour[vStartIdx].r + uint8_t((float)(i - vStart.y) * dcr1_step), vColour[vStartIdx].g + uint8_t((float)(i - vStart.y) * dcg1_step), + vColour[vStartIdx].b + uint8_t((float)(i - vStart.y) * dcb1_step), vColour[vStartIdx].a + uint8_t((float)(i - vStart.y) * dca1_step)); + + olc::Pixel col_e(vColour[0].r + uint8_t((float)(i - p1.y) * dcr2_step), vColour[0].g + uint8_t((float)(i - p1.y) * dcg2_step), + vColour[0].b + uint8_t((float)(i - p1.y) * dcb2_step), vColour[0].a + uint8_t((float)(i - p1.y) * dca2_step)); + + if (ax > bx) { std::swap(ax, bx); std::swap(tex_s, tex_e); std::swap(col_s, col_e); } + + float tstep = 1.0f / ((float)(bx - ax)); + float t = 0.0f; + + for (int j = ax; j < bx; j++) + { + olc::Pixel pixel = PixelLerp(col_s, col_e, t); + if (sprTex != nullptr) pixel *= sprTex->Sample(tex_s.lerp(tex_e, t)); + Draw(j, i, pixel); + t += tstep; + } + } + } + } + } + + void PixelGameEngine::FillTexturedPolygon(const std::vector& vPoints, const std::vector& vTex, const std::vector& vColour, olc::Sprite* sprTex, olc::DecalStructure structure) + { + if (structure == olc::DecalStructure::LINE) + { + return; // Meaningless, so do nothing + } + + if (vPoints.size() < 3 || vTex.size() < 3 || vColour.size() < 3) + return; + + if (structure == olc::DecalStructure::LIST) + { + for (int tri = 0; tri < vPoints.size() / 3; tri++) + { + std::vector vP = { vPoints[tri * 3 + 0], vPoints[tri * 3 + 1], vPoints[tri * 3 + 2] }; + std::vector vT = { vTex[tri * 3 + 0], vTex[tri * 3 + 1], vTex[tri * 3 + 2] }; + std::vector vC = { vColour[tri * 3 + 0], vColour[tri * 3 + 1], vColour[tri * 3 + 2] }; + FillTexturedTriangle(vP, vT, vC, sprTex); + } + return; + } + + if (structure == olc::DecalStructure::STRIP) + { + for (int tri = 2; tri < vPoints.size(); tri++) + { + std::vector vP = { vPoints[tri - 2], vPoints[tri-1], vPoints[tri] }; + std::vector vT = { vTex[tri - 2], vTex[tri - 1], vTex[tri] }; + std::vector vC = { vColour[tri - 2], vColour[tri - 1], vColour[tri] }; + FillTexturedTriangle(vP, vT, vC, sprTex); + } + return; + } + + if (structure == olc::DecalStructure::FAN) + { + for (int tri = 2; tri < vPoints.size(); tri++) + { + std::vector vP = { vPoints[0], vPoints[tri - 1], vPoints[tri] }; + std::vector vT = { vTex[0], vTex[tri - 1], vTex[tri] }; + std::vector vC = { vColour[0], vColour[tri - 1], vColour[tri] }; + FillTexturedTriangle(vP, vT, vC, sprTex); + } + return; + } + } + + + void PixelGameEngine::DrawSprite(const olc::vi2d& pos, Sprite* sprite, uint32_t scale, uint8_t flip) + { DrawSprite(pos.x, pos.y, sprite, scale, flip); } + + void PixelGameEngine::DrawSprite(int32_t x, int32_t y, Sprite* sprite, uint32_t scale, uint8_t flip) + { + if (sprite == nullptr) + return; + + int32_t fxs = 0, fxm = 1, fx = 0; + int32_t fys = 0, fym = 1, fy = 0; + if (flip & olc::Sprite::Flip::HORIZ) { fxs = sprite->width - 1; fxm = -1; } + if (flip & olc::Sprite::Flip::VERT) { fys = sprite->height - 1; fym = -1; } + + if (scale > 1) + { + fx = fxs; + for (int32_t i = 0; i < sprite->width; i++, fx += fxm) + { + fy = fys; + for (int32_t j = 0; j < sprite->height; j++, fy += fym) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + (i * scale) + is, y + (j * scale) + js, sprite->GetPixel(fx, fy)); + } + } + else + { + fx = fxs; + for (int32_t i = 0; i < sprite->width; i++, fx += fxm) + { + fy = fys; + for (int32_t j = 0; j < sprite->height; j++, fy += fym) + Draw(x + i, y + j, sprite->GetPixel(fx, fy)); + } + } + } + + void PixelGameEngine::DrawPartialSprite(const olc::vi2d& pos, Sprite* sprite, const olc::vi2d& sourcepos, const olc::vi2d& size, uint32_t scale, uint8_t flip) + { DrawPartialSprite(pos.x, pos.y, sprite, sourcepos.x, sourcepos.y, size.x, size.y, scale, flip); } + + void PixelGameEngine::DrawPartialSprite(int32_t x, int32_t y, Sprite* sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, uint32_t scale, uint8_t flip) + { + if (sprite == nullptr) + return; + + int32_t fxs = 0, fxm = 1, fx = 0; + int32_t fys = 0, fym = 1, fy = 0; + if (flip & olc::Sprite::Flip::HORIZ) { fxs = w - 1; fxm = -1; } + if (flip & olc::Sprite::Flip::VERT) { fys = h - 1; fym = -1; } + + if (scale > 1) + { + fx = fxs; + for (int32_t i = 0; i < w; i++, fx += fxm) + { + fy = fys; + for (int32_t j = 0; j < h; j++, fy += fym) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + (i * scale) + is, y + (j * scale) + js, sprite->GetPixel(fx + ox, fy + oy)); + } + } + else + { + fx = fxs; + for (int32_t i = 0; i < w; i++, fx += fxm) + { + fy = fys; + for (int32_t j = 0; j < h; j++, fy += fym) + Draw(x + i, y + j, sprite->GetPixel(fx + ox, fy + oy)); + } + } + } + + void PixelGameEngine::SetDecalMode(const olc::DecalMode& mode) + { nDecalMode = mode; } + + void PixelGameEngine::SetDecalStructure(const olc::DecalStructure& structure) + { nDecalStructure = structure; } + + void PixelGameEngine::DrawPartialDecal(const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::vf2d& scale, const olc::Pixel& tint) + { + olc::vf2d vScreenSpacePos = + { + (pos.x * vInvScreenSize.x) * 2.0f - 1.0f, + -((pos.y * vInvScreenSize.y) * 2.0f - 1.0f) + }; + + + olc::vf2d vScreenSpaceDim = + { + ((pos.x + source_size.x * scale.x) * vInvScreenSize.x) * 2.0f - 1.0f, + -(((pos.y + source_size.y * scale.y) * vInvScreenSize.y) * 2.0f - 1.0f) + }; + + olc::vf2d vWindow = olc::vf2d(vViewSize); + olc::vf2d vQuantisedPos = ((vScreenSpacePos * vWindow) + olc::vf2d(0.5f, 0.5f)).floor() / vWindow; + olc::vf2d vQuantisedDim = ((vScreenSpaceDim * vWindow) + olc::vf2d(0.5f, -0.5f)).ceil() / vWindow; + + DecalInstance di; + di.points = 4; + di.decal = decal; + di.tint = { tint, tint, tint, tint }; + di.pos = { { vQuantisedPos.x, vQuantisedPos.y }, { vQuantisedPos.x, vQuantisedDim.y }, { vQuantisedDim.x, vQuantisedDim.y }, { vQuantisedDim.x, vQuantisedPos.y } }; + olc::vf2d uvtl = (source_pos + olc::vf2d(0.0001f, 0.0001f)) * decal->vUVScale; + olc::vf2d uvbr = (source_pos + source_size - olc::vf2d(0.0001f, 0.0001f)) * decal->vUVScale; + di.uv = { { uvtl.x, uvtl.y }, { uvtl.x, uvbr.y }, { uvbr.x, uvbr.y }, { uvbr.x, uvtl.y } }; + di.w = { 1,1,1,1 }; + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawPartialDecal(const olc::vf2d& pos, const olc::vf2d& size, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint) + { + olc::vf2d vScreenSpacePos = + { + (pos.x * vInvScreenSize.x) * 2.0f - 1.0f, + ((pos.y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f + }; + + olc::vf2d vScreenSpaceDim = + { + vScreenSpacePos.x + (2.0f * size.x * vInvScreenSize.x), + vScreenSpacePos.y - (2.0f * size.y * vInvScreenSize.y) + }; + + DecalInstance di; + di.points = 4; + di.decal = decal; + di.tint = { tint, tint, tint, tint }; + di.pos = { { vScreenSpacePos.x, vScreenSpacePos.y }, { vScreenSpacePos.x, vScreenSpaceDim.y }, { vScreenSpaceDim.x, vScreenSpaceDim.y }, { vScreenSpaceDim.x, vScreenSpacePos.y } }; + olc::vf2d uvtl = (source_pos) * decal->vUVScale; + olc::vf2d uvbr = uvtl + ((source_size) * decal->vUVScale); + di.uv = { { uvtl.x, uvtl.y }, { uvtl.x, uvbr.y }, { uvbr.x, uvbr.y }, { uvbr.x, uvtl.y } }; + di.w = { 1,1,1,1 }; + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + + void PixelGameEngine::DrawDecal(const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& scale, const olc::Pixel& tint) + { + olc::vf2d vScreenSpacePos = + { + (pos.x * vInvScreenSize.x) * 2.0f - 1.0f, + ((pos.y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f + }; + + olc::vf2d vScreenSpaceDim = + { + vScreenSpacePos.x + (2.0f * (float(decal->sprite->width) * vInvScreenSize.x)) * scale.x, + vScreenSpacePos.y - (2.0f * (float(decal->sprite->height) * vInvScreenSize.y)) * scale.y + }; + + DecalInstance di; + di.decal = decal; + di.points = 4; + di.tint = { tint, tint, tint, tint }; + di.pos = { { vScreenSpacePos.x, vScreenSpacePos.y }, { vScreenSpacePos.x, vScreenSpaceDim.y }, { vScreenSpaceDim.x, vScreenSpaceDim.y }, { vScreenSpaceDim.x, vScreenSpacePos.y } }; + di.uv = { { 0.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 0.0f} }; + di.w = { 1, 1, 1, 1 }; + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawExplicitDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::vf2d* uv, const olc::Pixel* col, uint32_t elements) + { + DecalInstance di; + di.decal = decal; + di.pos.resize(elements); + di.uv.resize(elements); + di.w.resize(elements); + di.tint.resize(elements); + di.points = elements; + for (uint32_t i = 0; i < elements; i++) + { + di.pos[i] = { (pos[i].x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos[i].y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + di.uv[i] = uv[i]; + di.tint[i] = col[i]; + di.w[i] = 1.0f; + } + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const olc::Pixel tint) + { + DecalInstance di; + di.decal = decal; + di.points = uint32_t(pos.size()); + di.pos.resize(di.points); + di.uv.resize(di.points); + di.w.resize(di.points); + di.tint.resize(di.points); + for (uint32_t i = 0; i < di.points; i++) + { + di.pos[i] = { (pos[i].x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos[i].y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + di.uv[i] = uv[i]; + di.tint[i] = tint; + di.w[i] = 1.0f; + } + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const std::vector &tint) + { + DecalInstance di; + di.decal = decal; + di.points = uint32_t(pos.size()); + di.pos.resize(di.points); + di.uv.resize(di.points); + di.w.resize(di.points); + di.tint.resize(di.points); + for (uint32_t i = 0; i < di.points; i++) + { + di.pos[i] = { (pos[i].x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos[i].y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + di.uv[i] = uv[i]; + di.tint[i] = tint[i]; + di.w[i] = 1.0f; + } + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const std::vector& colours, const olc::Pixel tint) + { + std::vector newColours(colours.size(), olc::WHITE); + std::transform(colours.begin(), colours.end(), newColours.begin(), + [&tint](const olc::Pixel pin) { return pin * tint; }); + DrawPolygonDecal(decal, pos, uv, newColours); + } + + + void PixelGameEngine::DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& depth, const std::vector& uv, const olc::Pixel tint) + { + DecalInstance di; + di.decal = decal; + di.points = uint32_t(pos.size()); + di.pos.resize(di.points); + di.uv.resize(di.points); + di.w.resize(di.points); + di.tint.resize(di.points); + for (uint32_t i = 0; i < di.points; i++) + { + di.pos[i] = { (pos[i].x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos[i].y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + di.uv[i] = uv[i]; + di.tint[i] = tint; + di.w[i] = depth[i]; + } + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& depth, const std::vector& uv, const std::vector& colours, const olc::Pixel tint) + { + DecalInstance di; + di.decal = decal; + di.points = uint32_t(pos.size()); + di.pos.resize(di.points); + di.uv.resize(di.points); + di.w.resize(di.points); + di.tint.resize(di.points); + for (uint32_t i = 0; i < di.points; i++) + { + di.pos[i] = { (pos[i].x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos[i].y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + di.uv[i] = uv[i]; + di.tint[i] = colours[i] * tint; + di.w[i] = depth[i]; + } + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + +#ifdef OLC_ENABLE_EXPERIMENTAL + // Lightweight 3D + void PixelGameEngine::LW3D_DrawTriangles(olc::Decal* decal, const std::vector>& pos, const std::vector& tex, const std::vector& col) + { + DecalInstance di; + di.decal = decal; + di.points = uint32_t(pos.size()); + di.pos.resize(di.points); + di.uv.resize(di.points); + di.w.resize(di.points); + di.z.resize(di.points); + di.tint.resize(di.points); + for (uint32_t i = 0; i < di.points; i++) + { + di.pos[i] = { pos[i][0], pos[i][1] }; + di.w[i] = pos[i][2]; + di.z[i] = pos[i][2]; + di.uv[i] = tex[i]; + di.tint[i] = col[i]; + } + di.mode = nDecalMode; + di.structure = DecalStructure::LIST; + di.depth = true; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::LW3D_DrawWarpedDecal(olc::Decal* decal, const std::vector>& pos, const olc::Pixel& tint) + { + // Thanks Nathan Reed, a brilliant article explaining whats going on here + // http://www.reedbeta.com/blog/quadrilateral-interpolation-part-1/ + DecalInstance di; + di.points = 4; + di.decal = decal; + di.tint = { tint, tint, tint, tint }; + di.w = { 1, 1, 1, 1 }; + di.z = { 1, 1, 1, 1 }; + di.pos.resize(4); + di.uv = { { 0.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 0.0f} }; + olc::vf2d center; + float rd = ((pos[2][0] - pos[0][0]) * (pos[3][1] - pos[1][1]) - (pos[3][0] - pos[1][0]) * (pos[2][1] - pos[0][1])); + if (rd != 0) + { + rd = 1.0f / rd; + float rn = ((pos[3][0] - pos[1][0]) * (pos[0][1] - pos[1][1]) - (pos[3][1] - pos[1][1]) * (pos[0][0] - pos[1][0])) * rd; + float sn = ((pos[2][0] - pos[0][0]) * (pos[0][1] - pos[1][1]) - (pos[2][1] - pos[0][1]) * (pos[0][0] - pos[1][0])) * rd; + if (!(rn < 0.f || rn > 1.f || sn < 0.f || sn > 1.f)) + { + center.x = pos[0][0] + rn * (pos[2][0] - pos[0][0]); + center.y = pos[0][1] + rn * (pos[2][1] - pos[0][1]); + } + float d[4]; + for (int i = 0; i < 4; i++) + d[i] = std::sqrt((pos[i][0] - center.x) * (pos[i][0] - center.x) + (pos[i][1] - center.y) * (pos[i][1] - center.y)); + + for (int i = 0; i < 4; i++) + { + float q = d[i] == 0.0f ? 1.0f : (d[i] + d[(i + 2) & 3]) / d[(i + 2) & 3]; + di.uv[i] *= q; + di.w[i] *= q; + di.z[i] = pos[i][2]; + di.pos[i] = { (pos[i][0] * vInvScreenSize.x) * 2.0f - 1.0f, ((pos[i][1] * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + } + di.mode = nDecalMode; + di.structure = nDecalStructure; + di.depth = true; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + } +#endif + + void PixelGameEngine::DrawLineDecal(const olc::vf2d& pos1, const olc::vf2d& pos2, Pixel p) + { + auto m = nDecalMode; + nDecalMode = olc::DecalMode::WIREFRAME; + DrawPolygonDecal(nullptr, { pos1, pos2 }, { {0, 0}, {0,0} }, p); + nDecalMode = m; + + /*DecalInstance di; + di.decal = nullptr; + di.points = uint32_t(2); + di.pos.resize(di.points); + di.uv.resize(di.points); + di.w.resize(di.points); + di.tint.resize(di.points); + di.pos[0] = { (pos1.x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos1.y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + di.uv[0] = { 0.0f, 0.0f }; + di.tint[0] = p; + di.w[0] = 1.0f; + di.pos[1] = { (pos2.x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos2.y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + di.uv[1] = { 0.0f, 0.0f }; + di.tint[1] = p; + di.w[1] = 1.0f; + di.mode = olc::DecalMode::WIREFRAME; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di);*/ + } + + void PixelGameEngine::DrawRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) + { + auto m = nDecalMode; + SetDecalMode(olc::DecalMode::WIREFRAME); + olc::vf2d vNewSize = size;// (size - olc::vf2d(0.375f, 0.375f)).ceil(); + std::array points = { { {pos}, {pos.x, pos.y + vNewSize.y}, {pos + vNewSize}, {pos.x + vNewSize.x, pos.y} } }; + std::array uvs = { {{0,0},{0,0},{0,0},{0,0}} }; + std::array cols = { {col, col, col, col} }; + DrawExplicitDecal(nullptr, points.data(), uvs.data(), cols.data(), 4); + SetDecalMode(m); + + } + + void PixelGameEngine::FillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) + { + olc::vf2d vNewSize = size;// (size - olc::vf2d(0.375f, 0.375f)).ceil(); + std::array points = { { {pos}, {pos.x, pos.y + vNewSize.y}, {pos + vNewSize}, {pos.x + vNewSize.x, pos.y} } }; + std::array uvs = { {{0,0},{0,0},{0,0},{0,0}} }; + std::array cols = { {col, col, col, col} }; + DrawExplicitDecal(nullptr, points.data(), uvs.data(), cols.data(), 4); + } + + void PixelGameEngine::GradientFillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel colTR) + { + std::array points = { { {pos}, {pos.x, pos.y + size.y}, {pos + size}, {pos.x + size.x, pos.y} } }; + std::array uvs = { {{0,0},{0,0},{0,0},{0,0}} }; + std::array cols = { {colTL, colBL, colBR, colTR} }; + DrawExplicitDecal(nullptr, points.data(), uvs.data(), cols.data(), 4); + } + + void PixelGameEngine::DrawRotatedDecal(const olc::vf2d& pos, olc::Decal* decal, const float fAngle, const olc::vf2d& center, const olc::vf2d& scale, const olc::Pixel& tint) + { + DecalInstance di; + di.decal = decal; + di.pos.resize(4); + di.uv = { { 0.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 0.0f} }; + di.w = { 1, 1, 1, 1 }; + di.tint = { tint, tint, tint, tint }; + di.points = 4; + di.pos[0] = (olc::vf2d(0.0f, 0.0f) - center) * scale; + di.pos[1] = (olc::vf2d(0.0f, float(decal->sprite->height)) - center) * scale; + di.pos[2] = (olc::vf2d(float(decal->sprite->width), float(decal->sprite->height)) - center) * scale; + di.pos[3] = (olc::vf2d(float(decal->sprite->width), 0.0f) - center) * scale; + float c = cos(fAngle), s = sin(fAngle); + for (int i = 0; i < 4; i++) + { + di.pos[i] = pos + olc::vf2d(di.pos[i].x * c - di.pos[i].y * s, di.pos[i].x * s + di.pos[i].y * c); + di.pos[i] = di.pos[i] * vInvScreenSize * 2.0f - olc::vf2d(1.0f, 1.0f); + di.pos[i].y *= -1.0f; + di.w[i] = 1; + } + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + + void PixelGameEngine::DrawPartialRotatedDecal(const olc::vf2d& pos, olc::Decal* decal, const float fAngle, const olc::vf2d& center, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::vf2d& scale, const olc::Pixel& tint) + { + DecalInstance di; + di.decal = decal; + di.points = 4; + di.tint = { tint, tint, tint, tint }; + di.w = { 1, 1, 1, 1 }; + di.pos.resize(4); + di.pos[0] = (olc::vf2d(0.0f, 0.0f) - center) * scale; + di.pos[1] = (olc::vf2d(0.0f, source_size.y) - center) * scale; + di.pos[2] = (olc::vf2d(source_size.x, source_size.y) - center) * scale; + di.pos[3] = (olc::vf2d(source_size.x, 0.0f) - center) * scale; + float c = cos(fAngle), s = sin(fAngle); + for (int i = 0; i < 4; i++) + { + di.pos[i] = pos + olc::vf2d(di.pos[i].x * c - di.pos[i].y * s, di.pos[i].x * s + di.pos[i].y * c); + di.pos[i] = di.pos[i] * vInvScreenSize * 2.0f - olc::vf2d(1.0f, 1.0f); + di.pos[i].y *= -1.0f; + } + + olc::vf2d uvtl = source_pos * decal->vUVScale; + olc::vf2d uvbr = uvtl + (source_size * decal->vUVScale); + di.uv = { { uvtl.x, uvtl.y }, { uvtl.x, uvbr.y }, { uvbr.x, uvbr.y }, { uvbr.x, uvtl.y } }; + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint) + { + DecalInstance di; + di.points = 4; + di.decal = decal; + di.tint = { tint, tint, tint, tint }; + di.w = { 1, 1, 1, 1 }; + di.pos.resize(4); + di.uv = { { 0.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 0.0f} }; + olc::vf2d center; + float rd = ((pos[2].x - pos[0].x) * (pos[3].y - pos[1].y) - (pos[3].x - pos[1].x) * (pos[2].y - pos[0].y)); + if (rd != 0) + { + olc::vf2d uvtl = source_pos * decal->vUVScale; + olc::vf2d uvbr = uvtl + (source_size * decal->vUVScale); + di.uv = { { uvtl.x, uvtl.y }, { uvtl.x, uvbr.y }, { uvbr.x, uvbr.y }, { uvbr.x, uvtl.y } }; + + rd = 1.0f / rd; + float rn = ((pos[3].x - pos[1].x) * (pos[0].y - pos[1].y) - (pos[3].y - pos[1].y) * (pos[0].x - pos[1].x)) * rd; + float sn = ((pos[2].x - pos[0].x) * (pos[0].y - pos[1].y) - (pos[2].y - pos[0].y) * (pos[0].x - pos[1].x)) * rd; + if (!(rn < 0.f || rn > 1.f || sn < 0.f || sn > 1.f)) center = pos[0] + rn * (pos[2] - pos[0]); + float d[4]; for (int i = 0; i < 4; i++) d[i] = (pos[i] - center).mag(); + for (int i = 0; i < 4; i++) + { + float q = d[i] == 0.0f ? 1.0f : (d[i] + d[(i + 2) & 3]) / d[(i + 2) & 3]; + di.uv[i] *= q; di.w[i] *= q; + di.pos[i] = { (pos[i].x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos[i].y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + } + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + } + + void PixelGameEngine::DrawWarpedDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::Pixel& tint) + { + // Thanks Nathan Reed, a brilliant article explaining whats going on here + // http://www.reedbeta.com/blog/quadrilateral-interpolation-part-1/ + DecalInstance di; + di.points = 4; + di.decal = decal; + di.tint = { tint, tint, tint, tint }; + di.w = { 1, 1, 1, 1 }; + di.pos.resize(4); + di.uv = { { 0.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 0.0f} }; + olc::vf2d center; + float rd = ((pos[2].x - pos[0].x) * (pos[3].y - pos[1].y) - (pos[3].x - pos[1].x) * (pos[2].y - pos[0].y)); + if (rd != 0) + { + rd = 1.0f / rd; + float rn = ((pos[3].x - pos[1].x) * (pos[0].y - pos[1].y) - (pos[3].y - pos[1].y) * (pos[0].x - pos[1].x)) * rd; + float sn = ((pos[2].x - pos[0].x) * (pos[0].y - pos[1].y) - (pos[2].y - pos[0].y) * (pos[0].x - pos[1].x)) * rd; + if (!(rn < 0.f || rn > 1.f || sn < 0.f || sn > 1.f)) center = pos[0] + rn * (pos[2] - pos[0]); + float d[4]; for (int i = 0; i < 4; i++) d[i] = (pos[i] - center).mag(); + for (int i = 0; i < 4; i++) + { + float q = d[i] == 0.0f ? 1.0f : (d[i] + d[(i + 2) & 3]) / d[(i + 2) & 3]; + di.uv[i] *= q; di.w[i] *= q; + di.pos[i] = { (pos[i].x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos[i].y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + } + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + } + + void PixelGameEngine::DrawWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::Pixel& tint) + { DrawWarpedDecal(decal, pos.data(), tint); } + + void PixelGameEngine::DrawWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::Pixel& tint) + { DrawWarpedDecal(decal, &pos[0], tint); } + + void PixelGameEngine::DrawPartialWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint) + { DrawPartialWarpedDecal(decal, pos.data(), source_pos, source_size, tint); } + + void PixelGameEngine::DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint) + { DrawPartialWarpedDecal(decal, &pos[0], source_pos, source_size, tint); } + + void PixelGameEngine::DrawStringDecal(const olc::vf2d& pos, const std::string& sText, const Pixel col, const olc::vf2d& scale) + { + olc::vf2d spos = { 0.0f, 0.0f }; + for (auto c : sText) + { + if (c == '\n') + { + spos.x = 0; spos.y += 8.0f * scale.y; + } + else if (c == '\t') + { + spos.x += 8.0f * float(nTabSizeInSpaces) * scale.x; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + DrawPartialDecal(pos + spos, fontRenderable.Decal(), {float(ox) * 8.0f, float(oy) * 8.0f}, {8.0f, 8.0f}, scale, col); + spos.x += 8.0f * scale.x; + } + } + } + + void PixelGameEngine::DrawStringPropDecal(const olc::vf2d& pos, const std::string& sText, const Pixel col, const olc::vf2d& scale) + { + olc::vf2d spos = { 0.0f, 0.0f }; + for (auto c : sText) + { + if (c == '\n') + { + spos.x = 0; spos.y += 8.0f * scale.y; + } + else if (c == '\t') + { + spos.x += 8.0f * float(nTabSizeInSpaces) * scale.x; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + DrawPartialDecal(pos + spos, fontRenderable.Decal(), { float(ox) * 8.0f + float(vFontSpacing[c - 32].x), float(oy) * 8.0f }, { float(vFontSpacing[c - 32].y), 8.0f }, scale, col); + spos.x += float(vFontSpacing[c - 32].y) * scale.x; + } + } + } + // Thanks Oso-Grande/Sopadeoso For these awesom and stupidly clever Text Rotation routines... duh XD + void PixelGameEngine::DrawRotatedStringDecal(const olc::vf2d& pos, const std::string& sText, const float fAngle, const olc::vf2d& center, const Pixel col, const olc::vf2d& scale) + { + olc::vf2d spos = center; + for (auto c : sText) + { + if (c == '\n') + { + spos.x = center.x; spos.y -= 8.0f; + } + else if (c == '\t') + { + spos.x += 8.0f * float(nTabSizeInSpaces) * scale.x; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + DrawPartialRotatedDecal(pos, fontRenderable.Decal(), fAngle, spos, { float(ox) * 8.0f, float(oy) * 8.0f }, { 8.0f, 8.0f }, scale, col); + spos.x -= 8.0f; + } + } + } + + void PixelGameEngine::DrawRotatedStringPropDecal(const olc::vf2d& pos, const std::string& sText, const float fAngle, const olc::vf2d& center, const Pixel col, const olc::vf2d& scale) + { + olc::vf2d spos = center; + for (auto c : sText) + { + if (c == '\n') + { + spos.x = center.x; spos.y -= 8.0f; + } + else if (c == '\t') + { + spos.x += 8.0f * float(nTabSizeInSpaces) * scale.x; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + DrawPartialRotatedDecal(pos, fontRenderable.Decal(), fAngle, spos, { float(ox) * 8.0f + float(vFontSpacing[c - 32].x), float(oy) * 8.0f }, { float(vFontSpacing[c - 32].y), 8.0f }, scale, col); + spos.x -= float(vFontSpacing[c - 32].y); + } + } + } + + olc::vi2d PixelGameEngine::GetTextSize(const std::string& s) + { + olc::vi2d size = { 0,1 }; + olc::vi2d pos = { 0,1 }; + for (auto c : s) + { + if (c == '\n') { pos.y++; pos.x = 0; } + else if (c == '\t') { pos.x += nTabSizeInSpaces; } + else pos.x++; + size.x = std::max(size.x, pos.x); + size.y = std::max(size.y, pos.y); + } + return size * 8; + } + + void PixelGameEngine::DrawString(const olc::vi2d& pos, const std::string& sText, Pixel col, uint32_t scale) + { DrawString(pos.x, pos.y, sText, col, scale); } + + void PixelGameEngine::DrawString(int32_t x, int32_t y, const std::string& sText, Pixel col, uint32_t scale) + { + int32_t sx = 0; + int32_t sy = 0; + Pixel::Mode m = nPixelMode; + // Thanks @tucna, spotted bug with col.ALPHA :P + if (m != Pixel::CUSTOM) // Thanks @Megarev, required for "shaders" + { + if (col.a != 255) SetPixelMode(Pixel::ALPHA); + else SetPixelMode(Pixel::MASK); + } + for (auto c : sText) + { + if (c == '\n') + { + sx = 0; sy += 8 * scale; + } + else if (c == '\t') + { + sx += 8 * nTabSizeInSpaces * scale; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + + if (scale > 1) + { + for (uint32_t i = 0; i < 8; i++) + for (uint32_t j = 0; j < 8; j++) + if (fontRenderable.Sprite()->GetPixel(i + ox * 8, j + oy * 8).r > 0) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + sx + (i * scale) + is, y + sy + (j * scale) + js, col); + } + else + { + for (uint32_t i = 0; i < 8; i++) + for (uint32_t j = 0; j < 8; j++) + if (fontRenderable.Sprite()->GetPixel(i + ox * 8, j + oy * 8).r > 0) + Draw(x + sx + i, y + sy + j, col); + } + sx += 8 * scale; + } + } + SetPixelMode(m); + } + + olc::vi2d PixelGameEngine::GetTextSizeProp(const std::string& s) + { + olc::vi2d size = { 0,1 }; + olc::vi2d pos = { 0,1 }; + for (auto c : s) + { + if (c == '\n') { pos.y += 1; pos.x = 0; } + else if (c == '\t') { pos.x += nTabSizeInSpaces * 8; } + else pos.x += vFontSpacing[c - 32].y; + size.x = std::max(size.x, pos.x); + size.y = std::max(size.y, pos.y); + } + + size.y *= 8; + return size; + } + + void PixelGameEngine::DrawStringProp(const olc::vi2d& pos, const std::string& sText, Pixel col, uint32_t scale) + { DrawStringProp(pos.x, pos.y, sText, col, scale); } + + void PixelGameEngine::DrawStringProp(int32_t x, int32_t y, const std::string& sText, Pixel col, uint32_t scale) + { + int32_t sx = 0; + int32_t sy = 0; + Pixel::Mode m = nPixelMode; + + if (m != Pixel::CUSTOM) + { + if (col.a != 255) SetPixelMode(Pixel::ALPHA); + else SetPixelMode(Pixel::MASK); + } + for (auto c : sText) + { + if (c == '\n') + { + sx = 0; sy += 8 * scale; + } + else if (c == '\t') + { + sx += 8 * nTabSizeInSpaces * scale; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + + if (scale > 1) + { + for (int32_t i = 0; i < vFontSpacing[c - 32].y; i++) + for (int32_t j = 0; j < 8; j++) + if (fontRenderable.Sprite()->GetPixel(i + ox * 8 + vFontSpacing[c - 32].x, j + oy * 8).r > 0) + for (int32_t is = 0; is < int(scale); is++) + for (int32_t js = 0; js < int(scale); js++) + Draw(x + sx + (i * scale) + is, y + sy + (j * scale) + js, col); + } + else + { + for (int32_t i = 0; i < vFontSpacing[c - 32].y; i++) + for (int32_t j = 0; j < 8; j++) + if (fontRenderable.Sprite()->GetPixel(i + ox * 8 + vFontSpacing[c - 32].x, j + oy * 8).r > 0) + Draw(x + sx + i, y + sy + j, col); + } + sx += vFontSpacing[c - 32].y * scale; + } + } + SetPixelMode(m); + } + + void PixelGameEngine::SetPixelMode(Pixel::Mode m) + { nPixelMode = m; } + + Pixel::Mode PixelGameEngine::GetPixelMode() + { return nPixelMode; } + + void PixelGameEngine::SetPixelMode(std::function pixelMode) + { + funcPixelMode = pixelMode; + nPixelMode = Pixel::Mode::CUSTOM; + } + + void PixelGameEngine::SetPixelBlend(float fBlend) + { + fBlendFactor = fBlend; + if (fBlendFactor < 0.0f) fBlendFactor = 0.0f; + if (fBlendFactor > 1.0f) fBlendFactor = 1.0f; + } + + std::stringstream& PixelGameEngine::ConsoleOut() + { return ssConsoleOutput; } + + bool PixelGameEngine::IsConsoleShowing() const + { return bConsoleShow; } + + void PixelGameEngine::ConsoleShow(const olc::Key& keyExit, bool bSuspendTime) + { + if (bConsoleShow) + return; + + bConsoleShow = true; + bConsoleSuspendTime = bSuspendTime; + TextEntryEnable(true); + keyConsoleExit = keyExit; + pKeyboardState[keyConsoleExit].bHeld = false; + pKeyboardState[keyConsoleExit].bPressed = false; + pKeyboardState[keyConsoleExit].bReleased = true; + } + + void PixelGameEngine::ConsoleClear() + { sConsoleLines.clear(); } + + void PixelGameEngine::ConsoleCaptureStdOut(const bool bCapture) + { + if(bCapture) + sbufOldCout = std::cout.rdbuf(ssConsoleOutput.rdbuf()); + else + std::cout.rdbuf(sbufOldCout); + } + + void PixelGameEngine::UpdateConsole() + { + if (GetKey(keyConsoleExit).bPressed) + { + TextEntryEnable(false); + bConsoleSuspendTime = false; + bConsoleShow = false; + return; + } + + // Keep Console sizes based in real screen dimensions + vConsoleCharacterScale = olc::vf2d(1.0f, 2.0f) / (olc::vf2d(vViewSize) * vInvScreenSize); + vConsoleSize = (vViewSize / olc::vi2d(8, 16)) - olc::vi2d(2, 4); + + // If console has changed size, simply reset it + if (vConsoleSize.y != sConsoleLines.size()) + { + vConsoleCursor = { 0,0 }; + sConsoleLines.clear(); + sConsoleLines.resize(vConsoleSize.y); + } + + auto TypeCharacter = [&](const char c) + { + if (c >= 32 && c < 127) + { + sConsoleLines[vConsoleCursor.y].append(1, c); + vConsoleCursor.x++; + } + + if( c == '\n' || vConsoleCursor.x >= vConsoleSize.x) + { + vConsoleCursor.y++; vConsoleCursor.x = 0; + } + + if (vConsoleCursor.y >= vConsoleSize.y) + { + vConsoleCursor.y = vConsoleSize.y - 1; + for (size_t i = 1; i < vConsoleSize.y; i++) + sConsoleLines[i - 1] = sConsoleLines[i]; + sConsoleLines[vConsoleCursor.y].clear(); + } + }; + + // Empty out "std::cout", parsing as we go + while (ssConsoleOutput.rdbuf()->sgetc() != -1) + { + char c = ssConsoleOutput.rdbuf()->sbumpc(); + TypeCharacter(c); + } + + // Draw Shadow + GradientFillRectDecal({ 0,0 }, olc::vf2d(vScreenSize), olc::PixelF(0, 0, 0.5f, 0.5f), olc::PixelF(0, 0, 0.25f, 0.5f), olc::PixelF(0, 0, 0.25f, 0.5f), olc::PixelF(0, 0, 0.25f, 0.5f)); + + // Draw the console buffer + SetDecalMode(olc::DecalMode::NORMAL); + for (int32_t nLine = 0; nLine < vConsoleSize.y; nLine++) + DrawStringDecal(olc::vf2d( 1, 1 + float(nLine) ) * vConsoleCharacterScale * 8.0f, sConsoleLines[nLine], olc::WHITE, vConsoleCharacterScale); + + // Draw Input State + FillRectDecal(olc::vf2d(1 + float((TextEntryGetCursor() + 1)), 1 + float((vConsoleSize.y - 1))) * vConsoleCharacterScale * 8.0f, olc::vf2d(8, 8) * vConsoleCharacterScale, olc::DARK_CYAN); + DrawStringDecal(olc::vf2d(1, 1 + float((vConsoleSize.y - 1))) * vConsoleCharacterScale * 8.0f, std::string(">") + TextEntryGetString(), olc::YELLOW, vConsoleCharacterScale); + } + + + const std::vector& PixelGameEngine::GetDroppedFiles() const + { return vDroppedFiles; } + + const olc::vi2d& PixelGameEngine::GetDroppedFilesPoint() const + { return vDroppedFilesPoint; } + + + void PixelGameEngine::TextEntryEnable(const bool bEnable, const std::string& sText) + { + if (bEnable) + { + nTextEntryCursor = int32_t(sText.size()); + sTextEntryString = sText; + bTextEntryEnable = true; + } + else + { + bTextEntryEnable = false; + } + } + + std::string PixelGameEngine::TextEntryGetString() const + { return sTextEntryString; } + + int32_t PixelGameEngine::TextEntryGetCursor() const + { return nTextEntryCursor; } + + bool PixelGameEngine::IsTextEntryEnabled() const + { return bTextEntryEnable; } + + + void PixelGameEngine::UpdateTextEntry() + { + // Check for typed characters + for (const auto& key : vKeyboardMap) + if (GetKey(std::get<0>(key)).bPressed) + { + sTextEntryString.insert(nTextEntryCursor, GetKey(olc::Key::SHIFT).bHeld ? std::get<2>(key) : std::get<1>(key)); + nTextEntryCursor++; + } + + // Check for command characters + if (GetKey(olc::Key::LEFT).bPressed) + nTextEntryCursor = std::max(0, nTextEntryCursor - 1); + if (GetKey(olc::Key::RIGHT).bPressed) + nTextEntryCursor = std::min(int32_t(sTextEntryString.size()), nTextEntryCursor + 1); + if (GetKey(olc::Key::BACK).bPressed && nTextEntryCursor > 0) + { + sTextEntryString.erase(nTextEntryCursor-1, 1); + nTextEntryCursor = std::max(0, nTextEntryCursor - 1); + } + if (GetKey(olc::Key::DEL).bPressed && nTextEntryCursor < sTextEntryString.size()) + sTextEntryString.erase(nTextEntryCursor, 1); + + if (GetKey(olc::Key::UP).bPressed) + { + if (!sCommandHistory.empty()) + { + if (sCommandHistoryIt != sCommandHistory.begin()) + sCommandHistoryIt--; + + nTextEntryCursor = int32_t(sCommandHistoryIt->size()); + sTextEntryString = *sCommandHistoryIt; + } + } + + if (GetKey(olc::Key::DOWN).bPressed) + { + if (!sCommandHistory.empty()) + { + if (sCommandHistoryIt != sCommandHistory.end()) + { + sCommandHistoryIt++; + if (sCommandHistoryIt != sCommandHistory.end()) + { + nTextEntryCursor = int32_t(sCommandHistoryIt->size()); + sTextEntryString = *sCommandHistoryIt; + } + else + { + nTextEntryCursor = 0; + sTextEntryString = ""; + } + } + } + } + + if (GetKey(olc::Key::ENTER).bPressed) + { + if (bConsoleShow) + { + std::cout << ">" + sTextEntryString + "\n"; + if (OnConsoleCommand(sTextEntryString)) + { + sCommandHistory.push_back(sTextEntryString); + sCommandHistoryIt = sCommandHistory.end(); + } + sTextEntryString.clear(); + nTextEntryCursor = 0; + } + else + { + OnTextEntryComplete(sTextEntryString); + TextEntryEnable(false); + } + } + } + + // User must override these functions as required. I have not made + // them abstract because I do need a default behaviour to occur if + // they are not overwritten + + bool PixelGameEngine::OnUserCreate() + { return false; } + + bool PixelGameEngine::OnUserUpdate(float fElapsedTime) + { UNUSED(fElapsedTime); return false; } + + bool PixelGameEngine::OnUserDestroy() + { return true; } + + void PixelGameEngine::OnTextEntryComplete(const std::string& sText) { UNUSED(sText); } + bool PixelGameEngine::OnConsoleCommand(const std::string& sCommand) { UNUSED(sCommand); return false; } + + // Externalised API + void PixelGameEngine::olc_UpdateViewport() + { + int32_t ww = vScreenSize.x * vPixelSize.x; + int32_t wh = vScreenSize.y * vPixelSize.y; + float wasp = (float)ww / (float)wh; + + if (bPixelCohesion) + { + vScreenPixelSize = (vWindowSize / vScreenSize); + vViewSize = (vWindowSize / vScreenSize) * vScreenSize; + } + else + { + vViewSize.x = (int32_t)vWindowSize.x; + vViewSize.y = (int32_t)((float)vViewSize.x / wasp); + + if (vViewSize.y > vWindowSize.y) + { + vViewSize.y = vWindowSize.y; + vViewSize.x = (int32_t)((float)vViewSize.y * wasp); + } + } + + vViewPos = (vWindowSize - vViewSize) / 2; + } + + void PixelGameEngine::olc_UpdateWindowSize(int32_t x, int32_t y) + { + vWindowSize = { x, y }; + olc_UpdateViewport(); + } + + void PixelGameEngine::olc_UpdateMouseWheel(int32_t delta) + { nMouseWheelDeltaCache += delta; } + + void PixelGameEngine::olc_UpdateMouse(int32_t x, int32_t y) + { + // Mouse coords come in screen space + // But leave in pixel space + bHasMouseFocus = true; + vMouseWindowPos = { x, y }; + // Full Screen mode may have a weird viewport we must clamp to + x -= vViewPos.x; + y -= vViewPos.y; + vMousePosCache.x = (int32_t)(((float)x / (float)(vWindowSize.x - (vViewPos.x * 2)) * (float)vScreenSize.x)); + vMousePosCache.y = (int32_t)(((float)y / (float)(vWindowSize.y - (vViewPos.y * 2)) * (float)vScreenSize.y)); + if (vMousePosCache.x >= (int32_t)vScreenSize.x) vMousePosCache.x = vScreenSize.x - 1; + if (vMousePosCache.y >= (int32_t)vScreenSize.y) vMousePosCache.y = vScreenSize.y - 1; + if (vMousePosCache.x < 0) vMousePosCache.x = 0; + if (vMousePosCache.y < 0) vMousePosCache.y = 0; + } + + void PixelGameEngine::olc_UpdateMouseState(int32_t button, bool state) + { pMouseNewState[button] = state; } + + void PixelGameEngine::olc_UpdateKeyState(int32_t key, bool state) + { pKeyNewState[key] = state; } + + void PixelGameEngine::olc_UpdateMouseFocus(bool state) + { bHasMouseFocus = state; } + + void PixelGameEngine::olc_UpdateKeyFocus(bool state) + { bHasInputFocus = state; } + + void PixelGameEngine::olc_DropFiles(int32_t x, int32_t y, const std::vector& vFiles) + { + x -= vViewPos.x; + y -= vViewPos.y; + vDroppedFilesPointCache.x = (int32_t)(((float)x / (float)(vWindowSize.x - (vViewPos.x * 2)) * (float)vScreenSize.x)); + vDroppedFilesPointCache.y = (int32_t)(((float)y / (float)(vWindowSize.y - (vViewPos.y * 2)) * (float)vScreenSize.y)); + if (vDroppedFilesPointCache.x >= (int32_t)vScreenSize.x) vDroppedFilesPointCache.x = vScreenSize.x - 1; + if (vDroppedFilesPointCache.y >= (int32_t)vScreenSize.y) vDroppedFilesPointCache.y = vScreenSize.y - 1; + if (vDroppedFilesPointCache.x < 0) vDroppedFilesPointCache.x = 0; + if (vDroppedFilesPointCache.y < 0) vDroppedFilesPointCache.y = 0; + vDroppedFilesCache = vFiles; + } + + void PixelGameEngine::olc_Reanimate() + { bAtomActive = true; } + + bool PixelGameEngine::olc_IsRunning() + { return bAtomActive; } + + void PixelGameEngine::olc_Terminate() + { bAtomActive = false; } + + void PixelGameEngine::EngineThread() + { + // Allow platform to do stuff here if needed, since its now in the + // context of this thread + if (platform->ThreadStartUp() == olc::FAIL) return; + + // Do engine context specific initialisation + olc_PrepareEngine(); + + // Create user resources as part of this thread + for (auto& ext : vExtensions) ext->OnBeforeUserCreate(); + if (!OnUserCreate()) bAtomActive = false; + for (auto& ext : vExtensions) ext->OnAfterUserCreate(); + + while (bAtomActive) + { + // Run as fast as possible + while (bAtomActive) { olc_CoreUpdate(); } + + // Allow the user to free resources if they have overrided the destroy function + if (!OnUserDestroy()) + { + // User denied destroy for some reason, so continue running + bAtomActive = true; + } + } + + platform->ThreadCleanUp(); + } + + void PixelGameEngine::olc_PrepareEngine() + { + // Start OpenGL, the context is owned by the game thread + if (platform->CreateGraphics(bFullScreen, bEnableVSYNC, vViewPos, vViewSize) == olc::FAIL) return; + + // Construct default font sheet + olc_ConstructFontSheet(); + + // Create Primary Layer "0" + CreateLayer(); + vLayers[0].bUpdate = true; + vLayers[0].bShow = true; + SetDrawTarget(nullptr); + + m_tp1 = std::chrono::system_clock::now(); + m_tp2 = std::chrono::system_clock::now(); + } + + + void PixelGameEngine::olc_CoreUpdate() + { + // Handle Timing + m_tp2 = std::chrono::system_clock::now(); + std::chrono::duration elapsedTime = m_tp2 - m_tp1; + m_tp1 = m_tp2; + + // Our time per frame coefficient + float fElapsedTime = elapsedTime.count(); + fLastElapsed = fElapsedTime; + + if (bConsoleSuspendTime) + fElapsedTime = 0.0f; + + // Some platforms will need to check for events + platform->HandleSystemEvent(); + + // Compare hardware input states from previous frame + auto ScanHardware = [&](HWButton* pKeys, bool* pStateOld, bool* pStateNew, uint32_t nKeyCount) + { + for (uint32_t i = 0; i < nKeyCount; i++) + { + pKeys[i].bPressed = false; + pKeys[i].bReleased = false; + if (pStateNew[i] != pStateOld[i]) + { + if (pStateNew[i]) + { + pKeys[i].bPressed = !pKeys[i].bHeld; + pKeys[i].bHeld = true; + } + else + { + pKeys[i].bReleased = true; + pKeys[i].bHeld = false; + } + } + pStateOld[i] = pStateNew[i]; + } + }; + + ScanHardware(pKeyboardState, pKeyOldState, pKeyNewState, 256); + ScanHardware(pMouseState, pMouseOldState, pMouseNewState, nMouseButtons); + + // Cache mouse coordinates so they remain consistent during frame + vMousePos = vMousePosCache; + nMouseWheelDelta = nMouseWheelDeltaCache; + nMouseWheelDeltaCache = 0; + + vDroppedFiles = vDroppedFilesCache; + vDroppedFilesPoint = vDroppedFilesPointCache; + vDroppedFilesCache.clear(); + + if (bTextEntryEnable) + { + UpdateTextEntry(); + } + + // Handle Frame Update + bool bExtensionBlockFrame = false; + for (auto& ext : vExtensions) bExtensionBlockFrame |= ext->OnBeforeUserUpdate(fElapsedTime); + if (!bExtensionBlockFrame) + { + if (!OnUserUpdate(fElapsedTime)) bAtomActive = false; + + } + for (auto& ext : vExtensions) ext->OnAfterUserUpdate(fElapsedTime); + + if (bConsoleShow) + { + SetDrawTarget((uint8_t)0); + UpdateConsole(); + } + + + + // Display Frame + renderer->UpdateViewport(vViewPos, vViewSize); + renderer->ClearBuffer(olc::BLACK, true); + + // Layer 0 must always exist + vLayers[0].bUpdate = true; + vLayers[0].bShow = true; + SetDecalMode(DecalMode::NORMAL); + renderer->PrepareDrawing(); + + for (auto layer = vLayers.rbegin(); layer != vLayers.rend(); ++layer) + { + if (layer->bShow) + { + if (layer->funcHook == nullptr) + { + renderer->ApplyTexture(layer->pDrawTarget.Decal()->id); + if (!bSuspendTextureTransfer && layer->bUpdate) + { + layer->pDrawTarget.Decal()->Update(); + layer->bUpdate = false; + } + + renderer->DrawLayerQuad(layer->vOffset, layer->vScale, layer->tint); + + // Display Decals in order for this layer + for (auto& decal : layer->vecDecalInstance) + renderer->DrawDecal(decal); + layer->vecDecalInstance.clear(); + } + else + { + // Mwa ha ha.... Have Fun!!! + layer->funcHook(); + } + } + } + + + + // Present Graphics to screen + renderer->DisplayFrame(); + + // Update Title Bar + fFrameTimer += fElapsedTime; + nFrameCount++; + if (fFrameTimer >= 1.0f) + { + nLastFPS = nFrameCount; + fFrameTimer -= 1.0f; + std::string sTitle = "OneLoneCoder.com - Pixel Game Engine - " + sAppName + " - FPS: " + std::to_string(nFrameCount); + platform->SetWindowTitle(sTitle); + nFrameCount = 0; + } + } + + void PixelGameEngine::olc_ConstructFontSheet() + { + std::string data; + data += "?Q`0001oOch0o01o@F40o000000000"; + data += "O000000nOT0063Qo4d8>?7a14Gno94AA4gno94AaOT0>o3`oO400o7QN00000400"; + data += "Of80001oOg<7O7moBGT7O7lABET024@aBEd714AiOdl717a_=TH013Q>00000000"; + data += "720D000V?V5oB3Q_HdUoE7a9@DdDE4A9@DmoE4A;Hg]oM4Aj8S4D84@`00000000"; + data += "OaPT1000Oa`^13P1@AI[?g`1@A=[OdAoHgljA4Ao?WlBA7l1710007l100000000"; + data += "ObM6000oOfMV?3QoBDD`O7a0BDDH@5A0BDD<@5A0BGeVO5ao@CQR?5Po00000000"; + data += "Oc``000?Ogij70PO2D]??0Ph2DUM@7i`2DTg@7lh2GUj?0TO0C1870T?00000000"; + data += "70<4001o?P<7?1QoHg43O;`h@GT0@:@LB@d0>:@hN@L0@?aoN@<0O7ao0000?000"; + data += "OcH0001SOglLA7mg24TnK7ln24US>0PL24U140PnOgl0>7QgOcH0K71S0000A000"; + data += "00H00000@Dm1S007@DUSg00?OdTnH7YhOfTL<7Yh@Cl0700?@Ah0300700000000"; + data += "<008001QL00ZA41a@6HnI<1i@FHLM81M@@0LG81?O`0nC?Y7?`0ZA7Y300080000"; + data += "O`082000Oh0827mo6>Hn?Wmo?6HnMb11MP08@C11H`08@FP0@@0004@000000000"; + data += "00P00001Oab00003OcKP0006@6=PMgl<@440MglH@000000`@000001P00000000"; + data += "Ob@8@@00Ob@8@Ga13R@8Mga172@8?PAo3R@827QoOb@820@0O`0007`0000007P0"; + data += "O`000P08Od400g`<3V=P0G`673IP0`@3>1`00P@6O`P00g`SetPixel(px, py, olc::Pixel(k, k, k, k)); + if (++py == 48) { px++; py = 0; } + } + } + + fontRenderable.Decal()->Update(); + + constexpr std::array vSpacing = { { + 0x03,0x25,0x16,0x08,0x07,0x08,0x08,0x04,0x15,0x15,0x08,0x07,0x15,0x07,0x24,0x08, + 0x08,0x17,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x24,0x15,0x06,0x07,0x16,0x17, + 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x17,0x08,0x08,0x17,0x08,0x08,0x08, + 0x08,0x08,0x08,0x08,0x17,0x08,0x08,0x08,0x08,0x17,0x08,0x15,0x08,0x15,0x08,0x08, + 0x24,0x18,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x33,0x17,0x17,0x33,0x18,0x17,0x17, + 0x17,0x17,0x17,0x17,0x07,0x17,0x17,0x18,0x18,0x17,0x17,0x07,0x33,0x07,0x08,0x00, } }; + + for (auto c : vSpacing) vFontSpacing.push_back({ c >> 4, c & 15 }); + + // UK Standard Layout +#ifdef OLC_KEYBOARD_UK + vKeyboardMap = + { + {olc::Key::A, "a", "A"}, {olc::Key::B, "b", "B"}, {olc::Key::C, "c", "C"}, {olc::Key::D, "d", "D"}, {olc::Key::E, "e", "E"}, + {olc::Key::F, "f", "F"}, {olc::Key::G, "g", "G"}, {olc::Key::H, "h", "H"}, {olc::Key::I, "i", "I"}, {olc::Key::J, "j", "J"}, + {olc::Key::K, "k", "K"}, {olc::Key::L, "l", "L"}, {olc::Key::M, "m", "M"}, {olc::Key::N, "n", "N"}, {olc::Key::O, "o", "O"}, + {olc::Key::P, "p", "P"}, {olc::Key::Q, "q", "Q"}, {olc::Key::R, "r", "R"}, {olc::Key::S, "s", "S"}, {olc::Key::T, "t", "T"}, + {olc::Key::U, "u", "U"}, {olc::Key::V, "v", "V"}, {olc::Key::W, "w", "W"}, {olc::Key::X, "x", "X"}, {olc::Key::Y, "y", "Y"}, + {olc::Key::Z, "z", "Z"}, + + {olc::Key::K0, "0", ")"}, {olc::Key::K1, "1", "!"}, {olc::Key::K2, "2", "\""}, {olc::Key::K3, "3", "#"}, {olc::Key::K4, "4", "$"}, + {olc::Key::K5, "5", "%"}, {olc::Key::K6, "6", "^"}, {olc::Key::K7, "7", "&"}, {olc::Key::K8, "8", "*"}, {olc::Key::K9, "9", "("}, + + {olc::Key::NP0, "0", "0"}, {olc::Key::NP1, "1", "1"}, {olc::Key::NP2, "2", "2"}, {olc::Key::NP3, "3", "3"}, {olc::Key::NP4, "4", "4"}, + {olc::Key::NP5, "5", "5"}, {olc::Key::NP6, "6", "6"}, {olc::Key::NP7, "7", "7"}, {olc::Key::NP8, "8", "8"}, {olc::Key::NP9, "9", "9"}, + {olc::Key::NP_MUL, "*", "*"}, {olc::Key::NP_DIV, "/", "/"}, {olc::Key::NP_ADD, "+", "+"}, {olc::Key::NP_SUB, "-", "-"}, {olc::Key::NP_DECIMAL, ".", "."}, + + {olc::Key::PERIOD, ".", ">"}, {olc::Key::EQUALS, "=", "+"}, {olc::Key::COMMA, ",", "<"}, {olc::Key::MINUS, "-", "_"}, {olc::Key::SPACE, " ", " "}, + + {olc::Key::OEM_1, ";", ":"}, {olc::Key::OEM_2, "/", "?"}, {olc::Key::OEM_3, "\'", "@"}, {olc::Key::OEM_4, "[", "{"}, + {olc::Key::OEM_5, "\\", "|"}, {olc::Key::OEM_6, "]", "}"}, {olc::Key::OEM_7, "#", "~"}, + + // {olc::Key::TAB, "\t", "\t"} + }; +#endif + } + + void PixelGameEngine::pgex_Register(olc::PGEX* pgex) + { + if (std::find(vExtensions.begin(), vExtensions.end(), pgex) == vExtensions.end()) + vExtensions.push_back(pgex); + } + + + PGEX::PGEX(bool bHook) { if(bHook) pge->pgex_Register(this); } + void PGEX::OnBeforeUserCreate() {} + void PGEX::OnAfterUserCreate() {} + bool PGEX::OnBeforeUserUpdate(float& fElapsedTime) { return false; } + void PGEX::OnAfterUserUpdate(float fElapsedTime) {} + + // Need a couple of statics as these are singleton instances + // read from multiple locations + std::atomic PixelGameEngine::bAtomActive{ false }; + olc::PixelGameEngine* olc::PGEX::pge = nullptr; + olc::PixelGameEngine* olc::Platform::ptrPGE = nullptr; + olc::PixelGameEngine* olc::Renderer::ptrPGE = nullptr; + std::unique_ptr olc::Sprite::loader = nullptr; +}; +#pragma endregion + + +#pragma region platform_headless +namespace olc +{ +#if defined(OLC_GFX_HEADLESS) + class Renderer_Headless : public olc::Renderer + { + public: + virtual void PrepareDevice() {}; + virtual olc::rcode CreateDevice(std::vector params, bool bFullScreen, bool bVSYNC) { return olc::rcode::OK; } + virtual olc::rcode DestroyDevice() { return olc::rcode::OK; } + virtual void DisplayFrame() {} + virtual void PrepareDrawing() {} + virtual void SetDecalMode(const olc::DecalMode& mode) {} + virtual void DrawLayerQuad(const olc::vf2d& offset, const olc::vf2d& scale, const olc::Pixel tint) {} + virtual void DrawDecal(const olc::DecalInstance& decal) {} + virtual uint32_t CreateTexture(const uint32_t width, const uint32_t height, const bool filtered = false, const bool clamp = true) {return 1;}; + virtual void UpdateTexture(uint32_t id, olc::Sprite* spr) {} + virtual void ReadTexture(uint32_t id, olc::Sprite* spr) {} + virtual uint32_t DeleteTexture(const uint32_t id) {return 1;} + virtual void ApplyTexture(uint32_t id) {} + virtual void UpdateViewport(const olc::vi2d& pos, const olc::vi2d& size) {} + virtual void ClearBuffer(olc::Pixel p, bool bDepth) {} + }; +#endif +#if defined(OLC_PLATFORM_HEADLESS) + class Platform_Headless : public olc::Platform + { + public: + virtual olc::rcode ApplicationStartUp() { return olc::rcode::OK; } + virtual olc::rcode ApplicationCleanUp() { return olc::rcode::OK; } + virtual olc::rcode ThreadStartUp() { return olc::rcode::OK; } + virtual olc::rcode ThreadCleanUp() { return olc::rcode::OK; } + virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) { return olc::rcode::OK; } + virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d& vWindowSize, bool bFullScreen) { return olc::rcode::OK; } + virtual olc::rcode SetWindowTitle(const std::string& s) { return olc::rcode::OK; } + virtual olc::rcode StartSystemEventLoop() { return olc::rcode::OK; } + virtual olc::rcode HandleSystemEvent() { return olc::rcode::OK; } + }; +#endif +} +#pragma endregion + +// O------------------------------------------------------------------------------O +// | olcPixelGameEngine Renderers - the draw-y bits | +// O------------------------------------------------------------------------------O + +#pragma region image_stb +// O------------------------------------------------------------------------------O +// | START IMAGE LOADER: stb_image.h, all systems, very fast | +// O------------------------------------------------------------------------------O +// Thanks to Sean Barrett - https://github.com/nothings/stb/blob/master/stb_image.h +// MIT License - Copyright(c) 2017 Sean Barrett + +// Note you need to download the above file into your project folder, and +// #define OLC_IMAGE_STB +// #define OLC_PGE_APPLICATION +// #include "olcPixelGameEngine.h" + +#if defined(OLC_IMAGE_STB) +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" +namespace olc +{ + class ImageLoader_STB : public olc::ImageLoader + { + public: + ImageLoader_STB() : ImageLoader() + {} + + olc::rcode LoadImageResource(olc::Sprite* spr, const std::string& sImageFile, olc::ResourcePack* pack) override + { + UNUSED(pack); + // clear out existing sprite + spr->pColData.clear(); + // Open file + stbi_uc* bytes = nullptr; + int w = 0, h = 0, cmp = 0; + if (pack != nullptr) + { + ResourceBuffer rb = pack->GetFileBuffer(sImageFile); + bytes = stbi_load_from_memory((unsigned char*)rb.vMemory.data(), rb.vMemory.size(), &w, &h, &cmp, 4); + } + else + { + // Check file exists + if (!_gfs::exists(sImageFile)) return olc::rcode::NO_FILE; + bytes = stbi_load(sImageFile.c_str(), &w, &h, &cmp, 4); + } + + if (!bytes) return olc::rcode::FAIL; + spr->width = w; spr->height = h; + spr->pColData.resize(spr->width * spr->height); + std::memcpy(spr->pColData.data(), bytes, spr->width * spr->height * 4); + delete[] bytes; + return olc::rcode::OK; + } + + olc::rcode SaveImageResource(olc::Sprite* spr, const std::string& sImageFile) override + { + return olc::rcode::OK; + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | START IMAGE LOADER: stb_image.h | +// O------------------------------------------------------------------------------O +#pragma endregion + + + +#if !defined(OLC_PGE_HEADLESS) + +#pragma region renderer_ogl10 +// O------------------------------------------------------------------------------O +// | START RENDERER: OpenGL 1.0 (the original, the best...) | +// O------------------------------------------------------------------------------O +#if defined(OLC_GFX_OPENGL10) + +#if defined(OLC_PLATFORM_WINAPI) + #include + #include + #if !defined(__MINGW32__) + #pragma comment(lib, "Dwmapi.lib") + #endif + typedef BOOL(WINAPI wglSwapInterval_t) (int interval); + static wglSwapInterval_t* wglSwapInterval = nullptr; + typedef HDC glDeviceContext_t; + typedef HGLRC glRenderContext_t; +#endif + +#if defined(__linux__) || defined(__FreeBSD__) + #include +#endif + +#if defined(OLC_PLATFORM_X11) + namespace X11 + { + #include + } + typedef int(glSwapInterval_t)(X11::Display* dpy, X11::GLXDrawable drawable, int interval); + static glSwapInterval_t* glSwapIntervalEXT; + typedef X11::GLXContext glDeviceContext_t; + typedef X11::GLXContext glRenderContext_t; +#endif + +#if defined(__APPLE__) + #define GL_SILENCE_DEPRECATION + #include + #include + #include +#endif + +namespace olc +{ + class Renderer_OGL10 : public olc::Renderer + { + private: +#if defined(OLC_PLATFORM_GLUT) + bool mFullScreen = false; +#else + glDeviceContext_t glDeviceContext = 0; + glRenderContext_t glRenderContext = 0; +#endif + + bool bSync = false; + olc::DecalMode nDecalMode = olc::DecalMode(-1); // Thanks Gusgo & Bispoo + olc::DecalStructure nDecalStructure = olc::DecalStructure(-1); +#if defined(OLC_PLATFORM_X11) + X11::Display* olc_Display = nullptr; + X11::Window* olc_Window = nullptr; + X11::XVisualInfo* olc_VisualInfo = nullptr; +#endif + + public: + void PrepareDevice() override + { +#if defined(OLC_PLATFORM_GLUT) + //glutInit has to be called with main() arguments, make fake ones + int argc = 0; + char* argv[1] = { (char*)"" }; + glutInit(&argc, argv); + glutInitWindowPosition(0, 0); + glutInitWindowSize(512, 512); + glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGBA); + // Creates the window and the OpenGL context for it + glutCreateWindow("OneLoneCoder.com - Pixel Game Engine"); + glEnable(GL_TEXTURE_2D); // Turn on texturing + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); +#endif + } + + olc::rcode CreateDevice(std::vector params, bool bFullScreen, bool bVSYNC) override + { +#if defined(OLC_PLATFORM_WINAPI) + // Create Device Context + glDeviceContext = GetDC((HWND)(params[0])); + PIXELFORMATDESCRIPTOR pfd = + { + sizeof(PIXELFORMATDESCRIPTOR), 1, + PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + PFD_MAIN_PLANE, 0, 0, 0, 0 + }; + + int pf = 0; + if (!(pf = ChoosePixelFormat(glDeviceContext, &pfd))) return olc::FAIL; + SetPixelFormat(glDeviceContext, pf, &pfd); + + if (!(glRenderContext = wglCreateContext(glDeviceContext))) return olc::FAIL; + wglMakeCurrent(glDeviceContext, glRenderContext); + + // Remove Frame cap + wglSwapInterval = (wglSwapInterval_t*)wglGetProcAddress("wglSwapIntervalEXT"); + if (wglSwapInterval && !bVSYNC) wglSwapInterval(0); + bSync = bVSYNC; +#endif + +#if defined(OLC_PLATFORM_X11) + using namespace X11; + // Linux has tighter coupling between OpenGL and X11, so we store + // various "platform" handles in the renderer + olc_Display = (X11::Display*)(params[0]); + olc_Window = (X11::Window*)(params[1]); + olc_VisualInfo = (X11::XVisualInfo*)(params[2]); + + glDeviceContext = glXCreateContext(olc_Display, olc_VisualInfo, nullptr, GL_TRUE); + glXMakeCurrent(olc_Display, *olc_Window, glDeviceContext); + + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, *olc_Window, &gwa); + glViewport(0, 0, gwa.width, gwa.height); + + glSwapIntervalEXT = nullptr; + glSwapIntervalEXT = (glSwapInterval_t*)glXGetProcAddress((unsigned char*)"glXSwapIntervalEXT"); + + if (glSwapIntervalEXT == nullptr && !bVSYNC) + { + printf("NOTE: Could not disable VSYNC, glXSwapIntervalEXT() was not found!\n"); + printf(" Don't worry though, things will still work, it's just the\n"); + printf(" frame rate will be capped to your monitors refresh rate - javidx9\n"); + } + + if (glSwapIntervalEXT != nullptr && !bVSYNC) + glSwapIntervalEXT(olc_Display, *olc_Window, 0); +#endif + +#if defined(OLC_PLATFORM_GLUT) + mFullScreen = bFullScreen; + if (!bVSYNC) + { +#if defined(__APPLE__) + GLint sync = 0; + CGLContextObj ctx = CGLGetCurrentContext(); + if (ctx) CGLSetParameter(ctx, kCGLCPSwapInterval, &sync); +#endif + } +#else + glEnable(GL_TEXTURE_2D); // Turn on texturing + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); +#endif + return olc::rcode::OK; + } + + olc::rcode DestroyDevice() override + { +#if defined(OLC_PLATFORM_WINAPI) + wglDeleteContext(glRenderContext); +#endif + +#if defined(OLC_PLATFORM_X11) + glXMakeCurrent(olc_Display, None, NULL); + glXDestroyContext(olc_Display, glDeviceContext); +#endif + +#if defined(OLC_PLATFORM_GLUT) + glutDestroyWindow(glutGetWindow()); +#endif + return olc::rcode::OK; + } + + void DisplayFrame() override + { +#if defined(OLC_PLATFORM_WINAPI) + SwapBuffers(glDeviceContext); + if (bSync) DwmFlush(); // Woooohooooooo!!!! SMOOOOOOOTH! +#endif + +#if defined(OLC_PLATFORM_X11) + X11::glXSwapBuffers(olc_Display, *olc_Window); +#endif + +#if defined(OLC_PLATFORM_GLUT) + glutSwapBuffers(); +#endif + } + + void PrepareDrawing() override + { + + //ClearBuffer(olc::GREEN, true); + glEnable(GL_BLEND); + nDecalMode = DecalMode::NORMAL; + nDecalStructure = DecalStructure::FAN; + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + void SetDecalMode(const olc::DecalMode& mode) + { + if (mode != nDecalMode) + { + switch (mode) + { + case olc::DecalMode::NORMAL: + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case olc::DecalMode::ADDITIVE: + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + break; + case olc::DecalMode::MULTIPLICATIVE: + glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); + break; + case olc::DecalMode::STENCIL: + glBlendFunc(GL_ZERO, GL_SRC_ALPHA); + break; + case olc::DecalMode::ILLUMINATE: + glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA); + break; + case olc::DecalMode::WIREFRAME: + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + } + + nDecalMode = mode; + } + } + + void DrawLayerQuad(const olc::vf2d& offset, const olc::vf2d& scale, const olc::Pixel tint) override + { + glBegin(GL_QUADS); + glColor4ub(tint.r, tint.g, tint.b, tint.a); + glTexCoord2f(0.0f * scale.x + offset.x, 1.0f * scale.y + offset.y); + glVertex3f(-1.0f /*+ vSubPixelOffset.x*/, -1.0f /*+ vSubPixelOffset.y*/, 0.0f); + glTexCoord2f(0.0f * scale.x + offset.x, 0.0f * scale.y + offset.y); + glVertex3f(-1.0f /*+ vSubPixelOffset.x*/, 1.0f /*+ vSubPixelOffset.y*/, 0.0f); + glTexCoord2f(1.0f * scale.x + offset.x, 0.0f * scale.y + offset.y); + glVertex3f(1.0f /*+ vSubPixelOffset.x*/, 1.0f /*+ vSubPixelOffset.y*/, 0.0f); + glTexCoord2f(1.0f * scale.x + offset.x, 1.0f * scale.y + offset.y); + glVertex3f(1.0f /*+ vSubPixelOffset.x*/, -1.0f /*+ vSubPixelOffset.y*/, 0.0f); + glEnd(); + } + + void DrawDecal(const olc::DecalInstance& decal) override + { + SetDecalMode(decal.mode); + + if (decal.decal == nullptr) + glBindTexture(GL_TEXTURE_2D, 0); + else + glBindTexture(GL_TEXTURE_2D, decal.decal->id); + + if (decal.depth) + { + glEnable(GL_DEPTH_TEST); + } + + if (nDecalMode == DecalMode::WIREFRAME) + glBegin(GL_LINE_LOOP); + else + { + if(decal.structure == olc::DecalStructure::FAN) + glBegin(GL_TRIANGLE_FAN); + else if(decal.structure == olc::DecalStructure::STRIP) + glBegin(GL_TRIANGLE_STRIP); + else if(decal.structure == olc::DecalStructure::LIST) + glBegin(GL_TRIANGLES); + } + + if (decal.depth) + { + + // Render as 3D Spatial Entity + for (uint32_t n = 0; n < decal.points; n++) + { + glColor4ub(decal.tint[n].r, decal.tint[n].g, decal.tint[n].b, decal.tint[n].a); + glTexCoord4f(decal.uv[n].x, decal.uv[n].y, 0.0f, decal.w[n]); + glVertex3f(decal.pos[n].x, decal.pos[n].y, decal.z[n]); + } + } + else + { + // Render as 2D Spatial entity + for (uint32_t n = 0; n < decal.points; n++) + { + glColor4ub(decal.tint[n].r, decal.tint[n].g, decal.tint[n].b, decal.tint[n].a); + glTexCoord4f(decal.uv[n].x, decal.uv[n].y, 0.0f, decal.w[n]); + glVertex2f(decal.pos[n].x, decal.pos[n].y); + } + } + + glEnd(); + + if (decal.depth) + { + glDisable(GL_DEPTH_TEST); + } + + } + + uint32_t CreateTexture(const uint32_t width, const uint32_t height, const bool filtered, const bool clamp) override + { + UNUSED(width); + UNUSED(height); + uint32_t id = 0; + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + if (filtered) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + else + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + + if (clamp) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + } + else + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + } + + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + return id; + } + + uint32_t DeleteTexture(const uint32_t id) override + { + glDeleteTextures(1, &id); + return id; + } + + void UpdateTexture(uint32_t id, olc::Sprite* spr) override + { + UNUSED(id); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, spr->width, spr->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spr->GetData()); + } + + void ReadTexture(uint32_t id, olc::Sprite* spr) override + { + glReadPixels(0, 0, spr->width, spr->height, GL_RGBA, GL_UNSIGNED_BYTE, spr->GetData()); + } + + void ApplyTexture(uint32_t id) override + { + glBindTexture(GL_TEXTURE_2D, id); + } + + void ClearBuffer(olc::Pixel p, bool bDepth) override + { + glClearColor(float(p.r) / 255.0f, float(p.g) / 255.0f, float(p.b) / 255.0f, float(p.a) / 255.0f); + glClear(GL_COLOR_BUFFER_BIT); + if (bDepth) glClear(GL_DEPTH_BUFFER_BIT); + } + + void UpdateViewport(const olc::vi2d& pos, const olc::vi2d& size) override + { + glViewport(pos.x, pos.y, size.x, size.y); + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | END RENDERER: OpenGL 1.0 (the original, the best...) | +// O------------------------------------------------------------------------------O +#pragma endregion + +#pragma region renderer_ogl33 +// O------------------------------------------------------------------------------O +// | START RENDERER: OpenGL 3.3 (3.0 es) (sh-sh-sh-shaders....) | +// O------------------------------------------------------------------------------O +#if defined(OLC_GFX_OPENGL33) + +#if defined(OLC_PLATFORM_WINAPI) + #include + //#include + #if !defined(__MINGW32__) + #pragma comment(lib, "Dwmapi.lib") + #endif + //typedef void __stdcall locSwapInterval_t(GLsizei n); + typedef HDC glDeviceContext_t; + typedef HGLRC glRenderContext_t; + //#define CALLSTYLE __stdcall + #define OGL_LOAD(t, n) (t*)wglGetProcAddress(#n) +#endif +// +//#if defined(__linux__) || defined(__FreeBSD__) +// #include +//#endif + +#if defined(OLC_PLATFORM_X11) + /*namespace X11 + { + #include + } + typedef int(locSwapInterval_t)(X11::Display* dpy, X11::GLXDrawable drawable, int interval);*/ + typedef X11::GLXContext glDeviceContext_t; + typedef X11::GLXContext glRenderContext_t; + //#define CALLSTYLE + #define OGL_LOAD(t, n) (t*)glXGetProcAddress((unsigned char*)#n); +#endif + +//#if defined(__APPLE__) +// #define GL_SILENCE_DEPRECATION +// #include +// #include +// #include +//#endif + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + #include + #include + #define GL_GLEXT_PROTOTYPES + #include + #include + #define CALLSTYLE + typedef EGLBoolean(locSwapInterval_t)(EGLDisplay display, EGLint interval); + #define GL_CLAMP GL_CLAMP_TO_EDGE + #define OGL_LOAD(t, n) n; +#endif + +namespace olc +{ +// typedef char GLchar; +// typedef ptrdiff_t GLsizeiptr; +// typedef GLuint CALLSTYLE locCreateShader_t(GLenum type); +// typedef GLuint CALLSTYLE locCreateProgram_t(void); +// typedef void CALLSTYLE locDeleteShader_t(GLuint shader); +//#if defined(OLC_PLATFORM_EMSCRIPTEN) +// typedef void CALLSTYLE locShaderSource_t(GLuint shader, GLsizei count, const GLchar* const* string, const GLint* length); +//#else +// typedef void CALLSTYLE locShaderSource_t(GLuint shader, GLsizei count, const GLchar** string, const GLint* length); +//#endif +// typedef void CALLSTYLE locCompileShader_t(GLuint shader); +// typedef void CALLSTYLE locLinkProgram_t(GLuint program); +// typedef void CALLSTYLE locDeleteProgram_t(GLuint program); +// typedef void CALLSTYLE locAttachShader_t(GLuint program, GLuint shader); +// typedef void CALLSTYLE locBindBuffer_t(GLenum target, GLuint buffer); +// typedef void CALLSTYLE locBufferData_t(GLenum target, GLsizeiptr size, const void* data, GLenum usage); +// typedef void CALLSTYLE locGenBuffers_t(GLsizei n, GLuint* buffers); +// typedef void CALLSTYLE locVertexAttribPointer_t(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer); +// typedef void CALLSTYLE locEnableVertexAttribArray_t(GLuint index); +// typedef void CALLSTYLE locUseProgram_t(GLuint program); +// typedef void CALLSTYLE locBindVertexArray_t(GLuint array); +// typedef void CALLSTYLE locGenVertexArrays_t(GLsizei n, GLuint* arrays); +// typedef void CALLSTYLE locGetShaderInfoLog_t(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog); +// typedef GLint CALLSTYLE locGetUniformLocation_t(GLuint program, const GLchar* name); +// typedef void CALLSTYLE locUniform1f_t(GLint location, GLfloat v0); +// typedef void CALLSTYLE locUniform1i_t(GLint location, GLint v0); +// typedef void CALLSTYLE locUniform2fv_t(GLint location, GLsizei count, const GLfloat* value); +// typedef void CALLSTYLE locActiveTexture_t(GLenum texture); +// typedef void CALLSTYLE locGenFrameBuffers_t(GLsizei n, GLuint* ids); +// typedef void CALLSTYLE locBindFrameBuffer_t(GLenum target, GLuint fb); +// typedef GLenum CALLSTYLE locCheckFrameBufferStatus_t(GLenum target); +// typedef void CALLSTYLE locDeleteFrameBuffers_t(GLsizei n, const GLuint* fbs); +// typedef void CALLSTYLE locFrameBufferTexture2D_t(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +// typedef void CALLSTYLE locDrawBuffers_t(GLsizei n, const GLenum* bufs); +// typedef void CALLSTYLE locBlendFuncSeparate_t(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); + + + + class Renderer_OGL33 : public olc::Renderer + { + private: +#if defined(OLC_PLATFORM_EMSCRIPTEN) + EGLDisplay olc_Display; + EGLConfig olc_Config; + EGLContext olc_Context; + EGLSurface olc_Surface; +#endif + +#if defined(OLC_PLATFORM_GLUT) + bool mFullScreen = false; +#else + #if !defined(OLC_PLATFORM_EMSCRIPTEN) + glDeviceContext_t glDeviceContext = 0; + glRenderContext_t glRenderContext = 0; + #endif +#endif + bool bSync = false; + olc::DecalMode nDecalMode = olc::DecalMode(-1); // Thanks Gusgo & Bispoo +#if defined(OLC_PLATFORM_X11) + X11::Display* olc_Display = nullptr; + X11::Window* olc_Window = nullptr; + X11::XVisualInfo* olc_VisualInfo = nullptr; +#endif + + private: + locCreateShader_t* locCreateShader = nullptr; + locShaderSource_t* locShaderSource = nullptr; + locCompileShader_t* locCompileShader = nullptr; + locDeleteShader_t* locDeleteShader = nullptr; + locCreateProgram_t* locCreateProgram = nullptr; + locDeleteProgram_t* locDeleteProgram = nullptr; + locLinkProgram_t* locLinkProgram = nullptr; + locAttachShader_t* locAttachShader = nullptr; + locBindBuffer_t* locBindBuffer = nullptr; + locBufferData_t* locBufferData = nullptr; + locGenBuffers_t* locGenBuffers = nullptr; + locVertexAttribPointer_t* locVertexAttribPointer = nullptr; + locEnableVertexAttribArray_t* locEnableVertexAttribArray = nullptr; + locUseProgram_t* locUseProgram = nullptr; + locBindVertexArray_t* locBindVertexArray = nullptr; + locGenVertexArrays_t* locGenVertexArrays = nullptr; + locSwapInterval_t* locSwapInterval = nullptr; + locGetShaderInfoLog_t* locGetShaderInfoLog = nullptr; + + uint32_t m_nFS = 0; + uint32_t m_nVS = 0; + uint32_t m_nQuadShader = 0; + uint32_t m_vbQuad = 0; + uint32_t m_vaQuad = 0; + + struct locVertex + { + float pos[3]; + olc::vf2d tex; + olc::Pixel col; + }; + + locVertex pVertexMem[OLC_MAX_VERTS]; + + olc::Renderable rendBlankQuad; + + public: + void PrepareDevice() override + { +#if defined(OLC_PLATFORM_GLUT) + //glutInit has to be called with main() arguments, make fake ones + int argc = 0; + char* argv[1] = { (char*)"" }; + glutInit(&argc, argv); + glutInitWindowPosition(0, 0); + glutInitWindowSize(512, 512); + glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGBA); + // Creates the window and the OpenGL context for it + glutCreateWindow("OneLoneCoder.com - Pixel Game Engine"); + glEnable(GL_TEXTURE_2D); // Turn on texturing + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); +#endif + } + + olc::rcode CreateDevice(std::vector params, bool bFullScreen, bool bVSYNC) override + { + // Create OpenGL Context +#if defined(OLC_PLATFORM_WINAPI) + // Create Device Context + glDeviceContext = GetDC((HWND)(params[0])); + PIXELFORMATDESCRIPTOR pfd = + { + sizeof(PIXELFORMATDESCRIPTOR), 1, + PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + PFD_MAIN_PLANE, 0, 0, 0, 0 + }; + + int pf = 0; + if (!(pf = ChoosePixelFormat(glDeviceContext, &pfd))) return olc::FAIL; + SetPixelFormat(glDeviceContext, pf, &pfd); + + if (!(glRenderContext = wglCreateContext(glDeviceContext))) return olc::FAIL; + wglMakeCurrent(glDeviceContext, glRenderContext); + + // Set Vertical Sync + locSwapInterval = OGL_LOAD(locSwapInterval_t, wglSwapIntervalEXT); + if (locSwapInterval && !bVSYNC) locSwapInterval(0); + bSync = bVSYNC; +#endif + +#if defined(OLC_PLATFORM_X11) + using namespace X11; + // Linux has tighter coupling between OpenGL and X11, so we store + // various "platform" handles in the renderer + olc_Display = (X11::Display*)(params[0]); + olc_Window = (X11::Window*)(params[1]); + olc_VisualInfo = (X11::XVisualInfo*)(params[2]); + + glDeviceContext = glXCreateContext(olc_Display, olc_VisualInfo, nullptr, GL_TRUE); + glXMakeCurrent(olc_Display, *olc_Window, glDeviceContext); + + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, *olc_Window, &gwa); + glViewport(0, 0, gwa.width, gwa.height); + + locSwapInterval = OGL_LOAD(locSwapInterval_t, glXSwapIntervalEXT); + + if (locSwapInterval == nullptr && !bVSYNC) + { + printf("NOTE: Could not disable VSYNC, glXSwapIntervalEXT() was not found!\n"); + printf(" Don't worry though, things will still work, it's just the\n"); + printf(" frame rate will be capped to your monitors refresh rate - javidx9\n"); + } + + if (locSwapInterval != nullptr && !bVSYNC) + locSwapInterval(olc_Display, *olc_Window, 0); +#endif + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + EGLint const attribute_list[] = { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_NONE }; + EGLint const context_config[] = { EGL_CONTEXT_CLIENT_VERSION , 2, EGL_NONE }; + EGLint num_config; + + olc_Display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + eglInitialize(olc_Display, nullptr, nullptr); + eglChooseConfig(olc_Display, attribute_list, &olc_Config, 1, &num_config); + + /* create an EGL rendering context */ + olc_Context = eglCreateContext(olc_Display, olc_Config, EGL_NO_CONTEXT, context_config); + olc_Surface = eglCreateWindowSurface(olc_Display, olc_Config, NULL, nullptr); + eglMakeCurrent(olc_Display, olc_Surface, olc_Surface, olc_Context); + //eglSwapInterval is currently a NOP, plement anyways in case it becomes supported + locSwapInterval = &eglSwapInterval; + locSwapInterval(olc_Display, bVSYNC ? 1 : 0); +#endif + +#if defined(OLC_PLATFORM_GLUT) + mFullScreen = bFullScreen; + if (!bVSYNC) + { +#if defined(__APPLE__) + GLint sync = 0; + CGLContextObj ctx = CGLGetCurrentContext(); + if (ctx) CGLSetParameter(ctx, kCGLCPSwapInterval, &sync); +#endif + } +#else + #if !defined(OLC_PLATFORM_EMSCRIPTEN) + glEnable(GL_TEXTURE_2D); // Turn on texturing + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); + #endif +#endif + // Load External OpenGL Functions + locCreateShader = OGL_LOAD(locCreateShader_t, glCreateShader); + locCompileShader = OGL_LOAD(locCompileShader_t, glCompileShader); + locShaderSource = OGL_LOAD(locShaderSource_t, glShaderSource); + locDeleteShader = OGL_LOAD(locDeleteShader_t, glDeleteShader); + locCreateProgram = OGL_LOAD(locCreateProgram_t, glCreateProgram); + locDeleteProgram = OGL_LOAD(locDeleteProgram_t, glDeleteProgram); + locLinkProgram = OGL_LOAD(locLinkProgram_t, glLinkProgram); + locAttachShader = OGL_LOAD(locAttachShader_t, glAttachShader); + locBindBuffer = OGL_LOAD(locBindBuffer_t, glBindBuffer); + locBufferData = OGL_LOAD(locBufferData_t, glBufferData); + locGenBuffers = OGL_LOAD(locGenBuffers_t, glGenBuffers); + locVertexAttribPointer = OGL_LOAD(locVertexAttribPointer_t, glVertexAttribPointer); + locEnableVertexAttribArray = OGL_LOAD(locEnableVertexAttribArray_t, glEnableVertexAttribArray); + locUseProgram = OGL_LOAD(locUseProgram_t, glUseProgram); + locGetShaderInfoLog = OGL_LOAD(locGetShaderInfoLog_t, glGetShaderInfoLog); +#if !defined(OLC_PLATFORM_EMSCRIPTEN) + locBindVertexArray = OGL_LOAD(locBindVertexArray_t, glBindVertexArray); + locGenVertexArrays = OGL_LOAD(locGenVertexArrays_t, glGenVertexArrays); +#else + locBindVertexArray = glBindVertexArrayOES; + locGenVertexArrays = glGenVertexArraysOES; +#endif + + // Load & Compile Quad Shader - assumes no errors + m_nFS = locCreateShader(0x8B30); + const GLchar* strFS = +#if defined(__arm__) || defined(OLC_PLATFORM_EMSCRIPTEN) + "#version 300 es\n" + "precision mediump float;" +#else + "#version 330 core\n" +#endif + "out vec4 pixel;\n""in vec2 oTex;\n" + "in vec4 oCol;\n""uniform sampler2D sprTex;\n""void main(){pixel = texture(sprTex, oTex) * oCol;}"; + locShaderSource(m_nFS, 1, &strFS, NULL); + locCompileShader(m_nFS); + + m_nVS = locCreateShader(0x8B31); + const GLchar* strVS = +#if defined(__arm__) || defined(OLC_PLATFORM_EMSCRIPTEN) + "#version 300 es\n" + "precision mediump float;" +#else + "#version 330 core\n" +#endif + "layout(location = 0) in vec3 aPos;\n""layout(location = 1) in vec2 aTex;\n" + "layout(location = 2) in vec4 aCol;\n""out vec2 oTex;\n""out vec4 oCol;\n" + "void main(){ float p = 1.0 / aPos.z; gl_Position = p * vec4(aPos.x, aPos.y, 0.0, 1.0); oTex = p * aTex; oCol = aCol;}"; + locShaderSource(m_nVS, 1, &strVS, NULL); + locCompileShader(m_nVS); + + m_nQuadShader = locCreateProgram(); + locAttachShader(m_nQuadShader, m_nFS); + locAttachShader(m_nQuadShader, m_nVS); + locLinkProgram(m_nQuadShader); + + // Create Quad + locGenBuffers(1, &m_vbQuad); + locGenVertexArrays(1, &m_vaQuad); + locBindVertexArray(m_vaQuad); + locBindBuffer(0x8892, m_vbQuad); + + locVertex verts[OLC_MAX_VERTS]; + locBufferData(0x8892, sizeof(locVertex) * OLC_MAX_VERTS, verts, 0x88E0); + locVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(locVertex), 0); locEnableVertexAttribArray(0); + locVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(locVertex), (void*)(3 * sizeof(float))); locEnableVertexAttribArray(1); + locVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(locVertex), (void*)(5 * sizeof(float))); locEnableVertexAttribArray(2); + locBindBuffer(0x8892, 0); + locBindVertexArray(0); + + // Create blank texture for spriteless decals + rendBlankQuad.Create(1, 1); + rendBlankQuad.Sprite()->GetData()[0] = olc::WHITE; + rendBlankQuad.Decal()->Update(); + return olc::rcode::OK; + } + + olc::rcode DestroyDevice() override + { +#if defined(OLC_PLATFORM_WINAPI) + wglDeleteContext(glRenderContext); +#endif + +#if defined(OLC_PLATFORM_X11) + glXMakeCurrent(olc_Display, None, NULL); + glXDestroyContext(olc_Display, glDeviceContext); +#endif + +#if defined(OLC_PLATFORM_GLUT) + glutDestroyWindow(glutGetWindow()); +#endif + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + eglMakeCurrent(olc_Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroyContext(olc_Display, olc_Context); + eglDestroySurface(olc_Display, olc_Surface); + eglTerminate(olc_Display); + olc_Display = EGL_NO_DISPLAY; + olc_Surface = EGL_NO_SURFACE; + olc_Context = EGL_NO_CONTEXT; +#endif + return olc::rcode::OK; + } + + void DisplayFrame() override + { +#if defined(OLC_PLATFORM_WINAPI) + SwapBuffers(glDeviceContext); + if (bSync) DwmFlush(); // Woooohooooooo!!!! SMOOOOOOOTH! +#endif + +#if defined(OLC_PLATFORM_X11) + X11::glXSwapBuffers(olc_Display, *olc_Window); +#endif + +#if defined(OLC_PLATFORM_GLUT) + glutSwapBuffers(); +#endif + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + eglSwapBuffers(olc_Display, olc_Surface); +#endif + } + + void PrepareDrawing() override + { + glEnable(GL_BLEND); + nDecalMode = DecalMode::NORMAL; + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + locUseProgram(m_nQuadShader); + locBindVertexArray(m_vaQuad); + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + locVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(locVertex), 0); locEnableVertexAttribArray(0); + locVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(locVertex), (void*)(3 * sizeof(float))); locEnableVertexAttribArray(1); + locVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(locVertex), (void*)(5 * sizeof(float))); locEnableVertexAttribArray(2); +#endif + } + + void SetDecalMode(const olc::DecalMode& mode) override + { + if (mode != nDecalMode) + { + switch (mode) + { + case olc::DecalMode::NORMAL: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); break; + case olc::DecalMode::ADDITIVE: glBlendFunc(GL_SRC_ALPHA, GL_ONE); break; + case olc::DecalMode::MULTIPLICATIVE: glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); break; + case olc::DecalMode::STENCIL: glBlendFunc(GL_ZERO, GL_SRC_ALPHA); break; + case olc::DecalMode::ILLUMINATE: glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA); break; + case olc::DecalMode::WIREFRAME: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); break; + } + + nDecalMode = mode; + } + } + + void DrawLayerQuad(const olc::vf2d& offset, const olc::vf2d& scale, const olc::Pixel tint) override + { + locBindBuffer(0x8892, m_vbQuad); + locVertex verts[4] = { + {{-1.0f, -1.0f, 1.0}, {0.0f * scale.x + offset.x, 1.0f * scale.y + offset.y}, tint}, + {{+1.0f, -1.0f, 1.0}, {1.0f * scale.x + offset.x, 1.0f * scale.y + offset.y}, tint}, + {{-1.0f, +1.0f, 1.0}, {0.0f * scale.x + offset.x, 0.0f * scale.y + offset.y}, tint}, + {{+1.0f, +1.0f, 1.0}, {1.0f * scale.x + offset.x, 0.0f * scale.y + offset.y}, tint}, + }; + + locBufferData(0x8892, sizeof(locVertex) * 4, verts, 0x88E0); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } + + void DrawDecal(const olc::DecalInstance& decal) override + { + SetDecalMode(decal.mode); + if (decal.decal == nullptr) + glBindTexture(GL_TEXTURE_2D, rendBlankQuad.Decal()->id); + else + glBindTexture(GL_TEXTURE_2D, decal.decal->id); + + locBindBuffer(0x8892, m_vbQuad); + + for (uint32_t i = 0; i < decal.points; i++) + pVertexMem[i] = { { decal.pos[i].x, decal.pos[i].y, decal.w[i] }, { decal.uv[i].x, decal.uv[i].y }, decal.tint[i] }; + + locBufferData(0x8892, sizeof(locVertex) * decal.points, pVertexMem, 0x88E0); + + if (nDecalMode == DecalMode::WIREFRAME) + glDrawArrays(GL_LINE_LOOP, 0, decal.points); + else + { + if (decal.structure == olc::DecalStructure::FAN) + glDrawArrays(GL_TRIANGLE_FAN, 0, decal.points); + else if (decal.structure == olc::DecalStructure::STRIP) + glDrawArrays(GL_TRIANGLE_STRIP, 0, decal.points); + else if (decal.structure == olc::DecalStructure::LIST) + glDrawArrays(GL_TRIANGLES, 0, decal.points); + } + } + + uint32_t CreateTexture(const uint32_t width, const uint32_t height, const bool filtered, const bool clamp) override + { + UNUSED(width); + UNUSED(height); + uint32_t id = 0; + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + + if (filtered) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + else + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + + if (clamp) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + } + else + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + } +#if !defined(OLC_PLATFORM_EMSCRIPTEN) + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); +#endif + return id; + } + + uint32_t DeleteTexture(const uint32_t id) override + { + glDeleteTextures(1, &id); + return id; + } + + void UpdateTexture(uint32_t id, olc::Sprite* spr) override + { + UNUSED(id); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, spr->width, spr->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spr->GetData()); + } + + void ReadTexture(uint32_t id, olc::Sprite* spr) override + { + glReadPixels(0, 0, spr->width, spr->height, GL_RGBA, GL_UNSIGNED_BYTE, spr->GetData()); + } + + void ApplyTexture(uint32_t id) override + { + glBindTexture(GL_TEXTURE_2D, id); + } + + void ClearBuffer(olc::Pixel p, bool bDepth) override + { + glClearColor(float(p.r) / 255.0f, float(p.g) / 255.0f, float(p.b) / 255.0f, float(p.a) / 255.0f); + glClear(GL_COLOR_BUFFER_BIT); + if (bDepth) glClear(GL_DEPTH_BUFFER_BIT); + } + + void UpdateViewport(const olc::vi2d& pos, const olc::vi2d& size) override + { + glViewport(pos.x, pos.y, size.x, size.y); + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | END RENDERER: OpenGL 3.3 (3.0 es) (sh-sh-sh-shaders....) | +// O------------------------------------------------------------------------------O +#pragma endregion + +// O------------------------------------------------------------------------------O +// | olcPixelGameEngine Image loaders | +// O------------------------------------------------------------------------------O + +#pragma region image_gdi +// O------------------------------------------------------------------------------O +// | START IMAGE LOADER: GDI+, Windows Only, always exists, a little slow | +// O------------------------------------------------------------------------------O +#if defined(OLC_IMAGE_GDI) + +#define min(a, b) ((a < b) ? a : b) +#define max(a, b) ((a > b) ? a : b) +#include +#include +#if defined(__MINGW32__) // Thanks Gusgo & Dandistine, but c'mon mingw!! wtf?! + #include +#else + #include +#endif +#include +#undef min +#undef max + +#if !defined(__MINGW32__) + #pragma comment(lib, "gdiplus.lib") + #pragma comment(lib, "Shlwapi.lib") +#endif + +namespace olc +{ + // Thanks @MaGetzUb for this, which allows sprites to be defined + // at construction, by initialising the GDI subsystem + static class GDIPlusStartup + { + public: + GDIPlusStartup() + { + Gdiplus::GdiplusStartupInput startupInput; + GdiplusStartup(&token, &startupInput, NULL); + } + + ULONG_PTR token; + + ~GDIPlusStartup() + { + // Well, MarcusTU thought this was important :D + Gdiplus::GdiplusShutdown(token); + } + } gdistartup; + + class ImageLoader_GDIPlus : public olc::ImageLoader + { + private: + std::wstring ConvertS2W(std::string s) + { +#ifdef __MINGW32__ + wchar_t* buffer = new wchar_t[s.length() + 1]; + mbstowcs(buffer, s.c_str(), s.length()); + buffer[s.length()] = L'\0'; +#else + int count = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0); + wchar_t* buffer = new wchar_t[count]; + MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, buffer, count); +#endif + std::wstring w(buffer); + delete[] buffer; + return w; + } + + public: + ImageLoader_GDIPlus() : ImageLoader() + {} + + olc::rcode LoadImageResource(olc::Sprite* spr, const std::string& sImageFile, olc::ResourcePack* pack) override + { + // clear out existing sprite + spr->pColData.clear(); + + // Open file + UNUSED(pack); + Gdiplus::Bitmap* bmp = nullptr; + if (pack != nullptr) + { + // Load sprite from input stream + ResourceBuffer rb = pack->GetFileBuffer(sImageFile); + bmp = Gdiplus::Bitmap::FromStream(SHCreateMemStream((BYTE*)rb.vMemory.data(), UINT(rb.vMemory.size()))); + } + else + { + // Check file exists + if (!_gfs::exists(sImageFile)) return olc::rcode::NO_FILE; + + // Load sprite from file + bmp = Gdiplus::Bitmap::FromFile(ConvertS2W(sImageFile).c_str()); + } + + if (bmp->GetLastStatus() != Gdiplus::Ok) return olc::rcode::FAIL; + spr->width = bmp->GetWidth(); + spr->height = bmp->GetHeight(); + + spr->pColData.resize(spr->width * spr->height); + + for (int y = 0; y < spr->height; y++) + for (int x = 0; x < spr->width; x++) + { + Gdiplus::Color c; + bmp->GetPixel(x, y, &c); + spr->SetPixel(x, y, olc::Pixel(c.GetRed(), c.GetGreen(), c.GetBlue(), c.GetAlpha())); + } + delete bmp; + return olc::rcode::OK; + } + + olc::rcode SaveImageResource(olc::Sprite* spr, const std::string& sImageFile) override + { + return olc::rcode::OK; + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | END IMAGE LOADER: GDI+ | +// O------------------------------------------------------------------------------O +#pragma endregion + +#pragma region image_libpng +// O------------------------------------------------------------------------------O +// | START IMAGE LOADER: libpng, default on linux, requires -lpng (libpng-dev) | +// O------------------------------------------------------------------------------O +#if defined(OLC_IMAGE_LIBPNG) +#include +namespace olc +{ + void pngReadStream(png_structp pngPtr, png_bytep data, png_size_t length) + { + png_voidp a = png_get_io_ptr(pngPtr); + ((std::istream*)a)->read((char*)data, length); + } + + class ImageLoader_LibPNG : public olc::ImageLoader + { + public: + ImageLoader_LibPNG() : ImageLoader() + {} + + olc::rcode LoadImageResource(olc::Sprite* spr, const std::string& sImageFile, olc::ResourcePack* pack) override + { + UNUSED(pack); + + // clear out existing sprite + spr->pColData.clear(); + + //////////////////////////////////////////////////////////////////////////// + // Use libpng, Thanks to Guillaume Cottenceau + // https://gist.github.com/niw/5963798 + // Also reading png from streams + // http://www.piko3d.net/tutorials/libpng-tutorial-loading-png-files-from-streams/ + png_structp png; + png_infop info; + + auto loadPNG = [&]() + { + png_read_info(png, info); + png_byte color_type; + png_byte bit_depth; + png_bytep* row_pointers; + spr->width = png_get_image_width(png, info); + spr->height = png_get_image_height(png, info); + color_type = png_get_color_type(png, info); + bit_depth = png_get_bit_depth(png, info); + if (bit_depth == 16) png_set_strip_16(png); + if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png); + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png); + if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png); + if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) + png_set_filler(png, 0xFF, PNG_FILLER_AFTER); + if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png); + png_read_update_info(png, info); + row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * spr->height); + for (int y = 0; y < spr->height; y++) { + row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png, info)); + } + png_read_image(png, row_pointers); + //////////////////////////////////////////////////////////////////////////// + // Create sprite array + spr->pColData.resize(spr->width * spr->height); + // Iterate through image rows, converting into sprite format + for (int y = 0; y < spr->height; y++) + { + png_bytep row = row_pointers[y]; + for (int x = 0; x < spr->width; x++) + { + png_bytep px = &(row[x * 4]); + spr->SetPixel(x, y, Pixel(px[0], px[1], px[2], px[3])); + } + } + + for (int y = 0; y < spr->height; y++) // Thanks maksym33 + free(row_pointers[y]); + free(row_pointers); + png_destroy_read_struct(&png, &info, nullptr); + }; + + png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png) goto fail_load; + + info = png_create_info_struct(png); + if (!info) goto fail_load; + + if (setjmp(png_jmpbuf(png))) goto fail_load; + + if (pack == nullptr) + { + FILE* f = fopen(sImageFile.c_str(), "rb"); + if (!f) return olc::rcode::NO_FILE; + png_init_io(png, f); + loadPNG(); + fclose(f); + } + else + { + ResourceBuffer rb = pack->GetFileBuffer(sImageFile); + std::istream is(&rb); + png_set_read_fn(png, (png_voidp)&is, pngReadStream); + loadPNG(); + } + + return olc::rcode::OK; + + fail_load: + spr->width = 0; + spr->height = 0; + spr->pColData.clear(); + return olc::rcode::FAIL; + } + + olc::rcode SaveImageResource(olc::Sprite* spr, const std::string& sImageFile) override + { + return olc::rcode::OK; + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | END IMAGE LOADER: | +// O------------------------------------------------------------------------------O +#pragma endregion + + +// O------------------------------------------------------------------------------O +// | olcPixelGameEngine Platforms | +// O------------------------------------------------------------------------------O + +#pragma region platform_windows +// O------------------------------------------------------------------------------O +// | START PLATFORM: MICROSOFT WINDOWS XP, VISTA, 7, 8, 10 | +// O------------------------------------------------------------------------------O +#if defined(OLC_PLATFORM_WINAPI) + +#if defined(_WIN32) && !defined(__MINGW32__) + #pragma comment(lib, "user32.lib") // Visual Studio Only + #pragma comment(lib, "gdi32.lib") // For other Windows Compilers please add + #pragma comment(lib, "opengl32.lib") // these libs to your linker input +#endif + +namespace olc +{ + class Platform_Windows : public olc::Platform + { + private: + HWND olc_hWnd = nullptr; + std::wstring wsAppName; + + std::wstring ConvertS2W(std::string s) + { +#ifdef __MINGW32__ + wchar_t* buffer = new wchar_t[s.length() + 1]; + mbstowcs(buffer, s.c_str(), s.length()); + buffer[s.length()] = L'\0'; +#else + int count = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0); + wchar_t* buffer = new wchar_t[count]; + MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, buffer, count); +#endif + std::wstring w(buffer); + delete[] buffer; + return w; + } + + + + public: + virtual olc::rcode ApplicationStartUp() override { return olc::rcode::OK; } + virtual olc::rcode ApplicationCleanUp() override { return olc::rcode::OK; } + virtual olc::rcode ThreadStartUp() override { return olc::rcode::OK; } + + virtual olc::rcode ThreadCleanUp() override + { + renderer->DestroyDevice(); + PostMessage(olc_hWnd, WM_DESTROY, 0, 0); + return olc::OK; + } + + virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) override + { + if (renderer->CreateDevice({ olc_hWnd }, bFullScreen, bEnableVSYNC) == olc::rcode::OK) + { + renderer->UpdateViewport(vViewPos, vViewSize); + return olc::rcode::OK; + } + else + return olc::rcode::FAIL; + } + + virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d& vWindowSize, bool bFullScreen) override + { + WNDCLASS wc; + wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wc.hInstance = GetModuleHandle(nullptr); + wc.lpfnWndProc = olc_WindowEvent; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.lpszMenuName = nullptr; + wc.hbrBackground = nullptr; + wc.lpszClassName = olcT("OLC_PIXEL_GAME_ENGINE"); + RegisterClass(&wc); + + // Define window furniture + DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + DWORD dwStyle = WS_CAPTION | WS_SYSMENU | WS_VISIBLE | WS_THICKFRAME; + + olc::vi2d vTopLeft = vWindowPos; + + // Handle Fullscreen + if (bFullScreen) + { + dwExStyle = 0; + dwStyle = WS_VISIBLE | WS_POPUP; + HMONITOR hmon = MonitorFromWindow(olc_hWnd, MONITOR_DEFAULTTONEAREST); + MONITORINFO mi = { sizeof(mi) }; + if (!GetMonitorInfo(hmon, &mi)) return olc::rcode::FAIL; + vWindowSize = { mi.rcMonitor.right, mi.rcMonitor.bottom }; + vTopLeft.x = 0; + vTopLeft.y = 0; + } + + // Keep client size as requested + RECT rWndRect = { 0, 0, vWindowSize.x, vWindowSize.y }; + AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); + int width = rWndRect.right - rWndRect.left; + int height = rWndRect.bottom - rWndRect.top; + + olc_hWnd = CreateWindowEx(dwExStyle, olcT("OLC_PIXEL_GAME_ENGINE"), olcT(""), dwStyle, + vTopLeft.x, vTopLeft.y, width, height, NULL, NULL, GetModuleHandle(nullptr), this); + + DragAcceptFiles(olc_hWnd, true); + + // Create Keyboard Mapping + mapKeys[0x00] = Key::NONE; + mapKeys[0x41] = Key::A; mapKeys[0x42] = Key::B; mapKeys[0x43] = Key::C; mapKeys[0x44] = Key::D; mapKeys[0x45] = Key::E; + mapKeys[0x46] = Key::F; mapKeys[0x47] = Key::G; mapKeys[0x48] = Key::H; mapKeys[0x49] = Key::I; mapKeys[0x4A] = Key::J; + mapKeys[0x4B] = Key::K; mapKeys[0x4C] = Key::L; mapKeys[0x4D] = Key::M; mapKeys[0x4E] = Key::N; mapKeys[0x4F] = Key::O; + mapKeys[0x50] = Key::P; mapKeys[0x51] = Key::Q; mapKeys[0x52] = Key::R; mapKeys[0x53] = Key::S; mapKeys[0x54] = Key::T; + mapKeys[0x55] = Key::U; mapKeys[0x56] = Key::V; mapKeys[0x57] = Key::W; mapKeys[0x58] = Key::X; mapKeys[0x59] = Key::Y; + mapKeys[0x5A] = Key::Z; + + mapKeys[VK_F1] = Key::F1; mapKeys[VK_F2] = Key::F2; mapKeys[VK_F3] = Key::F3; mapKeys[VK_F4] = Key::F4; + mapKeys[VK_F5] = Key::F5; mapKeys[VK_F6] = Key::F6; mapKeys[VK_F7] = Key::F7; mapKeys[VK_F8] = Key::F8; + mapKeys[VK_F9] = Key::F9; mapKeys[VK_F10] = Key::F10; mapKeys[VK_F11] = Key::F11; mapKeys[VK_F12] = Key::F12; + + mapKeys[VK_DOWN] = Key::DOWN; mapKeys[VK_LEFT] = Key::LEFT; mapKeys[VK_RIGHT] = Key::RIGHT; mapKeys[VK_UP] = Key::UP; + //mapKeys[VK_RETURN] = Key::ENTER;// mapKeys[VK_RETURN] = Key::RETURN; + + mapKeys[VK_BACK] = Key::BACK; mapKeys[VK_ESCAPE] = Key::ESCAPE; mapKeys[VK_RETURN] = Key::ENTER; mapKeys[VK_PAUSE] = Key::PAUSE; + mapKeys[VK_SCROLL] = Key::SCROLL; mapKeys[VK_TAB] = Key::TAB; mapKeys[VK_DELETE] = Key::DEL; mapKeys[VK_HOME] = Key::HOME; + mapKeys[VK_END] = Key::END; mapKeys[VK_PRIOR] = Key::PGUP; mapKeys[VK_NEXT] = Key::PGDN; mapKeys[VK_INSERT] = Key::INS; + mapKeys[VK_SHIFT] = Key::SHIFT; mapKeys[VK_CONTROL] = Key::CTRL; + mapKeys[VK_SPACE] = Key::SPACE; + + mapKeys[0x30] = Key::K0; mapKeys[0x31] = Key::K1; mapKeys[0x32] = Key::K2; mapKeys[0x33] = Key::K3; mapKeys[0x34] = Key::K4; + mapKeys[0x35] = Key::K5; mapKeys[0x36] = Key::K6; mapKeys[0x37] = Key::K7; mapKeys[0x38] = Key::K8; mapKeys[0x39] = Key::K9; + + mapKeys[VK_NUMPAD0] = Key::NP0; mapKeys[VK_NUMPAD1] = Key::NP1; mapKeys[VK_NUMPAD2] = Key::NP2; mapKeys[VK_NUMPAD3] = Key::NP3; mapKeys[VK_NUMPAD4] = Key::NP4; + mapKeys[VK_NUMPAD5] = Key::NP5; mapKeys[VK_NUMPAD6] = Key::NP6; mapKeys[VK_NUMPAD7] = Key::NP7; mapKeys[VK_NUMPAD8] = Key::NP8; mapKeys[VK_NUMPAD9] = Key::NP9; + mapKeys[VK_MULTIPLY] = Key::NP_MUL; mapKeys[VK_ADD] = Key::NP_ADD; mapKeys[VK_DIVIDE] = Key::NP_DIV; mapKeys[VK_SUBTRACT] = Key::NP_SUB; mapKeys[VK_DECIMAL] = Key::NP_DECIMAL; + + // Thanks scripticuk + mapKeys[VK_OEM_1] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[VK_OEM_2] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[VK_OEM_3] = Key::OEM_3; // On US keyboard this is the '~' key + mapKeys[VK_OEM_4] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[VK_OEM_5] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[VK_OEM_6] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[VK_OEM_7] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key + mapKeys[VK_OEM_8] = Key::OEM_8; // miscellaneous characters. Varies by keyboard + mapKeys[VK_OEM_PLUS] = Key::EQUALS; // the '+' key on any keyboard + mapKeys[VK_OEM_COMMA] = Key::COMMA; // the comma key on any keyboard + mapKeys[VK_OEM_MINUS] = Key::MINUS; // the minus key on any keyboard + mapKeys[VK_OEM_PERIOD] = Key::PERIOD; // the period key on any keyboard + mapKeys[VK_CAPITAL] = Key::CAPS_LOCK; + return olc::OK; + } + + virtual olc::rcode SetWindowTitle(const std::string& s) override + { +#ifdef UNICODE + SetWindowText(olc_hWnd, ConvertS2W(s).c_str()); +#else + SetWindowText(olc_hWnd, s.c_str()); +#endif + return olc::OK; + } + + virtual olc::rcode StartSystemEventLoop() override + { + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + return olc::OK; + } + + virtual olc::rcode HandleSystemEvent() override { return olc::rcode::FAIL; } + + // Windows Event Handler - this is statically connected to the windows event system + static LRESULT CALLBACK olc_WindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + switch (uMsg) + { + case WM_MOUSEMOVE: + { + // Thanks @ForAbby (Discord) + uint16_t x = lParam & 0xFFFF; uint16_t y = (lParam >> 16) & 0xFFFF; + int16_t ix = *(int16_t*)&x; int16_t iy = *(int16_t*)&y; + ptrPGE->olc_UpdateMouse(ix, iy); + return 0; + } + case WM_SIZE: ptrPGE->olc_UpdateWindowSize(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); return 0; + case WM_MOUSEWHEEL: ptrPGE->olc_UpdateMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); return 0; + case WM_MOUSELEAVE: ptrPGE->olc_UpdateMouseFocus(false); return 0; + case WM_SETFOCUS: ptrPGE->olc_UpdateKeyFocus(true); return 0; + case WM_KILLFOCUS: ptrPGE->olc_UpdateKeyFocus(false); return 0; + case WM_KEYDOWN: ptrPGE->olc_UpdateKeyState(mapKeys[wParam], true); return 0; + case WM_KEYUP: ptrPGE->olc_UpdateKeyState(mapKeys[wParam], false); return 0; + case WM_SYSKEYDOWN: ptrPGE->olc_UpdateKeyState(mapKeys[wParam], true); return 0; + case WM_SYSKEYUP: ptrPGE->olc_UpdateKeyState(mapKeys[wParam], false); return 0; + case WM_LBUTTONDOWN:ptrPGE->olc_UpdateMouseState(0, true); return 0; + case WM_LBUTTONUP: ptrPGE->olc_UpdateMouseState(0, false); return 0; + case WM_RBUTTONDOWN:ptrPGE->olc_UpdateMouseState(1, true); return 0; + case WM_RBUTTONUP: ptrPGE->olc_UpdateMouseState(1, false); return 0; + case WM_MBUTTONDOWN:ptrPGE->olc_UpdateMouseState(2, true); return 0; + case WM_MBUTTONUP: ptrPGE->olc_UpdateMouseState(2, false); return 0; + case WM_DROPFILES: + { + // This is all eww... + HDROP drop = (HDROP)wParam; + + uint32_t nFiles = DragQueryFile(drop, 0xFFFFFFFF, nullptr, 0); + std::vector vFiles; + for (uint32_t i = 0; i < nFiles; i++) + { + TCHAR dfbuffer[256]{}; + uint32_t len = DragQueryFile(drop, i, nullptr, 0); + DragQueryFile(drop, i, dfbuffer, 256); +#ifdef UNICODE + #ifdef __MINGW32__ + char* buffer = new char[len + 1]; + wcstombs(buffer, dfbuffer, len); + buffer[len] = '\0'; + #else + int count = WideCharToMultiByte(CP_UTF8, 0, dfbuffer, -1, NULL, 0, NULL, NULL); + char* buffer = new char[count]; + WideCharToMultiByte(CP_UTF8, 0, dfbuffer, -1, buffer, count, NULL, NULL); + #endif + vFiles.push_back(std::string(buffer)); + delete[] buffer; +#else + vFiles.push_back(std::string(dfbuffer)); +#endif + } + + // Even more eww... + POINT p; DragQueryPoint(drop, &p); + ptrPGE->olc_DropFiles(p.x, p.y, vFiles); + DragFinish(drop); + return 0; + } + break; + + + case WM_CLOSE: ptrPGE->olc_Terminate(); return 0; + case WM_DESTROY: PostQuitMessage(0); DestroyWindow(hWnd); return 0; + } + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | END PLATFORM: MICROSOFT WINDOWS XP, VISTA, 7, 8, 10 | +// O------------------------------------------------------------------------------O +#pragma endregion + +#pragma region platform_linux +// O------------------------------------------------------------------------------O +// | START PLATFORM: LINUX | +// O------------------------------------------------------------------------------O +#if defined(OLC_PLATFORM_X11) +namespace olc +{ + class Platform_Linux : public olc::Platform + { + private: + X11::Display* olc_Display = nullptr; + X11::Window olc_WindowRoot; + X11::Window olc_Window; + X11::XVisualInfo* olc_VisualInfo; + X11::Colormap olc_ColourMap; + X11::XSetWindowAttributes olc_SetWindowAttribs; + + public: + virtual olc::rcode ApplicationStartUp() override + { + return olc::rcode::OK; + } + + virtual olc::rcode ApplicationCleanUp() override + { + XDestroyWindow(olc_Display, olc_Window); + return olc::rcode::OK; + } + + virtual olc::rcode ThreadStartUp() override + { + return olc::rcode::OK; + } + + virtual olc::rcode ThreadCleanUp() override + { + renderer->DestroyDevice(); + return olc::OK; + } + + virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) override + { + if (renderer->CreateDevice({ olc_Display, &olc_Window, olc_VisualInfo }, bFullScreen, bEnableVSYNC) == olc::rcode::OK) + { + renderer->UpdateViewport(vViewPos, vViewSize); + return olc::rcode::OK; + } + else + return olc::rcode::FAIL; + } + + virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d& vWindowSize, bool bFullScreen) override + { + using namespace X11; + XInitThreads(); + + // Grab the deafult display and window + olc_Display = XOpenDisplay(NULL); + olc_WindowRoot = DefaultRootWindow(olc_Display); + + // Based on the display capabilities, configure the appearance of the window + GLint olc_GLAttribs[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None }; + olc_VisualInfo = glXChooseVisual(olc_Display, 0, olc_GLAttribs); + olc_ColourMap = XCreateColormap(olc_Display, olc_WindowRoot, olc_VisualInfo->visual, AllocNone); + olc_SetWindowAttribs.colormap = olc_ColourMap; + + // Register which events we are interested in receiving + olc_SetWindowAttribs.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | + ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask | StructureNotifyMask; + + // Create the window + olc_Window = XCreateWindow(olc_Display, olc_WindowRoot, vWindowPos.x, vWindowPos.y, + vWindowSize.x, vWindowSize.y, + 0, olc_VisualInfo->depth, InputOutput, olc_VisualInfo->visual, + CWColormap | CWEventMask, &olc_SetWindowAttribs); + + Atom wmDelete = XInternAtom(olc_Display, "WM_DELETE_WINDOW", true); + XSetWMProtocols(olc_Display, olc_Window, &wmDelete, 1); + + XMapWindow(olc_Display, olc_Window); + XStoreName(olc_Display, olc_Window, "OneLoneCoder.com - Pixel Game Engine"); + + if (bFullScreen) // Thanks DragonEye, again :D + { + Atom wm_state; + Atom fullscreen; + wm_state = XInternAtom(olc_Display, "_NET_WM_STATE", False); + fullscreen = XInternAtom(olc_Display, "_NET_WM_STATE_FULLSCREEN", False); + XEvent xev{ 0 }; + xev.type = ClientMessage; + xev.xclient.window = olc_Window; + xev.xclient.message_type = wm_state; + xev.xclient.format = 32; + xev.xclient.data.l[0] = (bFullScreen ? 1 : 0); // the action (0: off, 1: on, 2: toggle) + xev.xclient.data.l[1] = fullscreen; // first property to alter + xev.xclient.data.l[2] = 0; // second property to alter + xev.xclient.data.l[3] = 0; // source indication + XMapWindow(olc_Display, olc_Window); + XSendEvent(olc_Display, DefaultRootWindow(olc_Display), False, + SubstructureRedirectMask | SubstructureNotifyMask, &xev); + XFlush(olc_Display); + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, olc_Window, &gwa); + vWindowSize.x = gwa.width; + vWindowSize.y = gwa.height; + } + + // Create Keyboard Mapping + mapKeys[0x00] = Key::NONE; + mapKeys[0x61] = Key::A; mapKeys[0x62] = Key::B; mapKeys[0x63] = Key::C; mapKeys[0x64] = Key::D; mapKeys[0x65] = Key::E; + mapKeys[0x66] = Key::F; mapKeys[0x67] = Key::G; mapKeys[0x68] = Key::H; mapKeys[0x69] = Key::I; mapKeys[0x6A] = Key::J; + mapKeys[0x6B] = Key::K; mapKeys[0x6C] = Key::L; mapKeys[0x6D] = Key::M; mapKeys[0x6E] = Key::N; mapKeys[0x6F] = Key::O; + mapKeys[0x70] = Key::P; mapKeys[0x71] = Key::Q; mapKeys[0x72] = Key::R; mapKeys[0x73] = Key::S; mapKeys[0x74] = Key::T; + mapKeys[0x75] = Key::U; mapKeys[0x76] = Key::V; mapKeys[0x77] = Key::W; mapKeys[0x78] = Key::X; mapKeys[0x79] = Key::Y; + mapKeys[0x7A] = Key::Z; + + mapKeys[XK_F1] = Key::F1; mapKeys[XK_F2] = Key::F2; mapKeys[XK_F3] = Key::F3; mapKeys[XK_F4] = Key::F4; + mapKeys[XK_F5] = Key::F5; mapKeys[XK_F6] = Key::F6; mapKeys[XK_F7] = Key::F7; mapKeys[XK_F8] = Key::F8; + mapKeys[XK_F9] = Key::F9; mapKeys[XK_F10] = Key::F10; mapKeys[XK_F11] = Key::F11; mapKeys[XK_F12] = Key::F12; + + mapKeys[XK_Down] = Key::DOWN; mapKeys[XK_Left] = Key::LEFT; mapKeys[XK_Right] = Key::RIGHT; mapKeys[XK_Up] = Key::UP; + mapKeys[XK_KP_Enter] = Key::ENTER; mapKeys[XK_Return] = Key::ENTER; + + mapKeys[XK_BackSpace] = Key::BACK; mapKeys[XK_Escape] = Key::ESCAPE; mapKeys[XK_Linefeed] = Key::ENTER; mapKeys[XK_Pause] = Key::PAUSE; + mapKeys[XK_Scroll_Lock] = Key::SCROLL; mapKeys[XK_Tab] = Key::TAB; mapKeys[XK_Delete] = Key::DEL; mapKeys[XK_Home] = Key::HOME; + mapKeys[XK_End] = Key::END; mapKeys[XK_Page_Up] = Key::PGUP; mapKeys[XK_Page_Down] = Key::PGDN; mapKeys[XK_Insert] = Key::INS; + mapKeys[XK_Shift_L] = Key::SHIFT; mapKeys[XK_Shift_R] = Key::SHIFT; mapKeys[XK_Control_L] = Key::CTRL; mapKeys[XK_Control_R] = Key::CTRL; + mapKeys[XK_space] = Key::SPACE; mapKeys[XK_period] = Key::PERIOD; + + mapKeys[XK_0] = Key::K0; mapKeys[XK_1] = Key::K1; mapKeys[XK_2] = Key::K2; mapKeys[XK_3] = Key::K3; mapKeys[XK_4] = Key::K4; + mapKeys[XK_5] = Key::K5; mapKeys[XK_6] = Key::K6; mapKeys[XK_7] = Key::K7; mapKeys[XK_8] = Key::K8; mapKeys[XK_9] = Key::K9; + + mapKeys[XK_KP_0] = Key::NP0; mapKeys[XK_KP_1] = Key::NP1; mapKeys[XK_KP_2] = Key::NP2; mapKeys[XK_KP_3] = Key::NP3; mapKeys[XK_KP_4] = Key::NP4; + mapKeys[XK_KP_5] = Key::NP5; mapKeys[XK_KP_6] = Key::NP6; mapKeys[XK_KP_7] = Key::NP7; mapKeys[XK_KP_8] = Key::NP8; mapKeys[XK_KP_9] = Key::NP9; + mapKeys[XK_KP_Multiply] = Key::NP_MUL; mapKeys[XK_KP_Add] = Key::NP_ADD; mapKeys[XK_KP_Divide] = Key::NP_DIV; mapKeys[XK_KP_Subtract] = Key::NP_SUB; mapKeys[XK_KP_Decimal] = Key::NP_DECIMAL; + + // These keys vary depending on the keyboard. I've included comments for US and UK keyboard layouts + mapKeys[XK_semicolon] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[XK_slash] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[XK_asciitilde] = Key::OEM_3; // On US keyboard this is the '~' key + mapKeys[XK_bracketleft] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[XK_backslash] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[XK_bracketright] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[XK_apostrophe] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key + mapKeys[XK_numbersign] = Key::OEM_8; // miscellaneous characters. Varies by keyboard. I believe this to be the '#~' key on UK keyboards + mapKeys[XK_equal] = Key::EQUALS; // the '+' key on any keyboard + mapKeys[XK_comma] = Key::COMMA; // the comma key on any keyboard + mapKeys[XK_minus] = Key::MINUS; // the minus key on any keyboard + + mapKeys[XK_Caps_Lock] = Key::CAPS_LOCK; + + return olc::OK; + } + + virtual olc::rcode SetWindowTitle(const std::string& s) override + { + X11::XStoreName(olc_Display, olc_Window, s.c_str()); + return olc::OK; + } + + virtual olc::rcode StartSystemEventLoop() override + { + return olc::OK; + } + + virtual olc::rcode HandleSystemEvent() override + { + using namespace X11; + // Handle Xlib Message Loop - we do this in the + // same thread that OpenGL was created so we dont + // need to worry too much about multithreading with X11 + XEvent xev; + while (XPending(olc_Display)) + { + XNextEvent(olc_Display, &xev); + if (xev.type == Expose) + { + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, olc_Window, &gwa); + ptrPGE->olc_UpdateWindowSize(gwa.width, gwa.height); + } + else if (xev.type == ConfigureNotify) + { + XConfigureEvent xce = xev.xconfigure; + ptrPGE->olc_UpdateWindowSize(xce.width, xce.height); + } + else if (xev.type == KeyPress) + { + KeySym sym = XLookupKeysym(&xev.xkey, 0); + ptrPGE->olc_UpdateKeyState(mapKeys[sym], true); + XKeyEvent* e = (XKeyEvent*)&xev; // Because DragonEye loves numpads + XLookupString(e, NULL, 0, &sym, NULL); + ptrPGE->olc_UpdateKeyState(mapKeys[sym], true); + } + else if (xev.type == KeyRelease) + { + KeySym sym = XLookupKeysym(&xev.xkey, 0); + ptrPGE->olc_UpdateKeyState(mapKeys[sym], false); + XKeyEvent* e = (XKeyEvent*)&xev; + XLookupString(e, NULL, 0, &sym, NULL); + ptrPGE->olc_UpdateKeyState(mapKeys[sym], false); + } + else if (xev.type == ButtonPress) + { + switch (xev.xbutton.button) + { + case 1: ptrPGE->olc_UpdateMouseState(0, true); break; + case 2: ptrPGE->olc_UpdateMouseState(2, true); break; + case 3: ptrPGE->olc_UpdateMouseState(1, true); break; + case 4: ptrPGE->olc_UpdateMouseWheel(120); break; + case 5: ptrPGE->olc_UpdateMouseWheel(-120); break; + default: break; + } + } + else if (xev.type == ButtonRelease) + { + switch (xev.xbutton.button) + { + case 1: ptrPGE->olc_UpdateMouseState(0, false); break; + case 2: ptrPGE->olc_UpdateMouseState(2, false); break; + case 3: ptrPGE->olc_UpdateMouseState(1, false); break; + default: break; + } + } + else if (xev.type == MotionNotify) + { + ptrPGE->olc_UpdateMouse(xev.xmotion.x, xev.xmotion.y); + } + else if (xev.type == FocusIn) + { + ptrPGE->olc_UpdateKeyFocus(true); + } + else if (xev.type == FocusOut) + { + ptrPGE->olc_UpdateKeyFocus(false); + } + else if (xev.type == ClientMessage) + { + ptrPGE->olc_Terminate(); + } + } + return olc::OK; + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | END PLATFORM: LINUX | +// O------------------------------------------------------------------------------O +#pragma endregion + +#pragma region platform_glut +// O------------------------------------------------------------------------------O +// | START PLATFORM: GLUT (used to make it simple for Apple) | +// O------------------------------------------------------------------------------O +// +// VERY IMPORTANT!!! The Apple port was originally created by @Mumflr (discord) +// and the repo for the development of this project can be found here: +// https://github.com/MumflrFumperdink/olcPGEMac which contains maccy goodness +// and support on how to setup your build environment. +// +// "MASSIVE MASSIVE THANKS TO MUMFLR" - Javidx9 +#if defined(OLC_PLATFORM_GLUT) +namespace olc { + + class Platform_GLUT : public olc::Platform + { + public: + static std::atomic* bActiveRef; + + virtual olc::rcode ApplicationStartUp() override { + return olc::rcode::OK; + } + + virtual olc::rcode ApplicationCleanUp() override + { + return olc::rcode::OK; + } + + virtual olc::rcode ThreadStartUp() override + { + return olc::rcode::OK; + } + + virtual olc::rcode ThreadCleanUp() override + { + renderer->DestroyDevice(); + return olc::OK; + } + + virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) override + { + if (renderer->CreateDevice({}, bFullScreen, bEnableVSYNC) == olc::rcode::OK) + { + renderer->UpdateViewport(vViewPos, vViewSize); + return olc::rcode::OK; + } + else + return olc::rcode::FAIL; + } + + static void ExitMainLoop() { + if (!ptrPGE->OnUserDestroy()) { + *bActiveRef = true; + return; + } + platform->ThreadCleanUp(); + platform->ApplicationCleanUp(); + exit(0); + } + +#if defined(__APPLE__) + static void scrollWheelUpdate(id selff, SEL _sel, id theEvent) { + static const SEL deltaYSel = sel_registerName("deltaY"); + +#if defined(__aarch64__) // Thanks ruarq! + double deltaY = ((double (*)(id, SEL))objc_msgSend)(theEvent, deltaYSel); +#else + double deltaY = ((double (*)(id, SEL))objc_msgSend_fpret)(theEvent, deltaYSel); +#endif + + for (int i = 0; i < abs(deltaY); i++) { + if (deltaY > 0) { + ptrPGE->olc_UpdateMouseWheel(-1); + } + else if (deltaY < 0) { + ptrPGE->olc_UpdateMouseWheel(1); + } + } + } +#endif + static void ThreadFunct() { +#if defined(__APPLE__) + static bool hasEnabledCocoa = false; + if (!hasEnabledCocoa) { + // Objective-C Wizardry + Class NSApplicationClass = objc_getClass("NSApplication"); + + // NSApp = [NSApplication sharedApplication] + SEL sharedApplicationSel = sel_registerName("sharedApplication"); + id NSApp = ((id(*)(Class, SEL))objc_msgSend)(NSApplicationClass, sharedApplicationSel); + // window = [NSApp mainWindow] + SEL mainWindowSel = sel_registerName("mainWindow"); + id window = ((id(*)(id, SEL))objc_msgSend)(NSApp, mainWindowSel); + + // [window setStyleMask: NSWindowStyleMaskClosable | ~NSWindowStyleMaskResizable] + SEL setStyleMaskSel = sel_registerName("setStyleMask:"); + ((void (*)(id, SEL, NSUInteger))objc_msgSend)(window, setStyleMaskSel, 7); + + hasEnabledCocoa = true; + } +#endif + if (!*bActiveRef) { + ExitMainLoop(); + return; + } + glutPostRedisplay(); + } + + static void DrawFunct() { + ptrPGE->olc_CoreUpdate(); + } + + virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d& vWindowSize, bool bFullScreen) override + { +#if defined(__APPLE__) + Class GLUTViewClass = objc_getClass("GLUTView"); + + SEL scrollWheelSel = sel_registerName("scrollWheel:"); + bool resultAddMethod = class_addMethod(GLUTViewClass, scrollWheelSel, (IMP)scrollWheelUpdate, "v@:@"); + assert(resultAddMethod); +#endif + + renderer->PrepareDevice(); + + if (bFullScreen) + { + vWindowSize.x = glutGet(GLUT_SCREEN_WIDTH); + vWindowSize.y = glutGet(GLUT_SCREEN_HEIGHT); + glutFullScreen(); + } + else + { + if (vWindowSize.x > glutGet(GLUT_SCREEN_WIDTH) || vWindowSize.y > glutGet(GLUT_SCREEN_HEIGHT)) + { + perror("ERROR: The specified window dimensions do not fit on your screen\n"); + return olc::FAIL; + } + glutReshapeWindow(vWindowSize.x, vWindowSize.y - 1); + } + + // Create Keyboard Mapping + mapKeys[0x00] = Key::NONE; + mapKeys['A'] = Key::A; mapKeys['B'] = Key::B; mapKeys['C'] = Key::C; mapKeys['D'] = Key::D; mapKeys['E'] = Key::E; + mapKeys['F'] = Key::F; mapKeys['G'] = Key::G; mapKeys['H'] = Key::H; mapKeys['I'] = Key::I; mapKeys['J'] = Key::J; + mapKeys['K'] = Key::K; mapKeys['L'] = Key::L; mapKeys['M'] = Key::M; mapKeys['N'] = Key::N; mapKeys['O'] = Key::O; + mapKeys['P'] = Key::P; mapKeys['Q'] = Key::Q; mapKeys['R'] = Key::R; mapKeys['S'] = Key::S; mapKeys['T'] = Key::T; + mapKeys['U'] = Key::U; mapKeys['V'] = Key::V; mapKeys['W'] = Key::W; mapKeys['X'] = Key::X; mapKeys['Y'] = Key::Y; + mapKeys['Z'] = Key::Z; + + mapKeys[GLUT_KEY_F1] = Key::F1; mapKeys[GLUT_KEY_F2] = Key::F2; mapKeys[GLUT_KEY_F3] = Key::F3; mapKeys[GLUT_KEY_F4] = Key::F4; + mapKeys[GLUT_KEY_F5] = Key::F5; mapKeys[GLUT_KEY_F6] = Key::F6; mapKeys[GLUT_KEY_F7] = Key::F7; mapKeys[GLUT_KEY_F8] = Key::F8; + mapKeys[GLUT_KEY_F9] = Key::F9; mapKeys[GLUT_KEY_F10] = Key::F10; mapKeys[GLUT_KEY_F11] = Key::F11; mapKeys[GLUT_KEY_F12] = Key::F12; + + mapKeys[GLUT_KEY_DOWN] = Key::DOWN; mapKeys[GLUT_KEY_LEFT] = Key::LEFT; mapKeys[GLUT_KEY_RIGHT] = Key::RIGHT; mapKeys[GLUT_KEY_UP] = Key::UP; + mapKeys[13] = Key::ENTER; + + mapKeys[127] = Key::BACK; mapKeys[27] = Key::ESCAPE; + mapKeys[9] = Key::TAB; mapKeys[GLUT_KEY_HOME] = Key::HOME; + mapKeys[GLUT_KEY_END] = Key::END; mapKeys[GLUT_KEY_PAGE_UP] = Key::PGUP; mapKeys[GLUT_KEY_PAGE_DOWN] = Key::PGDN; mapKeys[GLUT_KEY_INSERT] = Key::INS; + mapKeys[32] = Key::SPACE; mapKeys[46] = Key::PERIOD; + + mapKeys[48] = Key::K0; mapKeys[49] = Key::K1; mapKeys[50] = Key::K2; mapKeys[51] = Key::K3; mapKeys[52] = Key::K4; + mapKeys[53] = Key::K5; mapKeys[54] = Key::K6; mapKeys[55] = Key::K7; mapKeys[56] = Key::K8; mapKeys[57] = Key::K9; + + // NOTE: MISSING KEYS :O + + glutKeyboardFunc([](unsigned char key, int x, int y) -> void { + switch (glutGetModifiers()) { + case 0: //This is when there are no modifiers + if ('a' <= key && key <= 'z') key -= 32; + break; + case GLUT_ACTIVE_SHIFT: + ptrPGE->olc_UpdateKeyState(Key::SHIFT, true); + break; + case GLUT_ACTIVE_CTRL: + if ('a' <= key && key <= 'z') key -= 32; + ptrPGE->olc_UpdateKeyState(Key::CTRL, true); + break; + case GLUT_ACTIVE_ALT: + if ('a' <= key && key <= 'z') key -= 32; + break; + } + + if (mapKeys[key]) + ptrPGE->olc_UpdateKeyState(mapKeys[key], true); + }); + + glutKeyboardUpFunc([](unsigned char key, int x, int y) -> void { + switch (glutGetModifiers()) { + case 0: //This is when there are no modifiers + if ('a' <= key && key <= 'z') key -= 32; + break; + case GLUT_ACTIVE_SHIFT: + ptrPGE->olc_UpdateKeyState(Key::SHIFT, false); + break; + case GLUT_ACTIVE_CTRL: + if ('a' <= key && key <= 'z') key -= 32; + ptrPGE->olc_UpdateKeyState(Key::CTRL, false); + break; + case GLUT_ACTIVE_ALT: + if ('a' <= key && key <= 'z') key -= 32; + //No ALT in PGE + break; + } + + if (mapKeys[key]) + ptrPGE->olc_UpdateKeyState(mapKeys[key], false); + }); + + //Special keys + glutSpecialFunc([](int key, int x, int y) -> void { + if (mapKeys[key]) + ptrPGE->olc_UpdateKeyState(mapKeys[key], true); + }); + + glutSpecialUpFunc([](int key, int x, int y) -> void { + if (mapKeys[key]) + ptrPGE->olc_UpdateKeyState(mapKeys[key], false); + }); + + glutMouseFunc([](int button, int state, int x, int y) -> void { + switch (button) { + case GLUT_LEFT_BUTTON: + if (state == GLUT_UP) ptrPGE->olc_UpdateMouseState(0, false); + else if (state == GLUT_DOWN) ptrPGE->olc_UpdateMouseState(0, true); + break; + case GLUT_MIDDLE_BUTTON: + if (state == GLUT_UP) ptrPGE->olc_UpdateMouseState(2, false); + else if (state == GLUT_DOWN) ptrPGE->olc_UpdateMouseState(2, true); + break; + case GLUT_RIGHT_BUTTON: + if (state == GLUT_UP) ptrPGE->olc_UpdateMouseState(1, false); + else if (state == GLUT_DOWN) ptrPGE->olc_UpdateMouseState(1, true); + break; + } + }); + + auto mouseMoveCall = [](int x, int y) -> void { + ptrPGE->olc_UpdateMouse(x, y); + }; + + glutMotionFunc(mouseMoveCall); + glutPassiveMotionFunc(mouseMoveCall); + + glutEntryFunc([](int state) -> void { + if (state == GLUT_ENTERED) ptrPGE->olc_UpdateKeyFocus(true); + else if (state == GLUT_LEFT) ptrPGE->olc_UpdateKeyFocus(false); + }); + + glutDisplayFunc(DrawFunct); + glutIdleFunc(ThreadFunct); + + return olc::OK; + } + + virtual olc::rcode SetWindowTitle(const std::string& s) override + { + glutSetWindowTitle(s.c_str()); + return olc::OK; + } + + virtual olc::rcode StartSystemEventLoop() override { + glutMainLoop(); + return olc::OK; + } + + virtual olc::rcode HandleSystemEvent() override + { + return olc::OK; + } + }; + + std::atomic* Platform_GLUT::bActiveRef{ nullptr }; + + //Custom Start + olc::rcode PixelGameEngine::Start() + { + if (platform->ApplicationStartUp() != olc::OK) return olc::FAIL; + + // Construct the window + if (platform->CreateWindowPane({ 30,30 }, vWindowSize, bFullScreen) != olc::OK) return olc::FAIL; + olc_UpdateWindowSize(vWindowSize.x, vWindowSize.y); + + if (platform->ThreadStartUp() == olc::FAIL) return olc::FAIL; + olc_PrepareEngine(); + if (!OnUserCreate()) return olc::FAIL; + Platform_GLUT::bActiveRef = &bAtomActive; + glutWMCloseFunc(Platform_GLUT::ExitMainLoop); + bAtomActive = true; + platform->StartSystemEventLoop(); + + //This code will not even be run but why not + if (platform->ApplicationCleanUp() != olc::OK) return olc::FAIL; + + return olc::OK; + } +} + +#endif +// O------------------------------------------------------------------------------O +// | END PLATFORM: GLUT | +// O------------------------------------------------------------------------------O +#pragma endregion + + + + + +#pragma region platform_emscripten +// O------------------------------------------------------------------------------O +// | START PLATFORM: Emscripten - Totally Game Changing... | +// O------------------------------------------------------------------------------O + +// +// Firstly a big mega thank you to members of the OLC Community for sorting this +// out. Making a browser compatible version has been a priority for quite some +// time, but I lacked the expertise to do it. This awesome feature is possible +// because a group of former strangers got together and formed friendships over +// their shared passion for code. If anything demonstrates how powerful helping +// each other can be, it's this. - Javidx9 + +// Emscripten Platform: MaGetzUb, Moros1138, Slavka, Dandistine, Gorbit99, Bispoo +// also: Ishidex, Gusgo99, SlicEnDicE, Alexio + + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + +#include +#include + +extern "C" +{ + EMSCRIPTEN_KEEPALIVE inline int olc_OnPageUnload() + { olc::platform->ApplicationCleanUp(); return 0; } +} + +namespace olc +{ + class Platform_Emscripten : public olc::Platform + { + public: + + virtual olc::rcode ApplicationStartUp() override + { return olc::rcode::OK; } + + virtual olc::rcode ApplicationCleanUp() override + { ThreadCleanUp(); return olc::rcode::OK; } + + virtual olc::rcode ThreadStartUp() override + { return olc::rcode::OK; } + + virtual olc::rcode ThreadCleanUp() override + { renderer->DestroyDevice(); return olc::OK; } + + virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) override + { + if (renderer->CreateDevice({}, bFullScreen, bEnableVSYNC) == olc::rcode::OK) + { + renderer->UpdateViewport(vViewPos, vViewSize); + return olc::rcode::OK; + } + else + return olc::rcode::FAIL; + } + + virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d& vWindowSize, bool bFullScreen) override + { + emscripten_set_canvas_element_size("#canvas", vWindowSize.x, vWindowSize.y); + + mapKeys[DOM_PK_UNKNOWN] = Key::NONE; + mapKeys[DOM_PK_A] = Key::A; mapKeys[DOM_PK_B] = Key::B; mapKeys[DOM_PK_C] = Key::C; mapKeys[DOM_PK_D] = Key::D; + mapKeys[DOM_PK_E] = Key::E; mapKeys[DOM_PK_F] = Key::F; mapKeys[DOM_PK_G] = Key::G; mapKeys[DOM_PK_H] = Key::H; + mapKeys[DOM_PK_I] = Key::I; mapKeys[DOM_PK_J] = Key::J; mapKeys[DOM_PK_K] = Key::K; mapKeys[DOM_PK_L] = Key::L; + mapKeys[DOM_PK_M] = Key::M; mapKeys[DOM_PK_N] = Key::N; mapKeys[DOM_PK_O] = Key::O; mapKeys[DOM_PK_P] = Key::P; + mapKeys[DOM_PK_Q] = Key::Q; mapKeys[DOM_PK_R] = Key::R; mapKeys[DOM_PK_S] = Key::S; mapKeys[DOM_PK_T] = Key::T; + mapKeys[DOM_PK_U] = Key::U; mapKeys[DOM_PK_V] = Key::V; mapKeys[DOM_PK_W] = Key::W; mapKeys[DOM_PK_X] = Key::X; + mapKeys[DOM_PK_Y] = Key::Y; mapKeys[DOM_PK_Z] = Key::Z; + mapKeys[DOM_PK_0] = Key::K0; mapKeys[DOM_PK_1] = Key::K1; mapKeys[DOM_PK_2] = Key::K2; + mapKeys[DOM_PK_3] = Key::K3; mapKeys[DOM_PK_4] = Key::K4; mapKeys[DOM_PK_5] = Key::K5; + mapKeys[DOM_PK_6] = Key::K6; mapKeys[DOM_PK_7] = Key::K7; mapKeys[DOM_PK_8] = Key::K8; + mapKeys[DOM_PK_9] = Key::K9; + mapKeys[DOM_PK_F1] = Key::F1; mapKeys[DOM_PK_F2] = Key::F2; mapKeys[DOM_PK_F3] = Key::F3; mapKeys[DOM_PK_F4] = Key::F4; + mapKeys[DOM_PK_F5] = Key::F5; mapKeys[DOM_PK_F6] = Key::F6; mapKeys[DOM_PK_F7] = Key::F7; mapKeys[DOM_PK_F8] = Key::F8; + mapKeys[DOM_PK_F9] = Key::F9; mapKeys[DOM_PK_F10] = Key::F10; mapKeys[DOM_PK_F11] = Key::F11; mapKeys[DOM_PK_F12] = Key::F12; + mapKeys[DOM_PK_ARROW_UP] = Key::UP; mapKeys[DOM_PK_ARROW_DOWN] = Key::DOWN; + mapKeys[DOM_PK_ARROW_LEFT] = Key::LEFT; mapKeys[DOM_PK_ARROW_RIGHT] = Key::RIGHT; + mapKeys[DOM_PK_SPACE] = Key::SPACE; mapKeys[DOM_PK_TAB] = Key::TAB; + mapKeys[DOM_PK_SHIFT_LEFT] = Key::SHIFT; mapKeys[DOM_PK_SHIFT_RIGHT] = Key::SHIFT; + mapKeys[DOM_PK_CONTROL_LEFT] = Key::CTRL; mapKeys[DOM_PK_CONTROL_RIGHT] = Key::CTRL; + mapKeys[DOM_PK_INSERT] = Key::INS; mapKeys[DOM_PK_DELETE] = Key::DEL; mapKeys[DOM_PK_HOME] = Key::HOME; + mapKeys[DOM_PK_END] = Key::END; mapKeys[DOM_PK_PAGE_UP] = Key::PGUP; mapKeys[DOM_PK_PAGE_DOWN] = Key::PGDN; + mapKeys[DOM_PK_BACKSPACE] = Key::BACK; mapKeys[DOM_PK_ESCAPE] = Key::ESCAPE; + mapKeys[DOM_PK_ENTER] = Key::ENTER; mapKeys[DOM_PK_NUMPAD_EQUAL] = Key::EQUALS; + mapKeys[DOM_PK_NUMPAD_ENTER] = Key::ENTER; mapKeys[DOM_PK_PAUSE] = Key::PAUSE; + mapKeys[DOM_PK_SCROLL_LOCK] = Key::SCROLL; + mapKeys[DOM_PK_NUMPAD_0] = Key::NP0; mapKeys[DOM_PK_NUMPAD_1] = Key::NP1; mapKeys[DOM_PK_NUMPAD_2] = Key::NP2; + mapKeys[DOM_PK_NUMPAD_3] = Key::NP3; mapKeys[DOM_PK_NUMPAD_4] = Key::NP4; mapKeys[DOM_PK_NUMPAD_5] = Key::NP5; + mapKeys[DOM_PK_NUMPAD_6] = Key::NP6; mapKeys[DOM_PK_NUMPAD_7] = Key::NP7; mapKeys[DOM_PK_NUMPAD_8] = Key::NP8; + mapKeys[DOM_PK_NUMPAD_9] = Key::NP9; + mapKeys[DOM_PK_NUMPAD_MULTIPLY] = Key::NP_MUL; mapKeys[DOM_PK_NUMPAD_DIVIDE] = Key::NP_DIV; + mapKeys[DOM_PK_NUMPAD_ADD] = Key::NP_ADD; mapKeys[DOM_PK_NUMPAD_SUBTRACT] = Key::NP_SUB; + mapKeys[DOM_PK_NUMPAD_DECIMAL] = Key::NP_DECIMAL; + mapKeys[DOM_PK_PERIOD] = Key::PERIOD; mapKeys[DOM_PK_EQUAL] = Key::EQUALS; + mapKeys[DOM_PK_COMMA] = Key::COMMA; mapKeys[DOM_PK_MINUS] = Key::MINUS; + mapKeys[DOM_PK_CAPS_LOCK] = Key::CAPS_LOCK; + mapKeys[DOM_PK_SEMICOLON] = Key::OEM_1; mapKeys[DOM_PK_SLASH] = Key::OEM_2; mapKeys[DOM_PK_BACKQUOTE] = Key::OEM_3; + mapKeys[DOM_PK_BRACKET_LEFT] = Key::OEM_4; mapKeys[DOM_PK_BACKSLASH] = Key::OEM_5; mapKeys[DOM_PK_BRACKET_RIGHT] = Key::OEM_6; + mapKeys[DOM_PK_QUOTE] = Key::OEM_7; mapKeys[DOM_PK_BACKSLASH] = Key::OEM_8; + + // Keyboard Callbacks + emscripten_set_keydown_callback("#canvas", 0, 1, keyboard_callback); + emscripten_set_keyup_callback("#canvas", 0, 1, keyboard_callback); + + // Mouse Callbacks + emscripten_set_wheel_callback("#canvas", 0, 1, wheel_callback); + emscripten_set_mousedown_callback("#canvas", 0, 1, mouse_callback); + emscripten_set_mouseup_callback("#canvas", 0, 1, mouse_callback); + emscripten_set_mousemove_callback("#canvas", 0, 1, mouse_callback); + + // Touch Callbacks + emscripten_set_touchstart_callback("#canvas", 0, 1, touch_callback); + emscripten_set_touchmove_callback("#canvas", 0, 1, touch_callback); + emscripten_set_touchend_callback("#canvas", 0, 1, touch_callback); + + // Canvas Focus Callbacks + emscripten_set_blur_callback("#canvas", 0, 1, focus_callback); + emscripten_set_focus_callback("#canvas", 0, 1, focus_callback); + +#pragma warning disable format + EM_ASM( window.onunload = Module._olc_OnPageUnload; ); + + // IMPORTANT! - Sorry About This... + // + // In order to handle certain browser based events, such as resizing and + // going to full screen, we have to effectively inject code into the container + // running the PGE. Yes, I vomited about 11 times too when the others were + // convincing me this is the future. Well, this isnt the future, and if it + // were to be, I want no part of what must be a miserable distopian free + // for all of anarchic code injection to get rudimentary events like "Resize()". + // + // Wake up people! Of course theres a spoon. There has to be to keep feeding + // the giant web baby. + + + EM_ASM({ + + // olc_ApsectRatio + // + // Used by olc_ResizeHandler to calculate the viewport from the + // dimensions of the canvas container's element. + Module.olc_AspectRatio = $0 / $1; + + // HACK ALERT! + // + // Here we assume any html shell that uses 3 or more instance of the class "emscripten" + // is using one of the default or minimal emscripten page layouts + Module.olc_AssumeDefaultShells = (document.querySelectorAll('.emscripten').length >= 3) ? true : false; + + // olc_ResizeHandler + // + // Used by olc_Init, and is called when a resize observer and fullscreenchange event is triggered. + var olc_ResizeHandler = function() + { + // are we in fullscreen mode? + let isFullscreen = (document.fullscreenElement != null); + + // get the width of the containing element + let width = (isFullscreen) ? window.innerWidth : Module.canvas.parentNode.clientWidth; + let height = (isFullscreen) ? window.innerHeight : Module.canvas.parentNode.clientHeight; + + // calculate the expected viewport size + let viewWidth = width; + let viewHeight = width / Module.olc_AspectRatio; + + // if we're taller than the containing element, recalculate based on height + if(viewHeight > height) + { + viewWidth = height * Module.olc_AspectRatio; + viewHeight = height; + } + + // ensure resulting viewport is in integer space + viewWidth = parseInt(viewWidth); + viewHeight = parseInt(viewHeight); + + setTimeout(function() + { + // if default shells, apply default styles + if(Module.olc_AssumeDefaultShells) + Module.canvas.parentNode.setAttribute('style', 'width: 100%; height: 70vh; margin-left: auto; margin-right: auto;'); + + // apply viewport dimensions to teh canvas + Module.canvas.setAttribute('width', viewWidth); + Module.canvas.setAttribute('height', viewHeight); + Module.canvas.setAttribute('style', `width: ${viewWidth}px; height: ${viewHeight}px;`); + + // update the PGE window size + Module._olc_PGE_UpdateWindowSize(viewWidth, viewHeight); + + // force focus on our PGE canvas + Module.canvas.focus(); + }, 200); + }; + + + // olc_Init + // + // set up resize observer and fullscreenchange event handler + var olc_Init = function() + { + if(Module.olc_AspectRatio === undefined) + { + setTimeout(function() { Module.olc_Init(); }, 50); + return; + } + + let resizeObserver = new ResizeObserver(function(entries) + { + Module.olc_ResizeHandler(); + }).observe(Module.canvas.parentNode); + + let mutationObserver = new MutationObserver(function(mutationsList, observer) + { + setTimeout(function() { Module.olc_ResizeHandler(); }, 200); + }).observe(Module.canvas.parentNode, { attributes: false, childList: true, subtree: false }); + + window.addEventListener('fullscreenchange', function(e) + { + setTimeout(function() { Module.olc_ResizeHandler();}, 200); + }); + }; + + // set up hooks + Module.olc_ResizeHandler = (Module.olc_ResizeHandler != undefined) ? Module.olc_ResizeHandler : olc_ResizeHandler; + Module.olc_Init = (Module.olc_Init != undefined) ? Module.olc_Init : olc_Init; + + // run everything! + Module.olc_Init(); + + }, vWindowSize.x, vWindowSize.y); // Fullscreen and Resize Observers +#pragma warning restore format + return olc::rcode::OK; + } + + // Interface PGE's UpdateWindowSize, for use in Javascript + void UpdateWindowSize(int width, int height) + { + ptrPGE->olc_UpdateWindowSize(width, height); + } + + //TY Gorbit + static EM_BOOL focus_callback(int eventType, const EmscriptenFocusEvent* focusEvent, void* userData) + { + if (eventType == EMSCRIPTEN_EVENT_BLUR) + { + ptrPGE->olc_UpdateKeyFocus(false); + ptrPGE->olc_UpdateMouseFocus(false); + } + else if (eventType == EMSCRIPTEN_EVENT_FOCUS) + { + ptrPGE->olc_UpdateKeyFocus(true); + ptrPGE->olc_UpdateMouseFocus(true); + } + + return 0; + } + + //TY Moros + static EM_BOOL keyboard_callback(int eventType, const EmscriptenKeyboardEvent* e, void* userData) + { + if (eventType == EMSCRIPTEN_EVENT_KEYDOWN) + ptrPGE->olc_UpdateKeyState(mapKeys[emscripten_compute_dom_pk_code(e->code)], true); + + // THANK GOD!! for this compute function. And thanks Dandistine for pointing it out! + if (eventType == EMSCRIPTEN_EVENT_KEYUP) + ptrPGE->olc_UpdateKeyState(mapKeys[emscripten_compute_dom_pk_code(e->code)], false); + + //Consume keyboard events so that keys like F1 and F5 don't do weird things + return EM_TRUE; + } + + //TY Moros + static EM_BOOL wheel_callback(int eventType, const EmscriptenWheelEvent* e, void* userData) + { + if (eventType == EMSCRIPTEN_EVENT_WHEEL) + ptrPGE->olc_UpdateMouseWheel(-1 * e->deltaY); + + return EM_TRUE; + } + + //TY Bispoo + static EM_BOOL touch_callback(int eventType, const EmscriptenTouchEvent* e, void* userData) + { + // Move + if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) + { + ptrPGE->olc_UpdateMouse(e->touches->targetX, e->touches->targetY); + } + + // Start + if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) + { + ptrPGE->olc_UpdateMouse(e->touches->targetX, e->touches->targetY); + ptrPGE->olc_UpdateMouseState(0, true); + } + + // End + if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) + { + ptrPGE->olc_UpdateMouseState(0, false); + } + + return EM_TRUE; + } + + //TY Moros + static EM_BOOL mouse_callback(int eventType, const EmscriptenMouseEvent* e, void* userData) + { + //Mouse Movement + if (eventType == EMSCRIPTEN_EVENT_MOUSEMOVE) + ptrPGE->olc_UpdateMouse(e->targetX, e->targetY); + + + //Mouse button press + if (e->button == 0) // left click + { + if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) + ptrPGE->olc_UpdateMouseState(0, true); + else if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) + ptrPGE->olc_UpdateMouseState(0, false); + } + + if (e->button == 2) // right click + { + if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) + ptrPGE->olc_UpdateMouseState(1, true); + else if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) + ptrPGE->olc_UpdateMouseState(1, false); + + } + + if (e->button == 1) // middle click + { + if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) + ptrPGE->olc_UpdateMouseState(2, true); + else if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) + ptrPGE->olc_UpdateMouseState(2, false); + + //at the moment only middle mouse needs to consume events. + return EM_TRUE; + } + + return EM_FALSE; + } + + + virtual olc::rcode SetWindowTitle(const std::string& s) override + { emscripten_set_window_title(s.c_str()); return olc::OK; } + + virtual olc::rcode StartSystemEventLoop() override + { return olc::OK; } + + virtual olc::rcode HandleSystemEvent() override + { return olc::OK; } + + static void MainLoop() + { + olc::Platform::ptrPGE->olc_CoreUpdate(); + if (!ptrPGE->olc_IsRunning()) + { + if (ptrPGE->OnUserDestroy()) + { + emscripten_cancel_main_loop(); + platform->ApplicationCleanUp(); + } + else + { + ptrPGE->olc_Reanimate(); + } + } + } + }; + + //Emscripten needs a special Start function + //Much of this is usually done in EngineThread, but that isn't used here + olc::rcode PixelGameEngine::Start() + { + if (platform->ApplicationStartUp() != olc::OK) return olc::FAIL; + + // Construct the window + if (platform->CreateWindowPane({ 30,30 }, vWindowSize, bFullScreen) != olc::OK) return olc::FAIL; + olc_UpdateWindowSize(vWindowSize.x, vWindowSize.y); + + // Some implementations may form an event loop here + if (platform->ThreadStartUp() == olc::FAIL) return olc::FAIL; + + // Do engine context specific initialisation + olc_PrepareEngine(); + + // Consider the "thread" started + bAtomActive = true; + + // Create user resources as part of this thread + for (auto& ext : vExtensions) ext->OnBeforeUserCreate(); + if (!OnUserCreate()) bAtomActive = false; + for (auto& ext : vExtensions) ext->OnAfterUserCreate(); + + platform->StartSystemEventLoop(); + + //This causes a heap memory corruption in Emscripten for some reason + //Platform_Emscripten::bActiveRef = &bAtomActive; + emscripten_set_main_loop(&Platform_Emscripten::MainLoop, 0, 1); + + // Wait for thread to be exited + if (platform->ApplicationCleanUp() != olc::OK) return olc::FAIL; + return olc::OK; + } +} + +extern "C" +{ + EMSCRIPTEN_KEEPALIVE inline void olc_PGE_UpdateWindowSize(int width, int height) + { + emscripten_set_canvas_element_size("#canvas", width, height); + // Thanks slavka + ((olc::Platform_Emscripten*)olc::platform.get())->UpdateWindowSize(width, height); + } +} + +#endif +// O------------------------------------------------------------------------------O +// | END PLATFORM: Emscripten | +// O------------------------------------------------------------------------------O +#pragma endregion + + +#endif // Headless + +// O------------------------------------------------------------------------------O +// | olcPixelGameEngine Auto-Configuration | +// O------------------------------------------------------------------------------O +#pragma region pge_config +namespace olc +{ + void PixelGameEngine::olc_ConfigureSystem() + { + +//#if !defined(OLC_PGE_HEADLESS) + + olc::Sprite::loader = nullptr; + +#if defined(OLC_IMAGE_GDI) + olc::Sprite::loader = std::make_unique(); +#endif + +#if defined(OLC_IMAGE_LIBPNG) + olc::Sprite::loader = std::make_unique(); +#endif + +#if defined(OLC_IMAGE_STB) + olc::Sprite::loader = std::make_unique(); +#endif + +#if defined(OLC_IMAGE_CUSTOM_EX) + olc::Sprite::loader = std::make_unique(); +#endif + + +#if defined(OLC_PLATFORM_HEADLESS) + platform = std::make_unique(); +#endif + +#if defined(OLC_PLATFORM_WINAPI) + platform = std::make_unique(); +#endif + +#if defined(OLC_PLATFORM_X11) + platform = std::make_unique(); +#endif + +#if defined(OLC_PLATFORM_GLUT) + platform = std::make_unique(); +#endif + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + platform = std::make_unique(); +#endif + +#if defined(OLC_PLATFORM_CUSTOM_EX) + platform = std::make_unique(); +#endif + +#if defined(OLC_GFX_HEADLESS) + renderer = std::make_unique(); +#endif + +#if defined(OLC_GFX_OPENGL10) + renderer = std::make_unique(); +#endif + +#if defined(OLC_GFX_OPENGL33) + renderer = std::make_unique(); +#endif + +#if defined(OLC_GFX_OPENGLES2) + renderer = std::make_unique(); +#endif + +#if defined(OLC_GFX_DIRECTX10) + renderer = std::make_unique(); +#endif + +#if defined(OLC_GFX_DIRECTX11) + renderer = std::make_unique(); +#endif + +#if defined(OLC_GFX_CUSTOM_EX) + renderer = std::make_unique(); +#endif + + // Associate components with PGE instance + platform->ptrPGE = this; + renderer->ptrPGE = this; +//#else +// olc::Sprite::loader = nullptr; +// platform = nullptr; +// renderer = nullptr; +//#endif + } +} + +#pragma endregion + +#endif // End OLC_PGE_APPLICATION + +// O------------------------------------------------------------------------------O +// | END OF OLC_PGE_APPLICATION | +// O------------------------------------------------------------------------------O + diff --git a/src/SnakeGame.cpp b/src/SnakeGame.cpp deleted file mode 100644 index a766b2a..0000000 --- a/src/SnakeGame.cpp +++ /dev/null @@ -1,246 +0,0 @@ -// PacmanConsole.cpp: . -// - -#include "PacmanConsole.h" - -using namespace std::chrono; - - -bool game_exit = false; - -struct vector2di { - int x = 0; - int y = 0; -}; - -void show_map(const vector2di& apple, const int& eaten_apples); -void move_snake(vector2di& snake_head, vector2di& vector_step, std::vector& snake_tail, vector2di& apple, vector2di rnd, int& eaten_apples); - -int main() -{ - // - system("chcp 1251"); - // curses - initscr(); - // - curs_set(0); - // - start_color(); - // 1,2,3,4,5 - init_pair(1, COLOR_WHITE, COLOR_BLUE); - init_pair(2, COLOR_MAGENTA, COLOR_BLUE); - init_pair(3, COLOR_GREEN, COLOR_BLUE); - init_pair(4, COLOR_RED, COLOR_BLUE); - init_pair(5, COLOR_YELLOW, COLOR_BLACK); - // , 1 - bkgd(COLOR_PAIR(1)); - // - vector2di snake_head{ 10,10 }; - // - vector2di vector_step{ 1,0 }; - // - vector2di apple{ 15,15 }; - // - vector2di rnd_apple; - // - std::vector snake_tail; - // - int frame_rate = 100; - // - int eaten_apples = 0; - - // **** **** - // - long long seed = system_clock::now().time_since_epoch().count(); - // - std::default_random_engine rnd(static_cast(seed)); - // - std::uniform_int_distribution apple_x(10, 97); - std::uniform_int_distribution apple_y(5, 22); - // **** **** - - // - keypad(stdscr, true); - // - noecho(); - - // **** **** - while (!game_exit) { - // - rnd_apple.x = apple_x(rnd); - rnd_apple.y = apple_y(rnd); - // - show_map(apple, eaten_apples); - // - move_snake(snake_head, vector_step, snake_tail, apple, rnd_apple, eaten_apples); - // - timeout(frame_rate); - - // **** **** - // - switch (getch()) { - case KEY_UP: - if (vector_step.y == 0) { - - vector_step.y = -1; - vector_step.x = 0; - frame_rate = 170; - } - break; - case KEY_DOWN: - if (vector_step.y == 0) { - - vector_step.y = 1; - vector_step.x = 0; - frame_rate = 170; - } - break; - case KEY_LEFT: - if (vector_step.x == 0) { - - vector_step.x = -1; - vector_step.y = 0; - frame_rate = 100; - } - break; - case KEY_RIGHT: - if (vector_step.x == 0) { - - vector_step.x = 1; - vector_step.y = 0; - frame_rate = 100; - } - break; - case 'q': - game_exit = true; - break; - default: - break; - } - // **** **** - } - // **** **** - endwin(); - return 0; -} - -void show_map(const vector2di& apple, const int& eaten_apples) -{ - - // - clear(); - // - move(2, 55); - // - attrset(A_DIM | COLOR_PAIR(1)); - // - printw("\t\t"); - attrset(A_BOLD | COLOR_PAIR(5)); - printw(" < "); - // - std::string s_eaten_apples = std::to_string(eaten_apples); - // - printw(s_eaten_apples.c_str()); - printw(" > "); - attrset(A_DIM | COLOR_PAIR(1)); - // - for (int y = 4; y < 28; y++) { - - for (int x = 5; x < 112; x++) { - - if (y == 4 || y == 27 || x == 5 || x == 111) { - - move(y, x); - printw("*"); - } - } - } - // - move(apple.y, apple.x); - attrset(A_BOLD | COLOR_PAIR(2)); - printw("@"); -} - -void move_snake(vector2di& snake_head, vector2di& vector_step, std::vector& snake_tail, - vector2di& apple, vector2di rnd, int& eaten_apples) -{ - - // - attrset(A_BOLD | COLOR_PAIR(3)); - - // , - if (!snake_tail.empty()) { - - for (auto const& mov : snake_tail) { - - move(mov.y, mov.x); - - printw("#"); - } - } - // - snake_head.x += vector_step.x; - snake_head.y += vector_step.y; - // - move(snake_head.y, snake_head.x); - // - auto s = static_cast(winch(stdscr)); - // *** - // ### ### - if (s == '*' || s == '#') { - - attrset(A_BOLD | COLOR_PAIR(4)); - move(13, 55); - printw(" "); - move(14, 42); - printw(" - < q > - < n >"); - // *** *** - do { - if (getch() == 'q') { - // - game_exit = true; - return; - } - if (getch() == 'n') - { - // - snake_head = { 10,10 }; - vector_step = { 1,0 }; - snake_tail.clear(); - apple = { 15,15 }; - eaten_apples = 0; - return; - } - } while (true); - // *** *** - } - // ### ### - - // - if (s == '@') { - // - eaten_apples++; - // - snake_tail.push_back({ snake_head.x, snake_head.y }); - // - printw("$"); - - do { - // - apple.x = rnd.x; apple.y = rnd.y; - move(apple.y, apple.x); - auto s = static_cast(winch(stdscr)); - // - } while (s == '#'); - } - // - else { - // - printw("$"); - // - if (!snake_tail.empty()) { - snake_tail.erase(snake_tail.begin()); - snake_tail.push_back({ snake_head.x, snake_head.y }); - } - } -}; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index c914a52..3ac1c56 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,20 @@ // PacmanConsole.cpp: определяет точку входа для приложения. // +#ifndef ASIO_STANDALONE +#define ASIO_STANDALONE +#endif + +#define OLC_PGE_APPLICATION + +#include + +#define OLC_PGEX_TRANSFORMEDVIEW + +#include + #include +#include "game_structs.h" //#include "game_client.h" //#include "game_server.h" @@ -38,7 +51,7 @@ class GameClient : public olc::PixelGameEngine, net::client_interface net::message clientReadyMsg; clientReadyMsg.header.id = PTypes::CLIENT_READY; // clientReadyMsg.SwapEndianness(); - + clientReadyMsg.ReverseHeader(); Send(clientReadyMsg); } @@ -129,7 +142,7 @@ class GameClient : public olc::PixelGameEngine, net::client_interface // std::this_thread::sleep_for(100ms); m_StartTime = std::chrono::system_clock::now(); } - + } public: @@ -155,7 +168,7 @@ class GameClient : public olc::PixelGameEngine, net::client_interface } if (foodLeft == 0) bGameOver = true; - if (bGameOver) + if (bGameOver) { std::cout << "Your score: " << score << std::endl; return false; @@ -197,7 +210,7 @@ class GameClient : public olc::PixelGameEngine, net::client_interface case PTypes::SERVER_GAME_START: { bWaitingForRegistration = false; - if (!bReady) + if (!bReady) { m_StartTime = std::chrono::system_clock::now(); bReady = true; @@ -263,7 +276,7 @@ class GameClient : public olc::PixelGameEngine, net::client_interface GameMap(playerSent->start_x, playerSent->start_y) = CellType::EMPTY; - if (OtherDir == PlayerMoves::UP) playerSent->start_y--; + if (OtherDir == PlayerMoves::UP) playerSent->start_y--; else if (OtherDir == PlayerMoves::DOWN) playerSent->start_y++; else if (OtherDir == PlayerMoves::LEFT) playerSent->start_x--; else if (OtherDir == PlayerMoves::RIGHT) playerSent->start_x++; @@ -318,7 +331,7 @@ class GameClient : public olc::PixelGameEngine, net::client_interface case CellType::PLAYER: { if (vTile.x == localPlayer->start_x && vTile.y == localPlayer->start_y) - tv.FillRect(olc::vf2d(vTile) + olc::vf2d(0.2f, 0.2f), {0.8f, 0.8f}, olc::GREEN); + tv.FillRect(olc::vf2d(vTile) + olc::vf2d(0.2f, 0.2f), { 0.8f, 0.8f }, olc::GREEN); else tv.FillRect(olc::vf2d(vTile) + olc::vf2d(0.2f, 0.2f), { 0.8f, 0.8f }, olc::RED); break; @@ -633,13 +646,13 @@ class GameServer : public net::server_interface } case 2u: { - newPlayer.start_x = 2u * GenMap.GetWidth() - PlayerStartX - 1u; + newPlayer.start_x = 2u * GenMap.GetWidth() - PlayerStartX - 1u; newPlayer.start_y = PlayerStartY; break; } case 3u: { - newPlayer.start_x = 2u * GenMap.GetWidth() - PlayerStartX - 1u; + newPlayer.start_x = 2u * GenMap.GetWidth() - PlayerStartX - 1u; newPlayer.start_y = 2u * GenMap.GetHeight() - PlayerStartY - 1u; break; } @@ -708,7 +721,7 @@ class GameServer : public net::server_interface break; } - + default: { std::cout << "[GameServer] [" << client->GetID() << "] wrong packet id. Removing client\n"; @@ -759,33 +772,131 @@ class GameServer : public net::server_interface // // socket.async_read_some(asio::buffer(vBuffer.data(), vBuffer.size())); //} +void ParseArguments(int argc, char* argv[], std::string& program_type, std::string& program_playername, std::string& program_ip, uint16_t& program_port, uint32_t& program_playercount); - -int main(int argc, char * argv[]) +int main(int argc, char* argv[]) { + std::string program_type; + std::string program_playername; + std::string program_ip; + uint16_t program_port; + uint32_t program_playercount; - argparse::ArgumentParser program("PacmanConsole"); + ParseArguments(argc, argv, program_type, program_playername, program_ip, program_port, program_playercount); - program.add_argument("-t", "--type").help("decide if a program is a client or a server").default_value(std::string("client")).nargs(1); - program.add_argument("-i", "--ip").help("if server, then decide the ip address the server will be running on. if client, then connect to a server with specific ip address").default_value(std::string("127.0.0.1")).nargs(1); - program.add_argument("-p", "--port").help("the server will be listening on specific port, or a client will connect to a server running on a specific port").default_value(60000).scan <'d', uint16_t>().nargs(1); + std::cout << "Program type is " << program_type << ", ip address is " << program_ip << " and port is " << program_port << std::endl; - try + if (program_type == "server") { - program.parse_args(argc, argv); + GameServer server(program_port, program_playercount); + server.Start(); + + while (1) + { + server.Update(-1, true); + } } - catch (const std::exception& err) + else if (program_type == "client") { - std::cerr << "Argparse failed. Error: " << err.what() << std::endl; - std::cerr << program; - return -1; + GameClient demo(program_playername, program_ip, program_port); + if (demo.Construct(480, 480, 1, 1)) + demo.Start(); } - const auto program_type = program.get("-t"); - const auto program_ip = program.get("-i"); - const auto program_port = program.get("-p"); + /*uint8_t arr[20 * 15]; - std::cout << "Program type is " << program_type << ", ip address is " << program_ip << " and port is " << program_port << std::endl; + { + FieldPart f; + f.GenerateMap(); + + std::cout << f << '\n'; + + f.WriteToCArray(arr); + } + + net::message msg; + + msg << arr; + + FieldPart f1; + f1.GenerateMap(); + + f1(1, 1) = CellType::PLAYER; + + std::cout << f1 << '\n'; + + std::memset(&arr[0], 0x0, sizeof(arr)); + + msg >> arr; + + f1.ReadFromCArray(arr); + + std::cout << f1 << '\n';*/ + + //uint8_t arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + //net::message msg; + + //msg << arr; + + //std::memset(&arr[0], 0x0, sizeof(arr)); + + //for (size_t i = 0; i < 10u; i++) std::cout << (uint32_t)arr[i] << ' '; + //std::cout << '\n'; + + //msg >> arr; + + //for (size_t i = 0; i < 10u; i++) std::cout << (uint32_t)arr[i] << ' '; + //std::cout << '\n'; + + //Example demo; + //if (demo.Construct(256, 240, 4, 4)) + // demo.Start(); + + // GameField<20, 15> field; + + // field.GenerateMap(); + + // MazeGenerator gen(field); + + // std::cout << field << '\n'; + + //std::cout << std::hex << (static_cast(CellType::EMPTY) == 0x00) << + // " " << (static_cast(CellType::FOOD) == 0xaa) << + // " " << (static_cast(CellType::PLAYER) == 0x22) << + // " " << (static_cast(CellType::WALL) == 0xff) << '\n'; + + //GameField<20, 15> field; + + //auto arr = field.GetBytes(); + + //std::cout << field << '\n'; + + //for (size_t j = 0; j < field.GetHeight(); j++) + //{ + // for (size_t i = 0; i < field.GetWidth(); i++) + // { + // std::cout << std::hex << uint32_t(arr.at(j * field.GetHeight() + i)) << '\t'; + // } + // std::cout << '\n'; + //} + + // GameField<40, 30> BigField; + + // field.GenerateSymmetricMap(BigField); + + // auto field1 = field; + // field1.InvertHorizontal(); + // + // field.InsertToAnother(BigField, 0, 0); + // field1.InsertToAnother(BigField, field1.GetWidth(), 0); + // + // field.InvertVertical(); + // field.InsertToAnother(BigField, 0, field.GetHeight()); + // + // field1.InvertVertical(); + // field1.InsertToAnother(BigField, field1.GetWidth(), field1.GetHeight()); + + // std::cout << BigField << '\n'; //net::message msg; @@ -846,4 +957,39 @@ int main(int argc, char * argv[]) return 0; +} + +void ParseArguments(int argc, char* argv[], std::string& program_type, std::string& program_playername, std::string& program_ip, uint16_t& program_port, uint32_t& program_playercount) +{ + argparse::ArgumentParser program("PacmanConsole"); + + program.add_argument("-t", "--type").help("decide if a program is a client or a server").default_value(std::string("client")).nargs(1); + program.add_argument("-n", "--name").help("if type is a client then decide a player name").default_value(std::string("Default")).nargs(1); + program.add_argument("-i", "--ip").help("if server, then decide the ip address the server will be running on. if client, then connect to a server with specific ip address").default_value(std::string("127.0.0.1")).nargs(1); + program.add_argument("-p", "--port").help("the server will be listening on specific port, or a client will connect to a server running on a specific port").default_value(60000).scan <'d', uint16_t>().nargs(1); + program.add_argument("-c", "--count").help("decide the amount of players, it can only be between 2 and 4").default_value(2).scan<'d', uint32_t>().nargs(1); + + try + { + program.parse_args(argc, argv); + } + + catch (const std::exception& err) + { + std::cerr << "Argparse failed. Error: " << err.what() << std::endl; + std::cerr << program; + exit(-1); + } + + program_type = program.get("-t"); + program_playername = program.get("-n"); + program_ip = program.get("-i"); + program_port = program.get("-p"); + program_playercount = program.get("-c"); + + if (program_playername.empty()) + program_playername = std::to_string(static_cast(std::chrono::system_clock::now().time_since_epoch().count())); + + if (program_playercount < 2 || program_playercount > 4) + program_playercount = (program_playercount % 3) + 2; } \ No newline at end of file From 66cce40da04e9844cc7e30d8bf49a30977717164 Mon Sep 17 00:00:00 2001 From: UnknownXXX000 <59707969+UnknownXXX000@users.noreply.github.com> Date: Thu, 30 May 2024 11:04:38 +0300 Subject: [PATCH 5/5] change byte order --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 3ac1c56..9b395f9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -111,7 +111,7 @@ class GameClient : public olc::PixelGameEngine, net::client_interface offset_x += static_cast(localPlayer->start_x); offset_y += static_cast(localPlayer->start_y); - if (offset_x >= 0 && offset_x < GameMap.GetWidth() && offset_y >= 0 && offset_y < GameMap.GetHeight() && + if (offset_x >= 0 && offset_x < static_cast(GameMap.GetWidth()) && offset_y >= 0 && offset_y < static_cast(GameMap.GetHeight()) && (GameMap.at(offset_x, offset_y) == CellType::FOOD || GameMap.at(offset_x, offset_y) == CellType::EMPTY)) { localPlayer->start_direction = dir;