feat: add experimental game engine & demo game, fix ACPI events
@@ -59,7 +59,7 @@ BINDIR := bin
|
||||
PROGRAMS := $(notdir $(wildcard src/*))
|
||||
|
||||
# Programs with custom Makefiles (built separately).
|
||||
CUSTOM_BUILDS := doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet pdfviewer disks devexplorer installer volume music bluetooth desktop login shell
|
||||
CUSTOM_BUILDS := doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet pdfviewer disks devexplorer installer volume music bluetooth desktop login shell rpgdemo
|
||||
SYSTEM_PROGRAMS := $(filter-out $(CUSTOM_BUILDS),$(PROGRAMS))
|
||||
|
||||
# Build targets: system programs go to bin/os/, apps go to bin/apps/<name>/.
|
||||
@@ -81,9 +81,9 @@ CA_CERTS := $(BINDIR)/etc/ca-certificates.crt
|
||||
# Common shared assets (wallpapers, etc.)
|
||||
COMMONKEEP := $(BINDIR)/common/.keep
|
||||
|
||||
.PHONY: all clean doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet pdfviewer disks devexplorer installer volume music bluetooth login desktop shell icons fonts bearssl libc tls libjpeg install-apps
|
||||
.PHONY: all clean doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet pdfviewer disks devexplorer installer volume music bluetooth login desktop shell rpgdemo icons fonts bearssl libc tls libjpeg install-apps
|
||||
|
||||
all: bearssl libc libjpeg tls $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet pdfviewer disks devexplorer installer volume music bluetooth doom login desktop shell icons fonts install-apps $(MANDST) $(WWWDST) $(CA_CERTS) $(COMMONKEEP)
|
||||
all: bearssl libc libjpeg tls $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet pdfviewer disks devexplorer installer volume music bluetooth doom rpgdemo login desktop shell icons fonts install-apps $(MANDST) $(WWWDST) $(CA_CERTS) $(COMMONKEEP)
|
||||
|
||||
# Build BearSSL static library (cross-compiled for freestanding x86_64).
|
||||
BEARSSL_INCLUDES := -isystem $(shell cd .. && pwd)/kernel/freestnd-c-hdrs/x86_64/include -isystem $(abspath include/libc)
|
||||
@@ -184,12 +184,16 @@ icons:
|
||||
fonts:
|
||||
../scripts/copy_fonts.sh
|
||||
|
||||
# Build RPG demo game via its own Makefile (depends on libc).
|
||||
rpgdemo: libc
|
||||
$(MAKE) -C src/rpgdemo
|
||||
|
||||
# Build doom via its own Makefile.
|
||||
doom:
|
||||
$(MAKE) -C src/doom
|
||||
|
||||
# Install app bundles (manifests, icons, data files) into bin/apps/<name>/.
|
||||
install-apps: doom spreadsheet weather wikipedia imageviewer fontpreview pdfviewer disks devexplorer installer volume music bluetooth
|
||||
install-apps: doom rpgdemo spreadsheet weather wikipedia imageviewer fontpreview pdfviewer disks devexplorer installer volume music bluetooth
|
||||
../scripts/install_apps.sh
|
||||
|
||||
# Copy man pages into bin/man/ so mkramdisk.sh picks them up.
|
||||
@@ -241,3 +245,4 @@ clean:
|
||||
$(MAKE) -C src/volume clean
|
||||
$(MAKE) -C src/music clean
|
||||
$(MAKE) -C src/bluetooth clean
|
||||
$(MAKE) -C src/rpgdemo clean
|
||||
|
||||
|
After Width: | Height: | Size: 622 B |
|
After Width: | Height: | Size: 983 B |
|
After Width: | Height: | Size: 735 B |
|
After Width: | Height: | Size: 748 B |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 386 B |
|
After Width: | Height: | Size: 585 B |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 921 B |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 648 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 371 B |
|
After Width: | Height: | Size: 99 B |
|
After Width: | Height: | Size: 99 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 99 B |
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,10 @@
|
||||
Hello! Thank you for downloading the Cute Fantasy asset pack.
|
||||
|
||||
This project will be getting updates over time. This version of the asset pack is not final and there will be few more additional sprites.
|
||||
|
||||
License - Free Version
|
||||
- You can use these assets in non-commercial projects.
|
||||
- You can modify the assets.
|
||||
- You can not redistribute or resale, even if modified
|
||||
|
||||
If you like the asset pack leave a comment. It helps to support the asset pack and get more people to see it. Thanks!
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* audio.h
|
||||
* MontaukOS 2D Game Engine - Audio System
|
||||
* Wraps MontaukOS audio syscalls for game sound playback
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <montauk/syscall.h>
|
||||
|
||||
namespace engine {
|
||||
|
||||
static constexpr int AUDIO_SAMPLE_RATE = 44100;
|
||||
static constexpr int AUDIO_CHANNELS = 2;
|
||||
static constexpr int AUDIO_BITS = 16;
|
||||
|
||||
struct AudioEngine {
|
||||
int handle = -1;
|
||||
int volume = 80;
|
||||
|
||||
bool init() {
|
||||
handle = montauk::audio_open(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS, AUDIO_BITS);
|
||||
if (handle < 0) return false;
|
||||
montauk::audio_set_volume(handle, volume);
|
||||
return true;
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
if (handle >= 0) {
|
||||
montauk::audio_close(handle);
|
||||
handle = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void set_volume(int percent) {
|
||||
volume = percent;
|
||||
if (handle >= 0)
|
||||
montauk::audio_set_volume(handle, volume);
|
||||
}
|
||||
|
||||
// Write raw PCM samples to the audio device.
|
||||
// data: signed 16-bit stereo PCM samples
|
||||
// size: number of bytes
|
||||
bool write_pcm(const void* data, uint32_t size) {
|
||||
if (handle < 0) return false;
|
||||
return montauk::audio_write(handle, data, size) >= 0;
|
||||
}
|
||||
|
||||
// Generate and play a simple tone (square wave) for duration_ms.
|
||||
// Useful for basic sound effects. Non-blocking (writes samples to buffer).
|
||||
void play_tone(int freq_hz, int duration_ms, int tone_volume = 50) {
|
||||
if (handle < 0 || freq_hz <= 0) return;
|
||||
|
||||
int total_samples = (AUDIO_SAMPLE_RATE * duration_ms) / 1000;
|
||||
int half_period = AUDIO_SAMPLE_RATE / (2 * freq_hz);
|
||||
if (half_period <= 0) half_period = 1;
|
||||
|
||||
int16_t amp = (int16_t)(327 * tone_volume / 100); // ~1% of max
|
||||
static constexpr int CHUNK = 512;
|
||||
int16_t buf[CHUNK * 2]; // stereo
|
||||
|
||||
int written = 0;
|
||||
while (written < total_samples) {
|
||||
int n = total_samples - written;
|
||||
if (n > CHUNK) n = CHUNK;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
int sample_pos = written + i;
|
||||
int16_t val = ((sample_pos / half_period) % 2 == 0) ? amp : -amp;
|
||||
buf[i * 2] = val;
|
||||
buf[i * 2 + 1] = val;
|
||||
}
|
||||
|
||||
montauk::audio_write(handle, buf, n * 4);
|
||||
written += n;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace engine
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* collision.h
|
||||
* MontaukOS 2D Game Engine - Collision Detection
|
||||
* AABB overlap testing and tile-based collision response
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include "engine/tilemap.h"
|
||||
|
||||
namespace engine {
|
||||
|
||||
struct AABB {
|
||||
float x, y, w, h;
|
||||
|
||||
bool overlaps(const AABB& other) const {
|
||||
return x < other.x + other.w && x + w > other.x &&
|
||||
y < other.y + other.h && y + h > other.y;
|
||||
}
|
||||
|
||||
bool contains_point(float px, float py) const {
|
||||
return px >= x && px < x + w && py >= y && py < y + h;
|
||||
}
|
||||
};
|
||||
|
||||
// Try to move an entity by (dx, dy) on the tilemap.
|
||||
// Uses separate X/Y axis resolution so the entity slides along walls.
|
||||
// box: the entity's collision box in world pixel coordinates.
|
||||
// Returns adjusted dx, dy after collision.
|
||||
inline void resolve_tilemap_collision(const Tilemap& map,
|
||||
float bx, float by, float bw, float bh,
|
||||
float& dx, float& dy) {
|
||||
// Try X movement
|
||||
float new_x = bx + dx;
|
||||
if (map.collides((int)new_x, (int)by, (int)bw, (int)bh)) {
|
||||
dx = 0.0f;
|
||||
new_x = bx;
|
||||
}
|
||||
|
||||
// Try Y movement
|
||||
float new_y = by + dy;
|
||||
if (map.collides((int)new_x, (int)new_y, (int)bw, (int)bh)) {
|
||||
dy = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace engine
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* engine.h
|
||||
* MontaukOS 2D Game Engine - Core
|
||||
* Window management, timing, pixel buffer helpers
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <montauk/syscall.h>
|
||||
#include <montauk/heap.h>
|
||||
#include <montauk/string.h>
|
||||
#include <gui/gui.hpp>
|
||||
#include <gui/truetype.hpp>
|
||||
|
||||
extern "C" {
|
||||
#include <stdio.h>
|
||||
}
|
||||
|
||||
namespace engine {
|
||||
|
||||
// ============================================================================
|
||||
// Core engine
|
||||
// ============================================================================
|
||||
|
||||
struct Engine {
|
||||
int win_id = -1;
|
||||
uint32_t* pixels = nullptr;
|
||||
int screen_w = 0;
|
||||
int screen_h = 0;
|
||||
|
||||
// Timing
|
||||
uint64_t last_time_ms = 0;
|
||||
float dt = 0.016f; // delta time in seconds
|
||||
uint64_t frame_count = 0;
|
||||
|
||||
// Font
|
||||
gui::TrueTypeFont* font = nullptr;
|
||||
|
||||
bool running = true;
|
||||
|
||||
// ---- Lifecycle ----
|
||||
|
||||
bool init(const char* title, int w, int h) {
|
||||
screen_w = w;
|
||||
screen_h = h;
|
||||
|
||||
// Load font
|
||||
font = (gui::TrueTypeFont*)montauk::malloc(sizeof(gui::TrueTypeFont));
|
||||
if (font) {
|
||||
montauk::memset(font, 0, sizeof(gui::TrueTypeFont));
|
||||
if (!font->init("0:/fonts/Roboto-Medium.ttf")) {
|
||||
montauk::mfree(font);
|
||||
font = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Create window
|
||||
Montauk::WinCreateResult wres;
|
||||
if (montauk::win_create(title, w, h, &wres) < 0 || wres.id < 0)
|
||||
return false;
|
||||
|
||||
win_id = wres.id;
|
||||
pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
|
||||
last_time_ms = montauk::get_milliseconds();
|
||||
return true;
|
||||
}
|
||||
|
||||
void update_timing() {
|
||||
uint64_t now = montauk::get_milliseconds();
|
||||
uint64_t elapsed = now - last_time_ms;
|
||||
if (elapsed == 0) elapsed = 1;
|
||||
if (elapsed > 100) elapsed = 100; // cap to avoid spiral
|
||||
dt = (float)elapsed / 1000.0f;
|
||||
last_time_ms = now;
|
||||
frame_count++;
|
||||
}
|
||||
|
||||
// Poll one window event. Returns true if an event was received.
|
||||
bool poll(Montauk::WinEvent* ev) {
|
||||
int r = montauk::win_poll(win_id, ev);
|
||||
if (r < 0) { running = false; return false; }
|
||||
if (r == 0) return false;
|
||||
if (ev->type == 3) { running = false; return false; } // close
|
||||
if (ev->type == 2) { // resize
|
||||
screen_w = ev->resize.w;
|
||||
screen_h = ev->resize.h;
|
||||
pixels = (uint32_t*)(uintptr_t)montauk::win_resize(
|
||||
win_id, screen_w, screen_h);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void present() {
|
||||
montauk::win_present(win_id);
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
if (win_id >= 0) montauk::win_destroy(win_id);
|
||||
win_id = -1;
|
||||
}
|
||||
|
||||
// ---- Drawing helpers ----
|
||||
|
||||
void clear(uint32_t color) {
|
||||
int count = screen_w * screen_h;
|
||||
for (int i = 0; i < count; i++)
|
||||
pixels[i] = color;
|
||||
}
|
||||
|
||||
void fill_rect(int x, int y, int w, int h, uint32_t color) {
|
||||
int x0 = x < 0 ? 0 : x;
|
||||
int y0 = y < 0 ? 0 : y;
|
||||
int x1 = x + w > screen_w ? screen_w : x + w;
|
||||
int y1 = y + h > screen_h ? screen_h : y + h;
|
||||
for (int row = y0; row < y1; row++)
|
||||
for (int col = x0; col < x1; col++)
|
||||
pixels[row * screen_w + col] = color;
|
||||
}
|
||||
|
||||
void fill_rect_alpha(int x, int y, int w, int h, uint32_t color) {
|
||||
uint8_t sa = (color >> 24) & 0xFF;
|
||||
uint8_t sr = (color >> 16) & 0xFF;
|
||||
uint8_t sg = (color >> 8) & 0xFF;
|
||||
uint8_t sb = color & 0xFF;
|
||||
if (sa == 0) return;
|
||||
int x0 = x < 0 ? 0 : x;
|
||||
int y0 = y < 0 ? 0 : y;
|
||||
int x1 = x + w > screen_w ? screen_w : x + w;
|
||||
int y1 = y + h > screen_h ? screen_h : y + h;
|
||||
for (int row = y0; row < y1; row++) {
|
||||
for (int col = x0; col < x1; col++) {
|
||||
uint32_t dst = pixels[row * screen_w + col];
|
||||
uint8_t dr = (dst >> 16) & 0xFF;
|
||||
uint8_t dg = (dst >> 8) & 0xFF;
|
||||
uint8_t db = dst & 0xFF;
|
||||
uint32_t inv = 255 - sa;
|
||||
uint32_t rr = (sa * sr + inv * dr + 128) / 255;
|
||||
uint32_t gg = (sa * sg + inv * dg + 128) / 255;
|
||||
uint32_t bb = (sa * sb + inv * db + 128) / 255;
|
||||
pixels[row * screen_w + col] =
|
||||
0xFF000000 | (rr << 16) | (gg << 8) | bb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void draw_rect_outline(int x, int y, int w, int h, uint32_t color) {
|
||||
fill_rect(x, y, w, 1, color);
|
||||
fill_rect(x, y + h - 1, w, 1, color);
|
||||
fill_rect(x, y, 1, h, color);
|
||||
fill_rect(x + w - 1, y, 1, h, color);
|
||||
}
|
||||
|
||||
void draw_text(int x, int y, const char* text, gui::Color c, int size = 16) {
|
||||
if (font)
|
||||
font->draw_to_buffer(pixels, screen_w, screen_h, x, y, text, c, size);
|
||||
}
|
||||
|
||||
int text_width(const char* text, int size = 16) {
|
||||
return font ? font->measure_text(text, size) : 0;
|
||||
}
|
||||
|
||||
int text_height(int size = 16) {
|
||||
if (!font) return size;
|
||||
auto* gc = font->get_cache(size);
|
||||
if (!gc) return size;
|
||||
return gc->ascent - gc->descent;
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// File loading helper
|
||||
// ============================================================================
|
||||
|
||||
struct FileData {
|
||||
uint8_t* data = nullptr;
|
||||
uint64_t size = 0;
|
||||
|
||||
bool load(const char* vfs_path) {
|
||||
int fd = montauk::open(vfs_path);
|
||||
if (fd < 0) return false;
|
||||
size = montauk::getsize(fd);
|
||||
if (size == 0 || size > 16 * 1024 * 1024) {
|
||||
montauk::close(fd);
|
||||
return false;
|
||||
}
|
||||
data = (uint8_t*)montauk::malloc(size);
|
||||
if (!data) { montauk::close(fd); return false; }
|
||||
montauk::read(fd, data, 0, size);
|
||||
montauk::close(fd);
|
||||
return true;
|
||||
}
|
||||
|
||||
void free() {
|
||||
if (data) { montauk::mfree(data); data = nullptr; }
|
||||
size = 0;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace engine
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* input.h
|
||||
* MontaukOS 2D Game Engine - Input State
|
||||
* Keyboard and mouse state tracking across frames
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <montauk/string.h>
|
||||
#include <Api/Syscall.hpp>
|
||||
|
||||
namespace engine {
|
||||
|
||||
// Scancode constants for common keys
|
||||
namespace key {
|
||||
static constexpr uint8_t ESC = 0x01;
|
||||
static constexpr uint8_t W = 0x11;
|
||||
static constexpr uint8_t A = 0x1E;
|
||||
static constexpr uint8_t S = 0x1F;
|
||||
static constexpr uint8_t D = 0x20;
|
||||
static constexpr uint8_t E = 0x12;
|
||||
static constexpr uint8_t SPACE = 0x39;
|
||||
static constexpr uint8_t UP = 0x48;
|
||||
static constexpr uint8_t DOWN = 0x50;
|
||||
static constexpr uint8_t LEFT = 0x4B;
|
||||
static constexpr uint8_t RIGHT = 0x4D;
|
||||
static constexpr uint8_t ENTER = 0x1C;
|
||||
static constexpr uint8_t TAB = 0x0F;
|
||||
}
|
||||
|
||||
struct InputState {
|
||||
// Key states: true = currently held
|
||||
bool keys[256];
|
||||
// Keys pressed this frame (edge-triggered)
|
||||
bool keys_pressed[256];
|
||||
|
||||
// Mouse
|
||||
int mouse_x = 0;
|
||||
int mouse_y = 0;
|
||||
int mouse_scroll = 0;
|
||||
uint8_t mouse_buttons = 0;
|
||||
uint8_t prev_mouse_buttons = 0;
|
||||
|
||||
void init() {
|
||||
montauk::memset(keys, 0, sizeof(keys));
|
||||
montauk::memset(keys_pressed, 0, sizeof(keys_pressed));
|
||||
}
|
||||
|
||||
// Call at the start of each frame to clear per-frame state
|
||||
void begin_frame() {
|
||||
montauk::memset(keys_pressed, 0, sizeof(keys_pressed));
|
||||
mouse_scroll = 0;
|
||||
prev_mouse_buttons = mouse_buttons;
|
||||
}
|
||||
|
||||
// Process a window event
|
||||
void handle_event(const Montauk::WinEvent& ev) {
|
||||
if (ev.type == 0) { // keyboard
|
||||
// PS/2 scan code set 1: release codes have bit 7 set.
|
||||
// Mask to base scancode so press and release map to the same index.
|
||||
uint8_t sc = ev.key.scancode & 0x7F;
|
||||
if (ev.key.pressed) {
|
||||
if (!keys[sc])
|
||||
keys_pressed[sc] = true;
|
||||
keys[sc] = true;
|
||||
} else {
|
||||
keys[sc] = false;
|
||||
}
|
||||
} else if (ev.type == 1) { // mouse
|
||||
mouse_x = ev.mouse.x;
|
||||
mouse_y = ev.mouse.y;
|
||||
mouse_scroll = ev.mouse.scroll;
|
||||
mouse_buttons = ev.mouse.buttons;
|
||||
}
|
||||
}
|
||||
|
||||
bool key_held(uint8_t scancode) const { return keys[scancode]; }
|
||||
bool key_just_pressed(uint8_t scancode) const { return keys_pressed[scancode]; }
|
||||
|
||||
bool mouse_clicked() const {
|
||||
return (mouse_buttons & 1) && !(prev_mouse_buttons & 1);
|
||||
}
|
||||
bool mouse_held() const { return mouse_buttons & 1; }
|
||||
};
|
||||
|
||||
} // namespace engine
|
||||
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* sprite.h
|
||||
* MontaukOS 2D Game Engine - Sprite and Animation System
|
||||
* PNG spritesheet loading, frame extraction, alpha-blended rendering
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <montauk/heap.h>
|
||||
#include <montauk/string.h>
|
||||
#include "engine/engine.h"
|
||||
|
||||
extern "C" {
|
||||
#include <gui/stb_image.h>
|
||||
}
|
||||
|
||||
namespace engine {
|
||||
|
||||
// ============================================================================
|
||||
// Convert stb_image RGBA output to MontaukOS ARGB pixel format
|
||||
// stb on little-endian x86: pixel bytes in memory are R,G,B,A
|
||||
// When read as uint32_t: 0xAABBGGRR
|
||||
// MontaukOS format: 0xAARRGGBB
|
||||
// ============================================================================
|
||||
|
||||
inline void rgba_to_argb(uint32_t* data, int count) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
uint32_t px = data[i];
|
||||
uint8_t r = px & 0xFF;
|
||||
uint8_t g = (px >> 8) & 0xFF;
|
||||
uint8_t b = (px >> 16) & 0xFF;
|
||||
uint8_t a = (px >> 24) & 0xFF;
|
||||
data[i] = ((uint32_t)a << 24) | ((uint32_t)r << 16) |
|
||||
((uint32_t)g << 8) | b;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Spritesheet
|
||||
// ============================================================================
|
||||
|
||||
struct Spritesheet {
|
||||
uint32_t* pixels = nullptr;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int frame_w = 0;
|
||||
int frame_h = 0;
|
||||
int cols = 0;
|
||||
int rows = 0;
|
||||
|
||||
// Load a PNG spritesheet from VFS and split into frames of given size.
|
||||
// If frame_w/frame_h are 0, treat the entire image as a single frame.
|
||||
bool load(const char* vfs_path, int fw = 0, int fh = 0) {
|
||||
FileData file;
|
||||
if (!file.load(vfs_path)) return false;
|
||||
|
||||
int w, h, channels;
|
||||
uint32_t* img = (uint32_t*)stbi_load_from_memory(
|
||||
file.data, (int)file.size, &w, &h, &channels, 4);
|
||||
file.free();
|
||||
|
||||
if (!img) return false;
|
||||
|
||||
// Convert RGBA to ARGB
|
||||
rgba_to_argb(img, w * h);
|
||||
|
||||
pixels = img;
|
||||
width = w;
|
||||
height = h;
|
||||
frame_w = fw > 0 ? fw : w;
|
||||
frame_h = fh > 0 ? fh : h;
|
||||
cols = w / frame_w;
|
||||
rows = h / frame_h;
|
||||
return true;
|
||||
}
|
||||
|
||||
void unload() {
|
||||
if (pixels) {
|
||||
stbi_image_free(pixels);
|
||||
pixels = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Blit a single frame to the destination buffer with scaling and alpha.
|
||||
// frame_col/frame_row select which frame from the spritesheet.
|
||||
// dst_x/dst_y is the screen position. scale is the integer scale factor.
|
||||
// flip_h mirrors the sprite horizontally.
|
||||
void draw_frame(uint32_t* dst, int dst_w, int dst_h,
|
||||
int frame_col, int frame_row,
|
||||
int dst_x, int dst_y, int scale = 1,
|
||||
bool flip_h = false) const {
|
||||
if (!pixels) return;
|
||||
if (frame_col < 0 || frame_col >= cols) return;
|
||||
if (frame_row < 0 || frame_row >= rows) return;
|
||||
|
||||
int src_ox = frame_col * frame_w;
|
||||
int src_oy = frame_row * frame_h;
|
||||
int out_w = frame_w * scale;
|
||||
int out_h = frame_h * scale;
|
||||
|
||||
for (int py = 0; py < out_h; py++) {
|
||||
int dy = dst_y + py;
|
||||
if (dy < 0 || dy >= dst_h) continue;
|
||||
int sy = src_oy + py / scale;
|
||||
|
||||
for (int px = 0; px < out_w; px++) {
|
||||
int dx = dst_x + px;
|
||||
if (dx < 0 || dx >= dst_w) continue;
|
||||
|
||||
int sx_local = px / scale;
|
||||
if (flip_h) sx_local = frame_w - 1 - sx_local;
|
||||
int sx = src_ox + sx_local;
|
||||
|
||||
uint32_t src_px = pixels[sy * width + sx];
|
||||
uint8_t sa = (src_px >> 24) & 0xFF;
|
||||
if (sa == 0) continue;
|
||||
|
||||
if (sa == 255) {
|
||||
dst[dy * dst_w + dx] = src_px;
|
||||
} else {
|
||||
uint32_t d = dst[dy * dst_w + dx];
|
||||
uint8_t sr = (src_px >> 16) & 0xFF;
|
||||
uint8_t sg = (src_px >> 8) & 0xFF;
|
||||
uint8_t sb = src_px & 0xFF;
|
||||
uint8_t dr = (d >> 16) & 0xFF;
|
||||
uint8_t dg = (d >> 8) & 0xFF;
|
||||
uint8_t db = d & 0xFF;
|
||||
uint32_t inv = 255 - sa;
|
||||
uint32_t rr = (sa * sr + inv * dr + 128) / 255;
|
||||
uint32_t gg = (sa * sg + inv * dg + 128) / 255;
|
||||
uint32_t bb = (sa * sb + inv * db + 128) / 255;
|
||||
dst[dy * dst_w + dx] =
|
||||
0xFF000000 | (rr << 16) | (gg << 8) | bb;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw an arbitrary sub-rectangle of the spritesheet (not frame-aligned)
|
||||
void draw_region(uint32_t* dst, int dst_w, int dst_h,
|
||||
int src_x, int src_y, int src_w, int src_h,
|
||||
int dst_x, int dst_y, int scale = 1) const {
|
||||
if (!pixels) return;
|
||||
|
||||
int out_w = src_w * scale;
|
||||
int out_h = src_h * scale;
|
||||
|
||||
for (int py = 0; py < out_h; py++) {
|
||||
int dy = dst_y + py;
|
||||
if (dy < 0 || dy >= dst_h) continue;
|
||||
int sy = src_y + py / scale;
|
||||
if (sy < 0 || sy >= height) continue;
|
||||
|
||||
for (int px = 0; px < out_w; px++) {
|
||||
int dx = dst_x + px;
|
||||
if (dx < 0 || dx >= dst_w) continue;
|
||||
int sx = src_x + px / scale;
|
||||
if (sx < 0 || sx >= width) continue;
|
||||
|
||||
uint32_t src_px = pixels[sy * width + sx];
|
||||
uint8_t sa = (src_px >> 24) & 0xFF;
|
||||
if (sa == 0) continue;
|
||||
|
||||
if (sa == 255) {
|
||||
dst[dy * dst_w + dx] = src_px;
|
||||
} else {
|
||||
uint32_t d = dst[dy * dst_w + dx];
|
||||
uint8_t sr = (src_px >> 16) & 0xFF;
|
||||
uint8_t sg = (src_px >> 8) & 0xFF;
|
||||
uint8_t sb = src_px & 0xFF;
|
||||
uint8_t dr = (d >> 16) & 0xFF;
|
||||
uint8_t dg = (d >> 8) & 0xFF;
|
||||
uint8_t db = d & 0xFF;
|
||||
uint32_t inv = 255 - sa;
|
||||
uint32_t rr = (sa * sr + inv * dr + 128) / 255;
|
||||
uint32_t gg = (sa * sg + inv * dg + 128) / 255;
|
||||
uint32_t bb = (sa * sb + inv * db + 128) / 255;
|
||||
dst[dy * dst_w + dx] =
|
||||
0xFF000000 | (rr << 16) | (gg << 8) | bb;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Animation
|
||||
// ============================================================================
|
||||
|
||||
struct Animation {
|
||||
int row = 0; // spritesheet row for this animation
|
||||
int start_col = 0; // first frame column
|
||||
int num_frames = 1; // number of frames
|
||||
float speed = 8.0f; // frames per second
|
||||
|
||||
float timer = 0.0f;
|
||||
int current = 0;
|
||||
|
||||
void update(float dt) {
|
||||
timer += dt * speed;
|
||||
while (timer >= 1.0f) {
|
||||
timer -= 1.0f;
|
||||
current++;
|
||||
if (current >= num_frames) current = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void reset() {
|
||||
timer = 0.0f;
|
||||
current = 0;
|
||||
}
|
||||
|
||||
int frame_col() const { return start_col + current; }
|
||||
int frame_row() const { return row; }
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Animated Sprite - combines a spritesheet with named animations
|
||||
// ============================================================================
|
||||
|
||||
static constexpr int MAX_ANIMS = 16;
|
||||
|
||||
struct AnimatedSprite {
|
||||
Spritesheet* sheet = nullptr;
|
||||
Animation anims[MAX_ANIMS];
|
||||
int anim_count = 0;
|
||||
int current_anim = 0;
|
||||
bool flip_h = false;
|
||||
|
||||
int add_anim(int row, int start_col, int num_frames, float speed = 8.0f) {
|
||||
if (anim_count >= MAX_ANIMS) return -1;
|
||||
int idx = anim_count++;
|
||||
anims[idx].row = row;
|
||||
anims[idx].start_col = start_col;
|
||||
anims[idx].num_frames = num_frames;
|
||||
anims[idx].speed = speed;
|
||||
return idx;
|
||||
}
|
||||
|
||||
void play(int anim_idx) {
|
||||
if (anim_idx < 0 || anim_idx >= anim_count) return;
|
||||
if (current_anim != anim_idx) {
|
||||
current_anim = anim_idx;
|
||||
anims[current_anim].reset();
|
||||
}
|
||||
}
|
||||
|
||||
void update(float dt) {
|
||||
if (current_anim >= 0 && current_anim < anim_count)
|
||||
anims[current_anim].update(dt);
|
||||
}
|
||||
|
||||
void draw(uint32_t* dst, int dst_w, int dst_h,
|
||||
int x, int y, int scale = 1) const {
|
||||
if (!sheet || current_anim < 0 || current_anim >= anim_count) return;
|
||||
const Animation& a = anims[current_anim];
|
||||
sheet->draw_frame(dst, dst_w, dst_h,
|
||||
a.frame_col(), a.frame_row(),
|
||||
x, y, scale, flip_h);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace engine
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* tilemap.h
|
||||
* MontaukOS 2D Game Engine - Tile Map
|
||||
* Grid-based terrain with per-tile rendering and collision
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <montauk/heap.h>
|
||||
#include <montauk/string.h>
|
||||
#include "engine/sprite.h"
|
||||
|
||||
namespace engine {
|
||||
|
||||
static constexpr int MAX_TILE_TYPES = 16;
|
||||
|
||||
struct TileType {
|
||||
Spritesheet* sheet; // tile image (may be a single-tile spritesheet)
|
||||
int src_x, src_y; // source position within the sheet
|
||||
int src_w, src_h; // source size (typically tile_size x tile_size)
|
||||
bool solid; // blocks movement
|
||||
};
|
||||
|
||||
struct Tilemap {
|
||||
int* data = nullptr;
|
||||
int map_w = 0;
|
||||
int map_h = 0;
|
||||
int tile_size = 16; // native tile size in pixels
|
||||
|
||||
TileType types[MAX_TILE_TYPES];
|
||||
int type_count = 0;
|
||||
|
||||
bool alloc(int w, int h, int ts = 16) {
|
||||
map_w = w;
|
||||
map_h = h;
|
||||
tile_size = ts;
|
||||
data = (int*)montauk::malloc(w * h * sizeof(int));
|
||||
if (!data) return false;
|
||||
montauk::memset(data, 0, w * h * sizeof(int));
|
||||
return true;
|
||||
}
|
||||
|
||||
void free_map() {
|
||||
if (data) { montauk::mfree(data); data = nullptr; }
|
||||
}
|
||||
|
||||
// Register a tile type. Returns the tile ID.
|
||||
int add_type(Spritesheet* sheet, int sx, int sy, int sw, int sh, bool solid) {
|
||||
if (type_count >= MAX_TILE_TYPES) return -1;
|
||||
int id = type_count++;
|
||||
types[id].sheet = sheet;
|
||||
types[id].src_x = sx;
|
||||
types[id].src_y = sy;
|
||||
types[id].src_w = sw;
|
||||
types[id].src_h = sh;
|
||||
types[id].solid = solid;
|
||||
return id;
|
||||
}
|
||||
|
||||
void set(int x, int y, int tile_id) {
|
||||
if (x >= 0 && x < map_w && y >= 0 && y < map_h)
|
||||
data[y * map_w + x] = tile_id;
|
||||
}
|
||||
|
||||
int get(int x, int y) const {
|
||||
if (x < 0 || x >= map_w || y < 0 || y >= map_h) return -1;
|
||||
return data[y * map_w + x];
|
||||
}
|
||||
|
||||
bool is_solid(int x, int y) const {
|
||||
int id = get(x, y);
|
||||
if (id < 0 || id >= type_count) return true; // out of bounds = solid
|
||||
return types[id].solid;
|
||||
}
|
||||
|
||||
// Check if a world-pixel rectangle collides with any solid tile.
|
||||
// World coordinates are in native (unscaled) pixels.
|
||||
bool collides(int wx, int wy, int ww, int wh) const {
|
||||
// Negative coordinates are always solid (out of bounds)
|
||||
if (wx < 0 || wy < 0) return true;
|
||||
int tx0 = wx / tile_size;
|
||||
int ty0 = wy / tile_size;
|
||||
int tx1 = (wx + ww - 1) / tile_size;
|
||||
int ty1 = (wy + wh - 1) / tile_size;
|
||||
|
||||
for (int ty = ty0; ty <= ty1; ty++)
|
||||
for (int tx = tx0; tx <= tx1; tx++)
|
||||
if (is_solid(tx, ty))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Draw visible tiles to the pixel buffer.
|
||||
// cam_x/cam_y: camera position in world pixels (native scale).
|
||||
// scale: rendering scale factor.
|
||||
void draw(uint32_t* dst, int dst_w, int dst_h,
|
||||
int cam_x, int cam_y, int scale) const {
|
||||
if (!data) return;
|
||||
|
||||
int ts = tile_size * scale;
|
||||
|
||||
// Determine visible tile range
|
||||
int tx0 = cam_x / tile_size;
|
||||
int ty0 = cam_y / tile_size;
|
||||
int tx1 = tx0 + dst_w / ts + 2;
|
||||
int ty1 = ty0 + dst_h / ts + 2;
|
||||
|
||||
if (tx0 < 0) tx0 = 0;
|
||||
if (ty0 < 0) ty0 = 0;
|
||||
if (tx1 > map_w) tx1 = map_w;
|
||||
if (ty1 > map_h) ty1 = map_h;
|
||||
|
||||
for (int ty = ty0; ty < ty1; ty++) {
|
||||
for (int tx = tx0; tx < tx1; tx++) {
|
||||
int id = data[ty * map_w + tx];
|
||||
if (id < 0 || id >= type_count) continue;
|
||||
|
||||
const TileType& tt = types[id];
|
||||
if (!tt.sheet) continue;
|
||||
|
||||
int sx = tx * ts - cam_x * scale;
|
||||
int sy = ty * ts - cam_y * scale;
|
||||
|
||||
tt.sheet->draw_region(dst, dst_w, dst_h,
|
||||
tt.src_x, tt.src_y,
|
||||
tt.src_w, tt.src_h,
|
||||
sx, sy, scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace engine
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* ui.h
|
||||
* MontaukOS 2D Game Engine - UI Rendering
|
||||
* Health bars, text overlays, dialog boxes, menus
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include "engine/engine.h"
|
||||
|
||||
extern "C" {
|
||||
#include <stdio.h>
|
||||
}
|
||||
|
||||
namespace engine {
|
||||
|
||||
// ============================================================================
|
||||
// Health / stat bar
|
||||
// ============================================================================
|
||||
|
||||
inline void draw_bar(Engine& eng, int x, int y, int w, int h,
|
||||
int value, int max_value,
|
||||
uint32_t fill_color, uint32_t bg_color,
|
||||
uint32_t border_color) {
|
||||
eng.fill_rect(x, y, w, h, bg_color);
|
||||
if (max_value > 0 && value > 0) {
|
||||
int fw = (value * (w - 2)) / max_value;
|
||||
if (fw < 1) fw = 1;
|
||||
eng.fill_rect(x + 1, y + 1, fw, h - 2, fill_color);
|
||||
}
|
||||
eng.draw_rect_outline(x, y, w, h, border_color);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Text box / dialog
|
||||
// ============================================================================
|
||||
|
||||
inline void draw_dialog(Engine& eng, int x, int y, int w, int h,
|
||||
const char* text, gui::Color text_color,
|
||||
int font_size = 16) {
|
||||
// Background with border
|
||||
eng.fill_rect(x, y, w, h, 0xF0FFFFFF);
|
||||
eng.draw_rect_outline(x, y, w, h, 0xFF333333);
|
||||
eng.draw_rect_outline(x + 1, y + 1, w - 2, h - 2, 0xFFCCCCCC);
|
||||
|
||||
// Text (with word wrapping)
|
||||
int tx = x + 8;
|
||||
int ty = y + 6;
|
||||
int max_w = w - 16;
|
||||
int line_h = eng.text_height(font_size) + 2;
|
||||
|
||||
// Simple word wrap
|
||||
char line_buf[128];
|
||||
int li = 0;
|
||||
const char* p = text;
|
||||
while (*p) {
|
||||
li = 0;
|
||||
while (*p && *p != '\n') {
|
||||
// Save state before adding next word
|
||||
int word_start = li;
|
||||
const char* word_p = p;
|
||||
|
||||
while (*p && *p != ' ' && *p != '\n' && li < 126) {
|
||||
line_buf[li++] = *p++;
|
||||
}
|
||||
line_buf[li] = '\0';
|
||||
|
||||
// Check width
|
||||
if (eng.text_width(line_buf, font_size) > max_w && word_start > 0) {
|
||||
// Line too long - rewind to before this word
|
||||
li = word_start;
|
||||
if (li > 0 && line_buf[li - 1] == ' ') li--;
|
||||
line_buf[li] = '\0';
|
||||
p = word_p; // rewind pointer to retry this word on next line
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == ' ') { line_buf[li++] = ' '; p++; }
|
||||
}
|
||||
line_buf[li] = '\0';
|
||||
if (*p == '\n') p++;
|
||||
|
||||
if (ty + line_h > y + h - 4) break;
|
||||
eng.draw_text(tx, ty, line_buf, text_color, font_size);
|
||||
ty += line_h;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Prompt bar (bottom of screen)
|
||||
// ============================================================================
|
||||
|
||||
inline void draw_prompt(Engine& eng, const char* text, int font_size = 14) {
|
||||
int bar_h = eng.text_height(font_size) + 8;
|
||||
int y = eng.screen_h - bar_h;
|
||||
eng.fill_rect_alpha(0, y, eng.screen_w, bar_h, 0xCC000000);
|
||||
int tw = eng.text_width(text, font_size);
|
||||
eng.draw_text((eng.screen_w - tw) / 2, y + 4, text,
|
||||
gui::Color::from_rgb(0xFF, 0xFF, 0xFF), font_size);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Simple HUD text
|
||||
// ============================================================================
|
||||
|
||||
inline void draw_hud_text(Engine& eng, int x, int y,
|
||||
const char* text, gui::Color c, int size = 14) {
|
||||
// Draw shadow first for readability
|
||||
eng.draw_text(x + 1, y + 1, text, gui::Color::from_rgba(0, 0, 0, 180), size);
|
||||
eng.draw_text(x, y, text, c, size);
|
||||
}
|
||||
|
||||
} // namespace engine
|
||||
@@ -0,0 +1,116 @@
|
||||
# Makefile for rpgdemo (Montauk Quest - 2D RPG demo) on MontaukOS
|
||||
# Copyright (c) 2026 Daniel Hammer
|
||||
|
||||
MAKEFLAGS += -rR
|
||||
.SUFFIXES:
|
||||
|
||||
# ---- Toolchain ----
|
||||
|
||||
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-elf-
|
||||
ifneq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),)
|
||||
CXX := $(TOOLCHAIN_PREFIX)g++
|
||||
else
|
||||
CXX := g++
|
||||
endif
|
||||
|
||||
# ---- Paths ----
|
||||
|
||||
PROG_INC := ../../include
|
||||
LINK_LD := ../../link.ld
|
||||
BINDIR := ../../bin
|
||||
OBJDIR := obj
|
||||
LIBDIR := ../../lib
|
||||
|
||||
# Game asset source directory
|
||||
ASSET_SRC := ../../gameengine/Cute_Fantasy_Free
|
||||
|
||||
# ---- Compiler flags ----
|
||||
|
||||
CXXFLAGS := \
|
||||
-std=gnu++20 \
|
||||
-g -O2 -pipe \
|
||||
-Wall \
|
||||
-Wextra \
|
||||
-Wno-unused-parameter \
|
||||
-Wno-unused-function \
|
||||
-Wno-missing-field-initializers \
|
||||
-nostdinc \
|
||||
-ffreestanding \
|
||||
-fno-stack-protector \
|
||||
-fno-stack-check \
|
||||
-fno-PIC \
|
||||
-fno-rtti \
|
||||
-fno-exceptions \
|
||||
-ffunction-sections \
|
||||
-fdata-sections \
|
||||
-fno-tree-loop-distribute-patterns \
|
||||
-m64 \
|
||||
-march=x86-64 \
|
||||
-msse \
|
||||
-msse2 \
|
||||
-mno-red-zone \
|
||||
-mcmodel=small \
|
||||
-I $(PROG_INC) \
|
||||
-I . \
|
||||
-isystem $(PROG_INC)/libc \
|
||||
-isystem ../../../kernel/freestnd-c-hdrs/x86_64/include \
|
||||
-isystem ../../../kernel/freestnd-cxx-hdrs/x86_64/include
|
||||
|
||||
# ---- Linker flags ----
|
||||
|
||||
LDFLAGS := \
|
||||
-nostdlib \
|
||||
-static \
|
||||
-Wl,--build-id=none \
|
||||
-Wl,--gc-sections \
|
||||
-Wl,-m,elf_x86_64 \
|
||||
-z max-page-size=0x1000 \
|
||||
-T $(LINK_LD)
|
||||
|
||||
# ---- Source files ----
|
||||
|
||||
SRCS := main.cpp stb_impl.cpp
|
||||
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
||||
|
||||
# ---- Target ----
|
||||
|
||||
TARGET := $(BINDIR)/apps/rpgdemo/rpgdemo.elf
|
||||
APP_DIR := $(BINDIR)/apps/rpgdemo
|
||||
|
||||
.PHONY: all clean assets
|
||||
|
||||
all: $(TARGET) assets
|
||||
|
||||
$(TARGET): $(OBJS) $(LINK_LD) Makefile
|
||||
mkdir -p $(APP_DIR)
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBDIR)/libc/liblibc.a -o $@
|
||||
|
||||
$(OBJDIR)/%.o: %.cpp Makefile
|
||||
mkdir -p $(OBJDIR)
|
||||
$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||
|
||||
# Copy game assets into the app bundle
|
||||
assets:
|
||||
mkdir -p $(APP_DIR)
|
||||
cp $(ASSET_SRC)/Player/Player.png $(APP_DIR)/Player.png
|
||||
cp $(ASSET_SRC)/Player/Player_Actions.png $(APP_DIR)/Player_Actions.png
|
||||
cp $(ASSET_SRC)/Enemies/Skeleton.png $(APP_DIR)/Skeleton.png
|
||||
cp $(ASSET_SRC)/Enemies/Slime_Green.png $(APP_DIR)/Slime_Green.png
|
||||
cp $(ASSET_SRC)/Tiles/Grass_Middle.png $(APP_DIR)/Grass_Middle.png
|
||||
cp $(ASSET_SRC)/Tiles/Path_Middle.png $(APP_DIR)/Path_Middle.png
|
||||
cp $(ASSET_SRC)/Tiles/Water_Middle.png $(APP_DIR)/Water_Middle.png
|
||||
cp $(ASSET_SRC)/Tiles/Cliff_Tile.png $(APP_DIR)/Cliff_Tile.png
|
||||
cp "$(ASSET_SRC)/Outdoor decoration/Oak_Tree.png" $(APP_DIR)/Oak_Tree.png
|
||||
cp "$(ASSET_SRC)/Outdoor decoration/Oak_Tree_Small.png" $(APP_DIR)/Oak_Tree_Small.png
|
||||
cp "$(ASSET_SRC)/Outdoor decoration/House_1_Wood_Base_Blue.png" $(APP_DIR)/House.png
|
||||
cp "$(ASSET_SRC)/Outdoor decoration/Chest.png" $(APP_DIR)/Chest.png
|
||||
cp "$(ASSET_SRC)/Outdoor decoration/Fences.png" $(APP_DIR)/Fences.png
|
||||
cp "$(ASSET_SRC)/Outdoor decoration/Bridge_Wood.png" $(APP_DIR)/Bridge_Wood.png
|
||||
cp "$(ASSET_SRC)/Outdoor decoration/Outdoor_Decor_Free.png" $(APP_DIR)/Outdoor_Decor.png
|
||||
cp $(ASSET_SRC)/Animals/Chicken/Chicken.png $(APP_DIR)/Chicken.png
|
||||
cp $(ASSET_SRC)/Animals/Cow/Cow.png $(APP_DIR)/Cow.png
|
||||
cp $(ASSET_SRC)/Animals/Pig/Pig.png $(APP_DIR)/Pig.png
|
||||
cp $(ASSET_SRC)/Animals/Sheep/Sheep.png $(APP_DIR)/Sheep.png
|
||||
|
||||
clean:
|
||||
rm -rf $(OBJDIR) $(TARGET)
|
||||
@@ -0,0 +1,8 @@
|
||||
[app]
|
||||
name = "Montauk Quest"
|
||||
binary = "rpgdemo.elf"
|
||||
icon = "icon.svg"
|
||||
|
||||
[menu]
|
||||
category = "Games"
|
||||
visible = true
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* stb_impl.cpp
|
||||
* Single compilation unit for stb_image (PNG) and stb_truetype
|
||||
* for MontaukOS freestanding environment
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <montauk/heap.h>
|
||||
#include <montauk/string.h>
|
||||
#include <gui/stb_math.h>
|
||||
|
||||
extern "C" {
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// stb_image - PNG decoder
|
||||
// ============================================================================
|
||||
|
||||
#define STBI_ONLY_PNG
|
||||
#define STBI_NO_JPEG
|
||||
#define STBI_NO_BMP
|
||||
#define STBI_NO_PSD
|
||||
#define STBI_NO_TGA
|
||||
#define STBI_NO_GIF
|
||||
#define STBI_NO_HDR
|
||||
#define STBI_NO_PIC
|
||||
#define STBI_NO_PNM
|
||||
#define STBI_NO_LINEAR
|
||||
#define STBI_NO_STDIO
|
||||
#define STBI_NO_THREAD_LOCALS
|
||||
|
||||
#define STBI_MALLOC(sz) malloc(sz)
|
||||
#define STBI_FREE(p) free(p)
|
||||
#define STBI_REALLOC(p, newsz) realloc(p, newsz)
|
||||
|
||||
#define STBI_ASSERT(x) ((void)(x))
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <gui/stb_image.h>
|
||||
|
||||
// ============================================================================
|
||||
// stb_truetype - TrueType font renderer
|
||||
// ============================================================================
|
||||
|
||||
#define STBTT_ifloor(x) ((int) stb_floor(x))
|
||||
#define STBTT_iceil(x) ((int) stb_ceil(x))
|
||||
#define STBTT_sqrt(x) stb_sqrt(x)
|
||||
#define STBTT_pow(x,y) stb_pow(x,y)
|
||||
#define STBTT_fmod(x,y) stb_fmod(x,y)
|
||||
#define STBTT_cos(x) stb_cos(x)
|
||||
#define STBTT_acos(x) stb_acos(x)
|
||||
#define STBTT_fabs(x) stb_fabs(x)
|
||||
|
||||
#define STBTT_malloc(x,u) ((void)(u), montauk::malloc(x))
|
||||
#define STBTT_free(x,u) ((void)(u), montauk::mfree(x))
|
||||
|
||||
#define STBTT_memcpy(d,s,n) montauk::memcpy(d,s,n)
|
||||
#define STBTT_memset(d,v,n) montauk::memset(d,v,n)
|
||||
|
||||
#define STBTT_strlen(x) montauk::slen(x)
|
||||
|
||||
#define STBTT_assert(x) ((void)(x))
|
||||
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#include <gui/stb_truetype.h>
|
||||