diff --git a/assets/breakout/sounds/sndBreak.mid b/assets/breakout/sounds/sndBreak.mid new file mode 100644 index 000000000..02e71c09b Binary files /dev/null and b/assets/breakout/sounds/sndBreak.mid differ diff --git a/assets/fonts/radiostars.font.png b/assets/fonts/radiostars.font.png new file mode 100644 index 000000000..73bbf7301 Binary files /dev/null and b/assets/fonts/radiostars.font.png differ diff --git a/assets/platformer/levels/dac01.bin b/assets/platformer/levels/dac01.bin new file mode 100644 index 000000000..d23523ec2 Binary files /dev/null and b/assets/platformer/levels/dac01.bin differ diff --git a/assets/platformer/levels/dac02.bin b/assets/platformer/levels/dac02.bin new file mode 100644 index 000000000..bc57084b4 Binary files /dev/null and b/assets/platformer/levels/dac02.bin differ diff --git a/assets/platformer/levels/dac03.bin b/assets/platformer/levels/dac03.bin new file mode 100644 index 000000000..29d8a3ed3 Binary files /dev/null and b/assets/platformer/levels/dac03.bin differ diff --git a/assets/platformer/levels/debug.bin b/assets/platformer/levels/debug.bin new file mode 100644 index 000000000..a04039a4e Binary files /dev/null and b/assets/platformer/levels/debug.bin differ diff --git a/assets/platformer/levels/level1-1.bin b/assets/platformer/levels/level1-1.bin new file mode 100644 index 000000000..43d95cef2 Binary files /dev/null and b/assets/platformer/levels/level1-1.bin differ diff --git a/assets/platformer/levels/level1-3.bin b/assets/platformer/levels/level1-3.bin new file mode 100644 index 000000000..069b5d6f0 Binary files /dev/null and b/assets/platformer/levels/level1-3.bin differ diff --git a/assets/platformer/levels/level1-4.bin b/assets/platformer/levels/level1-4.bin new file mode 100644 index 000000000..f4ae136db Binary files /dev/null and b/assets/platformer/levels/level1-4.bin differ diff --git a/assets/platformer/levels/level2-1.bin b/assets/platformer/levels/level2-1.bin new file mode 100644 index 000000000..d90b98de6 Binary files /dev/null and b/assets/platformer/levels/level2-1.bin differ diff --git a/assets/platformer/levels/level2-3.bin b/assets/platformer/levels/level2-3.bin new file mode 100644 index 000000000..ca83bdec1 Binary files /dev/null and b/assets/platformer/levels/level2-3.bin differ diff --git a/assets/platformer/levels/level2-4.bin b/assets/platformer/levels/level2-4.bin new file mode 100644 index 000000000..ed5202a66 Binary files /dev/null and b/assets/platformer/levels/level2-4.bin differ diff --git a/assets/platformer/levels/level3-1.bin b/assets/platformer/levels/level3-1.bin new file mode 100644 index 000000000..90bfc659b Binary files /dev/null and b/assets/platformer/levels/level3-1.bin differ diff --git a/assets/platformer/levels/level3-2.bin b/assets/platformer/levels/level3-2.bin new file mode 100644 index 000000000..09f2f821d Binary files /dev/null and b/assets/platformer/levels/level3-2.bin differ diff --git a/assets/platformer/levels/level3-3.bin b/assets/platformer/levels/level3-3.bin new file mode 100644 index 000000000..8a3a4487c Binary files /dev/null and b/assets/platformer/levels/level3-3.bin differ diff --git a/assets/platformer/levels/level3-4.bin b/assets/platformer/levels/level3-4.bin new file mode 100644 index 000000000..5a3fc953b Binary files /dev/null and b/assets/platformer/levels/level3-4.bin differ diff --git a/assets/platformer/levels/level4-1.bin b/assets/platformer/levels/level4-1.bin new file mode 100644 index 000000000..092c42a6a Binary files /dev/null and b/assets/platformer/levels/level4-1.bin differ diff --git a/assets/platformer/levels/level4-2.bin b/assets/platformer/levels/level4-2.bin new file mode 100644 index 000000000..ea92511a3 Binary files /dev/null and b/assets/platformer/levels/level4-2.bin differ diff --git a/assets/platformer/levels/level4-3.bin b/assets/platformer/levels/level4-3.bin new file mode 100644 index 000000000..ff91c7abf Binary files /dev/null and b/assets/platformer/levels/level4-3.bin differ diff --git a/assets/platformer/levels/level4-4.bin b/assets/platformer/levels/level4-4.bin new file mode 100644 index 000000000..bf1353515 Binary files /dev/null and b/assets/platformer/levels/level4-4.bin differ diff --git a/assets/platformer/sounds/bgmCastle.mid b/assets/platformer/sounds/bgmCastle.mid new file mode 100644 index 000000000..953c02d0c Binary files /dev/null and b/assets/platformer/sounds/bgmCastle.mid differ diff --git a/assets/platformer/sounds/bgmDeMAGio.mid b/assets/platformer/sounds/bgmDeMAGio.mid new file mode 100644 index 000000000..e50939584 Binary files /dev/null and b/assets/platformer/sounds/bgmDeMAGio.mid differ diff --git a/assets/platformer/sounds/bgmGameOver.mid b/assets/platformer/sounds/bgmGameOver.mid new file mode 100644 index 000000000..ed84b35d3 Binary files /dev/null and b/assets/platformer/sounds/bgmGameOver.mid differ diff --git a/assets/platformer/sounds/bgmGameStart.mid b/assets/platformer/sounds/bgmGameStart.mid new file mode 100644 index 000000000..e4018c766 Binary files /dev/null and b/assets/platformer/sounds/bgmGameStart.mid differ diff --git a/assets/platformer/sounds/bgmIntro.mid b/assets/platformer/sounds/bgmIntro.mid new file mode 100644 index 000000000..8c79b83c1 Binary files /dev/null and b/assets/platformer/sounds/bgmIntro.mid differ diff --git a/assets/platformer/sounds/bgmNameEntry.mid b/assets/platformer/sounds/bgmNameEntry.mid new file mode 100644 index 000000000..817d7e476 Binary files /dev/null and b/assets/platformer/sounds/bgmNameEntry.mid differ diff --git a/assets/platformer/sounds/bgmSmooth.mid b/assets/platformer/sounds/bgmSmooth.mid new file mode 100644 index 000000000..a56157d7e Binary files /dev/null and b/assets/platformer/sounds/bgmSmooth.mid differ diff --git a/assets/platformer/sounds/bgmUnderground.mid b/assets/platformer/sounds/bgmUnderground.mid new file mode 100644 index 000000000..92aa5f273 Binary files /dev/null and b/assets/platformer/sounds/bgmUnderground.mid differ diff --git a/assets/platformer/sounds/snd1up.mid b/assets/platformer/sounds/snd1up.mid new file mode 100644 index 000000000..e3b994f42 Binary files /dev/null and b/assets/platformer/sounds/snd1up.mid differ diff --git a/assets/platformer/sounds/sndCheckpoint.mid b/assets/platformer/sounds/sndCheckpoint.mid new file mode 100644 index 000000000..866cec141 Binary files /dev/null and b/assets/platformer/sounds/sndCheckpoint.mid differ diff --git a/assets/platformer/sounds/sndCoin.mid b/assets/platformer/sounds/sndCoin.mid new file mode 100644 index 000000000..10fb39a7d Binary files /dev/null and b/assets/platformer/sounds/sndCoin.mid differ diff --git a/assets/platformer/sounds/sndDie.mid b/assets/platformer/sounds/sndDie.mid new file mode 100644 index 000000000..dba1e7513 Binary files /dev/null and b/assets/platformer/sounds/sndDie.mid differ diff --git a/assets/platformer/sounds/sndHit.mid b/assets/platformer/sounds/sndHit.mid new file mode 100644 index 000000000..2be02c48a Binary files /dev/null and b/assets/platformer/sounds/sndHit.mid differ diff --git a/assets/platformer/sounds/sndHurt.mid b/assets/platformer/sounds/sndHurt.mid new file mode 100644 index 000000000..530006503 Binary files /dev/null and b/assets/platformer/sounds/sndHurt.mid differ diff --git a/assets/platformer/sounds/sndJump1.mid b/assets/platformer/sounds/sndJump1.mid new file mode 100644 index 000000000..3f3bb4e42 Binary files /dev/null and b/assets/platformer/sounds/sndJump1.mid differ diff --git a/assets/platformer/sounds/sndJump2.mid b/assets/platformer/sounds/sndJump2.mid new file mode 100644 index 000000000..b523d077b Binary files /dev/null and b/assets/platformer/sounds/sndJump2.mid differ diff --git a/assets/platformer/sounds/sndJump3.mid b/assets/platformer/sounds/sndJump3.mid new file mode 100644 index 000000000..941b35664 Binary files /dev/null and b/assets/platformer/sounds/sndJump3.mid differ diff --git a/assets/platformer/sounds/sndLevelClearA.mid b/assets/platformer/sounds/sndLevelClearA.mid new file mode 100644 index 000000000..5faca34ed Binary files /dev/null and b/assets/platformer/sounds/sndLevelClearA.mid differ diff --git a/assets/platformer/sounds/sndLevelClearB.mid b/assets/platformer/sounds/sndLevelClearB.mid new file mode 100644 index 000000000..f56147fbe Binary files /dev/null and b/assets/platformer/sounds/sndLevelClearB.mid differ diff --git a/assets/platformer/sounds/sndLevelClearC.mid b/assets/platformer/sounds/sndLevelClearC.mid new file mode 100644 index 000000000..887708f95 Binary files /dev/null and b/assets/platformer/sounds/sndLevelClearC.mid differ diff --git a/assets/platformer/sounds/sndLevelClearD.mid b/assets/platformer/sounds/sndLevelClearD.mid new file mode 100644 index 000000000..b400807ba Binary files /dev/null and b/assets/platformer/sounds/sndLevelClearD.mid differ diff --git a/assets/platformer/sounds/sndLevelClearS.mid b/assets/platformer/sounds/sndLevelClearS.mid new file mode 100644 index 000000000..50b1a27ea Binary files /dev/null and b/assets/platformer/sounds/sndLevelClearS.mid differ diff --git a/assets/platformer/sounds/sndMenuConfirm.mid b/assets/platformer/sounds/sndMenuConfirm.mid new file mode 100644 index 000000000..956d9460f Binary files /dev/null and b/assets/platformer/sounds/sndMenuConfirm.mid differ diff --git a/assets/platformer/sounds/sndMenuDeny.mid b/assets/platformer/sounds/sndMenuDeny.mid new file mode 100644 index 000000000..586cc825a Binary files /dev/null and b/assets/platformer/sounds/sndMenuDeny.mid differ diff --git a/assets/platformer/sounds/sndMenuSelect.mid b/assets/platformer/sounds/sndMenuSelect.mid new file mode 100644 index 000000000..2be02c48a Binary files /dev/null and b/assets/platformer/sounds/sndMenuSelect.mid differ diff --git a/assets/platformer/sounds/sndOuttatime.mid b/assets/platformer/sounds/sndOuttatime.mid new file mode 100644 index 000000000..9ee1f7a27 Binary files /dev/null and b/assets/platformer/sounds/sndOuttatime.mid differ diff --git a/assets/platformer/sounds/sndPause.mid b/assets/platformer/sounds/sndPause.mid new file mode 100644 index 000000000..5712c1e37 Binary files /dev/null and b/assets/platformer/sounds/sndPause.mid differ diff --git a/assets/platformer/sounds/sndPowerUp.mid b/assets/platformer/sounds/sndPowerUp.mid new file mode 100644 index 000000000..4763e03df Binary files /dev/null and b/assets/platformer/sounds/sndPowerUp.mid differ diff --git a/assets/platformer/sounds/sndSquish.mid b/assets/platformer/sounds/sndSquish.mid new file mode 100644 index 000000000..09c091044 Binary files /dev/null and b/assets/platformer/sounds/sndSquish.mid differ diff --git a/assets/platformer/sounds/sndWarp.mid b/assets/platformer/sounds/sndWarp.mid new file mode 100644 index 000000000..f4a5e6e66 Binary files /dev/null and b/assets/platformer/sounds/sndWarp.mid differ diff --git a/assets/platformer/sounds/sndWaveBall.mid b/assets/platformer/sounds/sndWaveBall.mid new file mode 100644 index 000000000..f04eb8b26 Binary files /dev/null and b/assets/platformer/sounds/sndWaveBall.mid differ diff --git a/assets/platformer/sprites/sprite000.png b/assets/platformer/sprites/sprite000.png new file mode 100644 index 000000000..f479dbbcd Binary files /dev/null and b/assets/platformer/sprites/sprite000.png differ diff --git a/assets/platformer/sprites/sprite001.png b/assets/platformer/sprites/sprite001.png new file mode 100644 index 000000000..c9f1801d0 Binary files /dev/null and b/assets/platformer/sprites/sprite001.png differ diff --git a/assets/platformer/sprites/sprite002.png b/assets/platformer/sprites/sprite002.png new file mode 100644 index 000000000..3463f5592 Binary files /dev/null and b/assets/platformer/sprites/sprite002.png differ diff --git a/assets/platformer/sprites/sprite003.png b/assets/platformer/sprites/sprite003.png new file mode 100644 index 000000000..7bab794de Binary files /dev/null and b/assets/platformer/sprites/sprite003.png differ diff --git a/assets/platformer/sprites/sprite004.png b/assets/platformer/sprites/sprite004.png new file mode 100644 index 000000000..365f7aaad Binary files /dev/null and b/assets/platformer/sprites/sprite004.png differ diff --git a/assets/platformer/sprites/sprite005.png b/assets/platformer/sprites/sprite005.png new file mode 100644 index 000000000..5f1d0f3b5 Binary files /dev/null and b/assets/platformer/sprites/sprite005.png differ diff --git a/assets/platformer/sprites/sprite006.png b/assets/platformer/sprites/sprite006.png new file mode 100644 index 000000000..03322d64e Binary files /dev/null and b/assets/platformer/sprites/sprite006.png differ diff --git a/assets/platformer/sprites/sprite007.png b/assets/platformer/sprites/sprite007.png new file mode 100644 index 000000000..1b8860153 Binary files /dev/null and b/assets/platformer/sprites/sprite007.png differ diff --git a/assets/platformer/sprites/sprite008.png b/assets/platformer/sprites/sprite008.png new file mode 100644 index 000000000..95c0e9244 Binary files /dev/null and b/assets/platformer/sprites/sprite008.png differ diff --git a/assets/platformer/sprites/sprite009.png b/assets/platformer/sprites/sprite009.png new file mode 100644 index 000000000..d0270e0b1 Binary files /dev/null and b/assets/platformer/sprites/sprite009.png differ diff --git a/assets/platformer/sprites/sprite012.png b/assets/platformer/sprites/sprite012.png new file mode 100644 index 000000000..7620e2f90 Binary files /dev/null and b/assets/platformer/sprites/sprite012.png differ diff --git a/assets/platformer/sprites/sprite013.png b/assets/platformer/sprites/sprite013.png new file mode 100644 index 000000000..34b309852 Binary files /dev/null and b/assets/platformer/sprites/sprite013.png differ diff --git a/assets/platformer/sprites/sprite014.png b/assets/platformer/sprites/sprite014.png new file mode 100644 index 000000000..0caf4c0df Binary files /dev/null and b/assets/platformer/sprites/sprite014.png differ diff --git a/assets/platformer/sprites/sprite015.png b/assets/platformer/sprites/sprite015.png new file mode 100644 index 000000000..b7acd93cf Binary files /dev/null and b/assets/platformer/sprites/sprite015.png differ diff --git a/assets/platformer/sprites/sprite016.png b/assets/platformer/sprites/sprite016.png new file mode 100644 index 000000000..1d9f274c8 Binary files /dev/null and b/assets/platformer/sprites/sprite016.png differ diff --git a/assets/platformer/sprites/sprite017.png b/assets/platformer/sprites/sprite017.png new file mode 100644 index 000000000..c1bf2af22 Binary files /dev/null and b/assets/platformer/sprites/sprite017.png differ diff --git a/assets/platformer/sprites/sprite018.png b/assets/platformer/sprites/sprite018.png new file mode 100644 index 000000000..1bef33e7d Binary files /dev/null and b/assets/platformer/sprites/sprite018.png differ diff --git a/assets/platformer/sprites/sprite019.png b/assets/platformer/sprites/sprite019.png new file mode 100644 index 000000000..a81e1158e Binary files /dev/null and b/assets/platformer/sprites/sprite019.png differ diff --git a/assets/platformer/sprites/sprite020.png b/assets/platformer/sprites/sprite020.png new file mode 100644 index 000000000..16c9e7a6e Binary files /dev/null and b/assets/platformer/sprites/sprite020.png differ diff --git a/assets/platformer/sprites/sprite021.png b/assets/platformer/sprites/sprite021.png new file mode 100644 index 000000000..331e55c40 Binary files /dev/null and b/assets/platformer/sprites/sprite021.png differ diff --git a/assets/platformer/sprites/sprite022.png b/assets/platformer/sprites/sprite022.png new file mode 100644 index 000000000..8be1d4e63 Binary files /dev/null and b/assets/platformer/sprites/sprite022.png differ diff --git a/assets/platformer/sprites/sprite023.png b/assets/platformer/sprites/sprite023.png new file mode 100644 index 000000000..af71d40b4 Binary files /dev/null and b/assets/platformer/sprites/sprite023.png differ diff --git a/assets/platformer/sprites/sprite024.png b/assets/platformer/sprites/sprite024.png new file mode 100644 index 000000000..2814eb54c Binary files /dev/null and b/assets/platformer/sprites/sprite024.png differ diff --git a/assets/platformer/sprites/sprite025.png b/assets/platformer/sprites/sprite025.png new file mode 100644 index 000000000..baa3f3d9b Binary files /dev/null and b/assets/platformer/sprites/sprite025.png differ diff --git a/assets/platformer/sprites/sprite026.png b/assets/platformer/sprites/sprite026.png new file mode 100644 index 000000000..e2ceb9733 Binary files /dev/null and b/assets/platformer/sprites/sprite026.png differ diff --git a/assets/platformer/sprites/sprite027.png b/assets/platformer/sprites/sprite027.png new file mode 100644 index 000000000..235b7a84c Binary files /dev/null and b/assets/platformer/sprites/sprite027.png differ diff --git a/assets/platformer/sprites/sprite028.png b/assets/platformer/sprites/sprite028.png new file mode 100644 index 000000000..cf2c1ee2f Binary files /dev/null and b/assets/platformer/sprites/sprite028.png differ diff --git a/assets/platformer/sprites/sprite029.png b/assets/platformer/sprites/sprite029.png new file mode 100644 index 000000000..93231230b Binary files /dev/null and b/assets/platformer/sprites/sprite029.png differ diff --git a/assets/platformer/sprites/sprite030.png b/assets/platformer/sprites/sprite030.png new file mode 100644 index 000000000..18b7c4d29 Binary files /dev/null and b/assets/platformer/sprites/sprite030.png differ diff --git a/assets/platformer/sprites/sprite031.png b/assets/platformer/sprites/sprite031.png new file mode 100644 index 000000000..284bbfb8d Binary files /dev/null and b/assets/platformer/sprites/sprite031.png differ diff --git a/assets/platformer/sprites/sprite032.png b/assets/platformer/sprites/sprite032.png new file mode 100644 index 000000000..9b70de423 Binary files /dev/null and b/assets/platformer/sprites/sprite032.png differ diff --git a/assets/platformer/sprites/sprite033.png b/assets/platformer/sprites/sprite033.png new file mode 100644 index 000000000..f4c716466 Binary files /dev/null and b/assets/platformer/sprites/sprite033.png differ diff --git a/assets/platformer/sprites/sprite034.png b/assets/platformer/sprites/sprite034.png new file mode 100644 index 000000000..e3c53a7f2 Binary files /dev/null and b/assets/platformer/sprites/sprite034.png differ diff --git a/assets/platformer/sprites/sprite035.png b/assets/platformer/sprites/sprite035.png new file mode 100644 index 000000000..552165e10 Binary files /dev/null and b/assets/platformer/sprites/sprite035.png differ diff --git a/assets/platformer/sprites/sprite036.png b/assets/platformer/sprites/sprite036.png new file mode 100644 index 000000000..2e8e02fe3 Binary files /dev/null and b/assets/platformer/sprites/sprite036.png differ diff --git a/assets/platformer/sprites/sprite037.png b/assets/platformer/sprites/sprite037.png new file mode 100644 index 000000000..3a29881b3 Binary files /dev/null and b/assets/platformer/sprites/sprite037.png differ diff --git a/assets/platformer/sprites/sprite038.png b/assets/platformer/sprites/sprite038.png new file mode 100644 index 000000000..b4d8dcb2e Binary files /dev/null and b/assets/platformer/sprites/sprite038.png differ diff --git a/assets/platformer/sprites/sprite039.png b/assets/platformer/sprites/sprite039.png new file mode 100644 index 000000000..a3ade2ca8 Binary files /dev/null and b/assets/platformer/sprites/sprite039.png differ diff --git a/assets/platformer/sprites/sprite040.png b/assets/platformer/sprites/sprite040.png new file mode 100644 index 000000000..dab990474 Binary files /dev/null and b/assets/platformer/sprites/sprite040.png differ diff --git a/assets/platformer/sprites/sprite041.png b/assets/platformer/sprites/sprite041.png new file mode 100644 index 000000000..19647a820 Binary files /dev/null and b/assets/platformer/sprites/sprite041.png differ diff --git a/assets/platformer/sprites/sprite042.png b/assets/platformer/sprites/sprite042.png new file mode 100644 index 000000000..d52a61321 Binary files /dev/null and b/assets/platformer/sprites/sprite042.png differ diff --git a/assets/platformer/sprites/sprite043.png b/assets/platformer/sprites/sprite043.png new file mode 100644 index 000000000..64d9f3f97 Binary files /dev/null and b/assets/platformer/sprites/sprite043.png differ diff --git a/assets/platformer/sprites/sprite044.png b/assets/platformer/sprites/sprite044.png new file mode 100644 index 000000000..386709dd8 Binary files /dev/null and b/assets/platformer/sprites/sprite044.png differ diff --git a/assets/platformer/sprites/sprite045.png b/assets/platformer/sprites/sprite045.png new file mode 100644 index 000000000..f59c0a270 Binary files /dev/null and b/assets/platformer/sprites/sprite045.png differ diff --git a/assets/platformer/sprites/sprite046.png b/assets/platformer/sprites/sprite046.png new file mode 100644 index 000000000..9da75cf9d Binary files /dev/null and b/assets/platformer/sprites/sprite046.png differ diff --git a/assets/platformer/sprites/sprite047.png b/assets/platformer/sprites/sprite047.png new file mode 100644 index 000000000..425908657 Binary files /dev/null and b/assets/platformer/sprites/sprite047.png differ diff --git a/assets/platformer/sprites/sprite048.png b/assets/platformer/sprites/sprite048.png new file mode 100644 index 000000000..f247fc256 Binary files /dev/null and b/assets/platformer/sprites/sprite048.png differ diff --git a/assets/platformer/sprites/sprite049.png b/assets/platformer/sprites/sprite049.png new file mode 100644 index 000000000..f69c8431b Binary files /dev/null and b/assets/platformer/sprites/sprite049.png differ diff --git a/assets/platformer/tiles/tile032.png b/assets/platformer/tiles/tile032.png new file mode 100644 index 000000000..d4cff3fd5 Binary files /dev/null and b/assets/platformer/tiles/tile032.png differ diff --git a/assets/platformer/tiles/tile033.png b/assets/platformer/tiles/tile033.png new file mode 100644 index 000000000..b91658d3d Binary files /dev/null and b/assets/platformer/tiles/tile033.png differ diff --git a/assets/platformer/tiles/tile034.png b/assets/platformer/tiles/tile034.png new file mode 100644 index 000000000..34ea5848e Binary files /dev/null and b/assets/platformer/tiles/tile034.png differ diff --git a/assets/platformer/tiles/tile035.png b/assets/platformer/tiles/tile035.png new file mode 100644 index 000000000..a0b9152b2 Binary files /dev/null and b/assets/platformer/tiles/tile035.png differ diff --git a/assets/platformer/tiles/tile036.png b/assets/platformer/tiles/tile036.png new file mode 100644 index 000000000..323b41ef5 Binary files /dev/null and b/assets/platformer/tiles/tile036.png differ diff --git a/assets/platformer/tiles/tile037.png b/assets/platformer/tiles/tile037.png new file mode 100644 index 000000000..f63bae32d Binary files /dev/null and b/assets/platformer/tiles/tile037.png differ diff --git a/assets/platformer/tiles/tile038.png b/assets/platformer/tiles/tile038.png new file mode 100644 index 000000000..f1da3f8d3 Binary files /dev/null and b/assets/platformer/tiles/tile038.png differ diff --git a/assets/platformer/tiles/tile039.png b/assets/platformer/tiles/tile039.png new file mode 100644 index 000000000..f2e475c9d Binary files /dev/null and b/assets/platformer/tiles/tile039.png differ diff --git a/assets/platformer/tiles/tile040.png b/assets/platformer/tiles/tile040.png new file mode 100644 index 000000000..91de315cd Binary files /dev/null and b/assets/platformer/tiles/tile040.png differ diff --git a/assets/platformer/tiles/tile041.png b/assets/platformer/tiles/tile041.png new file mode 100644 index 000000000..a8fc5c93d Binary files /dev/null and b/assets/platformer/tiles/tile041.png differ diff --git a/assets/platformer/tiles/tile059.png b/assets/platformer/tiles/tile059.png new file mode 100644 index 000000000..4ae9241af Binary files /dev/null and b/assets/platformer/tiles/tile059.png differ diff --git a/assets/platformer/tiles/tile060.png b/assets/platformer/tiles/tile060.png new file mode 100644 index 000000000..18a23ecf4 Binary files /dev/null and b/assets/platformer/tiles/tile060.png differ diff --git a/assets/platformer/tiles/tile061.png b/assets/platformer/tiles/tile061.png new file mode 100644 index 000000000..e31213873 Binary files /dev/null and b/assets/platformer/tiles/tile061.png differ diff --git a/assets/platformer/tiles/tile062.png b/assets/platformer/tiles/tile062.png new file mode 100644 index 000000000..a07c3be82 Binary files /dev/null and b/assets/platformer/tiles/tile062.png differ diff --git a/assets/platformer/tiles/tile063.png b/assets/platformer/tiles/tile063.png new file mode 100644 index 000000000..57e406323 Binary files /dev/null and b/assets/platformer/tiles/tile063.png differ diff --git a/assets/platformer/tiles/tile064.png b/assets/platformer/tiles/tile064.png new file mode 100644 index 000000000..8d8013fcc Binary files /dev/null and b/assets/platformer/tiles/tile064.png differ diff --git a/assets/platformer/tiles/tile065.png b/assets/platformer/tiles/tile065.png new file mode 100644 index 000000000..5139f62af Binary files /dev/null and b/assets/platformer/tiles/tile065.png differ diff --git a/assets/platformer/tiles/tile066.png b/assets/platformer/tiles/tile066.png new file mode 100644 index 000000000..8a526bffc Binary files /dev/null and b/assets/platformer/tiles/tile066.png differ diff --git a/assets/platformer/tiles/tile067.png b/assets/platformer/tiles/tile067.png new file mode 100644 index 000000000..9c713163e Binary files /dev/null and b/assets/platformer/tiles/tile067.png differ diff --git a/assets/platformer/tiles/tile068.png b/assets/platformer/tiles/tile068.png new file mode 100644 index 000000000..6bdcbd247 Binary files /dev/null and b/assets/platformer/tiles/tile068.png differ diff --git a/assets/platformer/tiles/tile069.png b/assets/platformer/tiles/tile069.png new file mode 100644 index 000000000..f13f29442 Binary files /dev/null and b/assets/platformer/tiles/tile069.png differ diff --git a/assets/platformer/tiles/tile070.png b/assets/platformer/tiles/tile070.png new file mode 100644 index 000000000..4fb61f364 Binary files /dev/null and b/assets/platformer/tiles/tile070.png differ diff --git a/assets/platformer/tiles/tile080.png b/assets/platformer/tiles/tile080.png new file mode 100644 index 000000000..8d487cb22 Binary files /dev/null and b/assets/platformer/tiles/tile080.png differ diff --git a/assets/platformer/tiles/tile081.png b/assets/platformer/tiles/tile081.png new file mode 100644 index 000000000..389880e07 Binary files /dev/null and b/assets/platformer/tiles/tile081.png differ diff --git a/assets/platformer/tiles/tile082.png b/assets/platformer/tiles/tile082.png new file mode 100644 index 000000000..fd6dfdd9d Binary files /dev/null and b/assets/platformer/tiles/tile082.png differ diff --git a/assets/platformer/tiles/tile083.png b/assets/platformer/tiles/tile083.png new file mode 100644 index 000000000..333bb309a Binary files /dev/null and b/assets/platformer/tiles/tile083.png differ diff --git a/assets/platformer/tiles/tile084.png b/assets/platformer/tiles/tile084.png new file mode 100644 index 000000000..62276f7aa Binary files /dev/null and b/assets/platformer/tiles/tile084.png differ diff --git a/assets/platformer/tiles/tile085.png b/assets/platformer/tiles/tile085.png new file mode 100644 index 000000000..b7dbd426e Binary files /dev/null and b/assets/platformer/tiles/tile085.png differ diff --git a/assets/platformer/tiles/tile086.png b/assets/platformer/tiles/tile086.png new file mode 100644 index 000000000..8b8c057c9 Binary files /dev/null and b/assets/platformer/tiles/tile086.png differ diff --git a/assets/platformer/tiles/tile087.png b/assets/platformer/tiles/tile087.png new file mode 100644 index 000000000..458c90e44 Binary files /dev/null and b/assets/platformer/tiles/tile087.png differ diff --git a/assets/platformer/tiles/tile088.png b/assets/platformer/tiles/tile088.png new file mode 100644 index 000000000..6401e8aca Binary files /dev/null and b/assets/platformer/tiles/tile088.png differ diff --git a/assets/platformer/tiles/tile089.png b/assets/platformer/tiles/tile089.png new file mode 100644 index 000000000..861ee8208 Binary files /dev/null and b/assets/platformer/tiles/tile089.png differ diff --git a/assets/platformer/tiles/tile090.png b/assets/platformer/tiles/tile090.png new file mode 100644 index 000000000..693f067d3 Binary files /dev/null and b/assets/platformer/tiles/tile090.png differ diff --git a/assets/platformer/tiles/tile091.png b/assets/platformer/tiles/tile091.png new file mode 100644 index 000000000..a0c93673d Binary files /dev/null and b/assets/platformer/tiles/tile091.png differ diff --git a/assets/platformer/tiles/tile092.png b/assets/platformer/tiles/tile092.png new file mode 100644 index 000000000..a54cdbe12 Binary files /dev/null and b/assets/platformer/tiles/tile092.png differ diff --git a/assets/platformer/tiles/tile093.png b/assets/platformer/tiles/tile093.png new file mode 100644 index 000000000..652a6c027 Binary files /dev/null and b/assets/platformer/tiles/tile093.png differ diff --git a/assets/platformer/tiles/tile094.png b/assets/platformer/tiles/tile094.png new file mode 100644 index 000000000..f11ad852a Binary files /dev/null and b/assets/platformer/tiles/tile094.png differ diff --git a/assets/platformer/tiles/tile095.png b/assets/platformer/tiles/tile095.png new file mode 100644 index 000000000..01c1c73ab Binary files /dev/null and b/assets/platformer/tiles/tile095.png differ diff --git a/assets/platformer/tiles/tile096.png b/assets/platformer/tiles/tile096.png new file mode 100644 index 000000000..3f79b4c95 Binary files /dev/null and b/assets/platformer/tiles/tile096.png differ diff --git a/assets/platformer/tiles/tile097.png b/assets/platformer/tiles/tile097.png new file mode 100644 index 000000000..94530f48d Binary files /dev/null and b/assets/platformer/tiles/tile097.png differ diff --git a/assets/platformer/tiles/tile098.png b/assets/platformer/tiles/tile098.png new file mode 100644 index 000000000..b7c4c7f0a Binary files /dev/null and b/assets/platformer/tiles/tile098.png differ diff --git a/assets/platformer/tiles/tile099.png b/assets/platformer/tiles/tile099.png new file mode 100644 index 000000000..0c99d2d53 Binary files /dev/null and b/assets/platformer/tiles/tile099.png differ diff --git a/assets/platformer/tiles/tile100.png b/assets/platformer/tiles/tile100.png new file mode 100644 index 000000000..1be9d44cf Binary files /dev/null and b/assets/platformer/tiles/tile100.png differ diff --git a/assets/platformer/tiles/tile101.png b/assets/platformer/tiles/tile101.png new file mode 100644 index 000000000..d693fdffa Binary files /dev/null and b/assets/platformer/tiles/tile101.png differ diff --git a/assets/platformer/tiles/tile102.png b/assets/platformer/tiles/tile102.png new file mode 100644 index 000000000..c8163d6c4 Binary files /dev/null and b/assets/platformer/tiles/tile102.png differ diff --git a/assets/platformer/tiles/tile103.png b/assets/platformer/tiles/tile103.png new file mode 100644 index 000000000..ecf86569e Binary files /dev/null and b/assets/platformer/tiles/tile103.png differ diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index d762a1af6..cbfded2d3 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -51,6 +51,12 @@ idf_component_register(SRCS "swadge2024.c" "modes/breakout/soundManager.c" "modes/breakout/aabb_utils.c" "modes/breakout/starfield.c" + "modes/platformer/plEntity.c" + "modes/platformer/plEntityManager.c" + "modes/platformer/plGameData.c" + "modes/platformer/mode_platformer.c" + "modes/platformer/plTilemap.c" + "modes/platformer/plSoundManager.c" "modes/touchTest/touchTest.c" "modes/quickSettings/menuQuickSettingsRenderer.c" "modes/quickSettings/quickSettings.c" @@ -105,6 +111,7 @@ idf_component_register(SRCS "swadge2024.c" "./modes/pushy" "./modes/pong" "./modes/breakout" + "./modes/platformer" "./modes/tunernome" "./modes/mainMenu" "./modes/demo" diff --git a/main/modes/mainMenu/mainMenu.c b/main/modes/mainMenu/mainMenu.c index 63c126a0e..9dc420ee0 100644 --- a/main/modes/mainMenu/mainMenu.c +++ b/main/modes/mainMenu/mainMenu.c @@ -16,6 +16,7 @@ #include "lumberjack.h" #include "marbles.h" #include "mode_paint.h" +#include "mode_platformer.h" #include "mode_ray.h" #include "paint_share.h" #include "pong.h" @@ -120,6 +121,7 @@ static void mainMenuEnterMode(void) // Add single items mainMenu->menu = startSubMenu(mainMenu->menu, "Games"); addSingleItemToMenu(mainMenu->menu, breakoutMode.modeName); + addSingleItemToMenu(mainMenu->menu, modePlatformer.modeName); addSingleItemToMenu(mainMenu->menu, lumberjackMode.modeName); addSingleItemToMenu(mainMenu->menu, marblesMode.modeName); addSingleItemToMenu(mainMenu->menu, pongMode.modeName); @@ -260,6 +262,10 @@ static void mainMenuCb(const char* label, bool selected, uint32_t settingVal) { switchToSwadgeMode(&modePaint); } + else if (label == modePlatformer.modeName) + { + switchToSwadgeMode(&modePlatformer); + } else if (label == pongMode.modeName) { switchToSwadgeMode(&pongMode); diff --git a/main/modes/platformer/mode_platformer.c b/main/modes/platformer/mode_platformer.c new file mode 100644 index 000000000..e170e6fa5 --- /dev/null +++ b/main/modes/platformer/mode_platformer.c @@ -0,0 +1,1392 @@ +/** + * @file mode_platformer.c + * @author J.Vega (JVeg199X) + * @brief Super Swadge Land (port to Swadge 2024) + * @date 2023-09-22 + * + */ + +//============================================================================== +// Includes +//============================================================================== + +#include +#include + +#include "esp_log.h" +#include "esp_timer.h" + +#include "mode_platformer.h" +#include "esp_random.h" + +#include "platformer_typedef.h" +#include "plTilemap.h" +#include "plGameData.h" +#include "plEntityManager.h" +#include "plLeveldef.h" + +#include "hdw-led.h" +#include "palette.h" +#include "hdw-nvs.h" +#include "plSoundManager.h" +#include +#include "mainMenu.h" +#include "fill.h" + +//============================================================================== +// Constants +//============================================================================== +#define BIG_SCORE 4000000UL +#define BIGGER_SCORE 10000000UL +#define FAST_TIME 1500 //25 minutes + +static const paletteColor_t highScoreNewEntryColors[4] = {c050, c055, c005, c055}; + +static const paletteColor_t redColors[4] = {c501, c540, c550, c540}; +static const paletteColor_t yellowColors[4] = {c550, c331, c550, c555}; +static const paletteColor_t greenColors[4] = {c555, c051, c030, c051}; +static const paletteColor_t cyanColors[4] = {c055, c455, c055, c033}; +static const paletteColor_t purpleColors[4] = {c213, c535, c555, c535}; +static const paletteColor_t rgbColors[4] = {c500, c050, c005, c050}; + +static const int16_t cheatCode[11] = {PB_UP, PB_UP, PB_DOWN, PB_DOWN, PB_LEFT, PB_RIGHT, PB_LEFT, PB_RIGHT, PB_B, PB_A, PB_START}; + +//============================================================================== +// Functions Prototypes +//============================================================================== + +void platformerEnterMode(void); +void platformerExitMode(void); +void platformerMainLoop(int64_t elapsedUs); + +//============================================================================== +// Structs +//============================================================================== + +typedef void (*gameUpdateFuncton_t)(platformer_t *self); +struct platformer_t +{ + font_t radiostars; + + plTilemap_t tilemap; + plEntityManager_t entityManager; + plGameData_t gameData; + + plSoundManager_t soundManager; + + uint8_t menuState; + uint8_t menuSelection; + uint8_t cheatCodeIdx; + + int16_t btnState; + int16_t prevBtnState; + + int32_t frameTimer; + + platformerHighScores_t highScores; + platformerUnlockables_t unlockables; + bool easterEgg; + + gameUpdateFuncton_t update; +}; + +//============================================================================== +// Function Prototypes +//============================================================================== +void drawPlatformerHud(font_t *font, plGameData_t *gameData); +void drawPlatformerTitleScreen(font_t *font, plGameData_t *gameData); +void changeStateReadyScreen(platformer_t *self); +void updateReadyScreen(platformer_t *self); +void drawReadyScreen(font_t *font, plGameData_t *gameData); +void changeStateGame(platformer_t *self); +void detectGameStateChange(platformer_t *self); +void detectBgmChange(platformer_t *self); +void changeStateDead(platformer_t *self); +void updateDead(platformer_t *self); +void changeStateGameOver(platformer_t *self); +void updateGameOver(platformer_t *self); +void drawGameOver(font_t *font, plGameData_t *gameData); +void changeStateTitleScreen(platformer_t *self); +void changeStateLevelClear(platformer_t *self); +void updateLevelClear(platformer_t *self); +void drawLevelClear(font_t *font, plGameData_t *gameData); +void changeStateGameClear(platformer_t *self); +void updateGameClear(platformer_t *self); +void drawGameClear(font_t *font, plGameData_t *gameData); +void initializePlatformerHighScores(platformer_t* self); +void loadPlatformerHighScores(platformer_t* self); +void savePlatformerHighScores(platformer_t* self); +void initializePlatformerUnlockables(platformer_t* self); +void loadPlatformerUnlockables(platformer_t* self); +void savePlatformerUnlockables(platformer_t* self); +void drawPlatformerHighScores(font_t *font, platformerHighScores_t *highScores, plGameData_t *gameData); +uint8_t getHighScoreRank(platformerHighScores_t *highScores, uint32_t newScore); +void insertScoreIntoHighScores(platformerHighScores_t *highScores, uint32_t newScore, char newInitials[], uint8_t rank); +void changeStateNameEntry(platformer_t *self); +void updateNameEntry(platformer_t *self); +void drawNameEntry(font_t *font, plGameData_t *gameData, uint8_t currentInitial); +void changeStateShowHighScores(platformer_t *self); +void updateShowHighScores(platformer_t *self); +void drawShowHighScores(font_t *font, uint8_t menuState); +void changeStatePause(platformer_t *self); +void updatePause(platformer_t *self); +void drawPause(font_t *font); +uint16_t getLevelIndex(uint8_t world, uint8_t level); + +//============================================================================== +// Variables +//============================================================================== + +platformer_t *platformer = NULL; + +swadgeMode_t modePlatformer = { + .modeName = "Swadge Land", + .wifiMode = NO_WIFI, + .overrideUsb = false, + .usesAccelerometer = false, + .usesThermometer = false, + .fnEnterMode = platformerEnterMode, + .fnExitMode = platformerExitMode, + .fnMainLoop = platformerMainLoop, + .fnAudioCallback = NULL, + .fnBackgroundDrawCallback = NULL, + .fnEspNowRecvCb = NULL, + .fnEspNowSendCb = NULL +}; + +#define NUM_LEVELS 16 + +static const plLeveldef_t leveldef[17] = { + {.filename = "level1-1.bin", + .timeLimit = 180, + .checkpointTimeLimit = 90}, + {.filename = "dac01.bin", + .timeLimit = 180, + .checkpointTimeLimit = 90}, + {.filename = "level1-3.bin", + .timeLimit = 180, + .checkpointTimeLimit = 90}, + {.filename = "level1-4.bin", + .timeLimit = 180, + .checkpointTimeLimit = 90}, + {.filename = "level2-1.bin", + .timeLimit = 180, + .checkpointTimeLimit = 90}, + {.filename = "dac03.bin", + .timeLimit = 220, + .checkpointTimeLimit = 90}, + {.filename = "level2-3.bin", + .timeLimit = 200, + .checkpointTimeLimit = 90}, + {.filename = "level2-4.bin", + .timeLimit = 180, + .checkpointTimeLimit = 90}, + {.filename = "level3-1.bin", + .timeLimit = 180, + .checkpointTimeLimit = 90}, + {.filename = "dac02.bin", + .timeLimit = 180, + .checkpointTimeLimit = 90}, + {.filename = "level3-3.bin", + .timeLimit = 180, + .checkpointTimeLimit = 90}, + {.filename = "level3-4.bin", + .timeLimit = 220, + .checkpointTimeLimit = 110}, + {.filename = "level4-1.bin", + .timeLimit = 270, + .checkpointTimeLimit = 90}, + {.filename = "level4-2.bin", + .timeLimit = 240, + .checkpointTimeLimit = 90}, + {.filename = "level4-3.bin", + .timeLimit = 240, + .checkpointTimeLimit = 90}, + {.filename = "level4-4.bin", + .timeLimit = 240, + .checkpointTimeLimit = 90}, + {.filename = "debug.bin", + .timeLimit = 180, + .checkpointTimeLimit = 90}}; + +led_t platLeds[CONFIG_NUM_LEDS]; + +static const char str_get_ready[] = "Get Ready!"; +static const char str_time_up[] = "-Time Up!-"; +static const char str_game_over[] = "Game Over"; +static const char str_well_done[] = "Well done!"; +static const char str_congrats[] = "Congratulations!"; +static const char str_initials[] = "Enter your initials!"; +static const char str_hbd[] = "Happy Birthday, Evelyn!"; +static const char str_registrated[] = "Your name registrated."; +static const char str_do_your_best[] = "Do your best!"; +static const char str_pause[] = "-Pause-"; + +static const char KEY_SCORES[] = "pf_scores"; +static const char KEY_UNLOCKS[] = "pf_unlocks"; + +//============================================================================== +// Functions +//============================================================================== + +/** + * @brief TODO + * + */ +void platformerEnterMode(void) +{ + // Allocate memory for this mode + platformer = (platformer_t *)calloc(1, sizeof(platformer_t)); + memset(platformer, 0, sizeof(platformer_t)); + + platformer->menuState = 0; + platformer->menuSelection = 0; + platformer->btnState = 0; + platformer->prevBtnState = 0; + + loadPlatformerHighScores(platformer); + loadPlatformerUnlockables(platformer); + if(platformer->highScores.initials[0][0] == 'E' && platformer->highScores.initials[0][1] == 'F' && platformer->highScores.initials[0][2] == 'V'){ + platformer->easterEgg = true; + } + + loadFont("radiostars.font", &platformer->radiostars, false); + + pl_initializeTileMap(&(platformer->tilemap)); + pl_loadMapFromFile(&(platformer->tilemap), leveldef[0].filename); + + pl_initializeSoundManager(&(platformer->soundManager)); + + pl_initializeGameData(&(platformer->gameData), &(platformer->soundManager)); + pl_initializeEntityManager(&(platformer->entityManager), &(platformer->tilemap), &(platformer->gameData), &(platformer->soundManager)); + + platformer->tilemap.entityManager = &(platformer->entityManager); + platformer->tilemap.tileSpawnEnabled = true; + + setFrameRateUs(16666); + + platformer->update = &updateTitleScreen; +} + +/** + * @brief TODO + * + */ +void platformerExitMode(void) +{ + freeFont(&platformer->radiostars); + pl_freeTilemap(&(platformer->tilemap)); + pl_freeSoundManager(&(platformer->soundManager)); + pl_freeEntityManager(&(platformer->entityManager)); + free(platformer); +} + +/** + * @brief TODO + * + * @param elapsedUs + */ +void platformerMainLoop(int64_t elapsedUs) +{ + //Check inputs + buttonEvt_t evt = {0}; + while (checkButtonQueueWrapper(&evt)) + { + // Save the button state + platformer->btnState = evt.state; + platformer->gameData.btnState = evt.state; + } + + platformer->update(platformer); + + platformer->prevBtnState = platformer->btnState; + platformer->gameData.prevBtnState = platformer->prevBtnState; +} + +/** + * @brief TODO + * + * @param opt + */ +// void platformerCb(const char *opt) +// { +// ESP_LOGI("MNU", "%s", opt); +// } + +void updateGame(platformer_t *self) +{ + // Clear the display + fillDisplayArea(0, 0, TFT_WIDTH, TFT_HEIGHT, self->gameData.bgColor); + + pl_updateEntities(&(self->entityManager)); + + pl_drawTileMap(&(self->tilemap)); + pl_drawEntities(&(self->entityManager)); + detectGameStateChange(self); + detectBgmChange(self); + drawPlatformerHud(&(self->radiostars), &(self->gameData)); + + self->gameData.frameCount++; + if(self->gameData.frameCount > 59){ + self->gameData.frameCount = 0; + self->gameData.countdown--; + self->gameData.inGameTimer++; + + if(self->gameData.countdown < 10){ + bzrPlayBgm(&(self->soundManager.sndOuttaTime), BZR_STEREO); + } + + if(self->gameData.countdown < 0){ + killPlayer(self->entityManager.playerEntity); + } + } + + updateComboTimer(&(self->gameData)); +} + +void drawPlatformerHud(font_t *font, plGameData_t *gameData) +{ + char coinStr[8]; + snprintf(coinStr, sizeof(coinStr) - 1, "C:%02d", gameData->coins); + + char scoreStr[32]; + snprintf(scoreStr, sizeof(scoreStr) - 1, "%06" PRIu32, gameData->score); + + char levelStr[15]; + snprintf(levelStr, sizeof(levelStr) - 1, "Level %d-%d", gameData->world, gameData->level); + + char livesStr[8]; + snprintf(livesStr, sizeof(livesStr) - 1, "x%d", gameData->lives); + + char timeStr[10]; + snprintf(timeStr, sizeof(timeStr) - 1, "T:%03d", gameData->countdown); + + if(gameData->frameCount > 29) { + drawText(font, c500, "1UP", 24, 2); + } + + drawText(font, c555, livesStr, 56, 2); + drawText(font, c555, coinStr, 160, 16); + drawText(font, c555, scoreStr, 8, 16); + drawText(font, c555, levelStr, 152, 2); + drawText(font, (gameData->countdown > 30) ? c555 : redColors[(gameData->frameCount >> 3) % 4], timeStr, 220, 16); + + if(gameData->comboTimer == 0){ + return; + } + + snprintf(scoreStr, sizeof(scoreStr) - 1, "+%" PRIu32 " (x%d)", gameData->comboScore, gameData->combo); + drawText(font, (gameData->comboTimer < 60) ? c030: greenColors[(platformer->gameData.frameCount >> 3) % 4], scoreStr, 8, 30); +} + +void updateTitleScreen(platformer_t *self) +{ + // Clear the display + fillDisplayArea(0, 0, TFT_WIDTH, TFT_HEIGHT, self->gameData.bgColor); + + self->gameData.frameCount++; + + // Handle inputs + switch(platformer->menuState){ + case 0:{ + if(self->gameData.frameCount > 600){ + pl_resetGameDataLeds(&(self->gameData)); + changeStateShowHighScores(self); + } + + if ( + (self->gameData.btnState & cheatCode[platformer->cheatCodeIdx]) + && + !(self->gameData.prevBtnState & cheatCode[platformer->cheatCodeIdx]) + ) { + platformer->cheatCodeIdx++; + + if(platformer->cheatCodeIdx > 10){ + platformer->cheatCodeIdx = 0; + platformer->menuState = 1; + platformer->gameData.debugMode = true; + bzrPlaySfx(&(platformer->soundManager.sndLevelClearS), BZR_STEREO); + break; + } else { + bzrPlaySfx(&(platformer->soundManager.sndMenuSelect), BZR_STEREO); + break; + } + } + else + { + if( !(self->gameData.frameCount % 150 ) ){ + platformer->cheatCodeIdx = 0; + } + } + + if ( + ( + (self->gameData.btnState & PB_START) + && + !(self->gameData.prevBtnState & PB_START) + ) + || + ( + (self->gameData.btnState & PB_A) + && + !(self->gameData.prevBtnState & PB_A) + ) + ) + { + bzrPlaySfx(&(self->soundManager.sndMenuConfirm), BZR_STEREO); + platformer->menuState = 1; + platformer->menuSelection = 0; + } + + break; + } + case 1:{ + if ( + ( + (self->gameData.btnState & PB_START) + && + !(self->gameData.prevBtnState & PB_START) + ) + || + ( + (self->gameData.btnState & PB_A) + && + !(self->gameData.prevBtnState & PB_A) + ) + ) + { + switch(self->menuSelection){ + case 0 ... 1: { + uint16_t levelIndex = getLevelIndex(self->gameData.world, self->gameData.level); + if( + (levelIndex >= NUM_LEVELS) + || + (!self->gameData.debugMode && levelIndex > self->unlockables.maxLevelIndexUnlocked) + ){ + bzrPlaySfx(&(self->soundManager.sndMenuDeny), BZR_STEREO); + break; + } + + /*if(self->menuSelection == 0){ + self->gameData.world = 1; + self->gameData.level = 1; + }*/ + + pl_initializeGameDataFromTitleScreen(&(self->gameData)); + changeStateReadyScreen(self); + break; + } + case 2: { + if(self->gameData.debugMode){ + //Reset Progress + initializePlatformerUnlockables(self); + bzrPlaySfx(&(self->soundManager.sndBreak), BZR_STEREO); + } else { + //Show High Scores + self->menuSelection = 0; + self->menuState = 0; + + changeStateShowHighScores(self); + bzrPlaySfx(&(self->soundManager.sndMenuConfirm), BZR_STEREO); + } + break; + } + case 3: { + if(self->gameData.debugMode){ + //Reset High Scores + initializePlatformerHighScores(self); + bzrPlaySfx(&(self->soundManager.sndBreak), BZR_STEREO); + } else { + //Show Achievements + self->menuSelection = 0; + self->menuState = 2; + bzrPlaySfx(&(self->soundManager.sndMenuConfirm), BZR_STEREO); + } + break; + } + case 4: { + if(self->gameData.debugMode){ + //Save & Quit + savePlatformerHighScores(self); + savePlatformerUnlockables(self); + bzrPlaySfx(&(self->soundManager.sndMenuConfirm), BZR_STEREO); + switchToSwadgeMode(&mainMenuMode); + } else { + bzrPlaySfx(&(self->soundManager.sndMenuConfirm), BZR_STEREO); + switchToSwadgeMode(&mainMenuMode); + } + break; + } + default: { + bzrPlaySfx(&(self->soundManager.sndMenuDeny), BZR_STEREO); + self->menuSelection = 0; + } + } + } else if ( + ( + self->gameData.btnState & PB_UP + && + !(self->gameData.prevBtnState & PB_UP) + ) + ) + { + if(platformer->menuSelection > 0){ + platformer->menuSelection--; + + if(!self->gameData.debugMode && platformer->menuSelection == 1 && self->unlockables.maxLevelIndexUnlocked == 0){ + platformer->menuSelection--; + } + + bzrPlaySfx(&(self->soundManager.sndMenuSelect), BZR_STEREO); + } + } else if ( + ( + self->gameData.btnState & PB_DOWN + && + !(self->gameData.prevBtnState & PB_DOWN) + ) + ) + { + if(platformer->menuSelection < 4){ + platformer->menuSelection++; + + if(!self->gameData.debugMode && platformer->menuSelection == 1 && self->unlockables.maxLevelIndexUnlocked == 0){ + platformer->menuSelection++; + } + + bzrPlaySfx(&(self->soundManager.sndMenuSelect), BZR_STEREO); + } else { + bzrPlaySfx(&(self->soundManager.sndMenuDeny), BZR_STEREO); + } + } else if ( + ( + self->gameData.btnState & PB_LEFT + && + !(self->gameData.prevBtnState & PB_LEFT) + ) + ) + { + if(platformer->menuSelection == 1){ + if(platformer->gameData.level == 1 && platformer->gameData.world == 1){ + bzrPlaySfx(&(self->soundManager.sndMenuDeny), BZR_STEREO); + } else { + platformer->gameData.level--; + if(platformer->gameData.level < 1){ + platformer->gameData.level = 4; + if(platformer->gameData.world > 1){ + platformer->gameData.world--; + } + } + bzrPlaySfx(&(self->soundManager.sndMenuSelect), BZR_STEREO); + } + } + } else if ( + ( + self->gameData.btnState & PB_RIGHT + && + !(self->gameData.prevBtnState & PB_RIGHT) + ) + ) + { + if(platformer->menuSelection == 1){ + if( + (platformer->gameData.level == 4 && platformer->gameData.world == 4) + || + (!platformer->gameData.debugMode && getLevelIndex(platformer->gameData.world, platformer->gameData.level + 1) > platformer->unlockables.maxLevelIndexUnlocked ) + ) + { + bzrPlaySfx(&(self->soundManager.sndMenuDeny), BZR_STEREO); + } else { + platformer->gameData.level++; + if(platformer->gameData.level > 4){ + platformer->gameData.level = 1; + if(platformer->gameData.world < 8){ + platformer->gameData.world++; + } + } + bzrPlaySfx(&(self->soundManager.sndMenuSelect), BZR_STEREO); + } + + } + } else if ( + ( + self->gameData.btnState & PB_B + && + !(self->gameData.prevBtnState & PB_B) + ) + ) + { + self->gameData.frameCount = 0; + platformer->menuState = 0; + bzrPlaySfx(&(self->soundManager.sndMenuConfirm), BZR_STEREO); + } + break; + } + case 2:{ + if ( + ( + self->gameData.btnState & PB_B + && + !(self->gameData.prevBtnState & PB_B) + ) + ) + { + self->gameData.frameCount = 0; + platformer->menuState = 1; + bzrPlaySfx(&(self->soundManager.sndMenuConfirm), BZR_STEREO); + } + break; + } + default: + platformer->menuState = 0; + bzrPlaySfx(&(platformer->soundManager.sndMenuDeny), BZR_STEREO); + break; + } + + + pl_scrollTileMap(&(platformer->tilemap), 1, 0); + if(self->tilemap.mapOffsetX >= self->tilemap.maxMapOffsetX && self->gameData.frameCount > 58){ + self->tilemap.mapOffsetX = 0; + } + + drawPlatformerTitleScreen(&(self->radiostars), &(self->gameData)); + + if(( (self->gameData.frameCount) % 10) == 0){ + for (int32_t i = 0; i < CONFIG_NUM_LEDS; i++) + { + + //self->gameData.leds[i].r = (( (self->gameData.frameCount >> 4) % NUM_LEDS) == i) ? 0xFF : 0x00; + + platLeds[i].r += (esp_random() % 1); + platLeds[i].g += (esp_random() % 8); + platLeds[i].b += (esp_random() % 8); + } + } + setLeds(platLeds, CONFIG_NUM_LEDS); +} + +void drawPlatformerTitleScreen(font_t *font, plGameData_t *gameData) +{ + pl_drawTileMap(&(platformer->tilemap)); + + drawText(font, c555, "Super Swadge Land", 40, 32); + + if(platformer->gameData.debugMode){ + drawText(font, c555, "Debug Mode", 80, 48); + } + + switch(platformer->menuState){ + case 0: { + if ((gameData->frameCount % 60 ) < 30) + { + drawText(font, c555, "- Press START button -", 20, 128); + } + break; + } + + case 1: { + drawText(font, c555, "Start Game", 48, 128); + + if(platformer->gameData.debugMode || platformer->unlockables.maxLevelIndexUnlocked > 0){ + char levelStr[24]; + snprintf(levelStr, sizeof(levelStr) - 1, "Level Select: %d-%d", gameData->world, gameData->level); + drawText(font, c555, levelStr, 48, 144); + } + + if(platformer->gameData.debugMode){ + drawText(font, c555, "Reset Progress", 48, 160); + drawText(font, c555, "Reset High Scores", 48, 176); + drawText(font, c555, "Save & Exit to Menu", 48, 192); + } else { + drawText(font, c555, "High Scores", 48, 160); + drawText(font, c555, "Achievements", 48, 176); + drawText(font, c555, "Exit to Menu", 48, 192); + } + + drawText(font, c555, "->", 32, 128 + platformer->menuSelection * 16); + + break; + } + + case 2: { + if(platformer->unlockables.gameCleared){ + drawText(font, redColors[(gameData->frameCount >> 3) % 4], "Beat the game!", 48, 80); + } + + if(platformer->unlockables.oneCreditCleared){ + drawText(font, yellowColors[(gameData->frameCount >> 3) % 4], "1 Credit Clear!", 48, 96); + } + + if(platformer->unlockables.bigScore){ + drawText(font, greenColors[(gameData->frameCount >> 3) % 4], "Got 4 million points!", 48, 112); + } + + if(platformer->unlockables.biggerScore){ + drawText(font, cyanColors[(gameData->frameCount >> 3) % 4], "Got 10 million points!", 48, 128); + } + + if(platformer->unlockables.fastTime){ + drawText(font, purpleColors[(gameData->frameCount >> 3) % 4], "Beat within 25 min!", 48, 144); + } + + if(platformer->unlockables.gameCleared && platformer->unlockables.oneCreditCleared && platformer->unlockables.bigScore && platformer->unlockables.biggerScore && platformer->unlockables.fastTime){ + drawText(font, rgbColors[(gameData->frameCount >> 3) % 4], "100% 100% 100%", 48, 160); + } + + drawText(font, c555, "Press B to Return", 48, 192); + break; + } + + default: + break; + } +} + +void changeStateReadyScreen(platformer_t *self){ + self->gameData.frameCount = 0; + + bzrPlayBgm(&(self->soundManager.bgmIntro), BZR_STEREO); + + pl_resetGameDataLeds(&(self->gameData)); + + self->update=&updateReadyScreen; +} + +void updateReadyScreen(platformer_t *self){ + // Clear the display + fillDisplayArea(0, 0, TFT_WIDTH, TFT_HEIGHT, c000); + + self->gameData.frameCount++; + if(self->gameData.frameCount > 179){ + bzrStop(true); + changeStateGame(self); + } + + drawReadyScreen(&(self->radiostars), &(self->gameData)); +} + +void drawReadyScreen(font_t *font, plGameData_t *gameData){ + drawPlatformerHud(font, gameData); + int16_t xOff = (TFT_WIDTH - textWidth(font, str_get_ready)) / 2; + drawText(font, c555, str_get_ready, xOff, 128); + + if(getLevelIndex(gameData->world, gameData->level) == 0) + { + drawText(font, c555, "A: Jump", xOff, 128 + (font->height + 3) * 3); + drawText(font, c555, "B: Run / Fire", xOff, 128 + (font->height + 3) * 4); + } +} + +void changeStateGame(platformer_t *self){ + self->gameData.frameCount = 0; + self->gameData.currentBgm = 0; + pl_resetGameDataLeds(&(self->gameData)); + + pl_deactivateAllEntities(&(self->entityManager), false); + + uint16_t levelIndex = getLevelIndex(self->gameData.world, self->gameData.level); + pl_loadMapFromFile(&(platformer->tilemap), leveldef[levelIndex].filename); + self->gameData.countdown = leveldef[levelIndex].timeLimit; + + plEntityManager_t * entityManager = &(self->entityManager); + entityManager->viewEntity = pl_createPlayer(entityManager, entityManager->tilemap->warps[self->gameData.checkpoint].x * 16, entityManager->tilemap->warps[self->gameData.checkpoint].y * 16); + entityManager->playerEntity = entityManager->viewEntity; + entityManager->playerEntity->hp = self->gameData.initialHp; + pl_viewFollowEntity(&(self->tilemap),entityManager->playerEntity); + + + pl_updateLedsHpMeter(&(self->entityManager),&(self->gameData)); + + self->tilemap.executeTileSpawnAll = true; + + self->update = &updateGame; +} + +void detectGameStateChange(platformer_t *self){ + if(!self->gameData.changeState){ + return; + } + + switch (self->gameData.changeState) + { + case PL_ST_DEAD: + changeStateDead(self); + break; + + case PL_ST_READY_SCREEN: + changeStateReadyScreen(self); + break; + + case PL_ST_LEVEL_CLEAR: + changeStateLevelClear(self); + break; + + case PL_ST_PAUSE: + changeStatePause(self); + break; + + default: + break; + } + + self->gameData.changeState = 0; +} + +void detectBgmChange(platformer_t *self){ + if(!self->gameData.changeBgm){ + return; + } + + switch (self->gameData.changeBgm) + { + case PL_BGM_NULL: + if(self->gameData.currentBgm != PL_BGM_NULL){ + bzrStop(true); + } + break; + + case PL_BGM_MAIN: + if(self->gameData.currentBgm != PL_BGM_MAIN){ + bzrPlayBgm(&(self->soundManager.bgmDemagio), BZR_STEREO); + } + break; + + case PL_BGM_ATHLETIC: + if(self->gameData.currentBgm != PL_BGM_ATHLETIC){ + bzrPlayBgm(&(self->soundManager.bgmSmooth), BZR_STEREO); + } + break; + + case PL_BGM_UNDERGROUND: + if(self->gameData.currentBgm != PL_BGM_UNDERGROUND){ + bzrPlayBgm(&(self->soundManager.bgmUnderground), BZR_STEREO); + } + break; + + case PL_BGM_FORTRESS: + if(self->gameData.currentBgm != PL_BGM_FORTRESS){ + bzrPlayBgm(&(self->soundManager.bgmCastle), BZR_STEREO); + } + break; + + default: + break; + } + + self->gameData.currentBgm = self->gameData.changeBgm; + self->gameData.changeBgm = 0; +} + +void changeStateDead(platformer_t *self){ + self->gameData.frameCount = 0; + self->gameData.lives--; + self->gameData.levelDeaths++; + self->gameData.combo = 0; + self->gameData.comboTimer = 0; + self->gameData.initialHp = 1; + + bzrStop(true); + bzrPlayBgm(&(self->soundManager.sndDie), BZR_STEREO); + + self->update=&updateDead; +} + +void updateDead(platformer_t *self){ + // Clear the display + fillDisplayArea(0, 0, TFT_WIDTH, TFT_HEIGHT, self->gameData.bgColor); + + self->gameData.frameCount++; + if(self->gameData.frameCount > 179){ + if(self->gameData.lives > 0){ + changeStateReadyScreen(self); + } else { + changeStateGameOver(self); + } + } + + pl_updateEntities(&(self->entityManager)); + pl_drawTileMap(&(self->tilemap)); + pl_drawEntities(&(self->entityManager)); + drawPlatformerHud(&(self->radiostars), &(self->gameData)); + + if(self->gameData.countdown < 0){ + drawText(&(self->radiostars), c555, str_time_up, (TFT_WIDTH - textWidth(&(self->radiostars), str_time_up)) / 2, 128); + } +} + + +void updateGameOver(platformer_t *self){ + // Clear the display + fillDisplayArea(0, 0, TFT_WIDTH, TFT_HEIGHT, c000); + + self->gameData.frameCount++; + if(self->gameData.frameCount > 179){ + //Handle unlockables + + if(self->gameData.score >= BIG_SCORE) { + self->unlockables.bigScore = true; + } + + if(self->gameData.score >= BIGGER_SCORE) { + self->unlockables.biggerScore = true; + } + + if(!self->gameData.debugMode){ + savePlatformerUnlockables(self); + } + + changeStateNameEntry(self); + } + + drawGameOver(&(self->radiostars), &(self->gameData)); + pl_updateLedsGameOver(&(self->gameData)); +} + +void changeStateGameOver(platformer_t *self){ + self->gameData.frameCount = 0; + pl_resetGameDataLeds(&(self->gameData)); + bzrPlayBgm(&(self->soundManager.bgmGameOver), BZR_STEREO); + self->update=&updateGameOver; + +} + +void drawGameOver(font_t *font, plGameData_t *gameData){ + drawPlatformerHud(font, gameData); + drawText(font, c555, str_game_over, (TFT_WIDTH - textWidth(font, str_game_over)) / 2, 128); +} + +void changeStateTitleScreen(platformer_t *self){ + self->gameData.frameCount = 0; + self->update=&updateTitleScreen; +} + +void changeStateLevelClear(platformer_t *self){ + self->gameData.frameCount = 0; + self->gameData.checkpoint = 0; + self->gameData.levelDeaths = 0; + self->gameData.initialHp = self->entityManager.playerEntity->hp; + self->gameData.extraLifeCollected = false; + pl_resetGameDataLeds(&(self->gameData)); + self->update=&updateLevelClear; +} + +void updateLevelClear(platformer_t *self){ + // Clear the display + fillDisplayArea(0, 0, TFT_WIDTH, TFT_HEIGHT, self->gameData.bgColor); + + self->gameData.frameCount++; + + if(self->gameData.frameCount > 60){ + if(self->gameData.countdown > 0){ + self->gameData.countdown--; + + if(self->gameData.countdown % 2){ + bzrPlayBgm(&(self->soundManager.sndTally), BZR_STEREO); + } + + uint16_t comboPoints = 50 * self->gameData.combo; + + self->gameData.score += comboPoints; + self->gameData.comboScore = comboPoints; + + if(self->gameData.combo > 1){ + self->gameData.combo--; + } + } else if(self->gameData.frameCount % 60 == 0) { + //Hey look, it's a frame rule! + + uint16_t levelIndex = getLevelIndex(self->gameData.world, self->gameData.level); + + if(levelIndex >= NUM_LEVELS - 1){ + //Game Cleared! + + if(!self->gameData.debugMode){ + //Determine achievements + self->unlockables.gameCleared = true; + + if(!self->gameData.continuesUsed){ + self->unlockables.oneCreditCleared = true; + + if(self->gameData.inGameTimer < FAST_TIME) { + self->unlockables.fastTime = true; + } + } + + if(self->gameData.score >= BIG_SCORE) { + self->unlockables.bigScore = true; + } + + if(self->gameData.score >= BIGGER_SCORE) { + self->unlockables.biggerScore = true; + } + } + + changeStateGameClear(self); + } else { + //Advance to the next level + self->gameData.level++; + if(self->gameData.level > 4){ + self->gameData.world++; + self->gameData.level = 1; + } + + //Unlock the next level + levelIndex++; + if(levelIndex > self->unlockables.maxLevelIndexUnlocked){ + self->unlockables.maxLevelIndexUnlocked = levelIndex; + } + + changeStateReadyScreen(self); + } + + if(!self->gameData.debugMode){ + savePlatformerUnlockables(self); + } + } + } + + pl_updateEntities(&(self->entityManager)); + pl_drawTileMap(&(self->tilemap)); + pl_drawEntities(&(self->entityManager)); + drawPlatformerHud(&(self->radiostars), &(self->gameData)); + drawLevelClear(&(self->radiostars), &(self->gameData)); + pl_updateLedsLevelClear(&(self->gameData)); +} + +void drawLevelClear(font_t *font, plGameData_t *gameData){ + drawPlatformerHud(font, gameData); + drawText(font, c555, str_well_done, (TFT_WIDTH - textWidth(font, str_well_done)) / 2, 128); +} + +void changeStateGameClear(platformer_t *self){ + self->gameData.frameCount = 0; + self->update=&updateGameClear; + pl_resetGameDataLeds(&(self->gameData)); + bzrPlayBgm(&(self->soundManager.bgmSmooth), BZR_STEREO); +} + +void updateGameClear(platformer_t *self){ + // Clear the display + fillDisplayArea(0, 0, TFT_WIDTH, TFT_HEIGHT, c000); + + self->gameData.frameCount++; + + if(self->gameData.frameCount > 450){ + if(self->gameData.lives > 0){ + if(self->gameData.frameCount % 60 == 0){ + self->gameData.lives--; + self->gameData.score += 200000; + bzrPlaySfx(&(self->soundManager.snd1up), BZR_STEREO); + } + } else if(self->gameData.frameCount % 960 == 0) { + changeStateGameOver(self); + } + } + + drawPlatformerHud(&(self->radiostars), &(self->gameData)); + drawGameClear(&(self->radiostars), &(self->gameData)); + pl_updateLedsGameClear(&(self->gameData)); +} + +void drawGameClear( font_t *font, plGameData_t *gameData){ + drawPlatformerHud(font, gameData); + + char timeStr[32]; + snprintf(timeStr, sizeof(timeStr) - 1, "in %06" PRIu32 " seconds!", gameData->inGameTimer); + + drawText(font, yellowColors[(gameData->frameCount >> 3) % 4], str_congrats, (TFT_WIDTH - textWidth(font, str_congrats)) / 2, 48); + + if(gameData->frameCount > 120){ + drawText(font, c555, "You've completed your", 8, 80); + drawText(font, c555, "trip across Swadge Land", 8, 96); + } + + if(gameData->frameCount > 180){ + drawText(font, (gameData->inGameTimer < FAST_TIME) ? cyanColors[(gameData->frameCount >> 3) % 4] : c555, timeStr, (TFT_WIDTH - textWidth(font, timeStr)) / 2, 112); + } + + if(gameData->frameCount > 300){ + drawText(font, c555, "The Swadge staff", 8, 144); + drawText(font, c555, "thanks you for playing!", 8, 160); + } + + if(gameData->frameCount > 420){ + drawText(font, (gameData->lives > 0) ? highScoreNewEntryColors[(gameData->frameCount >> 3) % 4] : c555, "Bonus 200000pts per life!", (TFT_WIDTH - textWidth(font, "Bonus 100000pts per life!")) / 2, 192); + } + + /* + drawText(font, c555, "Thanks for playing.", 24, 48); + drawText(font, c555, "Many more battle scenes", 8, 96); + drawText(font, c555, "will soon be available!", 8, 112); + drawText(font, c555, "Bonus 100000pts per life!", 8, 160); + */ +} + +void initializePlatformerHighScores(platformer_t* self){ + self->highScores.scores[0] = 100000; + self->highScores.scores[1] = 80000; + self->highScores.scores[2] = 40000; + self->highScores.scores[3] = 20000; + self->highScores.scores[4] = 10000; + + for(uint8_t i=0; ihighScores.initials[i][0] = 'J' + i; + self->highScores.initials[i][1] = 'P' - i; + self->highScores.initials[i][2] = 'V' + i; + } +} + +void loadPlatformerHighScores(platformer_t* self) +{ + size_t size = sizeof(platformerHighScores_t); + // Try reading the value + if(false == readNvsBlob(KEY_SCORES, &(self->highScores), &(size))) + { + // Value didn't exist, so write the default + initializePlatformerHighScores(self); + } +} + +void savePlatformerHighScores(platformer_t* self){ + size_t size = sizeof(platformerHighScores_t); + writeNvsBlob( KEY_SCORES, &(self->highScores), size); +} + +void initializePlatformerUnlockables(platformer_t* self){ + self->unlockables.maxLevelIndexUnlocked = 0; + self->unlockables.gameCleared = false; + self->unlockables.oneCreditCleared = false; + self->unlockables.bigScore = false; + self->unlockables.fastTime = false; + self->unlockables.biggerScore = false; +} + +void loadPlatformerUnlockables(platformer_t* self){ + size_t size = sizeof(platformerUnlockables_t); + // Try reading the value + if(false == readNvsBlob(KEY_UNLOCKS, &(self->unlockables), &(size))) + { + // Value didn't exist, so write the default + initializePlatformerUnlockables(self); + } +}; + +void savePlatformerUnlockables(platformer_t* self){ + size_t size = sizeof(platformerUnlockables_t); + writeNvsBlob(KEY_UNLOCKS, &(self->unlockables), size); +}; + +void drawPlatformerHighScores(font_t *font, platformerHighScores_t *highScores, plGameData_t *gameData){ + drawText(font, c555, "RANK SCORE NAME", 48, 96); + for(uint8_t i=0; iscores[i], highScores->initials[i][0], highScores->initials[i][1], highScores->initials[i][2]); + drawText(font, (gameData->rank == i) ? highScoreNewEntryColors[(gameData->frameCount >> 3) % 4] : c555, rowStr, 60, 128 + i*16); + } +} + +uint8_t getHighScoreRank(platformerHighScores_t *highScores, uint32_t newScore){ + uint8_t i; + for(i=0; iscores[i] < newScore){ + break; + } + } + + return i; +} + +void insertScoreIntoHighScores(platformerHighScores_t *highScores, uint32_t newScore, char newInitials[], uint8_t rank){ + + if(rank >= NUM_PLATFORMER_HIGH_SCORES){ + return; + } + + for(uint8_t i=NUM_PLATFORMER_HIGH_SCORES - 1; i>rank; i--){ + highScores->scores[i] = highScores->scores[i-1]; + highScores->initials[i][0] = highScores->initials[i-1][0]; + highScores->initials[i][1] = highScores->initials[i-1][1]; + highScores->initials[i][2] = highScores->initials[i-1][2]; + } + + highScores->scores[rank] = newScore; + highScores->initials[rank][0] = newInitials[0]; + highScores->initials[rank][1] = newInitials[1]; + highScores->initials[rank][2] = newInitials[2]; + +} + +void changeStateNameEntry(platformer_t *self){ + self->gameData.frameCount = 0; + uint8_t rank = getHighScoreRank(&(self->highScores),self->gameData.score); + self->gameData.rank = rank; + self->menuState = 0; + + pl_resetGameDataLeds(&(self->gameData)); + + if(rank >= NUM_PLATFORMER_HIGH_SCORES || self->gameData.debugMode){ + self->menuSelection = 0; + self->gameData.rank = NUM_PLATFORMER_HIGH_SCORES; + changeStateShowHighScores(self); + return; + } + + bzrPlayBgm(&(self->soundManager.bgmNameEntry), BZR_STEREO); + self->menuSelection = self->gameData.initials[0]; + self->update=&updateNameEntry; +} + +void updateNameEntry(platformer_t *self){ + fillDisplayArea(0, 0, TFT_WIDTH, TFT_HEIGHT, c000); + + self->gameData.frameCount++; + + if( + self->gameData.btnState & PB_LEFT + && + !(self->gameData.prevBtnState & PB_LEFT) + ) { + self->menuSelection--; + + if(self->menuSelection < 32){ + self->menuSelection = 90; + } + + self->gameData.initials[self->menuState]=self->menuSelection; + bzrPlaySfx(&(self->soundManager.sndMenuSelect), BZR_STEREO); + } else if( + self->gameData.btnState & PB_RIGHT + && + !(self->gameData.prevBtnState & PB_RIGHT) + ) { + self->menuSelection++; + + if(self->menuSelection > 90){ + self->menuSelection = 32; + } + + self->gameData.initials[self->menuState]=self->menuSelection; + bzrPlaySfx(&(self->soundManager.sndMenuSelect), BZR_STEREO); + } else if( + self->gameData.btnState & PB_B + && + !(self->gameData.prevBtnState & PB_B) + ) { + if(self->menuState > 0){ + self->menuState--; + self->menuSelection = self->gameData.initials[self->menuState]; + bzrPlaySfx(&(self->soundManager.sndMenuSelect), BZR_STEREO); + } else { + bzrPlaySfx(&(self->soundManager.sndMenuDeny), BZR_STEREO); + } + } else if( + self->gameData.btnState & PB_A + && + !(self->gameData.prevBtnState & PB_A) + ) { + self->menuState++; + + if(self->menuState >2){ + insertScoreIntoHighScores(&(self->highScores), self->gameData.score, self->gameData.initials, self->gameData.rank); + savePlatformerHighScores(self); + changeStateShowHighScores(self); + bzrPlaySfx(&(self->soundManager.sndPowerUp), BZR_STEREO); + } else { + self->menuSelection = self->gameData.initials[self->menuState]; + bzrPlaySfx(&(self->soundManager.sndMenuSelect), BZR_STEREO); + } + } + + drawNameEntry(&(self->radiostars), &(self->gameData), self->menuState); + pl_updateLedsShowHighScores(&(self->gameData)); +} + +void drawNameEntry(font_t *font, plGameData_t *gameData, uint8_t currentInitial){ + drawText(font, greenColors[(platformer->gameData.frameCount >> 3) % 4], str_initials, (TFT_WIDTH - textWidth(font, str_initials)) / 2, 64); + + char rowStr[32]; + snprintf(rowStr, sizeof(rowStr) - 1, "%d %06" PRIu32, gameData->rank+1, gameData->score); + drawText(font, c555, rowStr, 64, 128); + + for(uint8_t i=0; i<3; i++){ + snprintf(rowStr, sizeof(rowStr) - 1, "%c", gameData->initials[i]); + drawText(font, (currentInitial == i) ? highScoreNewEntryColors[(gameData->frameCount >> 3) % 4] : c555, rowStr, 192+16*i, 128); + } +} + +void changeStateShowHighScores(platformer_t *self){ + self->gameData.frameCount = 0; + self->update=&updateShowHighScores; +} + +void updateShowHighScores(platformer_t *self){ + fillDisplayArea(0, 0, TFT_WIDTH, TFT_HEIGHT, c000); + + self->gameData.frameCount++; + + if((self->gameData.frameCount > 300) || ( + ( + (self->gameData.btnState & PB_START) + && + !(self->gameData.prevBtnState & PB_START) + ) + || + ( + (self->gameData.btnState & PB_A) + && + !(self->gameData.prevBtnState & PB_A) + ) + )){ + self->menuState = 0; + self->menuSelection = 0; + bzrStop(true); + changeStateTitleScreen(self); + } + + drawShowHighScores(&(self->radiostars), self->menuState); + drawPlatformerHighScores(&(self->radiostars), &(self->highScores), &(self->gameData)); + + pl_updateLedsShowHighScores(&(self->gameData)); +} + +void drawShowHighScores(font_t *font, uint8_t menuState){ + if(platformer->easterEgg){ + drawText(font, highScoreNewEntryColors[(platformer->gameData.frameCount >> 3) % 4], str_hbd, (TFT_WIDTH - textWidth(font, str_hbd)) / 2, 32); + } else if(menuState == 3){ + drawText(font, redColors[(platformer->gameData.frameCount >> 3) % 4], str_registrated, (TFT_WIDTH - textWidth(font, str_registrated)) / 2, 32); + } else { + drawText(font, c555, str_do_your_best, (TFT_WIDTH - textWidth(font, str_do_your_best)) / 2, 32); + } +} + +void changeStatePause(platformer_t *self){ + bzrStop(true); + bzrPlaySfx(&(self->soundManager.sndPause), BZR_STEREO); + self->update=&updatePause; +} + +void updatePause(platformer_t *self){ + if(( + (self->gameData.btnState & PB_START) + && + !(self->gameData.prevBtnState & PB_START) + )){ + bzrPlaySfx(&(self->soundManager.sndPause), BZR_STEREO); + self->gameData.changeBgm = self->gameData.currentBgm; + self->gameData.currentBgm = PL_BGM_NULL; + self->update=&updateGame; + } + + pl_drawTileMap(&(self->tilemap)); + pl_drawEntities(&(self->entityManager)); + drawPlatformerHud(&(self->radiostars), &(self->gameData)); + drawPause(&(self->radiostars)); +} + +void drawPause(font_t *font){ + drawText(font, c555, str_pause, (TFT_WIDTH - textWidth(font, str_pause)) / 2, 128); +} + +uint16_t getLevelIndex(uint8_t world, uint8_t level){ + return (world-1) * 4 + (level-1); +} \ No newline at end of file diff --git a/main/modes/platformer/mode_platformer.h b/main/modes/platformer/mode_platformer.h new file mode 100644 index 000000000..6f72dfce7 --- /dev/null +++ b/main/modes/platformer/mode_platformer.h @@ -0,0 +1,43 @@ +#ifndef _MODE_PLATFORMER_H_ +#define _MODE_PLATFORMER_H_ +//============================================================================== +// Includes +//============================================================================== + +#include "platformer_typedef.h" +#include "swadge2024.h" + +/*============================================================================== + * Defines + *============================================================================*/ + +#define NUM_PLATFORMER_HIGH_SCORES 5 + +//============================================================================== +// Structs +//============================================================================== + +typedef struct { + uint32_t scores[NUM_PLATFORMER_HIGH_SCORES]; + char initials[NUM_PLATFORMER_HIGH_SCORES][3]; +} platformerHighScores_t; + +typedef struct { + uint8_t maxLevelIndexUnlocked; + bool gameCleared; + bool oneCreditCleared; + bool bigScore; + bool fastTime; + bool biggerScore; +} platformerUnlockables_t; + +//============================================================================== +// Prototypes +//============================================================================== + +void updateGame(platformer_t *platformer); +void updateTitleScreen(platformer_t *platformer); + +extern swadgeMode_t modePlatformer; + +#endif diff --git a/main/modes/platformer/plEntity.c b/main/modes/platformer/plEntity.c new file mode 100644 index 000000000..cddabb7ec --- /dev/null +++ b/main/modes/platformer/plEntity.c @@ -0,0 +1,1844 @@ +//============================================================================== +// Includes +//============================================================================== + +#include +#include "plEntity.h" +#include "plEntityManager.h" +#include "plTilemap.h" +#include "plGameData.h" +#include "hdw-bzr.h" +#include "hdw-btn.h" +#include "esp_random.h" +#include "aabb_utils.h" +#include "trigonometry.h" +#include + +//============================================================================== +// Constants +//============================================================================== +#define SUBPIXEL_RESOLUTION 4 +#define PL_TILESIZE_IN_POWERS_OF_2 4 +#define PL_TILESIZE 16 +#define PL_HALF_TILESIZE 8 +#define DESPAWN_THRESHOLD 64 + +#define SIGNOF(x) ((x > 0) - (x < 0)) +#define PL_TO_TILECOORDS(x) ((x) >> PL_TILESIZE_IN_POWERS_OF_2) +// #define TO_PIXEL_COORDS(x) ((x) >> SUBPIXEL_RESOLUTION) +// #define TO_SUBPIXEL_COORDS(x) ((x) << SUBPIXEL_RESOLUTION) + +//============================================================================== +// Functions +//============================================================================== +void pl_initializeEntity(plEntity_t *self, plEntityManager_t *entityManager, plTilemap_t *tilemap, plGameData_t *gameData, plSoundManager_t * soundManager) +{ + self->active = false; + self->tilemap = tilemap; + self->gameData = gameData; + self->soundManager = soundManager; + self->homeTileX = 0; + self->homeTileY = 0; + self->gravity = false; + self->falling = false; + self->entityManager = entityManager; + self->fallOffTileHandler = &defaultFallOffTileHandler; + self->spriteFlipHorizontal = false; + self->spriteFlipVertical = false; + + // Fields not explicitly initialized + // self->type = 0; + // self->updateFunction = NULL; + // self->x = 0; + // self->y = 0; + // self->xspeed = 0; + // self->yspeed = 0; + // self->xMaxSpeed = 0; + // self->yMaxSpeed = 0; + // self->xDamping = 0; + // self->yDamping = 0; + // self->gravityEnabled = false; + // self->spriteIndex = 0; + // self->animationTimer = 0; + // self->jumpPower = 0; + // self->visible = false; + // self->hp = 0; + // self->invincibilityFrames = 0; + // self->scoreValue = 0; + // self->collisionHandler = NULL; + // self->tileCollisionHandler = NULL; + // self->overlapTileHandler = NULL; +}; + +void pl_updatePlayer(plEntity_t *self) +{ + if (self->gameData->btnState & PB_B) + { + self->xMaxSpeed = 52; + } + else + { + self->xMaxSpeed = 30; + } + + if (self->gameData->btnState & PB_LEFT) + { + self->xspeed -= (self->falling && self->xspeed < 0) ? (self->xspeed < -24) ? 0 : 2 : 3; + + if (self->xspeed < -self->xMaxSpeed) + { + self->xspeed = -self->xMaxSpeed; + } + } + else if (self->gameData->btnState & PB_RIGHT) + { + self->xspeed += (self->falling && self->xspeed > 0) ? (self->xspeed > 24) ? 0 : 2 : 3; + + if (self->xspeed > self->xMaxSpeed) + { + self->xspeed = self->xMaxSpeed; + } + } + + if(!self->gravityEnabled){ + if (self->gameData->btnState & PB_UP) + { + self->yspeed -= 8; + self->falling = true; + + if (self->yspeed < -16) + { + self->yspeed = -16; + } + } + else if (self->gameData->btnState & PB_DOWN) + { + self->yspeed += 8; + self->falling = true; + + if (self->yspeed > 32) + { + self->yspeed = 32; + } + } + } + + if (self->gameData->btnState & PB_A) + { + if (!self->falling && !(self->gameData->prevBtnState & PB_A)) + { + // initiate jump + self->jumpPower = 64 + ((abs(self->xspeed) + 16) >> 3); + self->yspeed = -self->jumpPower; + self->falling = true; + bzrPlaySfx(&(self->soundManager->sndJump1), BZR_LEFT); + } + else if (self->jumpPower > 0 && self->yspeed < 0) + { + // jump dampening + self->jumpPower -= 2; // 32 + self->yspeed = -self->jumpPower; + + if(self->jumpPower > 35 && self->jumpPower < 37){ + bzrPlaySfx(&(self->soundManager->sndJump2), BZR_LEFT); + } + + if(self->yspeed > -6 && self->yspeed < -2){ + bzrPlaySfx(&(self->soundManager->sndJump3), BZR_LEFT); + } + + if (self->jumpPower < 0) + { + self->jumpPower = 0; + } + } + } + else if (self->falling && self->jumpPower > 0 && self->yspeed < 0) + { + // Cut jump short if player lets go of jump button + self->jumpPower = 0; + self->yspeed = self->yspeed / 4; + } + + if(self->invincibilityFrames > 0){ + self->invincibilityFrames--; + if(self->invincibilityFrames % 2){ + self->visible = !self->visible; + } + + if(self->invincibilityFrames <= 0){ + self->visible = true; + } + } + + if(self->animationTimer > 0){ + self->animationTimer--; + } + + if (self->hp >2 && self->gameData->btnState & PB_B && !(self->gameData->prevBtnState & PB_B) && self->animationTimer == 0) + { + plEntity_t * createdEntity = pl_createEntity(self->entityManager, ENTITY_WAVE_BALL, self->x >> SUBPIXEL_RESOLUTION, self->y >> SUBPIXEL_RESOLUTION); + if(createdEntity != NULL){ + createdEntity->xspeed= (self->spriteFlipHorizontal) ? -(128 + abs(self->xspeed) + abs(self->yspeed)):128 + abs(self->xspeed) + abs(self->yspeed); + createdEntity->homeTileX = 0; + createdEntity->homeTileY = 0; + bzrPlaySfx(&(self->soundManager->sndWaveBall), BZR_LEFT); + } + self->animationTimer = 30; + } + + if( + ( + (self->gameData->btnState & PB_START) + && + !(self->gameData->prevBtnState & PB_START) + ) + ){ + self->gameData->changeState = PL_ST_PAUSE; + } + + pl_moveEntityWithTileCollisions(self); + dieWhenFallingOffScreen(self); + applyGravity(self); + applyDamping(self); + pl_detectEntityCollisions(self); + animatePlayer(self); + +}; + +void updateTestObject(plEntity_t *self) +{ + if(self->gameData->frameCount % 10 == 0) { + self->spriteFlipHorizontal = !self->spriteFlipHorizontal; + } + + despawnWhenOffscreen(self); + pl_moveEntityWithTileCollisions(self); + applyGravity(self); + pl_detectEntityCollisions(self); +}; + +void updateHitBlock(plEntity_t *self) +{ + if(self->homeTileY > self->tilemap->mapHeight){ + pl_destroyEntity(self, false); + return; + } + + self->x += self->xspeed; + self->y += self->yspeed; + + self->animationTimer++; + if (self->animationTimer == 6) + { + self->xspeed = -self->xspeed; + self->yspeed = -self->yspeed; + } + if (self->animationTimer > 12) + { + uint8_t aboveTile = (self->homeTileY == 0) ? 0 : self->tilemap->map[(self->homeTileY - 1) * self->tilemap->mapWidth + self->homeTileX]; + uint8_t belowTile = (self->homeTileY == (self->tilemap->mapHeight - 1))? 0 : self->tilemap->map[(self->homeTileY + 1) * self->tilemap->mapWidth + self->homeTileX]; + plEntity_t *createdEntity = NULL; + + switch (aboveTile) + { + case PL_TILECTNR_COIN: + case PL_TILECTNR_10COIN: + { + addCoins(self->gameData, 1); + pl_scorePoints(self->gameData, 10); + self->jumpPower = PL_TILECONTAINER_2; + break; + } + case PL_TILECTNR_POW1: + { + createdEntity = pl_createEntity(self->entityManager, ENTITY_POWERUP, (self->homeTileX * PL_TILESIZE) + PL_HALF_TILESIZE, ((self->homeTileY + ((self->yspeed < 0 && (!pl_isSolid(belowTile) && belowTile != PL_TILEBOUNCE_BLOCK))?1:-1)) * PL_TILESIZE) + PL_HALF_TILESIZE); + createdEntity->homeTileX = 0; + createdEntity->homeTileY = 0; + + self->jumpPower = PL_TILECONTAINER_2; + break; + } + case PL_TILEWARP_0 ... PL_TILEWARP_F: + { + createdEntity = pl_createEntity(self->entityManager, ENTITY_WARP, (self->homeTileX * PL_TILESIZE) + PL_HALF_TILESIZE, ((self->homeTileY + ((self->yspeed < 0 && (!pl_isSolid(belowTile) && belowTile != PL_TILEBOUNCE_BLOCK))?1:-1)) * PL_TILESIZE) + PL_HALF_TILESIZE); + + createdEntity->homeTileX = self->homeTileX; + createdEntity->homeTileY = self->homeTileY; + + createdEntity->jumpPower = aboveTile - PL_TILEWARP_0; + self->jumpPower = PL_TILECONTAINER_2; + break; + } + case PL_TILECTNR_1UP: + { + if(self->gameData->extraLifeCollected){ + addCoins(self->gameData, 1); + pl_scorePoints(self->gameData, 10); + } else { + createdEntity = pl_createEntity(self->entityManager, ENTITY_1UP, (self->homeTileX * PL_TILESIZE) + PL_HALF_TILESIZE, ((self->homeTileY + ((self->yspeed < 0 && (!pl_isSolid(belowTile) && belowTile != PL_TILEBOUNCE_BLOCK))?1:-1)) * PL_TILESIZE) + PL_HALF_TILESIZE); + createdEntity->homeTileX = 0; + createdEntity->homeTileY = 0; + self->gameData->extraLifeCollected = true; + } + + self->jumpPower = PL_TILECONTAINER_2; + break; + } + default: + { + break; + } + + } + + if(self->jumpPower == PL_TILEBRICK_BLOCK && (self->yspeed > 0 || self->yDamping == 1) && createdEntity == NULL ) { + self->jumpPower = PL_TILEEMPTY; + pl_scorePoints(self->gameData, 10); + bzrPlaySfx(&(self->soundManager->sndBreak), BZR_LEFT); + } + + self->tilemap->map[self->homeTileY * self->tilemap->mapWidth + self->homeTileX] = self->jumpPower; + + pl_destroyEntity(self, false); + } +}; + +void pl_moveEntityWithTileCollisions(plEntity_t *self) +{ + + uint16_t newX = self->x; + uint16_t newY = self->y; + uint8_t tx = PL_TO_TILECOORDS(self->x >> SUBPIXEL_RESOLUTION); + uint8_t ty = PL_TO_TILECOORDS(self->y >> SUBPIXEL_RESOLUTION); + // bool collision = false; + + // Are we inside a block? Push self out of block + uint8_t t = pl_getTile(self->tilemap, tx, ty); + self->overlapTileHandler(self, t, tx, ty); + + if (pl_isSolid(t)) + { + + if (self->xspeed == 0 && self->yspeed == 0) + { + newX += (self->spriteFlipHorizontal) ? 16 : -16; + } + else + { + if (self->yspeed != 0) + { + self->yspeed = -self->yspeed; + } + else + { + self->xspeed = -self->xspeed; + } + } + } + else + { + + if (self->yspeed != 0) + { + int16_t hcof = (((self->x >> SUBPIXEL_RESOLUTION) % PL_TILESIZE) - PL_HALF_TILESIZE); + + // Handle halfway though tile + uint8_t at = pl_getTile(self->tilemap, tx + SIGNOF(hcof), ty); + + if (pl_isSolid(at)) + { + // collision = true; + newX = ((tx + 1) * PL_TILESIZE - PL_HALF_TILESIZE) << SUBPIXEL_RESOLUTION; + } + + uint8_t newTy = PL_TO_TILECOORDS(((self->y + self->yspeed) >> SUBPIXEL_RESOLUTION) + SIGNOF(self->yspeed) * PL_HALF_TILESIZE); + + if (newTy != ty) + { + uint8_t newVerticalTile = pl_getTile(self->tilemap, tx, newTy); + + if (newVerticalTile > PL_TILEUNUSED_29 && newVerticalTile < PL_TILEBG_GOAL_ZONE) + { + if (self->tileCollisionHandler(self, newVerticalTile, tx, newTy, 2 << (self->yspeed > 0))) + { + newY = ((newTy + ((ty < newTy) ? -1 : 1)) * PL_TILESIZE + PL_HALF_TILESIZE) << SUBPIXEL_RESOLUTION; + } + } + } + } + + if (self->xspeed != 0) + { + int16_t vcof = (((self->y >> SUBPIXEL_RESOLUTION) % PL_TILESIZE) - PL_HALF_TILESIZE); + + // Handle halfway though tile + uint8_t att = pl_getTile(self->tilemap, tx, ty + SIGNOF(vcof)); + + if (pl_isSolid(att)) + { + // collision = true; + newY = ((ty + 1) * PL_TILESIZE - PL_HALF_TILESIZE) << SUBPIXEL_RESOLUTION; + } + + // Handle outside of tile + uint8_t newTx = PL_TO_TILECOORDS(((self->x + self->xspeed) >> SUBPIXEL_RESOLUTION) + SIGNOF(self->xspeed) * PL_HALF_TILESIZE); + + if (newTx != tx) + { + uint8_t newHorizontalTile = pl_getTile(self->tilemap, newTx, ty); + + if (newHorizontalTile > PL_TILEUNUSED_29 && newHorizontalTile < PL_TILEBG_GOAL_ZONE) + { + if (self->tileCollisionHandler(self, newHorizontalTile, newTx, ty, (self->xspeed > 0))) + { + newX = ((newTx + ((tx < newTx) ? -1 : 1)) * PL_TILESIZE + PL_HALF_TILESIZE) << SUBPIXEL_RESOLUTION; + } + } + + if (!self->falling) + { + uint8_t newBelowTile = pl_getTile(self->tilemap, tx, ty + 1); + + if ((self->gravityEnabled && !pl_isSolid(newBelowTile)) /*(|| (!self->gravityEnabled && newBelowTile != PL_TILELADDER)*/) + { + self->fallOffTileHandler(self); + } + } + } + } + } + + self->x = newX + self->xspeed; + self->y = newY + self->yspeed; +} + +void defaultFallOffTileHandler(plEntity_t *self){ + self->falling = true; +} + +void applyDamping(plEntity_t *self) +{ + if (!self->falling || !self->gravityEnabled) + { + if (self->xspeed > 0) + { + self->xspeed -= self->xDamping; + + if (self->xspeed < 0) + { + self->xspeed = 0; + } + } + else if (self->xspeed < 0) + { + self->xspeed += self->xDamping; + + if (self->xspeed > 0) + { + self->xspeed = 0; + } + } + } + + if (self->gravityEnabled) + { + return; + } + + if (self->yspeed > 0) + { + self->yspeed -= self->yDamping; + + if (self->yspeed < 0) + { + self->yspeed = 0; + } + } + else if (self->yspeed < 0) + { + self->yspeed += self->yDamping; + + if (self->yspeed > 0) + { + self->yspeed = 0; + } + } +} + +void applyGravity(plEntity_t *self) +{ + if (!self->gravityEnabled || !self->falling) + { + return; + } + + self->yspeed += self->gravity; + + if (self->yspeed > self->yMaxSpeed) + { + self->yspeed = self->yMaxSpeed; + } +} + +void despawnWhenOffscreen(plEntity_t *self) +{ + if ( + (self->x >> SUBPIXEL_RESOLUTION) < (self->tilemap->mapOffsetX - DESPAWN_THRESHOLD) || + (self->x >> SUBPIXEL_RESOLUTION) > (self->tilemap->mapOffsetX + PL_TILEMAP_DISPLAY_WIDTH_PIXELS + DESPAWN_THRESHOLD) + ) + { + pl_destroyEntity(self, true); + } + + if (self->y > 63616){ + return; + } + + if ( + (self->y >> SUBPIXEL_RESOLUTION) < (self->tilemap->mapOffsetY - (DESPAWN_THRESHOLD << 2)) || + (self->y >> SUBPIXEL_RESOLUTION) > (self->tilemap->mapOffsetY + PL_TILEMAP_DISPLAY_HEIGHT_PIXELS + DESPAWN_THRESHOLD) + ) + { + pl_destroyEntity(self, true); + } +} + +void pl_destroyEntity(plEntity_t *self, bool respawn) +{ + if (respawn && !(self->homeTileX == 0 && self->homeTileY == 0)) + { + self->tilemap->map[self->homeTileY * self->tilemap->mapWidth + self->homeTileX] = self->type + 128; + } + + // self->entityManager->activeEntities--; + self->active = false; +} + +void animatePlayer(plEntity_t *self) +{ + if (self->spriteIndex == SP_PLAYER_WIN || self->spriteIndex == SP_PLAYER_HURT) + { + // Win pose has been set; don't change it! + return; + } + + if (!self->gravityEnabled){ + self->spriteIndex = SP_PLAYER_CLIMB; + if(self->yspeed < 0 && self->gameData->frameCount % 10 == 0){ + self->spriteFlipHorizontal = !self->spriteFlipHorizontal; + } + } + else if (self->falling) + { + if (self->yspeed < 0) + { + // Jumping + self->spriteIndex = SP_PLAYER_JUMP; + } + else + { + // Falling + self->spriteIndex = SP_PLAYER_WALK1; + } + } + else if (self->xspeed != 0) + { + if ( ((self->gameData->btnState & PB_LEFT) && self->xspeed < 0) || ((self->gameData->btnState & PB_RIGHT) && self->xspeed > 0)) + { + // Running + self->spriteFlipHorizontal = (self->xspeed > 0) ? 0 : 1; + + if(self->gameData->frameCount % (10 - (abs(self->xspeed) >> 3) ) == 0) { + self->spriteIndex = 1 + ((self->spriteIndex + 1) % 3); + } + } + else + { + self->spriteIndex = SP_PLAYER_SLIDE; + } + } + else + { + // Standing + self->spriteIndex = SP_PLAYER_IDLE; + } +} + +void pl_detectEntityCollisions(plEntity_t *self) +{ + for (uint8_t i = 0; i < MAX_ENTITIES; i++) + { + plEntity_t *checkEntity = &(self->entityManager->entities[i]); + if (checkEntity->active && checkEntity != self) + { + uint32_t dist = abs(self->x - checkEntity->x) + abs(self->y - checkEntity->y); + + if (dist < 200) + { + self->collisionHandler(self, checkEntity); + } + } + } +} + +void pl_playerCollisionHandler(plEntity_t *self, plEntity_t *other) +{ + switch (other->type) + { + case plEntity_tEST: + case ENTITY_DUST_BUNNY: + case ENTITY_WASP: + case ENTITY_BUSH_2: + case ENTITY_BUSH_3: + case ENTITY_DUST_BUNNY_2: + case ENTITY_DUST_BUNNY_3: + case ENTITY_WASP_2: + case ENTITY_WASP_3: + { + other->xspeed = -other->xspeed; + + if (self->y < other->y || self->yspeed > 0) + { + pl_scorePoints(self->gameData, other->scoreValue); + + killEnemy(other); + bzrPlaySfx(&(self->soundManager->sndSquish), BZR_LEFT); + + self->yspeed = -180; + self->jumpPower = 64 + ((abs(self->xspeed) + 16) >> 3); + self->falling = true; + } + else if(self->invincibilityFrames <= 0) + { + self->hp--; + pl_updateLedsHpMeter(self->entityManager, self->gameData); + self->gameData->comboTimer = 0; + + if(!self->gameData->debugMode && self->hp == 0){ + self->updateFunction = &updateEntityDead; + self->type = ENTITY_DEAD; + self->xspeed = 0; + self->yspeed = -60; + self->spriteIndex = SP_PLAYER_HURT; + self->gameData->changeState = PL_ST_DEAD; + self->gravityEnabled = true; + self->falling = true; + } else { + self->xspeed = 0; + self->yspeed = 0; + self->jumpPower = 0; + self->invincibilityFrames = 120; + bzrPlaySfx(&(self->soundManager->sndHurt), BZR_LEFT); + } + } + + break; + } + case ENTITY_WARP:{ + //Execute warp + self->x = (self->tilemap->warps[other->jumpPower].x * PL_TILESIZE + PL_HALF_TILESIZE) << SUBPIXEL_RESOLUTION; + self->y = (self->tilemap->warps[other->jumpPower].y * PL_TILESIZE + PL_HALF_TILESIZE) << SUBPIXEL_RESOLUTION; + self->falling = true; + pl_viewFollowEntity(self->tilemap, self->entityManager->playerEntity); + + pl_unlockScrolling(self->tilemap); + pl_deactivateAllEntities(self->entityManager, true); + self->tilemap->executeTileSpawnAll = true; + bzrPlaySfx(&(self->soundManager->sndWarp), BZR_LEFT); + break; + } + case ENTITY_POWERUP:{ + self->hp++; + if(self->hp > 3){ + self->hp = 3; + } + pl_scorePoints(self->gameData, 1000); + bzrPlaySfx(&(self->soundManager->sndPowerUp), BZR_LEFT); + pl_updateLedsHpMeter(self->entityManager, self->gameData); + pl_destroyEntity(other, false); + break; + } + case ENTITY_1UP:{ + self->gameData->lives++; + pl_scorePoints(self->gameData, 0); + bzrPlaySfx(&(self->soundManager->snd1up), BZR_LEFT); + pl_destroyEntity(other, false); + break; + } + case ENTITY_CHECKPOINT: { + if(!other->xDamping){ + //Get tile above checkpoint + uint8_t aboveTile = self->tilemap->map[(other->homeTileY - 1) * self->tilemap->mapWidth + other->homeTileX]; + + if(aboveTile >= PL_TILEWARP_0 && aboveTile <= PL_TILEWARP_F) { + self->gameData->checkpoint = aboveTile - PL_TILEWARP_0; + other->xDamping = 1; + bzrPlaySfx(&(self->soundManager->sndCheckpoint), BZR_LEFT); + } + } + break; + } + default: + { + break; + } + } +} + +void pl_enemyCollisionHandler(plEntity_t *self, plEntity_t *other) +{ + switch (other->type) + { + case plEntity_tEST: + case ENTITY_DUST_BUNNY: + case ENTITY_WASP: + case ENTITY_BUSH_2: + case ENTITY_BUSH_3: + case ENTITY_DUST_BUNNY_2: + case ENTITY_DUST_BUNNY_3: + case ENTITY_WASP_2: + case ENTITY_WASP_3: + case ENTITY_POWERUP: + case ENTITY_1UP: + if((self->xspeed > 0 && self->x < other->x) || (self->xspeed < 0 && self->x > other->x)){ + self->xspeed = -self->xspeed; + self->spriteFlipHorizontal = -self->spriteFlipHorizontal; + } + break; + case ENTITY_HIT_BLOCK: + self->xspeed = other->xspeed*2; + self->yspeed = other->yspeed*2; + pl_scorePoints(self->gameData, self->scoreValue); + bzrPlaySfx(&(self->soundManager->sndSquish), BZR_LEFT); + killEnemy(self); + break; + case ENTITY_WAVE_BALL: + self->xspeed = other->xspeed >> 1; + self->yspeed = -abs(other->xspeed >> 1); + pl_scorePoints(self->gameData, self->scoreValue); + bzrPlaySfx(&(self->soundManager->sndBreak), BZR_LEFT); + killEnemy(self); + pl_destroyEntity(other, false); + break; + default: + { + break; + } + } +} + +void pl_dummyCollisionHandler(plEntity_t *self, plEntity_t *other) +{ + return; +} + +bool pl_playerTileCollisionHandler(plEntity_t *self, uint8_t tileId, uint8_t tx, uint8_t ty, uint8_t direction) +{ + switch (tileId) + { + case PL_TILECONTAINER_1: + case PL_TILEBRICK_BLOCK: + case PL_TILEINVISIBLE_CONTAINER: + case PL_TILEBOUNCE_BLOCK: + { + plEntity_t *hitBlock = pl_createEntity(self->entityManager, ENTITY_HIT_BLOCK, (tx * PL_TILESIZE) + PL_HALF_TILESIZE, (ty * PL_TILESIZE) + PL_HALF_TILESIZE); + + if (hitBlock != NULL) + { + + pl_setTile(self->tilemap, tx, ty, PL_TILEINVISIBLE_BLOCK); + hitBlock->homeTileX = tx; + hitBlock->homeTileY = ty; + hitBlock->jumpPower = tileId; + if (tileId == PL_TILEBRICK_BLOCK) + { + hitBlock->spriteIndex = SP_HITBLOCK_BRICKS; + if(abs(self->xspeed) > 51 && self->yspeed <= 0){ + hitBlock->yDamping = 1; + } + } + + if (tileId == PL_TILEBOUNCE_BLOCK){ + hitBlock->spriteIndex = SP_BOUNCE_BLOCK; + } + + switch (direction) + { + case 0: + hitBlock->xspeed = -24; + if(tileId == PL_TILEBOUNCE_BLOCK){ + self->xspeed = 48; + } + break; + case 1: + hitBlock->xspeed = 24; + if(tileId == PL_TILEBOUNCE_BLOCK){ + self->xspeed = -48; + } + break; + case 2: + hitBlock->yspeed = -48; + if(tileId == PL_TILEBOUNCE_BLOCK){ + self->yspeed = 48; + } + break; + case 4: + hitBlock->yspeed = (tileId == PL_TILEBRICK_BLOCK) ? 16 : 24; + if(tileId == PL_TILEBOUNCE_BLOCK){ + self->yspeed = -64; + if(self->gameData->btnState & PB_A){ + self->jumpPower = 80 + ((abs(self->xspeed) + 16) >> 3); + } + } + break; + default: + break; + } + + bzrPlaySfx(&(self->soundManager->sndHit), BZR_LEFT); + } + break; + } + case PL_TILEGOAL_100PTS: + { + if(direction == 4) { + pl_scorePoints(self->gameData, 100); + bzrStop(true); + bzrPlaySfx(&(self->soundManager->sndLevelClearD), BZR_LEFT); + self->spriteIndex = SP_PLAYER_WIN; + self->updateFunction = &pl_updateDummy; + self->gameData->changeState = PL_ST_LEVEL_CLEAR; + } + break; + } + case PL_TILEGOAL_500PTS: + { + if(direction == 4) { + pl_scorePoints(self->gameData, 500); + bzrStop(true); + bzrPlaySfx(&(self->soundManager->sndLevelClearC), BZR_LEFT); + self->spriteIndex = SP_PLAYER_WIN; + self->updateFunction = &pl_updateDummy; + self->gameData->changeState = PL_ST_LEVEL_CLEAR; + } + break; + } + case PL_TILEGOAL_1000PTS: + { + if(direction == 4) { + pl_scorePoints(self->gameData, 1000); + bzrStop(true); + bzrPlaySfx(&(self->soundManager->sndLevelClearB), BZR_LEFT); + self->spriteIndex = SP_PLAYER_WIN; + self->updateFunction = &pl_updateDummy; + self->gameData->changeState = PL_ST_LEVEL_CLEAR; + } + break; + } + case PL_TILEGOAL_2000PTS: + { + if(direction == 4) { + pl_scorePoints(self->gameData, 2000); + bzrStop(true); + bzrPlaySfx(&(self->soundManager->sndLevelClearA), BZR_LEFT); + self->spriteIndex = SP_PLAYER_WIN; + self->updateFunction = &pl_updateDummy; + self->gameData->changeState = PL_ST_LEVEL_CLEAR; + } + break; + } + case PL_TILEGOAL_5000PTS: + { + if(direction == 4) { + pl_scorePoints(self->gameData, 5000); + bzrStop(true); + bzrPlaySfx(&(self->soundManager->sndLevelClearS), BZR_LEFT); + self->spriteIndex = SP_PLAYER_WIN; + self->updateFunction = &pl_updateDummy; + self->gameData->changeState = PL_ST_LEVEL_CLEAR; + } + break; + } + /*case PL_TILECOIN_1 ... PL_TILECOIN_3: + { + pl_setTile(self->tilemap, tx, ty, PL_TILEEMPTY); + addCoins(self->gameData, 1); + pl_scorePoints(self->gameData, 50); + break; + } + case PL_TILELADDER: + { + self->gravityEnabled = false; + self->falling = false; + break; + }*/ + default: + { + break; + } + } + + if (pl_isSolid(tileId)) + { + switch (direction) + { + case 0: // LEFT + self->xspeed = 0; + break; + case 1: // RIGHT + self->xspeed = 0; + break; + case 2: // UP + self->yspeed = 0; + break; + case 4: // DOWN + // Landed on platform + self->falling = false; + self->yspeed = 0; + break; + default: // Should never hit + return false; + } + // trigger tile collision resolution + return true; + } + + return false; +} + +bool pl_enemyTileCollisionHandler(plEntity_t *self, uint8_t tileId, uint8_t tx, uint8_t ty, uint8_t direction) +{ + switch(tileId){ + case PL_TILEBOUNCE_BLOCK: { + switch (direction) + { + case 0: + //hitBlock->xspeed = -64; + if(tileId == PL_TILEBOUNCE_BLOCK){ + self->xspeed = 48; + } + break; + case 1: + //hitBlock->xspeed = 64; + if(tileId == PL_TILEBOUNCE_BLOCK){ + self->xspeed = -48; + } + break; + case 2: + //hitBlock->yspeed = -128; + if(tileId == PL_TILEBOUNCE_BLOCK){ + self->yspeed = 48; + } + break; + case 4: + //hitBlock->yspeed = (tileId == PL_TILEBRICK_BLOCK) ? 32 : 64; + if(tileId == PL_TILEBOUNCE_BLOCK){ + self->yspeed = -48; + } + break; + default: + break; + } + break; + } + default: { + break; + } + } + + if (pl_isSolid(tileId)) + { + switch (direction) + { + case 0: // LEFT + self->xspeed = -self->xspeed; + break; + case 1: // RIGHT + self->xspeed = -self->xspeed; + break; + case 2: // UP + self->yspeed = 0; + break; + case 4: // DOWN + // Landed on platform + self->falling = false; + self->yspeed = 0; + break; + default: // Should never hit + return false; + } + // trigger tile collision resolution + return true; + } + + return false; +} + +bool pl_dummyTileCollisionHandler(plEntity_t *self, uint8_t tileId, uint8_t tx, uint8_t ty, uint8_t direction) +{ + return false; +} + +void dieWhenFallingOffScreen(plEntity_t *self) +{ + uint16_t deathBoundary = (self->tilemap->mapOffsetY + PL_TILEMAP_DISPLAY_HEIGHT_PIXELS + DESPAWN_THRESHOLD); + if ( + ((self->y >> SUBPIXEL_RESOLUTION) > deathBoundary) && + ((self->y >> SUBPIXEL_RESOLUTION) < deathBoundary + DESPAWN_THRESHOLD)) + { + self->hp = 0; + pl_updateLedsHpMeter(self->entityManager, self->gameData); + self->gameData->changeState = PL_ST_DEAD; + pl_destroyEntity(self, true); + } +} + +void pl_updateDummy(plEntity_t *self) +{ + // Do nothing, because that's what dummies do! +} + +void updateScrollLockLeft(plEntity_t *self) +{ + self->tilemap->minMapOffsetX = (self->x >> SUBPIXEL_RESOLUTION) - 8; + pl_viewFollowEntity(self->entityManager->tilemap, self->entityManager->viewEntity); + pl_destroyEntity(self, true); +} + +void updateScrollLockRight(plEntity_t *self) +{ + self->tilemap->maxMapOffsetX = (self->x >> SUBPIXEL_RESOLUTION) + 8 - PL_TILEMAP_DISPLAY_WIDTH_PIXELS; + pl_viewFollowEntity(self->entityManager->tilemap, self->entityManager->viewEntity); + pl_destroyEntity(self, true); +} + +void updateScrollLockUp(plEntity_t *self) +{ + self->tilemap->minMapOffsetY = (self->y >> SUBPIXEL_RESOLUTION) - 8; + pl_viewFollowEntity(self->entityManager->tilemap, self->entityManager->viewEntity); + pl_destroyEntity(self, true); +} + +void updateScrollLockDown(plEntity_t *self) +{ + self->tilemap->maxMapOffsetY = (self->y >> SUBPIXEL_RESOLUTION) + 8 - PL_TILEMAP_DISPLAY_HEIGHT_PIXELS; + pl_viewFollowEntity(self->entityManager->tilemap, self->entityManager->viewEntity); + pl_destroyEntity(self, true); +} + +void updateScrollUnlock(plEntity_t *self) +{ + pl_unlockScrolling(self->tilemap); + pl_viewFollowEntity(self->entityManager->tilemap, self->entityManager->viewEntity); + pl_destroyEntity(self, true); +} + +void updateEntityDead(plEntity_t *self) +{ + applyGravity(self); + self->x += self->xspeed; + self->y += self->yspeed; + + despawnWhenOffscreen(self); +} + +void updatePowerUp(plEntity_t *self) +{ + if(self->gameData->frameCount % 10 == 0) { + self->spriteIndex = ((self->entityManager->playerEntity->hp < 2) ? SP_GAMING_1 : SP_MUSIC_1) + ((self->spriteIndex + 1) % 3); + } + + pl_moveEntityWithTileCollisions(self); + applyGravity(self); + despawnWhenOffscreen(self); +} + +void update1up(plEntity_t *self) +{ + if(self->gameData->frameCount % 10 == 0) { + self->spriteIndex = SP_1UP_1 + ((self->spriteIndex + 1) % 3); + } + + pl_moveEntityWithTileCollisions(self); + applyGravity(self); + despawnWhenOffscreen(self); +} + +void updateWarp(plEntity_t *self) +{ + if(self->gameData->frameCount % 10 == 0) { + self->spriteIndex = SP_WARP_1 + ((self->spriteIndex + 1) % 3); + } + + //Destroy self and respawn warp container block when offscreen + if ( + (self->x >> SUBPIXEL_RESOLUTION) < (self->tilemap->mapOffsetX - DESPAWN_THRESHOLD) || + (self->x >> SUBPIXEL_RESOLUTION) > (self->tilemap->mapOffsetX + PL_TILEMAP_DISPLAY_WIDTH_PIXELS + DESPAWN_THRESHOLD) || + (self->y >> SUBPIXEL_RESOLUTION) < (self->tilemap->mapOffsetY - DESPAWN_THRESHOLD) || + (self->y >> SUBPIXEL_RESOLUTION) > (self->tilemap->mapOffsetY + PL_TILEMAP_DISPLAY_HEIGHT_PIXELS + DESPAWN_THRESHOLD)) + { + //In pl_destroyEntity, this will overflow to the correct value. + self->type = 128 + PL_TILECONTAINER_1; + + pl_destroyEntity(self, true); + } +} + +void updateDustBunny(plEntity_t *self) +{ + if(!self->falling){ + self->yDamping--; + if(self->yDamping <= 0){ + bool directionToPlayer = (self->entityManager->playerEntity->x < self->x); + + switch(self->xDamping){ + case 0: { + self->yspeed = (int32_t)(2 + esp_random() % 3) * -24; + self->falling = true; + self->xDamping = 1; + self->yDamping = (1 + esp_random() % 3) * 9; + self->spriteIndex = SP_DUSTBUNNY_JUMP; + self->spriteFlipHorizontal = directionToPlayer; + break; + } + case 1: { + self->xDamping = 0; + self->yDamping = 30; + self->spriteIndex = SP_DUSTBUNNY_CHARGE; + self->spriteFlipHorizontal = directionToPlayer; + break; + } + default: + self->xDamping = 0; + break; + } + } + } + + despawnWhenOffscreen(self); + pl_moveEntityWithTileCollisions(self); + applyGravity(self); + pl_detectEntityCollisions(self); +}; + +void updateDustBunnyL2(plEntity_t *self) +{ + if(!self->falling){ + self->yDamping--; + if(self->yDamping <= 0){ + switch(self->xDamping){ + case 0: { + self->xspeed = (1 + esp_random() % 4) * 6 * ((self->spriteFlipHorizontal)?-1:1); + self->yspeed = (int32_t)(1 + esp_random() % 4) * -24; + self->xDamping = 1; + self->yDamping = (esp_random() % 3) * 6; + self->spriteIndex = SP_DUSTBUNNY_L2_JUMP; + break; + } + case 1: { + self->xDamping = 0; + self->yDamping = 15; + self->spriteIndex = SP_DUSTBUNNY_L2_CHARGE; + break; + } + default: + self->xDamping = 0; + break; + } + } + } + + despawnWhenOffscreen(self); + pl_moveEntityWithTileCollisions(self); + applyGravity(self); + pl_detectEntityCollisions(self); +}; + +void updateDustBunnyL3(plEntity_t *self) +{ + if(!self->falling){ + self->yDamping--; + if(self->yDamping <= 0){ + bool directionToPlayer = (self->entityManager->playerEntity->x < self->x); + + switch(self->xDamping){ + case 0: { + self->xspeed = (int32_t)(1 + esp_random() % 4) * 6 * ((directionToPlayer)?-1:1); + self->yspeed = (int32_t)(1 + esp_random() % 4) * -24; + self->xDamping = 1; + self->yDamping = (esp_random() % 3) * 30; + self->spriteIndex = SP_DUSTBUNNY_L3_JUMP; + self->spriteFlipHorizontal = directionToPlayer; + break; + } + case 1: { + self->xDamping = 0; + self->yDamping = 30; + self->spriteIndex = SP_DUSTBUNNY_L3_CHARGE; + self->spriteFlipHorizontal = directionToPlayer; + break; + } + default: + self->xDamping = 0; + break; + } + } + } + + despawnWhenOffscreen(self); + pl_moveEntityWithTileCollisions(self); + applyGravity(self); + pl_detectEntityCollisions(self); +}; + +bool dustBunnyTileCollisionHandler(plEntity_t *self, uint8_t tileId, uint8_t tx, uint8_t ty, uint8_t direction){ + switch(tileId){ + case PL_TILEBOUNCE_BLOCK: { + switch (direction) + { + case 0: + //hitBlock->xspeed = -64; + if(tileId == PL_TILEBOUNCE_BLOCK){ + self->xspeed = 48; + } + break; + case 1: + //hitBlock->xspeed = 64; + if(tileId == PL_TILEBOUNCE_BLOCK){ + self->xspeed = -48; + } + break; + case 2: + //hitBlock->yspeed = -128; + if(tileId == PL_TILEBOUNCE_BLOCK){ + self->yspeed = 48; + } + break; + case 4: + //hitBlock->yspeed = (tileId == PL_TILEBRICK_BLOCK) ? 32 : 64; + if(tileId == PL_TILEBOUNCE_BLOCK){ + self->xDamping = 0; + self->xspeed = 0; + self->yspeed = 0; + self->falling = false; + self->yDamping = -1; + } + break; + default: + break; + } + break; + } + default: { + break; + } + } + + if (pl_isSolid(tileId)) + { + switch (direction) + { + case 0: // LEFT + self->xspeed = -self->xspeed; + break; + case 1: // RIGHT + self->xspeed = -self->xspeed; + break; + case 2: // UP + self->yspeed = 0; + break; + case 4: // DOWN + // Landed on platform + self->falling = false; + self->yspeed = 0; + self->xspeed = 0; + self->spriteIndex = SP_DUSTBUNNY_IDLE; + break; + default: // Should never hit + return false; + } + // trigger tile collision resolution + return true; + } + + return false; +}; + +bool dustBunnyL2TileCollisionHandler(plEntity_t *self, uint8_t tileId, uint8_t tx, uint8_t ty, uint8_t direction){ + switch(tileId){ + case PL_TILEBOUNCE_BLOCK: { + switch (direction) + { + case 0: + //hitBlock->xspeed = -64; + if(tileId == PL_TILEBOUNCE_BLOCK){ + self->xspeed = 48; + } + break; + case 1: + //hitBlock->xspeed = 64; + if(tileId == PL_TILEBOUNCE_BLOCK){ + self->xspeed = -48; + } + break; + case 2: + //hitBlock->yspeed = -128; + if(tileId == PL_TILEBOUNCE_BLOCK){ + self->yspeed = 48; + } + break; + case 4: + //hitBlock->yspeed = (tileId == PL_TILEBRICK_BLOCK) ? 32 : 64; + if(tileId == PL_TILEBOUNCE_BLOCK){ + self->xDamping = 0; + self->xspeed = 0; + self->yspeed = 0; + self->falling = false; + self->yDamping = -1; + } + break; + default: + break; + } + break; + } + default: { + break; + } + } + + if (pl_isSolid(tileId)) + { + switch (direction) + { + case 0: // LEFT + self->xspeed = -self->xspeed; + self->spriteFlipHorizontal = false; + break; + case 1: // RIGHT + self->xspeed = -self->xspeed; + self->spriteFlipHorizontal = true; + break; + case 2: // UP + self->yspeed = 0; + break; + case 4: // DOWN + // Landed on platform + self->falling = false; + self->yspeed = 0; + self->xspeed = 0; + self->spriteIndex = SP_DUSTBUNNY_L2_IDLE; + break; + default: // Should never hit + return false; + } + // trigger tile collision resolution + return true; + } + + return false; +}; + +bool dustBunnyL3TileCollisionHandler(plEntity_t *self, uint8_t tileId, uint8_t tx, uint8_t ty, uint8_t direction){ + switch(tileId){ + case PL_TILEBOUNCE_BLOCK: { + switch (direction) + { + case 0: + //hitBlock->xspeed = -64; + if(tileId == PL_TILEBOUNCE_BLOCK){ + self->xspeed = 48; + } + break; + case 1: + //hitBlock->xspeed = 64; + if(tileId == PL_TILEBOUNCE_BLOCK){ + self->xspeed = -48; + } + break; + case 2: + //hitBlock->yspeed = -128; + if(tileId == PL_TILEBOUNCE_BLOCK){ + self->yspeed = 48; + } + break; + case 4: + //hitBlock->yspeed = (tileId == PL_TILEBRICK_BLOCK) ? 32 : 64; + if(tileId == PL_TILEBOUNCE_BLOCK){ + self->xDamping = 0; + self->xspeed = 0; + self->yspeed = 0; + self->falling = false; + self->yDamping = -1; + } + break; + default: + break; + } + break; + } + default: { + break; + } + } + + if (pl_isSolid(tileId)) + { + switch (direction) + { + case 0: // LEFT + self->xspeed = -self->xspeed; + break; + case 1: // RIGHT + self->xspeed = -self->xspeed; + break; + case 2: // UP + self->yspeed = 0; + break; + case 4: // DOWN + // Landed on platform + self->falling = false; + self->yspeed = 0; + self->xspeed = 0; + self->spriteIndex = SP_DUSTBUNNY_L3_IDLE; + break; + default: // Should never hit + return false; + } + // trigger tile collision resolution + return true; + } + + return false; +}; + +void updateWasp(plEntity_t *self) +{ + switch(self->xDamping){ + case 0: + if(self->gameData->frameCount % 5 == 0) { + self->spriteIndex = SP_WASP_1 + ((self->spriteIndex + 1) % 2); + } + self->yDamping--; + + if(self->entityManager->playerEntity->y > self->y && self->yDamping < 0 && abs(self->x - self->entityManager->playerEntity->x) < 512) { + self->xDamping = 1; + self->gravityEnabled = true; + self->falling = true; + self->spriteIndex = SP_WASP_DIVE; + self->xspeed = 0; + self->yspeed = 64; + } + break; + case 1: + if(!self->falling) { + self->yDamping--; + if(self->yDamping < 0){ + self->xDamping = 2; + self->gravityEnabled = false; + self->falling = false; + self->yspeed = -24; + self->yDamping = 120; + } + } + break; + case 2: + if(self->gameData->frameCount % 2 == 0) { + self->spriteIndex = SP_WASP_1 + ((self->spriteIndex + 1) % 2); + } + + self->yDamping--; + if(self->yDamping <0 || self->y <= ((self->homeTileY * PL_TILESIZE + 8) << SUBPIXEL_RESOLUTION )) { + self->xDamping = 0; + self->xspeed = (self->spriteFlipHorizontal)? -16 : 16; + self->yspeed = 0; + self->yDamping = (1 + esp_random() % 2) * 20; + } + default: + break; + } + + despawnWhenOffscreen(self); + pl_moveEntityWithTileCollisions(self); + applyGravity(self); + pl_detectEntityCollisions(self); +}; + +void updateWaspL2(plEntity_t *self) +{ + switch(self->xDamping){ + case 0: + if(self->gameData->frameCount % 5 == 0) { + self->spriteIndex = SP_WASP_L2_1 + ((self->spriteIndex) % 2); + } + + self->yDamping--; + if(esp_random() % 256 > 240){ + bool directionToPlayer = self->entityManager->playerEntity->x < self->x; + self->xspeed = directionToPlayer ? -24:24; + self->spriteFlipHorizontal = directionToPlayer; + } + + if(self->entityManager->playerEntity->y > self->y && self->yDamping < 0 && abs(self->x - self->entityManager->playerEntity->x) < self->jumpPower) { + self->xDamping = 1; + self->gravityEnabled = true; + self->falling = true; + self->spriteIndex = SP_WASP_L2_DIVE; + self->xspeed = 0; + self->yspeed = 96; + } + break; + case 1: + if(!self->falling) { + self->yDamping -= 2; + if(self->yDamping < 0){ + self->xDamping = 2; + self->gravityEnabled = false; + self->falling = false; + self->yspeed = -48; + self->jumpPower = (1 + esp_random() % 3) * 256; + self->yDamping = 80; + } + } + break; + case 2: + if(self->gameData->frameCount % 2 == 0) { + self->spriteIndex = SP_WASP_L2_1 + ((self->spriteIndex) % 2); + } + + self->yDamping--; + if(self->yDamping < 0 || self->y <= ((self->homeTileY * PL_TILESIZE + 8) << SUBPIXEL_RESOLUTION )) { + self->xDamping = 0; + self->xspeed = (self->spriteFlipHorizontal)? -24 : 24; + self->yspeed = 0; + self->yDamping = (1 + esp_random() % 2) * 20; + } + break; + default: + break; + } + + despawnWhenOffscreen(self); + pl_moveEntityWithTileCollisions(self); + applyGravity(self); + pl_detectEntityCollisions(self); +}; + +void updateWaspL3(plEntity_t *self) +{ + switch(self->xDamping){ + case 0: + if(self->gameData->frameCount % 5 == 0) { + self->spriteIndex = SP_WASP_L3_1 + ((self->spriteIndex + 1) % 2); + } + + self->yDamping--; + if(esp_random() % 256 > 192){ + bool directionToPlayer = self->entityManager->playerEntity->x < self->x; + self->xspeed = directionToPlayer ? -32:32; + self->spriteFlipHorizontal = directionToPlayer; + } + + if(self->entityManager->playerEntity->y > self->y && self->yDamping < 0 && abs(self->x - self->entityManager->playerEntity->x) < self->jumpPower) { + self->xDamping = 1; + self->gravityEnabled = true; + self->falling = true; + self->spriteIndex = SP_WASP_L3_DIVE; + self->xspeed = 0; + self->yspeed = 128; + } + break; + case 1: + if(!self->falling) { + self->yDamping -= 4; + if(self->yDamping < 0){ + self->xDamping = 2; + self->gravityEnabled = false; + self->falling = false; + self->yspeed = -64; + self->jumpPower = (1 + esp_random() % 3) * 256; + self->yDamping = (2 + esp_random() % 6) * 8; + } + } + break; + case 2: + if(self->gameData->frameCount % 2 == 0) { + self->spriteIndex = SP_WASP_L3_1 + ((self->spriteIndex + 1) % 2); + } + + self->yDamping--; + if(self->yDamping < 0 || self->y <= ((self->homeTileY * PL_TILESIZE + 8) << SUBPIXEL_RESOLUTION )) { + self->xDamping = 0; + self->xspeed = (self->spriteFlipHorizontal)? -32 : 32; + self->yspeed = 0; + self->yDamping = (1 + esp_random() % 2) * 20; + } + break; + default: + break; + } + + despawnWhenOffscreen(self); + pl_moveEntityWithTileCollisions(self); + applyGravity(self); + pl_detectEntityCollisions(self); +}; + +bool waspTileCollisionHandler(plEntity_t *self, uint8_t tileId, uint8_t tx, uint8_t ty, uint8_t direction){ + switch(tileId){ + case PL_TILEBOUNCE_BLOCK: { + self->xDamping = 1; + self->falling = false; + self->yDamping = 40; + + switch (direction) + { + case 0: + self->xspeed = 48; + break; + case 1: + self->xspeed = -48; + break; + case 2: + self->yspeed = 48; + break; + case 4: + self->yspeed = -48; + break; + default: + break; + } + break; + } + default: { + break; + } + } + + if (pl_isSolid(tileId)) + { + switch (direction) + { + case 0: // LEFT + case 1: // RIGHT + self->spriteFlipHorizontal = !self->spriteFlipHorizontal; + self->xspeed = -self->xspeed; + break; + case 2: // UP + self->yspeed = 0; + break; + case 4: // DOWN + // Landed on platform + self->falling = false; + self->yspeed = 0; + self->xspeed = 0; + self->xDamping = 1; + self->yDamping = 40; + + break; + default: // Should never hit + return false; + } + // trigger tile collision resolution + return true; + } + + return false; +}; + +void killEnemy(plEntity_t* target){ + target->homeTileX = 0; + target->homeTileY = 0; + target->gravityEnabled = true; + target->falling = true; + target->type = ENTITY_DEAD; + target->spriteFlipVertical = true; + target->updateFunction = &updateEntityDead; +} + +void updateBgCol(plEntity_t *self) +{ + self->gameData->bgColor = self->xDamping; + pl_destroyEntity(self, true); +} + +void turnAroundAtEdgeOfTileHandler(plEntity_t *self){ + self->falling = true; + self->xspeed = -self->xspeed; + self->yspeed = -self->gravity*4; +} + +void updateEnemyBushL3(plEntity_t* self){ + if(self->gameData->frameCount % 10 == 0) { + self->spriteFlipHorizontal = !self->spriteFlipHorizontal; + } + + self->yDamping--; + if(self->yDamping < 0){ + bool directionToPlayer = (self->entityManager->playerEntity->x < self->x); + + if( (self->xspeed < 0 && directionToPlayer) || (self->xspeed > 0 && !directionToPlayer) ){ + self->xspeed = -self->xspeed; + } else { + self->xspeed = (directionToPlayer)? -16: 16; + self->yspeed = -24; + self->falling = true; + } + + self->yDamping = (1 + esp_random() % 7) * 30; + + } + + despawnWhenOffscreen(self); + pl_moveEntityWithTileCollisions(self); + applyGravity(self); + pl_detectEntityCollisions(self); +} + +void updateCheckpoint(plEntity_t* self){ + if(self->xDamping){ + if(self->gameData->frameCount % 15 == 0) { + self->spriteIndex = SP_CHECKPOINT_ACTIVE_1 + ((self->spriteIndex + 1) % 2); + } + } + + despawnWhenOffscreen(self); +} + +void pl_playerOverlapTileHandler(plEntity_t* self, uint8_t tileId, uint8_t tx, uint8_t ty){ + switch(tileId){ + case PL_TILECOIN_1...PL_TILECOIN_3:{ + pl_setTile(self->tilemap, tx, ty, PL_TILEEMPTY); + addCoins(self->gameData, 1); + pl_scorePoints(self->gameData, 50); + break; + } + case PL_TILELADDER:{ + if(self->gravityEnabled){ + self->gravityEnabled = false; + self->xspeed = 0; + } + break; + } + default: { + break; + } + } + + if(!self->gravityEnabled && tileId != PL_TILELADDER){ + self->gravityEnabled = true; + self->falling = true; + if(self->yspeed < 0){ + self->yspeed = -32; + } + } +} + +void pl_defaultOverlapTileHandler(plEntity_t* self, uint8_t tileId, uint8_t tx, uint8_t ty){ + //Nothing to do. +} + +void updateBgmChange(plEntity_t* self){ + self->gameData->changeBgm = self->xDamping; + pl_destroyEntity(self, true); +} + +void updateWaveBall(plEntity_t* self){ + if(self->gameData->frameCount % 4 == 0) { + self->spriteIndex = (SP_WAVEBALL_1 + ((self->spriteIndex + 1) % 3)); + } + + if(self->gameData->frameCount % 4 == 0) { + self->xDamping++; + + switch(self->xDamping){ + case 0: + break; + case 1: + self->yDamping = self->xspeed+2; //((esp_random() % 2)?-16:16); + self->yspeed = -abs(self->yDamping); + self->xspeed = 0; + break; + case 2: + self->yspeed = 0; + self->xspeed = self->yDamping; + break; + case 3: + self->yDamping = self->xspeed+2; //((esp_random() % 2)?-16:16); + self->yspeed = abs(self->yDamping); + self->xspeed = 0; + break; + case 4: + self->yspeed = 0; + self->xspeed = self->yDamping; + self->xDamping = 0; + break; + default: + break; + } + } + + //self->yDamping++; + + pl_moveEntityWithTileCollisions(self); + despawnWhenOffscreen(self); +} + +// bool waveBallTileCollisionHandler(plEntity_t *self, uint8_t tileId, uint8_t tx, uint8_t ty, uint8_t direction){ +// if(self->yspeed == 0){ +// pl_destroyEntity(self, false); +// } +// return false; +// } + +void waveBallOverlapTileHandler(plEntity_t *self, uint8_t tileId, uint8_t tx, uint8_t ty){ + if(pl_isSolid(tileId) || tileId == PL_TILEBOUNCE_BLOCK){ + pl_destroyEntity(self, false); + bzrPlaySfx(&(self->soundManager->sndHit), BZR_LEFT); + } +} + +void powerUpCollisionHandler(plEntity_t *self, plEntity_t *other) +{ + switch (other->type) + { + case plEntity_tEST: + case ENTITY_DUST_BUNNY: + case ENTITY_WASP: + case ENTITY_BUSH_2: + case ENTITY_BUSH_3: + case ENTITY_DUST_BUNNY_2: + case ENTITY_DUST_BUNNY_3: + case ENTITY_WASP_2: + case ENTITY_WASP_3: + case ENTITY_POWERUP: + case ENTITY_1UP: + if((self->xspeed > 0 && self->x < other->x) || (self->xspeed < 0 && self->x > other->x)){ + self->xspeed = -self->xspeed; + } + break; + case ENTITY_HIT_BLOCK: + self->xspeed = other->xspeed; + self->yspeed = other->yspeed; + break; + default: + { + break; + } + } +} + +void killPlayer(plEntity_t *self) +{ + self->hp = 0; + pl_updateLedsHpMeter(self->entityManager, self->gameData); + + self->updateFunction = &updateEntityDead; + self->type = ENTITY_DEAD; + self->xspeed = 0; + self->yspeed = -60; + self->spriteIndex = SP_PLAYER_HURT; + self->gameData->changeState = PL_ST_DEAD; + self->falling = true; +} diff --git a/main/modes/platformer/plEntity.h b/main/modes/platformer/plEntity.h new file mode 100644 index 000000000..f3ef2b96a --- /dev/null +++ b/main/modes/platformer/plEntity.h @@ -0,0 +1,204 @@ +#ifndef _PL_ENTITY_H_ +#define _PL_ENTITY_H_ + +//============================================================================== +// Includes +//============================================================================== + +#include +#include +#include "platformer_typedef.h" +#include "plTilemap.h" +#include "plGameData.h" +#include "plSoundManager.h" + +//============================================================================== +// Enums +//============================================================================== + +typedef enum { + ENTITY_PLAYER, + plEntity_tEST, + ENTITY_SCROLL_LOCK_LEFT, + ENTITY_SCROLL_LOCK_RIGHT, + ENTITY_SCROLL_LOCK_UP, + ENTITY_SCROLL_LOCK_DOWN, + ENTITY_SCROLL_UNLOCK, + ENTITY_HIT_BLOCK, + ENTITY_DEAD, + ENTITY_POWERUP, + ENTITY_WARP, + ENTITY_DUST_BUNNY, + ENTITY_WASP, + ENTITY_BUSH_2, + ENTITY_BUSH_3, + ENTITY_DUST_BUNNY_2, + ENTITY_DUST_BUNNY_3, + ENTITY_WASP_2, + ENTITY_WASP_3, + ENTITY_BGCOL_BLUE, + ENTITY_BGCOL_YELLOW, + ENTITY_BGCOL_ORANGE, + ENTITY_BGCOL_PURPLE, + ENTITY_BGCOL_DARK_PURPLE, + ENTITY_BGCOL_BLACK, + ENTITY_BGCOL_NEUTRAL_GREEN, + ENTITY_BGCOL_DARK_RED, + ENTITY_BGCOL_DARK_GREEN, + ENTITY_1UP, + ENTITY_WAVE_BALL, + ENTITY_CHECKPOINT, + ENTITY_BGM_STOP, + ENTITY_BGM_CHANGE_1, + ENTITY_BGM_CHANGE_2, + ENTITY_BGM_CHANGE_3, + ENTITY_BGM_CHANGE_4, + ENTITY_BGM_CHANGE_5 +} plEntityIndex_t; + +//============================================================================== +// Structs +//============================================================================== + +typedef void(*pl_updateFunction_t)(struct plEntity_t *self); +typedef void(*pl_collisionHandler_t)(struct plEntity_t *self, struct plEntity_t *other); +typedef bool(*pl_tileCollisionHandler_t)(struct plEntity_t *self, uint8_t tileId, uint8_t tx, uint8_t ty, uint8_t direction); +typedef void(*pl_fallOffTileHandler_t)(struct plEntity_t *self); +typedef void(*pl_overlapTileHandler_t)(struct plEntity_t *self, uint8_t tileId, uint8_t tx, uint8_t ty); + +struct plEntity_t +{ + bool active; + //bool important; + + uint8_t type; + pl_updateFunction_t updateFunction; + + uint16_t x; + uint16_t y; + + int16_t xspeed; + int16_t yspeed; + + int16_t xMaxSpeed; + int16_t yMaxSpeed; + + int16_t xDamping; + int16_t yDamping; + + bool gravityEnabled; + int16_t gravity; + bool falling; + + uint8_t spriteIndex; + bool spriteFlipHorizontal; + bool spriteFlipVertical; + uint8_t animationTimer; + + plTilemap_t * tilemap; + plGameData_t * gameData; + plSoundManager_t * soundManager; + + uint8_t homeTileX; + uint8_t homeTileY; + + int16_t jumpPower; + + bool visible; + uint8_t hp; + int8_t invincibilityFrames; + uint16_t scoreValue; + + //plEntity_t *entities; + plEntityManager_t *entityManager; + + pl_collisionHandler_t collisionHandler; + pl_tileCollisionHandler_t tileCollisionHandler; + pl_fallOffTileHandler_t fallOffTileHandler; + pl_overlapTileHandler_t overlapTileHandler; +}; + +//============================================================================== +// Prototypes +//============================================================================== +void pl_initializeEntity(plEntity_t * self, plEntityManager_t * entityManager, plTilemap_t * tilemap, plGameData_t * gameData, plSoundManager_t * soundManager); + +void pl_updatePlayer(plEntity_t * self); +void updateTestObject(plEntity_t * self); +void updateHitBlock(plEntity_t * self); + +void pl_moveEntityWithTileCollisions(plEntity_t * self); +void defaultFallOffTileHandler(plEntity_t *self); + +void despawnWhenOffscreen(plEntity_t *self); + +void pl_destroyEntity(plEntity_t *self, bool respawn); + +void applyDamping(plEntity_t *self); + +void applyGravity(plEntity_t *self); + +void animatePlayer(plEntity_t * self); + +void pl_detectEntityCollisions(plEntity_t *self); + +void pl_playerCollisionHandler(plEntity_t *self, plEntity_t* other); +void pl_enemyCollisionHandler(plEntity_t *self, plEntity_t *other); +void pl_dummyCollisionHandler(plEntity_t *self, plEntity_t *other); + +bool pl_playerTileCollisionHandler(plEntity_t *self, uint8_t tileId, uint8_t tx, uint8_t ty, uint8_t direction); +bool pl_enemyTileCollisionHandler(plEntity_t *self, uint8_t tileId, uint8_t tx, uint8_t ty, uint8_t direction); +bool pl_dummyTileCollisionHandler(plEntity_t *self, uint8_t tileId, uint8_t tx, uint8_t ty, uint8_t direction); + +void dieWhenFallingOffScreen(plEntity_t *self); + +void pl_updateDummy(plEntity_t* self); + +void updateScrollLockLeft(plEntity_t* self); +void updateScrollLockRight(plEntity_t* self); +void updateScrollLockUp(plEntity_t* self); +void updateScrollLockDown(plEntity_t* self); +void updateScrollUnlock(plEntity_t* self); + +void updateEntityDead(plEntity_t* self); + +void updatePowerUp(plEntity_t* self); +void update1up(plEntity_t* self); +void updateWarp(plEntity_t* self); + +void updateDustBunny(plEntity_t* self); +void updateDustBunnyL2(plEntity_t* self); +void updateDustBunnyL3(plEntity_t* self); +bool dustBunnyTileCollisionHandler(plEntity_t *self, uint8_t tileId, uint8_t tx, uint8_t ty, uint8_t direction); +bool dustBunnyL2TileCollisionHandler(plEntity_t *self, uint8_t tileId, uint8_t tx, uint8_t ty, uint8_t direction); +bool dustBunnyL3TileCollisionHandler(plEntity_t *self, uint8_t tileId, uint8_t tx, uint8_t ty, uint8_t direction); + + +void updateWasp(plEntity_t* self); +void updateWaspL2(plEntity_t* self); +void updateWaspL3(plEntity_t* self); +bool waspTileCollisionHandler(plEntity_t *self, uint8_t tileId, uint8_t tx, uint8_t ty, uint8_t direction); + +void killEnemy(plEntity_t* target); + +void updateBgCol(plEntity_t* self); + +void turnAroundAtEdgeOfTileHandler(plEntity_t *self); + +void updateEnemyBushL3(plEntity_t* self); + +void updateCheckpoint(plEntity_t* self); + +void pl_playerOverlapTileHandler(plEntity_t* self, uint8_t tileId, uint8_t tx, uint8_t ty); +void pl_defaultOverlapTileHandler(plEntity_t* self, uint8_t tileId, uint8_t tx, uint8_t ty); + +void updateBgmChange(plEntity_t* self); + +void updateWaveBall(plEntity_t* self); + +// bool waveBallTileCollisionHandler(plEntity_t *self, uint8_t tileId, uint8_t tx, uint8_t ty, uint8_t direction); +void waveBallOverlapTileHandler(plEntity_t *self, uint8_t tileId, uint8_t tx, uint8_t ty); +void powerUpCollisionHandler(plEntity_t *self, plEntity_t *other); +void killPlayer(plEntity_t *self); + +#endif diff --git a/main/modes/platformer/plEntityManager.c b/main/modes/platformer/plEntityManager.c new file mode 100644 index 000000000..679e022fe --- /dev/null +++ b/main/modes/platformer/plEntityManager.c @@ -0,0 +1,1343 @@ +//============================================================================== +// Includes +//============================================================================== + +#include +#include +#include + +#include "plEntityManager.h" +#include "esp_random.h" +#include "palette.h" + +#include "hdw-spiffs.h" +#include "spiffs_wsg.h" + +//============================================================================== +// Constants +//============================================================================== +#define SUBPIXEL_RESOLUTION 4 + +//============================================================================== +// Functions +//============================================================================== +void pl_initializeEntityManager(plEntityManager_t * entityManager, plTilemap_t * tilemap, plGameData_t * gameData, plSoundManager_t * soundManager) +{ + pl_loadSprites(entityManager); + entityManager->entities = calloc(MAX_ENTITIES, sizeof(plEntity_t)); + + for(uint8_t i=0; i < MAX_ENTITIES; i++) + { + pl_initializeEntity(&(entityManager->entities[i]), entityManager, tilemap, gameData, soundManager); + } + + entityManager->activeEntities = 0; + entityManager->tilemap = tilemap; + + + //entityManager->viewEntity = pl_createPlayer(entityManager, entityManager->tilemap->warps[0].x * 16, entityManager->tilemap->warps[0].y * 16); + entityManager->playerEntity = entityManager->viewEntity; +}; + +void pl_loadSprites(plEntityManager_t * entityManager) +{ + loadWsg("sprite000.wsg", &entityManager->sprites[SP_PLAYER_IDLE], false); + loadWsg("sprite001.wsg", &entityManager->sprites[SP_PLAYER_WALK1], false); + loadWsg("sprite002.wsg", &entityManager->sprites[SP_PLAYER_WALK2], false); + loadWsg("sprite003.wsg", &entityManager->sprites[SP_PLAYER_WALK3], false); + loadWsg("sprite004.wsg", &entityManager->sprites[SP_PLAYER_JUMP], false); + loadWsg("sprite005.wsg", &entityManager->sprites[SP_PLAYER_SLIDE], false); + loadWsg("sprite006.wsg", &entityManager->sprites[SP_PLAYER_HURT], false); + loadWsg("sprite007.wsg", &entityManager->sprites[SP_PLAYER_CLIMB], false); + loadWsg("sprite008.wsg", &entityManager->sprites[SP_PLAYER_WIN], false); + loadWsg("sprite009.wsg", &entityManager->sprites[SP_ENEMY_BASIC], false); + loadWsg("tile066.wsg", &entityManager->sprites[SP_HITBLOCK_CONTAINER], false); + loadWsg("tile034.wsg", &entityManager->sprites[SP_HITBLOCK_BRICKS], false); + loadWsg("sprite012.wsg", &entityManager->sprites[SP_DUSTBUNNY_IDLE], false); + loadWsg("sprite013.wsg", &entityManager->sprites[SP_DUSTBUNNY_CHARGE], false); + loadWsg("sprite014.wsg", &entityManager->sprites[SP_DUSTBUNNY_JUMP], false); + loadWsg("sprite015.wsg", &entityManager->sprites[SP_GAMING_1], false); + loadWsg("sprite016.wsg", &entityManager->sprites[SP_GAMING_2], false); + loadWsg("sprite017.wsg", &entityManager->sprites[SP_GAMING_3], false); + loadWsg("sprite018.wsg", &entityManager->sprites[SP_MUSIC_1], false); + loadWsg("sprite019.wsg", &entityManager->sprites[SP_MUSIC_2], false); + loadWsg("sprite020.wsg", &entityManager->sprites[SP_MUSIC_3], false); + loadWsg("sprite021.wsg", &entityManager->sprites[SP_WARP_1], false); + loadWsg("sprite022.wsg", &entityManager->sprites[SP_WARP_2], false); + loadWsg("sprite023.wsg", &entityManager->sprites[SP_WARP_3], false); + loadWsg("sprite024.wsg", &entityManager->sprites[SP_WASP_1], false); + loadWsg("sprite025.wsg", &entityManager->sprites[SP_WASP_2], false); + loadWsg("sprite026.wsg", &entityManager->sprites[SP_WASP_DIVE], false); + loadWsg("sprite027.wsg", &entityManager->sprites[SP_1UP_1], false); + loadWsg("sprite028.wsg", &entityManager->sprites[SP_1UP_2], false); + loadWsg("sprite029.wsg", &entityManager->sprites[SP_1UP_3], false); + loadWsg("sprite030.wsg", &entityManager->sprites[SP_WAVEBALL_1], false); + loadWsg("sprite031.wsg", &entityManager->sprites[SP_WAVEBALL_2], false); + loadWsg("sprite032.wsg", &entityManager->sprites[SP_WAVEBALL_3], false); + loadWsg("sprite033.wsg", &entityManager->sprites[SP_ENEMY_BUSH_L2], false); + loadWsg("sprite034.wsg", &entityManager->sprites[SP_ENEMY_BUSH_L3], false); + loadWsg("sprite035.wsg", &entityManager->sprites[SP_DUSTBUNNY_L2_IDLE], false); + loadWsg("sprite036.wsg", &entityManager->sprites[SP_DUSTBUNNY_L2_CHARGE], false); + loadWsg("sprite037.wsg", &entityManager->sprites[SP_DUSTBUNNY_L2_JUMP], false); + loadWsg("sprite038.wsg", &entityManager->sprites[SP_DUSTBUNNY_L3_IDLE], false); + loadWsg("sprite039.wsg", &entityManager->sprites[SP_DUSTBUNNY_L3_CHARGE], false); + loadWsg("sprite040.wsg", &entityManager->sprites[SP_DUSTBUNNY_L3_JUMP], false); + loadWsg("sprite041.wsg", &entityManager->sprites[SP_WASP_L2_1], false); + loadWsg("sprite042.wsg", &entityManager->sprites[SP_WASP_L2_2], false); + loadWsg("sprite043.wsg", &entityManager->sprites[SP_WASP_L2_DIVE], false); + loadWsg("sprite044.wsg", &entityManager->sprites[SP_WASP_L3_1], false); + loadWsg("sprite045.wsg", &entityManager->sprites[SP_WASP_L3_2], false); + loadWsg("sprite046.wsg", &entityManager->sprites[SP_WASP_L3_DIVE], false); + loadWsg("sprite047.wsg", &entityManager->sprites[SP_CHECKPOINT_INACTIVE], false); + loadWsg("sprite048.wsg", &entityManager->sprites[SP_CHECKPOINT_ACTIVE_1], false); + loadWsg("sprite049.wsg", &entityManager->sprites[SP_CHECKPOINT_ACTIVE_2], false); + loadWsg("tile039.wsg", &entityManager->sprites[SP_BOUNCE_BLOCK], false); +}; + +void pl_updateEntities(plEntityManager_t * entityManager) +{ + for(uint8_t i=0; i < MAX_ENTITIES; i++) + { + if(entityManager->entities[i].active) + { + entityManager->entities[i].updateFunction(&(entityManager->entities[i])); + + if(&(entityManager->entities[i]) == entityManager->viewEntity){ + pl_viewFollowEntity(entityManager->tilemap, &(entityManager->entities[i])); + } + } + } +}; + +void pl_deactivateAllEntities(plEntityManager_t * entityManager, bool excludePlayer){ + for(uint8_t i=0; i < MAX_ENTITIES; i++) + { + plEntity_t* currentEntity = &(entityManager->entities[i]); + + currentEntity->active = false; + + //TODO: respawn warp container blocks + /* + if(currentEntity->type == ENTITY_WARP){ + //In pl_destroyEntity, this will overflow to the correct value. + currentEntity->type = 128 + PL_TILECONTAINER_1; + } + */ + + if(excludePlayer && currentEntity == entityManager->playerEntity){ + currentEntity->active = true; + } + } +} + +void pl_drawEntities(plEntityManager_t * entityManager) +{ + for(uint8_t i=0; i < MAX_ENTITIES; i++) + { + plEntity_t currentEntity = entityManager->entities[i]; + + if(currentEntity.active && currentEntity.visible) + { + drawWsg(&entityManager->sprites[currentEntity.spriteIndex], (currentEntity.x >> SUBPIXEL_RESOLUTION) - 8 - entityManager->tilemap->mapOffsetX, (currentEntity.y >> SUBPIXEL_RESOLUTION) - entityManager->tilemap->mapOffsetY - 8, currentEntity.spriteFlipHorizontal, currentEntity.spriteFlipVertical, 0); + } + } +}; + +plEntity_t * pl_findInactiveEntity(plEntityManager_t * entityManager) +{ + if(entityManager->activeEntities == MAX_ENTITIES) + { + return NULL; + }; + + uint8_t entityIndex = 0; + + while(entityManager->entities[entityIndex].active){ + entityIndex++; + + //Extra safeguard to make sure we don't get stuck here + if(entityIndex > MAX_ENTITIES) + { + return NULL; + } + } + + return &(entityManager->entities[entityIndex]); +} + +void pl_viewFollowEntity(plTilemap_t * tilemap, plEntity_t * entity){ + int16_t moveViewByX = (entity->x) >> SUBPIXEL_RESOLUTION; + int16_t moveViewByY = (entity->y > 63616) ? 0: (entity->y) >> SUBPIXEL_RESOLUTION; + + int16_t centerOfViewX = tilemap->mapOffsetX + 140; + int16_t centerOfViewY = tilemap->mapOffsetY + 120; + + //if(centerOfViewX != moveViewByX) { + moveViewByX -= centerOfViewX; + //} + + //if(centerOfViewY != moveViewByY) { + moveViewByY -= centerOfViewY; + //} + + //if(moveViewByX && moveViewByY){ + pl_scrollTileMap(tilemap, moveViewByX, moveViewByY); + //} +} + +plEntity_t* pl_createEntity(plEntityManager_t *entityManager, uint8_t objectIndex, uint16_t x, uint16_t y){ + //if(entityManager->activeEntities == MAX_ENTITIES){ + // return NULL; + //} + + plEntity_t *createdEntity; + + switch(objectIndex){ + case ENTITY_PLAYER: + createdEntity = pl_createPlayer(entityManager, x, y); + break; + case plEntity_tEST: + createdEntity = createTestObject(entityManager, x, y); + break; + case ENTITY_SCROLL_LOCK_LEFT: + createdEntity = createScrollLockLeft(entityManager, x, y); + break; + case ENTITY_SCROLL_LOCK_RIGHT: + createdEntity = createScrollLockRight(entityManager, x, y); + break; + case ENTITY_SCROLL_LOCK_UP: + createdEntity = createScrollLockUp(entityManager, x, y); + break; + case ENTITY_SCROLL_LOCK_DOWN: + createdEntity = createScrollLockDown(entityManager, x, y); + break; + case ENTITY_SCROLL_UNLOCK: + createdEntity = createScrollUnlock(entityManager, x, y); + break; + case ENTITY_HIT_BLOCK: + createdEntity = createHitBlock(entityManager, x, y); + break; + case ENTITY_POWERUP: + createdEntity = createPowerUp(entityManager, x, y); + break; + case ENTITY_WARP: + createdEntity = createWarp(entityManager, x, y); + break; + case ENTITY_DUST_BUNNY: + createdEntity = createDustBunny(entityManager, x, y); + break; + case ENTITY_WASP: + createdEntity = createWasp(entityManager, x, y); + break; + case ENTITY_BUSH_2: + createdEntity = createEnemyBushL2(entityManager, x, y); + break; + case ENTITY_BUSH_3: + createdEntity = createEnemyBushL3(entityManager, x, y); + break; + case ENTITY_DUST_BUNNY_2: + createdEntity = createDustBunnyL2(entityManager, x, y); + break; + case ENTITY_DUST_BUNNY_3: + createdEntity = createDustBunnyL3(entityManager, x, y); + break; + case ENTITY_WASP_2: + createdEntity = createWaspL2(entityManager, x, y); + break; + case ENTITY_WASP_3: + createdEntity = createWaspL3(entityManager, x, y); + break; + case ENTITY_BGCOL_BLUE: + createdEntity = createBgColBlue(entityManager, x, y); + break; + case ENTITY_BGCOL_YELLOW: + createdEntity = createBgColYellow(entityManager, x, y); + break; + case ENTITY_BGCOL_ORANGE: + createdEntity = createBgColOrange(entityManager, x, y); + break; + case ENTITY_BGCOL_PURPLE: + createdEntity = createBgColPurple(entityManager, x, y); + break; + case ENTITY_BGCOL_DARK_PURPLE: + createdEntity = createBgColDarkPurple(entityManager, x, y); + break; + case ENTITY_BGCOL_BLACK: + createdEntity = createBgColBlack(entityManager, x, y); + break; + case ENTITY_BGCOL_NEUTRAL_GREEN: + createdEntity = createBgColNeutralGreen(entityManager, x, y); + break; + case ENTITY_BGCOL_DARK_RED: + createdEntity = createBgColNeutralDarkRed(entityManager, x, y); + break; + case ENTITY_BGCOL_DARK_GREEN: + createdEntity = createBgColNeutralDarkGreen(entityManager, x, y); + break; + case ENTITY_1UP: + createdEntity = create1up(entityManager, x, y); + break; + case ENTITY_WAVE_BALL: + createdEntity = createWaveBall(entityManager, x, y); + break; + case ENTITY_CHECKPOINT: + createdEntity = createCheckpoint(entityManager, x, y); + break; + case ENTITY_BGM_STOP: + createdEntity = createBgmStop(entityManager, x, y); + break; + case ENTITY_BGM_CHANGE_1: + createdEntity = createBgmChange1(entityManager, x, y); + break; + case ENTITY_BGM_CHANGE_2: + createdEntity = createBgmChange2(entityManager, x, y); + break; + case ENTITY_BGM_CHANGE_3: + createdEntity = createBgmChange3(entityManager, x, y); + break; + case ENTITY_BGM_CHANGE_4: + createdEntity = createBgmChange4(entityManager, x, y); + break; + case ENTITY_BGM_CHANGE_5: + createdEntity = createBgmChange5(entityManager, x, y); + break; + + default: + createdEntity = NULL; + } + + //if(createdEntity != NULL) { + // entityManager->activeEntities++; + //} + + return createdEntity; +} + +plEntity_t* pl_createPlayer(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = true; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + + entity->xspeed = 0; + entity->yspeed = 0; + entity->xMaxSpeed = 40; //72; Walking + entity->yMaxSpeed = 64; //72; + entity->xDamping = 1; + entity->yDamping = 4; + entity->gravityEnabled = true; + entity->gravity = 4; + entity->falling = true; + entity->jumpPower = 0; + entity->spriteFlipVertical = false; + entity->hp = 1; + entity->animationTimer = 0; //Used as a cooldown for shooting square wave balls + + entity->type = ENTITY_PLAYER; + entity->spriteIndex = SP_PLAYER_IDLE; + entity->updateFunction = &pl_updatePlayer; + entity->collisionHandler = &pl_playerCollisionHandler; + entity->tileCollisionHandler = &pl_playerTileCollisionHandler; + entity->fallOffTileHandler = &defaultFallOffTileHandler; + entity->overlapTileHandler = &pl_playerOverlapTileHandler; + return entity; +} + +plEntity_t* createTestObject(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = true; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + + entity->xspeed = (x < (entityManager->tilemap->mapOffsetX + 120)) ? 8 : -8; + entity->yspeed = 0; + entity->xMaxSpeed = 132; + entity->yMaxSpeed = 132; + entity->gravityEnabled = true; + entity->gravity = 4; + entity->spriteFlipHorizontal = false; + entity->spriteFlipVertical = false; + entity->scoreValue = 100; + + entity->type = plEntity_tEST; + entity->spriteIndex = SP_ENEMY_BASIC; + entity->updateFunction = &updateTestObject; + entity->collisionHandler = &pl_enemyCollisionHandler; + entity->tileCollisionHandler = &pl_enemyTileCollisionHandler; + entity->fallOffTileHandler = &defaultFallOffTileHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createScrollLockLeft(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = false; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + + entity->type = ENTITY_SCROLL_LOCK_LEFT; + entity->updateFunction = &updateScrollLockLeft; + entity->collisionHandler = &pl_dummyCollisionHandler; + entity->tileCollisionHandler = &pl_dummyTileCollisionHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createScrollLockRight(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = false; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + + entity->type = ENTITY_SCROLL_LOCK_RIGHT; + entity->updateFunction = &updateScrollLockRight; + entity->collisionHandler = &pl_dummyCollisionHandler; + entity->tileCollisionHandler = &pl_dummyTileCollisionHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createScrollLockUp(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = false; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + + entity->type = ENTITY_SCROLL_LOCK_UP; + entity->updateFunction = &updateScrollLockUp; + entity->collisionHandler = &pl_dummyCollisionHandler; + entity->tileCollisionHandler = &pl_dummyTileCollisionHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createScrollLockDown(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = false; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + + entity->type = ENTITY_SCROLL_LOCK_DOWN; + entity->updateFunction = &updateScrollLockDown; + entity->collisionHandler = &pl_dummyCollisionHandler; + entity->tileCollisionHandler = &pl_dummyTileCollisionHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createScrollUnlock(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = false; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + + entity->type = ENTITY_SCROLL_UNLOCK; + entity->updateFunction = &updateScrollUnlock; + entity->collisionHandler = &pl_dummyCollisionHandler; + entity->tileCollisionHandler = &pl_dummyTileCollisionHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createHitBlock(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = true; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + + entity->xspeed = 0; + entity->yspeed = 0; + entity->yDamping = 0; + entity->xMaxSpeed = 132; + entity->yMaxSpeed = 132; + entity->gravityEnabled = true; + entity->gravity = 4; + + entity->spriteFlipHorizontal = false; + entity->spriteFlipVertical = false; + + entity->type = ENTITY_HIT_BLOCK; + entity->spriteIndex = SP_HITBLOCK_CONTAINER; + entity->animationTimer = 0; + entity->updateFunction = &updateHitBlock; + entity->collisionHandler = &pl_dummyCollisionHandler; + entity->tileCollisionHandler = &pl_dummyTileCollisionHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createPowerUp(plEntityManager_t * entityManager, uint16_t x, uint16_t y){ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = true; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + + entity->xspeed = (entityManager->playerEntity->x > entity->x)? -16: 16; + entity->yspeed = 0; + entity->xMaxSpeed = 132; + entity->yMaxSpeed = 132; + entity->gravityEnabled = true; + entity->gravity = 4; + entity->spriteFlipHorizontal = false; + entity->spriteFlipVertical = false; + + entity->type = ENTITY_POWERUP; + entity->spriteIndex = (entityManager->playerEntity->hp < 2) ? SP_GAMING_1 : SP_MUSIC_1; + entity->animationTimer = 0; + entity->updateFunction = &updatePowerUp; + entity->collisionHandler = &powerUpCollisionHandler; + entity->tileCollisionHandler = &pl_enemyTileCollisionHandler; + entity->fallOffTileHandler = &defaultFallOffTileHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +}; + +plEntity_t* createWarp(plEntityManager_t * entityManager, uint16_t x, uint16_t y){ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = true; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + + entity->xspeed = 0; + entity->yspeed = 0; + entity->xMaxSpeed = 132; + entity->yMaxSpeed = 132; + entity->gravityEnabled = true; + entity->gravity = 4; + + entity->spriteFlipVertical = false; + + entity->type = ENTITY_WARP; + entity->spriteIndex = SP_WARP_1; + entity->animationTimer = 0; + entity->updateFunction = &updateWarp; + entity->collisionHandler = &pl_dummyCollisionHandler; + entity->tileCollisionHandler = &pl_dummyTileCollisionHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +}; + +plEntity_t* createDustBunny(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = true; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + + entity->xspeed = 0; + entity->yspeed = 0; + entity->xMaxSpeed = 132; + entity->yMaxSpeed = 132; + entity->xDamping = 0; //This will be repurposed to track state + entity->yDamping = 0; //This will be repurposed as a state timer + entity->gravityEnabled = true; + entity->gravity = 4; + entity->spriteFlipHorizontal = (x < (entityManager->tilemap->mapOffsetX + 120)) ? true : false; + entity->spriteFlipVertical = false; + + entity->scoreValue = 150; + + entity->type = ENTITY_DUST_BUNNY; + entity->spriteIndex = SP_DUSTBUNNY_IDLE; + entity->updateFunction = &updateDustBunny; + entity->collisionHandler = &pl_enemyCollisionHandler; + entity->tileCollisionHandler = &dustBunnyTileCollisionHandler; + entity->fallOffTileHandler = &defaultFallOffTileHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createWasp(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = true; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + + entity->yspeed = 0; + entity->xMaxSpeed = 132; + entity->yMaxSpeed = 128; + entity->xDamping = 0; //This will be repurposed to track state + entity->yDamping = 0; //This will be repurposed as a state timer + entity->gravityEnabled = false; + entity->gravity = 8; + entity->spriteFlipHorizontal = (x < (entityManager->tilemap->mapOffsetX + 120)) ? false : true; + entity->spriteFlipVertical = false; + entity->scoreValue = 200; + + entity->xspeed = (entity->spriteFlipHorizontal)? -16 : 16; + + entity->type = ENTITY_WASP; + entity->spriteIndex = SP_WASP_1; + entity->updateFunction = &updateWasp; + entity->collisionHandler = &pl_enemyCollisionHandler; + entity->tileCollisionHandler = &waspTileCollisionHandler; + entity->fallOffTileHandler = &defaultFallOffTileHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createEnemyBushL2(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = true; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + + entity->xspeed = (x < (entityManager->tilemap->mapOffsetX + 120)) ? 12 : -12; + entity->yspeed = 0; + entity->xMaxSpeed = 132; + entity->yMaxSpeed = 132; + entity->gravityEnabled = true; + entity->gravity = 4; + entity->spriteFlipHorizontal = false; + entity->spriteFlipVertical = false; + entity->scoreValue = 150; + + entity->type = ENTITY_BUSH_2; + entity->spriteIndex = SP_ENEMY_BUSH_L2; + entity->updateFunction = &updateTestObject; + entity->collisionHandler = &pl_enemyCollisionHandler; + entity->tileCollisionHandler = &pl_enemyTileCollisionHandler; + entity->fallOffTileHandler = &turnAroundAtEdgeOfTileHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createEnemyBushL3(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = true; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + + entity->xspeed = (x < (entityManager->tilemap->mapOffsetX + 120)) ? 11 : -11; + entity->yspeed = 0; + entity->xMaxSpeed = 132; + entity->yMaxSpeed = 132; + entity->gravityEnabled = true; + entity->gravity = 4; + entity->spriteFlipHorizontal = false; + entity->spriteFlipVertical = false; + entity->scoreValue = 250; + + entity->yDamping = 20; //This will be repurposed as a state timer + + entity->type = ENTITY_BUSH_3; + entity->spriteIndex = SP_ENEMY_BUSH_L3; + entity->updateFunction = &updateEnemyBushL3; + entity->collisionHandler = &pl_enemyCollisionHandler; + entity->tileCollisionHandler = &pl_enemyTileCollisionHandler; + entity->fallOffTileHandler = &turnAroundAtEdgeOfTileHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createDustBunnyL2(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = true; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + + entity->xspeed = 0; + entity->yspeed = 0; + entity->xMaxSpeed = 132; + entity->yMaxSpeed = 132; + entity->xDamping = 0; //This will be repurposed to track state + entity->yDamping = 0; //This will be repurposed as a state timer + entity->gravityEnabled = true; + entity->gravity = 4; + entity->spriteFlipHorizontal = (x < (entityManager->tilemap->mapOffsetX + 120)) ? false : true; + entity->spriteFlipVertical = false; + entity->scoreValue = 200; + + entity->type = ENTITY_DUST_BUNNY_2; + entity->spriteIndex = SP_DUSTBUNNY_L2_IDLE; + entity->updateFunction = &updateDustBunnyL2; + entity->collisionHandler = &pl_enemyCollisionHandler; + entity->tileCollisionHandler = &dustBunnyL2TileCollisionHandler; + entity->fallOffTileHandler = &defaultFallOffTileHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createDustBunnyL3(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = true; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + + entity->xspeed = 0; + entity->yspeed = 0; + entity->xMaxSpeed = 132; + entity->yMaxSpeed = 132; + entity->xDamping = 0; //This will be repurposed to track state + entity->yDamping = 0; //This will be repurposed as a state timer + entity->gravityEnabled = true; + entity->gravity = 4; + entity->spriteFlipHorizontal = (x < (entityManager->tilemap->mapOffsetX + 120)) ? true : false; + entity->spriteFlipVertical = false; + entity->scoreValue = 300; + + entity->type = ENTITY_DUST_BUNNY_3; + entity->spriteIndex = SP_DUSTBUNNY_L3_IDLE; + entity->updateFunction = &updateDustBunnyL3; + entity->collisionHandler = &pl_enemyCollisionHandler; + entity->tileCollisionHandler = &dustBunnyL3TileCollisionHandler; + entity->fallOffTileHandler = &defaultFallOffTileHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createWaspL2(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = true; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + + entity->yspeed = 0; + entity->xMaxSpeed = 132; + entity->yMaxSpeed = 192; + entity->xDamping = 0; //This will be repurposed to track state + entity->yDamping = 0; //This will be repurposed as a state timer + entity->jumpPower = (1 + esp_random() % 4) * 256; + entity->gravityEnabled = false; + entity->gravity = 8; + entity->spriteFlipHorizontal = (x < (entityManager->tilemap->mapOffsetX + 120)) ? false : true; + entity->spriteFlipVertical = false; + entity->falling = false; + entity->scoreValue = 300; + + entity->xspeed = (entity->spriteFlipHorizontal)? -24 : 24; + + entity->type = ENTITY_WASP_2; + entity->spriteIndex = SP_WASP_L2_1; + entity->updateFunction = &updateWaspL2; + entity->collisionHandler = &pl_enemyCollisionHandler; + entity->tileCollisionHandler = &waspTileCollisionHandler; + entity->fallOffTileHandler = &defaultFallOffTileHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createWaspL3(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = true; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + + entity->yspeed = 0; + entity->xMaxSpeed = 132; + entity->yMaxSpeed = 256; + entity->xDamping = 0; //This will be repurposed to track state + entity->yDamping = 0; //This will be repurposed as a state timer + entity->jumpPower = (1 + esp_random() % 4) * 256; + entity->gravityEnabled = false; + entity->gravity = 8; + entity->spriteFlipHorizontal = (x < (entityManager->tilemap->mapOffsetX + 120)) ? false : true; + entity->spriteFlipVertical = false; + entity->scoreValue = 400; + + entity->xspeed = (entity->spriteFlipHorizontal)? -24 : 24; + + entity->type = ENTITY_WASP_3; + entity->spriteIndex = SP_WASP_L3_1; + entity->updateFunction = &updateWaspL3; + entity->collisionHandler = &pl_enemyCollisionHandler; + entity->tileCollisionHandler = &waspTileCollisionHandler; + entity->fallOffTileHandler = &defaultFallOffTileHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createBgColBlue(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = false; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + entity->xDamping = c335; + + entity->type = ENTITY_BGCOL_BLUE; + entity->updateFunction = &updateBgCol; + entity->collisionHandler = &pl_dummyCollisionHandler; + entity->tileCollisionHandler = &pl_dummyTileCollisionHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createBgColYellow(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = false; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + entity->xDamping = c542; + + entity->type = ENTITY_BGCOL_YELLOW; + entity->updateFunction = &updateBgCol; + entity->collisionHandler = &pl_dummyCollisionHandler; + entity->tileCollisionHandler = &pl_dummyTileCollisionHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createBgColOrange(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = false; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + entity->xDamping = c532; + + entity->type = ENTITY_BGCOL_ORANGE; + entity->updateFunction = &updateBgCol; + entity->collisionHandler = &pl_dummyCollisionHandler; + entity->tileCollisionHandler = &pl_dummyTileCollisionHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createBgColPurple(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = false; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + entity->xDamping = c214; + + entity->type = ENTITY_BGCOL_PURPLE; + entity->updateFunction = &updateBgCol; + entity->collisionHandler = &pl_dummyCollisionHandler; + entity->tileCollisionHandler = &pl_dummyTileCollisionHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createBgColDarkPurple(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = false; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + entity->xDamping = c103; + + entity->type = ENTITY_BGCOL_DARK_PURPLE; + entity->updateFunction = &updateBgCol; + entity->collisionHandler = &pl_dummyCollisionHandler; + entity->tileCollisionHandler = &pl_dummyTileCollisionHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createBgColBlack(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = false; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + entity->xDamping = c000; + + + entity->type = ENTITY_BGCOL_BLACK; + entity->updateFunction = &updateBgCol; + entity->collisionHandler = &pl_dummyCollisionHandler; + entity->tileCollisionHandler = &pl_dummyTileCollisionHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createBgColNeutralGreen(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = false; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + entity->xDamping = c133; + + entity->type = ENTITY_BGCOL_NEUTRAL_GREEN; + entity->updateFunction = &updateBgCol; + entity->collisionHandler = &pl_dummyCollisionHandler; + entity->tileCollisionHandler = &pl_dummyTileCollisionHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createBgColNeutralDarkRed(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = false; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + entity->xDamping = c200; + + entity->type = ENTITY_BGCOL_DARK_RED; + entity->updateFunction = &updateBgCol; + entity->collisionHandler = &pl_dummyCollisionHandler; + entity->tileCollisionHandler = &pl_dummyTileCollisionHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createBgColNeutralDarkGreen(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = false; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + entity->xDamping = c010; + + entity->type = ENTITY_BGCOL_DARK_GREEN; + entity->updateFunction = &updateBgCol; + entity->collisionHandler = &pl_dummyCollisionHandler; + entity->tileCollisionHandler = &pl_dummyTileCollisionHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* create1up(plEntityManager_t * entityManager, uint16_t x, uint16_t y){ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = true; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + + entity->xspeed = (entityManager->playerEntity->x > entity->x)? -16: 16; + entity->yspeed = 0; + entity->xMaxSpeed = 132; + entity->yMaxSpeed = 132; + entity->gravityEnabled = true; + entity->gravity = 4; + entity->spriteFlipHorizontal = false; + entity->spriteFlipVertical = false; + + entity->type = ENTITY_1UP; + entity->spriteIndex = SP_1UP_1; + entity->animationTimer = 0; + entity->updateFunction = &update1up; + entity->collisionHandler = &powerUpCollisionHandler; + entity->tileCollisionHandler = &pl_enemyTileCollisionHandler; + entity->fallOffTileHandler = &defaultFallOffTileHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +}; + +plEntity_t* createWaveBall(plEntityManager_t * entityManager, uint16_t x, uint16_t y){ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = true; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + + entity->xspeed = 0; + entity->yspeed = 0; + entity->xMaxSpeed = 132; + entity->yMaxSpeed = 132; + entity->gravityEnabled = false; + entity->gravity = 4; + entity->spriteFlipHorizontal = false; + entity->spriteFlipVertical = false; + entity->yDamping = 3; //This will be repurposed as a state timer + entity->xDamping = 0; //This will be repurposed as a state tracker + + entity->type = ENTITY_WAVE_BALL; + entity->spriteIndex = SP_WAVEBALL_1; + entity->animationTimer = 0; + entity->updateFunction = &updateWaveBall; + entity->collisionHandler = &pl_dummyCollisionHandler; + entity->tileCollisionHandler = &pl_dummyTileCollisionHandler; + entity->fallOffTileHandler = &defaultFallOffTileHandler; + entity->overlapTileHandler = &waveBallOverlapTileHandler; + + return entity; +}; + +plEntity_t* createCheckpoint(plEntityManager_t * entityManager, uint16_t x, uint16_t y){ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = true; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + + entity->xspeed = 0; + entity->yspeed = 0; + entity->xMaxSpeed = 132; + entity->yMaxSpeed = 132; + entity->gravityEnabled = true; + entity->gravity = 4; + entity->spriteFlipHorizontal = false; + entity->spriteFlipVertical = false; + + entity->xDamping = 0; //State of the checkpoint. 0 = inactive, 1 = active + + entity->type = ENTITY_CHECKPOINT; + entity->spriteIndex = SP_CHECKPOINT_INACTIVE; + entity->animationTimer = 0; + entity->updateFunction = &updateCheckpoint; + entity->collisionHandler = &pl_dummyCollisionHandler; + entity->tileCollisionHandler = &pl_dummyTileCollisionHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +}; + +void pl_freeEntityManager(plEntityManager_t * self){ + free(self->entities); + for(uint8_t i=0; isprites[i]); + } +} + +plEntity_t* createBgmChange1(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = false; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + entity->xDamping = PL_BGM_MAIN; + + entity->type = ENTITY_BGM_CHANGE_1; + entity->updateFunction = &updateBgmChange; + entity->collisionHandler = &pl_dummyCollisionHandler; + entity->tileCollisionHandler = &pl_dummyTileCollisionHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createBgmChange2(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = false; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + entity->xDamping = PL_BGM_ATHLETIC; + + entity->type = ENTITY_BGM_CHANGE_2; + entity->updateFunction = &updateBgmChange; + entity->collisionHandler = &pl_dummyCollisionHandler; + entity->tileCollisionHandler = &pl_dummyTileCollisionHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createBgmChange3(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = false; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + entity->xDamping = PL_BGM_UNDERGROUND; + + entity->type = ENTITY_BGM_CHANGE_3; + entity->updateFunction = &updateBgmChange; + entity->collisionHandler = &pl_dummyCollisionHandler; + entity->tileCollisionHandler = &pl_dummyTileCollisionHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createBgmChange4(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = false; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + entity->xDamping = PL_BGM_FORTRESS; + + entity->type = ENTITY_BGM_CHANGE_4; + entity->updateFunction = &updateBgmChange; + entity->collisionHandler = &pl_dummyCollisionHandler; + entity->tileCollisionHandler = &pl_dummyTileCollisionHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createBgmChange5(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = false; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + entity->xDamping = PL_BGM_NULL; + + entity->type = ENTITY_BGM_CHANGE_5; + entity->updateFunction = &updateBgmChange; + entity->collisionHandler = &pl_dummyCollisionHandler; + entity->tileCollisionHandler = &pl_dummyTileCollisionHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} + +plEntity_t* createBgmStop(plEntityManager_t * entityManager, uint16_t x, uint16_t y) +{ + plEntity_t * entity = pl_findInactiveEntity(entityManager); + + if(entity == NULL) { + return NULL; + } + + entity->active = true; + entity->visible = false; + entity->x = x << SUBPIXEL_RESOLUTION; + entity->y = y << SUBPIXEL_RESOLUTION; + entity->xDamping = PL_BGM_NULL; + + entity->type = ENTITY_BGM_STOP; + entity->updateFunction = &updateBgmChange; + entity->collisionHandler = &pl_dummyCollisionHandler; + entity->tileCollisionHandler = &pl_dummyTileCollisionHandler; + entity->overlapTileHandler = &pl_defaultOverlapTileHandler; + + return entity; +} \ No newline at end of file diff --git a/main/modes/platformer/plEntityManager.h b/main/modes/platformer/plEntityManager.h new file mode 100644 index 000000000..1ad32c760 --- /dev/null +++ b/main/modes/platformer/plEntityManager.h @@ -0,0 +1,100 @@ +#ifndef _PL_ENTITYMANAGER_H_ +#define _PL_ENTITYMANAGER_H_ + +//============================================================================== +// Includes +//============================================================================== + +#include +#include +#include "platformer_typedef.h" +#include "plEntity.h" +#include "plTilemap.h" +#include "plGameData.h" +#include "hdw-tft.h" +//#include "soundManager.h" + +//============================================================================== +// Constants +//============================================================================== +#define MAX_ENTITIES 32 +#define SPRITESET_SIZE 51 + +//============================================================================== +// Structs +//============================================================================== + +struct plEntityManager_t +{ + wsg_t sprites[SPRITESET_SIZE]; + plEntity_t * entities; + uint8_t activeEntities; + + plEntity_t * viewEntity; + plEntity_t * playerEntity; + + plTilemap_t * tilemap; +}; + +//============================================================================== +// Prototypes +//============================================================================== +void pl_initializeEntityManager(plEntityManager_t * entityManager, plTilemap_t * tilemap, plGameData_t * gameData, plSoundManager_t * soundManager); +void pl_loadSprites(plEntityManager_t * entityManager); +void pl_updateEntities(plEntityManager_t * entityManager); +void pl_deactivateAllEntities(plEntityManager_t * entityManager, bool excludePlayer); +void pl_drawEntities(plEntityManager_t * entityManager); +plEntity_t * pl_findInactiveEntity(plEntityManager_t * entityManager); + +void pl_viewFollowEntity(plTilemap_t * tilemap, plEntity_t * entity); +plEntity_t* pl_createEntity(plEntityManager_t *entityManager, uint8_t objectIndex, uint16_t x, uint16_t y); +plEntity_t* pl_createPlayer(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createTestObject(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createScrollLockLeft(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createScrollLockRight(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createScrollLockUp(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createScrollLockDown(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createScrollUnlock(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createHitBlock(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createPowerUp(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createWarp(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createDustBunny(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createWasp(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createEnemyBushL2(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createEnemyBushL3(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createDustBunnyL2(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createDustBunnyL3(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createWaspL2(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createWaspL3(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createBgColBlue(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createBgColYellow(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createBgColOrange(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createBgColPurple(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createBgColDarkPurple(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createBgColBlack(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createBgColNeutralGreen(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createBgColNeutralDarkRed(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createBgColNeutralDarkGreen(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* create1up(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createWaveBall(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createCheckpoint(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createBgmChange5(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createBgmChange1(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createBgmChange2(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createBgmChange3(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createBgmChange4(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +plEntity_t* createBgmStop(plEntityManager_t * entityManager, uint16_t x, uint16_t y); +void pl_freeEntityManager(plEntityManager_t * entityManager); + + + + + + + + + + + + +#endif diff --git a/main/modes/platformer/plGameData.c b/main/modes/platformer/plGameData.c new file mode 100644 index 000000000..7f4398a5a --- /dev/null +++ b/main/modes/platformer/plGameData.c @@ -0,0 +1,238 @@ +//============================================================================== +// Includes +//============================================================================== + +#include +#include "plGameData.h" +#include "plEntityManager.h" +#include "esp_random.h" +#include "hdw-btn.h" +#include "hdw-bzr.h" + +//============================================================================== +// Functions +//============================================================================== + void pl_initializeGameData(plGameData_t * gameData, plSoundManager_t * soundManager){ + gameData->gameState = 0; + gameData->btnState = 0; + gameData->score = 0; + gameData->lives = 3; + gameData->countdown = 000; + gameData->world = 1; + gameData->level = 1; + gameData->frameCount = 0; + gameData->coins = 0; + gameData->combo = 0; + gameData->comboTimer = 0; + gameData->bgColor = c335; + gameData->initials[0] = 'A'; + gameData->initials[1] = 'A'; + gameData->initials[2] = 'A'; + gameData->rank = 5; + gameData->extraLifeCollected = false; + gameData->checkpoint = 0; + gameData->levelDeaths = 0; + gameData->initialHp = 1; + gameData->debugMode = false; + gameData->continuesUsed = false; + gameData->inGameTimer = 0; + gameData->soundManager = soundManager; +} + + void pl_initializeGameDataFromTitleScreen(plGameData_t * gameData){ + gameData->gameState = 0; + gameData->btnState = 0; + gameData->score = 0; + gameData->lives = 3; + gameData->countdown = 000; + gameData->frameCount = 0; + gameData->coins = 0; + gameData->combo = 0; + gameData->comboTimer = 0; + gameData->bgColor = c000; + gameData->extraLifeCollected = false; + gameData->checkpoint = 0; + gameData->levelDeaths = 0; + gameData->currentBgm = 0; + gameData->changeBgm = 0; + gameData->initialHp = 1; + gameData->continuesUsed = (gameData->world == 1 && gameData->level == 1) ? false : true; + gameData->inGameTimer = 0; + + pl_resetGameDataLeds(gameData); +} + +void pl_updateLedsHpMeter(plEntityManager_t *entityManager, plGameData_t *gameData){ + if(entityManager->playerEntity == NULL){ + return; + } + + uint8_t hp = entityManager->playerEntity->hp; + if(hp > 3){ + hp = 3; + } + + //HP meter led pairs: + //3 4 + //2 5 + //1 6 + for (int32_t i = 1; i < 7; i++) + { + gameData->leds[i].r = 0x80; + gameData->leds[i].g = 0x00; + gameData->leds[i].b = 0x00; + } + + for (int32_t i = 1; i < 1+hp; i++) + { + gameData->leds[i].r = 0x00; + gameData->leds[i].g = 0x80; + + gameData->leds[7-i].r = 0x00; + gameData->leds[7-i].g = 0x80; + } + + setLeds(gameData->leds, CONFIG_NUM_LEDS); +} + +void pl_scorePoints(plGameData_t * gameData, uint16_t points){ + gameData->combo++; + + uint32_t comboPoints = points * gameData->combo; + + gameData->score += comboPoints; + gameData->comboScore = comboPoints; + + gameData->comboTimer = (gameData->levelDeaths < 3) ? 240: 1; +} + +void addCoins(plGameData_t * gameData, uint8_t coins){ + gameData->coins+=coins; + if(gameData->coins > 99){ + gameData->lives++; + bzrPlaySfx(&(gameData->soundManager->snd1up), BZR_LEFT); + gameData->coins = 0; + } else { + bzrPlaySfx(&(gameData->soundManager->sndCoin), BZR_LEFT); + } +} + +void updateComboTimer(plGameData_t * gameData){ + gameData->comboTimer--; + + if(gameData->comboTimer < 0){ + gameData->comboTimer = 0; + gameData->combo = 0; + } +}; + +void pl_resetGameDataLeds(plGameData_t * gameData) +{ + for(uint8_t i=0;ileds[i].r = 0; + gameData->leds[i].g = 0; + gameData->leds[i].b = 0; + } + + setLeds(gameData->leds, CONFIG_NUM_LEDS); +} + +void pl_updateLedsShowHighScores(plGameData_t * gameData){ + if(( (gameData->frameCount) % 10) == 0){ + for (int32_t i = 0; i < 8; i++) + { + + if(( (gameData->frameCount >> 4) % CONFIG_NUM_LEDS) == i) { + gameData->leds[i].r = 0xF0; + gameData->leds[i].g = 0xF0; + gameData->leds[i].b = 0x00; + } + + if(gameData->leds[i].r > 0){ + gameData->leds[i].r -= 0x05; + } + + if(gameData->leds[i].g > 0){ + gameData->leds[i].g -= 0x10; + } + + if(gameData->leds[i].b > 0){ + gameData->leds[i].b = 0x00; + } + + } + } + setLeds(gameData->leds, CONFIG_NUM_LEDS); +} + +void pl_updateLedsGameOver(plGameData_t * gameData){ + if(( (gameData->frameCount) % 10) == 0){ + for (int32_t i = 0; i < CONFIG_NUM_LEDS; i++) + { + + if(( (gameData->frameCount >> 4) % CONFIG_NUM_LEDS) == i) { + gameData->leds[i].r = 0xF0; + gameData->leds[i].g = 0x00; + gameData->leds[i].b = 0x00; + } + + gameData->leds[i].r -= 0x10; + gameData->leds[i].g = 0x00; + gameData->leds[i].b = 0x00; + } + } + setLeds(gameData->leds, CONFIG_NUM_LEDS); +} + +void pl_updateLedsLevelClear(plGameData_t * gameData){ + if(( (gameData->frameCount) % 10) == 0){ + for (int32_t i = 0; i < CONFIG_NUM_LEDS; i++) + { + + if(( (gameData->frameCount >> 4) % CONFIG_NUM_LEDS) == i) { + gameData->leds[i].g = (esp_random() % 24) * (10); + gameData->leds[i].b = (esp_random() % 24) * (10); + } + + if(gameData->leds[i].r > 0){ + gameData->leds[i].r -= 0x10; + } + + if(gameData->leds[i].g > 0){ + gameData->leds[i].g -= 0x10; + } + + if(gameData->leds[i].b > 0){ + gameData->leds[i].b -= 0x10; + } + } + } + setLeds(gameData->leds, CONFIG_NUM_LEDS); +} + +void pl_updateLedsGameClear(plGameData_t * gameData){ + if(( (gameData->frameCount) % 10) == 0){ + for (int32_t i = 0; i < CONFIG_NUM_LEDS; i++) + { + + if(( (gameData->frameCount >> 4) % CONFIG_NUM_LEDS) == i) { + gameData->leds[i].r = (esp_random() % 24) * (10); + gameData->leds[i].g = (esp_random() % 24) * (10); + gameData->leds[i].b = (esp_random() % 24) * (10); + } + + if(gameData->leds[i].r > 0){ + gameData->leds[i].r -= 0x10; + } + + if(gameData->leds[i].g > 0){ + gameData->leds[i].g -= 0x10; + } + + if(gameData->leds[i].b > 0){ + gameData->leds[i].b -= 0x10; + } + } + } + setLeds(gameData->leds, CONFIG_NUM_LEDS); +} diff --git a/main/modes/platformer/plGameData.h b/main/modes/platformer/plGameData.h new file mode 100644 index 000000000..4590afcee --- /dev/null +++ b/main/modes/platformer/plGameData.h @@ -0,0 +1,89 @@ +#ifndef _PL_GAMEDATA_H_ +#define _PL_GAMEDATA_H_ + +//============================================================================== +// Includes +//============================================================================== + +#include +#include "hdw-led.h" +#include "platformer_typedef.h" +//#include "swadgeMode.h" +#include "palette.h" +#include "plSoundManager.h" + +//============================================================================== +// Constants +//============================================================================== +/*static const song_t snd1up = +{ + .notes = + { + {G_7, 40},{D_6, 40},{B_5, 80} + }, + .numNotes = 3, + .shouldLoop = false +};*/ + +//============================================================================== +// Structs +//============================================================================== + +typedef struct +{ + int16_t btnState; + int16_t prevBtnState; + uint8_t gameState; + uint8_t changeState; + + uint32_t score; + uint8_t lives; + uint8_t coins; + int16_t countdown; + uint16_t frameCount; + + uint8_t world; + uint8_t level; + + uint16_t combo; + int16_t comboTimer; + uint32_t comboScore; + + bool extraLifeCollected; + uint8_t checkpoint; + uint8_t levelDeaths; + uint8_t initialHp; + + led_t leds[CONFIG_NUM_LEDS]; + + paletteColor_t bgColor; + + char initials[3]; + uint8_t rank; + bool debugMode; + + uint8_t changeBgm; + uint8_t currentBgm; + + bool continuesUsed; + uint32_t inGameTimer; + + plSoundManager_t* soundManager; +} plGameData_t; + +//============================================================================== +// Functions +//============================================================================== +void pl_initializeGameData(plGameData_t * gameData, plSoundManager_t * soundManager); +void pl_initializeGameDataFromTitleScreen(plGameData_t * gameData); +void pl_updateLedsHpMeter(plEntityManager_t *entityManager, plGameData_t *gameData); +void pl_scorePoints(plGameData_t * gameData, uint16_t points); +void addCoins(plGameData_t * gameData, uint8_t coins); +void updateComboTimer(plGameData_t * gameData); +void pl_resetGameDataLeds(plGameData_t * gameData); +void pl_updateLedsShowHighScores(plGameData_t * gameData); +void pl_updateLedsLevelClear(plGameData_t * gameData); +void pl_updateLedsGameClear(plGameData_t * gameData); +void pl_updateLedsGameOver(plGameData_t * gameData); + +#endif \ No newline at end of file diff --git a/main/modes/platformer/plLeveldef.h b/main/modes/platformer/plLeveldef.h new file mode 100644 index 000000000..4896ebe52 --- /dev/null +++ b/main/modes/platformer/plLeveldef.h @@ -0,0 +1,19 @@ +#ifndef _LEVELDEF_H_ +#define _LEVELDEF_H_ + +//============================================================================== +// Includes +//============================================================================== +#include + +//============================================================================== +// Structs +//============================================================================== +typedef struct { + char filename[16]; + uint16_t timeLimit; + uint16_t checkpointTimeLimit; +} plLeveldef_t; + + +#endif diff --git a/main/modes/platformer/plSoundManager.c b/main/modes/platformer/plSoundManager.c new file mode 100644 index 000000000..d4964ce1a --- /dev/null +++ b/main/modes/platformer/plSoundManager.c @@ -0,0 +1,91 @@ +//============================================================================== +// Includes +//============================================================================== + +#include +#include "plSoundManager.h" + +//============================================================================== +// Functions +//============================================================================== +void pl_initializeSoundManager(plSoundManager_t *self){ + loadSong("bgmCastle.sng", &self->bgmCastle, false); + self->bgmCastle.shouldLoop = true; + + loadSong("bgmDeMAGio.sng", &self->bgmDemagio, false); + self->bgmDemagio.shouldLoop = true; + + loadSong("bgmGameStart.sng", &self->bgmGameStart, false); + loadSong("bgmIntro.sng", &self->bgmIntro, false); + loadSong("bgmNameEntry.sng", &self->bgmNameEntry, false); + self->bgmNameEntry.shouldLoop = true; + + loadSong("bgmSmooth.sng", &self->bgmSmooth, false); + self->bgmSmooth.shouldLoop = true; + + loadSong("bgmUnderground.sng", &self->bgmUnderground, false); + self->bgmUnderground.shouldLoop = true; + + loadSong("snd1up.sng", &self->snd1up, false); + loadSong("sndBreak.sng", &self->sndBreak, false); + loadSong("sndCheckpoint.sng", &self->sndCheckpoint, false); + loadSong("sndCoin.sng", &self->sndCoin, false); + loadSong("sndDie.sng", &self->sndDie, false); + loadSong("bgmGameOver.sng", &self->bgmGameOver, false); + loadSong("sndHit.sng", &self->sndHit, false); + loadSong("sndHurt.sng", &self->sndHurt, false); + loadSong("sndJump1.sng", &self->sndJump1, false); + loadSong("sndJump2.sng", &self->sndJump2, false); + loadSong("sndJump3.sng", &self->sndJump3, false); + loadSong("sndLevelClearA.sng", &self->sndLevelClearA, false); + loadSong("sndLevelClearB.sng", &self->sndLevelClearB, false); + loadSong("sndLevelClearC.sng", &self->sndLevelClearC, false); + loadSong("sndLevelClearD.sng", &self->sndLevelClearD, false); + loadSong("sndLevelClearS.sng", &self->sndLevelClearS, false); + loadSong("sndMenuConfirm.sng", &self->sndMenuConfirm, false); + loadSong("sndMenuDeny.sng", &self->sndMenuDeny, false); + loadSong("sndMenuSelect.sng", &self->sndMenuSelect, false); + loadSong("sndOuttatime.sng", &self->sndOuttaTime, false); + loadSong("sndPause.sng", &self->sndPause, false); + loadSong("sndPowerUp.sng", &self->sndPowerUp, false); + loadSong("sndSquish.sng", &self->sndSquish, false); + loadSong("sndTally.sng", &self->sndTally, false); + loadSong("sndWarp.sng", &self->sndWarp, false); + loadSong("sndWaveBall.sng", &self->sndWaveBall, false); +} + +void pl_freeSoundManager(plSoundManager_t *self){ + freeSong(&self->bgmCastle); + freeSong(&self->bgmDemagio); + freeSong(&self->bgmGameStart); + freeSong(&self->bgmIntro); + freeSong(&self->bgmNameEntry); + freeSong(&self->bgmSmooth); + freeSong(&self->bgmUnderground); + freeSong(&self->snd1up); + freeSong(&self->sndBreak); + freeSong(&self->sndCheckpoint); + freeSong(&self->sndCoin); + freeSong(&self->sndDie); + freeSong(&self->bgmGameOver); + freeSong(&self->sndHit); + freeSong(&self->sndHurt); + freeSong(&self->sndJump1); + freeSong(&self->sndJump2); + freeSong(&self->sndJump3); + freeSong(&self->sndLevelClearA); + freeSong(&self->sndLevelClearB); + freeSong(&self->sndLevelClearC); + freeSong(&self->sndLevelClearD); + freeSong(&self->sndLevelClearS); + freeSong(&self->sndMenuConfirm); + freeSong(&self->sndMenuDeny); + freeSong(&self->sndMenuSelect); + freeSong(&self->sndOuttaTime); + freeSong(&self->sndPause); + freeSong(&self->sndPowerUp); + freeSong(&self->sndSquish); + freeSong(&self->sndTally); + freeSong(&self->sndWarp); + freeSong(&self->sndWaveBall); +} \ No newline at end of file diff --git a/main/modes/platformer/plSoundManager.h b/main/modes/platformer/plSoundManager.h new file mode 100644 index 000000000..70339ee3c --- /dev/null +++ b/main/modes/platformer/plSoundManager.h @@ -0,0 +1,63 @@ +#ifndef _PL_SOUNDMANAGER_H_ +#define _PL_SOUNDMANAGER_H_ + +//============================================================================== +// Includes +//============================================================================== + +#include +#include +#include + +//============================================================================== +// Constants +//============================================================================== + +//============================================================================== +// Structs +//============================================================================== + +typedef struct +{ + song_t bgmDemagio; + song_t bgmIntro; + song_t bgmSmooth; + song_t bgmUnderground; + song_t bgmCastle; + song_t bgmGameStart; + song_t sndDie; + song_t sndMenuSelect; + song_t sndMenuConfirm; + song_t sndMenuDeny; + song_t sndPause; + song_t sndHit; + song_t sndSquish; + song_t sndBreak; + song_t sndCoin; + song_t sndPowerUp; + song_t sndJump1; + song_t sndJump2; + song_t sndJump3; + song_t sndWarp; + song_t sndHurt; + song_t sndWaveBall; + song_t snd1up; + song_t sndCheckpoint; + song_t sndLevelClearD; + song_t sndLevelClearC; + song_t sndLevelClearB; + song_t sndLevelClearA; + song_t sndLevelClearS; + song_t sndTally; + song_t bgmNameEntry; + song_t bgmGameOver; + song_t sndOuttaTime; +} plSoundManager_t; + +//============================================================================== +// Functions +//============================================================================== +void pl_initializeSoundManager(plSoundManager_t *self); +void pl_freeSoundManager(plSoundManager_t *self); + +#endif \ No newline at end of file diff --git a/main/modes/platformer/plTilemap.c b/main/modes/platformer/plTilemap.c new file mode 100644 index 000000000..325f2f2a2 --- /dev/null +++ b/main/modes/platformer/plTilemap.c @@ -0,0 +1,387 @@ +//============================================================================== +// Includes +//============================================================================== + +#include +#include +#include +#include + +#include "spiffs_wsg.h" +#include "plTilemap.h" +#include "plLeveldef.h" +#include "esp_random.h" + +#include "hdw-spiffs.h" + +//============================================================================== +// Function Prototypes +//============================================================================== + +// bool isInteractive(uint8_t tileId); + +//============================================================================== +// Functions +//============================================================================== + +void pl_initializeTileMap(plTilemap_t *tilemap) +{ + tilemap->mapOffsetX = 0; + tilemap->mapOffsetY = 0; + + tilemap->tileSpawnEnabled = false; + tilemap->executeTileSpawnColumn = -1; + tilemap->executeTileSpawnRow = -1; + + tilemap->animationFrame = 0; + tilemap->animationTimer = 23; + + pl_loadTiles(tilemap); +} + +void pl_drawTileMap(plTilemap_t *tilemap) +{ + tilemap->animationTimer--; + if (tilemap->animationTimer < 0) + { + tilemap->animationFrame = ((tilemap->animationFrame + 1) % 3); + tilemap->animationTimer = 23; + } + + for (uint16_t y = (tilemap->mapOffsetY >> PL_TILESIZE_IN_POWERS_OF_2); y < (tilemap->mapOffsetY >> PL_TILESIZE_IN_POWERS_OF_2) + PL_TILEMAP_DISPLAY_HEIGHT_TILES; y++) + { + if (y >= tilemap->mapHeight) + { + break; + } + + for (uint16_t x = (tilemap->mapOffsetX >> PL_TILESIZE_IN_POWERS_OF_2); x < (tilemap->mapOffsetX >> PL_TILESIZE_IN_POWERS_OF_2) + PL_TILEMAP_DISPLAY_WIDTH_TILES; x++) + { + if (x >= tilemap->mapWidth) + { + break; + } + + uint8_t tile = tilemap->map[(y * tilemap->mapWidth) + x]; + + if(tile < PL_TILEGRASS){ + continue; + } + + // Test animated tiles + if (tile == 64 || tile == 67) + { + tile += tilemap->animationFrame; + } + + // Draw only non-garbage tiles + if (tile > 31 && tile < 104) + { + if(pl_needsTransparency(tile)){ + //drawWsgSimpleFast(&tilemap->tiles[tile - 32], x * PL_TILESIZE - tilemap->mapOffsetX, y * PL_TILESIZE - tilemap->mapOffsetY); + drawWsgSimple(&tilemap->tiles[tile - 32], x * PL_TILESIZE - tilemap->mapOffsetX, y * PL_TILESIZE - tilemap->mapOffsetY); + + } + else { + drawWsgTile(&tilemap->tiles[tile - 32], x * PL_TILESIZE - tilemap->mapOffsetX, y * PL_TILESIZE - tilemap->mapOffsetY); + } + } + else if (tile > 127 && tilemap->tileSpawnEnabled && (tilemap->executeTileSpawnColumn == x || tilemap->executeTileSpawnRow == y || tilemap->executeTileSpawnAll)) + { + pl_tileSpawnEntity(tilemap, tile - 128, x, y); + } + } + } + + tilemap->executeTileSpawnAll = 0; +} + +void pl_scrollTileMap(plTilemap_t *tilemap, int16_t x, int16_t y) +{ + if (x != 0) + { + uint8_t oldTx = tilemap->mapOffsetX >> PL_TILESIZE_IN_POWERS_OF_2; + tilemap->mapOffsetX = CLAMP(tilemap->mapOffsetX + x, tilemap->minMapOffsetX, tilemap->maxMapOffsetX); + uint8_t newTx = tilemap->mapOffsetX >> PL_TILESIZE_IN_POWERS_OF_2; + + if (newTx > oldTx) + { + tilemap->executeTileSpawnColumn = oldTx + PL_TILEMAP_DISPLAY_WIDTH_TILES; + } + else if (newTx < oldTx) + { + tilemap->executeTileSpawnColumn = newTx; + } + else + { + tilemap->executeTileSpawnColumn = -1; + } + } + + if (y != 0) + { + uint8_t oldTy = tilemap->mapOffsetY >> PL_TILESIZE_IN_POWERS_OF_2; + tilemap->mapOffsetY = CLAMP(tilemap->mapOffsetY + y, tilemap->minMapOffsetY, tilemap->maxMapOffsetY); + uint8_t newTy = tilemap->mapOffsetY >> PL_TILESIZE_IN_POWERS_OF_2; + + if (newTy > oldTy) + { + tilemap->executeTileSpawnRow = oldTy + PL_TILEMAP_DISPLAY_HEIGHT_TILES; + } + else if (newTy < oldTy) + { + tilemap->executeTileSpawnRow = newTy; + } + else + { + tilemap->executeTileSpawnRow = -1; + } + } +} + +bool pl_loadMapFromFile(plTilemap_t *tilemap, const char *name) +{ + if (tilemap->map != NULL) + { + free(tilemap->map); + } + + size_t sz; + uint8_t *buf = spiffsReadFile(name, &sz, false); + + if (NULL == buf) + { + ESP_LOGE("MAP", "Failed to read %s", name); + return false; + } + + uint8_t width = buf[0]; + uint8_t height = buf[1]; + + tilemap->map = (uint8_t *)heap_caps_calloc(width * height, sizeof(uint8_t), MALLOC_CAP_SPIRAM); + memcpy(tilemap->map, &buf[2], width * height); + + tilemap->mapWidth = width; + tilemap->mapHeight = height; + + tilemap->minMapOffsetX = 0; + tilemap->maxMapOffsetX = width * PL_TILESIZE - PL_TILEMAP_DISPLAY_WIDTH_PIXELS; + + tilemap->minMapOffsetY = 0; + tilemap->maxMapOffsetY = height * PL_TILESIZE - PL_TILEMAP_DISPLAY_HEIGHT_PIXELS; + + for(uint16_t i=0; i<16; i++){ + tilemap->warps[i].x = buf[2 + width * height + i * 2]; + tilemap->warps[i].y = buf[2 + width * height + i * 2 + 1]; + } + + free(buf); + + return true; +} + +bool pl_loadTiles(plTilemap_t *tilemap) +{ + // tiles 0-31 are invisible tiles; + // remember to subtract 32 from tile index before drawing tile + loadWsg("tile032.wsg", &tilemap->tiles[0], false); + loadWsg("tile033.wsg", &tilemap->tiles[1], false); + loadWsg("tile034.wsg", &tilemap->tiles[2], false); + loadWsg("tile035.wsg", &tilemap->tiles[3], false); + loadWsg("tile036.wsg", &tilemap->tiles[4], false); + loadWsg("tile037.wsg", &tilemap->tiles[5], false); + loadWsg("tile038.wsg", &tilemap->tiles[6], false); + loadWsg("tile039.wsg", &tilemap->tiles[7], false); + loadWsg("tile040.wsg", &tilemap->tiles[8], false); + loadWsg("tile041.wsg", &tilemap->tiles[9], false); + + tilemap->tiles[10] = tilemap->tiles[0]; + tilemap->tiles[11] = tilemap->tiles[0]; + tilemap->tiles[12] = tilemap->tiles[0]; + tilemap->tiles[13] = tilemap->tiles[0]; + tilemap->tiles[14] = tilemap->tiles[0]; + tilemap->tiles[15] = tilemap->tiles[0]; + tilemap->tiles[16] = tilemap->tiles[0]; + tilemap->tiles[17] = tilemap->tiles[0]; + tilemap->tiles[18] = tilemap->tiles[0]; + tilemap->tiles[19] = tilemap->tiles[0]; + tilemap->tiles[20] = tilemap->tiles[0]; + tilemap->tiles[21] = tilemap->tiles[0]; + tilemap->tiles[22] = tilemap->tiles[0]; + tilemap->tiles[23] = tilemap->tiles[0]; + tilemap->tiles[24] = tilemap->tiles[0]; + tilemap->tiles[25] = tilemap->tiles[0]; + tilemap->tiles[26] = tilemap->tiles[0]; + + loadWsg("tile059.wsg", &tilemap->tiles[27], false); + loadWsg("tile060.wsg", &tilemap->tiles[28], false); + loadWsg("tile061.wsg", &tilemap->tiles[29], false); + loadWsg("tile062.wsg", &tilemap->tiles[30], false); + loadWsg("tile063.wsg", &tilemap->tiles[31], false); + loadWsg("tile064.wsg", &tilemap->tiles[32], false); + loadWsg("tile065.wsg", &tilemap->tiles[33], false); + loadWsg("tile066.wsg", &tilemap->tiles[34], false); + loadWsg("tile067.wsg", &tilemap->tiles[35], false); + loadWsg("tile068.wsg", &tilemap->tiles[36], false); + loadWsg("tile069.wsg", &tilemap->tiles[37], false); + loadWsg("tile070.wsg", &tilemap->tiles[38], false); + + tilemap->tiles[39] = tilemap->tiles[0]; + tilemap->tiles[40] = tilemap->tiles[0]; + tilemap->tiles[41] = tilemap->tiles[0]; + tilemap->tiles[42] = tilemap->tiles[0]; + tilemap->tiles[43] = tilemap->tiles[0]; + tilemap->tiles[44] = tilemap->tiles[0]; + tilemap->tiles[45] = tilemap->tiles[0]; + tilemap->tiles[46] = tilemap->tiles[0]; + tilemap->tiles[47] = tilemap->tiles[0]; + + loadWsg("tile080.wsg", &tilemap->tiles[48], false); + loadWsg("tile081.wsg", &tilemap->tiles[49], false); + loadWsg("tile082.wsg", &tilemap->tiles[50], false); + loadWsg("tile083.wsg", &tilemap->tiles[51], false); + loadWsg("tile084.wsg", &tilemap->tiles[52], false); + loadWsg("tile085.wsg", &tilemap->tiles[53], false); + loadWsg("tile086.wsg", &tilemap->tiles[54], false); + loadWsg("tile087.wsg", &tilemap->tiles[55], false); + loadWsg("tile088.wsg", &tilemap->tiles[56], false); + loadWsg("tile089.wsg", &tilemap->tiles[57], false); + loadWsg("tile090.wsg", &tilemap->tiles[58], false); + loadWsg("tile091.wsg", &tilemap->tiles[59], false); + loadWsg("tile092.wsg", &tilemap->tiles[60], false); + loadWsg("tile093.wsg", &tilemap->tiles[61], false); + loadWsg("tile094.wsg", &tilemap->tiles[62], false); + loadWsg("tile095.wsg", &tilemap->tiles[63], false); + loadWsg("tile096.wsg", &tilemap->tiles[64], false); + loadWsg("tile097.wsg", &tilemap->tiles[65], false); + loadWsg("tile098.wsg", &tilemap->tiles[66], false); + loadWsg("tile099.wsg", &tilemap->tiles[67], false); + loadWsg("tile100.wsg", &tilemap->tiles[68], false); + loadWsg("tile101.wsg", &tilemap->tiles[69], false); + loadWsg("tile102.wsg", &tilemap->tiles[70], false); + loadWsg("tile103.wsg", &tilemap->tiles[71], false); + + + return true; +} + +void pl_tileSpawnEntity(plTilemap_t *tilemap, uint8_t objectIndex, uint8_t tx, uint8_t ty) +{ + plEntity_t *entityCreated = pl_createEntity(tilemap->entityManager, objectIndex, (tx << PL_TILESIZE_IN_POWERS_OF_2) + 8, (ty << PL_TILESIZE_IN_POWERS_OF_2) + 8); + + if (entityCreated != NULL) + { + entityCreated->homeTileX = tx; + entityCreated->homeTileY = ty; + tilemap->map[ty * tilemap->mapWidth + tx] = 0; + } +} + +uint8_t pl_getTile(plTilemap_t *tilemap, uint8_t tx, uint8_t ty) +{ + // ty = CLAMP(ty, 0, tilemap->mapHeight - 1); + + if (/*ty < 0 ||*/ ty >= tilemap->mapHeight) + { + ty = 0; + //return 0; + } + + if (/*tx < 0 ||*/ tx >= tilemap->mapWidth) + { + return 1; + } + + return tilemap->map[ty * tilemap->mapWidth + tx]; +} + +void pl_setTile(plTilemap_t *tilemap, uint8_t tx, uint8_t ty, uint8_t newTileId) +{ + // ty = CLAMP(ty, 0, tilemap->mapHeight - 1); + + if (ty >= tilemap->mapHeight || tx >= tilemap->mapWidth) + { + return; + } + + tilemap->map[ty * tilemap->mapWidth + tx] = newTileId; +} + +bool pl_isSolid(uint8_t tileId) +{ + switch (tileId) + { + case PL_TILEEMPTY ... PL_TILEUNUSED_29: + return false; + break; + case PL_TILEINVISIBLE_BLOCK ... PL_TILEMETAL_PIPE_V: + return true; + break; + case PL_TILEBOUNCE_BLOCK: + return false; + break; + case PL_TILEDIRT_PATH ... PL_TILECONTAINER_3: + return true; + break; + default: + return false; + } +} + +// bool isInteractive(uint8_t tileId) +// { +// return tileId > PL_TILEINVISIBLE_BLOCK && tileId < PL_TILEBG_GOAL_ZONE; +// } + +void pl_unlockScrolling(plTilemap_t *tilemap){ + tilemap->minMapOffsetX = 0; + tilemap->maxMapOffsetX = tilemap->mapWidth * PL_TILESIZE - PL_TILEMAP_DISPLAY_WIDTH_PIXELS; + + tilemap->minMapOffsetY = 0; + tilemap->maxMapOffsetY = tilemap->mapHeight * PL_TILESIZE - PL_TILEMAP_DISPLAY_HEIGHT_PIXELS; +} + +bool pl_needsTransparency(uint8_t tileId){ + switch(tileId) { + case PL_TILEBOUNCE_BLOCK: + case PL_TILEGIRDER: + case PL_TILECONTAINER_1 ... PL_TILECONTAINER_3: + case PL_TILECOIN_1 ... PL_TILECOIN_3: + case PL_TILELADDER: + case PL_TILEBG_GOAL_ZONE ... PL_TILEBG_CLOUD_D: + return true; + case PL_TILEBG_CLOUD: + return false; + case PL_TILEBG_TALL_GRASS ... PL_TILEBG_MOUNTAIN_R: + return true; + case PL_TILEBG_MOUNTAIN ... PL_TILEBG_METAL: + return false; + case PL_TILEBG_CHAINS: + return true; + case PL_TILEBG_WALL: + return false; + default: + return false; + } +} + +void pl_freeTilemap(plTilemap_t *tilemap){ + free(tilemap->map); + for(uint8_t i=0; itiles[i]); + break; + } + } + } + + +} \ No newline at end of file diff --git a/main/modes/platformer/plTilemap.h b/main/modes/platformer/plTilemap.h new file mode 100644 index 000000000..fdb4a4a25 --- /dev/null +++ b/main/modes/platformer/plTilemap.h @@ -0,0 +1,192 @@ +#ifndef _PL_TILEMAP_H_ +#define _PL_TILEMAP_H_ + +//============================================================================== +// Includes +//============================================================================== + +#include +#include +#include "wsg.h" +#include "platformer_typedef.h" +#include "plEntityManager.h" + +//============================================================================== +// Constants +//============================================================================== +#define CLAMP(x, l, u) ((x) < l ? l : ((x) > u ? u : (x))) + +#define PL_TILEMAP_DISPLAY_WIDTH_PIXELS 280 // The screen size +#define PL_TILEMAP_DISPLAY_HEIGHT_PIXELS 240 // The screen size +#define PL_TILEMAP_DISPLAY_WIDTH_TILES 19 // The screen size in tiles + 1 +#define PL_TILEMAP_DISPLAY_HEIGHT_TILES 16 // The screen size in tiles + 1 + +#define PL_TILESIZE 16 +#define PL_TILESIZE_IN_POWERS_OF_2 4 + +#define PL_TILESET_SIZE 72 + +//============================================================================== +// Enums +//============================================================================== +typedef enum { + PL_TILEEMPTY, + PL_TILEWARP_0, + PL_TILEWARP_1, + PL_TILEWARP_2, + PL_TILEWARP_3, + PL_TILEWARP_4, + PL_TILEWARP_5, + PL_TILEWARP_6, + PL_TILEWARP_7, + PL_TILEWARP_8, + PL_TILEWARP_9, + PL_TILEWARP_A, + PL_TILEWARP_B, + PL_TILEWARP_C, + PL_TILEWARP_D, + PL_TILEWARP_E, + PL_TILEWARP_F, + PL_TILECTNR_COIN, + PL_TILECTNR_10COIN, + PL_TILECTNR_POW1, + PL_TILECTNR_POW2, + PL_TILECTNR_LADDER, + PL_TILECTNR_1UP, + PL_TILECTRL_LEFT, + PL_TILECTRL_RIGHT, + PL_TILECTRL_UP, + PL_TILECTRL_DOWN, + PL_TILEUNUSED_27, + PL_TILEUNUSED_28, + PL_TILEUNUSED_29, + PL_TILEINVISIBLE_BLOCK, + PL_TILEINVISIBLE_CONTAINER, + PL_TILEGRASS, + PL_TILEGROUND, + PL_TILEBRICK_BLOCK, + PL_TILEBLOCK, + PL_TILEMETAL_BLOCK, + PL_TILEMETAL_PIPE_H, + PL_TILEMETAL_PIPE_V, + PL_TILEBOUNCE_BLOCK, + PL_TILEDIRT_PATH, + PL_TILEGIRDER, + PL_TILESOLID_UNUSED_42, + PL_TILESOLID_UNUSED_43, + PL_TILESOLID_UNUSED_44, + PL_TILESOLID_UNUSED_45, + PL_TILESOLID_UNUSED_46, + PL_TILESOLID_UNUSED_47, + PL_TILESOLID_UNUSED_48, + PL_TILESOLID_UNUSED_49, + PL_TILESOLID_UNUSED_50, + PL_TILESOLID_UNUSED_51, + PL_TILESOLID_UNUSED_52, + PL_TILESOLID_UNUSED_53, + PL_TILESOLID_UNUSED_54, + PL_TILESOLID_UNUSED_55, + PL_TILESOLID_UNUSED_56, + PL_TILESOLID_UNUSED_57, + PL_TILESOLID_UNUSED_58, + PL_TILEGOAL_100PTS, + PL_TILEGOAL_500PTS, + PL_TILEGOAL_1000PTS, + PL_TILEGOAL_2000PTS, + PL_TILEGOAL_5000PTS, + PL_TILECONTAINER_1, + PL_TILECONTAINER_2, + PL_TILECONTAINER_3, + PL_TILECOIN_1, + PL_TILECOIN_2, + PL_TILECOIN_3, + PL_TILELADDER, + PL_TILENONSOLID_INTERACTIVE_VISIBLE_71, + PL_TILENONSOLID_INTERACTIVE_VISIBLE_72, + PL_TILENONSOLID_INTERACTIVE_VISIBLE_73, + PL_TILENONSOLID_INTERACTIVE_VISIBLE_74, + PL_TILENONSOLID_INTERACTIVE_VISIBLE_75, + PL_TILENONSOLID_INTERACTIVE_VISIBLE_76, + PL_TILENONSOLID_INTERACTIVE_VISIBLE_77, + PL_TILENONSOLID_INTERACTIVE_VISIBLE_78, + PL_TILENONSOLID_INTERACTIVE_VISIBLE_79, + PL_TILEBG_GOAL_ZONE, + PL_TILEBG_ARROW_L, + PL_TILEBG_ARROW_R, + PL_TILEBG_ARROW_U, + PL_TILEBG_ARROW_D, + PL_TILEBG_ARROW_LU, + PL_TILEBG_ARROW_RU, + PL_TILEBG_ARROW_LD, + PL_TILEBG_ARROW_RD, + PL_TILEBG_CLOUD_LD, + PL_TILEBG_CLOUD_M, + PL_TILEBG_CLOUD_RD, + PL_TILEBG_CLOUD_LU, + PL_TILEBG_CLOUD_RU, + PL_TILEBG_CLOUD_D, + PL_TILEBG_CLOUD, + PL_TILEBG_TALL_GRASS, + PL_TILEBG_MOUNTAIN_L, + PL_TILEBG_MOUNTAIN_U, + PL_TILEBG_MOUNTAIN_R, + PL_TILEBG_MOUNTAIN, + PL_TILEBG_METAL, + PL_TILEBG_CHAINS, + PL_TILEBG_WALL +} pl_tileIndex_t; + +//============================================================================== +// Structs +//============================================================================== +typedef struct { + uint8_t x; + uint8_t y; +} pl_warp_t; + struct plTilemap_t +{ + wsg_t tiles[PL_TILESET_SIZE]; + + uint8_t * map; + uint8_t mapWidth; + uint8_t mapHeight; + + pl_warp_t warps[16]; + + int16_t mapOffsetX; + int16_t mapOffsetY; + + int16_t minMapOffsetX; + int16_t maxMapOffsetX; + int16_t minMapOffsetY; + int16_t maxMapOffsetY; + + bool tileSpawnEnabled; + int16_t executeTileSpawnColumn; + int16_t executeTileSpawnRow; + bool executeTileSpawnAll; + + plEntityManager_t *entityManager; + + uint8_t animationFrame; + int16_t animationTimer; +}; + +//============================================================================== +// Prototypes +//============================================================================== +void pl_initializeTileMap(plTilemap_t * tilemap); +void pl_drawTileMap(plTilemap_t * tilemap); +void pl_scrollTileMap(plTilemap_t * tilemap, int16_t x, int16_t y); +void pl_drawTile(plTilemap_t * tilemap, uint8_t tileId, int16_t x, int16_t y); +bool pl_loadMapFromFile(plTilemap_t * tilemap, const char * name); +bool pl_loadTiles(plTilemap_t * tilemap); +void pl_tileSpawnEntity(plTilemap_t * tilemap, uint8_t objectIndex, uint8_t tx, uint8_t ty); +uint8_t pl_getTile(plTilemap_t *tilemap, uint8_t tx, uint8_t ty); +void pl_setTile(plTilemap_t *tilemap, uint8_t tx, uint8_t ty, uint8_t newTileId); +bool pl_isSolid(uint8_t tileId); +void pl_unlockScrolling(plTilemap_t *tilemap); +bool pl_needsTransparency(uint8_t tileId); +void pl_freeTilemap(plTilemap_t *tilemap); + +#endif diff --git a/main/modes/platformer/platformer_typedef.h b/main/modes/platformer/platformer_typedef.h new file mode 100644 index 000000000..4e9cfe93c --- /dev/null +++ b/main/modes/platformer/platformer_typedef.h @@ -0,0 +1,87 @@ +#ifndef PLATFORMER_COMMON_TYPEDEF_INCLUDED +#define PLATFORMER_COMMON_TYPEDEF_INCLUDED + +typedef struct platformer_t platformer_t; +typedef struct plEntityManager_t plEntityManager_t; +typedef struct plTilemap_t plTilemap_t; +typedef struct plEntity_t plEntity_t; + +typedef enum { + PL_ST_NULL, + PL_ST_TITLE_SCREEN, + PL_ST_READY_SCREEN, + PL_ST_GAME, + PL_ST_DEAD, + PL_ST_LEVEL_CLEAR, + PL_ST_WORLD_CLEAR, + PL_ST_GAME_CLEAR, + PL_ST_GAME_OVER, + PL_ST_HIGH_SCORE_ENTRY, + PL_ST_HIGH_SCORE_TABLE, + PL_ST_PAUSE +} pl_gameStateEnum_t; + +typedef enum { + PL_BGM_NO_CHANGE, + PL_BGM_MAIN, + PL_BGM_ATHLETIC, + PL_BGM_UNDERGROUND, + PL_BGM_FORTRESS, + PL_BGM_NULL +} pl_bgmEnum_t; + +typedef enum { + SP_PLAYER_IDLE, + SP_PLAYER_WALK1, + SP_PLAYER_WALK2, + SP_PLAYER_WALK3, + SP_PLAYER_JUMP, + SP_PLAYER_SLIDE, + SP_PLAYER_HURT, + SP_PLAYER_CLIMB, + SP_PLAYER_WIN, + SP_ENEMY_BASIC, + SP_HITBLOCK_CONTAINER, + SP_HITBLOCK_BRICKS, + SP_DUSTBUNNY_IDLE, + SP_DUSTBUNNY_CHARGE, + SP_DUSTBUNNY_JUMP, + SP_GAMING_1, + SP_GAMING_2, + SP_GAMING_3, + SP_MUSIC_1, + SP_MUSIC_2, + SP_MUSIC_3, + SP_WARP_1, + SP_WARP_2, + SP_WARP_3, + SP_WASP_1, + SP_WASP_2, + SP_WASP_DIVE, + SP_1UP_1, + SP_1UP_2, + SP_1UP_3, + SP_WAVEBALL_1, + SP_WAVEBALL_2, + SP_WAVEBALL_3, + SP_ENEMY_BUSH_L2, + SP_ENEMY_BUSH_L3, + SP_DUSTBUNNY_L2_IDLE, + SP_DUSTBUNNY_L2_CHARGE, + SP_DUSTBUNNY_L2_JUMP, + SP_DUSTBUNNY_L3_IDLE, + SP_DUSTBUNNY_L3_CHARGE, + SP_DUSTBUNNY_L3_JUMP, + SP_WASP_L2_1, + SP_WASP_L2_2, + SP_WASP_L2_DIVE, + SP_WASP_L3_1, + SP_WASP_L3_2, + SP_WASP_L3_DIVE, + SP_CHECKPOINT_INACTIVE, + SP_CHECKPOINT_ACTIVE_1, + SP_CHECKPOINT_ACTIVE_2, + SP_BOUNCE_BLOCK +} pl_spriteDef_t; + +#endif \ No newline at end of file diff --git a/tools/platformer_editor/example.bin b/tools/platformer_editor/example.bin new file mode 100644 index 000000000..66f4a293d Binary files /dev/null and b/tools/platformer_editor/example.bin differ diff --git a/tools/platformer_editor/level-test.bin b/tools/platformer_editor/level-test.bin new file mode 100644 index 000000000..73c5d72b3 Binary files /dev/null and b/tools/platformer_editor/level-test.bin differ diff --git a/tools/platformer_editor/readme.md b/tools/platformer_editor/readme.md new file mode 100644 index 000000000..55cf477db --- /dev/null +++ b/tools/platformer_editor/readme.md @@ -0,0 +1,33 @@ +# Super Swadge Land level editor + +## Prerequisites: +- Tiled map editor application (www.mapeditor.org, open source, multiplatform, free to download) +- Swadge Land tileset file (swadge-land-tileset.tsx), provided in this directory + +## Setup: +- Place swadge-land-editor.js into the Extensions directory of the Tiled application +-- (Windows) C:/Users//AppData/Local/Tiled/extensions/ +-- (macOS) ~/Library/Preferences/Tiled/extensions/ +-- (Linux) ~/.config/tiled/extensions/ + +## To open an existing level file: +- Make sure swadge-land-tileset.tsx is in the same directory as the level binary (.bin) file + - Recommended, but optional: copy the level binary (.bin) file into this directory +- In Tiled, click File->Open File or Project +- Select "Super Swadge Land map format" as the file type +- Locate and select the level binary (.bin) file you want to open in your filesystem, then open it + +## To save a level file: +- In Tiled, click File->Export As... +- Select "Super Swadge Land map format" as the file type +- Save the file as normal + +## Testing your level: +This method allows you to test your level without modifying the code. +You can even use this with a precompiled Swadge emulator executable to test without setting up a full development environment! + +1. Make a copy of your level file +2. Name the copy with the same name as an existing level in the spiffs_image directory. +3. Place your copy of the level file into the spiffs_image directory. Overwrite any existing file with the same name. +4. (Skip this step if you want to test using a precompiled Swadge emulator executable): Reflash the firmware to your device (no need to compile!) +5. Use Level Select to navigate to the corresponsing level diff --git a/tools/platformer_editor/swadge-land-editor.js b/tools/platformer_editor/swadge-land-editor.js new file mode 100644 index 000000000..878373979 --- /dev/null +++ b/tools/platformer_editor/swadge-land-editor.js @@ -0,0 +1,131 @@ +tiled.registerMapFormat("Swadge Land", { + name: "Super Swadge Land map format", + extension: "bin", + read: (fileName) => { + var file = new BinaryFile(fileName, BinaryFile.ReadOnly); + var filePath = FileInfo.path(fileName); + var buffer = file.read(file.size); + var view = new Uint8Array(buffer); + const tileDataOffset = 2; + const tileSizeInPixels = 16; + + var map = new TileMap(); + + //The first two bytes contain the width and height of the tilemap in tiles + var tileDataLength = view[0] * view[1]; + map.setSize(view[0], view[1]); + map.setTileSize(tileSizeInPixels, tileSizeInPixels); + + var tileset = tiled.open(filePath + '/swadge-land-tileset.tsx'); + + var layer = new TileLayer(); + + map.addTileset(tileset); + + layer.width = map.width; + layer.height = map.height; + layer.name = 'Main'; + + var layerEdit = layer.edit(); + var importTileX = 0; + var importTileY = 0; + + + //Import tile data + for(let i = 0; i < tileDataLength; i++){ + let tileId = view[i + tileDataOffset]; + layerEdit.setTile(importTileX, importTileY, tileset.tile(tileId)); + + importTileX++; + if(importTileX >= map.width){ + importTileY++; + importTileX=0; + } + } + + //Reconstruct warp tiles + var warpDataOffset = tileDataLength + tileDataOffset; + const warpTileIdOffset = 1; + for(let i = 0; i < 32; i+=2){ + let warpX = view[warpDataOffset + i]; + let warpY = view[warpDataOffset + i + 1]; + if(warpX != 0 || warpY != 0){ + layerEdit.setTile(warpX, warpY, tileset.tile( (i + warpTileIdOffset + 1) >> 1)); + } + } + + layerEdit.apply(); + + map.addLayer(layer); + file.close(); + return map; + }, + write: (map, fileName) => { + var warps = []; + for(let i = 0; i < 16; i++){ + warps[i] = {x: 0, y: 0}; + } + + for (let i = 0; i < map.layerCount; ++i) { + const layer = map.layerAt(i); + + if (!layer.isTileLayer) { + continue; + } + + let file = new BinaryFile(fileName, BinaryFile.WriteOnly); + let buffer = new ArrayBuffer(2 + layer.width * layer.height + 32); //Buffer sized to max width of level + let view = new Uint8Array(buffer); + + //The first two bytes contain the width and height of the tilemap in tiles + view[0]=layer.width; + view[1]=layer.height; + let writePosition = 2; + + for (let y = 0; y < layer.height; ++y) { + const row = []; + + for (let x = 0; x < layer.width; ++x) { + const tile = layer.tileAt(x, y); + if(!tile){ + //file.write(0); + view[writePosition] = 0; + writePosition++; + continue; + } + + const tileId = tile.id; + + if(y < layer.height -1 && tileId > 0 && tileId < 17){ + //Handling warp tiles + const tileIdBelowCurrentTile = layer.tileAt(x,y+1).id; + + if(tileIdBelowCurrentTile == 34 || tileIdBelowCurrentTile == 64 || tileIdBelowCurrentTile == 158){ + //if tile below warp tile is brick block or container or checkpoint, write it like normal + view[writePosition]=tileId; + } else { + //otherwise store it in warps array and don't write it into the file just yet + warps[tileId-1].x = x; + warps[tileId-1].y = y; + view[writePosition]=0; + } + } else { + //Handling every other tile + view[writePosition]=tileId; + } + + writePosition++; + } + } + + for(let i = 0; i < 16; i++){ + view[writePosition] = warps[i].x; + view[writePosition+1] = warps[i].y; + writePosition+=2; + } + + file.write(buffer); + file.commit(); + } + }, +}); \ No newline at end of file diff --git a/tools/platformer_editor/swadge-land-tileset.tsx b/tools/platformer_editor/swadge-land-tileset.tsx new file mode 100644 index 000000000..0e50166b5 --- /dev/null +++ b/tools/platformer_editor/swadge-land-tileset.tsx @@ -0,0 +1,772 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +