refactor: split out game engine experiment and demo game
@@ -49,7 +49,7 @@ BINDIR := bin
|
|||||||
PROGRAMS := $(notdir $(wildcard src/*))
|
PROGRAMS := $(notdir $(wildcard src/*))
|
||||||
|
|
||||||
# Programs with custom Makefiles (built separately).
|
# Programs with custom Makefiles (built separately).
|
||||||
CUSTOM_BUILDS := 2048 doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display terminal klog procmgr powermgr calculator charmap desktop login shell rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs libloader crashpad
|
CUSTOM_BUILDS := 2048 doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display terminal klog procmgr powermgr calculator charmap desktop login shell paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs libloader crashpad
|
||||||
SYSTEM_PROGRAMS := $(filter-out $(CUSTOM_BUILDS),$(PROGRAMS))
|
SYSTEM_PROGRAMS := $(filter-out $(CUSTOM_BUILDS),$(PROGRAMS))
|
||||||
|
|
||||||
# Build targets: system programs go to bin/os/, apps go to bin/apps/<name>/.
|
# Build targets: system programs go to bin/os/, apps go to bin/apps/<name>/.
|
||||||
@@ -89,9 +89,9 @@ WPDIR := data/wallpapers
|
|||||||
WPSRC := $(wildcard $(WPDIR)/*.jpg)
|
WPSRC := $(wildcard $(WPDIR)/*.jpg)
|
||||||
WPDST := $(patsubst $(WPDIR)/%,$(BINDIR)/os/wallpapers/%,$(WPSRC))
|
WPDST := $(patsubst $(WPDIR)/%,$(BINDIR)/os/wallpapers/%,$(WPSRC))
|
||||||
|
|
||||||
.PHONY: all clean 2048 doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display terminal klog procmgr powermgr calculator charmap login desktop shell rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs icons fonts configs bearssl libc tls libjpeg libjpegwrite install-apps libloader crashpad check-syscalls gen-syscalls
|
.PHONY: all clean 2048 doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display terminal klog procmgr powermgr calculator charmap login desktop shell paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs icons fonts configs bearssl libc tls libjpeg libjpegwrite install-apps libloader crashpad check-syscalls gen-syscalls
|
||||||
|
|
||||||
all: bearssl libc libjpeg libjpegwrite tls libloader devkit $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display terminal klog procmgr powermgr calculator charmap 2048 doom rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs login desktop shell icons fonts install-apps crashpad $(MANDST) $(WWWDST) $(CA_CERTS) $(CONFIGDST) $(FWDST) $(LICDST) $(WPDST)
|
all: bearssl libc libjpeg libjpegwrite tls libloader devkit $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display terminal klog procmgr powermgr calculator charmap 2048 doom paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs login desktop shell icons fonts install-apps crashpad $(MANDST) $(WWWDST) $(CA_CERTS) $(CONFIGDST) $(FWDST) $(LICDST) $(WPDST)
|
||||||
|
|
||||||
# Build BearSSL static library (cross-compiled for freestanding x86_64).
|
# 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)
|
BEARSSL_INCLUDES := -isystem $(shell cd .. && pwd)/kernel/freestnd-c-hdrs/x86_64/include -isystem $(abspath include/libc)
|
||||||
@@ -284,9 +284,6 @@ mandelbrot: libc
|
|||||||
$(MAKE) -C src/2048
|
$(MAKE) -C src/2048
|
||||||
|
|
||||||
# Build RPG demo game via its own Makefile (depends on libc).
|
# Build RPG demo game via its own Makefile (depends on libc).
|
||||||
rpgdemo: libc
|
|
||||||
$(MAKE) -C src/rpgdemo
|
|
||||||
|
|
||||||
# Build doom via its own Makefile (depends on libc and the shared-lib loader,
|
# Build doom via its own Makefile (depends on libc and the shared-lib loader,
|
||||||
# both of which it links).
|
# both of which it links).
|
||||||
doom: libc libloader
|
doom: libc libloader
|
||||||
@@ -337,7 +334,7 @@ crashpad: libc
|
|||||||
$(MAKE) -C src/crashpad
|
$(MAKE) -C src/crashpad
|
||||||
|
|
||||||
# Install app bundles (manifests, icons, data files) into bin/apps/<name>/.
|
# Install app bundles (manifests, icons, data files) into bin/apps/<name>/.
|
||||||
install-apps: 2048 doom rpgdemo paint spreadsheet wordprocessor weather wikipedia imageviewer fontpreview pdfviewer disks devexplorer installer audio music video bluetooth network display terminal klog procmgr powermgr calculator charmap screenshot texteditor mandelbrot printers timezone crashpad
|
install-apps: 2048 doom paint spreadsheet wordprocessor weather wikipedia imageviewer fontpreview pdfviewer disks devexplorer installer audio music video bluetooth network display terminal klog procmgr powermgr calculator charmap screenshot texteditor mandelbrot printers timezone crashpad
|
||||||
../scripts/install_apps.sh
|
../scripts/install_apps.sh
|
||||||
|
|
||||||
# Copy man pages into bin/man/ so mkramdisk.sh picks them up.
|
# Copy man pages into bin/man/ so mkramdisk.sh picks them up.
|
||||||
@@ -495,7 +492,6 @@ clean:
|
|||||||
$(MAKE) -C src/printers clean
|
$(MAKE) -C src/printers clean
|
||||||
$(MAKE) -C src/timezone clean
|
$(MAKE) -C src/timezone clean
|
||||||
$(MAKE) -C src/paint clean
|
$(MAKE) -C src/paint clean
|
||||||
$(MAKE) -C src/rpgdemo clean
|
|
||||||
$(MAKE) -C src/screenshot clean
|
$(MAKE) -C src/screenshot clean
|
||||||
$(MAKE) -C src/mandelbrot clean
|
$(MAKE) -C src/mandelbrot clean
|
||||||
$(MAKE) -C src/2048 clean
|
$(MAKE) -C src/2048 clean
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 622 B |
|
Before Width: | Height: | Size: 983 B |
|
Before Width: | Height: | Size: 735 B |
|
Before Width: | Height: | Size: 748 B |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 386 B |
|
Before Width: | Height: | Size: 585 B |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 921 B |
|
Before Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 648 B |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 371 B |
|
Before Width: | Height: | Size: 99 B |
|
Before Width: | Height: | Size: 99 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 99 B |
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,10 +0,0 @@
|
|||||||
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!
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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::abi::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::abi::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
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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::abi::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
|
|
||||||
@@ -1,264 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
@@ -19,7 +19,6 @@ enum ComponentIndex {
|
|||||||
CI_GAMES,
|
CI_GAMES,
|
||||||
CI_DOOM,
|
CI_DOOM,
|
||||||
CI_2048,
|
CI_2048,
|
||||||
CI_RPGDEMO,
|
|
||||||
CI_MANDELBROT,
|
CI_MANDELBROT,
|
||||||
CI_OFFICE,
|
CI_OFFICE,
|
||||||
CI_WORDPROCESSOR,
|
CI_WORDPROCESSOR,
|
||||||
@@ -85,11 +84,6 @@ Component g_components[] = {
|
|||||||
CI_GAMES, "apps/2048", {"apps/2048", nullptr}, false, -1,
|
CI_GAMES, "apps/2048", {"apps/2048", nullptr}, false, -1,
|
||||||
false, false, false, false, 0, 0 },
|
false, false, false, false, 0, 0 },
|
||||||
|
|
||||||
{ "rpgdemo", "RPG demo",
|
|
||||||
"Top-down RPG game engine demo",
|
|
||||||
CI_GAMES, "apps/rpgdemo", {"apps/rpgdemo", nullptr}, false, -1,
|
|
||||||
false, false, false, false, 0, 0 },
|
|
||||||
|
|
||||||
{ "mandelbrot", "Mandelbrot",
|
{ "mandelbrot", "Mandelbrot",
|
||||||
"Fractal explorer",
|
"Fractal explorer",
|
||||||
CI_GAMES, "apps/mandelbrot", {"apps/mandelbrot", nullptr}, false, -1,
|
CI_GAMES, "apps/mandelbrot", {"apps/mandelbrot", nullptr}, false, -1,
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
# 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-montauk-
|
|
||||||
ifeq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),)
|
|
||||||
CXX = $(error x86_64-montauk toolchain not found. Run ./toolchain/build-montauk-toolchain.sh first)
|
|
||||||
else
|
|
||||||
CXX := $(TOOLCHAIN_PREFIX)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 \
|
|
||||||
-ffreestanding \
|
|
||||||
-fno-stack-protector \
|
|
||||||
-fno-stack-check \
|
|
||||||
-fno-rtti \
|
|
||||||
-fno-exceptions \
|
|
||||||
-ffunction-sections \
|
|
||||||
-fdata-sections \
|
|
||||||
-fno-tree-loop-distribute-patterns \
|
|
||||||
-msse \
|
|
||||||
-msse2 \
|
|
||||||
-I $(PROG_INC) \
|
|
||||||
-I . \
|
|
||||||
-isystem $(PROG_INC)/libc
|
|
||||||
|
|
||||||
# ---- Linker flags ----
|
|
||||||
|
|
||||||
LDFLAGS := \
|
|
||||||
-nostdlib \
|
|
||||||
-Wl,--gc-sections \
|
|
||||||
-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 $(LIBDIR)/libc/liblibc.a
|
|
||||||
mkdir -p $(APP_DIR)
|
|
||||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBDIR)/libc/liblibc.a -o $@
|
|
||||||
|
|
||||||
$(OBJDIR)/%.o: %.cpp Makefile
|
|
||||||
mkdir -p $(OBJDIR)
|
|
||||||
$(CXX) $(CXXFLAGS) -MMD -MP -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)
|
|
||||||
|
|
||||||
# Auto-generated header dependency tracking (-MMD -MP).
|
|
||||||
-include $(OBJS:.o=.d)
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
[app]
|
|
||||||
name = "Montauk Quest"
|
|
||||||
binary = "rpgdemo.elf"
|
|
||||||
icon = "icon.svg"
|
|
||||||
|
|
||||||
[menu]
|
|
||||||
category = "Games"
|
|
||||||
visible = true
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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>
|
|
||||||
@@ -41,7 +41,6 @@ APPS=(
|
|||||||
"charmap|apps/scalable/accessories-character-map.svg"
|
"charmap|apps/scalable/accessories-character-map.svg"
|
||||||
"printers|devices/scalable/preferences-devices-printer.svg"
|
"printers|devices/scalable/preferences-devices-printer.svg"
|
||||||
"timezone|categories/scalable/preferences-system-time.svg"
|
"timezone|categories/scalable/preferences-system-time.svg"
|
||||||
"rpgdemo|apps/scalable/utilities-terminal.svg"
|
|
||||||
"paint|apps/scalable/kolourpaint.svg"
|
"paint|apps/scalable/kolourpaint.svg"
|
||||||
"screenshot|apps/scalable/gnome-screenshot.svg"
|
"screenshot|apps/scalable/gnome-screenshot.svg"
|
||||||
"texteditor|apps/scalable/accessories-text-editor.svg"
|
"texteditor|apps/scalable/accessories-text-editor.svg"
|
||||||
|
|||||||