From b92cbcec5b2f288e5994116f8e5c4fdb12bf5c04 Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Sat, 16 Jul 2022 22:02:04 +0100 Subject: [PATCH] render, block, world: first prototype --- .gitignore | 2 +- CMakeLists.txt | 20 ++++---- assets-cg/blockinfo.yaml | 12 +++++ assets-cg/font_nooncraft.png | Bin 0 -> 1518 bytes assets-cg/fxconv-metadata.txt | 10 ++++ assets-cg/world.xcf | Bin 0 -> 258714 bytes converters.py | 92 ++++++++++++++++++++++++++++++++++ src/block.cpp | 17 +++++++ src/block.h | 35 +++++++++++++ src/graphics.cpp | 19 +++++++ src/graphics.h | 24 +++++++++ src/item.h | 8 +++ src/main.c | 12 ----- src/main.cpp | 53 ++++++++++++++++++++ src/render.cpp | 87 ++++++++++++++++++++++++++++++++ src/render.h | 69 +++++++++++++++++++++++++ src/world.cpp | 71 ++++++++++++++++++++++++++ src/world.h | 92 ++++++++++++++++++++++++++++++++++ 18 files changed, 601 insertions(+), 22 deletions(-) create mode 100644 assets-cg/blockinfo.yaml create mode 100644 assets-cg/font_nooncraft.png create mode 100644 assets-cg/world.xcf create mode 100644 converters.py create mode 100644 src/block.cpp create mode 100644 src/block.h create mode 100644 src/graphics.cpp create mode 100644 src/graphics.h create mode 100644 src/item.h delete mode 100644 src/main.c create mode 100644 src/main.cpp create mode 100644 src/render.cpp create mode 100644 src/render.h create mode 100644 src/world.cpp create mode 100644 src/world.h diff --git a/.gitignore b/.gitignore index 2c4f84b..be51d83 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ /*.g3a # Python bytecode - __pycache__/ +__pycache__/ # Common IDE files *.sublime-project diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f08c52..a36b832 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,24 +1,26 @@ cmake_minimum_required(VERSION 3.15) -project(Nooncraft) +project(Nooncraft LANGUAGES CXX) include(GenerateG3A) include(Fxconv) find_package(Gint 2.8 REQUIRED) set(SOURCES - src/main.c - # ... -) -# Shared assets, fx-9860G-only assets and fx-CG-50-only assets + src/block.cpp + src/graphics.cpp + src/main.cpp + src/render.cpp + src/world.cpp) set(ASSETS - # ... -) + assets-cg/font_nooncraft.png + assets-cg/blockinfo.yaml) fxconv_declare_assets(${ASSETS} WITH_METADATA) +fxconv_declare_converters(converters.py) add_executable(addin ${SOURCES} ${ASSETS}) -target_compile_options(addin PRIVATE -Wall -Wextra -Os) -target_link_libraries(addin Gint::Gint) +target_compile_options(addin PRIVATE -Wall -Wextra -Wno-narrowing -Os -std=c++20) +target_link_libraries(addin Gint::Gint -lsupc++) if("${FXSDK_PLATFORM_LONG}" STREQUAL fx9860G) message(FATAL_ERROR "This game is not supported on fx-9860G!") diff --git a/assets-cg/blockinfo.yaml b/assets-cg/blockinfo.yaml new file mode 100644 index 0000000..c3b486f --- /dev/null +++ b/assets-cg/blockinfo.yaml @@ -0,0 +1,12 @@ +- name: "AIR" + cluster: " " + walkable: yes + breakability: Fluid + +- name: "STONE" + cluster: "□□□□" + toolKind: Pickaxe + +- name: "COBBLESTONE" + cluster: "####" + toolKind: Pickaxe diff --git a/assets-cg/font_nooncraft.png b/assets-cg/font_nooncraft.png new file mode 100644 index 0000000000000000000000000000000000000000..52b3d4ad2617fc4900dd5654f4cf59fafeb81f29 GIT binary patch literal 1518 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 literal 0 HcmV?d00001 diff --git a/converters.py b/converters.py new file mode 100644 index 0000000..0274ac3 --- /dev/null +++ b/converters.py @@ -0,0 +1,92 @@ +import fxconv +import yaml + +def convert(input, output, params, target): + recognized = True + if params["custom-type"] == "blockinfo": + o = convert_blockinfo(input, params) + else: + recognized = False + + if recognized: + fxconv.elf(o, output, "_" + params["name"], **target) + return 0 + + return 1 + +def mkBreakability(br): + return fxconv.u8({ + None: 0, + "Fluid": 0, + "ByAnything": 1, + "ByTool": 2, + "Unbreakable": 3, + }[br]) + +def mkToolKind(tk): + return fxconv.u8({ + None: 0, + "Pickaxe": 0, + "Axe": 1, + "Shovel": 2, + "Hoe": 3, + }[tk]) + +def mkBool(b): + if isinstance(b, str): + b = b.lower() + return fxconv.u8(b in [True, "yes", "true"]) + +def mkGlyph(s): + assert len(s) == 1 + special = { + 0x2058: 1, + 0x00B7: 2, + 0x2225: 3, + 0x00A4: 4, + 0x2197: 5, + 0x00BB: 6, + 0x2193: 7, + 0x00EE: 8, + 0x222B: 9, + 0x25AA: 10, + 0x2605: 11, + 0x25A1: 0x7f, + } + if ord(s) >= 0x20 and ord(s) <= 0x7f: + return fxconv.u8(ord(s)) + elif ord(s) in special: + return fxconv.u8(special[ord(s)]) + else: + raise ValueError(f"unknown glyph {s}") + +def mkGlyphCluster(str): + assert len(str) == 4 + return b"".join(mkGlyph(s) for s in str) + +def convert_blockinfo(input, params): + with open(input, "r") as fp: + data = yaml.safe_load(fp.read()) + + o = fxconv.ObjectData() + + for b in data: + # Make sure there are no typos in the fields + assert (f in ["name", "breakability", "toolKind", + "baseBreakDurationTicks", "walkable"] for f in b) + + # If a tool is specified, default to Breakability::ByTool + tk = b.get("toolKind", None) + br = b.get("breakability", "ByTool" if tk is not None else "Fluid") + + o += fxconv.string(b["name"]) + o += mkGlyphCluster(b["cluster"]) + o += mkBreakability(br) + o += mkToolKind(tk) + o += fxconv.u16(int(b.get("baseBreakDurationTicks", "20"))) + o += mkBool(b.get("walkable", False)) + o += bytes(3) + + o += fxconv.sym("Nooncraft_blockInfoCount") + o += fxconv.u32(len(data)) + return o diff --git a/src/block.cpp b/src/block.cpp new file mode 100644 index 0000000..351c089 --- /dev/null +++ b/src/block.cpp @@ -0,0 +1,17 @@ +#include "block.h" + +extern "C" { +extern BlockInfo Nooncraft_blockInfo[]; +extern int Nooncraft_blockInfoCount; +} + +namespace Nooncraft { + +BlockInfo *getBlockInfo(Block b) +{ + if(b >= Nooncraft_blockInfoCount) + return nullptr; + return &Nooncraft_blockInfo[b]; +} + +} /* namespace Nooncraft */ diff --git a/src/block.h b/src/block.h new file mode 100644 index 0000000..d32a3b4 --- /dev/null +++ b/src/block.h @@ -0,0 +1,35 @@ +// nooncraft.block: Block data in world map and general block information + +#pragma once +#include "item.h" +#include "render.h" +#include + +using Block = uint16_t; + +enum class Breakability: uint8_t { + Fluid, + ByAnything, + ByTool, + Unbreakable, +}; + +/* General information on blocks */ +struct BlockInfo +{ + char const *name; + + // TODO: Upgrade to random and/or dimension-specific sprites + GlyphCluster cluster; + + Breakability breakability; + ToolKind toolKind; + int16_t baseBreakDurationTicks; + bool walkable; +}; + +namespace Nooncraft { + +BlockInfo *getBlockInfo(Block b); + +} /* namespace Nooncraft */ diff --git a/src/graphics.cpp b/src/graphics.cpp new file mode 100644 index 0000000..02ceb78 --- /dev/null +++ b/src/graphics.cpp @@ -0,0 +1,19 @@ +#include "graphics.h" +#include "render.h" + +namespace Nooncraft { + +void renderCamera(int x, int y, Camera *camera) +{ + if(!camera) + return; + + WorldRect r = camera->region; + for(int dy = r.ymin; dy <= r.ymax; dy++) + for(int dx = r.xmin; dx <= r.xmax; dx++) { + GlyphCluster c('@', '#', '~', '&'); + renderCluster(x+2*dx, y+2*dy, c); + } +} + +} /* namespace Nooncraft */ diff --git a/src/graphics.h b/src/graphics.h new file mode 100644 index 0000000..e807159 --- /dev/null +++ b/src/graphics.h @@ -0,0 +1,24 @@ +// nooncraft.graphics: Application-level rendering logic + +#pragma once +#include "world.h" + +struct Camera +{ + Camera(World const *w): world {w}, center {0,0}, region {-1,1,-1,1} {} + + /* Underlying world object */ + World const *world; + + /* Coordinates of the center point */ + WorldCoord center; + /* Local rectangle around center at (0,0). This influences how much of the + screen the camera will take up when rendered */ + WorldRect region; +}; + +namespace Nooncraft { + +void renderCamera(int x, int y, Camera *camera); + +} /* namespace Nooncraft */ diff --git a/src/item.h b/src/item.h new file mode 100644 index 0000000..3b7afdd --- /dev/null +++ b/src/item.h @@ -0,0 +1,8 @@ +// nooncraft.item: Item storage and properties + +#pragma once +#include + +enum class ToolKind: uint8_t { + Pickaxe, Axe, Shovel, Hoe, +}; diff --git a/src/main.c b/src/main.c deleted file mode 100644 index 1a9e9b4..0000000 --- a/src/main.c +++ /dev/null @@ -1,12 +0,0 @@ -#include -#include - -int main(void) -{ - dclear(C_WHITE); - dtext(1, 1, C_BLACK, "Sample fxSDK add-in."); - dupdate(); - - getkey(); - return 1; -} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..336bf9d --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,53 @@ +#include "render.h" +#include "world.h" +#include "graphics.h" +#include +#include + +int main(void) +{ + World *world = Nooncraft::mkWorld(128, 128); + if(!world) + return 0; + + Nooncraft::genWorld(world); + + Camera *camera = new Camera(world); + camera->region = WorldRect(-13, 14, -6, 6); + + // - + + char separator[57]; + memset(separator, '-', 56); + separator[56] = 0; + + renderClear(); + Nooncraft::renderCamera(26, 12, camera); + + // HUD + + GlyphCluster invLeft(' ', GLYPH_PARALLEL_TO, ' ', GLYPH_PARALLEL_TO); + GlyphCluster invRight(GLYPH_PARALLEL_TO, ' ', GLYPH_PARALLEL_TO, ' '); + int hudY = 27; + + renderText(0, hudY, separator); + renderText(24, hudY+1, "COBBLESTONE"); + renderCluster(0, hudY+1, invLeft); + renderCluster(2, hudY+1, Nooncraft::getBlockInfo(2)->cluster); + renderCluster(20, hudY+1, invRight); + renderText(2, hudY+3, "1 2 3 4 5 6 7 8 9"); + + // - + + renderText(0, 0, "NOONCRAFT"); + renderUpdate(); + + getkey(); + + //- + + delete camera; + delete world; + + return 1; +} diff --git a/src/render.cpp b/src/render.cpp new file mode 100644 index 0000000..dea9890 --- /dev/null +++ b/src/render.cpp @@ -0,0 +1,87 @@ +#include "render.h" +#include +#include +#include + +extern "C" { +extern font_t font_nooncraft; +} + +GCONSTRUCTOR +static void init_font(void) +{ + dfont(&font_nooncraft); +} + +static bool nightMode = false; + +static inline int fg() +{ + return nightMode ? C_WHITE : C_BLACK; +} +static inline int bg() +{ + return nightMode ? C_BLACK : C_WHITE; +} +static inline void dglyph(int x, int y, Glyph g) +{ + dtext_opt(x, y, fg(), bg(), DTEXT_LEFT, DTEXT_TOP, &g, 1); +} + +void renderSetNight(bool night) +{ + nightMode = night; +} + +void renderClear(void) +{ + dclear(bg()); +} + +void renderUpdate(void) +{ + dupdate(); +} + +static int cellX(int x, bool text) +{ + return 4 + 7 * x - (!text && (x & 1)); +} + +static int cellY(int y, bool text) +{ + (void)text; + return 2 + 7 * y - (y & 1); +} + +void renderGlyph(int x, int y, Glyph g) +{ + dglyph(cellX(x, true), cellY(y, true), g); +} + +void renderCluster(int x, int y, GlyphCluster c) +{ + int px = cellX(x & -2, false); + int py = cellY(y & -2, false); + + dglyph(px, py, c.glyphs[0]); + dglyph(px+6, py, c.glyphs[1]); + dglyph(px, py+6, c.glyphs[2]); + dglyph(px+6, py+6, c.glyphs[3]); +} + +void renderText(int x, int y, char const *text) +{ + for(int i = 0; text[i]; i++) + renderGlyph(x + i, y, text[i]); +} + +void renderFormat(int x, int y, char const *fmt, ...) +{ + static char str[128]; + va_list args; + va_start(args, fmt); + vsnprintf(str, sizeof str, fmt, args); + va_end(args); + renderText(x, y, str); +} diff --git a/src/render.h b/src/render.h new file mode 100644 index 0000000..47bc769 --- /dev/null +++ b/src/render.h @@ -0,0 +1,69 @@ +// nooncraft.render: Grid-like text rendering +// +// Owing to the CPC constraint, the entire rendering system for this game is +// just text with a single font. We make two adjustments on the grid model: +// +// 1. We group rows and columns by pairs, so that the row/column spacing +// alternates between 1 and 3. This allows blocks to be represented by +// "clusters" of 4 characters. +// 2. To keep text natural, we provide a text function which forces column +// spacing to 2, offsetting every other character by 1 pixel. Rows remain +// irregularly spaced. +//--- + +#pragma once + +using Glyph = char; + +/* Non-ASCII glyphs */ +enum { + GLYPH_FOUR_DOT = 1, /* U+2058 FOUR DOT PUNCTUATION */ + GLYPH_MIDDLE_DOT = 2, /* U+00B7 MIDDLE DOT */ + GLYPH_PARALLEL_TO = 3, /* U+2225 PARALLEL TO */ + GLYPH_CURRENCY_SIGN = 4, /* U+00A4 CURRENCY SIGN */ + GLYPH_NORTH_EAST_ARROW = 5, /* U+2197 NORTH EAST ARROW */ + GLYPH_RIGHT_GUILLEMET = 6, /* U+00BB RIGHT GUILLEMET */ + GLYPH_DOWN_ARROW = 7, /* U+2193 DOWNARDS ARROW */ + GLYPH_I_CIRCUMFLEX = 8, /* U+00EE SMALL LATIN I WITH CIRCUMFLEX */ + GLYPH_INTEGRAL = 9, /* U+222B INTEGRAL */ + GLYPH_SMALL_SQUARE = 10, /* U+25AA BLACK SMALL SQUARE */ + GLYPH_STAR = 11, /* U+2605 BLACK STAR */ +}; + +/* A cluster of 4 glyphs. Can be conversion-constructed from a multi-byte + character literal like 'AXE '. */ +struct GlyphCluster +{ + GlyphCluster(Glyph g1, Glyph g2, Glyph g3, Glyph g4): + glyphs {g1, g2, g3, g4} + {} + + GlyphCluster(int mbliteral) { + this->glyphs[0] = mbliteral >> 24; + this->glyphs[1] = (mbliteral >> 16) & 0xff; + this->glyphs[2] = (mbliteral >> 8) & 0xff; + this->glyphs[3] = mbliteral & 0xff; + } + + Glyph glyphs[4]; +}; + +/* Enable or disable night mode (black-on-white). */ +void renderSetNight(bool night); +/* Clear the screen. */ +void renderClear(void); +/* Update the screen. */ +void renderUpdate(void); + +/* Render a single character with text alignment (2-pixel column spacing). + The position (x,y) is counted in characters units with (0,0) top-left. */ +void renderGlyph(int x, int y, Glyph g); + +/* Render a 2x2 cluster of 4 characters. The position (x,y) should be even on + both axes. */ +void renderCluster(int x, int y, GlyphCluster c); + +/* Render a string with text alignment. */ +void renderText(int x, int y, char const *text); +/* Same with printf() formatting. */ +void renderFormat(int x, int y, char const *fmt, ...); diff --git a/src/world.cpp b/src/world.cpp new file mode 100644 index 0000000..9b8a51e --- /dev/null +++ b/src/world.cpp @@ -0,0 +1,71 @@ +#include "world.h" +#include +#include + +WorldCoord WorldCoord::Up(0, -1); +WorldCoord WorldCoord::Down(0, 1); +WorldCoord WorldCoord::Left(-1, 0); +WorldCoord WorldCoord::Right(1, 0); + +namespace Nooncraft { + +World *mkWorld(int width, int height) +{ + WorldRect l, br; + + World *w = (World *)malloc(sizeof *w); + if(!w) goto fail; + + w->cells = (Block *)malloc(width * height * sizeof *w->cells); + if(!w->cells) goto fail; + + l = WorldRect( + -width / 2, width - width / 2, + -height / 2, height - height / 2); + br = WorldRect( + l.xmin + 2, l.xmax - 2, + l.ymin + 2, l.ymax - 2); + + w->limits = l; + w->worldBorder = br; + return w; + +fail: + if(w) + free(w->cells); + free(w); + return nullptr; +} + +void genWorld(World *w) +{ + memset(w->cells, 0, w->cellDataSize()); + + int x1 = w->worldBorder.xmin + 1; + int x2 = w->worldBorder.xmax - 1; + int y1 = w->worldBorder.ymin + 1; + int y2 = w->worldBorder.ymax - 1; + + for(int x = x1; x <= x2; x++) { + w->cellAt(x, y1) = 1; // TODO: Use cobblestone enum? + w->cellAt(x, y2) = 1; + } + for(int y = y1; y <= y2; y++) { + w->cellAt(x1, y) = 1; + w->cellAt(x2, y) = 1; + } + + for(int x = -1; x <= +1; x++) + for(int y = -1; y <= +1; y++) { + if(x || y) + w->cellAt(x, y) = 1; + } +} + +void freeWorld(World *w) +{ + free(w->cells); + free(w); +} + +} /* namespace Nooncraft */ diff --git a/src/world.h b/src/world.h new file mode 100644 index 0000000..8c651cc --- /dev/null +++ b/src/world.h @@ -0,0 +1,92 @@ +// nooncraft.world: World data and structures + +#pragma once +#include "block.h" +#include +#include + +struct WorldCoord +{ + int16_t x, y; + + constexpr WorldCoord(): x {0}, y {0} {} + constexpr WorldCoord(int _x, int _y): x {(int16_t)_x}, y {(int16_t)_y} {} + + inline constexpr WorldCoord &operator+=(WorldCoord const &other) { + this->x += other.x; + this->y += other.y; + return *this; + } + inline constexpr WorldCoord operator-=(WorldCoord const &other) { + this->x -= other.x; + this->y -= other.y; + return *this; + } + + static WorldCoord Up, Down, Left, Right; +}; + +inline constexpr WorldCoord operator+(WorldCoord l, WorldCoord const &r) { + return (l += r); +} +inline constexpr WorldCoord operator-(WorldCoord l, WorldCoord const &r) { + return (l -= r); +} + +struct WorldRect +{ + /* All included */ + int16_t xmin, xmax; + int16_t ymin, ymax; + + constexpr WorldRect(): xmin {0}, xmax {0}, ymin {0}, ymax {0} {} + constexpr WorldRect(int _xmin, int _xmax, int _ymin, int _ymax): + xmin {(int16_t)_xmin}, xmax {(int16_t)_xmax}, + ymin {(int16_t)_ymin}, ymax {(int16_t)_ymax} {} + + int width() const { + return xmax - xmin + 1; + } + int height() const { + return ymax - ymin + 1; + } +}; + +struct World +{ + /* Map data limits (ie. how far blocks exist) */ + WorldRect limits; + /* World border position */ + WorldRect worldBorder; + + /* Block cells in row-major order */ + Block *cells; + + int cellCount() const { + return limits.width() * limits.height(); + } + size_t cellDataSize() const { + return cellCount() * sizeof(*cells); + } + GINLINE Block const &cellAt(int16_t x, int16_t y) const { + int ix = (x + limits.xmin) + (y + limits.ymin) * limits.width(); + return cells[ix]; + } + GINLINE Block &cellAt(int16_t x, int16_t y) { + int ix = (x + limits.xmin) + (y + limits.ymin) * limits.width(); + return cells[ix]; + } +}; + +namespace Nooncraft { + +/* Make an empty uninitialized world map. */ +World *mkWorld(int width, int height); + +/* Run the world generator, initializing the entire world. */ +void genWorld(World *w); + +/* Free a world object. */ +void freeWorld(World *w); + +} /* namespace Nooncraft */