Creating generic term renderer.
This commit is contained in:
@@ -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)
|
||||
10
assets/8x8ascii-darkrose/README.md
Normal file
10
assets/8x8ascii-darkrose/README.md
Normal file
@@ -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
|
||||
BIN
assets/8x8ascii-darkrose/font.png
Normal file
BIN
assets/8x8ascii-darkrose/font.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
61
cmake/Findraylib.cmake
Normal file
61
cmake/Findraylib.cmake
Normal file
@@ -0,0 +1,61 @@
|
||||
# Findraylib.cmake — minimal: system paths → FetchContent
|
||||
# Exposes: raylib::raylib, raylib_FOUND
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
set(_RAYLIB_TAG "5.5")
|
||||
set(_RAYLIB_URL "https://github.com/raysan5/raylib/archive/refs/tags/${_RAYLIB_TAG}.tar.gz")
|
||||
set(_RAYLIB_ROOT "")
|
||||
|
||||
# Try system install first
|
||||
find_path(_RAYLIB_INCLUDE_DIR raylib.h
|
||||
HINTS "${_RAYLIB_ROOT}/include"
|
||||
PATHS /usr/include /usr/local/include
|
||||
PATH_SUFFIXES raylib
|
||||
)
|
||||
find_library(_RAYLIB_LIBRARY raylib
|
||||
HINTS "${_RAYLIB_ROOT}/lib" "${_RAYLIB_ROOT}/lib64"
|
||||
PATHS /usr/lib /usr/lib64 /usr/local/lib /usr/local/lib64
|
||||
)
|
||||
|
||||
if (_RAYLIB_INCLUDE_DIR AND _RAYLIB_LIBRARY)
|
||||
add_library(raylib UNKNOWN IMPORTED)
|
||||
set_target_properties(raylib PROPERTIES
|
||||
IMPORTED_LOCATION "${_RAYLIB_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${_RAYLIB_INCLUDE_DIR}"
|
||||
)
|
||||
else()
|
||||
include(FetchContent)
|
||||
|
||||
# Fix CMP0135 warning
|
||||
if(POLICY CMP0135)
|
||||
cmake_policy(SET CMP0135 NEW)
|
||||
endif()
|
||||
|
||||
if (DEFINED FETCHCONTENT_SOURCE_DIR_RAYLIB AND
|
||||
NOT EXISTS "${FETCHCONTENT_SOURCE_DIR_RAYLIB}")
|
||||
unset(FETCHCONTENT_SOURCE_DIR_RAYLIB CACHE)
|
||||
endif()
|
||||
|
||||
set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||
set(BUILD_GAMES OFF CACHE BOOL "" FORCE)
|
||||
set(RAYLIB_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||
set(RAYLIB_BUILD_GAMES OFF CACHE BOOL "" FORCE)
|
||||
|
||||
FetchContent_Declare(raylib
|
||||
URL "${_RAYLIB_URL}"
|
||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||
)
|
||||
FetchContent_MakeAvailable(raylib)
|
||||
|
||||
if (NOT TARGET raylib)
|
||||
message(FATAL_ERROR "raylib target not defined after fetch.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Normalize exported target
|
||||
if (NOT TARGET raylib::raylib)
|
||||
add_library(raylib::raylib ALIAS raylib)
|
||||
endif()
|
||||
|
||||
set(raylib_FOUND TRUE)
|
||||
@@ -8,6 +8,10 @@
|
||||
#pragma once
|
||||
#include "cutscene.h"
|
||||
|
||||
static void testCutsceneTest() {
|
||||
abort();
|
||||
}
|
||||
|
||||
cutsceneitem_t TEST_CUTSCENE_ITEMS[] = {
|
||||
{ .type = CUTSCENE_ITEM_TEXTBOX, .textbox = "Hello World" }
|
||||
{ .type = CUTSCENE_ITEM_CALLBACK, .callback = testCutsceneTest }
|
||||
};
|
||||
@@ -10,10 +10,10 @@
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
gameInit();
|
||||
platformInit();
|
||||
if(platformInit() != PLATFORM_OK) return 1;
|
||||
|
||||
while(1) {
|
||||
platformUpdate();
|
||||
if(platformUpdate() != PLATFORM_OK) break;
|
||||
|
||||
gameTick();
|
||||
|
||||
|
||||
@@ -13,3 +13,5 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef bool bool_t;
|
||||
typedef char char_t;
|
||||
typedef float float_t;
|
||||
@@ -3,5 +3,6 @@
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Change inclusion to platform-specific
|
||||
add_subdirectory(raylib)
|
||||
add_subdirectory(term)
|
||||
#add_subdirectory(term)
|
||||
@@ -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.
|
||||
|
||||
27
src/platform/raylib/CMakeLists.txt
Normal file
27
src/platform/raylib/CMakeLists.txt
Normal file
@@ -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
|
||||
)
|
||||
89
src/platform/raylib/font.c
Normal file
89
src/platform/raylib/font.c
Normal file
@@ -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);
|
||||
}
|
||||
60
src/platform/raylib/font.h
Normal file
60
src/platform/raylib/font.h
Normal file
@@ -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 <raylib.h>
|
||||
|
||||
#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();
|
||||
24
src/platform/raylib/inputraylib.c
Normal file
24
src/platform/raylib/inputraylib.c
Normal file
@@ -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;
|
||||
}
|
||||
44
src/platform/raylib/platform.c
Normal file
44
src/platform/raylib/platform.c
Normal file
@@ -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 <raylib.h>
|
||||
#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();
|
||||
}
|
||||
84
src/platform/raylib/termraylib.c
Normal file
84
src/platform/raylib/termraylib.c
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
15
src/platform/term/CMakeLists.txt
Executable file → Normal file
15
src/platform/term/CMakeLists.txt
Executable file → Normal file
@@ -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
|
||||
|
||||
108
src/platform/term/term.c
Executable file → Normal file
108
src/platform/term/term.c
Executable file → Normal file
@@ -6,107 +6,11 @@
|
||||
*/
|
||||
|
||||
#include "term.h"
|
||||
#include "input.h"
|
||||
#include <time.h>
|
||||
|
||||
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();
|
||||
}
|
||||
62
src/platform/term/term.h
Executable file → Normal file
62
src/platform/term/term.h
Executable file → Normal file
@@ -7,45 +7,51 @@
|
||||
|
||||
#pragma once
|
||||
#include "microrpg.h"
|
||||
#include "game.h"
|
||||
#include <ncurses.h>
|
||||
|
||||
typedef struct term_s {
|
||||
uint8_t inputCurrent;
|
||||
uint8_t inputPrevious;
|
||||
int lastch;
|
||||
} term_t;
|
||||
|
||||
extern term_t TERM;
|
||||
#include "termcolor.h"
|
||||
|
||||
/**
|
||||
* 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();
|
||||
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);
|
||||
20
src/platform/term/termcolor.h
Normal file
20
src/platform/term/termcolor.h
Normal file
@@ -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;
|
||||
29
src/platform/tty/CMakeLists.txt
Executable file
29
src/platform/tty/CMakeLists.txt
Executable file
@@ -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
|
||||
)
|
||||
146
src/platform/tty/term.c
Executable file
146
src/platform/tty/term.c
Executable file
@@ -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 <time.h>
|
||||
|
||||
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();
|
||||
}
|
||||
51
src/platform/tty/term.h
Executable file
51
src/platform/tty/term.h
Executable file
@@ -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 <ncurses.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.
|
||||
*/
|
||||
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();
|
||||
6
tools/CMakeLists.txt
Normal file
6
tools/CMakeLists.txt
Normal file
@@ -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)
|
||||
22
tools/embed/CMakeLists.txt
Normal file
22
tools/embed/CMakeLists.txt
Normal file
@@ -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()
|
||||
21
tools/embed/embed.cmake
Normal file
21
tools/embed/embed.cmake
Normal file
@@ -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}")
|
||||
Reference in New Issue
Block a user