commit 5d0e71c629a633dc76a36d199a4e9c8c4a91a690 Author: Mikhail Romanko Date: Thu Jul 17 21:52:37 2025 +0300 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e52b303 --- /dev/null +++ b/.gitignore @@ -0,0 +1,56 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Linker files +*.ilk + +# Debugger Files +*.pdb + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# debug information files +*.dwo + +# Binary archives +[Dd]ata.pak + +# Misc +[Oo]utdated +[Jj]unk +[Tt]rash +[Oo]ld + +# Binaries +eulamadness +eulamadness.html +eulamadness.wasm +eulamandess.js \ No newline at end of file diff --git a/Credits.txt b/Credits.txt new file mode 100644 index 0000000..896c4e3 --- /dev/null +++ b/Credits.txt @@ -0,0 +1,3 @@ +Bitmap font by gnsh +https://opengameart.org/content/bitmap-font-0 + diff --git a/Data/Audio/explosion1.wav b/Data/Audio/explosion1.wav new file mode 100644 index 0000000..48d4128 Binary files /dev/null and b/Data/Audio/explosion1.wav differ diff --git a/Data/Audio/explosion2.wav b/Data/Audio/explosion2.wav new file mode 100644 index 0000000..cb1df9c Binary files /dev/null and b/Data/Audio/explosion2.wav differ diff --git a/Data/Audio/explosion3.wav b/Data/Audio/explosion3.wav new file mode 100644 index 0000000..f486c76 Binary files /dev/null and b/Data/Audio/explosion3.wav differ diff --git a/Data/Audio/jump.wav b/Data/Audio/jump.wav new file mode 100644 index 0000000..8048117 Binary files /dev/null and b/Data/Audio/jump.wav differ diff --git a/Data/Audio/laser.wav b/Data/Audio/laser.wav new file mode 100644 index 0000000..6a38364 Binary files /dev/null and b/Data/Audio/laser.wav differ diff --git a/Data/Audio/pickup.wav b/Data/Audio/pickup.wav new file mode 100644 index 0000000..d493606 Binary files /dev/null and b/Data/Audio/pickup.wav differ diff --git a/Data/Audio/powerUp.wav b/Data/Audio/powerUp.wav new file mode 100644 index 0000000..37ba692 Binary files /dev/null and b/Data/Audio/powerUp.wav differ diff --git a/Data/Audio/select.wav b/Data/Audio/select.wav new file mode 100644 index 0000000..10af1d9 Binary files /dev/null and b/Data/Audio/select.wav differ diff --git a/Data/Audio/shoot.wav b/Data/Audio/shoot.wav new file mode 100644 index 0000000..45b9651 Binary files /dev/null and b/Data/Audio/shoot.wav differ diff --git a/Data/Credits.txt b/Data/Credits.txt new file mode 100644 index 0000000..896c4e3 --- /dev/null +++ b/Data/Credits.txt @@ -0,0 +1,3 @@ +Bitmap font by gnsh +https://opengameart.org/content/bitmap-font-0 + diff --git a/Data/Fonts/Blue.txt b/Data/Fonts/Blue.txt new file mode 100644 index 0000000..663d6f0 --- /dev/null +++ b/Data/Fonts/Blue.txt @@ -0,0 +1,101 @@ +Texture Texture/Font.bmp +Frame 200 60 5 12 +Frame 205 60 5 12 +Frame 210 60 5 12 +Frame 215 60 5 12 +Frame 220 60 5 12 +Frame 225 60 5 12 +Frame 230 60 5 12 +Frame 235 60 5 12 +Frame 240 60 5 12 +Frame 245 60 5 12 +Frame 250 60 5 12 +Frame 255 60 5 12 +Frame 260 60 5 12 +Frame 265 60 5 12 +Frame 270 60 5 12 +Frame 275 60 5 12 +Frame 280 60 5 12 +Frame 285 60 5 12 +Frame 290 60 5 12 +Frame 295 60 5 12 +Frame 200 72 5 12 +Frame 205 72 5 12 +Frame 210 72 5 12 +Frame 215 72 5 12 +Frame 220 72 5 12 +Frame 225 72 5 12 +Frame 230 72 5 12 +Frame 235 72 5 12 +Frame 240 72 5 12 +Frame 245 72 5 12 +Frame 250 72 5 12 +Frame 255 72 5 12 +Frame 260 72 5 12 +Frame 265 72 5 12 +Frame 270 72 5 12 +Frame 275 72 5 12 +Frame 280 72 5 12 +Frame 285 72 5 12 +Frame 290 72 5 12 +Frame 295 72 5 12 +Frame 200 84 5 12 +Frame 205 84 5 12 +Frame 210 84 5 12 +Frame 215 84 5 12 +Frame 220 84 5 12 +Frame 225 84 5 12 +Frame 230 84 5 12 +Frame 235 84 5 12 +Frame 240 84 5 12 +Frame 245 84 5 12 +Frame 250 84 5 12 +Frame 255 84 5 12 +Frame 260 84 5 12 +Frame 265 84 5 12 +Frame 270 84 5 12 +Frame 275 84 5 12 +Frame 280 84 5 12 +Frame 285 84 5 12 +Frame 290 84 5 12 +Frame 295 84 5 12 +Frame 200 96 5 12 +Frame 205 96 5 12 +Frame 210 96 5 12 +Frame 215 96 5 12 +Frame 220 96 5 12 +Frame 225 96 5 12 +Frame 230 96 5 12 +Frame 235 96 5 12 +Frame 240 96 5 12 +Frame 245 96 5 12 +Frame 250 96 5 12 +Frame 255 96 5 12 +Frame 260 96 5 12 +Frame 265 96 5 12 +Frame 270 96 5 12 +Frame 275 96 5 12 +Frame 280 96 5 12 +Frame 285 96 5 12 +Frame 290 96 5 12 +Frame 295 96 5 12 +Frame 200 108 5 12 +Frame 205 108 5 12 +Frame 210 108 5 12 +Frame 215 108 5 12 +Frame 220 108 5 12 +Frame 225 108 5 12 +Frame 230 108 5 12 +Frame 235 108 5 12 +Frame 240 108 5 12 +Frame 245 108 5 12 +Frame 250 108 5 12 +Frame 255 108 5 12 +Frame 260 108 5 12 +Frame 265 108 5 12 +Frame 270 108 5 12 +Frame 275 108 5 12 +Frame 280 108 5 12 +Frame 285 108 5 12 +Frame 290 108 5 12 +Frame 295 108 5 12 diff --git a/Data/Fonts/Gold.txt b/Data/Fonts/Gold.txt new file mode 100644 index 0000000..812a9ed --- /dev/null +++ b/Data/Fonts/Gold.txt @@ -0,0 +1,101 @@ +Texture Texture/Font.bmp +Frame 0 0 5 12 +Frame 5 0 5 12 +Frame 10 0 5 12 +Frame 15 0 5 12 +Frame 20 0 5 12 +Frame 25 0 5 12 +Frame 30 0 5 12 +Frame 35 0 5 12 +Frame 40 0 5 12 +Frame 45 0 5 12 +Frame 50 0 5 12 +Frame 55 0 5 12 +Frame 60 0 5 12 +Frame 65 0 5 12 +Frame 70 0 5 12 +Frame 75 0 5 12 +Frame 80 0 5 12 +Frame 85 0 5 12 +Frame 90 0 5 12 +Frame 95 0 5 12 +Frame 0 12 5 12 +Frame 5 12 5 12 +Frame 10 12 5 12 +Frame 15 12 5 12 +Frame 20 12 5 12 +Frame 25 12 5 12 +Frame 30 12 5 12 +Frame 35 12 5 12 +Frame 40 12 5 12 +Frame 45 12 5 12 +Frame 50 12 5 12 +Frame 55 12 5 12 +Frame 60 12 5 12 +Frame 65 12 5 12 +Frame 70 12 5 12 +Frame 75 12 5 12 +Frame 80 12 5 12 +Frame 85 12 5 12 +Frame 90 12 5 12 +Frame 95 12 5 12 +Frame 0 24 5 12 +Frame 5 24 5 12 +Frame 10 24 5 12 +Frame 15 24 5 12 +Frame 20 24 5 12 +Frame 25 24 5 12 +Frame 30 24 5 12 +Frame 35 24 5 12 +Frame 40 24 5 12 +Frame 45 24 5 12 +Frame 50 24 5 12 +Frame 55 24 5 12 +Frame 60 24 5 12 +Frame 65 24 5 12 +Frame 70 24 5 12 +Frame 75 24 5 12 +Frame 80 24 5 12 +Frame 85 24 5 12 +Frame 90 24 5 12 +Frame 95 24 5 12 +Frame 0 36 5 12 +Frame 5 36 5 12 +Frame 10 36 5 12 +Frame 15 36 5 12 +Frame 20 36 5 12 +Frame 25 36 5 12 +Frame 30 36 5 12 +Frame 35 36 5 12 +Frame 40 36 5 12 +Frame 45 36 5 12 +Frame 50 36 5 12 +Frame 55 36 5 12 +Frame 60 36 5 12 +Frame 65 36 5 12 +Frame 70 36 5 12 +Frame 75 36 5 12 +Frame 80 36 5 12 +Frame 85 36 5 12 +Frame 90 36 5 12 +Frame 95 36 5 12 +Frame 0 48 5 12 +Frame 5 48 5 12 +Frame 10 48 5 12 +Frame 15 48 5 12 +Frame 20 48 5 12 +Frame 25 48 5 12 +Frame 30 48 5 12 +Frame 35 48 5 12 +Frame 40 48 5 12 +Frame 45 48 5 12 +Frame 50 48 5 12 +Frame 55 48 5 12 +Frame 60 48 5 12 +Frame 65 48 5 12 +Frame 70 48 5 12 +Frame 75 48 5 12 +Frame 80 48 5 12 +Frame 85 48 5 12 +Frame 90 48 5 12 +Frame 95 48 5 12 \ No newline at end of file diff --git a/Data/Fonts/Gray.txt b/Data/Fonts/Gray.txt new file mode 100644 index 0000000..2393d37 --- /dev/null +++ b/Data/Fonts/Gray.txt @@ -0,0 +1,101 @@ +Texture Texture/Font.bmp +Frame 0 120 5 12 +Frame 5 120 5 12 +Frame 10 120 5 12 +Frame 15 120 5 12 +Frame 20 120 5 12 +Frame 25 120 5 12 +Frame 30 120 5 12 +Frame 35 120 5 12 +Frame 40 120 5 12 +Frame 45 120 5 12 +Frame 50 120 5 12 +Frame 55 120 5 12 +Frame 60 120 5 12 +Frame 65 120 5 12 +Frame 70 120 5 12 +Frame 75 120 5 12 +Frame 80 120 5 12 +Frame 85 120 5 12 +Frame 90 120 5 12 +Frame 95 120 5 12 +Frame 0 132 5 12 +Frame 5 132 5 12 +Frame 10 132 5 12 +Frame 15 132 5 12 +Frame 20 132 5 12 +Frame 25 132 5 12 +Frame 30 132 5 12 +Frame 35 132 5 12 +Frame 40 132 5 12 +Frame 45 132 5 12 +Frame 50 132 5 12 +Frame 55 132 5 12 +Frame 60 132 5 12 +Frame 65 132 5 12 +Frame 70 132 5 12 +Frame 75 132 5 12 +Frame 80 132 5 12 +Frame 85 132 5 12 +Frame 90 132 5 12 +Frame 95 132 5 12 +Frame 0 144 5 12 +Frame 5 144 5 12 +Frame 10 144 5 12 +Frame 15 144 5 12 +Frame 20 144 5 12 +Frame 25 144 5 12 +Frame 30 144 5 12 +Frame 35 144 5 12 +Frame 40 144 5 12 +Frame 45 144 5 12 +Frame 50 144 5 12 +Frame 55 144 5 12 +Frame 60 144 5 12 +Frame 65 144 5 12 +Frame 70 144 5 12 +Frame 75 144 5 12 +Frame 80 144 5 12 +Frame 85 144 5 12 +Frame 90 144 5 12 +Frame 95 144 5 12 +Frame 0 156 5 12 +Frame 5 156 5 12 +Frame 10 156 5 12 +Frame 15 156 5 12 +Frame 20 156 5 12 +Frame 25 156 5 12 +Frame 30 156 5 12 +Frame 35 156 5 12 +Frame 40 156 5 12 +Frame 45 156 5 12 +Frame 50 156 5 12 +Frame 55 156 5 12 +Frame 60 156 5 12 +Frame 65 156 5 12 +Frame 70 156 5 12 +Frame 75 156 5 12 +Frame 80 156 5 12 +Frame 85 156 5 12 +Frame 90 156 5 12 +Frame 95 156 5 12 +Frame 0 168 5 12 +Frame 5 168 5 12 +Frame 10 168 5 12 +Frame 15 168 5 12 +Frame 20 168 5 12 +Frame 25 168 5 12 +Frame 30 168 5 12 +Frame 35 168 5 12 +Frame 40 168 5 12 +Frame 45 168 5 12 +Frame 50 168 5 12 +Frame 55 168 5 12 +Frame 60 168 5 12 +Frame 65 168 5 12 +Frame 70 168 5 12 +Frame 75 168 5 12 +Frame 80 168 5 12 +Frame 85 168 5 12 +Frame 90 168 5 12 +Frame 95 168 5 12 diff --git a/Data/Fonts/Green.txt b/Data/Fonts/Green.txt new file mode 100644 index 0000000..19d04b2 --- /dev/null +++ b/Data/Fonts/Green.txt @@ -0,0 +1,101 @@ +Texture Texture/Font.bmp +Frame 200 0 5 12 +Frame 205 0 5 12 +Frame 210 0 5 12 +Frame 215 0 5 12 +Frame 220 0 5 12 +Frame 225 0 5 12 +Frame 230 0 5 12 +Frame 235 0 5 12 +Frame 240 0 5 12 +Frame 245 0 5 12 +Frame 250 0 5 12 +Frame 255 0 5 12 +Frame 260 0 5 12 +Frame 265 0 5 12 +Frame 270 0 5 12 +Frame 275 0 5 12 +Frame 280 0 5 12 +Frame 285 0 5 12 +Frame 290 0 5 12 +Frame 295 0 5 12 +Frame 200 12 5 12 +Frame 205 12 5 12 +Frame 210 12 5 12 +Frame 215 12 5 12 +Frame 220 12 5 12 +Frame 225 12 5 12 +Frame 230 12 5 12 +Frame 235 12 5 12 +Frame 240 12 5 12 +Frame 245 12 5 12 +Frame 250 12 5 12 +Frame 255 12 5 12 +Frame 260 12 5 12 +Frame 265 12 5 12 +Frame 270 12 5 12 +Frame 275 12 5 12 +Frame 280 12 5 12 +Frame 285 12 5 12 +Frame 290 12 5 12 +Frame 295 12 5 12 +Frame 200 24 5 12 +Frame 205 24 5 12 +Frame 210 24 5 12 +Frame 215 24 5 12 +Frame 220 24 5 12 +Frame 225 24 5 12 +Frame 230 24 5 12 +Frame 235 24 5 12 +Frame 240 24 5 12 +Frame 245 24 5 12 +Frame 250 24 5 12 +Frame 255 24 5 12 +Frame 260 24 5 12 +Frame 265 24 5 12 +Frame 270 24 5 12 +Frame 275 24 5 12 +Frame 280 24 5 12 +Frame 285 24 5 12 +Frame 290 24 5 12 +Frame 295 24 5 12 +Frame 200 36 5 12 +Frame 205 36 5 12 +Frame 210 36 5 12 +Frame 215 36 5 12 +Frame 220 36 5 12 +Frame 225 36 5 12 +Frame 230 36 5 12 +Frame 235 36 5 12 +Frame 240 36 5 12 +Frame 245 36 5 12 +Frame 250 36 5 12 +Frame 255 36 5 12 +Frame 260 36 5 12 +Frame 265 36 5 12 +Frame 270 36 5 12 +Frame 275 36 5 12 +Frame 280 36 5 12 +Frame 285 36 5 12 +Frame 290 36 5 12 +Frame 295 36 5 12 +Frame 200 48 5 12 +Frame 205 48 5 12 +Frame 210 48 5 12 +Frame 215 48 5 12 +Frame 220 48 5 12 +Frame 225 48 5 12 +Frame 230 48 5 12 +Frame 235 48 5 12 +Frame 240 48 5 12 +Frame 245 48 5 12 +Frame 250 48 5 12 +Frame 255 48 5 12 +Frame 260 48 5 12 +Frame 265 48 5 12 +Frame 270 48 5 12 +Frame 275 48 5 12 +Frame 280 48 5 12 +Frame 285 48 5 12 +Frame 290 48 5 12 +Frame 295 48 5 12 diff --git a/Data/Fonts/LightBlue.txt b/Data/Fonts/LightBlue.txt new file mode 100644 index 0000000..550654a --- /dev/null +++ b/Data/Fonts/LightBlue.txt @@ -0,0 +1,101 @@ +Texture Texture/Font.bmp +Frame 0 60 5 12 +Frame 5 60 5 12 +Frame 10 60 5 12 +Frame 15 60 5 12 +Frame 20 60 5 12 +Frame 25 60 5 12 +Frame 30 60 5 12 +Frame 35 60 5 12 +Frame 40 60 5 12 +Frame 45 60 5 12 +Frame 50 60 5 12 +Frame 55 60 5 12 +Frame 60 60 5 12 +Frame 65 60 5 12 +Frame 70 60 5 12 +Frame 75 60 5 12 +Frame 80 60 5 12 +Frame 85 60 5 12 +Frame 90 60 5 12 +Frame 95 60 5 12 +Frame 0 72 5 12 +Frame 5 72 5 12 +Frame 10 72 5 12 +Frame 15 72 5 12 +Frame 20 72 5 12 +Frame 25 72 5 12 +Frame 30 72 5 12 +Frame 35 72 5 12 +Frame 40 72 5 12 +Frame 45 72 5 12 +Frame 50 72 5 12 +Frame 55 72 5 12 +Frame 60 72 5 12 +Frame 65 72 5 12 +Frame 70 72 5 12 +Frame 75 72 5 12 +Frame 80 72 5 12 +Frame 85 72 5 12 +Frame 90 72 5 12 +Frame 95 72 5 12 +Frame 0 84 5 12 +Frame 5 84 5 12 +Frame 10 84 5 12 +Frame 15 84 5 12 +Frame 20 84 5 12 +Frame 25 84 5 12 +Frame 30 84 5 12 +Frame 35 84 5 12 +Frame 40 84 5 12 +Frame 45 84 5 12 +Frame 50 84 5 12 +Frame 55 84 5 12 +Frame 60 84 5 12 +Frame 65 84 5 12 +Frame 70 84 5 12 +Frame 75 84 5 12 +Frame 80 84 5 12 +Frame 85 84 5 12 +Frame 90 84 5 12 +Frame 95 84 5 12 +Frame 0 96 5 12 +Frame 5 96 5 12 +Frame 10 96 5 12 +Frame 15 96 5 12 +Frame 20 96 5 12 +Frame 25 96 5 12 +Frame 30 96 5 12 +Frame 35 96 5 12 +Frame 40 96 5 12 +Frame 45 96 5 12 +Frame 50 96 5 12 +Frame 55 96 5 12 +Frame 60 96 5 12 +Frame 65 96 5 12 +Frame 70 96 5 12 +Frame 75 96 5 12 +Frame 80 96 5 12 +Frame 85 96 5 12 +Frame 90 96 5 12 +Frame 95 96 5 12 +Frame 0 108 5 12 +Frame 5 108 5 12 +Frame 10 108 5 12 +Frame 15 108 5 12 +Frame 20 108 5 12 +Frame 25 108 5 12 +Frame 30 108 5 12 +Frame 35 108 5 12 +Frame 40 108 5 12 +Frame 45 108 5 12 +Frame 50 108 5 12 +Frame 55 108 5 12 +Frame 60 108 5 12 +Frame 65 108 5 12 +Frame 70 108 5 12 +Frame 75 108 5 12 +Frame 80 108 5 12 +Frame 85 108 5 12 +Frame 90 108 5 12 +Frame 95 108 5 12 diff --git a/Data/Fonts/Purple.txt b/Data/Fonts/Purple.txt new file mode 100644 index 0000000..47e2a54 --- /dev/null +++ b/Data/Fonts/Purple.txt @@ -0,0 +1,101 @@ +Texture Texture/Font.bmp +Frame 100 60 5 12 +Frame 105 60 5 12 +Frame 110 60 5 12 +Frame 115 60 5 12 +Frame 120 60 5 12 +Frame 125 60 5 12 +Frame 130 60 5 12 +Frame 135 60 5 12 +Frame 140 60 5 12 +Frame 145 60 5 12 +Frame 150 60 5 12 +Frame 155 60 5 12 +Frame 160 60 5 12 +Frame 165 60 5 12 +Frame 170 60 5 12 +Frame 175 60 5 12 +Frame 180 60 5 12 +Frame 185 60 5 12 +Frame 190 60 5 12 +Frame 195 60 5 12 +Frame 100 72 5 12 +Frame 105 72 5 12 +Frame 110 72 5 12 +Frame 115 72 5 12 +Frame 120 72 5 12 +Frame 125 72 5 12 +Frame 130 72 5 12 +Frame 135 72 5 12 +Frame 140 72 5 12 +Frame 145 72 5 12 +Frame 150 72 5 12 +Frame 155 72 5 12 +Frame 160 72 5 12 +Frame 165 72 5 12 +Frame 170 72 5 12 +Frame 175 72 5 12 +Frame 180 72 5 12 +Frame 185 72 5 12 +Frame 190 72 5 12 +Frame 195 72 5 12 +Frame 100 84 5 12 +Frame 105 84 5 12 +Frame 110 84 5 12 +Frame 115 84 5 12 +Frame 120 84 5 12 +Frame 125 84 5 12 +Frame 130 84 5 12 +Frame 135 84 5 12 +Frame 140 84 5 12 +Frame 145 84 5 12 +Frame 150 84 5 12 +Frame 155 84 5 12 +Frame 160 84 5 12 +Frame 165 84 5 12 +Frame 170 84 5 12 +Frame 175 84 5 12 +Frame 180 84 5 12 +Frame 185 84 5 12 +Frame 190 84 5 12 +Frame 195 84 5 12 +Frame 100 96 5 12 +Frame 105 96 5 12 +Frame 110 96 5 12 +Frame 115 96 5 12 +Frame 120 96 5 12 +Frame 125 96 5 12 +Frame 130 96 5 12 +Frame 135 96 5 12 +Frame 140 96 5 12 +Frame 145 96 5 12 +Frame 150 96 5 12 +Frame 155 96 5 12 +Frame 160 96 5 12 +Frame 165 96 5 12 +Frame 170 96 5 12 +Frame 175 96 5 12 +Frame 180 96 5 12 +Frame 185 96 5 12 +Frame 190 96 5 12 +Frame 195 96 5 12 +Frame 100 108 5 12 +Frame 105 108 5 12 +Frame 110 108 5 12 +Frame 115 108 5 12 +Frame 120 108 5 12 +Frame 125 108 5 12 +Frame 130 108 5 12 +Frame 135 108 5 12 +Frame 140 108 5 12 +Frame 145 108 5 12 +Frame 150 108 5 12 +Frame 155 108 5 12 +Frame 160 108 5 12 +Frame 165 108 5 12 +Frame 170 108 5 12 +Frame 175 108 5 12 +Frame 180 108 5 12 +Frame 185 108 5 12 +Frame 190 108 5 12 +Frame 195 108 5 12 diff --git a/Data/Fonts/Red.txt b/Data/Fonts/Red.txt new file mode 100644 index 0000000..a77ad87 --- /dev/null +++ b/Data/Fonts/Red.txt @@ -0,0 +1,101 @@ +Texture Texture/Font.bmp +Frame 100 0 5 12 +Frame 105 0 5 12 +Frame 110 0 5 12 +Frame 115 0 5 12 +Frame 120 0 5 12 +Frame 125 0 5 12 +Frame 130 0 5 12 +Frame 135 0 5 12 +Frame 140 0 5 12 +Frame 145 0 5 12 +Frame 150 0 5 12 +Frame 155 0 5 12 +Frame 160 0 5 12 +Frame 165 0 5 12 +Frame 170 0 5 12 +Frame 175 0 5 12 +Frame 180 0 5 12 +Frame 185 0 5 12 +Frame 190 0 5 12 +Frame 195 0 5 12 +Frame 100 12 5 12 +Frame 105 12 5 12 +Frame 110 12 5 12 +Frame 115 12 5 12 +Frame 120 12 5 12 +Frame 125 12 5 12 +Frame 130 12 5 12 +Frame 135 12 5 12 +Frame 140 12 5 12 +Frame 145 12 5 12 +Frame 150 12 5 12 +Frame 155 12 5 12 +Frame 160 12 5 12 +Frame 165 12 5 12 +Frame 170 12 5 12 +Frame 175 12 5 12 +Frame 180 12 5 12 +Frame 185 12 5 12 +Frame 190 12 5 12 +Frame 195 12 5 12 +Frame 100 24 5 12 +Frame 105 24 5 12 +Frame 110 24 5 12 +Frame 115 24 5 12 +Frame 120 24 5 12 +Frame 125 24 5 12 +Frame 130 24 5 12 +Frame 135 24 5 12 +Frame 140 24 5 12 +Frame 145 24 5 12 +Frame 150 24 5 12 +Frame 155 24 5 12 +Frame 160 24 5 12 +Frame 165 24 5 12 +Frame 170 24 5 12 +Frame 175 24 5 12 +Frame 180 24 5 12 +Frame 185 24 5 12 +Frame 190 24 5 12 +Frame 195 24 5 12 +Frame 100 36 5 12 +Frame 105 36 5 12 +Frame 110 36 5 12 +Frame 115 36 5 12 +Frame 120 36 5 12 +Frame 125 36 5 12 +Frame 130 36 5 12 +Frame 135 36 5 12 +Frame 140 36 5 12 +Frame 145 36 5 12 +Frame 150 36 5 12 +Frame 155 36 5 12 +Frame 160 36 5 12 +Frame 165 36 5 12 +Frame 170 36 5 12 +Frame 175 36 5 12 +Frame 180 36 5 12 +Frame 185 36 5 12 +Frame 190 36 5 12 +Frame 195 36 5 12 +Frame 100 48 5 12 +Frame 105 48 5 12 +Frame 110 48 5 12 +Frame 115 48 5 12 +Frame 120 48 5 12 +Frame 125 48 5 12 +Frame 130 48 5 12 +Frame 135 48 5 12 +Frame 140 48 5 12 +Frame 145 48 5 12 +Frame 150 48 5 12 +Frame 155 48 5 12 +Frame 160 48 5 12 +Frame 165 48 5 12 +Frame 170 48 5 12 +Frame 175 48 5 12 +Frame 180 48 5 12 +Frame 185 48 5 12 +Frame 190 48 5 12 +Frame 195 48 5 12 diff --git a/Data/Fonts/Swamp.txt b/Data/Fonts/Swamp.txt new file mode 100644 index 0000000..fd42736 --- /dev/null +++ b/Data/Fonts/Swamp.txt @@ -0,0 +1,101 @@ +Texture Texture/Font.bmp +Frame 200 120 5 12 +Frame 205 120 5 12 +Frame 210 120 5 12 +Frame 215 120 5 12 +Frame 220 120 5 12 +Frame 225 120 5 12 +Frame 230 120 5 12 +Frame 235 120 5 12 +Frame 240 120 5 12 +Frame 245 120 5 12 +Frame 250 120 5 12 +Frame 255 120 5 12 +Frame 260 120 5 12 +Frame 265 120 5 12 +Frame 270 120 5 12 +Frame 275 120 5 12 +Frame 280 120 5 12 +Frame 285 120 5 12 +Frame 290 120 5 12 +Frame 295 120 5 12 +Frame 200 132 5 12 +Frame 205 132 5 12 +Frame 210 132 5 12 +Frame 215 132 5 12 +Frame 220 132 5 12 +Frame 225 132 5 12 +Frame 230 132 5 12 +Frame 235 132 5 12 +Frame 240 132 5 12 +Frame 245 132 5 12 +Frame 250 132 5 12 +Frame 255 132 5 12 +Frame 260 132 5 12 +Frame 265 132 5 12 +Frame 270 132 5 12 +Frame 275 132 5 12 +Frame 280 132 5 12 +Frame 285 132 5 12 +Frame 290 132 5 12 +Frame 295 132 5 12 +Frame 200 144 5 12 +Frame 205 144 5 12 +Frame 210 144 5 12 +Frame 215 144 5 12 +Frame 220 144 5 12 +Frame 225 144 5 12 +Frame 230 144 5 12 +Frame 235 144 5 12 +Frame 240 144 5 12 +Frame 245 144 5 12 +Frame 250 144 5 12 +Frame 255 144 5 12 +Frame 260 144 5 12 +Frame 265 144 5 12 +Frame 270 144 5 12 +Frame 275 144 5 12 +Frame 280 144 5 12 +Frame 285 144 5 12 +Frame 290 144 5 12 +Frame 295 144 5 12 +Frame 200 156 5 12 +Frame 205 156 5 12 +Frame 210 156 5 12 +Frame 215 156 5 12 +Frame 220 156 5 12 +Frame 225 156 5 12 +Frame 230 156 5 12 +Frame 235 156 5 12 +Frame 240 156 5 12 +Frame 245 156 5 12 +Frame 250 156 5 12 +Frame 255 156 5 12 +Frame 260 156 5 12 +Frame 265 156 5 12 +Frame 270 156 5 12 +Frame 275 156 5 12 +Frame 280 156 5 12 +Frame 285 156 5 12 +Frame 290 156 5 12 +Frame 295 156 5 12 +Frame 200 168 5 12 +Frame 205 168 5 12 +Frame 210 168 5 12 +Frame 215 168 5 12 +Frame 220 168 5 12 +Frame 225 168 5 12 +Frame 230 168 5 12 +Frame 235 168 5 12 +Frame 240 168 5 12 +Frame 245 168 5 12 +Frame 250 168 5 12 +Frame 255 168 5 12 +Frame 260 168 5 12 +Frame 265 168 5 12 +Frame 270 168 5 12 +Frame 275 168 5 12 +Frame 280 168 5 12 +Frame 285 168 5 12 +Frame 290 168 5 12 +Frame 295 168 5 12 diff --git a/Data/Fonts/White.txt b/Data/Fonts/White.txt new file mode 100644 index 0000000..acaa198 --- /dev/null +++ b/Data/Fonts/White.txt @@ -0,0 +1,101 @@ +Texture Texture/Font.bmp +Frame 100 120 5 12 +Frame 105 120 5 12 +Frame 110 120 5 12 +Frame 115 120 5 12 +Frame 120 120 5 12 +Frame 125 120 5 12 +Frame 130 120 5 12 +Frame 135 120 5 12 +Frame 140 120 5 12 +Frame 145 120 5 12 +Frame 150 120 5 12 +Frame 155 120 5 12 +Frame 160 120 5 12 +Frame 165 120 5 12 +Frame 170 120 5 12 +Frame 175 120 5 12 +Frame 180 120 5 12 +Frame 185 120 5 12 +Frame 190 120 5 12 +Frame 195 120 5 12 +Frame 100 132 5 12 +Frame 105 132 5 12 +Frame 110 132 5 12 +Frame 115 132 5 12 +Frame 120 132 5 12 +Frame 125 132 5 12 +Frame 130 132 5 12 +Frame 135 132 5 12 +Frame 140 132 5 12 +Frame 145 132 5 12 +Frame 150 132 5 12 +Frame 155 132 5 12 +Frame 160 132 5 12 +Frame 165 132 5 12 +Frame 170 132 5 12 +Frame 175 132 5 12 +Frame 180 132 5 12 +Frame 185 132 5 12 +Frame 190 132 5 12 +Frame 195 132 5 12 +Frame 100 144 5 12 +Frame 105 144 5 12 +Frame 110 144 5 12 +Frame 115 144 5 12 +Frame 120 144 5 12 +Frame 125 144 5 12 +Frame 130 144 5 12 +Frame 135 144 5 12 +Frame 140 144 5 12 +Frame 145 144 5 12 +Frame 150 144 5 12 +Frame 155 144 5 12 +Frame 160 144 5 12 +Frame 165 144 5 12 +Frame 170 144 5 12 +Frame 175 144 5 12 +Frame 180 144 5 12 +Frame 185 144 5 12 +Frame 190 144 5 12 +Frame 195 144 5 12 +Frame 100 156 5 12 +Frame 105 156 5 12 +Frame 110 156 5 12 +Frame 115 156 5 12 +Frame 120 156 5 12 +Frame 125 156 5 12 +Frame 130 156 5 12 +Frame 135 156 5 12 +Frame 140 156 5 12 +Frame 145 156 5 12 +Frame 150 156 5 12 +Frame 155 156 5 12 +Frame 160 156 5 12 +Frame 165 156 5 12 +Frame 170 156 5 12 +Frame 175 156 5 12 +Frame 180 156 5 12 +Frame 185 156 5 12 +Frame 190 156 5 12 +Frame 195 156 5 12 +Frame 100 168 5 12 +Frame 105 168 5 12 +Frame 110 168 5 12 +Frame 115 168 5 12 +Frame 120 168 5 12 +Frame 125 168 5 12 +Frame 130 168 5 12 +Frame 135 168 5 12 +Frame 140 168 5 12 +Frame 145 168 5 12 +Frame 150 168 5 12 +Frame 155 168 5 12 +Frame 160 168 5 12 +Frame 165 168 5 12 +Frame 170 168 5 12 +Frame 175 168 5 12 +Frame 180 168 5 12 +Frame 185 168 5 12 +Frame 190 168 5 12 +Frame 195 168 5 12 diff --git a/Data/Sprites/Arrow.txt b/Data/Sprites/Arrow.txt new file mode 100644 index 0000000..2d7cc31 --- /dev/null +++ b/Data/Sprites/Arrow.txt @@ -0,0 +1,5 @@ +Texture Texture/Main.bmp +Frame 0 48 16 16 Right +Frame 16 48 16 16 Up +Frame 32 48 16 16 Left +Frame 48 48 16 16 Down diff --git a/Data/Sprites/Blood.txt b/Data/Sprites/Blood.txt new file mode 100644 index 0000000..9ee276b --- /dev/null +++ b/Data/Sprites/Blood.txt @@ -0,0 +1,2 @@ +Texture Texture/Main.bmp +Frame 101 21 6 6 diff --git a/Data/Sprites/BloodSplat.txt b/Data/Sprites/BloodSplat.txt new file mode 100644 index 0000000..b8c0508 --- /dev/null +++ b/Data/Sprites/BloodSplat.txt @@ -0,0 +1,5 @@ +Texture Texture/Main.bmp +Frame 64 48 16 16 Up +Frame 80 48 16 16 Left +Frame 64 64 16 16 Down +Frame 80 64 16 16 Right diff --git a/Data/Sprites/Button.txt b/Data/Sprites/Button.txt new file mode 100644 index 0000000..88f9ec2 --- /dev/null +++ b/Data/Sprites/Button.txt @@ -0,0 +1,8 @@ +Texture Texture/Main.bmp +Frame 0 0 16 16 Z +Frame 16 0 16 16 X +Frame 32 0 16 16 C +Frame 48 0 16 16 Right +Frame 64 0 16 16 Up +Frame 80 0 16 16 Left +Frame 96 0 16 16 Down diff --git a/Data/Sprites/Digits.txt b/Data/Sprites/Digits.txt new file mode 100644 index 0000000..11bafa0 --- /dev/null +++ b/Data/Sprites/Digits.txt @@ -0,0 +1,23 @@ +Texture Texture/Digits.bmp +Frame 0 0 7 12 +Frame 7 0 7 12 +Frame 14 0 7 12 +Frame 21 0 7 12 +Frame 28 0 7 12 +Frame 35 0 7 12 +Frame 42 0 7 12 +Frame 49 0 7 12 +Frame 56 0 7 12 +Frame 63 0 7 12 +Frame 70 0 7 12 +Frame 0 12 7 12 +Frame 7 12 7 12 +Frame 14 12 7 12 +Frame 21 12 7 12 +Frame 28 12 7 12 +Frame 35 12 7 12 +Frame 42 12 7 12 +Frame 49 12 7 12 +Frame 56 12 7 12 +Frame 63 12 7 12 +Frame 70 12 7 12 diff --git a/Data/Sprites/Flash.txt b/Data/Sprites/Flash.txt new file mode 100644 index 0000000..c10a98b --- /dev/null +++ b/Data/Sprites/Flash.txt @@ -0,0 +1,3 @@ +Texture Texture/Main.bmp +Frame 0 96 16 16 +Frame 112 0 16 16 diff --git a/Data/Sprites/Hero.txt b/Data/Sprites/Hero.txt new file mode 100644 index 0000000..15e033a --- /dev/null +++ b/Data/Sprites/Hero.txt @@ -0,0 +1,7 @@ +Texture Texture/Main.bmp +Frame 0 80 16 16 Stand +Frame 16 80 16 16 Hold +Frame 32 80 16 16 Jump +Frame 48 80 16 16 Run +Frame 64 80 16 16 Dead +Frame 32 96 16 16 Climb \ No newline at end of file diff --git a/Data/Sprites/Item.txt b/Data/Sprites/Item.txt new file mode 100644 index 0000000..ffc622c --- /dev/null +++ b/Data/Sprites/Item.txt @@ -0,0 +1,7 @@ +Texture Texture/Main.bmp +Frame 0 64 16 16 Time +Frame 16 64 16 16 Bullet +Frame 32 64 16 16 Torch +Frame 48 64 16 16 Key +Frame 80 16 16 16 Rope +Frame 16 96 16 16 Rock \ No newline at end of file diff --git a/Data/Sprites/Ladder.txt b/Data/Sprites/Ladder.txt new file mode 100644 index 0000000..48ba187 --- /dev/null +++ b/Data/Sprites/Ladder.txt @@ -0,0 +1,3 @@ +Texture Texture/Main.bmp +Frame 64 16 16 16 Ladder +Frame 80 16 16 16 Rope diff --git a/Data/Sprites/Light.txt b/Data/Sprites/Light.txt new file mode 100644 index 0000000..43f0815 --- /dev/null +++ b/Data/Sprites/Light.txt @@ -0,0 +1,2 @@ +Texture Texture/Main.bmp +Frame 80 80 48 48 \ No newline at end of file diff --git a/Data/Sprites/Mine.txt b/Data/Sprites/Mine.txt new file mode 100644 index 0000000..a39a6db --- /dev/null +++ b/Data/Sprites/Mine.txt @@ -0,0 +1,2 @@ +Texture Texture/Main.bmp +Frame 80 32 16 16 diff --git a/Data/Sprites/Passage.txt b/Data/Sprites/Passage.txt new file mode 100644 index 0000000..bfd085b --- /dev/null +++ b/Data/Sprites/Passage.txt @@ -0,0 +1,3 @@ +Texture Texture/Main.bmp +Frame 32 32 16 16 Closed +Frame 48 32 16 16 Open diff --git a/Data/Sprites/Plank.txt b/Data/Sprites/Plank.txt new file mode 100644 index 0000000..409b325 --- /dev/null +++ b/Data/Sprites/Plank.txt @@ -0,0 +1,3 @@ +Texture Texture/Main.bmp +Frame 96 32 16 16 + diff --git a/Data/Sprites/Possum.txt b/Data/Sprites/Possum.txt new file mode 100644 index 0000000..a7c6215 --- /dev/null +++ b/Data/Sprites/Possum.txt @@ -0,0 +1 @@ +Texture Texture/possum.bmp diff --git a/Data/Sprites/PushRock.txt b/Data/Sprites/PushRock.txt new file mode 100644 index 0000000..41ee549 --- /dev/null +++ b/Data/Sprites/PushRock.txt @@ -0,0 +1,2 @@ +Texture Texture/Main.bmp +Frame 48 16 16 16 diff --git a/Data/Sprites/Spike.txt b/Data/Sprites/Spike.txt new file mode 100644 index 0000000..dcf418f --- /dev/null +++ b/Data/Sprites/Spike.txt @@ -0,0 +1,3 @@ +Texture Texture/Main.bmp +Frame 0 32 16 16 Clean +Frame 16 32 16 16 Bloody diff --git a/Data/Sprites/Stone.txt b/Data/Sprites/Stone.txt new file mode 100644 index 0000000..18c7089 --- /dev/null +++ b/Data/Sprites/Stone.txt @@ -0,0 +1,2 @@ +Texture Texture/Main.bmp +Frame 20 100 8 8 diff --git a/Data/Sprites/Wall.txt b/Data/Sprites/Wall.txt new file mode 100644 index 0000000..ffb9381 --- /dev/null +++ b/Data/Sprites/Wall.txt @@ -0,0 +1,4 @@ +Texture Texture/Main.bmp +Frame 0 16 16 16 Backdrop +Frame 16 16 16 16 Dirt +Frame 32 16 16 16 Grass diff --git a/Data/Templates/Any.txt b/Data/Templates/Any.txt new file mode 100644 index 0000000..419a5a9 --- /dev/null +++ b/Data/Templates/Any.txt @@ -0,0 +1,64 @@ +Room +gggggggggg +g8ggg8g8gg +g8ggg8g8gg +g888g858gg +g8g8g8g8gg +g858g8g8gg + +Room +gggggggggg +gggggggggg +gggggggggg +gggggggggg +gggggggggg +gggggggggg + +Room +0000gggggg +0000gggggg +0000gggggg +0000gggggg +0000gggggg +gggggggggg + +Room +gggggg0000 +gggggg0000 +gggggg0000 +gggggg0000 +gggggg0000 +gggggggggg + +Room +000000000g +000000000g +000000000g +000000000g +000000000g +gggggggggg + +Room +g000000000 +g000000000 +g000000000 +g000000000 +g000000000 +gggggggggg + +Room +0000l00000 +0000l00000 +7775557757 +5557775575 +0000000000 +gggggggggg + +Room +g000l0000g +g000l0000g +g000l0000g +g000l0000g +gDDDlDDDDg +gggggggggg + diff --git a/Data/Templates/LR.txt b/Data/Templates/LR.txt new file mode 100644 index 0000000..3f62b67 --- /dev/null +++ b/Data/Templates/LR.txt @@ -0,0 +1,15 @@ +Room +5555555555 +0000000000 +0000000000 +0000000000 +0000000000 +GGGGGGGGGG + +Room +5555555555 +0000000000 +000lGGGGl0 +000lggggl0 +000lggggl0 +GGGGggggGG diff --git a/Data/Templates/LRD.txt b/Data/Templates/LRD.txt new file mode 100644 index 0000000..9f920b5 --- /dev/null +++ b/Data/Templates/LRD.txt @@ -0,0 +1,15 @@ +Room +0000000000 +0000000000 +0000000000 +0000000000 +0000000000 +GGGGPPPPGG + +Room +0000000000 +0000000000 +0000000000 +0lgggggggl +0l000000gl +GGGGPPPPgG diff --git a/Data/Templates/LRU.txt b/Data/Templates/LRU.txt new file mode 100644 index 0000000..c022d32 --- /dev/null +++ b/Data/Templates/LRU.txt @@ -0,0 +1,15 @@ +Room +0000l00000 +0000l00000 +0000l00000 +0000l00000 +0000l00000 +GGGGGGGGGG + +Room +0000l00000 +0000l00000 +0lggggggl0 +0l000000l0 +0l000000l0 +GGGGGGGGGG \ No newline at end of file diff --git a/Data/Templates/LRUD.txt b/Data/Templates/LRUD.txt new file mode 100644 index 0000000..58220b2 --- /dev/null +++ b/Data/Templates/LRUD.txt @@ -0,0 +1,15 @@ +Room +0000l00000 +0000l00000 +0000l00000 +0000l00000 +0000l00000 +GGPPlPPPGG + +Room +0000l00000 +0000l00000 +ggggggglgg +0000000l00 +0000l00l00 +GGPPlGGGGG diff --git a/Data/Texture/Digits.bmp b/Data/Texture/Digits.bmp new file mode 100644 index 0000000..c60bf52 Binary files /dev/null and b/Data/Texture/Digits.bmp differ diff --git a/Data/Texture/Font.bmp b/Data/Texture/Font.bmp new file mode 100644 index 0000000..07e71f4 Binary files /dev/null and b/Data/Texture/Font.bmp differ diff --git a/Data/Texture/Main.bmp b/Data/Texture/Main.bmp new file mode 100644 index 0000000..9fddf22 Binary files /dev/null and b/Data/Texture/Main.bmp differ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3386234 --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +# GNU makefile + +# User configuration +CXXFLAGS ?= -Og -std=c++17 -Iinclude +LDFLAGS ?= -lSDL2 +TARGET = eulamadness + +# System configuration +include Makefile.srcs + +EXE = $(TARGET) +OBJS = $(SRCS:.cpp=.o) + +# Targets +all: $(EXE) + +dist: $(EXE) + mkdir -p dist + cp $(EXE) dist/ + +$(EXE): $(OBJS) + $(CXX) -o $@ $(OBJS) $(LDFLAGS) + +.cpp.o: + $(CXX) $(CXXFLAGS) -c $< -o $@ + +clean: + -rm -rf $(OBJS) $(EXE) dist diff --git a/Makefile.emcc b/Makefile.emcc new file mode 100644 index 0000000..7764b63 --- /dev/null +++ b/Makefile.emcc @@ -0,0 +1,30 @@ +# GNU makefile + +# User configuration +CC = emcc +CXX = emcc +CXXFLAGS ?= -sUSE_SDL=2 -std=c++17 -Iinclude -fexceptions +LDFLAGS ?= -sUSE_SDL=2 -fexceptions --embed-file=data.pak@data.pak -sMAX_WEBGL_VERSION=2 +TARGET = eulamadness.html + +# System configuration +include Makefile.srcs + +EXE = $(TARGET) +OBJS = $(SRCS:.cpp=.o) + +# Targets +all: $(EXE) + +dist: $(EXE) + mkdir -p dist + cp $(EXE) dist/ + +$(EXE): $(OBJS) + $(CXX) -o $@ $(OBJS) $(LDFLAGS) + +.cpp.o: + $(CXX) $(CXXFLAGS) -c $< -o $@ + +clean: + -rm -rf $(OBJS) $(EXE) eulamadness.js eulamadness.wasm eulamadness.data dist diff --git a/Makefile.srcs b/Makefile.srcs new file mode 100644 index 0000000..0bc92c9 --- /dev/null +++ b/Makefile.srcs @@ -0,0 +1,19 @@ +# Source files + +SRCS = \ + src/Alarm.cpp \ + src/App.cpp \ + src/Common.cpp \ + src/Entity.cpp \ + src/EntityManager.cpp \ + src/FileManager.cpp \ + src/Gridmap.cpp \ + src/Main.cpp \ + src/RenderManager.cpp \ + src/SpriteManager.cpp \ + src/State.cpp \ + src/SurfaceManager.cpp \ + src/FontManager.cpp \ + src/DigitManager.cpp \ + src/RoomEntity.cpp \ + src/Variable.cpp \ No newline at end of file diff --git a/makepak.py b/makepak.py new file mode 100755 index 0000000..b5ac5bd --- /dev/null +++ b/makepak.py @@ -0,0 +1,43 @@ +import sys +import struct +import os + +#dummy class for stuffing the file headers into +class FileEntry: + pass + +#arguments are source directory, then target filename e.g. "pak1.pak" +rootdir = sys.argv[1] +pakfilename = sys.argv[2] + +pakfile = open(pakfilename,"wb") + +#write a dummy header to start with +pakfile.write(struct.Struct("<4s2l").pack(b"PACK",0,0)) + +#walk the directory recursively, add the files and record the file entries +offset = 12 +fileentries = [] +for root, subFolders, files in os.walk(rootdir): + for file in files: + entry = FileEntry() + impfilename = os.path.join(root,file) + entry.filename = os.path.relpath(impfilename,rootdir).replace("\\","/") + with open(impfilename, "rb") as importfile: + pakfile.write(importfile.read()) + entry.offset = offset + entry.length = importfile.tell() + offset = offset + entry.length + fileentries.append(entry) +tablesize = 0 + +#after all the file data, write the list of entries +for entry in fileentries: + pakfile.write(struct.Struct("<56s").pack(entry.filename.encode("ascii"))) + pakfile.write(struct.Struct("second; + if (alarm.Target) { + alarm.Current += delta; + } + + if (alarm.Current >= alarm.Target) { + DueAlarms_.push(it->first); + } + } +} + +bool TAlarm::Next(TId& id) { + bool repeat; + + do { + repeat = false; + + if (DueAlarms_.empty()) + return false; + + id = DueAlarms_.front(); + DueAlarms_.pop(); + + auto& alarm = Alarms_[id]; + if (alarm.Target) { + if (alarm.Current >= alarm.Target) { + alarm.Current -= alarm.Target; + } + + if (alarm.Current >= alarm.Target) { + DueAlarms_.push(id); + } + } else { + alarm.Current = 0; + repeat = true; + } + } while (repeat); + + return true; +} + +} // namespace NGame diff --git a/src/Alarm.h b/src/Alarm.h new file mode 100644 index 0000000..730e2d5 --- /dev/null +++ b/src/Alarm.h @@ -0,0 +1,30 @@ +#pragma once + +#include "Common.h" + +#include +#include +#include + +namespace NGame { + +class TAlarm { +public: + using TId = int; + + void Set(TId id, std::uint32_t millis); + void Unset(TId id); + void Update(std::uint32_t delta); + bool Next(TId& id); + +private: + struct TData { + std::uint32_t Target; + std::uint32_t Current; + }; + + std::unordered_map Alarms_; + std::queue DueAlarms_; +}; + +} \ No newline at end of file diff --git a/src/App.cpp b/src/App.cpp new file mode 100644 index 0000000..e88dd45 --- /dev/null +++ b/src/App.cpp @@ -0,0 +1,116 @@ +#include "App.h" + +#include + +#ifdef __EMSCRIPTEN__ +#include +#endif + +namespace NGame { + +TApp* TApp::Instance() { + static TApp* instance = nullptr; + + if (!instance) { + instance = new TApp(); + } + + return instance; +} + +TApp::TApp() { + FileManager_ = std::make_unique("Data", "data.pak"); + SurfaceManager_ = std::make_unique(*FileManager_); + RenderManager_ = std::make_unique(*SurfaceManager_, 320, 240, "SDL Game"); + SpriteManager_ = std::make_unique(*FileManager_, *RenderManager_); + EntityManager_ = std::make_unique(*RenderManager_, 1000 / 60); + FontManager_ = std::make_unique(*SpriteManager_); + DigitManager_ = std::make_unique(*SpriteManager_); +} + +TApp::~TApp() { +} + +void TApp::Process() { + if (!RenderManager_->IsRunning()) { + #ifdef __EMSCRIPTEN__ + emscripten_cancel_main_loop(); /* this should "kill" the app. */ + #else + exit(0); + #endif + } + + EntityManager_->Run(); + RenderManager_->Run(); +} + +int TApp::Run() { + Process(); + return 0; +} + +TState& TApp::State() { + return State_; +} + +const TState& TApp::State() const { + return State_; +} + +TFileManager& TApp::FileManager() { + return *FileManager_; +} + +TRenderManager& TApp::RenderManager() { + return *RenderManager_; +} + +TEntityManager& TApp::EntityManager() { + return *EntityManager_; +} + +TSurfaceManager& TApp::SurfaceManager() { + return *SurfaceManager_; +} + +TSpriteManager& TApp::SpriteManager() { + return *SpriteManager_; +} + +TFontManager& TApp::FontManager() { + return *FontManager_; +} + +TDigitManager& TApp::DigitManager() { + return *DigitManager_; +} + +const TFileManager& TApp::FileManager() const { + return *FileManager_; +} + +const TRenderManager& TApp::RenderManager() const { + return *RenderManager_; +} + +const TEntityManager& TApp::EntityManager() const { + return *EntityManager_; +} + +const TSurfaceManager& TApp::SurfaceManager() const { + return *SurfaceManager_; +} + +const TSpriteManager& TApp::SpriteManager() const { + return *SpriteManager_; +} + +const TFontManager& TApp::FontManager() const { + return *FontManager_; +} + +const TDigitManager& TApp::DigitManager() const { + return *DigitManager_; +} + +} // namespace NGame \ No newline at end of file diff --git a/src/App.h b/src/App.h new file mode 100644 index 0000000..88566e2 --- /dev/null +++ b/src/App.h @@ -0,0 +1,64 @@ +#pragma once + +#include "Common.h" + +#include "DigitManager.h" +#include "EntityManager.h" +#include "FileManager.h" +#include "FontManager.h" +#include "RenderManager.h" +#include "SpriteManager.h" +#include "State.h" +#include "SurfaceManager.h" + +#include +#include +#include +#include +#include + + +namespace NGame { + +class TApp { +public: + static TApp* Instance(); + int Run(); + + TState& State(); + const TState& State() const; + + TFileManager& FileManager(); + TRenderManager& RenderManager(); + TEntityManager& EntityManager(); + TSurfaceManager& SurfaceManager(); + TSpriteManager& SpriteManager(); + TFontManager& FontManager(); + TDigitManager& DigitManager(); + + const TFileManager& FileManager() const; + const TRenderManager& RenderManager() const; + const TEntityManager& EntityManager() const; + const TSurfaceManager& SurfaceManager() const; + const TSpriteManager& SpriteManager() const; + const TFontManager& FontManager() const; + const TDigitManager& DigitManager() const; + +private: + TApp(); + ~TApp(); + void Process(); + +private: + std::unique_ptr FileManager_; + std::unique_ptr RenderManager_; + std::unique_ptr EntityManager_; + std::unique_ptr SurfaceManager_; + std::unique_ptr SpriteManager_; + std::unique_ptr FontManager_; + std::unique_ptr DigitManager_; + TState State_; +}; + +} // namespace NGame + diff --git a/src/Common.cpp b/src/Common.cpp new file mode 100644 index 0000000..c0cae9a --- /dev/null +++ b/src/Common.cpp @@ -0,0 +1,81 @@ +#include "Common.h" + +#include +#include +#include +#include + +namespace NGame { + +std::string ToUpper(const std::string_view& value) { + std::string result; + result.resize(value.size()); + + std::transform(value.begin(), value.end(), result.begin(), ::toupper); + return result; +} + +std::string ToLower(const std::string_view& value) { + std::string result; + result.resize(value.size()); + + std::transform(value.begin(), value.end(), result.begin(), ::tolower); + return result; +} + +std::string_view LeftTrim(const std::string_view& value) { + auto firstNonSpace = std::find_if(value.begin(), value.end(), [](char symbol) { + return !std::isspace(symbol); + }); + + auto length = std::distance(firstNonSpace, value.end()); + return std::string_view(&*firstNonSpace, length); +} + +std::string_view RightTrim(const std::string_view& value) { + auto lastNonSpace = std::find_if(value.rbegin(), value.rend(), [](char symbol) { + return !std::isspace(symbol); + }); + + auto index = value.size() - std::distance(value.rbegin(), lastNonSpace); + auto length = std::distance(value.begin(), value.begin() + index); + return std::string_view(&*value.begin(), length); +} + +std::string_view Trim(const std::string_view& value) { + return LeftTrim(RightTrim(value)); +} + +std::string_view NextToken(std::string_view& value, const std::string_view& token) { + auto position = value.find(token); + auto result = value.substr(0, position); + + if (position == std::string_view::npos) { + value = {}; + } else { + value = value.substr(position + token.size()); + } + + return result; +} + +bool RectContains(const Vec2i& position, const Vec2i& size, const Vec2i& point) { + if (point.X < position.X || point.X - position.X > size.X || + point.Y < position.Y || point.Y - position.Y > size.Y) { + return false; + } + + return true; +} + +bool RectOverlaps(const Vec2i& positionA, const Vec2i& sizeA, const Vec2i& positionB, const Vec2i& sizeB) { + if (positionB.X + sizeB.X <= positionA.X || + positionA.X + sizeA.X <= positionB.X || + positionB.Y + sizeB.Y <= positionA.Y || + positionA.Y + sizeA.Y <= positionB.Y) { + return false; + } + return true; +} + +} // namespace NGame diff --git a/src/Common.h b/src/Common.h new file mode 100644 index 0000000..ccbddf8 --- /dev/null +++ b/src/Common.h @@ -0,0 +1,174 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#define GAME_UNUSED(x) (void)(x) + +#define DELETE_COPY(NAME) \ + NAME(const NAME&) = delete; \ + NAME& operator=(const NAME&) = delete; + +namespace NGame { + +template +struct TOverload : Ts... { using Ts::operator()...; }; + +template +TOverload(Ts...) -> TOverload; + +template +struct Vec2 { + using TType = T; + + Vec2() + : X(), Y() { + } + + Vec2(T value) + : X(value), Y(value) { + } + Vec2(T x, T y) + : X(x), Y(y) { + } + + Vec2 operator+(const Vec2& other) const { + Vec2 result(X + other.X, Y + other.Y); + return result; + } + + Vec2& operator+=(const Vec2& other) { + X += other.X; + Y += other.Y; + return *this; + } + + Vec2 operator-(const Vec2& other) const { + Vec2 result(X - other.X, Y - other.Y); + return result; + } + + Vec2& operator-=(const Vec2& other) { + X -= other.X; + Y -= other.Y; + return *this; + } + + template + Vec2 operator*(const Vec2& other) const { + Vec2 result(X * other.X, Y * other.Y); + return result; + } + + template + Vec2& operator*=(const Vec2& other) { + X *= other.X; + Y *= other.Y; + return *this; + } + + template + Vec2 operator*(const Q& other) const { + Vec2 result(X * other, Y * other); + return result; + } + + template + Vec2& operator*=(const Q& other) { + X *= other; + Y *= other; + return *this; + } + + template + Vec2 operator/(const Vec2& other) const { + Vec2 result(X / other.X, Y / other.Y); + return result; + } + + template + Vec2& operator/=(const Vec2& other) { + X /= other.X; + Y /= other.Y; + return *this; + } + + template + Vec2 operator/(const Q& other) const { + Vec2 result(X / other, Y / other); + return result; + } + + template + Vec2& operator/=(const Q& other) { + X /= other; + Y /= other; + return *this; + } + + template::value>::type> + Q Length() const { + return std::sqrt(X * X + Y * Y); + } + + template::value>::type> + Vec2 Normilize() const { + return *this / Length(); + } + + template::value>::type> + float Length() const { + return std::sqrt(static_cast(X * X + Y * Y)); + } + + bool operator==(const Vec2& other) const { + return X == other.X && Y == other.Y; + } + + bool operator!=(const Vec2& other) const { + return X != other.X || Y != other.Y; + } + + union { + struct { + T X; + T Y; + }; + T Data[2]; + }; +}; + +using Vec2f = Vec2; +using Vec2i = Vec2; + +std::string ToUpper(const std::string_view& value); +std::string ToLower(const std::string_view& value); +std::string_view LeftTrim(const std::string_view& value); +std::string_view RightTrim(const std::string_view& value); +std::string_view Trim(const std::string_view& value); +std::string_view NextToken(std::string_view& value, const std::string_view& token); + +bool RectContains(const Vec2i& position, const Vec2i& size, const Vec2i& point); +bool RectOverlaps(const Vec2i& positionA, const Vec2i& sizeA, const Vec2i& positionB, const Vec2i& sizeB); + +} // namespace NGame + +template<> +struct std::hash { + std::size_t operator()(const NGame::Vec2i& key) const { + return ((static_cast(key.X) * 73856093) ^ + (static_cast(key.Y) * 19349663)); + } +}; + +template<> +struct std::hash { + std::size_t operator()(const NGame::Vec2f& key) const { + return ((static_cast(key.X) * 73856093) ^ + (static_cast(key.Y) * 19349663)); + } +}; \ No newline at end of file diff --git a/src/DigitManager.cpp b/src/DigitManager.cpp new file mode 100644 index 0000000..85328a5 --- /dev/null +++ b/src/DigitManager.cpp @@ -0,0 +1,36 @@ +#include "DigitManager.h" + +namespace NGame { + +TDigitManager::TDigitManager(TSpriteManager& spriteManager) + : SpriteManager_(spriteManager) { + Digits_ = SpriteManager_.Get("Sprites/Digits.txt"); +} + +void TDigitManager::Draw(const Vec2i position, int value, int length, int point) { + Vec2i currentPosition = {position.X + length * GlyphSize.X, position.Y}; + + for (size_t remainder = length; remainder; --remainder, --point) { + if (value) { + auto digit = value % 10; + value /= 10; + + if (point == 0) { + SpriteManager_.Draw(Digits_, digit + 11, currentPosition); + } else { + SpriteManager_.Draw(Digits_, digit, currentPosition); + } + } else { + if (point == 0) { + SpriteManager_.Draw(Digits_, 21, currentPosition); + } else { + SpriteManager_.Draw(Digits_, 10, currentPosition); + } + } + + currentPosition.X -= GlyphSize.X; + } + +} + +} // namespace NGame \ No newline at end of file diff --git a/src/DigitManager.h b/src/DigitManager.h new file mode 100644 index 0000000..4b42ad2 --- /dev/null +++ b/src/DigitManager.h @@ -0,0 +1,22 @@ +#pragma once + +#include "Common.h" + +#include "SpriteManager.h" + +namespace NGame { + +class TDigitManager { +public: + TDigitManager(TSpriteManager& spriteManager); + DELETE_COPY(TDigitManager) + + void Draw(const Vec2i position, int value, int length, int point); + +private: + TSpriteManager& SpriteManager_; + const Vec2i GlyphSize = {7, 12}; + std::shared_ptr Digits_; +}; + +} // namespace NGame \ No newline at end of file diff --git a/src/Entity.cpp b/src/Entity.cpp new file mode 100644 index 0000000..3948f69 --- /dev/null +++ b/src/Entity.cpp @@ -0,0 +1,113 @@ +#include "Entity.h" + +namespace NGame { + +TEntity::TEntity(TEntity::TId id) + : Id_(id) { + +} + +void TEntity::Tick(std::uint32_t delta) { + Alarm_.Update(delta); + + TAlarm::TId alarmId; + while (Alarm_.Next(alarmId)) { + Alarm(alarmId); + } + Update(delta); +} + +void TEntity::Input(SDL_Event* event) { + GAME_UNUSED(event); +} + +void TEntity::Update(std::uint32_t delta) { + GAME_UNUSED(delta); +} + +void TEntity::Alarm(TAlarm::TId id) { + GAME_UNUSED(id); +} + +void TEntity::Draw() const { +} + +void TEntity::SetPosition(Vec2i value) { + Position_ = value; +} + +void TEntity::SetSize(Vec2i value) { + Size_ = value; +} + +const Vec2i& TEntity::Position() const { + return Position_; +} + +const Vec2i& TEntity::Size() const { + return Size_; +} + +const Vec2i& TEntity::AckPosition() const { + return AckPosition_; +} + +const Vec2i& TEntity::AckSize() const { + return AckSize_; +} + +bool TEntity::HasChanges() const { + if (AckPosition_ != Position_ || AckSize_ != Size_) + return true; + + return false; +} + +void TEntity::AckChanges() { + AckPosition_ = Position_; + AckSize_ = Size_; +} + +TEntity::TCollisionGroup TEntity::CollisionGroup() const { + return CollisionGroup_; +} + +void TEntity::SetCollisionGroup(TCollisionGroup group) { + CollisionGroup_ = group; +} + +float TEntity::MovementPerTick(std::uint32_t delta, float speed) { + return speed * (delta / 1000.0f); +} + +Vec2f TEntity::MovementPerTick(std::uint32_t delta, Vec2f speed) { + return speed * (delta / 1000.0f); +} + +std::pair TEntity::NextMovement(float speed, float fraction) { + std::pair result; + + result.second = speed + fraction; + result.first = static_cast(result.second); + result.second -= result.first; + + return result; +} + +TEntity::TId TEntity::Id() const { + return Id_; +} + +void TEntity::Remove() { + IsRemoved_ = true; +} + +bool TEntity::IsRemoved() const { + return IsRemoved_; +} + +bool TEntity::IsPersistent() const { + return IsPersistent_; +} + +} // namespace NGame diff --git a/src/Entity.h b/src/Entity.h new file mode 100644 index 0000000..24a9245 --- /dev/null +++ b/src/Entity.h @@ -0,0 +1,112 @@ +#pragma once + +#include "Common.h" + +#include "Alarm.h" +#include "State.h" + +#include +#include +#include +#include + +namespace NGame { + +class TEntity { +public: + using TId = std::uint32_t; + using TCollisionGroup = std::bitset<32>; + + TEntity(TEntity::TId id); + virtual ~TEntity() = default; + void Tick(std::uint32_t delta); + virtual void Input(SDL_Event* event); + virtual void Update(std::uint32_t delta); + virtual void Alarm(TAlarm::TId id); + virtual void Draw() const; + + void SetPosition(Vec2i value); + void SetSize(Vec2i value); + const Vec2i& Position() const; + const Vec2i& Size() const; + const Vec2i& AckPosition() const; + const Vec2i& AckSize() const; + bool HasChanges() const; + void AckChanges(); + + TCollisionGroup CollisionGroup() const; + void SetCollisionGroup(TCollisionGroup group); + static float MovementPerTick(std::uint32_t delta, float speed); + static Vec2f MovementPerTick(std::uint32_t delta, Vec2f speed); + std::pair NextMovement(float speed, float fraction); + + template + std::pair> MoveWithCondition(Vec2f speed, Vec2f fraction, UnaryPredicate p) { + auto distanceX = NextMovement(speed.X, fraction.X); + auto distanceY = NextMovement(speed.Y, fraction.Y); + auto directionFlags = std::make_pair(true, true); + + Vec2f resultFraction(distanceX.second, distanceY.second); + Vec2i resultDistance(distanceX.first, distanceY.first); + + Vec2i newPosition = Position() + resultDistance; + if (p(newPosition)) { + // There is no problem with the new position, we are done + SetPosition(newPosition); + } else { + // There might be some problem, move 1px at a time + Vec2i direction(1, 1); + + if (resultDistance.X < 0) { + direction.X = -1; + } + if (resultDistance.Y < 0) { + direction.Y = -1; + } + resultDistance.X = abs(resultDistance.X); + resultDistance.Y = abs(resultDistance.Y); + + for (Vec2i::TType i = 0; i < resultDistance.X; ++i) { + newPosition = Position() + Vec2i(direction.X, 0); + if (!p(newPosition)) { + resultFraction.X = 0; + directionFlags.first = false; + break; + } + SetPosition(newPosition); + } + + for (Vec2i::TType i = 0; i < resultDistance.Y; ++i) { + newPosition = Position() + Vec2i(0, direction.Y); + if (!p(newPosition)) { + resultFraction.Y = 0; + directionFlags.second = false; + break; + } + SetPosition(newPosition); + } + } + + return std::make_pair(resultFraction, directionFlags); + } + + TEntity::TId Id() const; + void Remove(); + bool IsRemoved() const; + bool IsPersistent() const; + +protected: + TAlarm Alarm_; + +private: + bool IsPersistent_ = false; + bool IsRemoved_ = false; + TEntity::TId Id_; + TCollisionGroup CollisionGroup_; + Vec2i Position_; + Vec2i Size_; + Vec2i AckPosition_; + Vec2i AckSize_; +}; + +} // namespace NGame \ No newline at end of file diff --git a/src/EntityManager.cpp b/src/EntityManager.cpp new file mode 100644 index 0000000..3ff2747 --- /dev/null +++ b/src/EntityManager.cpp @@ -0,0 +1,144 @@ +#include "EntityManager.h" + +#include +#include + +namespace NGame { + +TEntityManager::TEntityManager(TRenderManager& renderManager, std::size_t timestep) + : RenderManager_(renderManager), Timestep_(timestep) { + LastUpdateTick_ = SDL_GetTicks(); +} + +void TEntityManager::Run() { + Input(); + Update(); + Draw(); +} + +std::shared_ptr TEntityManager::Entity(TEntity::TId id) { + auto it = Entities_.find(id); + if (it != Entities_.end()) { + return it->second; + } + throw std::runtime_error("Could not find entity with id: " + std::to_string(id)); +} + +void TEntityManager::UpdateCollision(TEntity::TId id) { + auto entity = Entity(id); + UpdateCollision(entity); +} + +void TEntityManager::UpdateCollision(std::shared_ptr entity) { + if (entity->HasChanges()) { + Gridmap_.Remove(entity->AckPosition(), entity->AckSize(), entity->Id()); + Gridmap_.Add(entity->Position(), entity->Size(), entity->Id()); + entity->AckChanges(); + } +} + +std::shared_ptr TEntityManager::MakeEntityByName(const std::string& name) { + if (EntityFactories_.find(name) == EntityFactories_.end()) { + throw std::runtime_error("can't create entity with name " + name); + } + + auto entity = EntityFactories_[name](++IdCounter_); + Entities_[entity->Id()] = entity; + + // Explicilty ack changes in entity colision boxes + Gridmap_.Add(entity->Position(), entity->Size(), entity->Id()); + entity->AckChanges(); + return entity; +} + +bool TEntityManager::IsPlaceEmpty(const Vec2i& position, const Vec2i& size, TEntity::TCollisionGroup group, TEntity::TId ignoreId) { + auto query = Gridmap_.Query(position, size); + + return !std::any_of(query.begin(), query.end(), [&](auto id) { + auto entity = Entity(id); + if (id == ignoreId) + return false; + + if (!(entity->CollisionGroup() & group).any()) + return false; + + return RectOverlaps(position, size, entity->Position(), entity->Size()); + }); +} + +std::unordered_set TEntityManager::CollisionList(const Vec2i& position, const Vec2i& size, TEntity::TCollisionGroup group, TEntity::TId ignoreId) { + auto query = Gridmap_.Query(position, size); + + for (auto it = query.begin(); it != query.end(); ) { + auto entity = Entity(*it); + + if (entity->Id() == ignoreId || !(entity->CollisionGroup() & group).any()) { + it = query.erase(it); + } else { + if (RectOverlaps(position, size, entity->Position(), entity->Size())) { + ++it; + } else { + it = query.erase(it); + } + } + } + return query; +} + +void TEntityManager::Input() { + SDL_Event event; + while (RenderManager_.Input(&event)) { + std::for_each(Entities_.begin(), Entities_.end(), [&](auto& entity) { + entity.second->Input(&event); + }); + } +} + +void TEntityManager::Update() { + std::uint32_t currentTick = SDL_GetTicks(); + std::size_t attempts = 5; + + while (--attempts && (currentTick - LastUpdateTick_ > Timestep_)) { + LastUpdateTick_ += Timestep_; + + std::queue idForRemoval; + + // Update each enitity + std::for_each(Entities_.begin(), Entities_.end(), [&](auto& pair) { + auto& entity = pair.second; + + if (!entity->IsRemoved()) { + entity->Tick(Timestep_); + } + + if (entity->IsRemoved()) { + // Explicitly remove collision + Gridmap_.Remove(entity->AckPosition(), entity->AckSize(), entity->Id()); + idForRemoval.emplace(pair.first); + } else { + UpdateCollision(entity); + } + }); + + // Remove removed entities + while (!idForRemoval.empty()) { + Entities_.erase(idForRemoval.front()); + idForRemoval.pop(); + } + } + + // Failsafe when we run out of attempts + if (!attempts) { + LastUpdateTick_ = currentTick; + } +} + +void TEntityManager::Draw() { + std::for_each(Entities_.begin(), Entities_.end(), [&](auto& pair) { + auto& entity = pair.second; + entity->Draw(); + }); +} + + +} // namespace NGame diff --git a/src/EntityManager.h b/src/EntityManager.h new file mode 100644 index 0000000..6d62e5f --- /dev/null +++ b/src/EntityManager.h @@ -0,0 +1,66 @@ +#pragma once + +#include "Common.h" + +#include "Entity.h" +#include "Gridmap.h" +#include "RenderManager.h" + +namespace NGame { + +class TEntityManager { +public: + TEntityManager(TRenderManager& renderManager, const std::size_t timestep = 1000 / 30); + DELETE_COPY(TEntityManager) + + void Run(); + std::shared_ptr Entity(TEntity::TId id); + void UpdateCollision(TEntity::TId id); + void UpdateCollision(std::shared_ptr entity); + + template + void RegisterEntity(const std::string& name) { + if (EntityFactories_.find(name) != EntityFactories_.end()) { + throw std::runtime_error("can't register duplicate entity with name " + name); + } + EntityFactories_[name] = &MakeEntityFactory; + } + + template + std::shared_ptr MakeEntity(Args&&... args) { + auto entity = std::make_shared(++IdCounter_, std::forward(args)...); + Entities_[entity->Id()] = entity; + + // Explicilty ack changes in entity colision boxes + Gridmap_.Add(entity->Position(), entity->Size(), entity->Id()); + entity->AckChanges(); + return entity; + } + + std::shared_ptr MakeEntityByName(const std::string& name); + bool IsPlaceEmpty(const Vec2i& position, const Vec2i& size, TEntity::TCollisionGroup group, TEntity::TId ignoreId = {}); + std::unordered_set CollisionList(const Vec2i& position, const Vec2i& size, TEntity::TCollisionGroup group, TEntity::TId ignoreId = {}); + +private: + using TFactoryMethod = std::shared_ptr(*)(TEntity::TId); + + void Input(); + void Update(); + void Draw(); + + template + static std::shared_ptr MakeEntityFactory(TEntity::TId id) { + return std::make_shared(id); + } + +private: + TRenderManager& RenderManager_; + std::unordered_map> Entities_; + std::unordered_map EntityFactories_; + std::uint32_t LastUpdateTick_; + TEntity::TId IdCounter_; + std::size_t Timestep_; + TGridmap Gridmap_; +}; + +} // namespace NGame \ No newline at end of file diff --git a/src/FileManager.cpp b/src/FileManager.cpp new file mode 100644 index 0000000..9f8a32c --- /dev/null +++ b/src/FileManager.cpp @@ -0,0 +1,99 @@ +#include "FileManager.h" + +#include +#include +#include +#include +#include + +namespace NGame { + +TFileManager::TFileManager(const std::string& dataDirectory, const std::string& archivePath, std::size_t cacheSize) + : DataDirectory_(dataDirectory), ArchivePath_(archivePath), Cache_(cacheSize) { + +} + +std::string TFileManager::Get(const std::string& path) { + if (Cache_.Contains(path)) { + return Cache_.Get(path); + } + + auto getAttempt = FromFilesystem(path); + if (getAttempt.second) { + Cache_.Set(path, getAttempt.first); + return getAttempt.first; + } + + getAttempt = FromArchive(path); + if (getAttempt.second) { + Cache_.Set(path, getAttempt.first); + return getAttempt.first; + } + + throw std::runtime_error("can't find file " + path); +} + +std::pair TFileManager::FromFilesystem(const std::string& path) { + auto finalPath = DataDirectory_ + "/" + path; + + std::ifstream fileStream(finalPath, std::ios::in | std::ios::binary); + if (!fileStream.is_open()) { + return std::make_pair({}, false); + } + + std::stringstream fileStringStream; + fileStringStream << fileStream.rdbuf(); + return std::make_pair(fileStringStream.str(), true); +} + +std::pair TFileManager::FromArchive(const std::string& path) { + std::ifstream archiveStream(ArchivePath_, std::ios::in | std::ios::binary); + if (!archiveStream.is_open()) { + return std::make_pair({}, false); + } + + // Identify and validate PAK file + char identifier[4]; + archiveStream.read(identifier, 4); + if (std::memcmp(identifier, "PACK", 4)) { + return std::make_pair({}, false); + } + + std::uint32_t tocOffset = ReadInt32(archiveStream); + std::uint32_t tocSize = ReadInt32(archiveStream) / 64; + + if (!tocSize || tocOffset < 12) { + return std::make_pair({}, false); + } + + // Find requested file + archiveStream.seekg(tocOffset); + + for (std::uint32_t index = 0; index < tocSize; ++index) { + char name[56]; + + archiveStream.read(name, 56); + std::uint32_t fileOffset = ReadInt32(archiveStream); + std::uint32_t fileSize = ReadInt32(archiveStream); + + if (std::strncmp(name, path.c_str(), 56)) { + continue; + } + + archiveStream.seekg(fileOffset); + std::string result(fileSize, 0); + archiveStream.read(result.data(), result.size()); + + return std::make_pair(result, true); + } + + return std::make_pair({}, false); +} + +std::uint32_t TFileManager::ReadInt32(std::ifstream& stream) { + unsigned char bytes[4]; + stream.read(reinterpret_cast(bytes), 4); + return (bytes[0] << 0) | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24); +} + +} // namespace NGame \ No newline at end of file diff --git a/src/FileManager.h b/src/FileManager.h new file mode 100644 index 0000000..78c5ad2 --- /dev/null +++ b/src/FileManager.h @@ -0,0 +1,27 @@ +#pragma once + +#include "Common.h" + +#include "LRU.h" + +namespace NGame { + +class TFileManager { +public: + TFileManager(const std::string& dataDirectory, const std::string& archivePath, std::size_t cacheSize = 4); + DELETE_COPY(TFileManager) + + std::string Get(const std::string& path); + +private: + std::pair FromFilesystem(const std::string& path); + std::pair FromArchive(const std::string& path); + std::uint32_t ReadInt32(std::ifstream& stream); + +private: + const std::string DataDirectory_; + const std::string ArchivePath_; + TLRU Cache_; +}; + +} // namespace NGame \ No newline at end of file diff --git a/src/FontManager.cpp b/src/FontManager.cpp new file mode 100644 index 0000000..67e3fcf --- /dev/null +++ b/src/FontManager.cpp @@ -0,0 +1,35 @@ +#include "FontManager.h" + +#include + +namespace NGame { + +TFontManager::TFontManager(TSpriteManager& spriteManager) + : SpriteManager_(spriteManager) { + Fonts_[White] = SpriteManager_.Get("Fonts/White.txt"); + Fonts_[Gray] = SpriteManager_.Get("Fonts/Gray.txt"); + Fonts_[Red] = SpriteManager_.Get("Fonts/Red.txt"); + Fonts_[Green] = SpriteManager_.Get("Fonts/Green.txt"); + Fonts_[Blue] = SpriteManager_.Get("Fonts/Blue.txt"); + Fonts_[Purple] = SpriteManager_.Get("Fonts/Purple.txt"); + Fonts_[Gold] = SpriteManager_.Get("Fonts/Gold.txt"); + Fonts_[Swamp] = SpriteManager_.Get("Fonts/Swamp.txt"); + Fonts_[LightBlue] = SpriteManager_.Get("Fonts/LightBlue.txt"); +} + +void TFontManager::Draw(EColor color, const Vec2i position, const std::string& text) { + Vec2i currentPosition = position; + + for (const auto& symbol : text) { + if (symbol >= ' ' && symbol <= '~') { + SpriteManager_.Draw(Fonts_[color], (unsigned char)symbol - ' ', currentPosition); + currentPosition += Vec2i(GlyphSize.X, 0); + } else if (symbol == '\n') { + currentPosition = Vec2i(position.X, currentPosition.Y + GlyphSize.Y); + } else if (symbol == '\t') { + currentPosition += Vec2i(GlyphSize.X * 4, 0); + } + } +} + +} // namespace NGame \ No newline at end of file diff --git a/src/FontManager.h b/src/FontManager.h new file mode 100644 index 0000000..ba61aa2 --- /dev/null +++ b/src/FontManager.h @@ -0,0 +1,35 @@ +#pragma once + +#include "Common.h" + +#include "SpriteManager.h" + +namespace NGame { + +class TFontManager { +public: + enum EColor { + White, + Gray, + Red, + Green, + Blue, + Purple, + Gold, + Swamp, + LightBlue, + SentinelMax + }; + + TFontManager(TSpriteManager& spriteManager); + DELETE_COPY(TFontManager) + + void Draw(EColor color, const Vec2i position, const std::string& text); + +private: + TSpriteManager& SpriteManager_; + const Vec2i GlyphSize = {5, 12}; + std::array, SentinelMax> Fonts_; +}; + +} // namespace NGame \ No newline at end of file diff --git a/src/Gridmap.cpp b/src/Gridmap.cpp new file mode 100644 index 0000000..7365e8a --- /dev/null +++ b/src/Gridmap.cpp @@ -0,0 +1,58 @@ +#include "Gridmap.h" + +#include +#include + +namespace NGame { + +std::unordered_set TGridmap::Query(const Vec2i& position, const Vec2i& size) const { + Vec2i cellPosition = position / CellSize_; + Vec2i cellEnd = cellPosition + ((size + (CellSize_ - 1)) / CellSize_); + + std::unordered_set result; + for (Vec2i::TType i = cellPosition.X; i <= cellEnd.X; ++i) { + for (Vec2i::TType j = cellPosition.Y; j <= cellEnd.Y; ++j) { + TId gridId = GridId(Vec2i(i, j)); + const auto& cell = Grid_[gridId]; + for (auto id : cell) { + result.insert(id); + } + } + } + + return result; +} + +void TGridmap::Remove(const Vec2i& position, const Vec2i& size, TEntity::TId id) { + Vec2i cellPosition = position / CellSize_; + Vec2i cellEnd = cellPosition + ((size + (CellSize_ - 1)) / CellSize_); + + std::unordered_set result; + for (Vec2i::TType i = cellPosition.X; i <= cellEnd.X; ++i) { + for (Vec2i::TType j = cellPosition.Y; j <= cellEnd.Y; ++j) { + TId gridId = GridId(Vec2i(i, j)); + auto& cell = Grid_[gridId]; + auto it = std::remove(cell.begin(), cell.end(), id); + cell.erase(it, cell.end()); + } + } +} + +void TGridmap::Add(const Vec2i& position, const Vec2i& size, TEntity::TId id) { + Vec2i cellPosition = position / CellSize_; + Vec2i cellEnd = cellPosition + ((size + (CellSize_ - 1)) / CellSize_); + + std::unordered_set result; + for (Vec2i::TType i = cellPosition.X; i <= cellEnd.X; ++i) { + for (Vec2i::TType j = cellPosition.Y; j <= cellEnd.Y; ++j) { + TId gridId = GridId(Vec2i(i, j)); + Grid_[gridId].push_back(id); + } + } +} + +TGridmap::TId TGridmap::GridId(const Vec2i& point) const { + return std::hash{}(point) % Grid_.size(); +} + +} // namespace NGame \ No newline at end of file diff --git a/src/Gridmap.h b/src/Gridmap.h new file mode 100644 index 0000000..33f2799 --- /dev/null +++ b/src/Gridmap.h @@ -0,0 +1,29 @@ +#pragma once + +#include "Common.h" + +#include "Entity.h" + +#include +#include +#include +#include + +namespace NGame { + +class TGridmap { +public: + std::unordered_set Query(const Vec2i& position, const Vec2i& size) const; + void Remove(const Vec2i& position, const Vec2i& size, TEntity::TId id); + void Add(const Vec2i& position, const Vec2i& size, TEntity::TId id); + +private: + using TId = size_t; + TId GridId(const Vec2i& point) const; + +private: + std::array, 32768> Grid_; + const std::uint32_t CellSize_ = 128; +}; + +} // namespace NGame \ No newline at end of file diff --git a/src/LRU.h b/src/LRU.h new file mode 100644 index 0000000..219c45a --- /dev/null +++ b/src/LRU.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include +#include + +namespace NGame { + +template +class TLRU { +public: + using TPair = std::pair; + using TKey = Key; + using TValue = Value; + + TLRU(std::size_t capacity) + : Capacity_(capacity) { + } + + const Value& Get(const Key& key) const { + auto it = Map_.find(key); + if (it == Map_.end()) { + throw std::runtime_error("lru cache miss"); + } + + Cache_.splice(Cache_.begin(), Cache_, it->second); + return it->second->second; + } + + void Set(const Key& key, const Value& value) { + Set(key, value, [](auto& value){}); + } + + template + void Set(const Key& key, const Value& value, UnaryPredicate p) { + auto it = Map_.find(key); + if (it != Map_.end()) { + p(*it->second); + Cache_.erase(it->second); + Map_.erase(it); + } + + Cache_.push_front(std::make_pair(key, value)); + Map_[key] = Cache_.begin(); + + while (Map_.size() > Capacity_) { + auto lastElement = --(Cache_.end()); + p(*lastElement); + Map_.erase(lastElement->first); + Cache_.erase(lastElement); + } + } + + bool Contains(const Key& key) const { + return Map_.find(key) != Map_.end(); + } + + std::size_t Size() const { + return Map_.size(); + } + + std::size_t Capacity() const { + return Capacity_; + } + + void Clear() { + Cache_.clear(); + Map_.clear(); + } + +private: + using TIterator = typename std::list::iterator; + + mutable std::list Cache_; + mutable std::unordered_map Map_; + std::size_t Capacity_; +}; + +} // namespace NGame diff --git a/src/Main.cpp b/src/Main.cpp new file mode 100644 index 0000000..7c02441 --- /dev/null +++ b/src/Main.cpp @@ -0,0 +1,65 @@ +#include "App.h" + +#include +#include "RoomEntity.h" +#include +#include +#include + +#ifdef __EMSCRIPTEN__ +#include +#endif + +static NGame::TApp *app = nullptr; + +static void InitApp(void) { + app = NGame::TApp::Instance(); + + app->EntityManager().RegisterEntity("Hero"); + app->EntityManager().RegisterEntity("DirtEntity"); + app->EntityManager().RegisterEntity("GrassEntity"); + app->EntityManager().RegisterEntity("PlankEntity"); + app->EntityManager().RegisterEntity("StoneEntity"); + app->EntityManager().RegisterEntity("LadderEntity"); + app->EntityManager().RegisterEntity("ExplosionEntity"); + app->EntityManager().RegisterEntity("KeyEntity"); + app->EntityManager().RegisterEntity("MineEntity"); + app->EntityManager().RegisterEntity("CurseEntity"); + app->EntityManager().RegisterEntity("SpikeEntity"); + app->EntityManager().RegisterEntity("EntranceEntity"); + app->EntityManager().RegisterEntity("ExitEntity"); + app->EntityManager().RegisterEntity("FloatingTextEntity"); + app->EntityManager().RegisterEntity("BackgroundTiler"); + app->EntityManager().RegisterEntity("RoomEntity"); + + app->EntityManager().MakeEntityByName("RoomEntity"); + app->EntityManager().MakeEntityByName("BackgroundTiler"); + + auto h = app->EntityManager().MakeEntityByName("Hero"); + h->SetPosition({0, -64}); + app->EntityManager().UpdateCollision(h); +} + +static void MainLoop(void) { + if (app) { + app->Run(); + } else { + InitApp(); + } +} + +int main(int argc, char** argv) +{ + SDL_Init(SDL_INIT_VIDEO); + + srand(time(NULL)); + + #ifdef __EMSCRIPTEN__ + emscripten_set_main_loop(MainLoop, -1, true); + #else + while (true) { MainLoop(); } + #endif + + return 0; +} + diff --git a/src/RenderManager.cpp b/src/RenderManager.cpp new file mode 100644 index 0000000..0f29686 --- /dev/null +++ b/src/RenderManager.cpp @@ -0,0 +1,348 @@ +#include "RenderManager.h" + +#include +#include +#include +#include + +namespace NGame { + +TRenderManager::TRenderManager(TSurfaceManager& surfaceManager, int width, int height, const std::string& title, std::size_t cacheSize) + : SurfaceManager_(surfaceManager), Cache_(cacheSize), Size_(width, height) { + Window_ = SDL_CreateWindow(title.c_str(), SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, width, height, SDL_WINDOW_RESIZABLE); + if (!Window_) { + throw std::runtime_error("Can't create window"); + } + + Renderer_ = SDL_CreateRenderer(Window_, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (!Renderer_) { + SDL_DestroyWindow(Window_); + throw std::runtime_error("Can't create renderer"); + } + + ScreenTexture_ = SDL_CreateTexture(Renderer_, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_TARGET, width, height); + if (!ScreenTexture_) { + SDL_DestroyRenderer(Renderer_); + SDL_DestroyWindow(Window_); + throw std::runtime_error("Can't create screen texture"); + } + + LightTexture_ = SDL_CreateTexture(Renderer_, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_TARGET, width, height); + if (!LightTexture_) { + SDL_DestroyTexture(ScreenTexture_); + SDL_DestroyRenderer(Renderer_); + SDL_DestroyWindow(Window_); + throw std::runtime_error("Can't create light texture"); + } + SDL_SetTextureBlendMode(LightTexture_, SDL_BLENDMODE_MOD); + + // Make window bigger + if (width < 640 && height < 480) { + SDL_SetWindowSize(Window_, 640, 480); + } + + Reset(); +} + +std::shared_ptr TRenderManager::Get(const std::string& path) { + if (Cache_.Contains(path)) { + return Cache_.Get(path); + } + + std::shared_ptr surface = SurfaceManager_.Get(path); + SDL_Texture* texture = SDL_CreateTextureFromSurface(Renderer_, surface.get()); + if (!texture) { + std::runtime_error("Can't create texture from surface " + path); + } + + std::shared_ptr result(texture, [](SDL_Texture* texture) { SDL_DestroyTexture(texture); }); + Cache_.Set(path, result); + return result; +} + +void TRenderManager::SetLayer(ELayer layer) { + Layer_ = layer; +} + +void TRenderManager::SetColor(std::uint8_t red, std::uint8_t green, std::uint8_t blue, std::uint8_t alpha) { + Commands_[Layer_].push_back(TColorCommand{red, green, blue, alpha}); +} + +void TRenderManager::SetTexture(const std::shared_ptr& texture) { + if (ActiveTextures_[Layer_] != texture) { + Commands_[Layer_].push_back(TTextureCommand{texture}); + ActiveTextures_[Layer_] = texture; + } +} + +void TRenderManager::DrawRect(const Vec2i& position, const Vec2i& size, bool filled) { + Commands_[Layer_].push_back(TRectCommand{position, size, filled}); +} + +void TRenderManager::DrawLine(const Vec2i& start, const Vec2i& end) { + Commands_[Layer_].push_back(TLineCommand{start, end}); +} + +void TRenderManager::DrawSprite(const Vec2i& texturePosition, const Vec2i& textureSize, const Vec2i& targetPosition, const Vec2i& targetSize, SDL_RendererFlip flip) { + if (!ActiveTextures_[Layer_]) { + throw std::runtime_error("layer has not active texture"); + } + Commands_[Layer_].push_back(TSpriteCommand{texturePosition, textureSize, targetPosition, targetSize, flip}); +} + +void TRenderManager::DrawGeometry(const SDL_Vertex* vertices, std::size_t verticesCount, const int* indices, std::size_t indicesCount) { + if (!ActiveTextures_[Layer_]) { + throw std::runtime_error("layer has not active texture"); + } + + Commands_[Layer_].push_back(TGeometryCommand{vertices, verticesCount, indices, indicesCount}); +} + +void TRenderManager::SetCamera(const Vec2i& position) { + Camera_ = position; +} + +const Vec2i& TRenderManager::Camera() const { + return Camera_; +} + +const Vec2i& TRenderManager::Size() const { + return Size_; +} + +bool TRenderManager::IsLightEnabled() const { + return LightEnabled_; +} + +void TRenderManager::EnableLight(bool value) { + LightEnabled_ = value; +} + +void TRenderManager::SetDefaultLightColor(std::uint8_t red, std::uint8_t green, std::uint8_t blue) { + DefaultLightColor_ = {red, green, blue, 255}; +} + +std::uint8_t TRenderManager::DefaultLightRed() const { + return DefaultLightColor_.Red; +} + +std::uint8_t TRenderManager::DefaultLightGreen() const { + return DefaultLightColor_.Green; +} + +std::uint8_t TRenderManager::DefaultLightBlue() const { + return DefaultLightColor_.Blue; +} + + +void TRenderManager::Run() { + if (!IsRunning()) { + return; + } + + for (size_t layer = 0; layer < SentinelMax; ++layer) { + switch (layer) { + case ELayer::BackgroundGlow: + case ELayer::ForegroundGlow: + case ELayer::EffectsGlow: + if (SDL_SetRenderDrawBlendMode(Renderer_, SDL_BlendMode::SDL_BLENDMODE_ADD)) { + std::runtime_error("can't set blendmode"); + } + if (SDL_SetRenderTarget(Renderer_, ScreenTexture_)) { + std::runtime_error("can't set render target"); + } + break; + + case ELayer::Light: + if (!IsLightEnabled()) { + continue; + } + if (SDL_SetRenderDrawBlendMode(Renderer_, SDL_BlendMode::SDL_BLENDMODE_BLEND)) { + std::runtime_error("can't set blendmode"); + } + if (SDL_SetRenderTarget(Renderer_, LightTexture_)) { + std::runtime_error("can't set render target"); + } + break; + + default: + if (SDL_SetRenderDrawBlendMode(Renderer_, SDL_BlendMode::SDL_BLENDMODE_BLEND)) { + std::runtime_error("can't set blendmode"); + } + if (SDL_SetRenderTarget(Renderer_, ScreenTexture_)) { + std::runtime_error("can't set render target"); + } + break; + } + + SDL_Texture* activeTexture = nullptr; + + for (const auto& command : Commands_[layer]) { + auto drawOperation = TOverload { + [&](const TColorCommand& value) { + SDL_SetRenderDrawColor(Renderer_, value.Red, value.Green, value.Blue, value.Alpha); + }, + [&](const TRectCommand& value) { + SDL_Rect rect; + rect.x = value.Position.X; + rect.y = value.Position.Y; + rect.w = value.Size.X; + rect.h = value.Size.Y; + + if (layer != ELayer::Interface) { + rect.x -= Camera_.X; + rect.y -= Camera_.Y; + } + + if (value.ShouldFill) { + SDL_RenderFillRect(Renderer_, &rect); + } else { + SDL_RenderDrawRect(Renderer_, &rect); + } + }, + [&](const TLineCommand& value) { + if (layer != ELayer::Interface) { + SDL_RenderDrawLine(Renderer_, value.Start.X - Camera_.X, value.Start.Y - Camera_.Y, value.End.X - Camera_.X, value.End.Y - Camera_.Y); + } else { + SDL_RenderDrawLine(Renderer_, value.Start.X, value.Start.Y, value.End.X, value.End.Y); + } + }, + [&](const TTextureCommand& value) { + activeTexture = value.Texture.get(); + + switch (layer) { + case ELayer::BackgroundGlow: + case ELayer::ForegroundGlow: + case ELayer::EffectsGlow: + if (SDL_SetTextureBlendMode(activeTexture, SDL_BlendMode::SDL_BLENDMODE_ADD)) { + std::runtime_error("can't set texture blend mode"); + } + break; + + default: + if (SDL_SetTextureBlendMode(activeTexture, SDL_BlendMode::SDL_BLENDMODE_BLEND)) { + std::runtime_error("can't set texture blend mode"); + } + break; + } + }, + [&](const TSpriteCommand& value) { + SDL_Rect sourceRect; + sourceRect.x = value.TexturePosition.X; + sourceRect.y = value.TexturePosition.Y; + sourceRect.w = value.TextureSize.X; + sourceRect.h = value.TextureSize.Y; + + SDL_Rect destinationRect; + destinationRect.x = value.TargetPosition.X; + destinationRect.y = value.TargetPosition.Y; + destinationRect.w = value.TargetSize.X; + destinationRect.h = value.TargetSize.Y; + + if (layer != ELayer::Interface) { + destinationRect.x -= Camera_.X; + destinationRect.y -= Camera_.Y; + } + + SDL_RenderCopyEx(Renderer_, activeTexture, &sourceRect, &destinationRect, 0, NULL, value.Flip); + }, + [&](const TGeometryCommand& value) { + if (layer == ELayer::Interface) { + SDL_RenderGeometry(Renderer_, activeTexture, value.Vertices, value.VerticesCount, value.Indices, value.IndicesCount); + } else { + VerticesBuffer_.clear(); + for (std::size_t i = 0; i < value.VerticesCount; ++i) { + SDL_Vertex convertedVertex = value.Vertices[i]; + convertedVertex.position.x -= Camera_.X; + convertedVertex.position.y -= Camera_.Y; + VerticesBuffer_.push_back(convertedVertex); + } + + SDL_RenderGeometry(Renderer_, activeTexture, VerticesBuffer_.data(), VerticesBuffer_.size(), value.Indices, value.IndicesCount); + } + + } + }; + + std::visit(drawOperation, command); + } + + // Apply light + if (layer == ELayer::Light) { + SDL_SetRenderTarget(Renderer_, ScreenTexture_); + SDL_RenderCopy(Renderer_, LightTexture_, NULL, NULL); + } + } + + // Keep logical resolution the same + int windowWidth, windowHeight; + SDL_GetWindowSize(Window_, &windowWidth, &windowHeight); + Vec2f scaleFactors = {float(windowWidth) / Size_.X, float(windowHeight) / Size_.Y}; + float targetFactor = std::min(scaleFactors.X, scaleFactors.Y); + + SDL_Rect destinationRect; + destinationRect.w = targetFactor * Size_.X; + destinationRect.h = targetFactor * Size_.Y; + destinationRect.x = (windowWidth - destinationRect.w) / 2; + destinationRect.y = (windowHeight - destinationRect.h) / 2; + + SDL_SetRenderTarget(Renderer_, NULL); + SDL_RenderCopy(Renderer_, ScreenTexture_, NULL, &destinationRect); + SDL_RenderPresent(Renderer_); + Reset(); +} + +bool TRenderManager::Input(SDL_Event* event) { + bool result = SDL_PollEvent(event); + + if (result && event->type == SDL_QUIT) { + Shutdown(); + } + + return result; +} + +bool TRenderManager::IsRunning() { + return Window_ != nullptr; +} + +void TRenderManager::Shutdown() { + if (IsRunning()) { + Reset(); + SDL_DestroyTexture(LightTexture_); + SDL_DestroyTexture(ScreenTexture_); + SDL_DestroyRenderer(Renderer_); + SDL_DestroyWindow(Window_); + ScreenTexture_ = nullptr; + Renderer_ = nullptr; + Window_ = nullptr; + } +} + +void TRenderManager::Reset() { + Layer_ = Tile; + ActiveTextures_.fill(nullptr); + for (auto& queue : Commands_) { + queue.clear(); + } + + SDL_Rect a; + SDL_RenderGetViewport(Renderer_, &a); + + SDL_SetRenderDrawColor(Renderer_, 0, 0, 0, 0); + SDL_SetRenderTarget(Renderer_, NULL); + SDL_RenderClear(Renderer_); + + SDL_SetRenderDrawColor(Renderer_, 0, 0, 0, 0); + SDL_SetRenderTarget(Renderer_, ScreenTexture_); + SDL_RenderClear(Renderer_); + + if (IsLightEnabled()) { + SDL_SetRenderDrawColor(Renderer_, DefaultLightColor_.Red, DefaultLightColor_.Green, DefaultLightColor_.Blue, DefaultLightColor_.Alpha); + SDL_SetRenderTarget(Renderer_, LightTexture_); + SDL_RenderClear(Renderer_); + } +} + +} // namespace NGame \ No newline at end of file diff --git a/src/RenderManager.h b/src/RenderManager.h new file mode 100644 index 0000000..7b2a4af --- /dev/null +++ b/src/RenderManager.h @@ -0,0 +1,119 @@ +#pragma once + +#include "Common.h" + +#include "LRU.h" +#include "SurfaceManager.h" + +#include +#include +#include +#include +#include +#include + +namespace NGame { + +class TRenderManager { +public: + enum ELayer { + Tile, + Background, + BackgroundGlow, + Foreground, + ForegroundGlow, + Effects, + EffectsGlow, + Light, + Interface, + SentinelMax + }; + + struct TColorCommand { + std::uint8_t Red; + std::uint8_t Green; + std::uint8_t Blue; + std::uint8_t Alpha; + }; + + struct TRectCommand { + Vec2i Position; + Vec2i Size; + bool ShouldFill; + }; + + struct TLineCommand { + Vec2i Start; + Vec2i End; + }; + + struct TTextureCommand { + std::shared_ptr Texture; + }; + + struct TSpriteCommand { + Vec2i TexturePosition; + Vec2i TextureSize; + Vec2i TargetPosition; + Vec2i TargetSize; + SDL_RendererFlip Flip; + }; + + struct TGeometryCommand { + const SDL_Vertex* Vertices; + std::size_t VerticesCount; + const int* Indices; + std::size_t IndicesCount; + }; + + using TCommand = std::variant; + + TRenderManager(TSurfaceManager& surfaceManager, int width = 640, int height = 480, const std::string& title = "Game", std::size_t cacheSize = 4); + DELETE_COPY(TRenderManager) + + std::shared_ptr Get(const std::string& path); + void SetLayer(ELayer layer); + void SetColor(std::uint8_t red, std::uint8_t green, std::uint8_t blue, std::uint8_t alpha = 255); + void SetTexture(const std::shared_ptr& texture); + void DrawRect(const Vec2i& position, const Vec2i& size, bool filled = false); + void DrawLine(const Vec2i& start, const Vec2i& end); + void DrawSprite(const Vec2i& texturePosition, const Vec2i& textureSize, const Vec2i& targetPosition, const Vec2i& targetSize, SDL_RendererFlip flip = SDL_FLIP_NONE); + void DrawGeometry(const SDL_Vertex* vertices, std::size_t verticesCount, const int* indices, std::size_t indicesCount); + + void SetCamera(const Vec2i& position); + const Vec2i& Camera() const; + const Vec2i& Size() const; + + bool IsLightEnabled() const; + void EnableLight(bool value); + void SetDefaultLightColor(std::uint8_t red, std::uint8_t green, std::uint8_t blue); + std::uint8_t DefaultLightRed() const; + std::uint8_t DefaultLightGreen() const; + std::uint8_t DefaultLightBlue() const; + + void Run(); + bool Input(SDL_Event* event); + bool IsRunning(); + void Shutdown(); + +private: + void Reset(); + +private: + SDL_Window* Window_; + SDL_Renderer* Renderer_; + TSurfaceManager& SurfaceManager_; + TLRU> Cache_; + std::array, SentinelMax> Commands_; + std::array, SentinelMax> ActiveTextures_; + SDL_Texture* ScreenTexture_; + SDL_Texture* LightTexture_; + TColorCommand DefaultLightColor_; + bool LightEnabled_ = false; + ELayer Layer_; + Vec2i Camera_; + Vec2i Size_; + std::vector VerticesBuffer_; +}; + +} // namespace NGame \ No newline at end of file diff --git a/src/RoomEntity.cpp b/src/RoomEntity.cpp new file mode 100644 index 0000000..4d13477 --- /dev/null +++ b/src/RoomEntity.cpp @@ -0,0 +1,897 @@ +#include "RoomEntity.h" +#include +#include +#include +#include +#include + +#define ROOM_WIDTH 6 +#define ROOM_HEIGHT 6 + +TDirtEntity::TDirtEntity(NGame::TEntity::TId id) + : NGame::TEntity(id) { + SetCollisionGroup(TERRAIN_GROUP); + SetSize({16, 16}); + Sprite_ = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Wall.txt"); +} + +void TDirtEntity::Update(std::uint32_t delta) { + +} + +void TDirtEntity::Draw() const { + NGame::TApp::Instance()->RenderManager().SetLayer(NGame::TRenderManager::Background); + NGame::TApp::Instance()->SpriteManager().Draw(Sprite_, 1, Position()); +} + +TGrassEntity::TGrassEntity(NGame::TEntity::TId id) + : NGame::TEntity(id) { + SetCollisionGroup(TERRAIN_GROUP); + SetSize({16, 16}); + Sprite_ = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Wall.txt"); +} + +void TGrassEntity::Update(std::uint32_t delta) { + +} + +void TGrassEntity::Draw() const { + NGame::TApp::Instance()->RenderManager().SetLayer(NGame::TRenderManager::Background); + NGame::TApp::Instance()->SpriteManager().Draw(Sprite_, 2, Position()); +} + +TPlankEntity::TPlankEntity(NGame::TEntity::TId id) + : NGame::TEntity(id) { + SetCollisionGroup(TERRAIN_GROUP); + SetSize({16, 2}); + Sprite_ = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Plank.txt"); +} + +void TPlankEntity::Update(std::uint32_t delta) { + +} + +void TPlankEntity::Draw() const { + NGame::TApp::Instance()->RenderManager().SetLayer(NGame::TRenderManager::Background); + NGame::TApp::Instance()->SpriteManager().Draw(Sprite_, 0, Position()); +} + +TStoneEntity::TStoneEntity(NGame::TEntity::TId id) + : NGame::TEntity(id) { + SetCollisionGroup(TERRAIN_GROUP); + SetSize({16, 16}); + Sprite_ = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Wall.txt"); +} + +void TStoneEntity::Update(std::uint32_t delta) { + +} + +void TStoneEntity::Draw() const { + NGame::TApp::Instance()->RenderManager().SetLayer(NGame::TRenderManager::Background); + NGame::TApp::Instance()->SpriteManager().Draw(Sprite_, 0, Position()); +} + +TLadderEntity::TLadderEntity(NGame::TEntity::TId id) + : NGame::TEntity(id) { + SetCollisionGroup(LADDER_GROUP); + SetSize({16, 16}); + Sprite_ = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Ladder.txt"); +} + +void TLadderEntity::Update(std::uint32_t delta) { + +} + +void TLadderEntity::Draw() const { + NGame::TApp::Instance()->RenderManager().SetLayer(NGame::TRenderManager::Background); + NGame::TApp::Instance()->SpriteManager().Draw(Sprite_, 0, Position()); + + NGame::TApp::Instance()->RenderManager().SetLayer(NGame::TRenderManager::Light); + auto light = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Light.txt"); + NGame::TApp::Instance()->SpriteManager().Draw(light, 0, Position() - (light->Frames[0].Size * 1 / 2), {1, 1}); +} + +TExplosionEntity::TExplosionEntity(NGame::TEntity::TId id) + : NGame::TEntity(id) { + SetCollisionGroup(DAMAGE_GROUP); + SetSize({16, 16}); + Sprite_ = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Wall.txt"); +} + +void TExplosionEntity::Update(std::uint32_t delta) { + +} + +void TExplosionEntity::Draw() const { + NGame::TApp::Instance()->RenderManager().SetLayer(NGame::TRenderManager::Background); + NGame::TApp::Instance()->SpriteManager().Draw(Sprite_, 0, Position()); +} + +void TExplosionEntity::Alarm(NGame::TAlarm::TId id) { + +} + +TKeyEntity::TKeyEntity(NGame::TEntity::TId id) + : NGame::TEntity(id) { + SetCollisionGroup(ITEM_GROUP); + SetSize({16, 16}); + Sprite_ = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Item.txt"); +} + +void TKeyEntity::Update(std::uint32_t delta) { + +} + +void TKeyEntity::Draw() const { + NGame::TApp::Instance()->RenderManager().SetLayer(NGame::TRenderManager::Background); + NGame::TApp::Instance()->SpriteManager().Draw(Sprite_, 0, Position()); +} + +TMineEntity::TMineEntity(NGame::TEntity::TId id) + : NGame::TEntity(id) { + SetSize({16, 16}); + Sprite_ = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Mine.txt"); +} + +void TMineEntity::Update(std::uint32_t delta) { + +} + +void TMineEntity::Draw() const { + NGame::TApp::Instance()->RenderManager().SetLayer(NGame::TRenderManager::Background); + NGame::TApp::Instance()->SpriteManager().Draw(Sprite_, 0, Position()); +} + +void TMineEntity::Alarm(NGame::TAlarm::TId id) { + +} + +TCurseEntity::TCurseEntity(NGame::TEntity::TId id) + : NGame::TEntity(id) { + SetCollisionGroup(ITEM_GROUP); + SetSize({16, 16}); + Sprite_ = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Item.txt"); +} + +void TCurseEntity::Update(std::uint32_t delta) { + +} + +void TCurseEntity::Draw() const { + NGame::TApp::Instance()->RenderManager().SetLayer(NGame::TRenderManager::Background); + NGame::TApp::Instance()->SpriteManager().Draw(Sprite_, 0, Position()); +} + +TSpikeEntity::TSpikeEntity(NGame::TEntity::TId id) + : NGame::TEntity(id) { + SetCollisionGroup(DAMAGE_GROUP); + SetSize({16, 16}); + Sprite_ = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Spike.txt"); +} + +void TSpikeEntity::Update(std::uint32_t delta) { + +} + +void TSpikeEntity::Draw() const { + NGame::TApp::Instance()->RenderManager().SetLayer(NGame::TRenderManager::Background); + NGame::TApp::Instance()->SpriteManager().Draw(Sprite_, 0, Position()); +} + +TEntranceEntity::TEntranceEntity(NGame::TEntity::TId id) + : NGame::TEntity(id) { + SetCollisionGroup(PASSAGE_GROUP); + SetSize({16, 16}); + Sprite_ = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Passage.txt"); +} + +void TEntranceEntity::Update(std::uint32_t delta) { + +} + +void TEntranceEntity::Draw() const { + NGame::TApp::Instance()->RenderManager().SetLayer(NGame::TRenderManager::Background); + NGame::TApp::Instance()->SpriteManager().Draw(Sprite_, 0, Position()); +} + +TExitEntity::TExitEntity(NGame::TEntity::TId id) + : NGame::TEntity(id) { + SetCollisionGroup(PASSAGE_GROUP); + SetSize({16, 16}); + Sprite_ = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Passage.txt"); +} + +void TExitEntity::Update(std::uint32_t delta) { + +} + +void TExitEntity::Draw() const { + NGame::TApp::Instance()->RenderManager().SetLayer(NGame::TRenderManager::Background); + NGame::TApp::Instance()->SpriteManager().Draw(Sprite_, 0, Position()); +} + +TFloatingTextEntity::TFloatingTextEntity(NGame::TEntity::TId id) + : NGame::TEntity(id) { + Alarm_.Set(0, 1000); +} + +void TFloatingTextEntity::SetText(const std::string& text) { + Text_ = text; +} + +void TFloatingTextEntity::SetColor(NGame::TFontManager::EColor color) { + Color_ = color; +} + +void TFloatingTextEntity::Update(std::uint32_t delta) { + SetPosition({Position().X, Position().Y - 1}); +} + +void TFloatingTextEntity::Draw() const { + auto& renderManager = NGame::TApp::Instance()->RenderManager(); + auto& fontManager = NGame::TApp::Instance()->FontManager(); + + renderManager.SetLayer(NGame::TRenderManager::Effects); + fontManager.Draw(Color_, Position(), Text_); +} + +void TFloatingTextEntity::Alarm(NGame::TAlarm::TId id) { + Remove(); +} + +TRoomEntity::TRoomEntity(NGame::TEntity::TId id) + : NGame::TEntity(id) { + LoadTemplates(); + GenerateLayout(); + Remove(); +} + +void TRoomEntity::LoadTemplates() { + auto& fileManager = NGame::TApp::Instance()->FileManager(); + ParseRooms(fileManager.Get("Templates/LR.txt"), LRRooms_); + ParseRooms(fileManager.Get("Templates/LRD.txt"), LRDRooms_); + ParseRooms(fileManager.Get("Templates/LRU.txt"), LRURooms_); + ParseRooms(fileManager.Get("Templates/LRUD.txt"), LRUDRooms_); + ParseRooms(fileManager.Get("Templates/Any.txt"), AnyRooms_); +} + +void TRoomEntity::ParseRooms(const std::string& data, std::vector& rooms) { + std::istringstream inputStream(data); + + TRoom currentRoom; + while (ParseRoom(inputStream, currentRoom)) { + rooms.push_back(currentRoom); + } +} + +bool TRoomEntity::ParseRoom(std::istringstream& stream, TRoom& room) { + for (std::string line; std::getline(stream, line); ) { + auto action = NGame::Trim(std::string_view(line)); + + if (action == "Room") { + std::string rows[6]; + std::getline(stream, rows[0]); + std::getline(stream, rows[1]); + std::getline(stream, rows[2]); + std::getline(stream, rows[3]); + std::getline(stream, rows[4]); + std::getline(stream, rows[5]); + + std::memset(room.data, Empty, sizeof(room)); + FillRow(rows[0], 0, room); + FillRow(rows[1], 1, room); + FillRow(rows[2], 2, room); + FillRow(rows[3], 3, room); + FillRow(rows[4], 4, room); + FillRow(rows[5], 5, room); + + return true; + } + } + + return false; +} + +void TRoomEntity::FillRow(const std::string& data, std::size_t row, TRoom& room) { + for (std::size_t i = 0; i < 10 || i < data.size(); i++) { + switch (data[i]) { + case 'g': room.data[row * 10 + i] = Dirt; break; + case '1': case '2': + case '3': room.data[row * 10 + i] = LowDirt; break; + case '4': case '5': + case '6': room.data[row * 10 + i] = MidDirt; break; + case '7': case '8': + case '9': room.data[row * 10 + i] = HighDirt; break; + case 'G': room.data[row * 10 + i] = Grass; break; + case 'l': room.data[row * 10 + i] = Ladder; break; + case 'd': room.data[row * 10 + i] = Mine; break; + case 'D': room.data[row * 10 + i] = Spike; break; + case 'p': room.data[row * 10 + i] = Passage; break; + case 'P': room.data[row * 10 + i] = Plank; break; + case 'c': room.data[row * 10 + i] = Curse; break; + case 'b': room.data[row * 10 + i] = Baddy; break; + case 'u': room.data[row * 10 + i] = Utility; break; + default: break; + } + } +} + +bool TRoomEntity::GoLeft(int* maze, NGame::Vec2i& position) { + if (position.X > 0 && maze[ROOM_WIDTH * position.Y + position.X - 1] != 0) { + return true; + } + + if (position.X <= 0) { + maze[ROOM_WIDTH * position.Y + position.X] |= Down; + if (!GoDown(maze, position)) { + return false; + } + return GoRight(maze, position); + } else { + position.X--; + if (maze[ROOM_WIDTH * position.Y + position.X]) { + position.X++; + } else { + maze[ROOM_WIDTH * position.Y + position.X] = LeftRight; + } + } + + return true; +} + +bool TRoomEntity::GoRight(int* maze, NGame::Vec2i& position) { + if (position.X + 1 >= ROOM_WIDTH) { + maze[ROOM_WIDTH * position.Y + position.X] |= Down; + if (!GoDown(maze, position)) { + return false; + } + return GoLeft(maze, position); + } else { + position.X++; + if (maze[ROOM_WIDTH * position.Y + position.X]) { + position.X--; + } else { + maze[ROOM_WIDTH * position.Y + position.X] = LeftRight; + } + } + + return true; +} + +bool TRoomEntity::GoDown(int* maze, NGame::Vec2i& position) { + if (position.Y + 1 >= ROOM_HEIGHT) { + maze[ROOM_WIDTH * position.Y + position.X] |= Exit; + return false; + } else { + maze[ROOM_WIDTH * position.Y + position.X] |= Down; + position.Y++; + maze[ROOM_WIDTH * position.Y + position.X] = LeftRightUp; + } + + return true; +} + +void TRoomEntity::GenerateLayout() { + int maze[ROOM_HEIGHT * ROOM_WIDTH]; + + NGame::Vec2i position = {0, 0}; + std::memset(maze, None, sizeof(maze)); + + position.X = rand() % ROOM_WIDTH; + maze[ROOM_WIDTH * position.Y + position.X] = LeftRight | Start; + + // Generate rooms + while (true) { + int choice = rand() % 100; + + if (choice < 33) { + if (!GoLeft(maze, position)) { + break; + } + } else if (choice < 66) { + if (!GoRight(maze, position)) { + break; + } + } else { + if (!GoDown(maze, position)) { + break; + } + } + } + + for (int i = 0; i < ROOM_HEIGHT; i++) { + for (int j = 0; j < ROOM_WIDTH; j++) { + auto item = maze[i * ROOM_WIDTH + j]; + GenerateRoom(maze, NGame::Vec2i(j, i)); + } + } +} + +void TRoomEntity::GenerateRoom(int* maze, const NGame::Vec2i& position) { + + auto roomType = maze[ROOM_WIDTH * position.Y + position.X]; + TRoom sourceRoom; + int randomRoomChance = rand() % 100; + + SelectRoom(roomType & LeftRightUpDown, sourceRoom); + for (int i = 0; i < 6; i++) { + for (int j = 0; j < 10; j++) { + auto tile = sourceRoom.data[i * 10 + j]; + NGame::Vec2i entityPosition = {j * 16, i * 16}; + entityPosition += NGame::Vec2i(position.X * 160, position.Y * 96); + std::shared_ptr entity; + + switch (tile) { + default: + case Empty: + break; + + case LowDirt: + if (rand() % 100 < 25) { + entity = NGame::TApp::Instance()->EntityManager().MakeEntityByName("DirtEntity"); + } + break; + + case MidDirt: + if (rand() % 100 < 50) { + entity = NGame::TApp::Instance()->EntityManager().MakeEntityByName("DirtEntity"); + } + break; + + case HighDirt: + if (rand() % 100 < 75) { + entity = NGame::TApp::Instance()->EntityManager().MakeEntityByName("DirtEntity"); + } + break; + + case Dirt: + entity = NGame::TApp::Instance()->EntityManager().MakeEntityByName("DirtEntity"); + break; + + case Grass: + entity = NGame::TApp::Instance()->EntityManager().MakeEntityByName("GrassEntity"); + break; + + case Ladder: + entity = NGame::TApp::Instance()->EntityManager().MakeEntityByName("LadderEntity"); + break; + + case Plank: + entity = NGame::TApp::Instance()->EntityManager().MakeEntityByName("PlankEntity"); + break; + + case Mine: + if (NGame::TApp::Instance()->State().Variable("MoreMines").Bool()) { + if (rand() % 100 < 50) { + entity = NGame::TApp::Instance()->EntityManager().MakeEntityByName("MineEntity"); + } + } else { + if (rand() % 100 < 25) { + entity = NGame::TApp::Instance()->EntityManager().MakeEntityByName("MineEntity"); + } + } + break; + + case Passage: + entity = NGame::TApp::Instance()->EntityManager().MakeEntityByName("EntranceEntity"); + break; + + case Curse: + if (rand() % 100 < 25) { + entity = NGame::TApp::Instance()->EntityManager().MakeEntityByName("CurseEntity"); + } + break; + + case Spike: + if (rand() % 100 < 75) { + entity = NGame::TApp::Instance()->EntityManager().MakeEntityByName("SpikeEntity"); + } + break; + } + + if (!entity) { + continue; + } + + entity->SetPosition(entityPosition); + NGame::TApp::Instance()->EntityManager().UpdateCollision(entity); + } + } +} + +void TRoomEntity::SelectRoom(int type, TRoom& room) { + std::vector selection; + + if (type == LeftRight) { + selection.insert(selection.end(), LRRooms_.begin(), LRRooms_.end()); + selection.insert(selection.end(), LRDRooms_.begin(), LRDRooms_.end()); + selection.insert(selection.end(), LRURooms_.begin(), LRURooms_.end()); + selection.insert(selection.end(), LRUDRooms_.begin(), LRUDRooms_.end()); + room = selection[rand() % selection.size()]; + } else if (type == LeftRightUp) { + selection.insert(selection.end(), LRURooms_.begin(), LRURooms_.end()); + selection.insert(selection.end(), LRUDRooms_.begin(), LRUDRooms_.end()); + room = selection[rand() % selection.size()]; + } else if (type == LeftRightDown) { + selection.insert(selection.end(), LRDRooms_.begin(), LRDRooms_.end()); + selection.insert(selection.end(), LRUDRooms_.begin(), LRUDRooms_.end()); + room = selection[rand() % selection.size()]; + } else if (type == LeftRightUpDown) { + room = LRUDRooms_[rand() % LRUDRooms_.size()]; + } else { + selection.insert(selection.end(), AnyRooms_.begin(), AnyRooms_.end()); + selection.insert(selection.end(), LRRooms_.begin(), LRRooms_.end()); + selection.insert(selection.end(), LRDRooms_.begin(), LRDRooms_.end()); + selection.insert(selection.end(), LRURooms_.begin(), LRURooms_.end()); + selection.insert(selection.end(), LRUDRooms_.begin(), LRUDRooms_.end()); + room = selection[rand() % selection.size()]; + } +} + + +THero::THero(NGame::TEntity::TId id) + : NGame::TEntity(id) { + SetSize(NGame::Vec2i(4, 12)); + SetPosition(NGame::Vec2i(0, 0)); + NGame::TApp::Instance()->RenderManager().EnableLight(true); + NGame::TApp::Instance()->RenderManager().SetDefaultLightColor(0, 0, 0); + + Alarm_.Set(0, 100); +} + +void THero::Input(SDL_Event* event) { + switch (event->type) { + case SDL_KEYDOWN: + if (event->key.keysym.sym == SDLK_UP || event->key.keysym.sym == 'w') { + KeysHeld_[static_cast(EKeys::Up)] = true; + KeysPressed_[static_cast(EKeys::Up)] = true; + } + if (event->key.keysym.sym == SDLK_DOWN || event->key.keysym.sym == 's') { + KeysHeld_[static_cast(EKeys::Down)] = true; + KeysPressed_[static_cast(EKeys::Down)] = true; + } + if (event->key.keysym.sym == SDLK_LEFT || event->key.keysym.sym == 'a') { + KeysHeld_[static_cast(EKeys::Left)] = true; + KeysPressed_[static_cast(EKeys::Left)] = true; + } + if (event->key.keysym.sym == SDLK_RIGHT || event->key.keysym.sym == 'd') { + KeysHeld_[static_cast(EKeys::Right)] = true; + KeysPressed_[static_cast(EKeys::Right)] = true; + } + if (event->key.keysym.sym == 'z') { + KeysHeld_[static_cast(EKeys::Z)] = true; + KeysPressed_[static_cast(EKeys::Z)] = true; + } + if (event->key.keysym.sym == 'x') { + KeysHeld_[static_cast(EKeys::X)] = true; + KeysPressed_[static_cast(EKeys::X)] = true; + } + if (event->key.keysym.sym == 'c') { + KeysHeld_[static_cast(EKeys::C)] = true; + KeysPressed_[static_cast(EKeys::C)] = true; + } + if (event->key.keysym.sym == SDLK_LSHIFT) { + KeysHeld_[static_cast(EKeys::Shift)] = true; + KeysPressed_[static_cast(EKeys::Shift)] = true; + } + if (event->key.keysym.sym == SDLK_ESCAPE) { + KeysHeld_[static_cast(EKeys::Escape)] = true; + KeysPressed_[static_cast(EKeys::Escape)] = true; + } + break; + + case SDL_KEYUP: + if (event->key.keysym.sym == SDLK_UP || event->key.keysym.sym == 'w') { + KeysHeld_[static_cast(EKeys::Up)] = false; + } + if (event->key.keysym.sym == SDLK_DOWN || event->key.keysym.sym == 's') { + KeysHeld_[static_cast(EKeys::Down)] = false; + } + if (event->key.keysym.sym == SDLK_LEFT || event->key.keysym.sym == 'a') { + KeysHeld_[static_cast(EKeys::Left)] = false; + } + if (event->key.keysym.sym == SDLK_RIGHT || event->key.keysym.sym == 'd') { + KeysHeld_[static_cast(EKeys::Right)] = false; + } + if (event->key.keysym.sym == 'z') { + KeysHeld_[static_cast(EKeys::Z)] = false; + } + if (event->key.keysym.sym == 'x') { + KeysHeld_[static_cast(EKeys::X)] = false; + } + if (event->key.keysym.sym == 'c') { + KeysHeld_[static_cast(EKeys::C)] = false; + } + if (event->key.keysym.sym == SDLK_LSHIFT) { + KeysHeld_[static_cast(EKeys::Shift)] = false; + } + if (event->key.keysym.sym == SDLK_ESCAPE) { + KeysHeld_[static_cast(EKeys::Escape)] = false; + } + break; + } +} + + + +void THero::Update(std::uint32_t delta) { + auto app = NGame::TApp::Instance(); + + for (bool repeatState = true; repeatState; ) { + repeatState = false; + + switch (State_) { + case EState::Normal: + Speed_.X = 0; + Speed_.Y += MovementPerTick(delta, Gravity_); + + if (KeysHeld_[static_cast(EKeys::Right)]) { + Speed_.X = 100; + } + if (KeysHeld_[static_cast(EKeys::Left)]) { + Speed_.X = -100; + } + if (KeysPressed_[static_cast(EKeys::X)]) { + auto entity = app->EntityManager().MakeEntityByName("FloatingTextEntity"); + auto targetEntity = dynamic_cast(entity.get()); + targetEntity->SetText("You died, LMAO"); + targetEntity->SetColor(NGame::TFontManager::Red); + targetEntity->SetPosition(Position()); + } + + if (!app->EntityManager().IsPlaceEmpty(Position(), Size(), LADDER_GROUP)) { + if (KeysHeld_[static_cast(EKeys::Up)] || KeysHeld_[static_cast(EKeys::Down)]) { + State_ = EState::Climb; + continue; + } + } + if (!app->EntityManager().IsPlaceEmpty(Position() + NGame::Vec2i(-2, Size().Y), NGame::Vec2i(Size().X + 4, 1), TERRAIN_GROUP, Id())) { + if (KeysHeld_[static_cast(EKeys::Down)]) { + if (KeysPressed_[static_cast(EKeys::Z)]) { + IgnorePlanks_ = true; + Speed_.Y = 80; + State_ = EState::Fall; + continue; + } + } else { + if (KeysPressed_[static_cast(EKeys::Z)]) { + Speed_.Y = -std::sqrt(2 * Gravity_ * (16 + (16 - Size().Y))); + State_ = EState::Jump; + continue; + } + } + } + if (MovementPerTick(delta, Speed_.Y) >= 1.0) { + State_ = EState::Fall; + continue; + } + + break; + + case EState::Jump: + Speed_.X = 0; + Speed_.Y += MovementPerTick(delta, Gravity_); + + if (!app->EntityManager().IsPlaceEmpty(Position(), Size(), LADDER_GROUP)) { + if (KeysHeld_[static_cast(EKeys::Up)] || KeysHeld_[static_cast(EKeys::Down)]) { + State_ = EState::Climb; + continue; + } + } + if (KeysHeld_[static_cast(EKeys::Right)]) { + Speed_.X = 100; + } + if (KeysHeld_[static_cast(EKeys::Left)]) { + Speed_.X = -100; + } + if (Speed_.Y > 0) { + State_ = EState::Fall; + continue; + } + break; + + case EState::Fall: + Speed_.X = 0; + Speed_.Y += MovementPerTick(delta, Gravity_); + + if (!app->EntityManager().IsPlaceEmpty(Position(), Size(), LADDER_GROUP)) { + if (KeysHeld_[static_cast(EKeys::Up)] || KeysHeld_[static_cast(EKeys::Down)]) { + State_ = EState::Climb; + continue; + } + } + if (KeysHeld_[static_cast(EKeys::Right)]) { + Speed_.X = 100; + auto collisonIds = app->EntityManager().CollisionList(Position() + NGame::Vec2i(Size().X, 0), NGame::Vec2i(Size().X, 2), TERRAIN_GROUP, Id()); + auto containsWall = std::any_of(collisonIds.begin(), collisonIds.end(), [&](TEntity::TId id) { + auto other = app->EntityManager().Entity(id); + if (dynamic_cast(other.get())) { + return false; + } + return true; + }); + + if (containsWall) { + if (app->EntityManager().IsPlaceEmpty(Position() + NGame::Vec2i(0, -Size().Y), NGame::Vec2i(Size().X * 2, Size().Y), TERRAIN_GROUP, Id())) { + State_ = EState::Hold; + continue; + } + } + } + if (KeysHeld_[static_cast(EKeys::Left)]) { + Speed_.X = -100; + auto collisonIds = app->EntityManager().CollisionList(Position() - NGame::Vec2i(Size().X, 0), NGame::Vec2i(Size().X, 2), TERRAIN_GROUP, Id()); + auto containsWall = std::any_of(collisonIds.begin(), collisonIds.end(), [&](TEntity::TId id) { + auto other = app->EntityManager().Entity(id); + if (dynamic_cast(other.get())) { + return false; + } + return true; + }); + + if (containsWall) { + if (app->EntityManager().IsPlaceEmpty(Position() - NGame::Vec2i(Size().X, Size().Y), NGame::Vec2i(Size().X * 2, Size().Y), TERRAIN_GROUP, Id())) { + State_ = EState::Hold; + continue; + } + } + } + if (!app->EntityManager().IsPlaceEmpty(Position() + NGame::Vec2i(0, Size().Y), NGame::Vec2i(Size().X, 1), TERRAIN_GROUP, Id())) { + State_ = EState::Normal; + continue; + } + break; + + case EState::Climb: + if (app->EntityManager().IsPlaceEmpty(Position(), Size(), LADDER_GROUP)) { + State_ = EState::Normal; + repeatState = true; + continue; + } + + Speed_ = {0, 0}; + if (KeysHeld_[static_cast(EKeys::Right)]) { + Speed_.X = 100; + } + if (KeysHeld_[static_cast(EKeys::Left)]) { + Speed_.X = -100; + } + if (KeysHeld_[static_cast(EKeys::Up)]) { + Speed_.Y = -100; + } + if (KeysHeld_[static_cast(EKeys::Down)]) { + Speed_.Y = 100; + } + break; + + case EState::Hold: + Speed_ = 0; + if (KeysPressed_[static_cast(EKeys::Down)]) { + State_ = EState::Fall; + continue; + } + if (KeysPressed_[static_cast(EKeys::Z)]) { + Speed_.Y = -std::sqrt(2 * Gravity_ * (16 + (16 - Size().Y))); + State_ = EState::Jump; + continue; + } + break; + } + } + + // Check if we need to flip sprite + if (Speed_.X < 0) { + FaceLeft_ = 1; + } else if (Speed_.X > 0) { + FaceLeft_ = 0; + + } + // Perform movement and collision + NGame::Vec2f resultSpeed = MovementPerTick(delta, Speed_); + auto moveResult = MoveWithCondition(resultSpeed, Fraction_, [&](const NGame::Vec2i& position) { + auto collisionList = app->EntityManager().CollisionList(position, Size(), TERRAIN_GROUP, Id()); + if (collisionList.empty()) { + return true; + } + + // Check collision for planks + for (auto& otherId : collisionList) { + auto other = app->EntityManager().Entity(otherId); + if (dynamic_cast(other.get())) { + // Are we ignoring planks, because we are dropping down? + if (IgnorePlanks_) { + continue; + } + + // Ignore planks if we moving up + if (Speed_.Y < 0) { + continue; + } + + // Ignore planks, that are already inside of us + if (NGame::RectOverlaps(Position(), Size(), other->Position(), other->Size())) { + continue; + } + + // If all fails - we should be on top of the plank + return false; + } else { + return false; + } + } + + return true; + }); + + Fraction_ = moveResult.first; + if (!moveResult.second.first) { + Speed_.X = 0; + } + if (!moveResult.second.second) { + Speed_.Y = 0; + } + + // Set camera to track hero + app->RenderManager().SetCamera(Position() - app->RenderManager().Size() / 2); + + // Reset pressed keys + IgnorePlanks_ = false; + std::memset(KeysPressed_, false, sizeof(KeysPressed_)); +} + +void THero::Draw() const { + auto& renderManager = NGame::TApp::Instance()->RenderManager(); + auto& spriteManager = NGame::TApp::Instance()->SpriteManager(); + + renderManager.SetLayer(NGame::TRenderManager::Foreground); + auto hero = spriteManager.Get("Sprites/Hero.txt"); + int spriteIndex = 0; + + + switch (State_) { + case EState::Normal: spriteIndex = 0; break; + case EState::Jump: spriteIndex = 2; break; + case EState::Fall: spriteIndex = 0; break; + case EState::Climb: spriteIndex = 5; break; + case EState::Hold: spriteIndex = 1; break; + } + + if (State_ == EState::Normal && Speed_.X != 0 && AlternateRun_) { + spriteIndex = 3; + } + + spriteManager.Draw(hero, spriteIndex, Position() - NGame::Vec2i((16 - Size().X) / 2, 16 - Size().Y), {(FaceLeft_?-1.0f:1.0f), 1.0f}); + + renderManager.SetLayer(NGame::TRenderManager::Light); + auto light = spriteManager.Get("Sprites/Light.txt"); + spriteManager.Draw(light, 0, Position() - (light->Frames[0].Size * 4 / 2), {4, 4}); +} + +void THero::Alarm(NGame::TAlarm::TId id) { + if (id == 0) { + AlternateRun_ = !AlternateRun_; + } +} + +TBackgroundTiler::TBackgroundTiler(NGame::TEntity::TId id) + : NGame::TEntity(id) { + Background_ = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Wall.txt"); +} +void TBackgroundTiler::Draw() const { + auto& renderManager = NGame::TApp::Instance()->RenderManager(); + auto& spriteManager = NGame::TApp::Instance()->SpriteManager(); + + auto cameraPosition = renderManager.Camera(); + auto tileOffset = cameraPosition; + tileOffset.X %= 16; + tileOffset.Y %= 16; + + + renderManager.SetLayer(NGame::TRenderManager::Tile); + for (int i = -1; i <= 320 / 16; ++i) { + for (int j = -1; j <= 240 / 16; ++j) { + spriteManager.Draw(Background_, 0, NGame::Vec2i(i * 16, j * 16) + cameraPosition - tileOffset); + } + } +} \ No newline at end of file diff --git a/src/RoomEntity.h b/src/RoomEntity.h new file mode 100644 index 0000000..27eb3ad --- /dev/null +++ b/src/RoomEntity.h @@ -0,0 +1,272 @@ +#pragma once + +#include "App.h" + +#define TERRAIN_GROUP 0x01 +#define LADDER_GROUP 0x02 +#define DAMAGE_GROUP 0x04 +#define ITEM_GROUP 0x08 +#define PASSAGE_GROUP 0x10 +#define PLANK_GROUP 0x20 + +class TDirtEntity : public NGame::TEntity { +public: + TDirtEntity(NGame::TEntity::TId id); + + virtual void Update(std::uint32_t delta) override; + virtual void Draw() const override; + +private: + std::shared_ptr Sprite_; +}; + +class TGrassEntity : public NGame::TEntity { +public: + TGrassEntity(NGame::TEntity::TId id); + + virtual void Update(std::uint32_t delta) override; + virtual void Draw() const override; + +private: + std::shared_ptr Sprite_; +}; + +class TPlankEntity : public NGame::TEntity { +public: + TPlankEntity(NGame::TEntity::TId id); + + virtual void Update(std::uint32_t delta) override; + virtual void Draw() const override; + +private: + std::shared_ptr Sprite_; +}; + +class TStoneEntity : public NGame::TEntity { +public: + TStoneEntity(NGame::TEntity::TId id); + + virtual void Update(std::uint32_t delta) override; + virtual void Draw() const override; + +private: + std::shared_ptr Sprite_; +}; + +class TLadderEntity : public NGame::TEntity { +public: + TLadderEntity(NGame::TEntity::TId id); + + virtual void Update(std::uint32_t delta) override; + virtual void Draw() const override; + +private: + std::shared_ptr Sprite_; +}; + +class TExplosionEntity : public NGame::TEntity { +public: + TExplosionEntity(NGame::TEntity::TId id); + + virtual void Update(std::uint32_t delta) override; + virtual void Draw() const override; + virtual void Alarm(NGame::TAlarm::TId id) override; + +private: + std::shared_ptr Sprite_; +}; + +class TKeyEntity : public NGame::TEntity { +public: + TKeyEntity(NGame::TEntity::TId id); + + virtual void Update(std::uint32_t delta) override; + virtual void Draw() const override; + +private: + std::shared_ptr Sprite_; +}; + +class TMineEntity : public NGame::TEntity { +public: + TMineEntity(NGame::TEntity::TId id); + + virtual void Update(std::uint32_t delta) override; + virtual void Draw() const override; + virtual void Alarm(NGame::TAlarm::TId id) override; + +private: + std::shared_ptr Sprite_; +}; + +class TCurseEntity : public NGame::TEntity { +public: + TCurseEntity(NGame::TEntity::TId id); + + virtual void Update(std::uint32_t delta) override; + virtual void Draw() const override; + +private: + std::shared_ptr Sprite_; +}; + +class TSpikeEntity : public NGame::TEntity { +public: + TSpikeEntity(NGame::TEntity::TId id); + + virtual void Update(std::uint32_t delta) override; + virtual void Draw() const override; + +private: + std::shared_ptr Sprite_; +}; + +class TEntranceEntity : public NGame::TEntity { +public: + TEntranceEntity(NGame::TEntity::TId id); + + virtual void Update(std::uint32_t delta) override; + virtual void Draw() const override; + +private: + std::shared_ptr Sprite_; +}; + +class TExitEntity : public NGame::TEntity { +public: + TExitEntity(NGame::TEntity::TId id); + + virtual void Update(std::uint32_t delta) override; + virtual void Draw() const override; + +private: + std::shared_ptr Sprite_; +}; + +class TFloatingTextEntity : public NGame::TEntity { +public: + TFloatingTextEntity(NGame::TEntity::TId id); + + void SetText(const std::string& text); + void SetColor(NGame::TFontManager::EColor color); + + virtual void Update(std::uint32_t delta) override; + virtual void Draw() const override; + virtual void Alarm(NGame::TAlarm::TId id) override; + +private: + std::string Text_; + NGame::TFontManager::EColor Color_; +}; + +class TRoomEntity : public NGame::TEntity { +public: + TRoomEntity(NGame::TEntity::TId id); + +private: + enum EType { + None, + Left = 0x0001, + Right = 0x0002, + Up = 0x0004, + Down = 0x0008, + Start = 0x0010, + Exit = 0x0020, + LeftRight = Left | Right, + LeftRightDown = Left | Right | Down, + LeftRightUp = Left | Right | Up, + LeftRightUpDown = Left | Right | Up | Down, + }; + + enum ETile { + Empty, + Baddy, + Curse, + Dirt, + Grass, + HighDirt, + Ladder, + LowDirt, + MidDirt, + Mine, + Passage, + Plank, + Spike, + Utility, + }; + + struct TRoom { + ETile data[60]; + }; + + void LoadTemplates(); + void ParseRooms(const std::string& data, std::vector& rooms); + bool ParseRoom(std::istringstream& stream, TRoom& room); + void FillRow(const std::string& data, std::size_t row, TRoom& room); + bool GoLeft(int* maze, NGame::Vec2i& position); + bool GoRight(int* maze, NGame::Vec2i& position); + bool GoDown(int* maze, NGame::Vec2i& position); + void GenerateLayout(); + void GenerateRoom(int* maze, const NGame::Vec2i& position); + void SelectRoom(int type, TRoom& room); + +private: + std::vector LRRooms_; + std::vector LRURooms_; + std::vector LRDRooms_; + std::vector LRUDRooms_; + std::vector AnyRooms_; +}; + +class THero : public NGame::TEntity { +public: + THero(NGame::TEntity::TId id); + + enum class EKeys { + Left, + Right, + Up, + Down, + X, + Z, + C, + Shift, + Escape, + MaxSentinel + }; + + enum class EState { + Normal, + Jump, + Fall, + Climb, + Hold, + }; + + virtual void Input(SDL_Event* event) override; + virtual void Update(std::uint32_t delta) override; + virtual void Draw() const override; + virtual void Alarm(NGame::TAlarm::TId id) override; + +private: + bool KeysPressed_[static_cast(EKeys::MaxSentinel)] = {}; + bool KeysHeld_[static_cast(EKeys::MaxSentinel)] = {}; + bool FaceLeft_ = false; + bool IgnorePlanks_ = false; + bool AlternateRun_ = false; + EState State_ = EState::Normal; + + const float Gravity_ = 500.0; + NGame::Vec2f Speed_ = NGame::Vec2f(0.0, 0.0); + NGame::Vec2f Fraction_ = NGame::Vec2f(0.0, 0.0); + NGame::Vec2i Want_; +}; + +class TBackgroundTiler : public NGame::TEntity { +public: + TBackgroundTiler(NGame::TEntity::TId id); + virtual void Draw() const override; + +private: + std::shared_ptr Background_; +}; \ No newline at end of file diff --git a/src/SpriteManager.cpp b/src/SpriteManager.cpp new file mode 100644 index 0000000..80bc509 --- /dev/null +++ b/src/SpriteManager.cpp @@ -0,0 +1,119 @@ +#include "SpriteManager.h" + +#include +#include + +namespace NGame { + +TSpriteManager::TSpriteManager(TFileManager& fileManager, TRenderManager& renderManager, std::size_t cacheSize) + : FileManager_(fileManager), RenderManager_(renderManager), Cache_(cacheSize) { +} + +std::shared_ptr TSpriteManager::Get(const std::string& path) { + if (Cache_.Contains(path)) { + return Cache_.Get(path); + } + + auto result = std::make_shared(ParseInfoFile(path)); + Cache_.Set(path, result); + return result; +} + +void TSpriteManager::Draw(const std::shared_ptr& sprite, size_t frame, const Vec2i& position, const Vec2f& scale) { + const auto& frameData = sprite->Frames[frame]; + + int flip = 0; + Vec2i size = frameData.Size * scale; + + if (size.X < 0) { + size.X = -size.X; + flip |= SDL_FLIP_HORIZONTAL; + } + if (size.Y < 0) { + size.Y = -size.Y; + flip |= SDL_FLIP_VERTICAL; + } + + RenderManager_.SetTexture(sprite->Texture); + RenderManager_.DrawSprite(frameData.Position, frameData.Size, position, size, static_cast(flip)); +} + +TSpriteManager::TSprite TSpriteManager::ParseInfoFile(const std::string& path) { + std::string data = FileManager_.Get(path); + std::istringstream inputStream(data); + + std::shared_ptr currentTexture; + std::vector currentFrames; + + for (std::string line; std::getline(inputStream, line); ) { + auto remainder = Trim(std::string_view(line)); + + auto action = RightTrim(NextToken(remainder, " ")); + if (action == "Texture") { + auto textureString = Trim(NextToken(remainder, " ")); + if (textureString.empty()) { + throw std::runtime_error("texture name should not be empty for " + path); + } + currentTexture = RenderManager_.Get(std::string(textureString)); + } else if (action == "Frame") { + auto xString = Trim(NextToken(remainder, " ")); + auto yString = Trim(NextToken(remainder, " ")); + auto widthString = Trim(NextToken(remainder, " ")); + auto heightString = Trim(NextToken(remainder, " ")); + + + TFrame frame; + + if (xString.empty() && yString.empty()) { + frame.Position.X = 0; + frame.Position.Y = 0; + } else { + frame.Position.X = std::stoi(std::string(xString)); + frame.Position.Y = std::stoi(std::string(yString)); + } + + if (widthString.empty() && heightString.empty()) { + int width, height; + + if (SDL_QueryTexture(currentTexture.get(), NULL, NULL, &width, &height)) { + throw std::runtime_error("can't query texture information for " + path); + } + + frame.Size.X = width; + frame.Size.Y = height; + } else { + frame.Size.X = std::stoi(std::string(widthString)); + frame.Size.Y = std::stoi(std::string(heightString)); + } + + if (xString.empty() || yString.empty() || widthString.empty() || heightString.empty()) { + throw std::runtime_error("frame should have x, y, width, height parameters for " + path); + } + + currentFrames.push_back(frame); + } + } + + if (!currentTexture) { + std::runtime_error("no texture for " + path); + } + + if (currentFrames.empty()) { + TFrame frame; + int width, height; + if (SDL_QueryTexture(currentTexture.get(), NULL, NULL, &width, &height)) { + throw std::runtime_error("can't query texture information for " + path); + } + + frame.Position.X = 0; + frame.Position.Y = 0; + frame.Size.X = width; + frame.Size.Y = height; + currentFrames.push_back(frame); + } + + return {currentTexture, currentFrames}; +} + + +} // namespace NGame diff --git a/src/SpriteManager.h b/src/SpriteManager.h new file mode 100644 index 0000000..96ca002 --- /dev/null +++ b/src/SpriteManager.h @@ -0,0 +1,38 @@ +#pragma once + +#include "Common.h" + +#include "FileManager.h" +#include "LRU.h" +#include "RenderManager.h" + +namespace NGame { + +class TSpriteManager { +public: + struct TFrame { + Vec2i Position; + Vec2i Size; + }; + + struct TSprite { + std::shared_ptr Texture; + std::vector Frames; + }; + + TSpriteManager(TFileManager& fileManager, TRenderManager& renderManager, std::size_t cacheSize = 4); + DELETE_COPY(TSpriteManager) + + std::shared_ptr Get(const std::string& path); + void Draw(const std::shared_ptr& sprite, size_t frame, const Vec2i& position, const Vec2f& scale = {1.0f, 1.0f}); + +private: + TSprite ParseInfoFile(const std::string& path); + +private: + TFileManager& FileManager_; + TRenderManager& RenderManager_; + TLRU> Cache_; +}; + +} // namespace NGame \ No newline at end of file diff --git a/src/State.cpp b/src/State.cpp new file mode 100644 index 0000000..4c4e772 --- /dev/null +++ b/src/State.cpp @@ -0,0 +1,39 @@ +#include "State.h" + +#include + +namespace NGame { + +TVariable& TState::Variable(const std::string_view& name) { + auto stringName = std::string(name); + auto iterator = Variables_.find(stringName); + + if (iterator != Variables_.end()) { + return iterator->second; + } else { + return SetVariable(name, 0); + } +} + +const TVariable& TState::Variable(const std::string_view& name) const { + auto stringName = std::string(name); + auto iterator = Variables_.find(stringName); + + if (iterator != Variables_.end()) { + return iterator->second; + } + + throw std::runtime_error("Attempting to access non-existing variable"); +} + +bool TState::ContainsVariable(const std::string_view& name) const { + auto stringName = std::string(name); + auto iterator = Variables_.find(stringName); + + if (iterator != Variables_.end()) { + return true; + } + return false; +} + +} // namespace NGame diff --git a/src/State.h b/src/State.h new file mode 100644 index 0000000..74401e8 --- /dev/null +++ b/src/State.h @@ -0,0 +1,36 @@ +#pragma once + +#include "Common.h" + +#include "Variable.h" + +#include +#include +#include +#include +#include + +namespace NGame { + +class TState { +public: + TVariable& Variable(const std::string_view& name); + const TVariable& Variable(const std::string_view& name) const; + bool ContainsVariable(const std::string_view& name) const; + + template + TVariable& SetVariable(const std::string_view& name, const T& value) { + auto stringName = std::string(name); + auto result = Variables_.emplace(std::make_pair(stringName, value)); + if (result.second) { + return result.first->second; + } + + throw std::runtime_error("Failed to set variable"); + } + +private: + std::unordered_map Variables_; +}; + +} // namespace NGame \ No newline at end of file diff --git a/src/SurfaceManager.cpp b/src/SurfaceManager.cpp new file mode 100644 index 0000000..259f650 --- /dev/null +++ b/src/SurfaceManager.cpp @@ -0,0 +1,32 @@ +#include "SurfaceManager.h" + +#include +#include +#include +#include + +namespace NGame { + +TSurfaceManager::TSurfaceManager(TFileManager& fileManager, std::size_t cacheSize) + : FileManager_(fileManager), Cache_(cacheSize) { +} + +std::shared_ptr TSurfaceManager::Get(const std::string& path) { + if (Cache_.Contains(path)) { + return Cache_.Get(path); + } + + std::string data = FileManager_.Get(path); + SDL_RWops* rw = SDL_RWFromConstMem(data.data(), data.size()); + SDL_Surface* surface = SDL_LoadBMP_RW(rw, 1); + + if (!surface) { + std::runtime_error("Can't create surface from file " + path); + } + + std::shared_ptr result(surface, [](SDL_Surface* surface) { SDL_FreeSurface(surface); }); + Cache_.Set(path, result); + return result; +} + +} // namespace NGame \ No newline at end of file diff --git a/src/SurfaceManager.h b/src/SurfaceManager.h new file mode 100644 index 0000000..b1290b2 --- /dev/null +++ b/src/SurfaceManager.h @@ -0,0 +1,25 @@ +#pragma once + +#include "Common.h" + +#include "LRU.h" +#include "FileManager.h" + +#include +#include + +namespace NGame { + +class TSurfaceManager { +public: + TSurfaceManager(TFileManager& fileManager, std::size_t cacheSize = 4); + DELETE_COPY(TSurfaceManager) + + std::shared_ptr Get(const std::string& path); + +private: + TFileManager& FileManager_; + TLRU> Cache_; +}; + +} // namespace NGame \ No newline at end of file diff --git a/src/Variable.cpp b/src/Variable.cpp new file mode 100644 index 0000000..67c4a65 --- /dev/null +++ b/src/Variable.cpp @@ -0,0 +1,83 @@ +#include "Variable.h" + +namespace NGame { + +void TVariable::SetDouble(double value) { + Data_ = value; +} + +void TVariable::SetString(const std::string& value) { + Data_ = value; +} + +void TVariable::SetInt(int value) { + Data_ = value; +} + +void TVariable::SetBool(bool value) { + Data_ = value; +} + +double TVariable::Double() const { + const auto asDouble = TOverload { + [](int value) { return double(value); }, + [](double value) { return value; }, + [](const std::string value) { + try { + return std::stod(value); + } catch (...) { + return 0.0; + } + }, + [](bool value) { return double(value); } + }; + + return std::visit(asDouble, Data_); +} + +std::string TVariable::String() const { + const auto asString = TOverload { + [](int value) { return std::to_string(value); }, + [](double value) { return std::to_string(value); }, + [](const std::string value) { return value; }, + [](bool value) { return std::to_string(value); } + }; + + return std::visit(asString, Data_); +} + +int TVariable::Int() const { + const auto asInt = TOverload { + [](int value) { return value; }, + [](double value) { return int(value); }, + [](const std::string value) { + try { + return std::stoi(value); + } catch (...) { + return 0; + } + }, + [](bool value) { return int(value); } + }; + + return std::visit(asInt, Data_); +} + +bool TVariable::Bool() const { + const auto asBool = TOverload { + [](int value) { return value != 0; }, + [](double value) { return value != 0; }, + [](const std::string value) { + std::string caseFolded = ToUpper(Trim(value)); + if (caseFolded == "TRUE") { + return true; + } + return false; + }, + [](bool value) { return value; } + }; + + return std::visit(asBool, Data_); +} + +} // namespace NGame diff --git a/src/Variable.h b/src/Variable.h new file mode 100644 index 0000000..ea0ef0c --- /dev/null +++ b/src/Variable.h @@ -0,0 +1,33 @@ +#pragma once + +#include "Common.h" + +#include +#include + +namespace NGame { + +class TVariable { +public: + TVariable() = default; + + template + TVariable(const T& value) + : Data_(value) { + } + + void SetDouble(double value); + void SetString(const std::string& value); + void SetInt(int value); + void SetBool(bool value); + + double Double() const; + std::string String() const; + int Int() const; + bool Bool() const; + +private: + std::variant Data_; +}; + +} // namespace NGame \ No newline at end of file