From b5d2cc515cef5e1823a8a3793ae1d3d5b035131a Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Wed, 18 Mar 2026 14:26:50 +0100 Subject: [PATCH] feat: add experimental game engine & demo game, fix ACPI events --- GNUmakefile | 2 +- kernel/src/ACPI/AcpiEvents.cpp | 11 +- kernel/src/ACPI/AcpiEvents.hpp | 4 +- kernel/src/ACPI/AcpiShutdown.cpp | 4 +- kernel/src/Main.cpp | 4 + programs/GNUmakefile | 13 +- .../Animals/Chicken/Chicken.png | Bin 0 -> 622 bytes .../Cute_Fantasy_Free/Animals/Cow/Cow.png | Bin 0 -> 983 bytes .../Cute_Fantasy_Free/Animals/Pig/Pig.png | Bin 0 -> 735 bytes .../Cute_Fantasy_Free/Animals/Sheep/Sheep.png | Bin 0 -> 748 bytes .../Cute_Fantasy_Free/Enemies/Skeleton.png | Bin 0 -> 5356 bytes .../Cute_Fantasy_Free/Enemies/Slime_Green.png | Bin 0 -> 3967 bytes .../Outdoor decoration/Bridge_Wood.png | Bin 0 -> 1161 bytes .../Outdoor decoration/Chest.png | Bin 0 -> 386 bytes .../Outdoor decoration/Fences.png | Bin 0 -> 585 bytes .../House_1_Wood_Base_Blue.png | Bin 0 -> 2898 bytes .../Outdoor decoration/Oak_Tree.png | Bin 0 -> 1495 bytes .../Outdoor decoration/Oak_Tree_Small.png | Bin 0 -> 921 bytes .../Outdoor decoration/Outdoor_Decor_Free.png | Bin 0 -> 7431 bytes .../Cute_Fantasy_Free/Player/Player.png | Bin 0 -> 7770 bytes .../Player/Player_Actions.png | Bin 0 -> 7609 bytes .../Cute_Fantasy_Free/Tiles/Beach_Tile.png | Bin 0 -> 648 bytes .../Cute_Fantasy_Free/Tiles/Cliff_Tile.png | Bin 0 -> 1600 bytes .../Cute_Fantasy_Free/Tiles/FarmLand_Tile.png | Bin 0 -> 371 bytes .../Cute_Fantasy_Free/Tiles/Grass_Middle.png | Bin 0 -> 99 bytes .../Cute_Fantasy_Free/Tiles/Path_Middle.png | Bin 0 -> 99 bytes .../Cute_Fantasy_Free/Tiles/Path_Tile.png | Bin 0 -> 1129 bytes .../Cute_Fantasy_Free/Tiles/Water_Middle.png | Bin 0 -> 99 bytes .../Cute_Fantasy_Free/Tiles/Water_Tile.png | Bin 0 -> 1500 bytes .../gameengine/Cute_Fantasy_Free/read_me.txt | 10 + programs/include/engine/audio.h | 81 ++ programs/include/engine/collision.h | 48 + programs/include/engine/engine.h | 200 +++ programs/include/engine/input.h | 87 ++ programs/include/engine/sprite.h | 264 ++++ programs/include/engine/tilemap.h | 134 ++ programs/include/engine/ui.h | 114 ++ programs/src/rpgdemo/Makefile | 116 ++ programs/src/rpgdemo/main.cpp | 1079 +++++++++++++++++ programs/src/rpgdemo/manifest.toml | 8 + programs/src/rpgdemo/stb_impl.cpp | 69 ++ scripts/install_apps.sh | 1 + 42 files changed, 2237 insertions(+), 12 deletions(-) create mode 100644 programs/gameengine/Cute_Fantasy_Free/Animals/Chicken/Chicken.png create mode 100644 programs/gameengine/Cute_Fantasy_Free/Animals/Cow/Cow.png create mode 100644 programs/gameengine/Cute_Fantasy_Free/Animals/Pig/Pig.png create mode 100644 programs/gameengine/Cute_Fantasy_Free/Animals/Sheep/Sheep.png create mode 100644 programs/gameengine/Cute_Fantasy_Free/Enemies/Skeleton.png create mode 100644 programs/gameengine/Cute_Fantasy_Free/Enemies/Slime_Green.png create mode 100644 programs/gameengine/Cute_Fantasy_Free/Outdoor decoration/Bridge_Wood.png create mode 100644 programs/gameengine/Cute_Fantasy_Free/Outdoor decoration/Chest.png create mode 100644 programs/gameengine/Cute_Fantasy_Free/Outdoor decoration/Fences.png create mode 100644 programs/gameengine/Cute_Fantasy_Free/Outdoor decoration/House_1_Wood_Base_Blue.png create mode 100644 programs/gameengine/Cute_Fantasy_Free/Outdoor decoration/Oak_Tree.png create mode 100644 programs/gameengine/Cute_Fantasy_Free/Outdoor decoration/Oak_Tree_Small.png create mode 100644 programs/gameengine/Cute_Fantasy_Free/Outdoor decoration/Outdoor_Decor_Free.png create mode 100644 programs/gameengine/Cute_Fantasy_Free/Player/Player.png create mode 100644 programs/gameengine/Cute_Fantasy_Free/Player/Player_Actions.png create mode 100644 programs/gameengine/Cute_Fantasy_Free/Tiles/Beach_Tile.png create mode 100644 programs/gameengine/Cute_Fantasy_Free/Tiles/Cliff_Tile.png create mode 100644 programs/gameengine/Cute_Fantasy_Free/Tiles/FarmLand_Tile.png create mode 100644 programs/gameengine/Cute_Fantasy_Free/Tiles/Grass_Middle.png create mode 100644 programs/gameengine/Cute_Fantasy_Free/Tiles/Path_Middle.png create mode 100644 programs/gameengine/Cute_Fantasy_Free/Tiles/Path_Tile.png create mode 100644 programs/gameengine/Cute_Fantasy_Free/Tiles/Water_Middle.png create mode 100644 programs/gameengine/Cute_Fantasy_Free/Tiles/Water_Tile.png create mode 100644 programs/gameengine/Cute_Fantasy_Free/read_me.txt create mode 100644 programs/include/engine/audio.h create mode 100644 programs/include/engine/collision.h create mode 100644 programs/include/engine/engine.h create mode 100644 programs/include/engine/input.h create mode 100644 programs/include/engine/sprite.h create mode 100644 programs/include/engine/tilemap.h create mode 100644 programs/include/engine/ui.h create mode 100644 programs/src/rpgdemo/Makefile create mode 100644 programs/src/rpgdemo/main.cpp create mode 100644 programs/src/rpgdemo/manifest.toml create mode 100644 programs/src/rpgdemo/stb_impl.cpp diff --git a/GNUmakefile b/GNUmakefile index 90c658f..c815479 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -6,7 +6,7 @@ MAKEFLAGS += -rR ARCH := x86_64 # Default user QEMU flags. These are appended to the QEMU command calls. -QEMUFLAGS := -m 2G -d int -D qemu.log +QEMUFLAGS := -smp 4 -m 2G -d int -D qemu.log override IMAGE_NAME := montauk-$(ARCH) diff --git a/kernel/src/ACPI/AcpiEvents.cpp b/kernel/src/ACPI/AcpiEvents.cpp index 65a55de..bd69f3c 100644 --- a/kernel/src/ACPI/AcpiEvents.cpp +++ b/kernel/src/ACPI/AcpiEvents.cpp @@ -79,8 +79,7 @@ namespace Hal { KernelLogStream(INFO, "ACPI") << "Sleep button pressed"; } - // Send EOI - LocalApic::SendEOI(); + // Note: EOI is sent by HalIrqDispatch after this handler returns. } // ============================================================================ @@ -88,7 +87,13 @@ namespace Hal { // Initialize // ============================================================================ - void Initialize(const FADT::ParsedFADT& fadt) { + void Initialize(ACPI::CommonSDTHeader* xsdt) { + FADT::ParsedFADT fadt{}; + if (!FADT::Parse(xsdt, fadt) || !fadt.Valid) { + KernelLogStream(ERROR, "ACPI") << "Failed to parse FADT - ACPI events unavailable"; + return; + } + g_pm1aEventBlock = fadt.PM1aEventBlock; g_pm1bEventBlock = fadt.PM1bEventBlock; g_pm1EventLength = fadt.PM1EventLength; diff --git a/kernel/src/ACPI/AcpiEvents.hpp b/kernel/src/ACPI/AcpiEvents.hpp index b3287d4..47eb2f5 100644 --- a/kernel/src/ACPI/AcpiEvents.hpp +++ b/kernel/src/ACPI/AcpiEvents.hpp @@ -13,8 +13,8 @@ namespace Hal { // Initialize ACPI event handling: route the SCI interrupt, // register the handler, and enable fixed events (power button). - // Must be called after APIC and FADT initialization. - void Initialize(const FADT::ParsedFADT& fadt); + // Must be called after APIC initialization. + void Initialize(ACPI::CommonSDTHeader* xsdt); // Re-enable ACPI events after S3 resume. void Reinitialize(); diff --git a/kernel/src/ACPI/AcpiShutdown.cpp b/kernel/src/ACPI/AcpiShutdown.cpp index 26706e8..1d6b714 100644 --- a/kernel/src/ACPI/AcpiShutdown.cpp +++ b/kernel/src/ACPI/AcpiShutdown.cpp @@ -69,8 +69,8 @@ namespace Hal { // Initialize S3 suspend support (reads FACS and \_S3_ from namespace) AcpiSleep::Initialize(xsdt); - // Initialize ACPI event handling (SCI interrupt, power button) - AcpiEvents::Initialize(fadt); + // Note: ACPI event handling (SCI, power button) is initialized + // from Main.cpp after the APIC subsystem is ready. } bool IsAvailable() { diff --git a/kernel/src/Main.cpp b/kernel/src/Main.cpp index 6d07e17..236895b 100644 --- a/kernel/src/Main.cpp +++ b/kernel/src/Main.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -144,6 +145,9 @@ extern "C" void kmain() { Hal::ApicInitialize(g_acpi.GetXSDT()); + // Initialize ACPI events (SCI, power button) after APIC is ready + Hal::AcpiEvents::Initialize(g_acpi.GetXSDT()); + Pci::Initialize(g_acpi.GetXSDT()); Drivers::ProbeEarly(); diff --git a/programs/GNUmakefile b/programs/GNUmakefile index 31ff8ae..903a68d 100644 --- a/programs/GNUmakefile +++ b/programs/GNUmakefile @@ -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//. @@ -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//. -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 diff --git a/programs/gameengine/Cute_Fantasy_Free/Animals/Chicken/Chicken.png b/programs/gameengine/Cute_Fantasy_Free/Animals/Chicken/Chicken.png new file mode 100644 index 0000000000000000000000000000000000000000..19163df479a87f2fed57a50f2f85d58eaefe8518 GIT binary patch literal 622 zcmV-!0+IcRP)Px%C`m*?RCt{2n!irlKpe-vtxyhSI4{Xi9aKU>EM0g5CbrIfg7{H7@C;#sS3sRu zBEi4}Z-7+AREVYafO6?@49(AS{sNi!e+JSiOp7>hqN;8$8Hw@a6SHm?~ib+E<#fc@Gd}A_;2#RSH1$GH9#xlW~mlL zepkmr#GsXNSNn(VlFQAoi zPxlvSeL6%w8e;tG&U@Pbp_OrY-mGCe*Z#NK?rSk)+tw^#36e2 z;Wl^-sEW=mLm8JNR1sn|pbS8g7opkYWh|6_%m$P}REscMj3A;*v{&5#0O$eeiAeR3 zYm+0G{wo^brSJ0==mCIMrZ4}-`Jjz_&|Z3P!<2KjE{~CH@1XVb8*p=g?%r1vHcJbe zrM~(;yC>hIPCf6BqVKzXd~Fa-C%-QGgb+dqA%qY@2qBx##@gf2++h)qX^%Il@9ho7 zw#TFD$F|3#>c_UnL+NjfJ-!zDvEA7a`ra#GQHAAW1sRvF_|Cod`mx>F*!l~DSm4eE zrD^?I@6Sa-vGxLQ)%V>AmgZzWXd^j#hFIlEYFx#h4r-1THcPcKeX-_~qvxsEeDv#m zA23P2uvwaPx&kx4{BRCt{2TEA=CKp1^)3eMzYF&ON{Efh~7Q#LQ1P5y}{lef+uJ$K90_Mb4B zvv|v7GKD~!#R5Z?R8ut=b%;OZ)9LPXXPwTbxd*~`-zj?EFCCGdfPjF2fPercTFq{1 zWB;$vYIf5!O;a&gmAx(>Z0yd-39V)~J49RE{B;)Ky^j3p@ft^mJpjPd<8`(s+S>h5 z8YfYssxvD5`Mbo?VK3!#?q?NlX#yzt^>P6qA3V+5{Sg3Qyo#W!xX!l3F-SPBzofUv5NS5$2Zsj7ip$=l^#o!6;{;1Ru89#tRpnSYuF3=fH zvcFLjK+CO9z|U`a6%Z5ge0OV|1Rn)Z{v#A8 zQKS9ktJGNv{P^SyK)&_FUHSR$76339&v1XGVv|Ex0PY9(fw;wPpaOJ;lia4POaSnD z&$gP~l#>Ck!4nxz!t>oNj!(|A;OSa}@yxjTz6zjCfDSx30Dv$rtix^-VPmX#``2A%@WroHhk>FJtFpONhkf!FbOBMB6^Uf?stWBc z`K28|Z&^k0QFMYU#V#dspZrQ?NG6Gk5a0-wkp+=@$+kvFThK%qTM7>#vLFF&}7XObe*TdCJ7{yOS`YM zCD{uH6Aiv#XXCBfU*tF8R9q^2A0RV^?*jq?0s`JL{sFT-`b-!TvXcM+002ovPDHLk FV1gEO%zpp? literal 0 HcmV?d00001 diff --git a/programs/gameengine/Cute_Fantasy_Free/Animals/Pig/Pig.png b/programs/gameengine/Cute_Fantasy_Free/Animals/Pig/Pig.png new file mode 100644 index 0000000000000000000000000000000000000000..5312368b3220ea95e6a5753ac5e73a7b0f3825aa GIT binary patch literal 735 zcmV<50wDc~P)Px%nMp)JRCt{2n!k?XFc8MS94FDyAw{Bt9*M`e=Ll3sm68WYnJOK}i`+}Bv^{i@ zkkX|~fpW#}f)3F0OnB)`mT@XQT(zX_(;IFF5%*o;YoL9M>FDOo_(NG&FOg4}jX z>l17MfDh-wBx9Yw9`=|=F(#`eZs$v5uzJzl^{{`~NS_J#&P|q89)wBO+^9eD#VKuq zK?ez zirFeUq`=ugg;>vcR{~ry27nAv|CV&rLIl97@Yv!b0lfhLuvG`-uxdbA*0;&f7WnI&?yN4t%nu5{tM#n|Fo357;8pszd(_p4 zq0Wy*Ul0UA5ClOG1VIple~3}H$9?FJwmt4kf0XTUpZbH^<6)A$q&@Cae^7fIK&O#I z+T%X;N8KL(^6s#Y?(A3VkGehXL%;Vuz@|If#n1++5B<@$$9?HrWrr(B9C}us(IS7; z2F@nuBKU|f=e+AXfU6BI7`K2f%$*IOdCvJ~z&Wp<3E;5Rw>couh*HlzOtPc;BJ!wl zuBZvnlc67o2$PJ?2^hCv@BXAmxbGl>VLY@3Y}K`G4@xM=`2ax>1VIo4jz6+})-$GW R$szy%002ovPDHLkV1j?pQ}zG= literal 0 HcmV?d00001 diff --git a/programs/gameengine/Cute_Fantasy_Free/Animals/Sheep/Sheep.png b/programs/gameengine/Cute_Fantasy_Free/Animals/Sheep/Sheep.png new file mode 100644 index 0000000000000000000000000000000000000000..242c1a0f6e93905685df99df8ded1fffb9633ea7 GIT binary patch literal 748 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 bO5^zlDUXihtMcLC00000NkvXXu0mjfHHss` literal 0 HcmV?d00001 diff --git a/programs/gameengine/Cute_Fantasy_Free/Outdoor decoration/Chest.png b/programs/gameengine/Cute_Fantasy_Free/Outdoor decoration/Chest.png new file mode 100644 index 0000000000000000000000000000000000000000..a0fe2b05ca0563b775cf0be9b1972fed4ead3089 GIT binary patch literal 386 zcmV-|0e$|7P)Px$JV``BR5*>DlD|sBP!xy1luJs75-^%VQ7SGCLCm(0O=~kul$?)B4aLRIe+YJDM!`+&uz7y*iP6B>9f^+oNRt zfxW|HJh#Q;^~KEF+NTNo3^~5rj2Zl= gIVvTxU&Q3>8=P5PfHh`4(EtDd07*qoM6N<$f^{dPw*UYD literal 0 HcmV?d00001 diff --git a/programs/gameengine/Cute_Fantasy_Free/Outdoor decoration/Fences.png b/programs/gameengine/Cute_Fantasy_Free/Outdoor decoration/Fences.png new file mode 100644 index 0000000000000000000000000000000000000000..72e72607d7cc0b1b3c16058afe157afb949bbf95 GIT binary patch literal 585 zcmV-P0=E5$P)Px%14%?dRCt{2o4roMFcgIk6%t|xu|NkFD!~*9F=p(}GlYle6Yvo2GjwO{SRr+y zQdt;)1;Jji5S9dm#&+yDj{oi_RcczF*s^_{++!Iah^X1){gIB6yn4OZthi-;*6;Jg zlkpH&H|GF=r{xO((q2~Gh696z@Z#&iV5O#nrXGa6Pef|9Vt!$tz{Vt6ipi-fT`Fd+r&%@{psIu6s z?o`UBqh#8p9zX_Qwp-5VQ=lncUCjzZzZSwgp^*nmZ21|6) z>#oxC661bAbwq6HdBo%gFp2&t(DxdklHIXJP*-20&lzAlHD(fh2v7&uZh&o!`vI=& zqn`oB`~Y9{pi4-80CRs`7BBsP(07FZ!3^N+ z@9W~6A0P?=-TnF6{VeO5G_3(fDs_qI-vp@xOjkgDfa7o%LEU5sAV0thee@wfet<}% z>j!v$UO?9m2;#gzSA=k0z;{1D{k(wdZ*iveSvpGcVl~f-PjLFCM_X0YX8QZCCxYh} X@TLY^V#x>j00000NkvXXu0mjf_+1Y) literal 0 HcmV?d00001 diff --git a/programs/gameengine/Cute_Fantasy_Free/Outdoor decoration/House_1_Wood_Base_Blue.png b/programs/gameengine/Cute_Fantasy_Free/Outdoor decoration/House_1_Wood_Base_Blue.png new file mode 100644 index 0000000000000000000000000000000000000000..fa216ce7bb8d31777b3d6a3947d24fbbc05e63eb GIT binary patch literal 2898 zcmV-Y3$65tP)Px=3`s;mRCt{2T~BCS#~J@st|e3x#kDN2o0ZoVts~>$i$I082SHuX-G}thKrXG@ z-U4nZdoReP6r59=xb##gO+kko0m>%Zkn)i8o4O4BC53|$CeSxI*qmm?L5t! zotgK|pZDH8`Tbzg?(Ca4Z@=H0@ArM*%xZxWx#BB%Uw|!U&P;Xr*Ec@CQ^?Qq)x0mj z1~X@-x|c7W$CH&MzPVdO5(e_Vfae{BBcNKXc5U7Z6@?<8TCH}!cK!#-_Yc(06^?|$ z5RhbihN6*B2m+|aXDAX0!4pueR=Wpk=P-D9$EaUXNC=t$@y;JSyo1hCOL=^I{Xy(? z5l9G@0GZo)+IWbe(-mc(;p4{&M?#PU*chKdB;nwZLwGh=36_N52v7+w#rO=2go8&8 zMQx$ZgrEqJ8NajCGBQ4TcRc`XEiT8}LnI*>0;tAM5@G(XA3lC8coKpjz{L1TA}ktw z`1rBD<3b`KcLG$#M=$K;+o&QeBO%o2C|3exNtS467W?^6%8FH)_*} z@YdpTpX(7LAprPw{g;4519%*$okJDquC1-5rU$7JK#g{EcX`rrGUzg4=7Rq9&pYu> z>yMP@OPhpLTo5JB=VSUjGGP;>nJ0Lk0n~avBZLxuLXsmSK^2Y;jGV;p|NK{N-$O4? z%WFp|%ml9l@Phm7@qkfGuWJ)YQbCo3iJGvzwSu3&_QR;{)7B9QsaNlx(c@gvdWRAn@J z&i6qEwIgQ&sK%F7HBA;>T-Ss}Vg6+OQS{(nLvq)rNrICG_!eB7RWKQt;OcS$cDKu5 zw5a1K%+@o&WC2|j*(CK%BNLW|s4`GPS=X6hMSyM@QEaoV@u#^aOob|U@JWL84DjL0 zWv+;tpsAp9AN=g#H9*t@Szlcne_m5KiLffNp&C#n)@fzJytwjF4<8XhxWn1;l;uqu zI1xY*Qj_pADHAq>X$WVI$dFmh7!$!O~de47gi)&$rjPdb;C zW+p6ayY%*ZeK~-r0n~8RMd-bWune}dA&pE}X0*57z1^n;vh&FhtO)D&_qaqmJ~CmM z(JsCHUSEGo6cwZht1?^|`gdyh`Qpjb(srE>ml;wdff7X_L=mRC7?Vv{;&Cr6d;|bk zxOoc;H*ewn4_bKtgI(e#KBr~EvbMM0z1`PFDe46y!eUz*;pa;WAK}6)!_n*2jd*B! z;gw;G|Hm$JQO$&;qMoXaiqtY;lPxBc*}#m?j4m3VwW&y;L@u%rl@bXBCBo1Y+h8fU zNB`c88GpJmghs0qdrVh`0FH#SL-BK(loVw3ABvvXzVm)h;^OqP~iRLGw!ke2TEFjk+7c#phFyB+MXw zsRNow$X0}{383s;rp$&U<5S#@4yzkGkvpP9N0jaDvKjN`eY@-PBEql`TuQX#YJ3nh zfm*<$2(t~mm!q>3Tq?2@W>Z-0=wiE|nNd4SExa&2M#_#mOD(P7W(8hfP8=P;e6x+k zPTcrn!o}J~&Q4%!aXEUgz5W1$&swnAA?HnmU85a};5J$v+<4=i==IE%R{@}}je0JZ z=m^4bnPT+gAC&bY;ki1ZvqrtS6bmiwHBMCAgxI((J=vD$Kcyuy4WT zk9UFTSf;{^P%~j)NfuxK5k*)Q1@Y}B<4`kZ6d@D#VSL%5yDcc^@(~@`z(1wo z>+3^^g0%e;#&n5}OyRX0t%`QwSWZZH2aOZ?C-*{2Kg8rKC*1k9Y7>egp#n7g)@g_wkk zMMVE+ox18W3%tyL`fLsV{%mj0|9&+-B%jq4&IGV;DDqg{XvH*vJn-!F z2(_-{`?IF@&$~sR$r)6JC^26?Urb z5?T9vvyICa&qt-?7f+s!{uu{*UZ1U@K3hYh)lq&^-sKu7$NCPjQ~mt_N_2#t@p;29 z+`JX<(2Kjj{k`wv$;uM;lE1j0mW@Zf2Ge7GA@g5R647XNP@k>I4KIMbKRG@IF$1TL zC37RH_AHP)0iy9&H+GZ)ycdByv?763Fxf^fabp+T+MZqe2h_FvyIJp6JSEgA)`k=((H$Jap>`e4Fh)pU{wd6vSh4e5}u}i$qM0 zj{!})ZD`EB@Y~mAud_sWw?v8yI&22$G$3mPNkCeN39Lc`o@)^f0a@!rP#TaG-n{-g za2=TBH}~G{-_)B03NZubuHE;x$Ic`0yU)=ITyHiWS`~ zBPrhqo&W%7e0a~xc}$ihvqJC$)Msm$o2y69U172!mIZ<*04Z#S3JHY};r)z~fSlol z0F`?%SrFHqhk8sFi}Jo*VGxV*Jm#BiT);!^bCdBxUzGQfUh5?myuw6F$IC@hTE^7 ww?v5&B}$YiQKCeN5+zEMC{dzBiT#2912AAmKPx)kx4{BRCt{2TTf3LM-=}#HKGPZy|6_JI2R5EBBTZhM>2<8BIO!Kh)>ZkkOL;-7st9ulwigJiHfs%3>lFY%`(y}UWMx_D(po+= zrnHtv`(zjz+pIO5lkkXR66*4+J7w%`9|E{nvRbbMZF*n6RuSboS6H)KSnzJE^-9Qk zvVsEblVN0^YRumDVc<=B+lNlPfsFvy5MBlF<_4<}NP8Ju-6>;rr_B0ZV**6d6EWXD z83qZ>1?14;$_*?@P)>5(P~{FTZ?Gaj6ftxua{)T9SZ2B2WkdjVe_F`_a2g8jlOebh zu2D9nrK}1r!-0ZhWiVb&C<(NxOG|k<>`dzfs}P_9xLg5L;Cin%0dm%rW1gFcxcR8{ zO`+v7@-~*P&D8z5LLuoqbkn692FA-EU5aEuIrBi22XJ|scG6JAKNkd%D&^?ARQ;w~ zXDts<#eWhc9Y>Xy$rZVV&PBJ;rpusq2vqBpp!m`8fTRUdX)Pbjt2#G`I!iDuiM0vX zujj@NE#ee2EtBI2ljo||E2!2hMkHtvFgW*f{_a8Oo)A(ctgO5a396(jzk?$Y8YMws z{9rTg6ezAgDJInerj?+URD}ia*4x^IxAJY`*hyibn?HbPPp~;IGpYmp)>LR0s8S}X zE{F=1NipxL%R@OJwUjcDhy~sl$E7!Jjt4qh!SYl z8t}F@;caaiFM}+loKB%5kB@6l+FL|$(HTT}3N z4~&zbu?MJ>m+x^YPv;TDdn@09__z5^V3Y&oVn_%n1XSIZ+fndaQ_5%LXE~GyEY1W) zTaH6&eHpU1M0twJlT^6u3~}}8$chA`5D=fRXP&eg}NE<60kZq^@0 zE;~c`y9bzE%8%Wb5#`=d{g<8L*gRLCj)H$}tq)j!92KE3^ZZ3XfN)Fso=i{_{33`>Mp>-B29`SFC-;G?x!SDP!c#lkF)>afQE%YltOW1 zgu;|i7eA0-`8rG?3mK(+`EfCn(C9Uti!0eh!PyUxqQU|I%8!cxfW|-F&^5i6)=%b~ zeaIsUECWX^;9k*<`RTi^V(6LC{USb|{=PDL4ghfKN=Cpz2;5Azu99FZg!S8Je(3rkU`v3u z1%#U)_pSlldxWO@TggC?ng_gGUT?j7{S?5xvcmGmz3ZU;?)6i=TwZUbC6^5WM$C`0 z{s_gnM}a3C_pU>IX~+Tq-rc?VnECmS{-3{U$kW$V07y9rcKbJY|78$dQ;Xk~zv%XF&_4513k91( z=oATDPgwuM=N^uG*MXwIdaHQmkCE`vth_NUifngW#<3@?`}SD@r9}_F{PvZQqm2h6 z0C2QP(STJ+{2+zU{|Q`&4iDu8)*B+uQPx&Q%OWYRCt{2nLSS;K^VvX7>EIL-EAz;Yb@BqP(UIiYC?InhU2q%1(nTx7A5)_ zEFghPq%iVQP^rabO9SL`o9q?v!0fP#?AzRIGQWyhX8F(aIM}M%hykMCnU;44u6hiT-tWzSzY^Mv#%b@$5BYkivO= ze!EsX1MpW`eJS?<{$WR}ok9D$GQt?7Y-3=RZR8YVkg|PmG+SHm10waMtN_9oq!t-u zH;6YLup3U_PsS$pj*>8%Eot?o+yfRZz-H)VyuRU)Q+Vglc<(5Q$=F1uSV5*(*?JEU zsV`*+HHh!CbdBb)r5~2_!`7F! z0$2x3dmU`bL?_4z(_ROF_xC}f^rZ~|uzGkQTbQ|+M+iL}g(wDXc0nS-tG+UUA z&830tXWH~8V{@5#vg=D-0R($`V6%~jZ51>og1nNBQeWx{AZBky!pVSXuVXVZQ>?6I zp4+{$=*OZ0SlDbVe>wyJbl+dVZkR+Ae@}+3AG-h$RlQ^Zz{&nDe*OM~?)wXXXq+CU zzSOTGKH_A5cllYAryomv1)!>ztRLULVDxI>?s$Y#gspC=?2XLZMJ76bgkx@gMjH*`TkI^V&1V00000NkvXXu0mjfL_W96 literal 0 HcmV?d00001 diff --git a/programs/gameengine/Cute_Fantasy_Free/Outdoor decoration/Outdoor_Decor_Free.png b/programs/gameengine/Cute_Fantasy_Free/Outdoor decoration/Outdoor_Decor_Free.png new file mode 100644 index 0000000000000000000000000000000000000000..c4a82182e0c378deba5267d23fd76e0c03792b6a GIT binary patch literal 7431 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 literal 0 HcmV?d00001 diff --git a/programs/gameengine/Cute_Fantasy_Free/Player/Player.png b/programs/gameengine/Cute_Fantasy_Free/Player/Player.png new file mode 100644 index 0000000000000000000000000000000000000000..56575c844428bb1e1b9115dfa251e556a9786440 GIT binary patch literal 7770 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 R0$eNx40MdOYp=P){6D%-7`*@h literal 0 HcmV?d00001 diff --git a/programs/gameengine/Cute_Fantasy_Free/Player/Player_Actions.png b/programs/gameengine/Cute_Fantasy_Free/Player/Player_Actions.png new file mode 100644 index 0000000000000000000000000000000000000000..13c5d7114d70a4faeb1c95705a3bed67cb7de0c4 GIT binary patch literal 7609 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 literal 0 HcmV?d00001 diff --git a/programs/gameengine/Cute_Fantasy_Free/Tiles/Beach_Tile.png b/programs/gameengine/Cute_Fantasy_Free/Tiles/Beach_Tile.png new file mode 100644 index 0000000000000000000000000000000000000000..2a670529a52e49ae7ef2e9cbdda28209402784c6 GIT binary patch literal 648 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 literal 0 HcmV?d00001 diff --git a/programs/gameengine/Cute_Fantasy_Free/Tiles/Path_Tile.png b/programs/gameengine/Cute_Fantasy_Free/Tiles/Path_Tile.png new file mode 100644 index 0000000000000000000000000000000000000000..bc20df5a0919c4fc97bd3ec1bb7a17b187c7d6ab GIT binary patch literal 1129 zcmV-v1eW`WP)Px(BS}O-RCt{2TfJ@@F%14(7}>I9%FqHq#v+-5B3tJ^L$|y_-=a&$uHN!40h-B~ zjSL+;W$|VhXor9Y9!-k+xs!Z}za?koB=M6(illV2uRp*2!-VmnwE(3!5@?Mhf%S5$ zmTBeQ|8h`7V7=U8^StW`L}j7(`R`IB6QE9I=hjT{XDPf!J{2wjtpobIdEQ}hbJht) zLdh~-PXFWYStMcz%{}4ea@aiYaC*_NwzxUN-Q!F5EX#Pge3pGt+r01d>ytbJRFN8{ zQIo>T=d0UGJUy)T1iiYw1OPleth(O|pw}KfQ=cK>0bmy6O7JBhp$SExGFdLc-veF= z#Rv~LGhjgzCbrCLPm=MFq{Jyg<$|{#qr@fwzTsHQXDnz|%!0avr1c@;#~yuxRBnQ% z6YdhKEj;1s_A+YLRRZVNCpf*B+(p-KG{Yr@2W zxyhVmQR}`%a%wRJHT1#M0hzE$6{gl1n>hn54m5J$>VTKtwm|io>p+PqY-&dN72y&y z+FS>6%_!9!8_@wx%69@ak7uI@guW9<`F>yw1m{@0H3j(J3IM2fRUt@#?kfPVt~DQ7 zuAK6e1=zpMJ{DXplUM1jtM^*@L4=e*|Ip4_5)0-18c4EpA;DU?9+S@t9SO9?3A#%IcCUW&67nj> zKz?GFOEM(G-acR*O2iyicy&FYLW*m(9lep2oPk**AJdLUUR^@kbms_Ndc+j7bP0l+XkKXsH_! literal 0 HcmV?d00001 diff --git a/programs/gameengine/Cute_Fantasy_Free/Tiles/Water_Tile.png b/programs/gameengine/Cute_Fantasy_Free/Tiles/Water_Tile.png new file mode 100644 index 0000000000000000000000000000000000000000..904eff2065f50d46f41bc3e65508ba5157389d92 GIT binary patch literal 1500 zcmV<21ta>2P)Px)mPtfGRCt{2TfJ@@IS~GQ3IYglx*!)-5>glef=I{yY^JAeHB z=YM#>_^oRKsn`nW8e0Jur#Duc&F=fI4w3|1oZjH7xHSlo&GkJ0oQh}x)TnG+dlUSB zN!}(t7G43i2I%iqaf^f9yOJ1@k~VIq@Az|?h!8=KeB|YDxGHY(WMmdQ*uBH7*!(YT z+&&o&fb*r=7S+w$e*XG#JpojZDyCkOWGBM`CIC(tF^y>X-THZ1AN@~ly2t|oL+JVI z#|C)-`1tA>KE8UU662qx&GtO6kA_tU`5*yWqbKJ}EQ)I^ifg0i$@XP_W|Xtpzs%2! z@-DF&oI?nxP9!1@0Q3FBd*ksD7R8xqTiXdXyDUE*^($@r-olQy8|B1l_*95DjOkj~ zc+|&i)ia!hCdB#%r1v@4y~AWUz@j)a$-jJi|M$n&sx8DW%Fp)?F+Dyi9RT-Ynh5U@ zqKR7pC&d5Po^jb$4)^*PJ4@+kuIoP1ifhM=SDIgwELx{W(-*k;f{nE|ZCR{Ss zbxx0uFdp@dO`qKGc?AIXW1q(0Y}Ko^O4h|>I4Hdj=V61>3tgPvl>4AA(C8%Y3dy@s zLfdXM6ixwr;&kOkecn(NAj8JCA;VG)UN=e`xwbJG4h&bSiBSDh=|T%&l7*rla!YN4 z8V>-QH?eoQ$~*o!*n;-Y(_L?9>6`d+FJ>#fN*lYY+i&Q&sX7)t&BG&jv)acEU0e~ zPB#jeXO`vB;(Br|-@LaMQi62rDF%SSx6c4JK|Hk;ayl2Z4)DpuKVKEMSiX5*5|E-a zyCl!+BhtL>|dlegR7GA@}zcdrLyxZj_NHf-;z) z@3BX7In(YDdtPUXrD$TY#L0@yJJ zCua*jh@>lUG3v@7!W1CRCOJZSU9;yrTUFNqx)bmnu4>>haL)S$)s24m{xj&7Py;XX z;rmbQ)gTvwsH4SLv~kP15#TtRuRDa=xMTP=H*<8=PcE|V93YpPGgTofb2k=kWW1vsY% zzaPi}XFwz80U@N<+Zg|VM*z=2pRIaE&+;UYS%FGG!!z4+rs{Zy5c%)D%T?va3j}H3 zyIdJ%bHWrLCbG;xC$chwg)&QQ!-X=`bLCo8htT?lk!O~3XuZnQ@&QV>S8}ZzgnbUM zYf_muKN+GVy2-3smI934mUU);|8Q6A9H*Aw1lVjPe8jVzO4ty(4UXyUR!^sGY4bK) zi|GD-AQ8W2K=Y80<({|^NQa@!E&UCoocHG_tJG&dTI3JM5J@Rp=Xoeci?xt?RN^Zi zJ%l_3gCNt->WXx5sICLr%}U6Pi?6JYlxyNDb%!rxDG7X9&xh3G5wBlx1iCS2!>RXV z0Yffs9S9{7lCfA +#include + +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 diff --git a/programs/include/engine/collision.h b/programs/include/engine/collision.h new file mode 100644 index 0000000..3ba3234 --- /dev/null +++ b/programs/include/engine/collision.h @@ -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 +#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 diff --git a/programs/include/engine/engine.h b/programs/include/engine/engine.h new file mode 100644 index 0000000..2af3a1e --- /dev/null +++ b/programs/include/engine/engine.h @@ -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 +#include +#include +#include +#include +#include + +extern "C" { +#include +} + +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 diff --git a/programs/include/engine/input.h b/programs/include/engine/input.h new file mode 100644 index 0000000..da6c515 --- /dev/null +++ b/programs/include/engine/input.h @@ -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 +#include +#include + +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 diff --git a/programs/include/engine/sprite.h b/programs/include/engine/sprite.h new file mode 100644 index 0000000..3f38a5f --- /dev/null +++ b/programs/include/engine/sprite.h @@ -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 +#include +#include +#include "engine/engine.h" + +extern "C" { +#include +} + +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 diff --git a/programs/include/engine/tilemap.h b/programs/include/engine/tilemap.h new file mode 100644 index 0000000..3df9a9f --- /dev/null +++ b/programs/include/engine/tilemap.h @@ -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 +#include +#include +#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 diff --git a/programs/include/engine/ui.h b/programs/include/engine/ui.h new file mode 100644 index 0000000..98028f2 --- /dev/null +++ b/programs/include/engine/ui.h @@ -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 +#include "engine/engine.h" + +extern "C" { +#include +} + +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 diff --git a/programs/src/rpgdemo/Makefile b/programs/src/rpgdemo/Makefile new file mode 100644 index 0000000..689ac01 --- /dev/null +++ b/programs/src/rpgdemo/Makefile @@ -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) diff --git a/programs/src/rpgdemo/main.cpp b/programs/src/rpgdemo/main.cpp new file mode 100644 index 0000000..c244042 --- /dev/null +++ b/programs/src/rpgdemo/main.cpp @@ -0,0 +1,1079 @@ +/* + * main.cpp + * MontaukOS RPG Demo - "Montauk Quest" + * A 2D fantasy RPG demonstrating the MontaukOS game engine + * Copyright (c) 2026 Daniel Hammer + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +} + +using namespace engine; +using namespace gui; + +// ============================================================================ +// Constants +// ============================================================================ + +static constexpr int WIN_W = 800; +static constexpr int WIN_H = 600; +static constexpr int SCALE = 2; // pixel art scale +static constexpr int TILE_SIZE = 16; // native tile size +static constexpr int MAP_W = 50; // map width in tiles +static constexpr int MAP_H = 40; // map height in tiles +static constexpr float PLAYER_SPEED = 80.0f; // pixels/sec (native scale) +static constexpr float ENEMY_SPEED = 30.0f; +static constexpr int PLAYER_HP = 100; +static constexpr int MAX_ENEMIES = 8; +static constexpr int MAX_DECORATIONS = 96; +static constexpr int MAX_CHESTS = 6; + +// Player sprite layout (32x32 frames, 6 cols x 10 rows) +static constexpr int SPR_W = 32; +static constexpr int SPR_H = 32; + +// Directions +enum Dir { DIR_DOWN = 0, DIR_RIGHT = 1, DIR_UP = 2, DIR_LEFT = 3 }; + +// ============================================================================ +// Entity types +// ============================================================================ + +struct Player { + float x, y; // world position (native pixels) + int health; + int max_health; + int direction; + bool moving; + int score; + float hit_timer; // invincibility after taking damage + AnimatedSprite sprite; + int anim_walk[4]; // walk anim indices per direction + int anim_idle[4]; // idle anim indices per direction +}; + +struct Enemy { + float x, y; + int health; + int max_health; + int direction; + bool active; + float patrol_timer; + float patrol_duration; + AnimatedSprite sprite; + int anim_walk[4]; + int type; // 0 = skeleton, 1 = slime +}; + +struct Decoration { + float x, y; + Spritesheet* sheet; + bool solid; + float col_x, col_y, col_w, col_h; // collision box offset + int frame_col, frame_row; // -1 = draw full sheet +}; + +struct Chest { + float x, y; + bool opened; + bool collected; +}; + +// ============================================================================ +// Game state +// ============================================================================ + +static Engine g_engine; +static InputState g_input; +static AudioEngine g_audio; +static Tilemap g_map; + +// Spritesheets +static Spritesheet g_spr_player; +static Spritesheet g_spr_skeleton; +static Spritesheet g_spr_slime; +static Spritesheet g_spr_grass; +static Spritesheet g_spr_path; +static Spritesheet g_spr_water; +static Spritesheet g_spr_tree; +static Spritesheet g_spr_tree_small; +static Spritesheet g_spr_house; +static Spritesheet g_spr_chest; +static Spritesheet g_spr_fences; +static Spritesheet g_spr_gravestone; + +// Entities +static Player g_player; +static Enemy g_enemies[MAX_ENEMIES]; +static int g_enemy_count = 0; +static Decoration g_decor[MAX_DECORATIONS]; +static int g_decor_count = 0; +static Chest g_chests[MAX_CHESTS]; +static int g_chest_count = 0; + +// Camera +static float g_cam_x = 0; +static float g_cam_y = 0; + +// UI state +static char g_prompt[128] = {}; +static char g_dialog_text[256] = {}; +static float g_dialog_timer = 0; + +// ============================================================================ +// Asset loading +// ============================================================================ + +static bool load_assets() { + const char* base = "0:/apps/rpgdemo/"; + char path[128]; + + auto make_path = [&](const char* file) { + snprintf(path, sizeof(path), "%s%s", base, file); + }; + + // Player (32x32 frames, 6 columns x 10 rows) + make_path("Player.png"); + if (!g_spr_player.load(path, SPR_W, SPR_H)) return false; + + // Skeleton (same layout as player) + make_path("Skeleton.png"); + if (!g_spr_skeleton.load(path, SPR_W, SPR_H)) return false; + + // Slime (64x64 frames) + make_path("Slime_Green.png"); + g_spr_slime.load(path, 64, 64); // not fatal if missing + + // Tiles (single 16x16 images) + make_path("Grass_Middle.png"); + if (!g_spr_grass.load(path, TILE_SIZE, TILE_SIZE)) return false; + + make_path("Path_Middle.png"); + if (!g_spr_path.load(path, TILE_SIZE, TILE_SIZE)) return false; + + make_path("Water_Middle.png"); + if (!g_spr_water.load(path, TILE_SIZE, TILE_SIZE)) return false; + + // Decorations + make_path("Oak_Tree.png"); + g_spr_tree.load(path); + + make_path("Oak_Tree_Small.png"); + g_spr_tree_small.load(path, 32, 48); + + make_path("House.png"); + g_spr_house.load(path); + + make_path("Chest.png"); + g_spr_chest.load(path, TILE_SIZE, TILE_SIZE); + + make_path("Fences.png"); + g_spr_fences.load(path, TILE_SIZE, TILE_SIZE); + + // Procedural gravestone sprite (16x16) + { + static const uint8_t bmp[16 * 16] = { + 0,0,0,0,0,0,2,2,2,2,0,0,0,0,0,0, + 0,0,0,0,0,2,1,1,1,1,2,0,0,0,0,0, + 0,0,0,0,2,1,1,1,1,1,1,2,0,0,0,0, + 0,0,0,0,2,1,1,1,1,1,1,2,0,0,0,0, + 0,0,0,0,2,1,1,2,1,1,1,2,0,0,0,0, + 0,0,0,0,2,1,2,2,2,1,1,2,0,0,0,0, + 0,0,0,0,2,1,1,2,1,1,1,2,0,0,0,0, + 0,0,0,0,2,1,1,2,1,1,1,2,0,0,0,0, + 0,0,0,0,2,1,1,1,1,1,1,2,0,0,0,0, + 0,0,0,0,2,1,1,1,1,1,1,2,0,0,0,0, + 0,0,0,0,2,1,1,1,1,1,1,2,0,0,0,0, + 0,0,0,0,2,1,1,1,1,1,1,2,0,0,0,0, + 0,0,0,0,2,2,2,2,2,2,2,2,0,0,0,0, + 0,0,0,3,3,3,3,3,3,3,3,3,3,0,0,0, + 0,0,0,0,0,3,3,3,3,3,3,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + }; + static const uint32_t pal[4] = { + 0x00000000, 0xFF999999, 0xFF555555, 0xFF665544, + }; + g_spr_gravestone.width = 16; + g_spr_gravestone.height = 16; + g_spr_gravestone.frame_w = 16; + g_spr_gravestone.frame_h = 16; + g_spr_gravestone.cols = 1; + g_spr_gravestone.rows = 1; + g_spr_gravestone.pixels = (uint32_t*)montauk::malloc(16 * 16 * 4); + if (g_spr_gravestone.pixels) { + for (int i = 0; i < 16 * 16; i++) + g_spr_gravestone.pixels[i] = pal[bmp[i]]; + } + } + + return true; +} + +// ============================================================================ +// Entity initialization +// ============================================================================ + +enum TileId { TILE_GRASS = 0, TILE_PATH = 1, TILE_WATER = 2 }; + +static void setup_player_anims(AnimatedSprite& spr, Spritesheet* sheet) { + spr.sheet = sheet; + for (int d = 0; d < 4; d++) + spr.add_anim(d, 0, 6, 10.0f); + for (int d = 0; d < 4; d++) + spr.add_anim(d, 0, 1, 1.0f); +} + +static void init_player() { + g_player.x = 24 * TILE_SIZE; + g_player.y = 22 * TILE_SIZE; + g_player.health = PLAYER_HP; + g_player.max_health = PLAYER_HP; + g_player.direction = DIR_DOWN; + g_player.moving = false; + g_player.score = 0; + g_player.hit_timer = 0; + + setup_player_anims(g_player.sprite, &g_spr_player); + for (int d = 0; d < 4; d++) { + g_player.anim_walk[d] = d; + g_player.anim_idle[d] = 4 + d; + } + g_player.sprite.play(g_player.anim_idle[DIR_DOWN]); +} + +static void add_enemy(float x, float y, int type) { + if (g_enemy_count >= MAX_ENEMIES) return; + Enemy& e = g_enemies[g_enemy_count++]; + e.x = x; + e.y = y; + e.health = 30; + e.max_health = 30; + e.direction = DIR_DOWN; + e.active = true; + e.patrol_timer = 0; + e.patrol_duration = 2.0f; + e.type = type; + + if (type == 0) { + setup_player_anims(e.sprite, &g_spr_skeleton); + for (int d = 0; d < 4; d++) + e.anim_walk[d] = d; + } else { + e.sprite.sheet = &g_spr_slime; + e.sprite.add_anim(0, 0, 4, 6.0f); + for (int d = 0; d < 4; d++) + e.anim_walk[d] = 0; + } + e.sprite.play(0); +} + +static void add_decoration(float x, float y, Spritesheet* sheet, + bool solid, float cx, float cy, float cw, float ch, + int fcol = -1, int frow = -1) { + if (g_decor_count >= MAX_DECORATIONS) return; + Decoration& d = g_decor[g_decor_count++]; + d.x = x; + d.y = y; + d.sheet = sheet; + d.solid = solid; + d.col_x = cx; + d.col_y = cy; + d.col_w = cw; + d.col_h = ch; + d.frame_col = fcol; + d.frame_row = frow; +} + +static void add_chest(float x, float y) { + if (g_chest_count >= MAX_CHESTS) return; + Chest& c = g_chests[g_chest_count++]; + c.x = x; + c.y = y; + c.opened = false; + c.collected = false; +} + +// ============================================================================ +// World Generation +// ============================================================================ +// +// Uses an occupancy grid to prevent overlapping placements. Each tile cell +// stores bit flags indicating what occupies it. All placement functions +// validate against the grid before committing. +// +// Generation order (later phases respect earlier placements): +// 1. Water bodies -> OCC_WATER +// 2. Path network -> OCC_PATH +// 3. Houses -> OCC_VISUAL + OCC_SOLID (base) +// 4. Fences -> OCC_SOLID +// 5. Trees -> OCC_VISUAL + OCC_SOLID (trunk) +// 6. Gravestones -> OCC_VISUAL + OCC_SOLID +// 7. Chests -> OCC_SOLID +// 8. Enemies -> OCC_SPAWN +// +// ============================================================================ + +// ---- Occupancy grid ---- + +enum OccFlag : uint16_t { + OCC_WATER = 1 << 0, + OCC_PATH = 1 << 1, + OCC_SOLID = 1 << 2, + OCC_VISUAL = 1 << 3, + OCC_SPAWN = 1 << 4, +}; + +static uint16_t g_occ[MAP_W * MAP_H]; + +static uint16_t occ_at(int tx, int ty) { + if (tx < 0 || tx >= MAP_W || ty < 0 || ty >= MAP_H) return 0xFFFF; + return g_occ[ty * MAP_W + tx]; +} + +static bool occ_free(int tx, int ty, int tw, int th, uint16_t mask) { + for (int y = ty; y < ty + th; y++) + for (int x = tx; x < tx + tw; x++) + if (occ_at(x, y) & mask) return false; + return true; +} + +static void occ_mark(int tx, int ty, int tw, int th, uint16_t flags) { + for (int y = ty; y < ty + th; y++) + for (int x = tx; x < tx + tw; x++) { + if (x >= 0 && x < MAP_W && y >= 0 && y < MAP_H) + g_occ[y * MAP_W + x] |= flags; + } +} + +// ---- Terrain helpers ---- + +static void set_tiles(int x1, int y1, int x2, int y2, int tile, uint16_t occ) { + for (int y = y1; y <= y2; y++) + for (int x = x1; x <= x2; x++) { + if (x >= 0 && x < MAP_W && y >= 0 && y < MAP_H) { + g_map.set(x, y, tile); + occ_mark(x, y, 1, 1, occ); + } + } +} + +// ---- Placement functions ---- + +// Tree: 64x80px = 4w x 5h tiles +// Trunk collision at pixel (16,48) size (32,28) -> tile offset (1,3) size 2x2 +static bool place_tree(int tx, int ty) { + if (!occ_free(tx, ty, 4, 5, OCC_VISUAL | OCC_SOLID | OCC_WATER)) + return false; + if (!occ_free(tx + 1, ty + 3, 2, 2, OCC_PATH)) + return false; + add_decoration((float)tx * 16, (float)ty * 16, + &g_spr_tree, true, 16, 48, 32, 28); + occ_mark(tx, ty, 4, 5, OCC_VISUAL); + occ_mark(tx + 1, ty + 3, 2, 2, OCC_SOLID); + return true; +} + +// House: 96x128px = 6w x 8h tiles +// Base collision at pixel (8,80) size (80,44) -> tile offset (0,5) size ~6x3 +// Visual may overlap paths (roof overhang) but base must not. +static bool place_house(int tx, int ty) { + if (!occ_free(tx, ty, 6, 8, OCC_VISUAL | OCC_SOLID | OCC_WATER)) + return false; + if (!occ_free(tx, ty + 5, 6, 3, OCC_PATH)) + return false; + add_decoration((float)tx * 16, (float)ty * 16, + &g_spr_house, true, 8, 80, 80, 44); + occ_mark(tx, ty, 6, 8, OCC_VISUAL); + occ_mark(tx, ty + 5, 6, 3, OCC_SOLID); + return true; +} + +// Gravestone: 16x16px = 1x1 tile +static bool place_gravestone(int tx, int ty) { + if (!occ_free(tx, ty, 1, 1, OCC_SOLID | OCC_VISUAL | OCC_WATER)) + return false; + add_decoration((float)tx * 16, (float)ty * 16, + &g_spr_gravestone, true, 2, 2, 12, 14); + occ_mark(tx, ty, 1, 1, OCC_SOLID | OCC_VISUAL); + return true; +} + +// Chest: 1x1 tile, must be on walkable ground +static bool place_chest_at(int tx, int ty) { + if (!occ_free(tx, ty, 1, 1, OCC_SOLID | OCC_VISUAL | OCC_WATER)) + return false; + add_chest((float)tx * 16, (float)ty * 16); + occ_mark(tx, ty, 1, 1, OCC_SOLID); + return true; +} + +// Enemy: 2x2 tile footprint, needs walkable ground +static bool place_enemy_at(int tx, int ty, int type) { + if (!occ_free(tx, ty, 2, 2, OCC_SOLID | OCC_WATER)) + return false; + add_enemy((float)tx * 16, (float)ty * 16, type); + occ_mark(tx, ty, 2, 2, OCC_SPAWN); + return true; +} + +// Fence runs - each tile validated, skipped if blocked +static void gen_fence_h(int tx, int ty, int length) { + for (int i = 0; i < length; i++) { + int x = tx + i; + if (occ_at(x, ty) & (OCC_SOLID | OCC_WATER | OCC_PATH | OCC_VISUAL)) + continue; + int col = (i == 0) ? 0 : (i == length - 1) ? 3 : ((i % 2 == 1) ? 1 : 2); + add_decoration((float)x * 16, (float)ty * 16, &g_spr_fences, + true, 0, 4, 16, 12, col, 0); + occ_mark(x, ty, 1, 1, OCC_SOLID); + } +} + +static void gen_fence_v(int tx, int ty, int length) { + for (int i = 0; i < length; i++) { + int y = ty + i; + if (occ_at(tx, y) & (OCC_SOLID | OCC_WATER | OCC_PATH | OCC_VISUAL)) + continue; + int row = (i == 0) ? 0 : (i == length - 1) ? 3 : ((i % 2 == 1) ? 1 : 2); + add_decoration((float)tx * 16, (float)y * 16, &g_spr_fences, + true, 4, 0, 12, 16, 0, row); + occ_mark(tx, y, 1, 1, OCC_SOLID); + } +} + +// ---- Main world generation ---- + +static void generate_world() { + // Clear occupancy grid + for (int i = 0; i < MAP_W * MAP_H; i++) + g_occ[i] = 0; + + // Initialize tilemap + g_map.alloc(MAP_W, MAP_H, TILE_SIZE); + g_map.add_type(&g_spr_grass, 0, 0, TILE_SIZE, TILE_SIZE, false); + g_map.add_type(&g_spr_path, 0, 0, TILE_SIZE, TILE_SIZE, false); + g_map.add_type(&g_spr_water, 0, 0, TILE_SIZE, TILE_SIZE, true); + for (int i = 0; i < MAP_W * MAP_H; i++) + g_map.data[i] = TILE_GRASS; + + // ==== Phase 1: Water bodies ==== + + // Lake (top-right, ellipse centered at tile 40,7) + for (int y = 2; y < 12; y++) { + for (int x = 34; x < 47; x++) { + float dx = (float)(x - 40) / 6.0f; + float dy = (float)(y - 7) / 4.5f; + if (dx * dx + dy * dy < 1.0f) { + g_map.set(x, y, TILE_WATER); + occ_mark(x, y, 1, 1, OCC_WATER); + } + } + } + + // Pond (west side) + for (int y = 14; y < 18; y++) { + for (int x = 8; x < 13; x++) { + float dx = (float)(x - 10) / 2.5f; + float dy = (float)(y - 16) / 1.8f; + if (dx * dx + dy * dy < 1.0f) { + g_map.set(x, y, TILE_WATER); + occ_mark(x, y, 1, 1, OCC_WATER); + } + } + } + + // ==== Phase 2: Path network ==== + + set_tiles(0, 19, MAP_W - 1, 20, TILE_PATH, OCC_PATH); // main E-W road + set_tiles(24, 0, 25, MAP_H - 1, TILE_PATH, OCC_PATH); // main N-S road + set_tiles(25, 8, 42, 8, TILE_PATH, OCC_PATH); // lake spur + set_tiles(15, 19, 16, 30, TILE_PATH, OCC_PATH); // south branch + set_tiles(35, 20, 36, 26, TILE_PATH, OCC_PATH); // graveyard access + set_tiles(32, 26, 40, 33, TILE_PATH, OCC_PATH); // graveyard floor + + // ==== Phase 3: Houses ==== + // Village north of crossroads. Houses are 6x8 tiles. + // Positioned so bases (at ty+5..ty+7) don't overlap paths. + + place_house(18, 4); // left house + place_house(27, 4); // right house (gap at N-S path cols 24-25) + + // ==== Phase 4: Village fences ==== + // South boundary with gate at N-S path (cols 24-25) + + gen_fence_h(18, 16, 6); // left of gate: tiles 18-23 + gen_fence_h(26, 16, 7); // right of gate: tiles 26-32 + gen_fence_v(18, 12, 5); // left side + gen_fence_v(32, 12, 5); // right side + + // ==== Phase 5: Trees ==== + + // Northwest forest (4x5 tile spacing to prevent overlap) + place_tree(0, 0); + place_tree(5, 0); + place_tree(10, 0); + place_tree(2, 5); + place_tree(7, 5); + place_tree(12, 5); + place_tree(0, 10); + + // South wilderness + place_tree(2, 26); + place_tree(8, 30); + place_tree(28, 26); + place_tree(43, 28); + place_tree(44, 23); + + // East side + place_tree(46, 13); + place_tree(42, 21); + + // ==== Phase 6: Gravestones ==== + // Two rows of three within graveyard zone (32-40, 26-33) + + place_gravestone(34, 28); + place_gravestone(36, 28); + place_gravestone(38, 28); + place_gravestone(34, 31); + place_gravestone(36, 31); + place_gravestone(38, 31); + + // ==== Phase 7: Chests ==== + + place_chest_at(4, 3); // forest clearing + place_chest_at(43, 12); // near lake + place_chest_at(1, 32); // southern wilderness + place_chest_at(46, 35); // far corner + place_chest_at(26, 14); // village yard + + // ==== Phase 8: Enemies ==== + + // Skeletons patrol the graveyard + place_enemy_at(34, 29, 0); + place_enemy_at(37, 29, 0); + place_enemy_at(35, 32, 0); + + // Slimes in the northwest forest + place_enemy_at(4, 6, 1); + place_enemy_at(10, 6, 1); + + // Skeleton guard near lake + place_enemy_at(38, 14, 0); +} + +// ============================================================================ +// Update +// ============================================================================ + +static void update_camera() { + // Center camera on player + float target_x = g_player.x - (float)(WIN_W / SCALE) / 2.0f + SPR_W / 2.0f; + float target_y = g_player.y - (float)(WIN_H / SCALE) / 2.0f + SPR_H / 2.0f; + + // Smooth follow + g_cam_x += (target_x - g_cam_x) * 6.0f * g_engine.dt; + g_cam_y += (target_y - g_cam_y) * 6.0f * g_engine.dt; + + // Clamp to map bounds + float max_x = (float)(MAP_W * TILE_SIZE) - (float)(WIN_W / SCALE); + float max_y = (float)(MAP_H * TILE_SIZE) - (float)(WIN_H / SCALE); + if (g_cam_x < 0) g_cam_x = 0; + if (g_cam_y < 0) g_cam_y = 0; + if (g_cam_x > max_x) g_cam_x = max_x; + if (g_cam_y > max_y) g_cam_y = max_y; +} + +static bool check_decoration_collision(float x, float y, float w, float h) { + for (int i = 0; i < g_decor_count; i++) { + Decoration& d = g_decor[i]; + if (!d.solid) continue; + AABB a = { x, y, w, h }; + AABB b = { d.x + d.col_x, d.y + d.col_y, d.col_w, d.col_h }; + if (a.overlaps(b)) return true; + } + return false; +} + +static void update_player(float dt) { + float dx = 0, dy = 0; + bool moving = false; + + if (g_input.key_held(key::W) || g_input.key_held(key::UP)) { dy = -1; g_player.direction = DIR_UP; moving = true; } + if (g_input.key_held(key::S) || g_input.key_held(key::DOWN)) { dy = 1; g_player.direction = DIR_DOWN; moving = true; } + if (g_input.key_held(key::A) || g_input.key_held(key::LEFT)) { dx = -1; g_player.direction = DIR_LEFT; moving = true; } + if (g_input.key_held(key::D) || g_input.key_held(key::RIGHT)) { dx = 1; g_player.direction = DIR_RIGHT; moving = true; } + + // Normalize diagonal movement + if (dx != 0 && dy != 0) { + dx *= 0.707f; + dy *= 0.707f; + } + + dx *= PLAYER_SPEED * dt; + dy *= PLAYER_SPEED * dt; + + // Collision box: lower portion of sprite (feet area) + float bx = g_player.x + 8; + float by = g_player.y + 20; + float bw = 16; + float bh = 10; + + // Tilemap collision + resolve_tilemap_collision(g_map, bx, by, bw, bh, dx, dy); + + // Decoration collision - X axis + if (dx != 0 && check_decoration_collision(bx + dx, by, bw, bh)) + dx = 0; + // Decoration collision - Y axis + if (dy != 0 && check_decoration_collision(bx + dx, by + dy, bw, bh)) + dy = 0; + + // World bounds + float new_x = g_player.x + dx; + float new_y = g_player.y + dy; + if (new_x < 0) new_x = 0; + if (new_y < 0) new_y = 0; + if (new_x > (MAP_W - 2) * TILE_SIZE) new_x = (float)((MAP_W - 2) * TILE_SIZE); + if (new_y > (MAP_H - 2) * TILE_SIZE) new_y = (float)((MAP_H - 2) * TILE_SIZE); + + g_player.x = new_x; + g_player.y = new_y; + g_player.moving = moving; + + // Animation + if (moving) { + g_player.sprite.play(g_player.anim_walk[g_player.direction]); + } else { + g_player.sprite.play(g_player.anim_idle[g_player.direction]); + } + g_player.sprite.update(dt); + + // Invincibility timer + if (g_player.hit_timer > 0) + g_player.hit_timer -= dt; +} + +static void update_enemies(float dt) { + for (int i = 0; i < g_enemy_count; i++) { + Enemy& e = g_enemies[i]; + if (!e.active) continue; + + // Simple patrol AI + e.patrol_timer += dt; + if (e.patrol_timer >= e.patrol_duration) { + e.patrol_timer = 0; + e.direction = (e.direction + 1) % 4; + // Randomize duration slightly using frame count + e.patrol_duration = 1.5f + (float)(g_engine.frame_count % 3) * 0.5f; + } + + float dx = 0, dy = 0; + switch (e.direction) { + case DIR_DOWN: dy = ENEMY_SPEED * dt; break; + case DIR_UP: dy = -ENEMY_SPEED * dt; break; + case DIR_RIGHT: dx = ENEMY_SPEED * dt; break; + case DIR_LEFT: dx = -ENEMY_SPEED * dt; break; + } + + // Tilemap collision + float bx = e.x + 8; + float by = e.y + 20; + float bw = 16; + float bh = 10; + resolve_tilemap_collision(g_map, bx, by, bw, bh, dx, dy); + + // Reverse direction if blocked + if (dx == 0 && dy == 0) { + e.direction = (e.direction + 2) % 4; + e.patrol_timer = 0; + } + + // World bounds + float new_x = e.x + dx; + float new_y = e.y + dy; + if (new_x < 0 || new_x > (MAP_W - 2) * TILE_SIZE) { + e.direction = (e.direction + 2) % 4; + e.patrol_timer = 0; + new_x = e.x; + } + if (new_y < 0 || new_y > (MAP_H - 2) * TILE_SIZE) { + e.direction = (e.direction + 2) % 4; + e.patrol_timer = 0; + new_y = e.y; + } + + e.x = new_x; + e.y = new_y; + + // Animation + if (e.type == 0) { + e.sprite.play(e.anim_walk[e.direction]); + } + e.sprite.update(dt); + + // Damage player on contact + AABB pa = { g_player.x + 4, g_player.y + 8, 24, 22 }; + AABB ea = { e.x + 4, e.y + 8, 24, 22 }; + if (pa.overlaps(ea) && g_player.hit_timer <= 0) { + g_player.health -= 10; + g_player.hit_timer = 1.0f; // 1 second invincibility + if (g_player.health < 0) g_player.health = 0; + + // Knockback with collision checking + float kx = g_player.x - e.x; + float ky = g_player.y - e.y; + float len = 1.0f; + if (kx != 0 || ky != 0) { + float ax = kx < 0 ? -kx : kx; + float ay = ky < 0 ? -ky : ky; + len = ax > ay ? ax + ay * 0.4f : ay + ax * 0.4f; + } + float kb_dx = (kx / len) * 16.0f; + float kb_dy = (ky / len) * 16.0f; + + // Check collision before applying knockback + float bx = g_player.x + 8; + float by = g_player.y + 20; + resolve_tilemap_collision(g_map, bx, by, 16, 10, kb_dx, kb_dy); + float new_px = g_player.x + kb_dx; + float new_py = g_player.y + kb_dy; + if (new_px < 0) new_px = 0; + if (new_py < 0) new_py = 0; + if (new_px > (MAP_W - 2) * TILE_SIZE) new_px = (float)((MAP_W - 2) * TILE_SIZE); + if (new_py > (MAP_H - 2) * TILE_SIZE) new_py = (float)((MAP_H - 2) * TILE_SIZE); + g_player.x = new_px; + g_player.y = new_py; + + // Sound effect + g_audio.play_tone(200, 80, 30); + } + } +} + +static void update_interactions() { + g_prompt[0] = '\0'; + + // Check chest proximity + for (int i = 0; i < g_chest_count; i++) { + Chest& c = g_chests[i]; + if (c.collected) continue; + + float dx = g_player.x + 16 - (c.x + 8); + float dy = g_player.y + 16 - (c.y + 8); + float dist = dx * dx + dy * dy; + + if (dist < 40 * 40) { + if (!c.opened) { + snprintf(g_prompt, sizeof(g_prompt), "Press E to open chest"); + if (g_input.key_just_pressed(key::E)) { + c.opened = true; + c.collected = true; + g_player.score += 100; + snprintf(g_dialog_text, sizeof(g_dialog_text), + "You found treasure! +100 points"); + g_dialog_timer = 2.0f; + g_audio.play_tone(523, 100, 25); // C5 + g_audio.play_tone(659, 100, 25); // E5 + g_audio.play_tone(784, 150, 25); // G5 + } + } + } + } + + // Dialog timer + if (g_dialog_timer > 0) { + g_dialog_timer -= g_engine.dt; + if (g_dialog_timer <= 0) g_dialog_text[0] = '\0'; + } +} + +static void update(float dt) { + if (g_player.health <= 0) { + // Game over - respawn after pressing space + if (g_input.key_just_pressed(key::SPACE)) { + g_player.health = PLAYER_HP; + g_player.x = 24 * TILE_SIZE; + g_player.y = 22 * TILE_SIZE; + } + return; + } + + update_player(dt); + update_enemies(dt); + update_interactions(); + update_camera(); +} + +// ============================================================================ +// Rendering +// ============================================================================ + +// Y-sort helper for draw ordering +struct DrawEntry { + float y; + int type; // 0=decoration, 1=player, 2=enemy, 3=chest + int index; +}; + +static DrawEntry g_draw_list[MAX_DECORATIONS + MAX_ENEMIES + MAX_CHESTS + 1]; +static int g_draw_count = 0; + +static void sort_draw_list() { + // Simple insertion sort (small N) + for (int i = 1; i < g_draw_count; i++) { + DrawEntry tmp = g_draw_list[i]; + int j = i - 1; + while (j >= 0 && g_draw_list[j].y > tmp.y) { + g_draw_list[j + 1] = g_draw_list[j]; + j--; + } + g_draw_list[j + 1] = tmp; + } +} + +static void render() { + int cam_x = (int)g_cam_x; + int cam_y = (int)g_cam_y; + + // Draw tilemap + g_map.draw(g_engine.pixels, g_engine.screen_w, g_engine.screen_h, + cam_x, cam_y, SCALE); + + // Build Y-sorted draw list for entities and decorations + g_draw_count = 0; + + for (int i = 0; i < g_decor_count; i++) { + Decoration& dc = g_decor[i]; + float h = 0; + if (dc.sheet) { + h = (dc.frame_col >= 0) ? (float)dc.sheet->frame_h + : (float)dc.sheet->height; + } + g_draw_list[g_draw_count++] = { dc.y + h, 0, i }; + } + + for (int i = 0; i < g_chest_count; i++) { + g_draw_list[g_draw_count++] = { g_chests[i].y + TILE_SIZE, 3, i }; + } + + g_draw_list[g_draw_count++] = { g_player.y + SPR_H, 1, 0 }; + + for (int i = 0; i < g_enemy_count; i++) { + if (!g_enemies[i].active) continue; + float bottom = g_enemies[i].y + (g_enemies[i].type == 1 ? 64 : SPR_H); + g_draw_list[g_draw_count++] = { bottom, 2, i }; + } + + sort_draw_list(); + + // Render sorted entities + for (int i = 0; i < g_draw_count; i++) { + DrawEntry& de = g_draw_list[i]; + + if (de.type == 0) { + // Decoration + Decoration& d = g_decor[de.index]; + if (d.sheet && d.sheet->pixels) { + int sx = (int)(d.x * SCALE) - cam_x * SCALE; + int sy = (int)(d.y * SCALE) - cam_y * SCALE; + if (d.frame_col >= 0 && d.frame_row >= 0) { + d.sheet->draw_frame(g_engine.pixels, g_engine.screen_w, + g_engine.screen_h, d.frame_col, + d.frame_row, sx, sy, SCALE); + } else { + d.sheet->draw_region(g_engine.pixels, g_engine.screen_w, + g_engine.screen_h, 0, 0, + d.sheet->width, d.sheet->height, + sx, sy, SCALE); + } + } + } else if (de.type == 1) { + // Player + int px = (int)(g_player.x * SCALE) - cam_x * SCALE; + int py = (int)(g_player.y * SCALE) - cam_y * SCALE; + + // Flash when invincible + bool visible = true; + if (g_player.hit_timer > 0) { + visible = ((int)(g_player.hit_timer * 10) % 2 == 0); + } + + if (visible) { + g_player.sprite.draw(g_engine.pixels, g_engine.screen_w, + g_engine.screen_h, px, py, SCALE); + } + } else if (de.type == 2) { + // Enemy + Enemy& e = g_enemies[de.index]; + int ex = (int)(e.x * SCALE) - cam_x * SCALE; + int ey = (int)(e.y * SCALE) - cam_y * SCALE; + e.sprite.draw(g_engine.pixels, g_engine.screen_w, + g_engine.screen_h, ex, ey, SCALE); + + // Health bar above enemy + if (e.health < e.max_health) { + draw_bar(g_engine, ex + 4, ey - 8, 56, 6, + e.health, e.max_health, + 0xFFCC3333, 0xFF333333, 0xFF000000); + } + } else if (de.type == 3) { + // Chest + Chest& c = g_chests[de.index]; + int cx = (int)(c.x * SCALE) - cam_x * SCALE; + int cy = (int)(c.y * SCALE) - cam_y * SCALE; + + if (!c.collected && g_spr_chest.pixels) { + // Draw chest (frame 0 = closed, use tinting for opened) + g_spr_chest.draw_frame(g_engine.pixels, g_engine.screen_w, + g_engine.screen_h, 0, 0, + cx, cy, SCALE); + if (c.opened) { + // Darken opened chests slightly + g_engine.fill_rect_alpha(cx, cy, TILE_SIZE * SCALE, + TILE_SIZE * SCALE, 0x40000000); + } + } + } + } + + // ---- HUD ---- + + // Health bar + draw_bar(g_engine, 8, 8, 160, 20, + g_player.health, g_player.max_health, + 0xFFCC3333, 0xFF444444, 0xFF000000); + + char hp_text[32]; + snprintf(hp_text, sizeof(hp_text), "HP: %d/%d", g_player.health, g_player.max_health); + draw_hud_text(g_engine, 12, 9, hp_text, + Color::from_rgb(0xFF, 0xFF, 0xFF), 14); + + // Score + char score_text[32]; + snprintf(score_text, sizeof(score_text), "Score: %d", g_player.score); + draw_hud_text(g_engine, 8, 34, score_text, + Color::from_rgb(0xFF, 0xDD, 0x44), 14); + + // FPS counter + if (g_engine.dt > 0) { + char fps_text[16]; + int fps = (int)(1.0f / g_engine.dt); + snprintf(fps_text, sizeof(fps_text), "%d FPS", fps); + int fw = g_engine.text_width(fps_text, 12); + draw_hud_text(g_engine, g_engine.screen_w - fw - 8, 8, fps_text, + Color::from_rgb(0xAA, 0xAA, 0xAA), 12); + } + + // Interaction prompt + if (g_prompt[0]) { + draw_prompt(g_engine, g_prompt, 14); + } + + // Dialog text + if (g_dialog_text[0] && g_dialog_timer > 0) { + int dw = 300; + int dh = 50; + int dx = (g_engine.screen_w - dw) / 2; + int dy = g_engine.screen_h - 100; + draw_dialog(g_engine, dx, dy, dw, dh, g_dialog_text, + Color::from_rgb(0x33, 0x33, 0x33), 14); + } + + // Game over screen + if (g_player.health <= 0) { + g_engine.fill_rect_alpha(0, 0, g_engine.screen_w, g_engine.screen_h, + 0xAA000000); + const char* go_text = "Game Over"; + int tw = g_engine.text_width(go_text, 32); + g_engine.draw_text((g_engine.screen_w - tw) / 2, + g_engine.screen_h / 2 - 30, + go_text, Color::from_rgb(0xFF, 0x44, 0x44), 32); + + const char* restart_text = "Press SPACE to respawn"; + tw = g_engine.text_width(restart_text, 16); + g_engine.draw_text((g_engine.screen_w - tw) / 2, + g_engine.screen_h / 2 + 10, + restart_text, Color::from_rgb(0xCC, 0xCC, 0xCC), 16); + + char final_score[64]; + snprintf(final_score, sizeof(final_score), "Final Score: %d", g_player.score); + tw = g_engine.text_width(final_score, 18); + g_engine.draw_text((g_engine.screen_w - tw) / 2, + g_engine.screen_h / 2 + 40, + final_score, Color::from_rgb(0xFF, 0xDD, 0x44), 18); + } +} + +// ============================================================================ +// Entry point +// ============================================================================ + +extern "C" void _start() { + // Initialize engine + if (!g_engine.init("Montauk Quest", WIN_W, WIN_H)) { + montauk::print("Failed to create game window\n"); + montauk::exit(1); + } + + g_input.init(); + + // Initialize audio (non-fatal if it fails) + g_audio.init(); + + // Load assets + if (!load_assets()) { + montauk::print("Failed to load game assets\n"); + g_engine.shutdown(); + montauk::exit(1); + } + + // Initialize game world + generate_world(); + init_player(); + + // Main game loop + while (g_engine.running) { + g_engine.update_timing(); + g_input.begin_frame(); + + // Process all pending events + Montauk::WinEvent ev; + while (g_engine.poll(&ev)) { + g_input.handle_event(ev); + + // Escape to quit + if (ev.type == 0 && ev.key.pressed && ev.key.scancode == key::ESC) + g_engine.running = false; + } + + // Update + update(g_engine.dt); + + // Render + render(); + g_engine.present(); + + // Yield to avoid burning CPU when there's no input + montauk::sleep_ms(1); + } + + g_audio.shutdown(); + g_engine.shutdown(); + montauk::exit(0); +} diff --git a/programs/src/rpgdemo/manifest.toml b/programs/src/rpgdemo/manifest.toml new file mode 100644 index 0000000..69519f7 --- /dev/null +++ b/programs/src/rpgdemo/manifest.toml @@ -0,0 +1,8 @@ +[app] +name = "Montauk Quest" +binary = "rpgdemo.elf" +icon = "icon.svg" + +[menu] +category = "Games" +visible = true diff --git a/programs/src/rpgdemo/stb_impl.cpp b/programs/src/rpgdemo/stb_impl.cpp new file mode 100644 index 0000000..1d30d13 --- /dev/null +++ b/programs/src/rpgdemo/stb_impl.cpp @@ -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 +#include +#include +#include +#include + +extern "C" { +#include +#include +} + +// ============================================================================ +// 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 + +// ============================================================================ +// 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 diff --git a/scripts/install_apps.sh b/scripts/install_apps.sh index f6222b9..b896026 100755 --- a/scripts/install_apps.sh +++ b/scripts/install_apps.sh @@ -29,6 +29,7 @@ APPS=( "music|apps/scalable/audio-player.svg" "video|apps/scalable/multimedia-video-player.svg" "bluetooth|apps/scalable/bluetooth.svg" + "rpgdemo|apps/scalable/utilities-terminal.svg" ) installed=0