From b6aab033701af5a086edac4e7ff6dbc50ec6b19d Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Tue, 28 Oct 2025 16:48:35 -0500 Subject: [PATCH] Creating generic term renderer. --- CMakeLists.txt | 7 ++ assets/8x8ascii-darkrose/README.md | 10 ++ assets/8x8ascii-darkrose/font.png | Bin 0 -> 1359 bytes cmake/Findraylib.cmake | 61 +++++++++++ src/cutscene/testcutscene.h | 6 +- src/main.c | 4 +- src/microrpg.h | 4 +- src/platform/CMakeLists.txt | 5 +- src/platform/platform.h | 12 +- src/platform/raylib/CMakeLists.txt | 27 +++++ src/platform/raylib/font.c | 89 +++++++++++++++ src/platform/raylib/font.h | 60 ++++++++++ src/platform/raylib/inputraylib.c | 24 ++++ src/platform/raylib/platform.c | 44 ++++++++ src/platform/raylib/termraylib.c | 84 ++++++++++++++ src/platform/term/CMakeLists.txt | 15 --- src/platform/term/term.c | 108 ++---------------- src/platform/term/term.h | 70 ++++++------ src/platform/term/termcolor.h | 20 ++++ src/platform/tty/CMakeLists.txt | 29 +++++ src/platform/{term => tty}/inputterm.c | 0 src/platform/{term => tty}/platform.c | 0 src/platform/tty/term.c | 146 +++++++++++++++++++++++++ src/platform/tty/term.h | 51 +++++++++ tools/CMakeLists.txt | 6 + tools/embed/CMakeLists.txt | 22 ++++ tools/embed/embed.cmake | 21 ++++ 27 files changed, 770 insertions(+), 155 deletions(-) create mode 100644 assets/8x8ascii-darkrose/README.md create mode 100644 assets/8x8ascii-darkrose/font.png create mode 100644 cmake/Findraylib.cmake create mode 100644 src/platform/raylib/CMakeLists.txt create mode 100644 src/platform/raylib/font.c create mode 100644 src/platform/raylib/font.h create mode 100644 src/platform/raylib/inputraylib.c create mode 100644 src/platform/raylib/platform.c create mode 100644 src/platform/raylib/termraylib.c mode change 100755 => 100644 src/platform/term/CMakeLists.txt mode change 100755 => 100644 src/platform/term/term.c mode change 100755 => 100644 src/platform/term/term.h create mode 100644 src/platform/term/termcolor.h create mode 100755 src/platform/tty/CMakeLists.txt rename src/platform/{term => tty}/inputterm.c (100%) rename src/platform/{term => tty}/platform.c (100%) create mode 100755 src/platform/tty/term.c create mode 100755 src/platform/tty/term.h create mode 100644 tools/CMakeLists.txt create mode 100644 tools/embed/CMakeLists.txt create mode 100644 tools/embed/embed.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 4daa46f..e98be78 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,9 +11,16 @@ project(microrpg ) set(CMAKE_C_STANDARD 99) +list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +set(ASSETS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/assets" CACHE PATH "Path to the assets directory") +set(TOOLS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tools" CACHE PATH "Path to the tools directory") # Executable add_executable(microrpg) +# Tools +add_subdirectory(tools) + # Add sources add_subdirectory(src) \ No newline at end of file diff --git a/assets/8x8ascii-darkrose/README.md b/assets/8x8ascii-darkrose/README.md new file mode 100644 index 0000000..04a7cde --- /dev/null +++ b/assets/8x8ascii-darkrose/README.md @@ -0,0 +1,10 @@ +This is a simple 32x32 bitmap font I made as a byproduct of messing about with Xlib. Because of its a power of 2 sizing, it should be able to be resized easily, even if you're writting your own render. + +font.png is a screenshot of the output of my rendering text using font.c. +font.zip contains font.c, which is just a C array of 64bit unsigned integers, these are the original bitmaps for the 8x8 font which I resized 4x to get font.png. + +Note the font.c is licensed GPLv3 or later only, the font.png is also licensed GPLv2 and CC-BY-SA. +Copyright/Attribution Notice: +To satisfy CC-BY-SA's attribution clause, just give a link back to OGA. + +# Original URL: https://opengameart.org/content/8x8-ascii-bitmap-font-with-c-source \ No newline at end of file diff --git a/assets/8x8ascii-darkrose/font.png b/assets/8x8ascii-darkrose/font.png new file mode 100644 index 0000000000000000000000000000000000000000..bd36c1f3e3876a065a67c7512738fb099b837051 GIT binary patch literal 1359 zcmV-V1+e;wP)Px)2}wjjRCt{2TkVeHFbH&3eg9X^{aB?-DGY;6()8?+PPg?3V4Gl=v|DR!-}k*) z*tV^;-vO)tKkff9dlsg_a)8OP9AI)R2bdho0Vc=RTC-Ecg( ztOzhUmIF+V38ZEM@MK^bx!LvVQd(OhW*>O*qw;24&D);_nEaDYRgwQ%O? zkVeTvJPbPeqeEitMEaxop8e?aU4Rb%fLvoq1klc5Z8UnqrDsZqu;$D5D-S`h$=YNq z!m*IoTkpDh9-fjaz%n>`61A~bJNg^xKz2rRyfNCoXx!C^^m=>w<^a75U^Ye~GS(WS z+k0${?(fmV%N#_Nd{(Blub1-xpL#;>fIF^z5kZsVp&NQbI2+=<5N1K=2y9<=KGT19 zNc%Ikzc>IQfzZ9f-b*OE9l19n-BtEiA)CZTx_{jSnXE% zEI~T}Q$C?EqzRlTWMxs!AXbu0?7^`%jX08a_LnNaOC&0iEIF<^$z%zhUio_5Xq{i3 z14O#5>(?cM7W#3=I{$!Y+j4-3U_}744D1Pys1D7BUpLRFOPdsvNG@5&ygw*WMMRDWew?_ned zkWadow2jF3_K#u$^`tYBoskG|06Ec_WnPuM=#WR8z?<{a+G`zu{L2s{R=BzfQorUR zs=%x#yKhKN)L*&3DI!Q3fsD!~iMrn#JqL3sU#3BK-c0|FI0zYk4o*ett#~1%?X}402{)+r)1;`@akG`40HdY zke4=!`q<6FEf1xF|=eu%Qy> z*TSp0`U)`m**kay%>DjNczYRyl0S(kiOIeYLf)Stf+CiZadLEsQJEOck7O`%Ej?BV z%$!fx+dvB$X3&xyIRqPe8QM2m!af=C+a zw?H|76z?h0h-Ju8*l=dhq;K@bSO*I#>63Wg41)yp2O1j^=xx z(fEj8h~_i>(r(uN5md^0Qo;IwtU5A!ohrw9RYLUU%5#u2;jP!}_hiU5Tn)Xqtdl$) zAX`>N?H)^4QHMt*s60UZTG%6uq=S04BeDs#AM>l=kvh>DEWCUt&{I- #include -typedef bool bool_t; \ No newline at end of file +typedef bool bool_t; +typedef char char_t; +typedef float float_t; \ No newline at end of file diff --git a/src/platform/CMakeLists.txt b/src/platform/CMakeLists.txt index 74eecee..81008b3 100755 --- a/src/platform/CMakeLists.txt +++ b/src/platform/CMakeLists.txt @@ -3,5 +3,6 @@ # This software is released under the MIT License. # https://opensource.org/licenses/MIT -# Change inclusion to platform-specific -add_subdirectory(term) \ No newline at end of file +add_subdirectory(raylib) +add_subdirectory(term) +#add_subdirectory(term) \ No newline at end of file diff --git a/src/platform/platform.h b/src/platform/platform.h index 7be1d87..edc7726 100644 --- a/src/platform/platform.h +++ b/src/platform/platform.h @@ -8,15 +8,23 @@ #pragma once #include "microrpg.h" +#define PLATFORM_OK 0 +#define PLATFORM_EXIT 1 +#define PLATFORM_ERROR 2 + /** * Initialize the platform-specific subsystem. + * + * @return 0 on success, non-zero on failure. */ -void platformInit(void); +uint8_t platformInit(void); /** * Update the platform-specific subsystem. + * + * @return 0 to continue, 1 to exit, anything else for error. */ -void platformUpdate(void); +uint8_t platformUpdate(void); /** * Render the platform-specific subsystem. diff --git a/src/platform/raylib/CMakeLists.txt b/src/platform/raylib/CMakeLists.txt new file mode 100644 index 0000000..794003e --- /dev/null +++ b/src/platform/raylib/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (c) 2025 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Libraries +find_package(raylib REQUIRED) + +target_link_libraries(microrpg PRIVATE + raylib::raylib +) + +# Sources +target_sources(microrpg PRIVATE + inputraylib.c + termraylib.c + platform.c + font.c +) + +# Embed resources +embed_file(microrpg "${ASSETS_DIR}/8x8ascii-darkrose/font.png" "assets/font.h" FONT) + +# Compiler flags +target_compile_definitions(microrpg PRIVATE + RPG_RAYLIB=1 +) \ No newline at end of file diff --git a/src/platform/raylib/font.c b/src/platform/raylib/font.c new file mode 100644 index 0000000..003d08d --- /dev/null +++ b/src/platform/raylib/font.c @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "font.h" +#include "assets/font.h" + +font_t FONT; + +int32_t fontInit() { + memset(&FONT, 0, sizeof(FONT)); + + FONT.image = LoadImageFromMemory(".png", FONT_DATA, FONT_SIZE); + if(FONT.image.data == NULL) { + printf("Failed to load font image from memory.\n"); + return -1; // Failed to load image + } + + FONT.texture = LoadTextureFromImage(FONT.image); + if(FONT.texture.id == 0) { + printf("Failed to load font texture from image.\n"); + UnloadImage(FONT.image); + return -1; // Failed to load texture + } + + FONT.charWidthRaw = FONT.texture.width / FONT_COLUMN_COUNT; + FONT.charHeightRaw = FONT.texture.height / FONT_ROW_COUNT; + FONT.charWidth = FONT.charWidthRaw * FONT_SCALE; + FONT.charHeight = FONT.charHeightRaw * FONT_SCALE; + + return 0; +} + +void fontDraw( + const char_t c, const float_t x, const float_t y, const Color color +) { + int32_t charAdjusted = (int32_t)c - 1; + if(c >= '`') charAdjusted += 1;// For some reason the font has empty slot? + + int32_t col = (charAdjusted % FONT_COLUMN_COUNT); + int32_t row = (charAdjusted / FONT_COLUMN_COUNT); + + Rectangle sourceRec = { + .x = (float_t)(col * FONT.charWidthRaw), + .y = (float_t)(row * FONT.charHeightRaw), + .width = (float_t)FONT.charWidthRaw, + .height = (float_t)FONT.charHeightRaw + }; + Rectangle destRec = { + .x = x, + .y = y, + .width = (float_t)FONT.charWidth, + .height = (float_t)FONT.charHeight + }; + + DrawTexturePro( + FONT.texture, sourceRec, destRec, (Vector2){0, 0}, 0.0f, color + ); +} + +void fontDrawString( + const char_t *str, const float_t x, const float_t y, const Color color +) { + float_t cx, cy; + cx = x; + cy = y; + + const char_t *c = (char_t *)str; + while(*c) { + if(*c == '\n') { + cx = x; + cy += (float_t)(FONT.charHeight + FONT_CHAR_SPACING); + c++; + continue; + } + + fontDraw(*c, cx, cy, color); + cx += (float_t)(FONT.charWidth + FONT_CHAR_SPACING); + c++; + } +} + +void fontDispose() { + UnloadTexture(FONT.texture); + UnloadImage(FONT.image); +} \ No newline at end of file diff --git a/src/platform/raylib/font.h b/src/platform/raylib/font.h new file mode 100644 index 0000000..862f9ed --- /dev/null +++ b/src/platform/raylib/font.h @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "microrpg.h" +#include + +#define FONT_COLUMN_COUNT 16 +#define FONT_ROW_COUNT 8 +#define FONT_CHAR_SPACING 0// Font has built in spacing +#define FONT_SCALE 3.0f + +typedef struct font_s { + Texture2D texture; + Image image; + int32_t charWidthRaw; + int32_t charHeightRaw; + int32_t charWidth; + int32_t charHeight; +} font_t; + +extern font_t FONT; + +/** + * Initializes the font. + */ +int32_t fontInit(); + +/** + * Draws a character at the specified position. + * + * @param c The character to draw. + * @param x The x position. + * @param y The y position. + * @param color The color to draw the character with. + */ +void fontDraw( + const char_t c, const float_t x, const float_t y, const Color color +); + +/** + * Draws a string at the specified position. No wrapping is performed. + * + * @param str The string to draw. + * @param x The x position. + * @param y The y position. + * @param color The color to draw the string with. + */ +void fontDrawString( + const char_t *str, const float_t x, const float_t y, const Color color +); + +/** + * Disposes of the font. + */ +void fontDispose(); \ No newline at end of file diff --git a/src/platform/raylib/inputraylib.c b/src/platform/raylib/inputraylib.c new file mode 100644 index 0000000..8b5c0fe --- /dev/null +++ b/src/platform/raylib/inputraylib.c @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "input.h" + +bool_t inputDown(const uint8_t action) { + return false; +} + +bool_t inputUp(const uint8_t action) { + return true; +} + +bool_t inputWasDown(const uint8_t action) { + return false; +} + +bool_t inputWasUp(const uint8_t action) { + return true; +} \ No newline at end of file diff --git a/src/platform/raylib/platform.c b/src/platform/raylib/platform.c new file mode 100644 index 0000000..f3a4ed4 --- /dev/null +++ b/src/platform/raylib/platform.c @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "platform/platform.h" +#include +#include "font.h" +#include "platform/term/term.h" + +uint8_t platformInit(void) { + InitWindow(800, 600, "Micro JRPG"); + SetTargetFPS(60); + + if(fontInit() != 0) return PLATFORM_ERROR; + + SetWindowSize(40 * FONT.charWidth, 30 * FONT.charHeight); + + return PLATFORM_OK; +} + +uint8_t platformUpdate() { + if(WindowShouldClose()) { + return PLATFORM_EXIT; + } + + return PLATFORM_OK; +} + +void platformDraw() { + BeginDrawing(); + ClearBackground(BLACK); + + termDraw(); + + EndDrawing(); +} + +void platformDispose() { + fontDispose(); + CloseWindow(); +} \ No newline at end of file diff --git a/src/platform/raylib/termraylib.c b/src/platform/raylib/termraylib.c new file mode 100644 index 0000000..de7f213 --- /dev/null +++ b/src/platform/raylib/termraylib.c @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "font.h" +#include "platform/term/term.h" + +typedef struct termraylibcolormap_t { + termcolor_t termColor; + Color raylibColor; +} termraylibcolormap_t; + +typedef struct termraylib_s { + Color currentColor; + float_t x, y; +} termraylib_t; + +termraylibcolormap_t TERM_RAYLIB_COLORMAP[] = { + { TERM_COLOR_BLACK, BLACK }, + { TERM_COLOR_RED, RED }, + { TERM_COLOR_GREEN, GREEN }, + { TERM_COLOR_YELLOW, YELLOW }, + { TERM_COLOR_BLUE, BLUE }, + { TERM_COLOR_MAGENTA, MAGENTA }, + { TERM_COLOR_CYAN, SKYBLUE }, + { TERM_COLOR_WHITE, WHITE } +}; + +#define TERM_RAYLIB_INITIAL_COLOR WHITE + +termraylib_t TERM_RAYLIB; + +void termClear() { + TERM_RAYLIB.currentColor = WHITE; + TERM_RAYLIB.x = 0.0f; + TERM_RAYLIB.y = 0.0f; +} + +void termFlush() { + // Nothing to do +} + +uint8_t termGetColumnCount() { + // Get Window width + int32_t windowWidth = GetScreenWidth(); + return (uint8_t)(windowWidth / FONT.charWidth); +} + +uint8_t termGetRowCount() { + // Get Window height + int32_t windowHeight = GetScreenHeight(); + return (uint8_t)(windowHeight / FONT.charHeight); +} + +void termPushColor(termcolor_t color) { + // Find the corresponding raylib color + size_t colors = sizeof(TERM_RAYLIB_COLORMAP) / sizeof(termraylibcolormap_t); + for(size_t i = 0; i < colors; i++) { + if(TERM_RAYLIB_COLORMAP[i].termColor == color) { + TERM_RAYLIB.currentColor = TERM_RAYLIB_COLORMAP[i].raylibColor; + return; + } + } +} + +void termPushChar(char_t c) { + if(c == '\n') { + TERM_RAYLIB.x = 0.0f; + TERM_RAYLIB.y += (float_t)(FONT.charHeight + FONT_CHAR_SPACING); + return; + } + + fontDraw(c, TERM_RAYLIB.x, TERM_RAYLIB.y, TERM_RAYLIB.currentColor); + TERM_RAYLIB.x += (float_t)(FONT.charWidth + FONT_CHAR_SPACING); + + // Wrapping + if(TERM_RAYLIB.x + FONT.charWidth > (float_t)GetScreenWidth()) { + TERM_RAYLIB.x = 0.0f; + TERM_RAYLIB.y += (float_t)(FONT.charHeight + FONT_CHAR_SPACING); + } +} \ No newline at end of file diff --git a/src/platform/term/CMakeLists.txt b/src/platform/term/CMakeLists.txt old mode 100755 new mode 100644 index f06a029..37b8d22 --- a/src/platform/term/CMakeLists.txt +++ b/src/platform/term/CMakeLists.txt @@ -3,24 +3,9 @@ # This software is released under the MIT License. # https://opensource.org/licenses/MIT -# Libraries -find_package(Curses REQUIRED) - -target_link_libraries(microrpg PRIVATE - ${CURSES_LIBRARIES} -) -target_include_directories(microrpg PRIVATE - ${CURSES_INCLUDE_DIR} -) -target_link_libraries(microrpg PRIVATE - ${CURSES_LIBRARIES} -) - # Sources target_sources(microrpg PRIVATE term.c - inputterm.c - platform.c ) # Compiler flags diff --git a/src/platform/term/term.c b/src/platform/term/term.c old mode 100755 new mode 100644 index acdedf3..6740acf --- a/src/platform/term/term.c +++ b/src/platform/term/term.c @@ -6,107 +6,11 @@ */ #include "term.h" -#include "input.h" -#include - -typedef struct terminputmap_s { - int key; - uint8_t action; -} terminputmap_t; - -static const terminputmap_t TERM_INPUT_MAP[] = { - { KEY_UP, INPUT_ACTION_UP }, - { 'w', INPUT_ACTION_UP }, - { KEY_DOWN, INPUT_ACTION_DOWN }, - { 's', INPUT_ACTION_DOWN }, - { KEY_LEFT, INPUT_ACTION_LEFT }, - { 'a', INPUT_ACTION_LEFT }, - { KEY_RIGHT, INPUT_ACTION_RIGHT }, - { 'd', INPUT_ACTION_RIGHT }, - { 'j', INPUT_ACTION_A }, - { 'e', INPUT_ACTION_A }, - { 'k', INPUT_ACTION_B }, - { 'q', INPUT_ACTION_B }, - { KEY_ENTER, INPUT_ACTION_START }, - { ' ', INPUT_ACTION_SELECT }, - { -1, 0 } -}; - -term_t TERM; - -void termInit() { - memset(&TERM, 0, sizeof(TERM)); - - initscr(); - cbreak(); - noecho(); - keypad(stdscr, TRUE); - nodelay(stdscr, TRUE); - curs_set(0); - start_color(); - use_default_colors(); - - init_pair(1, COLOR_GREEN, -1); - - // Set in-game time to real world time. - time_t now = time(NULL); - struct tm *t = localtime(&now); - - GAME.time.days = t->tm_wday; - GAME.time.hours = t->tm_hour; - GAME.time.minutes = t->tm_min; - GAME.time.seconds = t->tm_sec; -} - -void termUpdate() { - TERM.inputPrevious = TERM.inputCurrent; - TERM.inputCurrent = 0; - - int ch = getch(); - if(ch == ERR) { - TERM.lastch = ERR; - return; - } - - if(ch == TERM.lastch) { - return; - } - TERM.lastch = ch; - - const terminputmap_t *map = TERM_INPUT_MAP; - while(map->key != -1) { - if(map->key == ch) { - TERM.inputCurrent |= map->action; - break; - } - map++; - } -} - -void termDraw() { - clear(); - - switch(GAME.scene) { - case SCENE_OVERWORLD: - termDrawOverworld(); - break; - - default: - break; - } - - attroff(COLOR_PAIR(1)); - refresh(); - - // 16ms delay (60FPS) - napms(16); -} +#include "game.h" void termDrawOverworld() { // Draw map. - // Draw entities. - attron(COLOR_PAIR(1)); termDrawEntity(&GAME.player); entity_t *start = GAME.overworld.map.entities; @@ -138,9 +42,13 @@ void termDrawEntity(const entity_t *ent) { break; } - mvaddch(ent->position.y, ent->position.x, c); + termPushChar } -void termDispose() { - endwin(); +void termDraw() { + termClear(); + + + + termFlush(); } \ No newline at end of file diff --git a/src/platform/term/term.h b/src/platform/term/term.h old mode 100755 new mode 100644 index ffbbb31..5f682c0 --- a/src/platform/term/term.h +++ b/src/platform/term/term.h @@ -7,45 +7,51 @@ #pragma once #include "microrpg.h" -#include "game.h" -#include +#include "termcolor.h" -typedef struct term_s { - uint8_t inputCurrent; - uint8_t inputPrevious; - int lastch; -} term_t; - -extern term_t TERM; - -/** - * Initialize the terminal subsystem. - */ -void termInit(); - -/** - * Update the terminal prior to game update. - */ -void termUpdate(); - -/** - * Draw the terminal game. +/** + * Common implementation to draw the game state to the terminal (whatever the + * terminal implementation by the platform is). */ void termDraw(); -/** - * Draw the overworld scene. +/** + * Clears the terminal screen and resets the cursor to the top left. Called at + * the start of each "frame". Should also reset any terminal state (e.g. color). */ -void termDrawOverworld(); +void termClear(); -/** - * Draw an entity to the terminal. +/** + * Flushes any pending terminal operations. Called at the end of each "frame". + */ +void termFlush(); + +/** + * Get the number of columns in the terminal. * - * @param ent The entity to draw. + * @return The number of columns. */ -void termDrawEntity(const entity_t *ent); +uint8_t termGetColumnCount(); -/** - * Dispose of the terminal subsystem. +/** + * Get the number of rows in the terminal. + * + * @return The number of rows. */ -void termDispose(); \ No newline at end of file +uint8_t termGetRowCount(); + +/** + * Push a color onto the terminal color stack. + * + * @param color The color to push. + */ +void termPushColor(termcolor_t color); + +/** + * Push a character onto the terminal at the current cursor position. This will + * also advance the cursor position by 1 character. Wrapping will be handled by + * the terminal implementation. + * + * @param c The character to push. + */ +void termPushChar(char_t c); \ No newline at end of file diff --git a/src/platform/term/termcolor.h b/src/platform/term/termcolor.h new file mode 100644 index 0000000..e563c56 --- /dev/null +++ b/src/platform/term/termcolor.h @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "microrpg.h" + +#define TERM_COLOR_WHITE 0 +#define TERM_COLOR_RED 1 +#define TERM_COLOR_GREEN 2 +#define TERM_COLOR_YELLOW 3 +#define TERM_COLOR_BLUE 4 +#define TERM_COLOR_MAGENTA 5 +#define TERM_COLOR_CYAN 6 +#define TERM_COLOR_BLACK 7 + +typedef uint8_t termcolor_t; \ No newline at end of file diff --git a/src/platform/tty/CMakeLists.txt b/src/platform/tty/CMakeLists.txt new file mode 100755 index 0000000..f06a029 --- /dev/null +++ b/src/platform/tty/CMakeLists.txt @@ -0,0 +1,29 @@ +# Copyright (c) 2025 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Libraries +find_package(Curses REQUIRED) + +target_link_libraries(microrpg PRIVATE + ${CURSES_LIBRARIES} +) +target_include_directories(microrpg PRIVATE + ${CURSES_INCLUDE_DIR} +) +target_link_libraries(microrpg PRIVATE + ${CURSES_LIBRARIES} +) + +# Sources +target_sources(microrpg PRIVATE + term.c + inputterm.c + platform.c +) + +# Compiler flags +target_compile_definitions(microrpg PRIVATE + RPG_TERM=1 +) \ No newline at end of file diff --git a/src/platform/term/inputterm.c b/src/platform/tty/inputterm.c similarity index 100% rename from src/platform/term/inputterm.c rename to src/platform/tty/inputterm.c diff --git a/src/platform/term/platform.c b/src/platform/tty/platform.c similarity index 100% rename from src/platform/term/platform.c rename to src/platform/tty/platform.c diff --git a/src/platform/tty/term.c b/src/platform/tty/term.c new file mode 100755 index 0000000..acdedf3 --- /dev/null +++ b/src/platform/tty/term.c @@ -0,0 +1,146 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "term.h" +#include "input.h" +#include + +typedef struct terminputmap_s { + int key; + uint8_t action; +} terminputmap_t; + +static const terminputmap_t TERM_INPUT_MAP[] = { + { KEY_UP, INPUT_ACTION_UP }, + { 'w', INPUT_ACTION_UP }, + { KEY_DOWN, INPUT_ACTION_DOWN }, + { 's', INPUT_ACTION_DOWN }, + { KEY_LEFT, INPUT_ACTION_LEFT }, + { 'a', INPUT_ACTION_LEFT }, + { KEY_RIGHT, INPUT_ACTION_RIGHT }, + { 'd', INPUT_ACTION_RIGHT }, + { 'j', INPUT_ACTION_A }, + { 'e', INPUT_ACTION_A }, + { 'k', INPUT_ACTION_B }, + { 'q', INPUT_ACTION_B }, + { KEY_ENTER, INPUT_ACTION_START }, + { ' ', INPUT_ACTION_SELECT }, + { -1, 0 } +}; + +term_t TERM; + +void termInit() { + memset(&TERM, 0, sizeof(TERM)); + + initscr(); + cbreak(); + noecho(); + keypad(stdscr, TRUE); + nodelay(stdscr, TRUE); + curs_set(0); + start_color(); + use_default_colors(); + + init_pair(1, COLOR_GREEN, -1); + + // Set in-game time to real world time. + time_t now = time(NULL); + struct tm *t = localtime(&now); + + GAME.time.days = t->tm_wday; + GAME.time.hours = t->tm_hour; + GAME.time.minutes = t->tm_min; + GAME.time.seconds = t->tm_sec; +} + +void termUpdate() { + TERM.inputPrevious = TERM.inputCurrent; + TERM.inputCurrent = 0; + + int ch = getch(); + if(ch == ERR) { + TERM.lastch = ERR; + return; + } + + if(ch == TERM.lastch) { + return; + } + TERM.lastch = ch; + + const terminputmap_t *map = TERM_INPUT_MAP; + while(map->key != -1) { + if(map->key == ch) { + TERM.inputCurrent |= map->action; + break; + } + map++; + } +} + +void termDraw() { + clear(); + + switch(GAME.scene) { + case SCENE_OVERWORLD: + termDrawOverworld(); + break; + + default: + break; + } + + attroff(COLOR_PAIR(1)); + refresh(); + + // 16ms delay (60FPS) + napms(16); +} + +void termDrawOverworld() { + // Draw map. + + // Draw entities. + attron(COLOR_PAIR(1)); + termDrawEntity(&GAME.player); + + entity_t *start = GAME.overworld.map.entities; + entity_t *end = start + MAP_ENTITY_COUNT; + while(start < end) { + if(start->type != ENTITY_TYPE_NULL) termDrawEntity(start); + start++; + } +} + +void termDrawEntity(const entity_t *ent) { + // Placeholder: Draw entity at its position + char c; + switch(ent->direction) { + case DIRECTION_NORTH: + c = '^'; + break; + case DIRECTION_EAST: + c = '>'; + break; + case DIRECTION_SOUTH: + c = 'v'; + break; + case DIRECTION_WEST: + c = '<'; + break; + default: + c = '@'; + break; + } + + mvaddch(ent->position.y, ent->position.x, c); +} + +void termDispose() { + endwin(); +} \ No newline at end of file diff --git a/src/platform/tty/term.h b/src/platform/tty/term.h new file mode 100755 index 0000000..ffbbb31 --- /dev/null +++ b/src/platform/tty/term.h @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "microrpg.h" +#include "game.h" +#include + +typedef struct term_s { + uint8_t inputCurrent; + uint8_t inputPrevious; + int lastch; +} term_t; + +extern term_t TERM; + +/** + * Initialize the terminal subsystem. + */ +void termInit(); + +/** + * Update the terminal prior to game update. + */ +void termUpdate(); + +/** + * Draw the terminal game. + */ +void termDraw(); + +/** + * Draw the overworld scene. + */ +void termDrawOverworld(); + +/** + * Draw an entity to the terminal. + * + * @param ent The entity to draw. + */ +void termDrawEntity(const entity_t *ent); + +/** + * Dispose of the terminal subsystem. + */ +void termDispose(); \ No newline at end of file diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000..2e3fe9a --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,6 @@ +# Copyright (c) 2025 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +add_subdirectory(embed) \ No newline at end of file diff --git a/tools/embed/CMakeLists.txt b/tools/embed/CMakeLists.txt new file mode 100644 index 0000000..410ee70 --- /dev/null +++ b/tools/embed/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright (c) 2025 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +function(embed_file TARGET_NAME INPUT_FILE_ABS OUTPUT_FILE_HEADER C_VAR_NAME) + set(OUTPUT_FILE_ABS "${CMAKE_BINARY_DIR}/generated_headers/${OUTPUT_FILE_HEADER}") + + # Create a custom target + add_custom_target( + "${INPUT_FILE}_embed" + COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/generated_headers" + COMMAND ${CMAKE_COMMAND} -DINPUT_FILE="${INPUT_FILE_ABS}" -DOUTPUT_FILE="${OUTPUT_FILE_ABS}" -DC_VAR_NAME="${C_VAR_NAME}" -P "${TOOLS_DIR}/embed/embed.cmake" + DEPENDS "${INPUT_FILE_ABS}" + ) + + # Add the generated header to the target's include directories + target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/generated_headers") + + # Make the target depend on the custom target + add_dependencies(${TARGET_NAME} "${INPUT_FILE}_embed") +endfunction() \ No newline at end of file diff --git a/tools/embed/embed.cmake b/tools/embed/embed.cmake new file mode 100644 index 0000000..a2a0c12 --- /dev/null +++ b/tools/embed/embed.cmake @@ -0,0 +1,21 @@ +# Copyright (c) 2025 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# We are passed -DINPUT_FILE (absolute) and -DOUTPUT_FILE (absolute) and -DC_VAR_NAME (name of the C variable to hold the data) + +file(READ "${INPUT_FILE}" RAW_DATA HEX) +string(LENGTH "${RAW_DATA}" DATA_LENGTH) +set(OUTPUT_CONTENT "/* This file is auto-generated. Do not edit directly. */\n") +set(OUTPUT_CONTENT "${OUTPUT_CONTENT}#pragma once\n#include \"microrpg.h\"\n\n") +set(OUTPUT_CONTENT "${OUTPUT_CONTENT}static const uint8_t ${C_VAR_NAME}_DATA[] = {\n ") +set(INDEX 0) +while(INDEX LESS DATA_LENGTH) + string(SUBSTRING "${RAW_DATA}" ${INDEX} 2 BYTE) + set(OUTPUT_CONTENT "${OUTPUT_CONTENT}0x${BYTE}, ") + math(EXPR INDEX "${INDEX} + 2") +endwhile() +set(OUTPUT_CONTENT "${OUTPUT_CONTENT}\n};\n\n") +set(OUTPUT_CONTENT "${OUTPUT_CONTENT}#define ${C_VAR_NAME}_SIZE ${DATA_LENGTH}u\n") +file(WRITE "${OUTPUT_FILE}" "${OUTPUT_CONTENT}") \ No newline at end of file