diff --git a/envs/models/02_gift.gt b/envs/models/02_gift.gt new file mode 100644 index 0000000..76ded1a --- /dev/null +++ b/envs/models/02_gift.gt @@ -0,0 +1,26 @@ +name=02_gift,width=6,height=4,collision=false +__gtransparent_unit__ +4 false 0 X +__gtransparent_unit__ +__gtransparent_unit__ +4 false 0 X +__gtransparent_unit__ +6 false 0 X +6 false 0 X +4 false 0 X +4 false 0 X +6 false 0 X +6 false 0 X +4 false 0 X +4 false 0 X +4 false 0 X +4 false 0 X +4 false 0 X +4 false 0 X +6 false 0 X +6 false 0 X +4 false 0 X +4 false 0 X +6 false 0 X +6 false 0 X +; %tsgmeng::builder.texture4_0() -> auto_generated Sat Nov 02 2024 diff --git a/envs/models/03_table.gt b/envs/models/03_table.gt new file mode 100644 index 0000000..a9c8228 --- /dev/null +++ b/envs/models/03_table.gt @@ -0,0 +1,62 @@ +name=03_table,width=12,height=5,collision=false +5 false 0 X +5 false 0 X +5 false 0 X +5 false 0 X +5 false 0 X +5 false 0 X +5 false 0 X +5 false 0 X +5 false 0 X +5 false 0 X +5 false 0 X +5 false 0 X +5 false 0 X +0 false 1 X +__gtransparent_unit__ +__gtransparent_unit__ +__gtransparent_unit__ +__gtransparent_unit__ +__gtransparent_unit__ +__gtransparent_unit__ +__gtransparent_unit__ +__gtransparent_unit__ +0 false 1 X +5 false 0 X +__gtransparent_unit__ +0 false 1 X +__gtransparent_unit__ +__gtransparent_unit__ +__gtransparent_unit__ +__gtransparent_unit__ +__gtransparent_unit__ +__gtransparent_unit__ +__gtransparent_unit__ +__gtransparent_unit__ +0 false 1 X +__gtransparent_unit__ +__gtransparent_unit__ +0 false 1 X +__gtransparent_unit__ +__gtransparent_unit__ +__gtransparent_unit__ +__gtransparent_unit__ +__gtransparent_unit__ +__gtransparent_unit__ +__gtransparent_unit__ +__gtransparent_unit__ +0 false 1 X +__gtransparent_unit__ +__gtransparent_unit__ +0 false 1 X +__gtransparent_unit__ +__gtransparent_unit__ +__gtransparent_unit__ +__gtransparent_unit__ +__gtransparent_unit__ +__gtransparent_unit__ +__gtransparent_unit__ +__gtransparent_unit__ +0 false 1 X +__gtransparent_unit__ +; %tsgmeng::builder.texture4_0() -> auto_generated Sat Nov 02 2024 diff --git a/envs/models/smol_player.gt b/envs/models/smol_player.gt new file mode 100644 index 0000000..a1ad2a3 --- /dev/null +++ b/envs/models/smol_player.gt @@ -0,0 +1,18 @@ +name=smol_player,width=4,height=4,collision=true +4 false 0 X +4 false 0 X +4 false 0 X +__gtransparent_unit__ +0 false 0 X +0 false 0 X +4 false 0 X +4 false 0 X +4 false 0 X +4 false 0 X +4 false 0 X +4 false 0 X +4 false 0 X +__gtransparent_unit__ +4 false 0 X +__gtransparent_unit__ +; %tsgmeng::builder.texture4_0() -> auto_generated Sat Oct 26 2024 diff --git a/lib/bin/cli/commands.cc b/lib/bin/cli/commands.cc index 88aeee0..69c6038 100644 --- a/lib/bin/cli/commands.cc +++ b/lib/bin/cli/commands.cc @@ -2,10 +2,45 @@ #include #include +#include "../gmeng.h" + #include "index.h" using string = std::string; + +class inspect_texture_command_t : public Gmeng_Commandline::Subcommand { + public: + inline void run(vector args) override { + if (args.size() < 1) { + LOG("~r~ERROR!~n~ please provide a texture file name."); + LOG("~g~Usage:~n~ ~y~" + Gmeng::global.executable + "~n~ preview ~b~file.tx~n~"); + return; + }; + LOG("Checking for texture atlas in ~g~"+args.at(0)+"~n~..."); + if (!filesystem::exists(args.at(0))) { + LOG("~r~ERROR!~n~ file ~b~" + args.at(0) + "~n~ does not exist."); + return; + } + Gmeng::texture txt = Gmeng::LoadTexture(args.at(0)); + std::string render = Gmeng::Util::draw_texture_string(txt); + MSG("~r~Gmeng~n~ TEXTURE PREVIEW:\n"); + MSG("~_~file: `" + args.at(0) + "`~n~\n"); + std::cout << render << std::endl; + }; + + inspect_texture_command_t(string _name, string _description) : Subcommand(_name, _description) { + this->info = { _name, _description }; + }; +}; + +static inspect_texture_command_t inspect_command("preview", "Displays a preview of a texture file"); + +static Gmeng_Commandline::InterfaceRegistrar +register_inspect_command( + std::make_unique( ( inspect_command ) ) +); + class test_command_t : public Gmeng_Commandline::Subcommand { public: inline void run(vector) override { @@ -66,3 +101,167 @@ static Gmeng_Commandline::InterfaceRegistrar register_editor_command( std::make_unique ( ( editor_command ) ) ); + + +#include +#include +#include +#include +#include +#include + +void modify_properties(gmeng_properties_t& properties); + +using namespace Gmeng; + +class gamestate_editor_t : public Gmeng_Commandline::Subcommand { + public: + inline void run(vector args) override { + if (args.size() < 1) { + LOG("~r~ERROR!~n~ provide a file name."); + MSG("~g~Usage:~n~ ~y~"+Gmeng::global.executable+" ~b~gamestate~n~ \n"); + return; + }; + + std::string filename = args.at(0); + + if (!filesystem::exists(filename)) { + LOG("~r~ERROR!~n~ the file `"+filename+"` does not exist."); + MSG("~g~Usage:~n~ ~y~"+Gmeng::global.executable+" ~b~gamestate~n~ \n"); + return; + }; + + gmeng_properties_t properties = read_properties(filename); + + initscr(); + cbreak(); + noecho(); + keypad(stdscr, TRUE); + + modify_properties(properties); + writeout_properties(filename, properties); + + endwin(); + }; + + gamestate_editor_t(string _name, string _description) : Subcommand(_name, _description) { + this->info = { + .name = _name, + .description = _description + }; + }; +}; + +static gamestate_editor_t gamestate_editor_command("gamestate", "Modifies or views a gamestate binary file"); + +static Gmeng_Commandline::InterfaceRegistrar +register_gamestate_command( + std::make_unique( gamestate_editor_command ) +); + +void modify_properties(gmeng_properties_t& properties) { + int highlight = 0; + int mode = 0; // 0 = Main properties, 1 = Model positions + const int num_main_fields = 6; + bool exit_program = false; + + while (!exit_program) { + clear(); + + // Display section based on mode + if (mode == 0) { + mvprintw(0, 0, "Modify Main Properties (Press ENTER to edit, UP/DOWN to navigate, 'm' for model positions, 'q' to quit)"); + mvprintw(2, 0, "1. DEF_DELTAX: %d", properties.DEF_DELTAX); + mvprintw(3, 0, "2. DEF_DELTAY: %d", properties.DEF_DELTAY); + mvprintw(4, 0, "3. SKY_WIDTH: %d", properties.SKY_WIDTH); + mvprintw(5, 0, "4. SKY_HEIGHT: %d", properties.SKY_HEIGHT); + mvprintw(6, 0, "5. SKY_COLOR: %d", properties.SKY_COLOR); + mvprintw(7, 0, "6. A00_CAKE_INTERACT_LOOPC: %d", properties.A00_CAKE_INTERACT_LOOPC); + mvchgat(2 + highlight, 0, -1, A_REVERSE, 0, NULL); + } else { + int pos_index = 0; + mvprintw(0, 0, "Modify Model Positions (Press ENTER to edit, 'a' to add, 'd' to delete, 'm' to go back, 'q' to quit)"); + for (const auto& [key, point] : properties.model_positions) { + mvprintw(2 + pos_index, 0, "%d. %s: (%d, %d)", pos_index + 1, key.c_str(), point.x, point.y); + if (pos_index == highlight) { + mvchgat(2 + pos_index, 0, -1, A_REVERSE, 0, NULL); + } + pos_index++; + } + } + + int ch = getch(); + switch (ch) { + case KEY_UP: + highlight = (highlight - 1 + (mode == 0 ? num_main_fields : properties.model_positions.size())) % + (mode == 0 ? num_main_fields : properties.model_positions.size()); + break; + case KEY_DOWN: + highlight = (highlight + 1) % (mode == 0 ? num_main_fields : properties.model_positions.size()); + break; + case '\n': { + if (mode == 0) { + int new_value; + echo(); + mvprintw(10, 0, "Enter new value: "); + scanw("%d", &new_value); + noecho(); + + switch (highlight) { + case 0: properties.DEF_DELTAX = new_value; break; + case 1: properties.DEF_DELTAY = new_value; break; + case 2: properties.SKY_WIDTH = new_value; break; + case 3: properties.SKY_HEIGHT = new_value; break; + case 4: properties.SKY_COLOR = (color_t)new_value; break; + case 5: properties.A00_CAKE_INTERACT_LOOPC = new_value; break; + } + } else if (!properties.model_positions.empty()) { + auto it = properties.model_positions.begin(); + std::advance(it, highlight); + echo(); + int new_x, new_y; + mvprintw(10, 0, "Enter new x value: "); + scanw("%d", &new_x); + mvprintw(11, 0, "Enter new y value: "); + scanw("%d", &new_y); + noecho(); + + it->second.x = new_x; + it->second.y = new_y; + } + break; + } + case 'm': + mode = 1 - mode; // Toggle between main properties and model positions + highlight = 0; + break; + case 'a': + if (mode == 1) { + char new_key[64]; + int new_x, new_y; + echo(); + mvprintw(10, 0, "Enter new key: "); + getstr(new_key); + mvprintw(11, 0, "Enter x value: "); + scanw("%d", &new_x); + mvprintw(12, 0, "Enter y value: "); + scanw("%d", &new_y); + noecho(); + + properties.model_positions[std::string(new_key)] = {new_x, new_y}; + } + break; + case 'd': + if (mode == 1 && !properties.model_positions.empty()) { + auto it = properties.model_positions.begin(); + std::advance(it, highlight); + properties.model_positions.erase(it); + highlight = std::min(highlight, static_cast(properties.model_positions.size()) - 1); + } + break; + case 'q': + exit_program = true; + break; + } + } +}; diff --git a/lib/bin/gmeng.h b/lib/bin/gmeng.h index 69de067..20f6f8e 100755 --- a/lib/bin/gmeng.h +++ b/lib/bin/gmeng.h @@ -316,7 +316,7 @@ namespace Gmeng { /// "-d" suffix means the version is a developer version, high unstability level /// "-b" suffix means the version is a beta version, low unstability level but unpolished /// "-c" suffix means the version is a coroded version, low to medium unstability level but specific methods will not perform as expected - static std::string version = "10.1.0-d"; + static std::string version = "10.1.0"; enum color_t { WHITE = 0, BLUE = 1, @@ -739,7 +739,7 @@ static void init_logc(int ms = 250) { return; #endif __gmeng_write_log__("gmeng.log", "-- cleared previous log --\n", false); - __gmeng_write_log__("gmeng.log", "Gmeng: Go-To Console Game Engine.\nSPAWN(1) = v_success / at " + get_curtime() + "/" + get_curdate() + "\ncontroller_t of termui/_udisplay_of(GMENG, window) handed over to: controller_t(gmeng::threads::get(0))\n"); + __gmeng_write_log__("gmeng.log", "Gmeng "+Gmeng::version+" (build " + GMENG_BUILD_NO + ").\n\nDocumentation available in https://gmeng.org.\nGmeng is an open source project. https://gmeng.org/git.\nPlease report bugs or unexpected behaviour at https://gmeng.org/report.\n\nGmeng: Go-To Console Game Engine.\n\nSPAWN(1) = v_success / at " + get_curtime() + "/" + get_curdate() + "\ncontroller_t of termui/_udisplay_of(GMENG, window) handed over to: controller_t(gmeng::threads::get(0))\n"); __gmeng_write_log__("gmeng.log", "----------------------------------\nExecutable Name: " + Gmeng::global.executable + "\nCurrent Working Directory: " + Gmeng::global.pwd + "\nCurrent User: " + Gmeng::global.user + "\n----------------------------------\n", true); __gmeng_write_log__("gmeng.log", "Global Variables\n\t- devmode: " + boolstr(Gmeng::global.dev_mode) + "\n\t- debugger: " + boolstr(Gmeng::global.debugger) + "\n\t- silenced: " + boolstr(Gmeng::global.shush) + "\n\t- dont_hold_back: " + boolstr(Gmeng::global.dont_hold_back) + "\n----------------------------------\n", true); @@ -843,14 +843,14 @@ static std::string ws2s(const std::wstring& wstr) { ///// __controller_satisfy__ ///// OS Check for windows -static void __explain_why_i_cannot_run_to_dumbass_using_windows() { - __annot__(__explain_why_i_cannot_run_to_dumbass_using_windows, "explains to a user using windows why windows cannot run gmeng."); - __functree_call__(__explain_why_i_cannot_run_to_dumbass_using_windows); +static void print_windows_error_message() { + __annot__(print_windows_error_message, "explains to a user using windows why windows cannot run gmeng."); + __functree_call__(print_windows_error_message); std::cout << Gmeng::colors[4] << "libgmeng-abi: __excuse__" << std::endl; std::cout << "INTERNAL: __gmeng_platform__, __gmeng_threading__, __stdlib__, __libc++-abi__, __std_com_apple_main_pthread__" << std::endl; std::cout << "Gmeng is not available in a core-platform other than darwin ( apple_kernel )." << std::endl; std::cout << "current_platform: win32 ( WINDOWS_NT )" << std::endl; - std::cout << "__gmeng_halt_execution__( CAUSE( gmeng::global.v_exceptions->__find__( \"controller.platform\" ) ) && CAUSE( \"__environment_not_suitable__\" ) )" << std::endl; + std::cout << "__gmeng_halt_execution__( CAUSE( gmeng::global.v_exceptions->__find__( \"controller.platform\" ) ) && CAUSE( \"__environment_not_suitable__\" ) )" << Gmeng::resetcolor << std::endl; exit(1); }; @@ -858,7 +858,7 @@ static void patch_argv_global(int argc, char* argv[]) { __annot__(patch_argv_global, "patches the Gmeng::global variable with the command-line arguments."); __functree_call__(patch_argv_global); #if _WIN32 - __explain_why_i_cannot_run_to_dumbass_using_windows(); + print_windows_error_message(); return; #endif Gmeng::global.pwd = get_cwd(); diff --git a/lib/bin/src/gmeng.cpp b/lib/bin/src/gmeng.cpp index 24eb6c5..7a8264e 100755 --- a/lib/bin/src/gmeng.cpp +++ b/lib/bin/src/gmeng.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include "../gmeng.h" @@ -36,7 +37,7 @@ std::vector g_trace_trajectory(int x1, int y1, int x2, int y2) { Objects::coord point; point.x = x2; point.y = y2; coordinates.push_back(point); - return coordinates; +return coordinates; } namespace Gmeng { @@ -455,12 +456,14 @@ static void _gremote_server_apl(bool state, std::string aplpass) { /// GMENG EVENTLOOP IMPLEMENTATION #include +#ifndef _WIN32 #include +#endif #include #include - +#ifndef _WIN32 namespace Gmeng::TerminalUtil { void enable_mouse_tracking() { std::cout << "\033[?1006h\033[?1003h\n" << std::flush; }; void disable_mouse_tracking() { std::cout << "\033[?1006l\033[?1003l\n" << std::flush; }; @@ -501,7 +504,7 @@ namespace Gmeng::TerminalUtil { }; }; }; - +#endif namespace Gmeng { @@ -585,6 +588,10 @@ namespace Gmeng { typedef struct EventLoop { int id; Gmeng::Level* level; + /// Processes, used for registering event calls for the + /// next tick of the event loop. Called with `UPDATE` event + vector processes; + vector hooks; vector defaults; @@ -593,18 +600,39 @@ namespace Gmeng { EventLoop( vector hooks_ ) : hooks(hooks_) { this->id = g_mkid(); gm_log("" $(id) ": created main game eventloop with id " $(this->id) "."); - TerminalUtil::set_raw_mode(true); - gm_log("" $(id) ": set terminal state to raw mode."); + +#ifndef _WIN32 TerminalUtil::enable_mouse_tracking(); gm_log("" $(id) ": enabled mouse tracking"); + TerminalUtil::set_raw_mode(true); + gm_log("" $(id) ": set terminal state to raw mode."); +#endif }; ~EventLoop() { +#ifndef _WIN32 gm_log("" $(id) ": destroyed eventloop " $(this->id) "."); gm_log("" $(id) ": disabled mouse tracking"); TerminalUtil::set_non_blocking(false); gm_log(""$(id)": disabled non blocking input mode"); TerminalUtil::disable_mouse_tracking(); +#endif + }; + + void next_tick(handler_function_type handler) { + int id = g_mkid(); + this->processes.push_back({ id, { UPDATE }, handler }); + }; + + void progress_tick() { + int i = 0; + for (auto proc : this->processes) { + /// delete the process + this->processes.erase(this->processes.begin()+i); + proc.handler(this->level, &Gmeng::NO_EVENT_INFO); + i++; + }; + /// clear next tick processes. }; void add_hook(vector events, handler_function_type handler) { @@ -664,6 +692,7 @@ namespace Gmeng { #define MOUSE_REST_1_CHECKER(x) x == 65 ? Gmeng::MOUSE_SCROLL_DOWN : MOUSE_REST_2_CHECKER(x) #define SELECT_MOUSE_EVENT(x) x == 64 ? Gmeng::MOUSE_SCROLL_UP : MOUSE_REST_1_CHECKER(x) +#ifndef _WIN32 /// runs an event loop instance /// (this means handling the level as the main event loop / the instance of the game) int do_event_loop(Gmeng::EventLoop* ev) { @@ -679,6 +708,7 @@ int do_event_loop(Gmeng::EventLoop* ev) { Gmeng::_ucreate_thread([&]() { while (!ev->cancelled) { ev->call_event(Gmeng::UPDATE, Gmeng::NO_EVENT_INFO); + ev->progress_tick(); }; }); @@ -753,3 +783,123 @@ int do_event_loop(Gmeng::EventLoop* ev) { Gmeng::_uclear_threads(); return 0; }; +#endif + +typedef struct { + int DEF_DELTAX; + int DEF_DELTAY; + + int SKY_WIDTH; + int SKY_HEIGHT; + + Gmeng::color_t SKY_COLOR; + + std::unordered_map model_positions; + + int A00_CAKE_INTERACT_LOOPC; +} gmeng_properties_t; + +static gmeng_properties_t default_properties = { + 50, 25, + + 100, 100, + + Gmeng::BLUE, + + { + { "player", { 0,0 } }, + + { "table1", { 13, 21 } }, + { "table2", { 24, 21 } }, + + { "cake", { 16, 17 } }, + + { "gift1", { 0, 22 } }, + { "gift2", { 44, 22 } }, + + { "CAKE_INTERACT_TIMES", { 1, 20 } }, + }, + + 100 +}; + +#if _WIN32 +/// WINDOWS IMPLEMENTATIONS +/// NOT COMING ANYTIME SOON. + +int do_event_loop(Gmeng::EventLoop* ev) { + return -1; +}; + +#endif + +void writeout_properties(const std::string& filename, const gmeng_properties_t& properties) { + std::ofstream outFile(filename, std::ios::binary); + if (!outFile) { + throw std::ios_base::failure("Failed to open file for writing"); + } + + // Write the int fields + outFile.write(reinterpret_cast(&properties.DEF_DELTAX), sizeof(properties.DEF_DELTAX)); + outFile.write(reinterpret_cast(&properties.DEF_DELTAY), sizeof(properties.DEF_DELTAY)); + outFile.write(reinterpret_cast(&properties.SKY_WIDTH), sizeof(properties.SKY_WIDTH)); + outFile.write(reinterpret_cast(&properties.SKY_HEIGHT), sizeof(properties.SKY_HEIGHT)); + outFile.write(reinterpret_cast(&properties.SKY_COLOR), sizeof(properties.SKY_COLOR)); + outFile.write(reinterpret_cast(&properties.A00_CAKE_INTERACT_LOOPC), sizeof(properties.A00_CAKE_INTERACT_LOOPC)); + + size_t map_size = properties.model_positions.size(); + outFile.write(reinterpret_cast(&map_size), sizeof(map_size)); + + for (const auto& [key, drawpoint] : properties.model_positions) { + size_t key_size = key.size(); + outFile.write(reinterpret_cast(&key_size), sizeof(key_size)); + outFile.write(key.c_str(), key_size); + + // Write the drawpoint struct (x and y) + outFile.write(reinterpret_cast(&drawpoint.x), sizeof(drawpoint.x)); + outFile.write(reinterpret_cast(&drawpoint.y), sizeof(drawpoint.y)); + } + + outFile.close(); +}; + +gmeng_properties_t read_properties(const std::string& filename) { + std::ifstream inFile(filename, std::ios::binary); + if (!inFile) { + throw std::ios_base::failure("Failed to open file for reading"); + } + + gmeng_properties_t properties; + + // Read the int fields + inFile.read(reinterpret_cast(&properties.DEF_DELTAX), sizeof(properties.DEF_DELTAX)); + inFile.read(reinterpret_cast(&properties.DEF_DELTAY), sizeof(properties.DEF_DELTAY)); + inFile.read(reinterpret_cast(&properties.SKY_WIDTH), sizeof(properties.SKY_WIDTH)); + inFile.read(reinterpret_cast(&properties.SKY_HEIGHT), sizeof(properties.SKY_HEIGHT)); + inFile.read(reinterpret_cast(&properties.SKY_COLOR), sizeof(properties.SKY_COLOR)); + inFile.read(reinterpret_cast(&properties.A00_CAKE_INTERACT_LOOPC), sizeof(properties.A00_CAKE_INTERACT_LOOPC)); + + // Read the map (model_positions) size + size_t map_size; + inFile.read(reinterpret_cast(&map_size), sizeof(map_size)); + + // Read each key-value pair in the map + for (size_t i = 0; i < map_size; ++i) { + // Read the string length, then the string (key) + size_t key_size; + inFile.read(reinterpret_cast(&key_size), sizeof(key_size)); + std::string key(key_size, '\0'); + inFile.read(&key[0], key_size); + + // Read the drawpoint struct (x and y) + Gmeng::Renderer::drawpoint drawpoint; + inFile.read(reinterpret_cast(&drawpoint.x), sizeof(drawpoint.x)); + inFile.read(reinterpret_cast(&drawpoint.y), sizeof(drawpoint.y)); + + // Insert into the map + properties.model_positions[key] = drawpoint; + } + + inFile.close(); + return properties; +}; diff --git a/lib/bin/src/renderer.cpp b/lib/bin/src/renderer.cpp index 8c18bdb..373bd0f 100755 --- a/lib/bin/src/renderer.cpp +++ b/lib/bin/src/renderer.cpp @@ -164,6 +164,7 @@ namespace Gmeng { } } else { std::cerr << "Gmeng::Renderer::get_placement: e_obj: out of boundaries: __getsize(__p, __s, __ws);" << std::endl; }; }; + ASSERT("pref.log", p_no); return vec; }; /// returns placement coordinates for a viewpoint within a map sizeof drawpointxy @@ -1053,6 +1054,7 @@ namespace Gmeng { if (global.debugger) { gm_slog(YELLOW, "DEBUGGER", "^^ above is reinterpereted_data from _vconcatenate_lvl_chunks"); }; + ASSERT("pref.log", p_no); return reinterpereted_data; }; diff --git a/lib/bin/utils/util.cpp b/lib/bin/utils/util.cpp index c2d1296..c7cd9f0 100644 --- a/lib/bin/utils/util.cpp +++ b/lib/bin/utils/util.cpp @@ -6,6 +6,24 @@ /// General Utilities for Gmeng namespace Gmeng::Util { + /// @since 10.1.0 + /// TODO: does not support cubic rendering, add option + std::string draw_texture_string(Gmeng::texture txt) { + Gmeng::Camera<1,1> cam; + std::string final; + for (int i = 0; i < txt.width*txt.height; i++) { + if (i != 0 && i % txt.width == 0) final += '\n'; + if (i >= txt.units.size()) final += cam.draw_unit({ + .color = 0, + .collidable = true, + .special = true, + .special_clr = 4, + .special_c_unit = "?" + }); + else final += cam.draw_unit(txt.units.at(i)); + }; + return final; + }; /// fills a texture with a single color void texture_fill(texture& tx, color_t color) { for (int i = 0; i < tx.width*tx.height && i < tx.units.size(); i++) { @@ -18,9 +36,14 @@ namespace Gmeng::Util { for (int i = 0; i < tx.width*tx.height && i < tx.units.size(); i++) { auto d = tx.units.at(i); if (d.color == color1) d.color = color2; + tx.units[i] = d; }; }; + int calculate_proximity(Renderer::drawpoint pos1, Renderer::drawpoint pos2) { + return std::abs(pos1.x - pos2.x) + std::abs(pos1.y - pos2.y); + }; + void level_set_skybox(Level* lvl, texture& tx) { lvl->base.height = tx.height; lvl->base.width = tx.width; diff --git a/makefile b/makefile index 139c2c2..322c301 100755 --- a/makefile +++ b/makefile @@ -5,6 +5,10 @@ CXXFLAGS := -Linclude -Iinclude --std=c++2a -pthread `pkg-config --libs --cflags VERSIONFLAGS := -DGMENG_BUILD_NO="UNKNOWN" OUTFILE := -o gmeng +# Compiler to Windows +WINDOWS_CXX := i686-w64-mingw32-g++ # MinGW compiler for unix-to-windows cross compile. +WINDOWS_CXXFLAGS := -std=c++20 -Wno-write-strings -Wno-return-type -static -static-libgcc -static-libstdc++ -lpthread -pthread + USE_NCURSES := true USE_EXTERNAL := false TARGET_NAME := all @@ -16,9 +20,11 @@ BUILD_NUMBER := $(shell printf G$$RANDOM-$$RANDOM) ifeq ($(OS), Windows_NT) BUILD_NUMBER := $(shell echo | set /p version="G%random%-%random%") + WINDOWS_CXX := g++ endif ifeq ($(UNAME_S), Windows_NT) BUILD_NUMBER := $(shell echo | set /p version="G%random%-%random%") + WINDOWS_CXX := g++ endif $(info selected build number: $(BUILD_NUMBER)) @@ -39,6 +45,15 @@ $(info buildoptions selected.) endif endif +ifeq ($(filter compile-windows, $(MAKECMDGOALS)),compile-windows) +ifeq ($(wildcard buildoptions.mk),) +$(error create a file named `buildoptions.mk` and add the line `TARGET_NAME := your_game_code.cpp`.) +else +include buildoptions.mk +$(info buildoptions selected. (WINDOWS)) +endif +endif + # If no arguments were passed, check for buildoptions.mk ifndef skip_warning ifeq ($(wildcard buildoptions.mk),) @@ -175,5 +190,16 @@ compile: @echo TARGET WILL BE NAMED\: ./game.out $(CXX) $(VERSIONFLAGS) $(CXXFLAGS) $(TARGET_NAME) -o game.out +compile-windows: + @echo CROSS COMPILING TO WINDOWS + @echo COMPILER\: $(WINDOWS_CXX) + @echo FLAGS\: $(WINDOWS_CXXFLAGS) + @echo + @echo GMENG-ACCEPTED COMPILING FLAGS + @echo COMPILING YOUR BUILDOPTIONS.MK + @echo TO CONFIGURE, EDIT THE FILE buildoptions.mk TO CHANGE YOUR TARGET + @echo EXECUTABLE WILL BE NAMED\: ./game.exe + $(WINDOWS_CXX) $(VERSIONFLAGS) $(WINDOWS_CXXFLAGS) $(TARGET_NAME) -o game.exe + # Phony targets -.PHONY: all test test2 debug no-ncurses warnings configure compile +.PHONY: all test test2 debug no-ncurses warnings configure compile compile-windows diff --git a/readme.md b/readme.md index d9f31db..b2a35fc 100755 --- a/readme.md +++ b/readme.md @@ -10,7 +10,7 @@

## Changelog -[`See what's new`](CHANGELOG.md) **(25-Oct-2024) info**: Released version 10.0.0: Game Event Loop | [`gmeng.org/changelog`](https://gmeng.org/changelog) +[`See what's new`](CHANGELOG.md) **(2-Nov-2024) info**: 10.1.0: game state binaries & general improvements | [`gmeng.org/changelog`](https://gmeng.org/changelog) ## Documentation Gmeng's documentation can be found in [`gmeng.org`](https://gmeng.org). Please refer to the website for enquiries about functionality and usage. @@ -62,15 +62,15 @@ builds: make test (builds interface tests / test.cpp) make test2 (builds unit tests / tests/test.cpp) make compile (builds your target file / specified in buildoptions.mk or `make configure`) + make compile-windows (builds your target file / cross compiled to windows) options: - make [debug] [no-ncurses] [use-external] [warnings] [all/test/test2] + make [debug] [no-ncurses] [use-external] [warnings] [all/test/test2/compile/compile-windows] make configure ``` - The `debug` option adds the `-g -O0 -fsanitize=address` flags to the compiler. - The `no-ncurses` option disables the auto-imports to `utils/interface.cpp` and `types/interface.h` from the `gmeng.h` header. - The `use-external` option enables the auto-imports to `SDL2/SDL.h` headers for SDL-based windows. - The `warnings` option enables `-Wall` so all warnings are displayed by the compiler. - - The `configure` option runs the configuration utility to set up the buildconfig for a program. ## Debugging diff --git a/tests/event_loop.cpp b/tests/event_loop.cpp new file mode 100644 index 0000000..b2a18bf --- /dev/null +++ b/tests/event_loop.cpp @@ -0,0 +1,232 @@ +#include +#include "../lib/bin/gmeng.h" + +using namespace Gmeng; +using namespace Gmeng::Util; + +static Level level; +static gmeng_properties_t cfg; +static EventLoop ev( {} ); + +void reset() { + _uread_into_vgm("envs/models"); + if (!filesystem::exists("gamestate.cfg")) writeout_properties("gamestate.cfg", default_properties); + cfg = read_properties("gamestate.cfg"); + + texture empty_texture = Renderer::generate_empty_texture(cfg.SKY_WIDTH, cfg.SKY_HEIGHT); + texture_fill(empty_texture, cfg.SKY_COLOR); + level_set_skybox(&level, empty_texture); + + texture cake_texture = default_texture_search("smol_player"); + texture_replace_color(cake_texture, RED, GREEN); + + texture island_texture = default_texture_search("01_island"); + texture gift_texture = default_texture_search("02_gift"); + texture real_cake_texture = default_texture_search("01_cake_txtr"); + texture table_texture = default_texture_search("03_table"); + + level.chunks.at(0) = { { {0, 0}, {99, 99} }, + { + model_from_txtr( cake_texture, cfg.model_positions["player"] ), // this is actually the player + + model_from_txtr(table_texture, cfg.model_positions["table1"]), + model_from_txtr(table_texture, cfg.model_positions["table2"]), + + model_from_txtr(real_cake_texture, cfg.model_positions["cake"]), + + model_from_txtr(gift_texture, cfg.model_positions["gift1"]), + model_from_txtr(gift_texture, cfg.model_positions["gift2"]), + } + }; + + level.display.viewpoint = { {0,0}, { cfg.DEF_DELTAX, cfg.DEF_DELTAY } }; + level.display.set_resolution(cfg.DEF_DELTAX, cfg.DEF_DELTAY); + + ev.level = &level; +}; + +int main(int argc, char** argv) { + _uread_into_vgm("envs/models"); + patch_argv_global(argc, argv); /// gmeng initialization + + if (!filesystem::exists("gamestate.cfg")) writeout_properties("gamestate.cfg", default_properties); + cfg = read_properties("gamestate.cfg"); + + int* DEF_DELTAY = &cfg.DEF_DELTAY; + int* DEF_DELTAX = &cfg.DEF_DELTAX; + + texture empty_texture = Renderer::generate_empty_texture(cfg.SKY_WIDTH, cfg.SKY_HEIGHT); + texture_fill(empty_texture, cfg.SKY_COLOR); + level_set_skybox(&level, empty_texture); + + texture cake_texture = default_texture_search("smol_player"); + texture_replace_color(cake_texture, RED, GREEN); + + texture island_texture = default_texture_search("01_island"); + texture gift_texture = default_texture_search("02_gift"); + texture real_cake_texture = default_texture_search("01_cake_txtr"); + texture table_texture = default_texture_search("03_table"); + level.load_chunk({ { {0, 0}, {99, 99} }, + { + model_from_txtr( cake_texture, cfg.model_positions["player"] ), // this is actually the player + + model_from_txtr(table_texture, cfg.model_positions["table1"]), + model_from_txtr(table_texture, cfg.model_positions["table2"]), + + model_from_txtr(real_cake_texture, cfg.model_positions["cake"]), + + model_from_txtr(gift_texture, cfg.model_positions["gift1"]), + model_from_txtr(gift_texture, cfg.model_positions["gift2"]), + } + }); + + level.display.viewpoint = { {0,0}, { *DEF_DELTAX, *DEF_DELTAY } }; + level.display.set_resolution(*DEF_DELTAX, *DEF_DELTAY); + ev.level = &level; + std::vector renderscale; + + ev.add_hook({ INIT }, [&](Level* level, EventInfo* info) { + std::cout << "Initialized the Game Event Hook (external hook received this event)\n"; + renderscale = get_renderscale(*level); + level->display.camera.clear_screen(); + ev.call_event(FIXED_UPDATE, *info); + }); + + ev.add_hook( { FIXED_UPDATE }, + [&](Level* level, EventInfo* info) { + if (info->EVENT == MOUSE_MOVE) return; + std::string lvl_view = get_lvl_view(*level, renderscale); + emplace_lvl_camera(*level, lvl_view); + level->display.camera.reset_cur(); + auto time = GET_TIME(); + std::cout << level->display.camera.draw() << "\n" << Gmeng::resetcolor; + level->display.camera.draw_time = GET_TIME() - time; + level->display.camera.draw_info(vp_width(level->display.viewpoint)+2, 0); + }); + + ev.add_hook({ KEYPRESS }, [&](Level* level, EventInfo* info) { + if (info->KEYPRESS_CODE == 'q') { + std::cout << "quiting the game event loop.\n"; + ev.cancelled = true; // cancel the game event loop + info->prevent_default = true; // disable other default hooks + }; + auto cake_model = level->chunks.at(0).models.at(0); + auto real_cake_model = level->chunks.at(0).models.at(3); + bool nomove = info->prevent_default; + switch (info->KEYPRESS_CODE) { + case 'a': case 'A': + if (cake_model.position.x-((int)nomove) > 0) { + cake_model.position.x--; + if (cake_model.position.x-((int)nomove)-cake_model.width <= level->display.viewpoint.start.x) { + level->display.viewpoint.start.x--; + level->display.viewpoint.end.x--; + }; + if (nomove) cake_model.position.x++; + }; + break; + case 'd': case 'D': + if (cake_model.position.x+((int)nomove)+cake_model.width < level->base.width-1) { + cake_model.position.x++; + if (cake_model.position.x+((int)nomove)+((cake_model.width)*2) > level->display.viewpoint.end.x) { + level->display.viewpoint.start.x++; + level->display.viewpoint.end.x++; + }; + if (nomove) cake_model.position.x--; + }; + break; + case 'w': case 'W': + if (cake_model.position.y-((int)nomove) > 0) { + cake_model.position.y--; + if (cake_model.position.y-((int)nomove)-cake_model.height <= level->display.viewpoint.start.y) { + level->display.viewpoint.start.y--; + level->display.viewpoint.end.y--; + }; + if (nomove) cake_model.position.y++; + }; + break; + case 's': case 'S': + if (cake_model.position.y+((int)nomove)+cake_model.height < level->base.height-1) { + cake_model.position.y++; + if (cake_model.position.y+((int)nomove)+((cake_model.height)*2) >= level->display.viewpoint.end.y) { + level->display.viewpoint.start.y++; + level->display.viewpoint.end.y++; + }; + if (nomove) cake_model.position.y--; + }; + break; + case 'r': case 'R': + level->display.camera.clear_screen(); + if (!info->prevent_default) { // scope + auto tim_ca = GET_TIME(); + reset(); + tim_ca = GET_TIME() - tim_ca; + renderscale = get_renderscale(*level); + level->display.camera.set_curXY(3,*DEF_DELTAX+2); + WRITE_PARSED("RESET performed in "$(tim_ca)"ms"); + }; + level->display.camera.draw_info(*DEF_DELTAX+2, 0); + break; + case 'e': case 'E': + if (calculate_proximity(cake_model.position, real_cake_model.position) <= 3) { + color_t prev_color = BLUE; + for (int e = 0; e < cfg.A00_CAKE_INTERACT_LOOPC; e++) { + color_t color = (color_t)(e%8); + auto tim = GET_TIME(); + texture_replace_color(level->base.lvl_template, prev_color, color); + prev_color = color; + renderscale = get_renderscale(*level); + ev.call_event(FIXED_UPDATE, Gmeng::NO_EVENT_INFO); + sleep(ms( std::max(cfg.model_positions["CAKE_INTERACT_TIMES"].x, cfg.model_positions["CAKE_INTERACT_TIMES"].y - (int)(GET_TIME()-tim)) )); + }; + texture_replace_color(level->base.lvl_template, prev_color, BLUE); + }; + break; + default: + break; + }; + + if (level->display.viewpoint.start.x < 0) level->display.viewpoint.start.x = 0, level->display.viewpoint.end.x = *DEF_DELTAX; + if (level->display.viewpoint.start.y < 0) level->display.viewpoint.start.y = 0, level->display.viewpoint.end.y = *DEF_DELTAY; + if (level->display.viewpoint.end.x > level->base.width) level->display.viewpoint.end.x = level->base.width, level->display.viewpoint.start.x = level->base.width-*DEF_DELTAX; + if (level->display.viewpoint.end.y > level->base.height) level->display.viewpoint.end.y = level->base.height, level->display.viewpoint.start.y = level->base.height-*DEF_DELTAY; + + level->chunks.at(0).models.at(0) = cake_model; + level->chunks.at(0).models.at(3) = real_cake_model; + renderscale = get_renderscale(*level); + }); + + ev.add_hook({ MOUSE_CLICK_LEFT_START, MOUSE_CLICK_MIDDLE_START, MOUSE_CLICK_RIGHT_START }, + [&](Level* level, EventInfo* info) { + string button_name = info->EVENT == MOUSE_CLICK_LEFT_START ? "left" : + ( info->EVENT == MOUSE_CLICK_RIGHT_START ? "right" : "middle" ); + //std::cout << "MOUSE CLICK: " << colors[GREEN] << button_name << " click at " << v_str(info->MOUSE_X_POS) << "," << v_str(info->MOUSE_Y_POS) << "\n" << colors[WHITE]; + if (info->EVENT == MOUSE_CLICK_LEFT_START) { + if (!viewpoint_includes_dp( + level->chunks.at(0).vp, { + (int)level->chunks.at(0).models.at(0).width + info->MOUSE_X_POS, + (int)level->chunks.at(0).models.at(0).height + info->MOUSE_Y_POS + } + )) return; + level->chunks.at(0).models.at(0).position = { info->MOUSE_X_POS, info->MOUSE_Y_POS*2 }; + renderscale = get_renderscale(*level); + }; + }); + + ev.add_hook({ MOUSE_SCROLL_UP, MOUSE_SCROLL_DOWN }, + [&](Level* lv, EventInfo* info) { + // asumes user goes down + EventInfo info_d = EventInfo { KEYPRESS, '{', -1, -1, true }; + if (info->EVENT == MOUSE_SCROLL_UP) { + lv->display.viewpoint.start.y--; + lv->display.viewpoint.end.y--; + } else if (info->EVENT == MOUSE_SCROLL_DOWN) { + lv->display.viewpoint.start.y++; + lv->display.viewpoint.end.y++; + }; + ev.call_event(KEYPRESS, info_d); + }); + + do_event_loop(&ev); + std::cout << "end program\n"; + return 0; +}; diff --git a/tests/prototype.cpp b/tests/prototype.cpp new file mode 100644 index 0000000..c5bb614 --- /dev/null +++ b/tests/prototype.cpp @@ -0,0 +1,154 @@ +#include +#include +#include "../lib/bin/gmeng.h" + +using namespace Gmeng; +using namespace Gmeng::Util; + +static Level level; + +int main(int argc, char** argv) { + _uread_into_vgm("envs/models"); + patch_argv_global(argc, argv); /// gmeng initialization + EventLoop ev( {} ); + + int DEF_DELTAX = 50; + int DEF_DELTAY = 25; + + + texture empty_texture = Renderer::generate_empty_texture(100, 100); + texture_fill(empty_texture, BLUE); + level_set_skybox(&level, empty_texture); + texture cake_texture = default_texture_search("smol_player"); + texture island_texture = default_texture_search("01_island"); + texture gift_texture = default_texture_search("02_gift"); + texture real_cake_texture = default_texture_search("01_cake_txtr"); + texture table_texture = default_texture_search("03_table"); + level.load_chunk({ { {0, 0}, {99, 99} }, + { + model_from_txtr( cake_texture, { 0,0 } ), + model_from_txtr(island_texture, {10, 10}), + model_from_txtr(gift_texture, { 0, DEF_DELTAY-(int)gift_texture.height+1 }) + } + }); + + level.display.viewpoint = { {0,0}, { DEF_DELTAX, DEF_DELTAY } }; + level.display.set_resolution(DEF_DELTAX, DEF_DELTAY); + ev.level = &level; + std::vector renderscale; + + ev.add_hook({ INIT }, [&](Level* level, EventInfo* info) { + std::cout << "Initialized the Game Event Hook (external hook received this event)\n"; + renderscale = get_renderscale(*level); + level->display.camera.clear_screen(); + }); + + ev.add_hook( { FIXED_UPDATE }, + [&](Level* level, EventInfo* info) { + if (info->EVENT == MOUSE_MOVE) return; + std::string lvl_view = get_lvl_view(*level, renderscale); + emplace_lvl_camera(*level, lvl_view); + level->display.camera.reset_cur(); + auto time = GET_TIME(); + std::cout << level->display.camera.draw() << "\n" << Gmeng::resetcolor; + level->display.camera.draw_time = GET_TIME() - time; + level->display.camera.draw_info(vp_width(level->display.viewpoint)+2, 0); + }); + + ev.add_hook({ KEYPRESS }, [&](Level* level, EventInfo* info) { + if (info->KEYPRESS_CODE == 'q') { + std::cout << "quiting the game event loop.\n"; + ev.cancelled = true; // cancel the game event loop + info->prevent_default = true; // disable other default hooks + }; + auto cake_model = level->chunks.at(0).models.at(0); + bool nomove = info->prevent_default; + switch (info->KEYPRESS_CODE) { + case 'a': case 'A': + if (cake_model.position.x-((int)nomove) > 0) { + cake_model.position.x--; + if (cake_model.position.x-((int)nomove)-cake_model.width <= level->display.viewpoint.start.x) { + level->display.viewpoint.start.x--; + level->display.viewpoint.end.x--; + }; + if (nomove) cake_model.position.x++; + }; + break; + case 'd': case 'D': + if (cake_model.position.x+((int)nomove)+cake_model.width < level->base.width-1) { + cake_model.position.x++; + if (cake_model.position.x+((int)nomove)+((cake_model.width)*2) > level->display.viewpoint.end.x) { + level->display.viewpoint.start.x++; + level->display.viewpoint.end.x++; + }; + if (nomove) cake_model.position.x--; + }; + break; + case 'w': case 'W': + if (cake_model.position.y-((int)nomove) > 0) { + cake_model.position.y--; + if (cake_model.position.y-((int)nomove)-cake_model.height <= level->display.viewpoint.start.y) { + level->display.viewpoint.start.y--; + level->display.viewpoint.end.y--; + }; + if (nomove) cake_model.position.y++; + }; + break; + case 's': case 'S': + if (cake_model.position.y+((int)nomove)+cake_model.height < level->base.height-1) { + cake_model.position.y++; + if (cake_model.position.y+((int)nomove)+((cake_model.height)*2) >= level->display.viewpoint.end.y) { + level->display.viewpoint.start.y++; + level->display.viewpoint.end.y++; + }; + if (nomove) cake_model.position.y--; + }; + break; + default: + break; + }; + + if (level->display.viewpoint.start.x < 0) level->display.viewpoint.start.x = 0, level->display.viewpoint.end.x = DEF_DELTAX; + if (level->display.viewpoint.start.y < 0) level->display.viewpoint.start.y = 0, level->display.viewpoint.end.y = DEF_DELTAY; + if (level->display.viewpoint.end.x > level->base.width) level->display.viewpoint.end.x = level->base.width, level->display.viewpoint.start.x = level->base.width-DEF_DELTAX; + if (level->display.viewpoint.end.y > level->base.height) level->display.viewpoint.end.y = level->base.height, level->display.viewpoint.start.y = level->base.height-DEF_DELTAY; + + level->chunks.at(0).models.at(0) = cake_model; + renderscale = get_renderscale(*level); + }); + + ev.add_hook({ MOUSE_CLICK_LEFT_START, MOUSE_CLICK_MIDDLE_START, MOUSE_CLICK_RIGHT_START }, + [&](Level* level, EventInfo* info) { + string button_name = info->EVENT == MOUSE_CLICK_LEFT_START ? "left" : + ( info->EVENT == MOUSE_CLICK_RIGHT_START ? "right" : "middle" ); + //std::cout << "MOUSE CLICK: " << colors[GREEN] << button_name << " click at " << v_str(info->MOUSE_X_POS) << "," << v_str(info->MOUSE_Y_POS) << "\n" << colors[WHITE]; + if (info->EVENT == MOUSE_CLICK_LEFT_START) { + if (!viewpoint_includes_dp( + level->chunks.at(0).vp, { + (int)level->chunks.at(0).models.at(0).width + info->MOUSE_X_POS, + (int)level->chunks.at(0).models.at(0).height + info->MOUSE_Y_POS + } + )) return; + level->chunks.at(0).models.at(0).position = { info->MOUSE_X_POS, info->MOUSE_Y_POS*2 }; + renderscale = get_renderscale(*level); + }; + }); + + ev.add_hook({ MOUSE_SCROLL_UP, MOUSE_SCROLL_DOWN }, + [&](Level* lv, EventInfo* info) { + // asumes user goes down + EventInfo info_d = EventInfo { KEYPRESS, '{', -1, -1, true }; + if (info->EVENT == MOUSE_SCROLL_UP) { + lv->display.viewpoint.start.y--; + lv->display.viewpoint.end.y--; + } else if (info->EVENT == MOUSE_SCROLL_DOWN) { + lv->display.viewpoint.start.y++; + lv->display.viewpoint.end.y++; + }; + ev.call_event(KEYPRESS, info_d); + }); + + do_event_loop(&ev); + std::cout << "end program\n"; + return 0; +};