diff --git a/applications/plugins/doom/README.md b/applications/plugins/doom/README.md
new file mode 100644
index 000000000..d43b7ab1a
--- /dev/null
+++ b/applications/plugins/doom/README.md
@@ -0,0 +1,68 @@
+# Doom Flipper Zero edition
+
+

+
+## Will it run Doom?
+As tradition goes, Doom is being ported to almost every possible embedded electronic device. Therefore I did an attempt to come up with something close to Doom and still compatible on the Flipper Zero's hardware.
This is not the actual Doom game but a port made from yet another Doom port to the Arduino Nano (https://github.com/daveruiz/doom-nano/). This port is basically a raycasting engine, using Doom sprites.
+This version is very basic and might be improved over time.
+
+## How to install on Flipper Zero
+During the porting process, minor changes were made to the workings (and build options) of the current firmware. These changes are documented here and are necessary in order to get a working firmware build that contains this Doom port.
+### Modifying the firmware & build options
+ * In the `sites/cc.scons` add the following values to the `CCFLAGS` section:
+ ```
+ ...
+"-Wno-unused-parameter",
+"-Wno-type-limits",
+"-Wno-unused-variable",
+...
+ ```
+ * In `applications/gui/canvas_i.h` comment out the following line:
+ `uint8_t* canvas_get_buffer(Canvas* canvas);` --> `//uint8_t* canvas_get_buffer(Canvas* canvas);`
+
+ * In `applications/gui/canvas.h` add the following lines:
+ ```
+ uint8_t* canvas_get_buffer(Canvas* canvas);
+ void canvas_draw_icon_bitmap(Canvas* canvas, uint8_t x, uint8_t y, int16_t w, int16_t h, const Icon* icon);
+ ```
+ * In `applications/gui/canvas.c` add the following function:
+ ```
+ void canvas_draw_icon_bitmap(Canvas* canvas, uint8_t x, uint8_t y, int16_t w, int16_t h, const Icon* icon){
+ furi_assert(canvas);
+ furi_assert(icon);
+
+ x += canvas->offset_x;
+ y += canvas->offset_y;
+ uint8_t* icon_data = NULL;
+ furi_hal_compress_icon_decode(icon_get_data(icon), &icon_data);
+ u8g2_DrawXBM(&canvas->fb, x, y, w, h, icon_data);
+ }
+ ```
+
+### Installing the plugin in the firmware
+ * Make a folder called Doom in the applications folder. Add all the source files (also the compiled folder and it's files) in the Doom folder.
+ * Make the `applications/meta/application.fam` look like the following:
+ ```
+ App(
+ appid="basic_plugins",
+ name="Basic applications for plug-in menu",
+ apptype=FlipperAppType.METAPACKAGE,
+ provides=[
+ ...
+ "doom_game",
+ ...
+
+ ],
+ )
+ ```
+
+If all went well the only thing left to do is building the firmware and installing it to the Flipper.
+
+## Screenshots
+
+
+
+
+
+
+
diff --git a/applications/plugins/doom/application.fam b/applications/plugins/doom/application.fam
new file mode 100644
index 000000000..f0ce5eea4
--- /dev/null
+++ b/applications/plugins/doom/application.fam
@@ -0,0 +1,15 @@
+App(
+ appid="game_doom",
+ name="DOOM",
+ apptype=FlipperAppType.PLUGIN,
+ entry_point="doom_app",
+ cdefines=["APP_DOOM_GAME"],
+ requires=[
+ "gui",
+ "music_player",
+ ],
+ stack_size=4 * 1024,
+ order=75,
+ fap_icon="doom_10px.png",
+ fap_category="Games",
+)
diff --git a/applications/plugins/doom/assets/door2.png b/applications/plugins/doom/assets/door2.png
new file mode 100644
index 000000000..b4b4f0399
Binary files /dev/null and b/applications/plugins/doom/assets/door2.png differ
diff --git a/applications/plugins/doom/assets/door_inv.png b/applications/plugins/doom/assets/door_inv.png
new file mode 100644
index 000000000..3185f524c
Binary files /dev/null and b/applications/plugins/doom/assets/door_inv.png differ
diff --git a/applications/plugins/doom/assets/fire_inv.png b/applications/plugins/doom/assets/fire_inv.png
new file mode 100644
index 000000000..46af8691b
Binary files /dev/null and b/applications/plugins/doom/assets/fire_inv.png differ
diff --git a/applications/plugins/doom/assets/fireball_inv.png b/applications/plugins/doom/assets/fireball_inv.png
new file mode 100644
index 000000000..b046288f8
Binary files /dev/null and b/applications/plugins/doom/assets/fireball_inv.png differ
diff --git a/applications/plugins/doom/assets/fireball_mask_inv.png b/applications/plugins/doom/assets/fireball_mask_inv.png
new file mode 100644
index 000000000..548c654b7
Binary files /dev/null and b/applications/plugins/doom/assets/fireball_mask_inv.png differ
diff --git a/applications/plugins/doom/assets/gradient_inv.png b/applications/plugins/doom/assets/gradient_inv.png
new file mode 100644
index 000000000..78eec8c20
Binary files /dev/null and b/applications/plugins/doom/assets/gradient_inv.png differ
diff --git a/applications/plugins/doom/assets/gun_inv.png b/applications/plugins/doom/assets/gun_inv.png
new file mode 100644
index 000000000..e2ec05295
Binary files /dev/null and b/applications/plugins/doom/assets/gun_inv.png differ
diff --git a/applications/plugins/doom/assets/gun_mask_inv.png b/applications/plugins/doom/assets/gun_mask_inv.png
new file mode 100644
index 000000000..2d761a70a
Binary files /dev/null and b/applications/plugins/doom/assets/gun_mask_inv.png differ
diff --git a/applications/plugins/doom/assets/imp_inv.png b/applications/plugins/doom/assets/imp_inv.png
new file mode 100644
index 000000000..4b480f1c5
Binary files /dev/null and b/applications/plugins/doom/assets/imp_inv.png differ
diff --git a/applications/plugins/doom/assets/imp_mask_inv.png b/applications/plugins/doom/assets/imp_mask_inv.png
new file mode 100644
index 000000000..70e991270
Binary files /dev/null and b/applications/plugins/doom/assets/imp_mask_inv.png differ
diff --git a/applications/plugins/doom/assets/intro-screen.png b/applications/plugins/doom/assets/intro-screen.png
new file mode 100644
index 000000000..e46d6fdc3
Binary files /dev/null and b/applications/plugins/doom/assets/intro-screen.png differ
diff --git a/applications/plugins/doom/assets/item_inv.png b/applications/plugins/doom/assets/item_inv.png
new file mode 100644
index 000000000..1d32dbcd8
Binary files /dev/null and b/applications/plugins/doom/assets/item_inv.png differ
diff --git a/applications/plugins/doom/assets/item_mask_inv.png b/applications/plugins/doom/assets/item_mask_inv.png
new file mode 100644
index 000000000..a0bde9c76
Binary files /dev/null and b/applications/plugins/doom/assets/item_mask_inv.png differ
diff --git a/applications/plugins/doom/assets/screenshot-imp2.jpg b/applications/plugins/doom/assets/screenshot-imp2.jpg
new file mode 100644
index 000000000..0b4c29c4f
Binary files /dev/null and b/applications/plugins/doom/assets/screenshot-imp2.jpg differ
diff --git a/applications/plugins/doom/assets/screenshot-intro2.jpg b/applications/plugins/doom/assets/screenshot-intro2.jpg
new file mode 100644
index 000000000..21fd72840
Binary files /dev/null and b/applications/plugins/doom/assets/screenshot-intro2.jpg differ
diff --git a/applications/plugins/doom/assets/screenshot-medkit2.jpg b/applications/plugins/doom/assets/screenshot-medkit2.jpg
new file mode 100644
index 000000000..4ee0269f3
Binary files /dev/null and b/applications/plugins/doom/assets/screenshot-medkit2.jpg differ
diff --git a/applications/plugins/doom/assets/screenshot-start2.jpg b/applications/plugins/doom/assets/screenshot-start2.jpg
new file mode 100644
index 000000000..6b28ed987
Binary files /dev/null and b/applications/plugins/doom/assets/screenshot-start2.jpg differ
diff --git a/applications/plugins/doom/assets/screenshot1.png b/applications/plugins/doom/assets/screenshot1.png
new file mode 100644
index 000000000..1ecb073a6
Binary files /dev/null and b/applications/plugins/doom/assets/screenshot1.png differ
diff --git a/applications/plugins/doom/assets/screenshot2.png b/applications/plugins/doom/assets/screenshot2.png
new file mode 100644
index 000000000..216b699de
Binary files /dev/null and b/applications/plugins/doom/assets/screenshot2.png differ
diff --git a/applications/plugins/doom/assets/screenshot3.png b/applications/plugins/doom/assets/screenshot3.png
new file mode 100644
index 000000000..b5aec03fd
Binary files /dev/null and b/applications/plugins/doom/assets/screenshot3.png differ
diff --git a/applications/plugins/doom/compiled/assets_icons.c b/applications/plugins/doom/compiled/assets_icons.c
new file mode 100644
index 000000000..041b35728
--- /dev/null
+++ b/applications/plugins/doom/compiled/assets_icons.c
@@ -0,0 +1,403 @@
+#include "assets_icons.h"
+
+#include
+
+// Inverted icons
+
+const uint8_t _I_fire_inv_0[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x30, 0x00, 0x40, 0xee, 0x00, 0x80, 0xe6, 0x00,
+ 0x80, 0xa7, 0x01, 0x80, 0xc7, 0x03, 0x40, 0x45, 0x03, 0xe0, 0x41, 0x07, 0xf8, 0x82, 0x9f, 0xb9,
+ 0x01, 0x3e, 0x7c, 0x00, 0x7a, 0x6e, 0x00, 0x56, 0x1c, 0x00, 0x6c, 0xf4, 0x01, 0x3a, 0x6c, 0x00,
+ 0x7e, 0xfc, 0x00, 0x1a, 0x08, 0x00, 0x3c, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+const uint8_t* const _I_fire_inv[] = {_I_fire_inv_0};
+
+const uint8_t _I_gun_inv_0[] = {
+ 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x80, 0x23, 0x00, 0x00, 0x40,
+ 0x20, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x40, 0x57, 0x00, 0x00, 0x20, 0x8b, 0x00, 0x00,
+ 0x90, 0x11, 0x01, 0x00, 0x98, 0x00, 0x00, 0x00, 0xb0, 0x43, 0x01, 0x00, 0x94, 0x81, 0x03,
+ 0x00, 0xd0, 0x45, 0x04, 0x00, 0x8c, 0x02, 0x02, 0x00, 0xc4, 0x00, 0x03, 0x00, 0xc8, 0x00,
+ 0x02, 0x00, 0x4e, 0x40, 0x00, 0x00, 0x92, 0x00, 0x02, 0x80, 0x07, 0x15, 0x04, 0xe0, 0x8f,
+ 0x00, 0x0c, 0xd0, 0x9d, 0x07, 0x17, 0xe0, 0x3a, 0xc0, 0x3f, 0xe0, 0xf7, 0xff, 0x77, 0xe0,
+ 0xae, 0xfe, 0x4b, 0xd8, 0xdd, 0xff, 0x4d, 0x88, 0xea, 0xbe, 0x26, 0x4c, 0xf5, 0xff, 0x17,
+ 0xc8, 0xfa, 0xae, 0x0b, 0xcc, 0xff, 0xdf, 0x19, 0xe8, 0xeb, 0xa7, 0x00, 0xd8, 0xf1, 0x4d,
+ 0x0c, 0xc0, 0xbe, 0x1a, 0x08, 0xf6, 0xfd, 0x37, 0x04,
+};
+const uint8_t* const _I_gun_inv[] = {_I_gun_inv_0};
+
+const uint8_t _I_gun_mask_inv_0[] = {
+ 0x01, 0x00, 0x53, 0x00, 0x00, 0x0c, 0x38, 0x04, 0x38, 0x09, 0xf8, 0x0c, 0x78, 0x17, 0xf0,
+ 0x18, 0xf8, 0x00, 0x67, 0xff, 0x01, 0xaf, 0xc3, 0xff, 0x01, 0x80, 0x7e, 0x3f, 0xf0, 0x38,
+ 0x07, 0xf0, 0x0e, 0x40, 0x31, 0x03, 0x8f, 0xfb, 0xff, 0x07, 0x01, 0x94, 0x3c, 0x0e, 0xc0,
+ 0x35, 0xff, 0x85, 0xc8, 0x06, 0x30, 0x7e, 0x00, 0x0c, 0x61, 0xe2, 0xe1, 0xff, 0xc7, 0xc5,
+ 0xc3, 0xff, 0x9f, 0x80, 0xca, 0xfe, 0x03, 0x2f, 0xf8, 0x0c, 0xc6, 0xc2, 0x03, 0x4b, 0xf8,
+ 0xa8, 0x42, 0xe2, 0x03, 0x28, 0xf8, 0x1e, 0x80, 0x68, 0x1e, 0x28, 0x78,
+};
+const uint8_t* const _I_gun_mask_inv[] = {_I_gun_mask_inv_0};
+
+const uint8_t _I_logo_inv_0[] = {
+ 0x01, 0x00, 0x71, 0x01, 0x00, 0x44, 0x0a, 0x21, 0x00, 0xc0, 0x48, 0x20, 0x91, 0x0c, 0x05, 0x20,
+ 0x14, 0x30, 0x80, 0x71, 0x80, 0x8e, 0x03, 0x00, 0x85, 0x40, 0x22, 0x98, 0x8a, 0x00, 0x20, 0x60,
+ 0xa0, 0x83, 0xa8, 0x50, 0x20, 0xd8, 0x00, 0x41, 0xcd, 0x01, 0x03, 0x82, 0xc3, 0xc3, 0x61, 0xf0,
+ 0xa8, 0xf0, 0x44, 0x6c, 0x14, 0x68, 0x26, 0x16, 0x3b, 0x0a, 0x89, 0xcd, 0x24, 0x14, 0x0a, 0x96,
+ 0x32, 0x1b, 0x11, 0x85, 0xc4, 0x62, 0x92, 0x00, 0x88, 0xe1, 0x30, 0xb1, 0x18, 0x54, 0x46, 0x6d,
+ 0x28, 0xa0, 0x70, 0x82, 0x23, 0x51, 0xa5, 0x54, 0x62, 0x90, 0x1b, 0x05, 0x0b, 0x19, 0x15, 0x89,
+ 0x86, 0x86, 0x69, 0x42, 0xb0, 0xf5, 0xb2, 0x9a, 0x58, 0xac, 0xae, 0xaf, 0x35, 0x85, 0x50, 0xb8,
+ 0xda, 0x69, 0x6d, 0x6e, 0x95, 0x9b, 0x9b, 0x50, 0xac, 0x9c, 0xac, 0x6e, 0x36, 0x37, 0x2b, 0xad,
+ 0xed, 0xa2, 0x41, 0xa1, 0xd7, 0x6b, 0x42, 0x23, 0x3f, 0xdb, 0x75, 0xad, 0x5d, 0x6c, 0x2c, 0x37,
+ 0xbf, 0x5b, 0x0d, 0x0e, 0x5a, 0xc5, 0xca, 0xdb, 0xef, 0x6b, 0xf6, 0xea, 0xff, 0xde, 0x6d, 0x4d,
+ 0xfb, 0x75, 0xe5, 0xfb, 0xfd, 0xde, 0xfe, 0x6d, 0xe7, 0xb9, 0x7b, 0xba, 0xff, 0xdb, 0xfd, 0xaf,
+ 0xbf, 0x77, 0xc7, 0xd9, 0xf3, 0x5f, 0x79, 0xed, 0x3c, 0x10, 0x3f, 0x79, 0x7d, 0xbf, 0x9f, 0xed,
+ 0xff, 0xf7, 0xe7, 0x5f, 0xff, 0xbd, 0xae, 0xbd, 0xd6, 0xfe, 0xdf, 0x62, 0x34, 0xbf, 0xdf, 0xde,
+ 0x77, 0x6b, 0xe9, 0x7f, 0xbf, 0xf5, 0x38, 0x77, 0xe7, 0xef, 0x7b, 0xb9, 0xf4, 0xbf, 0x7f, 0xde,
+ 0xbd, 0xb8, 0x34, 0x3f, 0xbb, 0x5f, 0xbb, 0xff, 0xff, 0xbf, 0xda, 0x80, 0x63, 0xfb, 0xa1, 0xfe,
+ 0xc1, 0x05, 0xdf, 0x11, 0x8f, 0xce, 0xdd, 0xf2, 0xff, 0xff, 0x40, 0xe3, 0xff, 0xf3, 0xfd, 0xe9,
+ 0x42, 0xb1, 0xfc, 0x84, 0xc4, 0x32, 0x3f, 0xbb, 0xf0, 0x10, 0x7e, 0x60, 0x82, 0xfe, 0xdc, 0xe8,
+ 0xc1, 0x11, 0x07, 0x97, 0xff, 0xfd, 0x7f, 0xbf, 0x82, 0x07, 0x8f, 0xff, 0xf8, 0x82, 0x06, 0xef,
+ 0x7e, 0x04, 0x0e, 0x0f, 0xff, 0xe0, 0x9f, 0xfe, 0x04, 0x67, 0x01, 0x9f, 0x60, 0x21, 0xff, 0x0d,
+ 0xf8, 0x68, 0xa0, 0x11, 0xcc, 0x04, 0x1f, 0xc1, 0xbf, 0x0d, 0x0c, 0xfe, 0x01, 0x08, 0x80, 0x40,
+ 0xb8, 0x1f, 0xc0, 0x88, 0xc7, 0xe0, 0x00, 0x03, 0xe4, 0x5f, 0xff, 0xf0, 0xf0, 0x42, 0x70, 0x03,
+ 0x43, 0x03, 0x04, 0x28, 0x68, 0x60, 0x20, 0x87, 0x03, 0xa4, 0x03, 0xc1, 0x7f, 0x1a, 0x08, 0x45,
+ 0x86, 0x8b, 0x00, 0xc0, 0x5f, 0xe1, 0xc0, 0x85, 0x80, 0x5f, 0xe0, 0x80, 0x86, 0xfe, 0x01, 0xda,
+ 0x01, 0x78, 0x04, 0x3c, 0xc0,
+};
+const uint8_t* const _I_logo_inv[] = {_I_logo_inv_0};
+
+const Icon I_fire_inv =
+ {.width = 24, .height = 20, .frame_count = 1, .frame_rate = 0, .frames = _I_fire_inv};
+const Icon I_gun_inv =
+ {.width = 32, .height = 32, .frame_count = 1, .frame_rate = 0, .frames = _I_gun_inv};
+const Icon I_gun_mask_inv =
+ {.width = 32, .height = 32, .frame_count = 1, .frame_rate = 0, .frames = _I_gun_mask_inv};
+const Icon I_logo_inv =
+ {.width = 72, .height = 47, .frame_count = 1, .frame_rate = 0, .frames = _I_logo_inv};
+
+const uint8_t space[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+const uint8_t zero[] = {0x00, 0x60, 0x90, 0x90, 0x90, 0x60};
+const uint8_t one[] = {0x00, 0x20, 0x20, 0x20, 0x20, 0x70};
+const uint8_t two[] = {0x00, 0x60, 0x90, 0x20, 0x40, 0xf0};
+const uint8_t three[] = {0x00, 0x60, 0x90, 0x20, 0x90, 0x60};
+const uint8_t four[] = {0x00, 0x90, 0x90, 0xf0, 0x10, 0x10};
+const uint8_t five[] = {0x00, 0xf0, 0x80, 0xe0, 0x10, 0xe0};
+const uint8_t six[] = {0x00, 0x60, 0x80, 0xe0, 0x90, 0x60};
+const uint8_t seven[] = {0x00, 0xf0, 0x10, 0x10, 0x10, 0x10};
+const uint8_t eight[] = {0x00, 0x60, 0x90, 0x60, 0x90, 0x60};
+const uint8_t nine[] = {0x00, 0x60, 0x90, 0x70, 0x10, 0x60};
+const uint8_t A[] = {0x00, 0x60, 0x90, 0xf0, 0x90, 0x90};
+const uint8_t B[] = {0x00, 0xe0, 0x90, 0xe0, 0x90, 0xe0};
+const uint8_t C[] = {0x00, 0x60, 0x90, 0x80, 0x90, 0x60};
+const uint8_t D[] = {0x00, 0xe0, 0x90, 0x90, 0x90, 0xe0};
+const uint8_t E[] = {0x00, 0xf0, 0x80, 0xe0, 0x80, 0xf0};
+const uint8_t F[] = {0x00, 0xf0, 0x80, 0xe0, 0x80, 0x80};
+const uint8_t G[] = {0x00, 0x60, 0x80, 0x80, 0x90, 0x60};
+const uint8_t H[] = {0x00, 0x90, 0x90, 0xf0, 0x90, 0x90};
+const uint8_t I[] = {0x00, 0x20, 0x20, 0x20, 0x20, 0x20};
+const uint8_t J[] = {0x00, 0x10, 0x10, 0x10, 0x90, 0x60};
+const uint8_t K[] = {0x00, 0x90, 0xa0, 0xc0, 0xa0, 0x90};
+const uint8_t L[] = {0x00, 0x80, 0x80, 0x80, 0x80, 0xf0};
+const uint8_t M[] = {0x00, 0x90, 0xf0, 0x90, 0x90, 0x90};
+const uint8_t N[] = {0x00, 0x90, 0xd0, 0xb0, 0x90, 0x90};
+const uint8_t O[] = {0x00, 0x60, 0x90, 0x90, 0x90, 0x60};
+const uint8_t P[] = {0x00, 0xe0, 0x90, 0xe0, 0x80, 0x80};
+const uint8_t Q[] = {0x00, 0x60, 0x90, 0x90, 0xb0, 0x70};
+const uint8_t R[] = {0x00, 0xe0, 0x90, 0xe0, 0x90, 0x90};
+const uint8_t S[] = {0x00, 0x60, 0x80, 0x60, 0x10, 0xe0};
+const uint8_t T[] = {0x00, 0xe0, 0x40, 0x40, 0x40, 0x40};
+const uint8_t U[] = {0x00, 0x90, 0x90, 0x90, 0x90, 0x60};
+const uint8_t V[] = {0x00, 0x90, 0x90, 0x90, 0x60, 0x60};
+const uint8_t W[] = {0x00, 0x90, 0x90, 0x90, 0xf0, 0x90};
+const uint8_t X[] = {0x00, 0x90, 0x90, 0x60, 0x90, 0x90};
+const uint8_t Y[] = {0x00, 0x90, 0x90, 0x60, 0x60, 0x60};
+const uint8_t Z[] = {0x00, 0xf0, 0x10, 0x60, 0x80, 0xf0};
+const uint8_t dot[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x40};
+const uint8_t comma[] = {0x00, 0x00, 0x00, 0x00, 0x20, 0x40};
+const uint8_t dash[] = {0x00, 0x00, 0x00, 0x60, 0x00, 0x00};
+const uint8_t underscore[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0xf0};
+const uint8_t bracket_open[] = {0x00, 0x20, 0x40, 0x40, 0x40, 0x20};
+const uint8_t bracket_close[] = {0x00, 0x40, 0x20, 0x20, 0x20, 0x40};
+const uint8_t cross_left[] = {0x10, 0x10, 0x70, 0x70, 0x10, 0x10};
+const uint8_t cross_right[] = {0x80, 0x80, 0xe0, 0xe0, 0x80, 0x80};
+const uint8_t pacman_left[] = {0x00, 0x30, 0x50, 0x70, 0x70, 0x00};
+const uint8_t pacman_right[] = {0x00, 0xc0, 0x60, 0xe0, 0xe0, 0xe0};
+const uint8_t box[] = {0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0x00};
+const uint8_t* char_arr[48] = {
+ space,
+ zero,
+ one,
+ two,
+ three,
+ four,
+ five,
+ six,
+ seven,
+ eight,
+ nine,
+ A,
+ B,
+ C,
+ D,
+ E,
+ F,
+ G,
+ H,
+ I,
+ J,
+ K,
+ L,
+ M,
+ N,
+ O,
+ P,
+ Q,
+ R,
+ S,
+ T,
+ U,
+ V,
+ W,
+ X,
+ Y,
+ Z,
+ dot,
+ comma,
+ dash,
+ underscore,
+ bracket_open,
+ bracket_close,
+ cross_left,
+ cross_right,
+ pacman_left,
+ pacman_right,
+ box};
+const uint8_t gradient[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x88, 0x00, 0x00, 0x22, 0x22,
+ 0x00, 0x00, 0x8a, 0x8a, 0x00, 0x00, 0x22, 0x22, 0x00, 0x00, 0xaa, 0xaa,
+ 0x10, 0x10, 0xaa, 0xaa, 0x00, 0x00, 0xaa, 0xaa, 0x01, 0x01, 0xaa, 0xaa,
+ 0x44, 0x44, 0xaa, 0xaa, 0x55, 0x55, 0xaa, 0xaa, 0x44, 0x44, 0xaa, 0xaa,
+ 0x15, 0x55, 0xaa, 0xaa, 0x55, 0x55, 0xaa, 0xaa, 0x55, 0x55, 0xbb, 0xbb,
+ 0x55, 0x55, 0xaa, 0xea, 0x55, 0x55, 0xbb, 0xbb, 0x55, 0x55, 0xff, 0xff,
+ 0x55, 0x55, 0xfb, 0xfb, 0x55, 0x55, 0xff, 0xff, 0x55, 0x55, 0xbb, 0xbf,
+ 0x57, 0x57, 0xff, 0xff, 0xdd, 0xdd, 0xff, 0xff, 0x77, 0x75, 0xff, 0xff,
+ 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+//const uint8_t gun[] = {0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0x27, 0xff, 0xff, 0xfe, 0x3b, 0xff, 0xff, 0xfd, 0xfb, 0xff, 0xff, 0xfd, 0xfd, 0xff, 0xff, 0xfd, 0x15, 0xff, 0xff, 0xfb, 0x2e, 0xff, 0xff, 0xf6, 0x77, 0x7f, 0xff, 0xe6, 0xff, 0xff, 0xff, 0xf2, 0x3d, 0x7f, 0xff, 0xd6, 0x7e, 0x3f, 0xff, 0xf4, 0x5d, 0xdf, 0xff, 0xce, 0xbf, 0xbf, 0xff, 0xdc, 0xff, 0x3f, 0xff, 0xec, 0xff, 0xbf, 0xff, 0x8d, 0xfd, 0xff, 0xff, 0xb6, 0xff, 0xbf, 0xfe, 0x1f, 0x57, 0xdf, 0xf8, 0x0e, 0xff, 0xcf, 0xf4, 0x46, 0x1f, 0x17, 0xf8, 0xa3, 0xfc, 0x03, 0xf8, 0x10, 0x00, 0x11, 0xf8, 0x8a, 0x80, 0x2d, 0xe4, 0x44, 0x00, 0x4d, 0xee, 0xa8, 0x82, 0x9b, 0xcd, 0x50, 0x00, 0x17, 0xec, 0xa0, 0x8a, 0x2f, 0xcc, 0x00, 0x04, 0x67, 0xe8, 0x28, 0x1a, 0xff, 0xe4, 0x70, 0x4d, 0xcf, 0xfc, 0x82, 0xa7, 0xef, 0x90, 0x40, 0x13, 0xdf};
+// const uint8_t gun_mask[] = {0xff, 0xff, 0x8f, 0xff, 0xff, 0xfe, 0x03, 0xff, 0xff, 0xfc, 0x01, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xc0, 0x00, 0x3f, 0xff, 0x80, 0x00, 0x1f, 0xff, 0x80, 0x00, 0x0f, 0xff, 0x80, 0x00, 0x1f, 0xff, 0x80, 0x00, 0x1f, 0xff, 0x80, 0x00, 0x1f, 0xff, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x1f, 0xfc, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0f};
+const uint8_t gun[] = {0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x01, 0xc4, 0x00,
+ 0x00, 0x02, 0x04, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x02, 0xea, 0x00,
+ 0x00, 0x04, 0xd1, 0x00, 0x00, 0x09, 0x88, 0x80, 0x00, 0x19, 0x00, 0x00,
+ 0x00, 0x0d, 0xc2, 0x80, 0x00, 0x29, 0x81, 0xc0, 0x00, 0x0b, 0xa2, 0x20,
+ 0x00, 0x31, 0x40, 0x40, 0x00, 0x23, 0x00, 0xc0, 0x00, 0x13, 0x00, 0x40,
+ 0x00, 0x72, 0x02, 0x00, 0x00, 0x49, 0x00, 0x40, 0x01, 0xe0, 0xa8, 0x20,
+ 0x07, 0xf1, 0x00, 0x30, 0x0b, 0xb9, 0xe0, 0xe8, 0x07, 0x5c, 0x03, 0xfc,
+ 0x07, 0xef, 0xff, 0xee, 0x07, 0x75, 0x7f, 0xd2, 0x1b, 0xbb, 0xff, 0xb2,
+ 0x11, 0x57, 0x7d, 0x64, 0x32, 0xaf, 0xff, 0xe8, 0x13, 0x5f, 0x75, 0xd0,
+ 0x33, 0xff, 0xfb, 0x98, 0x17, 0xd7, 0xe5, 0x00, 0x1b, 0x8f, 0xb2, 0x30,
+ 0x03, 0x7d, 0x58, 0x10, 0x6f, 0xbf, 0xec, 0x20};
+const uint8_t gun_mask[] = {0x00, 0x00, 0x70, 0x00, 0x00, 0x01, 0xfc, 0x00, 0x00, 0x03, 0xfe, 0x00,
+ 0x00, 0x07, 0xfe, 0x00, 0x00, 0x07, 0xff, 0x00, 0x00, 0x07, 0xff, 0x00,
+ 0x00, 0x0f, 0xff, 0x80, 0x00, 0x1f, 0xff, 0xc0, 0x00, 0x3f, 0xff, 0x80,
+ 0x00, 0x3f, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xf0,
+ 0x00, 0x7f, 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xe0,
+ 0x00, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xf0,
+ 0x0f, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xfc, 0x1f, 0xff, 0xff, 0xfe,
+ 0x1f, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff,
+ 0x3f, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xf8,
+ 0x7f, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xf8,
+ 0x7f, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xf0};
+
+const uint8_t
+ imp_inv[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00,
+ 0x02, 0x80, 0x00, 0x00, 0x07, 0x40, 0x00, 0x00, 0x02, 0x80, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x01, 0x0f, 0xb3, 0x00, 0x00, 0xd0, 0x4e, 0x00, 0x00, 0x79, 0x8c,
+ 0x00, 0x00, 0x1c, 0x19, 0x00, 0x01, 0x8a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0x02, 0x08, 0x00, 0x80, 0x00, 0x00, 0x01, 0x00, 0x01, 0x8e,
+ 0x30, 0x00, 0x00, 0x04, 0x10, 0x00, 0x00, 0x0c, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x06, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x20, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x03, 0xe0,
+ 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0xa1, 0x80, 0x01, 0x80, 0x13, 0x00,
+ 0x00, 0xf3, 0x8a, 0x00, 0x00, 0x09, 0x94, 0x00, 0x00, 0x88, 0x38, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x23, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x01, 0x00, 0x00, 0xe2, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x0c, 0x20, 0x00, 0x00, 0x04, 0x30, 0x00, 0x00, 0x02, 0x20, 0x00,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x20, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0,
+ 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00,
+ 0x00, 0x1f, 0x00, 0x00, 0x02, 0x2a, 0x80, 0x00, 0x01, 0x05, 0x00, 0x00, 0x01,
+ 0xae, 0x20, 0x00, 0x01, 0x24, 0x40, 0x00, 0x02, 0xac, 0x80, 0x00, 0x02, 0x86,
+ 0x00, 0x00, 0x03, 0x20, 0x20, 0x00, 0x04, 0x30, 0x40, 0x00, 0x0c, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x08, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x1a, 0x00, 0x00, 0x00,
+ 0x1c, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x98,
+ 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00,
+ 0x00, 0x00, 0x08, 0x40, 0x00, 0x00, 0x00, 0x80, 0x00, 0x01, 0xd6, 0x80, 0x00,
+ 0x02, 0xbf, 0x80, 0x00, 0x06, 0x61, 0xa0, 0x00, 0x0c, 0xe8, 0x80, 0x00, 0x0c,
+ 0x10, 0x00, 0x00, 0x1a, 0x22, 0x00, 0x00, 0x12, 0x40, 0x00, 0x00, 0x06, 0x0c,
+ 0x00, 0x00, 0x04, 0x0d, 0x00, 0x00, 0x3a, 0x03, 0x00, 0x00, 0x10, 0x02, 0x00,
+ 0x00, 0x60, 0x0a, 0x00, 0x00, 0x50, 0x04, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00,
+ 0x00, 0x04, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x24, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x01,
+ 0x18, 0x00, 0x00, 0x01, 0x41, 0x40, 0x02, 0x33, 0xb6, 0x80, 0x01, 0x9c, 0x04,
+ 0x00, 0x08, 0xfa, 0x02, 0x08, 0x05, 0x00, 0x01, 0x0c, 0x27, 0x83, 0xa2, 0x2a,
+ 0x00, 0x04, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00}; //{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x02, 0x80, 0x00, 0x00, 0x07, 0x40, 0x00, 0x00, 0x02, 0x80, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x0f, 0xb3, 0x00, 0x00, 0xd0, 0x4e, 0x00, 0x00, 0x79, 0x8c, 0x00, 0x00, 0x1c, 0x19, 0x00, 0x01, 0x8a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x02, 0x08, 0x00, 0x80, 0x00, 0x00, 0x01, 0x00, 0x01, 0x8e, 0x30, 0x00, 0x00, 0x04, 0x10, 0x00, 0x00, 0x0c, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x20, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+const uint8_t imp_mask_inv[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x03, 0xc0, 0x00,
+ 0x00, 0x07, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x01, 0x07, 0xf1, 0x80,
+ 0x00, 0xdf, 0xfe, 0x00, 0x00, 0x3f, 0xfe, 0x00, 0x00, 0x7f, 0xff, 0x00, 0x01, 0xff, 0xff, 0x80,
+ 0x00, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0x80, 0x03, 0xcf, 0xf1, 0xc0, 0x01, 0xc7, 0xf1, 0xc0,
+ 0x01, 0x87, 0xf1, 0xc0, 0x03, 0x0f, 0xf9, 0x80, 0x03, 0x0f, 0xfb, 0x80, 0x01, 0x8f, 0xff, 0x80,
+ 0x03, 0x9f, 0x79, 0x00, 0x00, 0x1f, 0x7c, 0x00, 0x00, 0x0f, 0x78, 0x00, 0x00, 0x0f, 0x78, 0x00,
+ 0x00, 0x07, 0x30, 0x00, 0x00, 0x07, 0x38, 0x00, 0x00, 0x07, 0x30, 0x00, 0x00, 0x07, 0x30, 0x00,
+ 0x00, 0x03, 0x78, 0x00, 0x00, 0x07, 0x30, 0x00, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x07, 0xe0, 0x00,
+ 0x00, 0x07, 0xc0, 0x00, 0x01, 0x07, 0xe1, 0x00, 0x00, 0x8f, 0xfa, 0x00, 0x00, 0xff, 0xfe, 0x00,
+ 0x00, 0x3f, 0xfe, 0x00, 0x01, 0x7f, 0xff, 0x80, 0x00, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0x80,
+ 0x03, 0xcf, 0xfb, 0xc0, 0x03, 0x87, 0xf1, 0xc0, 0x03, 0xcf, 0xf3, 0xc0, 0x01, 0xcf, 0xf1, 0x80,
+ 0x00, 0xcf, 0xf1, 0x00, 0x00, 0x0f, 0xfb, 0x80, 0x00, 0x1e, 0x78, 0x00, 0x00, 0x0e, 0x78, 0x00,
+ 0x00, 0x1e, 0x78, 0x00, 0x00, 0x0f, 0x70, 0x00, 0x00, 0x0f, 0x78, 0x00, 0x00, 0x07, 0x70, 0x00,
+ 0x00, 0x07, 0x70, 0x00, 0x00, 0x07, 0x38, 0x00, 0x00, 0x03, 0x30, 0x00, 0x00, 0x03, 0x20, 0x00,
+ 0x00, 0x07, 0x30, 0x00, 0x00, 0x05, 0x70, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x1f, 0x00,
+ 0x00, 0x00, 0x1f, 0x00, 0x00, 0x03, 0x3f, 0x80, 0x00, 0x01, 0x3f, 0x00, 0x00, 0x01, 0xff, 0x30,
+ 0x00, 0x03, 0xff, 0xc0, 0x00, 0x03, 0xff, 0xc0, 0x00, 0x03, 0xff, 0x80, 0x00, 0x07, 0xff, 0xe0,
+ 0x00, 0x07, 0xff, 0xc0, 0x00, 0x05, 0xff, 0xe0, 0x00, 0x00, 0xfc, 0xe0, 0x00, 0x01, 0xfc, 0xe0,
+ 0x00, 0x01, 0xfc, 0x70, 0x00, 0x03, 0xfc, 0x38, 0x00, 0x03, 0xfe, 0x70, 0x00, 0x07, 0xfc, 0x00,
+ 0x00, 0x07, 0x9e, 0x00, 0x00, 0x0f, 0xbc, 0x00, 0x00, 0x0f, 0x3e, 0x00, 0x00, 0x07, 0x9c, 0x00,
+ 0x00, 0x03, 0x9c, 0x00, 0x00, 0x03, 0xb8, 0x00, 0x00, 0x03, 0x98, 0x00, 0x00, 0x01, 0x98, 0x00,
+ 0x00, 0x02, 0x1c, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x1f, 0x00,
+ 0x00, 0x00, 0x1f, 0x40, 0x00, 0x00, 0x3e, 0x80, 0x00, 0x01, 0xff, 0x80, 0x00, 0x03, 0xff, 0x80,
+ 0x00, 0x07, 0xff, 0xe0, 0x00, 0x0e, 0xff, 0xc0, 0x00, 0x0c, 0xff, 0x80, 0x00, 0x1f, 0xfe, 0x00,
+ 0x00, 0x13, 0xfc, 0x00, 0x00, 0x07, 0xfe, 0x00, 0x00, 0x1f, 0xff, 0x00, 0x00, 0x3f, 0x9f, 0x00,
+ 0x00, 0x3e, 0x0f, 0x00, 0x00, 0x7c, 0x0f, 0x00, 0x00, 0x78, 0x0f, 0x00, 0x00, 0x78, 0x07, 0x80,
+ 0x00, 0x78, 0x07, 0x40, 0x00, 0x38, 0x07, 0x80, 0x00, 0x30, 0x07, 0x00, 0x00, 0x30, 0x01, 0x00,
+ 0x01, 0xf0, 0x00, 0x00, 0x01, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x1c, 0x00,
+ 0x00, 0x01, 0x3e, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x0f, 0xff, 0xe0, 0x01, 0x3f, 0xff, 0xc0,
+ 0x01, 0xff, 0xff, 0xc0, 0x19, 0xff, 0xff, 0xe8, 0x7f, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xfe,
+ 0x1f, 0xc2, 0x07, 0xe0, 0x1f, 0x00, 0x01, 0xe0, 0x0e, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00,
+}; //{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x01, 0x07, 0xf1, 0x80, 0x00, 0xdf, 0xfe, 0x00, 0x00, 0x3f, 0xfe, 0x00, 0x00, 0x7f, 0xff, 0x00, 0x01, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0x80, 0x03, 0xcf, 0xf1, 0xc0, 0x01, 0xc7, 0xf1, 0xc0, 0x01, 0x87, 0xf1, 0xc0, 0x03, 0x0f, 0xf9, 0x80, 0x03, 0x0f, 0xfb, 0x80, 0x01, 0x8f, 0xff, 0x80, 0x03, 0x9f, 0x79, 0x00, 0x00, 0x1f, 0x7c, 0x00, 0x00, 0x0f, 0x78, 0x00, 0x00, 0x0f, 0x78, 0x00, 0x00, 0x07, 0x30, 0x00, 0x00, 0x07, 0x38, 0x00, 0x00, 0x07, 0x30, 0x00, 0x00, 0x07, 0x30, 0x00, 0x00, 0x03, 0x78, 0x00, 0x00, 0x07, 0x30, 0x00, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00};
+const uint8_t fireball[] = {0x00, 0x00, 0x01, 0x40, 0x0a, 0xb0, 0x0e, 0xd0, 0x00, 0x68, 0x53,
+ 0xb4, 0x0f, 0x48, 0x27, 0x78, 0x17, 0xa8, 0x27, 0xf0, 0x21, 0xd6,
+ 0x02, 0xf8, 0x20, 0x48, 0x06, 0x20, 0x01, 0x00, 0x00, 0x00};
+const uint8_t fireball_mask[] = {0x1f, 0x40, 0x0f, 0xf0, 0x3f, 0xf8, 0x1f, 0xfc, 0x7f, 0xfd, 0x7f,
+ 0xfc, 0x7f, 0xfd, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
+ 0xff, 0xfe, 0x3f, 0xfe, 0x17, 0xf8, 0x07, 0xf4, 0x01, 0xe0};
+const uint8_t item[] = {0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, 0x7f, 0xfe, 0x77, 0xee, 0x3f,
+ 0xfc, 0x5f, 0xfa, 0x2f, 0xf6, 0x53, 0xcc, 0x3e, 0x7e, 0x5e, 0x7c,
+ 0x38, 0x1e, 0x58, 0x1c, 0x3e, 0x7e, 0x5e, 0x7e, 0x2e, 0xfc, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xfc,
+ 0x17, 0xfc, 0x22, 0x6c, 0x36, 0x44, 0x3f, 0xfc, 0x1f, 0xfc, 0x2b,
+ 0xfc, 0x05, 0x54, 0x02, 0xa8, 0x00, 0x00, 0x00, 0x00};
+const uint8_t item_mask[] = {0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f,
+ 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe,
+ 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x3f, 0xfc, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xfc,
+ 0x1f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f,
+ 0xfc, 0x07, 0xfc, 0x03, 0xf8, 0x00, 0x00, 0x00, 0x00};
+
+//const uint8_t door[] = {0xff, 0xff, 0xff, 0xff,0xb2, 0xbd, 0xcd, 0x5b,0x9a, 0xf4, 0x6d, 0x71,0xff, 0xff, 0xff, 0xff,0x00, 0x00, 0x00, 0x00,0xbf, 0xff, 0xff, 0xfd,0x3f, 0x00, 0xfe, 0xfc,0x3e, 0x00, 0xc6, 0xfc,0xbc, 0xaa, 0xfe, 0xbd,0x39, 0x54, 0xc6, 0xbc,0x32, 0x8e, 0xfe, 0xac,0xb5, 0xfe, 0xc6, 0xad,0x3f, 0xe0, 0xfe, 0xac,0x31, 0xe0, 0xc6, 0xac,0xb3, 0xf4, 0xfe, 0xad,0x3f, 0xe8, 0xc6, 0xac,0x3c, 0xf4, 0xd6, 0xac,0xb8, 0xff, 0xfe, 0xad,0x34, 0xc7, 0xfe, 0xfc,0x38, 0xd6, 0x0e, 0x0c,0xb0, 0xd6, 0x4e, 0x0d,0x3f, 0xd6, 0xaf, 0x5c,0x30, 0x47, 0xff, 0xac,0xb7, 0x57, 0xff, 0xfd,0x3f, 0xc6, 0x0e, 0x0c,0x35, 0x56, 0x40, 0x4c,0xb5, 0x46, 0xaa, 0xad,0x35, 0x56, 0x55, 0x4c,0xff, 0xff, 0xff, 0xff,0xb0, 0x1f, 0xf8, 0x0d,0xd9, 0x30, 0x0c, 0x9b,0xff, 0xe0, 0x07, 0xff};
+const uint8_t door[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x07, 0xe1, 0x8c, 0x00, 0x04,
+ 0x00, 0x7c, 0x03, 0x18, 0x60, 0x08, 0x00, 0x3e, 0x0f, 0xf7, 0xdf, 0x00, 0x1f, 0x00, 0xfe, 0x0f,
+ 0xbe, 0xf8, 0x3e, 0x00, 0x3f, 0x1f, 0xff, 0xdf, 0x00, 0x1f, 0x81, 0xff, 0x0f, 0xff, 0xf8, 0x7e,
+ 0x00, 0x3f, 0x8f, 0xff, 0xdf, 0x00, 0xff, 0xf9, 0xff, 0x1f, 0xff, 0xf8, 0xff, 0x80, 0x3f, 0xc7,
+ 0xff, 0xcc, 0x07, 0xff, 0xfc, 0xff, 0x1f, 0xff, 0xe3, 0xff, 0x80, 0x3f, 0xc7, 0xff, 0xc0, 0x07,
+ 0xff, 0xfc, 0x7f, 0x0f, 0xfe, 0x03, 0xff, 0xc0, 0x3f, 0xc3, 0xf7, 0xc0, 0x07, 0xdf, 0xf8, 0x3e,
+ 0x0f, 0xbe, 0x01, 0xff, 0x80, 0x1f, 0x80, 0xe3, 0x80, 0x07, 0x8f, 0xf8, 0x1e, 0x07, 0x1c, 0x01,
+ 0xff, 0x80, 0x3f, 0xc1, 0xff, 0xc0, 0x0f, 0xff, 0xfc, 0x3f, 0x0f, 0xbe, 0x03, 0xff, 0xc0, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0x80, 0x00,
+ 0x7f, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xe0, 0x00, 0x1f, 0xf0, 0xff, 0x00, 0x00, 0x7f, 0xff, 0xff,
+ 0xc0, 0x00, 0x00, 0xe0, 0x00, 0x0f, 0xf0, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x01,
+ 0xe0, 0x00, 0x0f, 0xf0, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x01, 0xf0, 0x00, 0x0f,
+ 0xf0, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0x81, 0xf0, 0x00, 0x0f, 0xf0, 0xff, 0x00,
+ 0x07, 0xff, 0xff, 0xff, 0xe0, 0xff, 0xc1, 0xf0, 0x00, 0x0f, 0xf0, 0xff, 0x00, 0x0f, 0xff, 0xff,
+ 0xff, 0xe1, 0xff, 0xe1, 0xf0, 0x00, 0x0f, 0xf0, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xe0, 0xff,
+ 0xc1, 0xf3, 0x00, 0x0f, 0xf0, 0xff, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xe0, 0xff, 0x81, 0xff, 0xc0,
+ 0x0f, 0xf0, 0xff, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xe0, 0x3f, 0x01, 0xff, 0xc0, 0x0f, 0xf0, 0xff,
+ 0x00, 0xff, 0xff, 0xff, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xe0, 0x0f, 0xf0, 0xff, 0x01, 0xff, 0xff,
+ 0xff, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xe0, 0x0f, 0xf0, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xe1,
+ 0xff, 0xe1, 0xff, 0xe0, 0x0f, 0xf0, 0xff, 0x07, 0xff, 0xff, 0xff, 0xff, 0xe0, 0xff, 0xc1, 0xff,
+ 0xf0, 0x0f, 0xf0, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x8f, 0xe0, 0xff, 0x81, 0xff, 0xff, 0x0f, 0xf0,
+ 0xff, 0x1f, 0xff, 0xff, 0xfe, 0x07, 0xe0, 0x7f, 0x81, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff,
+ 0xff, 0xfc, 0x07, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff, 0x8f, 0xfc, 0x03,
+ 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff, 0x07, 0xfc, 0x07, 0xe0, 0xff, 0xc1,
+ 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xfe, 0x00, 0x7f, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f,
+ 0xf0, 0xff, 0x0f, 0x9c, 0x00, 0x3f, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x07,
+ 0xfc, 0x00, 0x7f, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x07, 0xfc, 0x00, 0x7f,
+ 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x0f, 0xfc, 0x00, 0x7f, 0xff, 0xe0, 0xff,
+ 0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x0f, 0xfc, 0x00, 0x7f, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff,
+ 0x8f, 0xf0, 0xff, 0x1f, 0xfe, 0x00, 0x7f, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff,
+ 0x1f, 0xfc, 0x00, 0x3f, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xfc, 0x00,
+ 0x3f, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xf0, 0x00, 0x1f, 0xff, 0xe0,
+ 0x7f, 0x81, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x0f, 0xf0, 0x00, 0x1f, 0xff, 0xe0, 0xff, 0x81, 0xff,
+ 0xff, 0x8f, 0xf0, 0xff, 0x07, 0xe0, 0x00, 0x3f, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0,
+ 0xff, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xe1, 0xff, 0xe1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x00, 0x1f,
+ 0x80, 0x3f, 0xff, 0xe1, 0xff, 0xe1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x00, 0x3f, 0xc0, 0x1f, 0xff,
+ 0xe1, 0xff, 0xe1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x00, 0x7f, 0xc0, 0x0f, 0xff, 0xe1, 0xff, 0xe1,
+ 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x00, 0xff, 0xc0, 0x07, 0xff, 0xe1, 0xff, 0xe1, 0xff, 0xff, 0x8f,
+ 0xf0, 0xff, 0x01, 0xff, 0xc0, 0x03, 0x8f, 0xc0, 0xc1, 0x81, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x03,
+ 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x07, 0xff, 0xc0, 0xff,
+ 0x80, 0x00, 0x00, 0x01, 0xff, 0xff, 0x0f, 0xf0, 0xff, 0x0f, 0xff, 0xc1, 0xff, 0x80, 0x00, 0x00,
+ 0x01, 0xf3, 0x8e, 0x0f, 0xf0, 0xff, 0x0f, 0xff, 0xc3, 0xff, 0xc0, 0x00, 0x00, 0x01, 0xff, 0x9c,
+ 0x0f, 0xf0, 0xff, 0x0f, 0xff, 0xc3, 0xff, 0xc0, 0xff, 0xfc, 0x01, 0xff, 0xfe, 0x0f, 0xf0, 0xff,
+ 0x0f, 0xff, 0xc3, 0xff, 0xc1, 0xff, 0xfe, 0x01, 0xff, 0xff, 0x0f, 0xf0, 0xff, 0x07, 0xff, 0xc3,
+ 0xff, 0xc3, 0xff, 0xff, 0x01, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x0f, 0xff, 0xc3, 0xff, 0xc3, 0xff,
+ 0xff, 0x01, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x0f, 0xff, 0xc3, 0xff, 0xc3, 0xff, 0xff, 0x01, 0xff,
+ 0xff, 0x8f, 0xf0, 0xff, 0x0f, 0xff, 0xc3, 0xff, 0xc3, 0xff, 0xff, 0x01, 0xff, 0xff, 0x8f, 0xf0,
+ 0xff, 0x0f, 0xff, 0xc3, 0xff, 0xc3, 0xff, 0xff, 0x01, 0xff, 0xff, 0x0f, 0xf0, 0xff, 0x0f, 0xff,
+ 0xc3, 0xff, 0xc3, 0xff, 0xff, 0x00, 0xff, 0xff, 0x0f, 0xf0, 0xff, 0x0f, 0xff, 0xc3, 0xff, 0xc3,
+ 0xff, 0xff, 0x00, 0xff, 0xff, 0x0f, 0xf0, 0xff, 0x07, 0xff, 0xe3, 0xff, 0xc3, 0xff, 0xff, 0x00,
+ 0x3f, 0xfe, 0x0f, 0xf0, 0xff, 0x07, 0xff, 0xf3, 0xff, 0xc1, 0xef, 0xfe, 0x00, 0x3f, 0xfe, 0x0f,
+ 0xf0, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xc0, 0x82, 0x00, 0x00, 0x1f, 0xff, 0x0f, 0xf0, 0xff, 0x1f,
+ 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x07, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff, 0xff, 0xff,
+ 0xc0, 0x00, 0x00, 0x00, 0x07, 0xff, 0x0f, 0xf0, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00,
+ 0x00, 0x03, 0x8e, 0x0f, 0xf0, 0xff, 0x1f, 0xc1, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x03, 0x88,
+ 0x0f, 0xf0, 0xff, 0x0f, 0x80, 0xff, 0xff, 0xc1, 0xff, 0xfc, 0x00, 0xff, 0xfe, 0x0f, 0xf0, 0xff,
+ 0x06, 0x00, 0x73, 0xff, 0xc3, 0xff, 0xfe, 0x01, 0xff, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0x00, 0x03,
+ 0xff, 0xc3, 0xff, 0xff, 0x83, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x07, 0x0c, 0x73, 0xff, 0xc3, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x0f, 0xfe, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0x8f, 0xf0,
+ 0xff, 0x1f, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff,
+ 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xc3,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0x8f,
+ 0xf0, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0x87, 0xf0, 0xfe, 0x1f,
+ 0xfe, 0xfb, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xf0, 0xfc, 0x0f, 0x9e, 0x73, 0xff,
+ 0x81, 0xf9, 0xf7, 0xe7, 0x9c, 0xff, 0x03, 0xf0, 0xfc, 0x07, 0xfe, 0xfb, 0xc0, 0x00, 0xf0, 0x00,
+ 0x6f, 0xbe, 0xfe, 0x03, 0xf0, 0x3c, 0x07, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xfe,
+ 0x03, 0xc0, 0x1c, 0x0f, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0x03, 0x80, 0x1e,
+ 0x0f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0x07, 0x80, 0x3f, 0x0f, 0xff, 0xff,
+ 0xe0, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0x0f, 0xc0, 0x1f, 0x8f, 0xff, 0xff, 0xe7, 0xff, 0xff,
+ 0xfe, 0x7f, 0xff, 0xff, 0x1f, 0x80, 0x1f, 0xc7, 0xff, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xfe, 0x3f, 0x80, 0x07, 0xc3, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xfc, 0x3e, 0x00,
+ 0x07, 0xc1, 0xfe, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xcf, 0xf7, 0xf8, 0x3e, 0x00, 0x01, 0x00, 0xfc,
+ 0x7e, 0x7f, 0xff, 0xff, 0xff, 0xe7, 0xe3, 0xf0, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff,
+ 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xe0,
+ 0x00, 0x00, 0x00, 0x00};
\ No newline at end of file
diff --git a/applications/plugins/doom/compiled/assets_icons.h b/applications/plugins/doom/compiled/assets_icons.h
new file mode 100644
index 000000000..024aceab1
--- /dev/null
+++ b/applications/plugins/doom/compiled/assets_icons.h
@@ -0,0 +1,114 @@
+#pragma once
+#include
+
+#ifndef _sprites_h
+#define _sprites_h
+
+#define bmp_font_width 24 // in bytes
+#define bmp_font_height 6
+#define bmp_font_width_pxs 192
+#define bmp_font_height_pxs 48
+#define CHAR_MAP " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.,-_(){}[]#"
+#define CHAR_WIDTH 4
+#define CHAR_HEIGHT 6
+
+#define BMP_GUN_WIDTH 32
+#define BMP_GUN_HEIGHT 32
+
+#define BMP_FIRE_WIDTH 24
+#define BMP_FIRE_HEIGHT 20
+
+#define BMP_IMP_WIDTH 32
+#define BMP_IMP_HEIGHT 32
+#define BMP_IMP_COUNT 5
+
+#define BMP_FIREBALL_WIDTH 16
+#define BMP_FIREBALL_HEIGHT 16
+
+#define BMP_DOOR_WIDTH 100
+#define BMP_DOOR_HEIGHT 100
+
+#define BMP_ITEMS_WIDTH 16
+#define BMP_ITEMS_HEIGHT 16
+#define BMP_ITEMS_COUNT 2
+
+#define BMP_LOGO_WIDTH 72
+#define BMP_LOGO_HEIGHT 47
+
+#define GRADIENT_WIDTH 2
+#define GRADIENT_HEIGHT 8
+#define GRADIENT_COUNT 8
+#define GRADIENT_WHITE 7
+#define GRADIENT_BLACK 0
+
+// Inverted icons
+extern const Icon I_fire_inv;
+extern const Icon I_gun_inv;
+extern const Icon I_gun_mask_inv;
+extern const Icon I_logo_inv;
+
+// Fonts
+extern const uint8_t zero[];
+extern const uint8_t one[];
+extern const uint8_t two[];
+extern const uint8_t three[];
+extern const uint8_t four[];
+extern const uint8_t five[];
+extern const uint8_t six[];
+extern const uint8_t seven[];
+extern const uint8_t eight[];
+extern const uint8_t nine[];
+extern const uint8_t A[];
+extern const uint8_t B[];
+extern const uint8_t C[];
+extern const uint8_t D[];
+extern const uint8_t E[];
+extern const uint8_t F[];
+extern const uint8_t G[];
+extern const uint8_t H[];
+extern const uint8_t I[];
+extern const uint8_t J[];
+extern const uint8_t K[];
+extern const uint8_t L[];
+extern const uint8_t M[];
+extern const uint8_t N[];
+extern const uint8_t O[];
+extern const uint8_t P[];
+extern const uint8_t Q[];
+extern const uint8_t R[];
+extern const uint8_t S[];
+extern const uint8_t T[];
+extern const uint8_t U[];
+extern const uint8_t V[];
+extern const uint8_t W[];
+extern const uint8_t X[];
+extern const uint8_t Y[];
+extern const uint8_t Z[];
+extern const uint8_t dot[];
+extern const uint8_t comma[];
+extern const uint8_t dash[];
+extern const uint8_t underscore[];
+extern const uint8_t bracket_open[];
+extern const uint8_t bracket_close[];
+extern const uint8_t cross_left[];
+extern const uint8_t cross_right[];
+extern const uint8_t pacman_left[];
+extern const uint8_t pacman_right[];
+extern const uint8_t box[];
+extern const uint8_t* char_arr[48];
+extern const uint8_t gradient[];
+//extern const uint8_t gun[]
+//extern const uint8_t gun_mask[]
+extern const uint8_t gun[];
+extern const uint8_t gun_mask[];
+
+extern const uint8_t imp_inv[];
+extern const uint8_t imp_mask_inv[];
+extern const uint8_t fireball[];
+extern const uint8_t fireball_mask[];
+extern const uint8_t item[];
+extern const uint8_t item_mask[];
+
+extern const uint8_t door[];
+
+#endif
diff --git a/applications/plugins/doom/constants.h b/applications/plugins/doom/constants.h
new file mode 100644
index 000000000..4e0f10118
--- /dev/null
+++ b/applications/plugins/doom/constants.h
@@ -0,0 +1,91 @@
+#ifndef _constants_h
+#define _constants_h
+#define PB_CONSTEXPR constexpr
+
+#define PI 3.14159265358979323846
+
+// Key pinout
+#define USE_INPUT_PULLUP
+#define K_LEFT 6
+#define K_RIGHT 7
+#define K_UP 8
+#define K_DOWN 3
+#define K_FIRE 10
+
+// SNES Controller
+// uncomment following line to enable snes controller support
+// #define SNES_CONTROLLER
+const uint8_t DATA_CLOCK = 11;
+const uint8_t DATA_LATCH = 12;
+const uint8_t DATA_SERIAL = 13;
+
+// Sound
+const uint8_t SOUND_PIN = 9; // do not change, belongs to used timer
+
+// GFX settings
+#define OPTIMIZE_SSD1306 // Optimizations for SSD1366 displays
+
+#define FRAME_TIME 66.666666 // Desired time per frame in ms (66.666666 is ~15 fps)
+#define RES_DIVIDER 2
+
+/* Higher values will result in lower horizontal resolution when rasterize and lower process and memory usage
+ Lower will require more process and memory, but looks nicer
+ */
+#define Z_RES_DIVIDER 2 // Zbuffer resolution divider. We sacrifice resolution to save memory
+#define DISTANCE_MULTIPLIER 20
+
+/* Distances are stored as uint8_t, multiplying the distance we can obtain more precision taking care
+ of keep numbers inside the type range. Max is 256 / MAX_RENDER_DEPTH
+ */
+
+#define MAX_RENDER_DEPTH 12
+#define MAX_SPRITE_DEPTH 8
+
+#define ZBUFFER_SIZE SCREEN_WIDTH / Z_RES_DIVIDER
+
+// Level
+#define LEVEL_WIDTH_BASE 6
+#define LEVEL_WIDTH (1 << LEVEL_WIDTH_BASE)
+#define LEVEL_HEIGHT 57
+#define LEVEL_SIZE LEVEL_WIDTH / 2 * LEVEL_HEIGHT
+
+// scenes
+#define INTRO 0
+#define GAME_PLAY 1
+
+// Game
+#define GUN_TARGET_POS 18
+#define GUN_SHOT_POS GUN_TARGET_POS + 4
+
+#define ROT_SPEED .12
+#define MOV_SPEED .2
+#define MOV_SPEED_INV 5 // 1 / MOV_SPEED
+
+#define JOGGING_SPEED .005
+#define ENEMY_SPEED .02
+#define FIREBALL_SPEED .2
+#define FIREBALL_ANGLES 45 // Num of angles per PI
+
+#define MAX_ENTITIES 10 // Max num of active entities
+#define MAX_STATIC_ENTITIES 28 // Max num of entities in sleep mode
+
+#define MAX_ENTITY_DISTANCE 200 // * DISTANCE_MULTIPLIER
+#define MAX_ENEMY_VIEW 80 // * DISTANCE_MULTIPLIER
+#define ITEM_COLLIDER_DIST 6 // * DISTANCE_MULTIPLIER
+#define ENEMY_COLLIDER_DIST 4 // * DISTANCE_MULTIPLIER
+#define FIREBALL_COLLIDER_DIST 2 // * DISTANCE_MULTIPLIER
+#define ENEMY_MELEE_DIST 6 // * DISTANCE_MULTIPLIER
+#define WALL_COLLIDER_DIST .2
+
+#define ENEMY_MELEE_DAMAGE 8
+#define ENEMY_FIREBALL_DAMAGE 20
+#define GUN_MAX_DAMAGE 20
+
+// display
+const uint8_t SCREEN_WIDTH = 128;
+const uint8_t SCREEN_HEIGHT = 64;
+const uint8_t HALF_WIDTH = SCREEN_WIDTH / 2;
+const uint8_t RENDER_HEIGHT = 56; // raycaster working height (the rest is for the hud)
+const uint8_t HALF_HEIGHT = SCREEN_HEIGHT / 2;
+
+#endif
diff --git a/applications/plugins/doom/display.h b/applications/plugins/doom/display.h
new file mode 100644
index 000000000..cc9052acc
--- /dev/null
+++ b/applications/plugins/doom/display.h
@@ -0,0 +1,282 @@
+#include
+#include
+#include
+#include
+#include "constants.h"
+#include "compiled/assets_icons.h"
+
+#define CHECK_BIT(var, pos) ((var) & (1 << (pos)))
+
+static const uint8_t bit_mask[8] = {128, 64, 32, 16, 8, 4, 2, 1};
+
+#define pgm_read_byte(addr) (*(const unsigned char*)(addr))
+#define read_bit(b, n) b& pgm_read_byte(bit_mask + n) ? 1 : 0
+//#define read_bit(byte, index) (((unsigned)(byte) >> (index)) & 1)
+
+void drawVLine(uint8_t x, int8_t start_y, int8_t end_y, uint8_t intensity, Canvas* const canvas);
+void drawPixel(int8_t x, int8_t y, bool color, bool raycasterViewport, Canvas* const canvas);
+void drawSprite(
+ int8_t x,
+ int8_t y,
+ const uint8_t* bitmap,
+ const uint8_t* bitmap_mask,
+ int16_t w,
+ int16_t h,
+ uint8_t sprite,
+ double distance,
+ Canvas* const canvas);
+void drawBitmap(
+ int16_t x,
+ int16_t y,
+ const Icon* i,
+ int16_t w,
+ int16_t h,
+ uint16_t color,
+ Canvas* const canvas);
+void drawTextSpace(int8_t x, int8_t y, char* txt, uint8_t space, Canvas* const canvas);
+void drawChar(int8_t x, int8_t y, char ch, Canvas* const canvas);
+void clearRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, Canvas* const canvas);
+void drawGun(
+ int16_t x,
+ int16_t y,
+ const uint8_t* bitmap,
+ int16_t w,
+ int16_t h,
+ uint16_t color,
+ Canvas* const canvas);
+void drawRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, Canvas* const canvas);
+void drawText(uint8_t x, uint8_t y, uint8_t num, Canvas* const canvas);
+void fadeScreen(uint8_t intensity, bool color, Canvas* const canvas);
+bool getGradientPixel(uint8_t x, uint8_t y, uint8_t i);
+double getActualFps();
+void fps();
+void setupDisplay(Canvas* canvas);
+uint8_t reverse_bits(uint8_t num);
+
+// FPS control
+double delta = 1;
+uint32_t lastFrameTime = 0;
+uint8_t zbuffer[128]; /// 128 = screen width & REMOVE WHEN DISPLAY.H IMPLEMENTED
+uint8_t* display_buf = NULL;
+
+void drawGun(
+ int16_t x,
+ int16_t y,
+ const uint8_t* bitmap,
+ int16_t w,
+ int16_t h,
+ uint16_t color,
+ Canvas* const canvas) {
+ int16_t byteWidth = (w + 7) / 8;
+ uint8_t byte = 0;
+ for(int16_t j = 0; j < h; j++, y++) {
+ for(int16_t i = 0; i < w; i++) {
+ if(i & 7)
+ byte <<= 1;
+ else
+ byte = pgm_read_byte(&bitmap[j * byteWidth + i / 8]);
+ if(byte & 0x80) drawPixel(x + i, y, color, false, canvas);
+ }
+ }
+}
+
+void drawVLine(uint8_t x, int8_t start_y, int8_t end_y, uint8_t intensity, Canvas* const canvas) {
+ UNUSED(intensity);
+ uint8_t dots = end_y - start_y;
+ for(int i = 0; i < dots; i++) {
+ canvas_draw_dot(canvas, x, start_y + i);
+ }
+}
+
+void setupDisplay(Canvas* canvas) {
+ memset(zbuffer, 0xff, 128);
+ display_buf = (uint8_t*)canvas_get_buffer(canvas);
+ //display_buf = u8g2_GetBufferPtr(&canvas->fb);
+}
+
+void drawBitmap(
+ int16_t x,
+ int16_t y,
+ const Icon* i,
+ int16_t w,
+ int16_t h,
+ uint16_t color,
+ Canvas* const canvas) {
+ UNUSED(color);
+ canvas_draw_icon_bitmap(canvas, x, y, w, h, i);
+}
+
+void drawText(uint8_t x, uint8_t y, uint8_t num, Canvas* const canvas) {
+ char buf[4];
+ itoa(num, buf, 10);
+ drawTextSpace(x, y, buf, 1, canvas);
+}
+
+void drawTextSpace(int8_t x, int8_t y, char* txt, uint8_t space, Canvas* const canvas) {
+ uint8_t pos = x;
+ uint8_t i = 0;
+ char ch;
+ while((ch = txt[i]) != '\0') {
+ drawChar(pos, y, ch, canvas);
+ i++;
+ pos += CHAR_WIDTH + space;
+
+ // shortcut on end of screen
+ if(pos > SCREEN_WIDTH) return;
+ }
+}
+
+// Custom drawBitmap method with scale support, mask, zindex and pattern filling
+void drawSprite(
+ int8_t x,
+ int8_t y,
+ const uint8_t* bitmap,
+ const uint8_t* bitmap_mask,
+ int16_t w,
+ int16_t h,
+ uint8_t sprite,
+ double distance,
+ Canvas* const canvas) {
+ uint8_t tw = (double)w / distance;
+ uint8_t th = (double)h / distance;
+ uint8_t byte_width = w / 8;
+ uint8_t pixel_size = fmax(1, (double)1.0 / (double)distance);
+ uint16_t sprite_offset = byte_width * h * sprite;
+
+ bool pixel;
+ bool maskPixel;
+
+ // Don't draw the whole sprite if the anchor is hidden by z buffer
+ // Not checked per pixel for performance reasons
+ if(zbuffer[(int)(fmin(fmax(x, 0), ZBUFFER_SIZE - 1) / Z_RES_DIVIDER)] <
+ distance * DISTANCE_MULTIPLIER) {
+ return;
+ }
+
+ for(uint8_t ty = 0; ty < th; ty += pixel_size) {
+ // Don't draw out of screen
+ if(y + ty < 0 || y + ty >= RENDER_HEIGHT) {
+ continue;
+ }
+
+ uint8_t sy = ty * distance; // The y from the sprite
+
+ for(uint8_t tx = 0; tx < tw; tx += pixel_size) {
+ uint8_t sx = tx * distance; // The x from the sprite
+ uint16_t byte_offset = sprite_offset + sy * byte_width + sx / 8;
+
+ // Don't draw out of screen
+ if(x + tx < 0 || x + tx >= SCREEN_WIDTH) {
+ continue;
+ }
+
+ maskPixel = read_bit(pgm_read_byte(bitmap_mask + byte_offset), sx % 8);
+
+ if(maskPixel) {
+ pixel = read_bit(pgm_read_byte(bitmap + byte_offset), sx % 8);
+ for(uint8_t ox = 0; ox < pixel_size; ox++) {
+ for(uint8_t oy = 0; oy < pixel_size; oy++) {
+ if(bitmap == imp_inv)
+ drawPixel(x + tx + ox, y + ty + oy, 1, true, canvas);
+ else
+ drawPixel(x + tx + ox, y + ty + oy, pixel, true, canvas);
+ }
+ }
+ }
+ }
+ }
+}
+
+void drawPixel(int8_t x, int8_t y, bool color, bool raycasterViewport, Canvas* const canvas) {
+ if(x < 0 || x >= SCREEN_WIDTH || y < 0 ||
+ y >= (raycasterViewport ? RENDER_HEIGHT : SCREEN_HEIGHT)) {
+ return;
+ }
+ if(color)
+ canvas_draw_dot(canvas, x, y);
+ else {
+ canvas_invert_color(canvas);
+ canvas_draw_dot(canvas, x, y);
+ canvas_invert_color(canvas);
+ }
+}
+
+void drawChar(int8_t x, int8_t y, char ch, Canvas* const canvas) {
+ uint8_t lsb;
+ uint8_t c = 0;
+ while(CHAR_MAP[c] != ch && CHAR_MAP[c] != '\0') c++;
+ for(uint8_t i = 0; i < 6; i++) {
+ //lsb = (char_arr[c][i] >> 4);
+ lsb = reverse_bits(char_arr[c][i]);
+ for(uint8_t n = 0; n < 4; n++) {
+ if(CHECK_BIT(lsb, n)) {
+ drawPixel(x + n, y + i, true, false, canvas);
+ }
+ }
+ }
+}
+
+void clearRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, Canvas* const canvas) {
+ canvas_invert_color(canvas);
+
+ for(int i = 0; i < w; i++) {
+ for(int j = 0; j < h; j++) {
+ canvas_draw_dot(canvas, x + i, y + j);
+ }
+ }
+
+ canvas_invert_color(canvas);
+}
+
+void drawRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, Canvas* const canvas) {
+ for(int i = 0; i < w; i++) {
+ for(int j = 0; j < h; j++) {
+ canvas_draw_dot(canvas, x + i, y + j);
+ }
+ }
+}
+
+bool getGradientPixel(uint8_t x, uint8_t y, uint8_t i) {
+ if(i == 0) return 0;
+ if(i >= GRADIENT_COUNT - 1) return 1;
+
+ uint8_t index =
+ fmax(0, fmin(GRADIENT_COUNT - 1, i)) * GRADIENT_WIDTH * GRADIENT_HEIGHT // gradient index
+ + y * GRADIENT_WIDTH % (GRADIENT_WIDTH * GRADIENT_HEIGHT) // y byte offset
+ + x / GRADIENT_HEIGHT % GRADIENT_WIDTH; // x byte offset
+ //uint8_t *gradient_data = NULL;
+ //furi_hal_compress_icon_decode(icon_get_data(&I_gradient_inv), &gradient_data);
+ // return the bit based on x
+ return read_bit(pgm_read_byte(gradient + index), x % 8);
+}
+
+void fadeScreen(uint8_t intensity, bool color, Canvas* const canvas) {
+ for(uint8_t x = 0; x < SCREEN_WIDTH; x++) {
+ for(uint8_t y = 0; y < SCREEN_HEIGHT; y++) {
+ if(getGradientPixel(x, y, intensity)) drawPixel(x, y, color, false, canvas);
+ }
+ }
+}
+
+// Adds a delay to limit play to specified fps
+// Calculates also delta to keep movement consistent in lower framerates
+void fps() {
+ while(furi_get_tick() - lastFrameTime < FRAME_TIME)
+ ;
+ delta = (double)(furi_get_tick() - lastFrameTime) / (double)FRAME_TIME;
+ lastFrameTime = furi_get_tick();
+}
+
+double getActualFps() {
+ return 1000 / ((double)FRAME_TIME * (double)delta);
+}
+
+uint8_t reverse_bits(uint8_t num) {
+ unsigned int NO_OF_BITS = sizeof(num) * 8;
+ uint8_t reverse_num = 0;
+ uint8_t i;
+ for(i = 0; i < NO_OF_BITS; i++) {
+ if((num & (1 << i))) reverse_num |= 1 << ((NO_OF_BITS - 1) - i);
+ }
+ return reverse_num;
+}
\ No newline at end of file
diff --git a/applications/plugins/doom/doom.c b/applications/plugins/doom/doom.c
new file mode 100644
index 000000000..424bd0678
--- /dev/null
+++ b/applications/plugins/doom/doom.c
@@ -0,0 +1,1105 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include "sound.h"
+#include "display.h"
+#include "compiled/assets_icons.h"
+#include "constants.h"
+#include "entities.h"
+#include "types.h"
+#include "level.h"
+#include
+#include
+
+#define SOUND
+
+// Useful macros
+#define swap(a, b) \
+ do { \
+ typeof(a) temp = a; \
+ a = b; \
+ b = temp; \
+ } while(0)
+#define sign(a, b) (double)(a > b ? 1 : (b > a ? -1 : 0))
+#define pgm_read_byte(addr) (*(const unsigned char*)(addr))
+
+typedef enum {
+ EventTypeTick,
+ EventTypeKey,
+} EventType;
+
+typedef struct {
+ EventType type;
+ InputEvent input;
+} PluginEvent;
+
+typedef struct {
+ Player player;
+ Entity entity[MAX_ENTITIES];
+ StaticEntity static_entity[MAX_STATIC_ENTITIES];
+ uint8_t num_entities;
+ uint8_t num_static_entities;
+
+ uint8_t scene;
+ uint8_t gun_pos;
+ double jogging;
+ double view_height;
+ bool init;
+
+ bool up;
+ bool down;
+ bool left;
+ bool right;
+ bool fired;
+ bool gun_fired;
+
+ double rot_speed;
+ double old_dir_x;
+ double old_plane_x;
+ NotificationApp* notify;
+#ifdef SOUND
+ MusicPlayer* music_instance;
+ bool intro_sound;
+#endif
+} PluginState;
+
+static const NotificationSequence sequence_short_sound = {
+ &message_note_c5,
+ &message_delay_50,
+ &message_sound_off,
+ NULL,
+};
+static const NotificationSequence sequence_long_sound = {
+ &message_note_c3,
+ &message_delay_100,
+ &message_sound_off,
+ NULL,
+};
+
+Coords translateIntoView(Coords* pos, PluginState* const plugin_state);
+void updateHud(Canvas* const canvas, PluginState* const plugin_state);
+// general
+
+bool invert_screen = false;
+uint8_t flash_screen = 0;
+
+// game
+// player and entities
+
+uint8_t getBlockAt(const uint8_t level[], uint8_t x, uint8_t y) {
+ if(x >= LEVEL_WIDTH || y >= LEVEL_HEIGHT) {
+ return E_FLOOR;
+ }
+
+ // y is read in inverse order
+ return pgm_read_byte(level + (((LEVEL_HEIGHT - 1 - y) * LEVEL_WIDTH + x) / 2)) >>
+ (!(x % 2) * 4) // displace part of wanted bits
+ & 0b1111; // mask wanted bits
+}
+
+// Finds the player in the map
+void initializeLevel(const uint8_t level[], PluginState* const plugin_state) {
+ for(uint8_t y = LEVEL_HEIGHT - 1; y > 0; y--) {
+ for(uint8_t x = 0; x < LEVEL_WIDTH; x++) {
+ uint8_t block = getBlockAt(level, x, y);
+
+ if(block == E_PLAYER) {
+ plugin_state->player = create_player(x, y);
+ return;
+ }
+
+ // todo create other static entities
+ }
+ }
+}
+
+bool isSpawned(UID uid, PluginState* const plugin_state) {
+ for(uint8_t i = 0; i < plugin_state->num_entities; i++) {
+ if(plugin_state->entity[i].uid == uid) return true;
+ }
+
+ return false;
+}
+
+bool isStatic(UID uid, PluginState* const plugin_state) {
+ for(uint8_t i = 0; i < plugin_state->num_static_entities; i++) {
+ if(plugin_state->static_entity[i].uid == uid) return true;
+ }
+
+ return false;
+}
+
+void spawnEntity(uint8_t type, uint8_t x, uint8_t y, PluginState* const plugin_state) {
+ // Limit the number of spawned entities
+ if(plugin_state->num_entities >= MAX_ENTITIES) {
+ return;
+ }
+
+ // todo: read static entity status
+
+ switch(type) {
+ case E_ENEMY:
+ plugin_state->entity[plugin_state->num_entities] = create_enemy(x, y);
+ plugin_state->num_entities++;
+ break;
+
+ case E_KEY:
+ plugin_state->entity[plugin_state->num_entities] = create_key(x, y);
+ plugin_state->num_entities++;
+ break;
+
+ case E_MEDIKIT:
+ plugin_state->entity[plugin_state->num_entities] = create_medikit(x, y);
+ plugin_state->num_entities++;
+ break;
+ }
+}
+
+void spawnFireball(double x, double y, PluginState* const plugin_state) {
+ // Limit the number of spawned entities
+ if(plugin_state->num_entities >= MAX_ENTITIES) {
+ return;
+ }
+
+ UID uid = create_uid(E_FIREBALL, x, y);
+ // Remove if already exists, don't throw anything. Not the best, but shouldn't happen too often
+ if(isSpawned(uid, plugin_state)) return;
+
+ // Calculate direction. 32 angles
+ int16_t dir =
+ FIREBALL_ANGLES + atan2(y - plugin_state->player.pos.y, x - plugin_state->player.pos.x) /
+ (double)PI * FIREBALL_ANGLES;
+ if(dir < 0) dir += FIREBALL_ANGLES * 2;
+ plugin_state->entity[plugin_state->num_entities] = create_fireball(x, y, dir);
+ plugin_state->num_entities++;
+}
+
+void removeEntity(UID uid, PluginState* const plugin_state) {
+ uint8_t i = 0;
+ bool found = false;
+
+ while(i < plugin_state->num_entities) {
+ if(!found && plugin_state->entity[i].uid == uid) {
+ // todo: doze it
+ found = true;
+ plugin_state->num_entities--;
+ }
+
+ // displace entities
+ if(found) {
+ plugin_state->entity[i] = plugin_state->entity[i + 1];
+ }
+
+ i++;
+ }
+}
+
+void removeStaticEntity(UID uid, PluginState* const plugin_state) {
+ uint8_t i = 0;
+ bool found = false;
+
+ while(i < plugin_state->num_static_entities) {
+ if(!found && plugin_state->static_entity[i].uid == uid) {
+ found = true;
+ plugin_state->num_static_entities--;
+ }
+
+ // displace entities
+ if(found) {
+ plugin_state->static_entity[i] = plugin_state->static_entity[i + 1];
+ }
+
+ i++;
+ }
+}
+
+UID detectCollision(
+ const uint8_t level[],
+ Coords* pos,
+ double relative_x,
+ double relative_y,
+ bool only_walls,
+ PluginState* const plugin_state) {
+ // Wall collision
+ uint8_t round_x = (int)pos->x + (int)relative_x;
+ uint8_t round_y = (int)pos->y + (int)relative_y;
+ uint8_t block = getBlockAt(level, round_x, round_y);
+
+ if(block == E_WALL) {
+ //playSound(hit_wall_snd, HIT_WALL_SND_LEN);
+ return create_uid(block, round_x, round_y);
+ }
+
+ if(only_walls) {
+ return UID_null;
+ }
+
+ // Entity collision
+ for(uint8_t i = 0; i < plugin_state->num_entities; i++) {
+ // Don't collide with itself
+ if(&(plugin_state->entity[i].pos) == pos) {
+ continue;
+ }
+
+ uint8_t type = uid_get_type(plugin_state->entity[i].uid);
+
+ // Only ALIVE enemy collision
+ if(type != E_ENEMY || plugin_state->entity[i].state == S_DEAD ||
+ plugin_state->entity[i].state == S_HIDDEN) {
+ continue;
+ }
+
+ Coords new_coords = {
+ plugin_state->entity[i].pos.x - relative_x,
+ plugin_state->entity[i].pos.y - relative_y};
+ uint8_t distance = coords_distance(pos, &new_coords);
+
+ // Check distance and if it's getting closer
+ if(distance < ENEMY_COLLIDER_DIST && distance < plugin_state->entity[i].distance) {
+ return plugin_state->entity[i].uid;
+ }
+ }
+
+ return UID_null;
+}
+
+// Shoot
+void fire(PluginState* const plugin_state) {
+ //playSound(shoot_snd, SHOOT_SND_LEN);
+
+ for(uint8_t i = 0; i < plugin_state->num_entities; i++) {
+ // Shoot only ALIVE enemies
+ if(uid_get_type(plugin_state->entity[i].uid) != E_ENEMY ||
+ plugin_state->entity[i].state == S_DEAD || plugin_state->entity[i].state == S_HIDDEN) {
+ continue;
+ }
+
+ Coords transform = translateIntoView(&(plugin_state->entity[i].pos), plugin_state);
+ if(fabs(transform.x) < 20 && transform.y > 0) {
+ uint8_t damage = (double)fmin(
+ GUN_MAX_DAMAGE,
+ GUN_MAX_DAMAGE / (fabs(transform.x) * plugin_state->entity[i].distance) / 5);
+ if(damage > 0) {
+ plugin_state->entity[i].health = fmax(0, plugin_state->entity[i].health - damage);
+ plugin_state->entity[i].state = S_HIT;
+ plugin_state->entity[i].timer = 4;
+ }
+ }
+ }
+}
+
+UID updatePosition(
+ const uint8_t level[],
+ Coords* pos,
+ double relative_x,
+ double relative_y,
+ bool only_walls,
+ PluginState* const plugin_state) {
+ UID collide_x = detectCollision(level, pos, relative_x, 0, only_walls, plugin_state);
+ UID collide_y = detectCollision(level, pos, 0, relative_y, only_walls, plugin_state);
+
+ if(!collide_x) pos->x += relative_x;
+ if(!collide_y) pos->y += relative_y;
+
+ return collide_x || collide_y || UID_null;
+}
+
+void updateEntities(const uint8_t level[], Canvas* const canvas, PluginState* const plugin_state) {
+ uint8_t i = 0;
+ while(i < plugin_state->num_entities) {
+ // update distance
+ plugin_state->entity[i].distance =
+ coords_distance(&(plugin_state->player.pos), &(plugin_state->entity[i].pos));
+
+ // Run the timer. Works with actual frames.
+ // Todo: use delta here. But needs double type and more memory
+ if(plugin_state->entity[i].timer > 0) plugin_state->entity[i].timer--;
+
+ // too far away. put it in doze mode
+ if(plugin_state->entity[i].distance > MAX_ENTITY_DISTANCE) {
+ removeEntity(plugin_state->entity[i].uid, plugin_state);
+ // don't increase 'i', since current one has been removed
+ continue;
+ }
+
+ // bypass render if hidden
+ if(plugin_state->entity[i].state == S_HIDDEN) {
+ i++;
+ continue;
+ }
+
+ uint8_t type = uid_get_type(plugin_state->entity[i].uid);
+
+ switch(type) {
+ case E_ENEMY: {
+ // Enemy "IA"
+ if(plugin_state->entity[i].health == 0) {
+ if(plugin_state->entity[i].state != S_DEAD) {
+ plugin_state->entity[i].state = S_DEAD;
+ plugin_state->entity[i].timer = 6;
+ }
+ } else if(plugin_state->entity[i].state == S_HIT) {
+ if(plugin_state->entity[i].timer == 0) {
+ // Back to alert state
+ plugin_state->entity[i].state = S_ALERT;
+ plugin_state->entity[i].timer = 40; // delay next fireball thrown
+ }
+ } else if(plugin_state->entity[i].state == S_FIRING) {
+ if(plugin_state->entity[i].timer == 0) {
+ // Back to alert state
+ plugin_state->entity[i].state = S_ALERT;
+ plugin_state->entity[i].timer = 40; // delay next fireball throwm
+ }
+ } else {
+ // ALERT STATE
+ if(plugin_state->entity[i].distance > ENEMY_MELEE_DIST &&
+ plugin_state->entity[i].distance < MAX_ENEMY_VIEW) {
+ if(plugin_state->entity[i].state != S_ALERT) {
+ plugin_state->entity[i].state = S_ALERT;
+ plugin_state->entity[i].timer = 20; // used to throw fireballs
+ } else {
+ if(plugin_state->entity[i].timer == 0) {
+ // Throw a fireball
+ spawnFireball(
+ plugin_state->entity[i].pos.x,
+ plugin_state->entity[i].pos.y,
+ plugin_state);
+ plugin_state->entity[i].state = S_FIRING;
+ plugin_state->entity[i].timer = 6;
+ } else {
+ // move towards to the player.
+ updatePosition(
+ level,
+ &(plugin_state->entity[i].pos),
+ sign(plugin_state->player.pos.x, plugin_state->entity[i].pos.x) *
+ (double)ENEMY_SPEED * 1, // NOT SURE (delta)
+ sign(plugin_state->player.pos.y, plugin_state->entity[i].pos.y) *
+ (double)ENEMY_SPEED * 1, // NOT SURE (delta)
+ true,
+ plugin_state);
+ }
+ }
+ } else if(plugin_state->entity[i].distance <= ENEMY_MELEE_DIST) {
+ if(plugin_state->entity[i].state != S_MELEE) {
+ // Preparing the melee attack
+ plugin_state->entity[i].state = S_MELEE;
+ plugin_state->entity[i].timer = 10;
+ } else if(plugin_state->entity[i].timer == 0) {
+ // Melee attack
+ plugin_state->player.health =
+ fmax(0, plugin_state->player.health - ENEMY_MELEE_DAMAGE);
+ plugin_state->entity[i].timer = 14;
+ flash_screen = 1;
+ updateHud(canvas, plugin_state);
+ }
+ } else {
+ // stand
+ plugin_state->entity[i].state = S_STAND;
+ }
+ }
+ break;
+ }
+
+ case E_FIREBALL: {
+ if(plugin_state->entity[i].distance < FIREBALL_COLLIDER_DIST) {
+ // Hit the player and disappear
+ plugin_state->player.health =
+ fmax(0, plugin_state->player.health - ENEMY_FIREBALL_DAMAGE);
+ flash_screen = 1;
+ updateHud(canvas, plugin_state);
+ removeEntity(plugin_state->entity[i].uid, plugin_state);
+ continue; // continue in the loop
+ } else {
+ // Move. Only collide with walls.
+ // Note: using health to store the angle of the movement
+ UID collided = updatePosition(
+ level,
+ &(plugin_state->entity[i].pos),
+ cos((double)plugin_state->entity[i].health / FIREBALL_ANGLES * (double)PI) *
+ (double)FIREBALL_SPEED,
+ sin((double)plugin_state->entity[i].health / FIREBALL_ANGLES * (double)PI) *
+ (double)FIREBALL_SPEED,
+ true,
+ plugin_state);
+
+ if(collided) {
+ removeEntity(plugin_state->entity[i].uid, plugin_state);
+ continue; // continue in the entity check loop
+ }
+ }
+ break;
+ }
+
+ case E_MEDIKIT: {
+ if(plugin_state->entity[i].distance < ITEM_COLLIDER_DIST) {
+ // pickup
+ notification_message(plugin_state->notify, &sequence_long_sound);
+ //playSound(medkit_snd, MEDKIT_SND_LEN);
+ plugin_state->entity[i].state = S_HIDDEN;
+ plugin_state->player.health = fmin(100, plugin_state->player.health + 50);
+ updateHud(canvas, plugin_state);
+ flash_screen = 1;
+ }
+ break;
+ }
+
+ case E_KEY: {
+ if(plugin_state->entity[i].distance < ITEM_COLLIDER_DIST) {
+ // pickup
+ notification_message(plugin_state->notify, &sequence_long_sound);
+ //playSound(get_key_snd, GET_KEY_SND_LEN);
+ plugin_state->entity[i].state = S_HIDDEN;
+ plugin_state->player.keys++;
+ updateHud(canvas, plugin_state);
+ flash_screen = 1;
+ }
+ break;
+ }
+ }
+
+ i++;
+ }
+}
+
+// The map raycaster. Based on https://lodev.org/cgtutor/raycasting.html
+void renderMap(
+ const uint8_t level[],
+ double view_height,
+ Canvas* const canvas,
+ PluginState* const plugin_state) {
+ UID last_uid = 0; // NOT SURE ?
+
+ for(uint8_t x = 0; x < SCREEN_WIDTH; x += RES_DIVIDER) {
+ double camera_x = 2 * (double)x / SCREEN_WIDTH - 1;
+ double ray_x = plugin_state->player.dir.x + plugin_state->player.plane.x * camera_x;
+ double ray_y = plugin_state->player.dir.y + plugin_state->player.plane.y * camera_x;
+ uint8_t map_x = (uint8_t)plugin_state->player.pos.x;
+ uint8_t map_y = (uint8_t)plugin_state->player.pos.y;
+ Coords map_coords = {plugin_state->player.pos.x, plugin_state->player.pos.y};
+ double delta_x = fabs(1 / ray_x);
+ double delta_y = fabs(1 / ray_y);
+
+ int8_t step_x;
+ int8_t step_y;
+ double side_x;
+ double side_y;
+
+ if(ray_x < 0) {
+ step_x = -1;
+ side_x = (plugin_state->player.pos.x - map_x) * delta_x;
+ } else {
+ step_x = 1;
+ side_x = (map_x + (double)1.0 - plugin_state->player.pos.x) * delta_x;
+ }
+
+ if(ray_y < 0) {
+ step_y = -1;
+ side_y = (plugin_state->player.pos.y - map_y) * delta_y;
+ } else {
+ step_y = 1;
+ side_y = (map_y + (double)1.0 - plugin_state->player.pos.y) * delta_y;
+ }
+
+ // Wall detection
+ uint8_t depth = 0;
+ bool hit = 0;
+ bool side;
+ while(!hit && depth < MAX_RENDER_DEPTH) {
+ if(side_x < side_y) {
+ side_x += delta_x;
+ map_x += step_x;
+ side = 0;
+ } else {
+ side_y += delta_y;
+ map_y += step_y;
+ side = 1;
+ }
+
+ uint8_t block = getBlockAt(level, map_x, map_y);
+
+ if(block == E_WALL) {
+ hit = 1;
+ } else {
+ // Spawning entities here, as soon they are visible for the
+ // player. Not the best place, but would be a very performance
+ // cost scan for them in another loop
+ if(block == E_ENEMY || (block & 0b00001000) /* all collectable items */) {
+ // Check that it's close to the player
+ if(coords_distance(&(plugin_state->player.pos), &map_coords) <
+ MAX_ENTITY_DISTANCE) {
+ UID uid = create_uid(block, map_x, map_y);
+ if(last_uid != uid && !isSpawned(uid, plugin_state)) {
+ spawnEntity(block, map_x, map_y, plugin_state);
+ last_uid = uid;
+ }
+ }
+ }
+ }
+
+ depth++;
+ }
+
+ if(hit) {
+ double distance;
+
+ if(side == 0) {
+ distance =
+ fmax(1, (map_x - plugin_state->player.pos.x + (1 - step_x) / 2) / ray_x);
+ } else {
+ distance =
+ fmax(1, (map_y - plugin_state->player.pos.y + (1 - step_y) / 2) / ray_y);
+ }
+
+ // store zbuffer value for the column
+ zbuffer[x / Z_RES_DIVIDER] = fmin(distance * DISTANCE_MULTIPLIER, 255);
+
+ // rendered line height
+ uint8_t line_height = RENDER_HEIGHT / distance;
+
+ drawVLine(
+ x,
+ view_height / distance - line_height / 2 + RENDER_HEIGHT / 2,
+ view_height / distance + line_height / 2 + RENDER_HEIGHT / 2,
+ GRADIENT_COUNT - (int)distance / MAX_RENDER_DEPTH * GRADIENT_COUNT - side * 2,
+ canvas);
+ }
+ }
+}
+
+// Sort entities from far to close
+uint8_t sortEntities(PluginState* const plugin_state) {
+ uint8_t gap = plugin_state->num_entities;
+ bool swapped = false;
+ while(gap > 1 || swapped) {
+ //shrink factor 1.3
+ gap = (gap * 10) / 13;
+ if(gap == 9 || gap == 10) gap = 11;
+ if(gap < 1) gap = 1;
+ swapped = false;
+ for(uint8_t i = 0; i < plugin_state->num_entities - gap; i++) {
+ uint8_t j = i + gap;
+ if(plugin_state->entity[i].distance < plugin_state->entity[j].distance) {
+ swap(plugin_state->entity[i], plugin_state->entity[j]);
+ swapped = true;
+ }
+ }
+ }
+ return swapped;
+}
+
+Coords translateIntoView(Coords* pos, PluginState* const plugin_state) {
+ //translate sprite position to relative to camera
+ double sprite_x = pos->x - plugin_state->player.pos.x;
+ double sprite_y = pos->y - plugin_state->player.pos.y;
+
+ //required for correct matrix multiplication
+ double inv_det =
+ ((double)1.0 /
+ ((double)plugin_state->player.plane.x * (double)plugin_state->player.dir.y -
+ (double)plugin_state->player.dir.x * (double)plugin_state->player.plane.y));
+ double transform_x =
+ inv_det * (plugin_state->player.dir.y * sprite_x - plugin_state->player.dir.x * sprite_y);
+ double transform_y = inv_det * (-plugin_state->player.plane.y * sprite_x +
+ plugin_state->player.plane.x * sprite_y); // Z in screen
+ Coords res = {transform_x, transform_y};
+ return res;
+}
+
+void renderEntities(double view_height, Canvas* const canvas, PluginState* const plugin_state) {
+ sortEntities(plugin_state);
+
+ for(uint8_t i = 0; i < plugin_state->num_entities; i++) {
+ if(plugin_state->entity[i].state == S_HIDDEN) continue;
+
+ Coords transform = translateIntoView(&(plugin_state->entity[i].pos), plugin_state);
+
+ // don´t render if behind the player or too far away
+ if(transform.y <= (double)0.1 || transform.y > MAX_SPRITE_DEPTH) {
+ continue;
+ }
+
+ int16_t sprite_screen_x = HALF_WIDTH * ((double)1.0 + transform.x / transform.y);
+ int8_t sprite_screen_y = RENDER_HEIGHT / 2 + view_height / transform.y;
+ uint8_t type = uid_get_type(plugin_state->entity[i].uid);
+
+ // don´t try to render if outside of screen
+ // doing this pre-shortcut due int16 -> int8 conversion makes out-of-screen
+ // values fit into the screen space
+ if(sprite_screen_x < -HALF_WIDTH || sprite_screen_x > SCREEN_WIDTH + HALF_WIDTH) {
+ continue;
+ }
+
+ switch(type) {
+ case E_ENEMY: {
+ uint8_t sprite;
+ if(plugin_state->entity[i].state == S_ALERT) {
+ // walking
+ sprite = ((int)furi_get_tick() / 500) % 2;
+ } else if(plugin_state->entity[i].state == S_FIRING) {
+ // fireball
+ sprite = 2;
+ } else if(plugin_state->entity[i].state == S_HIT) {
+ // hit
+ sprite = 3;
+ } else if(plugin_state->entity[i].state == S_MELEE) {
+ // melee atack
+ sprite = plugin_state->entity[i].timer > 10 ? 2 : 1;
+ } else if(plugin_state->entity[i].state == S_DEAD) {
+ // dying
+ sprite = plugin_state->entity[i].timer > 0 ? 3 : 4;
+ } else {
+ // stand
+ sprite = 0;
+ }
+
+ drawSprite(
+ sprite_screen_x - BMP_IMP_WIDTH * (double).5 / transform.y,
+ sprite_screen_y - 8 / transform.y,
+ imp_inv,
+ imp_mask_inv,
+ BMP_IMP_WIDTH,
+ BMP_IMP_HEIGHT,
+ sprite,
+ transform.y,
+ canvas);
+ break;
+ }
+
+ case E_FIREBALL: {
+ drawSprite(
+ sprite_screen_x - BMP_FIREBALL_WIDTH / 2 / transform.y,
+ sprite_screen_y - BMP_FIREBALL_HEIGHT / 2 / transform.y,
+ fireball,
+ fireball_mask,
+ BMP_FIREBALL_WIDTH,
+ BMP_FIREBALL_HEIGHT,
+ 0,
+ transform.y,
+ canvas);
+ break;
+ }
+
+ case E_MEDIKIT: {
+ drawSprite(
+ sprite_screen_x - BMP_ITEMS_WIDTH / 2 / transform.y,
+ sprite_screen_y + 5 / transform.y,
+ item,
+ item_mask,
+ BMP_ITEMS_WIDTH,
+ BMP_ITEMS_HEIGHT,
+ 0,
+ transform.y,
+ canvas);
+ break;
+ }
+
+ case E_KEY: {
+ drawSprite(
+ sprite_screen_x - BMP_ITEMS_WIDTH / 2 / transform.y,
+ sprite_screen_y + 5 / transform.y,
+ item,
+ item_mask,
+ BMP_ITEMS_WIDTH,
+ BMP_ITEMS_HEIGHT,
+ 1,
+ transform.y,
+ canvas);
+ break;
+ }
+ }
+ }
+}
+
+void renderGun(uint8_t gun_pos, double amount_jogging, Canvas* const canvas) {
+ // jogging
+ char x = 48 + sin((double)furi_get_tick() * (double)JOGGING_SPEED) * 10 * amount_jogging;
+ char y = RENDER_HEIGHT - gun_pos +
+ fabs(cos((double)furi_get_tick() * (double)JOGGING_SPEED)) * 8 * amount_jogging;
+
+ if(gun_pos > GUN_SHOT_POS - 2) {
+ // Gun fire
+ drawBitmap(x + 6, y - 11, &I_fire_inv, BMP_FIRE_WIDTH, BMP_FIRE_HEIGHT, 1, canvas);
+ }
+
+ // Don't draw over the hud!
+ uint8_t clip_height = fmax(0, fmin(y + BMP_GUN_HEIGHT, RENDER_HEIGHT) - y);
+
+ // Draw the gun (black mask + actual sprite).
+ drawBitmap(x, y, &I_gun_mask_inv, BMP_GUN_WIDTH, clip_height, 0, canvas);
+ drawBitmap(x, y, &I_gun_inv, BMP_GUN_WIDTH, clip_height, 1, canvas);
+ //drawGun(x,y,gun_mask, BMP_GUN_WIDTH, clip_height, 0, canvas);
+ //drawGun(x,y,gun, BMP_GUN_WIDTH, clip_height, 1, canvas);
+}
+
+// Only needed first time
+void renderHud(Canvas* const canvas, PluginState* plugin_state) {
+ drawTextSpace(2, 58, "{}", 0, canvas); // Health symbol
+ drawTextSpace(40, 58, "[]", 0, canvas); // Keys symbol
+ updateHud(canvas, plugin_state);
+}
+
+// Render values for the HUD
+void updateHud(Canvas* const canvas, PluginState* plugin_state) {
+ clearRect(12, 58, 15, 6, canvas);
+ clearRect(50, 58, 15, 6, canvas);
+ drawText(12, 58, plugin_state->player.health, canvas);
+ drawText(50, 58, plugin_state->player.keys, canvas);
+}
+
+// Debug stats
+void renderStats(Canvas* const canvas, PluginState* plugin_state) {
+ clearRect(58, 58, 70, 6, canvas);
+ drawText(114, 58, (int)getActualFps(), canvas);
+ drawText(82, 58, plugin_state->num_entities, canvas);
+ // drawText(94, 58, freeMemory());
+}
+
+// Intro screen
+void loopIntro(Canvas* const canvas) {
+ canvas_draw_icon(
+ canvas,
+ (SCREEN_WIDTH - BMP_LOGO_WIDTH) / 2,
+ (SCREEN_HEIGHT - BMP_LOGO_HEIGHT) / 3,
+ &I_logo_inv);
+ drawTextSpace(SCREEN_WIDTH / 2 - 25, SCREEN_HEIGHT * .8, "PRESS FIRE", 1, canvas);
+}
+
+static void render_callback(Canvas* const canvas, void* ctx) {
+ PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25);
+ if(plugin_state == NULL) {
+ return;
+ }
+ if(plugin_state->init) setupDisplay(canvas);
+
+ canvas_set_font(canvas, FontPrimary);
+
+ switch(plugin_state->scene) {
+ case INTRO: {
+ loopIntro(canvas);
+ break;
+ }
+ case GAME_PLAY: {
+ updateEntities(sto_level_1, canvas, plugin_state);
+
+ renderGun(plugin_state->gun_pos, plugin_state->jogging, canvas);
+ renderMap(sto_level_1, plugin_state->view_height, canvas, plugin_state);
+
+ renderEntities(plugin_state->view_height, canvas, plugin_state);
+
+ renderHud(canvas, plugin_state);
+ updateHud(canvas, plugin_state);
+ renderStats(canvas, plugin_state);
+ break;
+ }
+ }
+ release_mutex((ValueMutex*)ctx, plugin_state);
+}
+
+static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
+ furi_assert(event_queue);
+
+ PluginEvent event = {.type = EventTypeKey, .input = *input_event};
+ furi_message_queue_put(event_queue, &event, 0);
+}
+
+static void doom_state_init(PluginState* const plugin_state) {
+ plugin_state->notify = furi_record_open(RECORD_NOTIFICATION);
+ plugin_state->num_entities = 0;
+ plugin_state->num_static_entities = 0;
+
+ plugin_state->scene = INTRO;
+ plugin_state->gun_pos = 0;
+ plugin_state->view_height = 0;
+ plugin_state->init = true;
+
+ plugin_state->up = false;
+ plugin_state->down = false;
+ plugin_state->left = false;
+ plugin_state->right = false;
+ plugin_state->fired = false;
+ plugin_state->gun_fired = false;
+#ifdef SOUND
+
+ plugin_state->music_instance = malloc(sizeof(MusicPlayer));
+ plugin_state->music_instance->model = malloc(sizeof(MusicPlayerModel));
+ memset(
+ plugin_state->music_instance->model->duration_history,
+ 0xff,
+ MUSIC_PLAYER_SEMITONE_HISTORY_SIZE);
+ memset(
+ plugin_state->music_instance->model->semitone_history,
+ 0xff,
+ MUSIC_PLAYER_SEMITONE_HISTORY_SIZE);
+ plugin_state->music_instance->model->volume = 2;
+
+ plugin_state->music_instance->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
+ //plugin_state->music_instance->view_port = view_port_alloc();
+
+ plugin_state->music_instance->worker = music_player_worker_alloc();
+ //music_player_worker_set_volume(plugin_state->music_instance->worker, 0.75);
+ music_player_worker_set_volume(
+ plugin_state->music_instance->worker,
+ MUSIC_PLAYER_VOLUMES[plugin_state->music_instance->model->volume]);
+ plugin_state->intro_sound = true;
+ //init_sound(plugin_state->music_instance);
+#endif
+}
+
+static void doom_game_update_timer_callback(FuriMessageQueue* event_queue) {
+ furi_assert(event_queue);
+
+ PluginEvent event = {.type = EventTypeTick};
+ furi_message_queue_put(event_queue, &event, 0);
+}
+
+static void doom_game_tick(PluginState* const plugin_state) {
+ if(plugin_state->scene == GAME_PLAY) {
+ //fps();
+ memset(display_buf, 0, SCREEN_WIDTH * (RENDER_HEIGHT / 8));
+ //player is alive
+ if(plugin_state->player.health > 0) {
+ if(plugin_state->up) {
+ plugin_state->player.velocity +=
+ ((double)MOV_SPEED - plugin_state->player.velocity) * (double).4;
+ plugin_state->jogging = fabs(plugin_state->player.velocity) * MOV_SPEED_INV;
+ //plugin_state->up = false;
+ } else if(plugin_state->down) {
+ plugin_state->player.velocity +=
+ (-(double)MOV_SPEED - plugin_state->player.velocity) * (double).4;
+ plugin_state->jogging = fabs(plugin_state->player.velocity) * MOV_SPEED_INV;
+ //plugin_state->down = false;
+ } else {
+ plugin_state->player.velocity *= (double).5;
+ plugin_state->jogging = fabs(plugin_state->player.velocity) * MOV_SPEED_INV;
+ }
+
+ if(plugin_state->right) {
+ plugin_state->rot_speed = (double)ROT_SPEED * delta;
+ plugin_state->old_dir_x = plugin_state->player.dir.x;
+ plugin_state->player.dir.x =
+ plugin_state->player.dir.x * cos(-(plugin_state->rot_speed)) -
+ plugin_state->player.dir.y * sin(-(plugin_state->rot_speed));
+ plugin_state->player.dir.y =
+ plugin_state->old_dir_x * sin(-(plugin_state->rot_speed)) +
+ plugin_state->player.dir.y * cos(-(plugin_state->rot_speed));
+ plugin_state->old_plane_x = plugin_state->player.plane.x;
+ plugin_state->player.plane.x =
+ plugin_state->player.plane.x * cos(-(plugin_state->rot_speed)) -
+ plugin_state->player.plane.y * sin(-(plugin_state->rot_speed));
+ plugin_state->player.plane.y =
+ plugin_state->old_plane_x * sin(-(plugin_state->rot_speed)) +
+ plugin_state->player.plane.y * cos(-(plugin_state->rot_speed));
+
+ //plugin_state->right = false;
+ } else if(plugin_state->left) {
+ plugin_state->rot_speed = (double)ROT_SPEED * delta;
+ plugin_state->old_dir_x = plugin_state->player.dir.x;
+ plugin_state->player.dir.x =
+ plugin_state->player.dir.x * cos(plugin_state->rot_speed) -
+ plugin_state->player.dir.y * sin(plugin_state->rot_speed);
+ plugin_state->player.dir.y =
+ plugin_state->old_dir_x * sin(plugin_state->rot_speed) +
+ plugin_state->player.dir.y * cos(plugin_state->rot_speed);
+ plugin_state->old_plane_x = plugin_state->player.plane.x;
+ plugin_state->player.plane.x =
+ plugin_state->player.plane.x * cos(plugin_state->rot_speed) -
+ plugin_state->player.plane.y * sin(plugin_state->rot_speed);
+ plugin_state->player.plane.y =
+ plugin_state->old_plane_x * sin(plugin_state->rot_speed) +
+ plugin_state->player.plane.y * cos(plugin_state->rot_speed);
+ //plugin_state->left = false;
+ }
+ plugin_state->view_height =
+ fabs(sin((double)furi_get_tick() * (double)JOGGING_SPEED)) * 6 *
+ plugin_state->jogging;
+
+ if(plugin_state->gun_pos > GUN_TARGET_POS) {
+ // Right after fire
+ plugin_state->gun_pos -= 1;
+ } else if(plugin_state->gun_pos < GUN_TARGET_POS) {
+ plugin_state->gun_pos += 2;
+ } else if(!plugin_state->gun_fired && plugin_state->fired) {
+ //furi_hal_speaker_start(20480 / 10, 0.45f);
+ /*#ifdef SOUND
+ music_player_worker_start(plugin_state->music_instance->worker);
+#endif*/
+ plugin_state->gun_pos = GUN_SHOT_POS;
+ plugin_state->gun_fired = true;
+ plugin_state->fired = false;
+ fire(plugin_state);
+
+ } else if(plugin_state->gun_fired && !plugin_state->fired) {
+ //furi_hal_speaker_stop();
+ plugin_state->gun_fired = false;
+
+ notification_message(plugin_state->notify, &sequence_short_sound);
+
+ /*#ifdef SOUND
+ music_player_worker_stop(plugin_state->music_instance->worker);
+#endif*/
+ }
+ } else {
+ // Player is dead
+ if(plugin_state->view_height > -10) plugin_state->view_height--;
+ if(plugin_state->gun_pos > 1) plugin_state->gun_pos -= 2;
+ }
+
+ if(fabs(plugin_state->player.velocity) > (double)0.003) {
+ updatePosition(
+ sto_level_1,
+ &(plugin_state->player.pos),
+ plugin_state->player.dir.x * plugin_state->player.velocity * delta,
+ plugin_state->player.dir.y * plugin_state->player.velocity * delta,
+ false,
+ plugin_state);
+ } else {
+ plugin_state->player.velocity = 0;
+ }
+ }
+}
+
+int32_t doom_app() {
+ FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
+ PluginState* plugin_state = malloc(sizeof(PluginState));
+ doom_state_init(plugin_state);
+ ValueMutex state_mutex;
+ if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) {
+ FURI_LOG_E("Doom_game", "cannot create mutex\r\n");
+ furi_record_close(RECORD_NOTIFICATION);
+ furi_message_queue_free(event_queue);
+ free(plugin_state);
+ return 255;
+ }
+ FuriTimer* timer =
+ furi_timer_alloc(doom_game_update_timer_callback, FuriTimerTypePeriodic, event_queue);
+ furi_timer_start(timer, furi_kernel_get_tick_frequency() / 12);
+ // Set system callbacks
+ ViewPort* view_port = view_port_alloc();
+ view_port_draw_callback_set(view_port, render_callback, &state_mutex);
+ view_port_input_callback_set(view_port, input_callback, event_queue);
+
+ // Open GUI and register view_port
+ Gui* gui = furi_record_open("gui");
+ gui_add_view_port(gui, view_port, GuiLayerFullscreen);
+
+ //////////////////////////////////
+ if(display_buf != NULL) plugin_state->init = false;
+
+ PluginEvent event;
+#ifdef SOUND
+ music_player_worker_load_rtttl_from_string(plugin_state->music_instance->worker, dsintro);
+ music_player_worker_start(plugin_state->music_instance->worker);
+#endif
+ for(bool processing = true; processing;) {
+ FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
+ PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex);
+#ifdef SOUND
+ furi_check(
+ furi_mutex_acquire(plugin_state->music_instance->model_mutex, FuriWaitForever) ==
+ FuriStatusOk);
+#endif
+ if(event_status == FuriStatusOk) {
+ // press events
+ if(event.type == EventTypeKey) {
+ if(event.input.key == InputKeyBack) {
+ processing = false;
+#ifdef SOUND
+ if(plugin_state->intro_sound) {
+ furi_mutex_release(plugin_state->music_instance->model_mutex);
+ music_player_worker_stop(plugin_state->music_instance->worker);
+ }
+#endif
+ }
+
+ if(event.input.type == InputTypePress) {
+ if(plugin_state->scene == INTRO && event.input.key == InputKeyOk) {
+ plugin_state->scene = GAME_PLAY;
+ initializeLevel(sto_level_1, plugin_state);
+#ifdef SOUND
+ furi_mutex_release(plugin_state->music_instance->model_mutex);
+ music_player_worker_stop(plugin_state->music_instance->worker);
+ plugin_state->intro_sound = false;
+#endif
+ goto skipintro;
+ }
+
+ //While playing game
+ if(plugin_state->scene == GAME_PLAY) {
+ // If the player is alive
+ if(plugin_state->player.health > 0) {
+ //Player speed
+ if(event.input.key == InputKeyUp) {
+ plugin_state->up = true;
+ } else if(event.input.key == InputKeyDown) {
+ plugin_state->down = true;
+ }
+ // Player rotation
+ if(event.input.key == InputKeyRight) {
+ plugin_state->right = true;
+ } else if(event.input.key == InputKeyLeft) {
+ plugin_state->left = true;
+ }
+ if(event.input.key == InputKeyOk) {
+ /*#ifdef SOUND
+ music_player_worker_load_rtttl_from_string(plugin_state->music_instance->worker, dspistol);
+#endif*/
+ if(plugin_state->fired) {
+ plugin_state->fired = false;
+ } else {
+ plugin_state->fired = true;
+ }
+ }
+ } else {
+ // Player is dead
+ if(event.input.key == InputKeyOk) plugin_state->scene = INTRO;
+ }
+ }
+ }
+ if(event.input.type == InputTypeRelease) {
+ if(plugin_state->player.health > 0) {
+ //Player speed
+ if(event.input.key == InputKeyUp) {
+ plugin_state->up = false;
+ } else if(event.input.key == InputKeyDown) {
+ plugin_state->down = false;
+ }
+ // Player rotation
+ if(event.input.key == InputKeyRight) {
+ plugin_state->right = false;
+ } else if(event.input.key == InputKeyLeft) {
+ plugin_state->left = false;
+ }
+ }
+ }
+ }
+
+ skipintro:
+ if(event.type == EventTypeTick) {
+ doom_game_tick(plugin_state);
+ }
+ }
+#ifdef SOUND
+ furi_mutex_release(plugin_state->music_instance->model_mutex);
+#endif
+ view_port_update(view_port);
+ release_mutex(&state_mutex, plugin_state);
+ }
+#ifdef SOUND
+ music_player_worker_free(plugin_state->music_instance->worker);
+ furi_mutex_free(plugin_state->music_instance->model_mutex);
+ free(plugin_state->music_instance->model);
+ free(plugin_state->music_instance);
+#endif
+ furi_record_close(RECORD_NOTIFICATION);
+ furi_timer_free(timer);
+ view_port_enabled_set(view_port, false);
+ gui_remove_view_port(gui, view_port);
+ furi_record_close("gui");
+ view_port_free(view_port);
+ furi_message_queue_free(event_queue);
+ free(plugin_state);
+ return 0;
+}
diff --git a/applications/plugins/doom/doom_10px.png b/applications/plugins/doom/doom_10px.png
new file mode 100644
index 000000000..4abf72cef
Binary files /dev/null and b/applications/plugins/doom/doom_10px.png differ
diff --git a/applications/plugins/doom/doom_music_player_worker.c b/applications/plugins/doom/doom_music_player_worker.c
new file mode 100644
index 000000000..406cf0576
--- /dev/null
+++ b/applications/plugins/doom/doom_music_player_worker.c
@@ -0,0 +1,499 @@
+#include "doom_music_player_worker.h"
+
+#include
+#include
+
+#include
+#include
+
+#include
+
+#define TAG "MusicPlayerWorker"
+
+#define MUSIC_PLAYER_FILETYPE "Flipper Music Format"
+#define MUSIC_PLAYER_VERSION 0
+
+#define SEMITONE_PAUSE 0xFF
+
+#define NOTE_C4 261.63f
+#define NOTE_C4_SEMITONE (4.0f * 12.0f)
+#define TWO_POW_TWELTH_ROOT 1.059463094359f
+
+typedef struct {
+ uint8_t semitone;
+ uint8_t duration;
+ uint8_t dots;
+} NoteBlock;
+
+ARRAY_DEF(NoteBlockArray, NoteBlock, M_POD_OPLIST);
+
+struct MusicPlayerWorker {
+ FuriThread* thread;
+ bool should_work;
+
+ MusicPlayerWorkerCallback callback;
+ void* callback_context;
+
+ float volume;
+ uint32_t bpm;
+ uint32_t duration;
+ uint32_t octave;
+ NoteBlockArray_t notes;
+};
+
+static int32_t music_player_worker_thread_callback(void* context) {
+ furi_assert(context);
+ MusicPlayerWorker* instance = context;
+
+ NoteBlockArray_it_t it;
+ NoteBlockArray_it(it, instance->notes);
+
+ while(instance->should_work) {
+ if(NoteBlockArray_end_p(it)) {
+ NoteBlockArray_it(it, instance->notes);
+ furi_delay_ms(10);
+ } else {
+ NoteBlock* note_block = NoteBlockArray_ref(it);
+
+ float note_from_a4 = (float)note_block->semitone - NOTE_C4_SEMITONE;
+ float frequency = NOTE_C4 * powf(TWO_POW_TWELTH_ROOT, note_from_a4);
+ float duration =
+ 60.0 * furi_kernel_get_tick_frequency() * 4 / instance->bpm / note_block->duration;
+ uint32_t dots = note_block->dots;
+ while(dots > 0) {
+ duration += duration / 2;
+ dots--;
+ }
+ uint32_t next_tick = furi_get_tick() + duration;
+ float volume = instance->volume;
+
+ if(instance->callback) {
+ instance->callback(
+ note_block->semitone,
+ note_block->dots,
+ note_block->duration,
+ 0.0,
+ instance->callback_context);
+ }
+
+ furi_hal_speaker_stop();
+ furi_hal_speaker_start(frequency, volume);
+ while(instance->should_work && furi_get_tick() < next_tick) {
+ volume *= 0.9945679;
+ furi_hal_speaker_set_volume(volume);
+ furi_delay_ms(2);
+ }
+ NoteBlockArray_next(it);
+ }
+ }
+
+ furi_hal_speaker_stop();
+
+ return 0;
+}
+
+MusicPlayerWorker* music_player_worker_alloc() {
+ MusicPlayerWorker* instance = malloc(sizeof(MusicPlayerWorker));
+
+ NoteBlockArray_init(instance->notes);
+
+ instance->thread = furi_thread_alloc();
+ furi_thread_set_name(instance->thread, "MusicPlayerWorker");
+ furi_thread_set_stack_size(instance->thread, 1024);
+ furi_thread_set_context(instance->thread, instance);
+ furi_thread_set_callback(instance->thread, music_player_worker_thread_callback);
+
+ instance->volume = 1.0f;
+
+ return instance;
+}
+
+void music_player_worker_free(MusicPlayerWorker* instance) {
+ furi_assert(instance);
+ furi_thread_free(instance->thread);
+ NoteBlockArray_clear(instance->notes);
+ free(instance);
+}
+
+static bool is_digit(const char c) {
+ return isdigit(c) != 0;
+}
+
+static bool is_letter(const char c) {
+ return islower(c) != 0 || isupper(c) != 0;
+}
+
+static bool is_space(const char c) {
+ return c == ' ' || c == '\t';
+}
+
+static size_t extract_number(const char* string, uint32_t* number) {
+ size_t ret = 0;
+ while(is_digit(*string)) {
+ *number *= 10;
+ *number += (*string - '0');
+ string++;
+ ret++;
+ }
+ return ret;
+}
+
+static size_t extract_dots(const char* string, uint32_t* number) {
+ size_t ret = 0;
+ while(*string == '.') {
+ *number += 1;
+ string++;
+ ret++;
+ }
+ return ret;
+}
+
+static size_t extract_char(const char* string, char* symbol) {
+ if(is_letter(*string)) {
+ *symbol = *string;
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+static size_t extract_sharp(const char* string, char* symbol) {
+ if(*string == '#' || *string == '_') {
+ *symbol = '#';
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+static size_t skip_till(const char* string, const char symbol) {
+ size_t ret = 0;
+ while(*string != '\0' && *string != symbol) {
+ string++;
+ ret++;
+ }
+ if(*string != symbol) {
+ ret = 0;
+ }
+ return ret;
+}
+
+static bool music_player_worker_add_note(
+ MusicPlayerWorker* instance,
+ uint8_t semitone,
+ uint8_t duration,
+ uint8_t dots) {
+ NoteBlock note_block;
+
+ note_block.semitone = semitone;
+ note_block.duration = duration;
+ note_block.dots = dots;
+
+ NoteBlockArray_push_back(instance->notes, note_block);
+
+ return true;
+}
+
+static int8_t note_to_semitone(const char note) {
+ switch(note) {
+ case 'C':
+ return 0;
+ // C#
+ case 'D':
+ return 2;
+ // D#
+ case 'E':
+ return 4;
+ case 'F':
+ return 5;
+ // F#
+ case 'G':
+ return 7;
+ // G#
+ case 'A':
+ return 9;
+ // A#
+ case 'B':
+ return 11;
+ default:
+ return 0;
+ }
+}
+
+static bool music_player_worker_parse_notes(MusicPlayerWorker* instance, const char* string) {
+ const char* cursor = string;
+ bool result = true;
+
+ while(*cursor != '\0') {
+ if(!is_space(*cursor)) {
+ uint32_t duration = 0;
+ char note_char = '\0';
+ char sharp_char = '\0';
+ uint32_t octave = 0;
+ uint32_t dots = 0;
+
+ // Parsing
+ cursor += extract_number(cursor, &duration);
+ cursor += extract_char(cursor, ¬e_char);
+ cursor += extract_sharp(cursor, &sharp_char);
+ cursor += extract_number(cursor, &octave);
+ cursor += extract_dots(cursor, &dots);
+
+ // Post processing
+ note_char = toupper(note_char);
+ if(!duration) {
+ duration = instance->duration;
+ }
+ if(!octave) {
+ octave = instance->octave;
+ }
+
+ // Validation
+ bool is_valid = true;
+ is_valid &= (duration >= 1 && duration <= 128);
+ is_valid &= ((note_char >= 'A' && note_char <= 'G') || note_char == 'P');
+ is_valid &= (sharp_char == '#' || sharp_char == '\0');
+ is_valid &= (octave <= 16);
+ is_valid &= (dots <= 16);
+ if(!is_valid) {
+ FURI_LOG_E(
+ TAG,
+ "Invalid note: %u%c%c%u.%u",
+ duration,
+ note_char == '\0' ? '_' : note_char,
+ sharp_char == '\0' ? '_' : sharp_char,
+ octave,
+ dots);
+ result = false;
+ break;
+ }
+
+ // Note to semitones
+ uint8_t semitone = 0;
+ if(note_char == 'P') {
+ semitone = SEMITONE_PAUSE;
+ } else {
+ semitone += octave * 12;
+ semitone += note_to_semitone(note_char);
+ semitone += sharp_char == '#' ? 1 : 0;
+ }
+
+ if(music_player_worker_add_note(instance, semitone, duration, dots)) {
+ FURI_LOG_D(
+ TAG,
+ "Added note: %c%c%u.%u = %u %u",
+ note_char == '\0' ? '_' : note_char,
+ sharp_char == '\0' ? '_' : sharp_char,
+ octave,
+ dots,
+ semitone,
+ duration);
+ } else {
+ FURI_LOG_E(
+ TAG,
+ "Invalid note: %c%c%u.%u = %u %u",
+ note_char == '\0' ? '_' : note_char,
+ sharp_char == '\0' ? '_' : sharp_char,
+ octave,
+ dots,
+ semitone,
+ duration);
+ }
+ cursor += skip_till(cursor, ',');
+ }
+
+ if(*cursor != '\0') cursor++;
+ }
+
+ return result;
+}
+
+bool music_player_worker_load(MusicPlayerWorker* instance, const char* file_path) {
+ furi_assert(instance);
+ furi_assert(file_path);
+
+ bool ret = false;
+ if(strcasestr(file_path, ".fmf")) {
+ ret = music_player_worker_load_fmf_from_file(instance, file_path);
+ } else {
+ ret = music_player_worker_load_rtttl_from_file(instance, file_path);
+ }
+ return ret;
+}
+
+bool music_player_worker_load_fmf_from_file(MusicPlayerWorker* instance, const char* file_path) {
+ furi_assert(instance);
+ furi_assert(file_path);
+
+ bool result = false;
+ string_t temp_str;
+ string_init(temp_str);
+
+ Storage* storage = furi_record_open(RECORD_STORAGE);
+ FlipperFormat* file = flipper_format_file_alloc(storage);
+
+ do {
+ if(!flipper_format_file_open_existing(file, file_path)) break;
+
+ uint32_t version = 0;
+ if(!flipper_format_read_header(file, temp_str, &version)) break;
+ if(string_cmp_str(temp_str, MUSIC_PLAYER_FILETYPE) || (version != MUSIC_PLAYER_VERSION)) {
+ FURI_LOG_E(TAG, "Incorrect file format or version");
+ break;
+ }
+
+ if(!flipper_format_read_uint32(file, "BPM", &instance->bpm, 1)) {
+ FURI_LOG_E(TAG, "BPM is missing");
+ break;
+ }
+ if(!flipper_format_read_uint32(file, "Duration", &instance->duration, 1)) {
+ FURI_LOG_E(TAG, "Duration is missing");
+ break;
+ }
+ if(!flipper_format_read_uint32(file, "Octave", &instance->octave, 1)) {
+ FURI_LOG_E(TAG, "Octave is missing");
+ break;
+ }
+
+ if(!flipper_format_read_string(file, "Notes", temp_str)) {
+ FURI_LOG_E(TAG, "Notes is missing");
+ break;
+ }
+
+ if(!music_player_worker_parse_notes(instance, string_get_cstr(temp_str))) {
+ break;
+ }
+
+ result = true;
+ } while(false);
+
+ furi_record_close(RECORD_STORAGE);
+ flipper_format_free(file);
+ string_clear(temp_str);
+
+ return result;
+}
+
+bool music_player_worker_load_rtttl_from_file(MusicPlayerWorker* instance, const char* file_path) {
+ furi_assert(instance);
+ furi_assert(file_path);
+
+ bool result = false;
+ string_t content;
+ string_init(content);
+ Storage* storage = furi_record_open(RECORD_STORAGE);
+ File* file = storage_file_alloc(storage);
+
+ do {
+ if(!storage_file_open(file, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
+ FURI_LOG_E(TAG, "Unable to open file");
+ break;
+ };
+
+ uint16_t ret = 0;
+ do {
+ uint8_t buffer[65] = {0};
+ ret = storage_file_read(file, buffer, sizeof(buffer) - 1);
+ for(size_t i = 0; i < ret; i++) {
+ string_push_back(content, buffer[i]);
+ }
+ } while(ret > 0);
+
+ string_strim(content);
+ if(!string_size(content)) {
+ FURI_LOG_E(TAG, "Empty file");
+ break;
+ }
+
+ if(!music_player_worker_load_rtttl_from_string(instance, string_get_cstr(content))) {
+ FURI_LOG_E(TAG, "Invalid file content");
+ break;
+ }
+
+ result = true;
+ } while(0);
+
+ storage_file_free(file);
+ furi_record_close(RECORD_STORAGE);
+ string_clear(content);
+
+ return result;
+}
+
+bool music_player_worker_load_rtttl_from_string(MusicPlayerWorker* instance, const char* string) {
+ furi_assert(instance);
+
+ const char* cursor = string;
+
+ // Skip name
+ cursor += skip_till(cursor, ':');
+ if(*cursor != ':') {
+ return false;
+ }
+
+ // Duration
+ cursor += skip_till(cursor, '=');
+ if(*cursor != '=') {
+ return false;
+ }
+ cursor++;
+ cursor += extract_number(cursor, &instance->duration);
+
+ // Octave
+ cursor += skip_till(cursor, '=');
+ if(*cursor != '=') {
+ return false;
+ }
+ cursor++;
+ cursor += extract_number(cursor, &instance->octave);
+
+ // BPM
+ cursor += skip_till(cursor, '=');
+ if(*cursor != '=') {
+ return false;
+ }
+ cursor++;
+ cursor += extract_number(cursor, &instance->bpm);
+
+ // Notes
+ cursor += skip_till(cursor, ':');
+ if(*cursor != ':') {
+ return false;
+ }
+ cursor++;
+ if(!music_player_worker_parse_notes(instance, cursor)) {
+ return false;
+ }
+
+ return true;
+}
+
+void music_player_worker_set_callback(
+ MusicPlayerWorker* instance,
+ MusicPlayerWorkerCallback callback,
+ void* context) {
+ furi_assert(instance);
+ instance->callback = callback;
+ instance->callback_context = context;
+}
+
+void music_player_worker_set_volume(MusicPlayerWorker* instance, float volume) {
+ furi_assert(instance);
+ instance->volume = volume;
+}
+
+void music_player_worker_start(MusicPlayerWorker* instance) {
+ furi_assert(instance);
+ furi_assert(instance->should_work == false);
+
+ instance->should_work = true;
+ furi_thread_start(instance->thread);
+}
+
+void music_player_worker_stop(MusicPlayerWorker* instance) {
+ furi_assert(instance);
+ furi_assert(instance->should_work == true);
+
+ instance->should_work = false;
+ furi_thread_join(instance->thread);
+}
diff --git a/applications/plugins/doom/doom_music_player_worker.h b/applications/plugins/doom/doom_music_player_worker.h
new file mode 100644
index 000000000..9958a9273
--- /dev/null
+++ b/applications/plugins/doom/doom_music_player_worker.h
@@ -0,0 +1,44 @@
+#pragma once
+
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef void (*MusicPlayerWorkerCallback)(
+ uint8_t semitone,
+ uint8_t dots,
+ uint8_t duration,
+ float position,
+ void* context);
+
+typedef struct MusicPlayerWorker MusicPlayerWorker;
+
+MusicPlayerWorker* music_player_worker_alloc();
+
+void music_player_worker_free(MusicPlayerWorker* instance);
+
+bool music_player_worker_load(MusicPlayerWorker* instance, const char* file_path);
+
+bool music_player_worker_load_fmf_from_file(MusicPlayerWorker* instance, const char* file_path);
+
+bool music_player_worker_load_rtttl_from_file(MusicPlayerWorker* instance, const char* file_path);
+
+bool music_player_worker_load_rtttl_from_string(MusicPlayerWorker* instance, const char* string);
+
+void music_player_worker_set_callback(
+ MusicPlayerWorker* instance,
+ MusicPlayerWorkerCallback callback,
+ void* context);
+
+void music_player_worker_set_volume(MusicPlayerWorker* instance, float volume);
+
+void music_player_worker_start(MusicPlayerWorker* instance);
+
+void music_player_worker_stop(MusicPlayerWorker* instance);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/applications/plugins/doom/entities.c b/applications/plugins/doom/entities.c
new file mode 100644
index 000000000..86c7f6ae1
--- /dev/null
+++ b/applications/plugins/doom/entities.c
@@ -0,0 +1,42 @@
+#include "entities.h"
+
+//extern "C"
+/*Player create_player(double x, double y){
+ return {create_coords((double) x + (double)0.5, (double) y + (double)0.5), create_coords(1, 0), create_coords(0, -0.66), 0, 100, 0};
+}*/
+
+Player create_player(double x, double y) {
+ Player p;
+ p.pos = create_coords((double)x + (double)0.5, (double)y + (double)0.5);
+ p.dir = create_coords(1, 0);
+ p.plane = create_coords(0, -0.66);
+ p.velocity = 0;
+ p.health = 100;
+ p.keys = 0;
+ return p; //{create_coords((double) x + (double)0.5, (double) y + (double)0.5), create_coords(1, 0), create_coords(0, -0.66), 0, 100, 0};
+}
+
+//extern "C"
+Entity
+ create_entity(uint8_t type, uint8_t x, uint8_t y, uint8_t initialState, uint8_t initialHealth) {
+ UID uid = create_uid(type, x, y);
+ Coords pos = create_coords((double)x + (double).5, (double)y + (double).5);
+ Entity new_entity; // = { uid, pos, initialState, initialHealth, 0, 0 };
+ new_entity.uid = uid;
+ new_entity.pos = pos;
+ new_entity.state = initialState;
+ new_entity.health = initialHealth;
+ new_entity.distance = 0;
+ new_entity.timer = 0;
+ return new_entity;
+}
+
+//extern "C"
+StaticEntity crate_static_entity(UID uid, uint8_t x, uint8_t y, bool active) {
+ StaticEntity ent;
+ ent.uid = uid;
+ ent.x = x;
+ ent.y = y;
+ ent.active = active;
+ return ent;
+}
\ No newline at end of file
diff --git a/applications/plugins/doom/entities.h b/applications/plugins/doom/entities.h
new file mode 100644
index 000000000..ef5eb1a78
--- /dev/null
+++ b/applications/plugins/doom/entities.h
@@ -0,0 +1,56 @@
+#ifndef _entities_h
+#define _entities_h
+#include
+#include
+#include "types.h"
+
+// Shortcuts
+//#define create_player(x, y) {create_coords((double) x + (double)0.5, (double) y + (double)0.5), create_coords(1, 0), create_coords(0, -0.66), 0, 100}
+
+#define create_enemy(x, y) create_entity(E_ENEMY, x, y, S_STAND, 50)
+#define create_medikit(x, y) create_entity(E_MEDIKIT, x, y, S_STAND, 0)
+#define create_key(x, y) create_entity(E_KEY, x, y, S_STAND, 0)
+#define create_fireball(x, y, dir) create_entity(E_FIREBALL, x, y, S_STAND, dir)
+#define create_door(x, y) create_entity(E_DOOR, x, y, S_STAND, 0)
+
+// entity statuses
+#define S_STAND 0
+#define S_ALERT 1
+#define S_FIRING 2
+#define S_MELEE 3
+#define S_HIT 4
+#define S_DEAD 5
+#define S_HIDDEN 6
+#define S_OPEN 7
+#define S_CLOSE 8
+
+typedef struct Player {
+ Coords pos;
+ Coords dir;
+ Coords plane;
+ double velocity;
+ uint8_t health;
+ uint8_t keys;
+} Player;
+
+typedef struct Entity {
+ UID uid;
+ Coords pos;
+ uint8_t state;
+ uint8_t health; // angle for fireballs
+ uint8_t distance;
+ uint8_t timer;
+} Entity;
+
+typedef struct StaticEntity {
+ UID uid;
+ uint8_t x;
+ uint8_t y;
+ bool active;
+} StaticEntity;
+
+Entity
+ create_entity(uint8_t type, uint8_t x, uint8_t y, uint8_t initialState, uint8_t initialHealth);
+StaticEntity create_static_entity(UID uid, uint8_t x, uint8_t y, bool active);
+Player create_player(double x, double y);
+#endif
diff --git a/applications/plugins/doom/level.h b/applications/plugins/doom/level.h
new file mode 100644
index 000000000..4d92a1cc6
--- /dev/null
+++ b/applications/plugins/doom/level.h
@@ -0,0 +1,188 @@
+#ifndef _level_h
+#define _level_h
+
+#include "constants.h"
+
+/*
+ Based on E1M1 from Wolfenstein 3D
+
+ ################################################################
+ #############################...........########################
+ ######....###################........E..########################
+ ######....########..........#...........#...####################
+ ######.....#######..........L.....E.......M.####################
+ ######.....#######..........#...........#...####################
+ ##################...########...........########################
+ ######.........###...########...........########################
+ ######.........###...#############D#############################
+ ######.........#......E##########...############################
+ ######....E....D...E...##########...############################
+ ######.........#.......##########...############################
+ ######....E....##################...############################
+ #...##.........##################...############################
+ #.K.######D######################...############################
+ #...#####...###############...#E.....K##########################
+ ##D######...###############..####...############################
+ #...#####...###############..####...############################
+ #...#...#...###############..####...############################
+ #...D...#...#####################...############################
+ #...#...#...#####################...############################
+ #...######D#######################L#############################
+ #.E.##.........#################.....#################........##
+ #...##.........############...............############........##
+ #...##...E.....############...............############........##
+ #....#.........############...E.......E....#.........#........##
+ #....L....K....############................D....E....D....E...##
+ #....#.........############................#.........#........##
+ #...##.....E...############...............####....####........##
+ #...##.........############...............#####..#####.....M..##
+ #...##.........#################.....##########..#####........##
+ #...######L#######################D############..###############
+ #...#####...#####################...###########..###############
+ #E.E#####...#####################...###########..###############
+ #...#...#...#####################.E.###########..###############
+ #...D.M.#...#####################...###########..###############
+ #...#...#...#####################...###########..###.#.#.#.#####
+ #...#####...#####################...###########...#.........####
+ #...#####...#####################...###########...D....E..K.####
+ #................##......########...###########...#.........####
+ #....E........E...L...E...X######...################.#.#.#.#####
+ #................##......########...############################
+ #################################...############################
+ #############..#..#..#############L#############################
+ ###########....#..#.########....#...#....#######################
+ #############.....##########.P..D...D....#######################
+ ############################....#...#....#######################
+ ##############..#################...############################
+ ##############..############....#...#....#######################
+ ############################....D...D....#######################
+ ############################....#...#....#######################
+ #################################...############################
+ ############################.............#######################
+ ############################..........EK.#######################
+ ############################.............#######################
+ ################################################################
+*/
+
+/*
+ Same map above built from some regexp replacements using the legend above.
+ Using this way lets me use only 4 bit to store each block
+*/
+const uint8_t sto_level_1[LEVEL_SIZE] = {
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00,
+ 0x00, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x02, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x90, 0xFF, 0xFF, 0xFF, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xF2,
+ 0x00, 0x00, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0x4F, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0x40, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x20, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0xF0, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0xF0, 0x00, 0xFF, 0x00, 0x02, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0xF0, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0xF0, 0x00, 0x05, 0x00, 0x00, 0x90, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x00, 0x20, 0x00, 0xFF,
+ 0xF0, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0xF0, 0x00, 0xFF, 0x00, 0x00, 0x02, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0xF0, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x08, 0x00, 0xFF,
+ 0xF0, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF2, 0x02, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x20, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0x40, 0x80, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0x0F, 0x0F, 0x0F, 0x0F, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x40, 0x00, 0x02, 0x00, 0x90, 0xFF, 0xFF,
+ 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
+ 0xF0, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x50, 0x00, 0x20, 0x00, 0x7F, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x0F, 0x0F, 0x0F, 0xFF, 0xFF,
+ 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x0F, 0x00, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
+ 0xF0, 0x00, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00,
+ 0x40, 0x00, 0x40, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
+ 0xF0, 0x00, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
+ 0xF0, 0x00, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
+ 0x40, 0x00, 0x40, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
+ 0xF0, 0x00, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x29, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+};
+
+#endif
diff --git a/applications/plugins/doom/sound.h b/applications/plugins/doom/sound.h
new file mode 100644
index 000000000..b33ac93c6
--- /dev/null
+++ b/applications/plugins/doom/sound.h
@@ -0,0 +1,35 @@
+#ifndef sound_h
+#define sound_h
+#include
+#include
+#include
+#include
+#include "doom_music_player_worker.h"
+
+//static const char dspistol[] = "AnyConv:d=,o=,b=120:408,40p,40p,40p,40p,405,40p,40p,40p,405,30p.,30p.,30p.,13p";
+static const char dsintro[] =
+ "Doom:d=32,o=4,b=56:f,f,f5,f,f,d#5,f,f,c#5,f,f,b,f,f,c5,c#5,f,f,f5,f,f,d#5,f,f,c#5,f,f,8b.,f,f,f5,f,f,d#5,f,f,c#5,f,f,b,f,f,c5,c#5,f,f,f5,f,f,d#5,f,f,c#5,f,f,8b.,a#,a#,a#5,a#,a#,g#5,a#,a#,f#5,a#,a#,e5,a#,a#,f5,f#5,a#,a#,a#5,a#,a#,g#5,a#,a#,f#5,a#,a#,8e5";
+//static const char dsgetpow[] = "dsgetpow:d=,o=,b=120:407,40p,30.6,407,40p,406,40p,407,40p,40p,407,30p.,407";
+//static const char dsnoway[] = "dsnoway:d=,o=,b=120:407,30.4";
+
+#define MUSIC_PLAYER_SEMITONE_HISTORY_SIZE 4
+static const float MUSIC_PLAYER_VOLUMES[] = {0, .25, .5, .75, 1};
+
+typedef struct {
+ uint8_t semitone_history[MUSIC_PLAYER_SEMITONE_HISTORY_SIZE];
+ uint8_t duration_history[MUSIC_PLAYER_SEMITONE_HISTORY_SIZE];
+
+ uint8_t volume;
+ uint8_t semitone;
+ uint8_t dots;
+ uint8_t duration;
+ float position;
+} MusicPlayerModel;
+
+typedef struct {
+ MusicPlayerModel* model;
+ MusicPlayerWorker* worker;
+ FuriMutex** model_mutex;
+} MusicPlayer;
+
+#endif
\ No newline at end of file
diff --git a/applications/plugins/doom/types.c b/applications/plugins/doom/types.c
new file mode 100644
index 000000000..6b55d56a7
--- /dev/null
+++ b/applications/plugins/doom/types.c
@@ -0,0 +1,33 @@
+#include "types.h"
+
+/*template
+inline T sq(T value) {
+ return value * value;
+}*/
+
+double sq(double val) {
+ return val * val;
+}
+
+//extern "C"
+Coords create_coords(double x, double y) {
+ Coords cord;
+ cord.x = x;
+ cord.y = y;
+ return cord;
+}
+
+//extern "C"
+uint8_t coords_distance(Coords* a, Coords* b) {
+ return sqrt(sq(a->x - b->x) + sq(a->y - b->y)) * 20;
+}
+
+//extern "C"
+UID create_uid(uint8_t type, uint8_t x, uint8_t y) {
+ return ((y << 6) | x) << 4 | type;
+}
+
+//extern "C"
+uint8_t uid_get_type(UID uid) {
+ return uid & 0x0F;
+}
diff --git a/applications/plugins/doom/types.h b/applications/plugins/doom/types.h
new file mode 100644
index 000000000..b8579f645
--- /dev/null
+++ b/applications/plugins/doom/types.h
@@ -0,0 +1,36 @@
+#ifndef _types_h
+#define _types_h
+
+#include
+#include
+//#include "constants.h"
+
+#define UID_null 0
+
+// Entity types (legend applies to level.h)
+#define E_FLOOR 0x0 // . (also null)
+#define E_WALL 0xF // #
+#define E_PLAYER 0x1 // P
+#define E_ENEMY 0x2 // E
+#define E_DOOR 0x4 // D
+#define E_LOCKEDDOOR 0x5 // L
+#define E_EXIT 0x7 // X
+// collectable entities >= 0x8
+#define E_MEDIKIT 0x8 // M
+#define E_KEY 0x9 // K
+#define E_FIREBALL 0xA // not in map
+
+typedef uint16_t UID;
+typedef uint8_t EType;
+
+typedef struct Coords {
+ double x;
+ double y;
+} Coords;
+
+UID create_uid(EType type, uint8_t x, uint8_t y);
+EType uid_get_type(UID uid);
+Coords create_coords(double x, double y);
+uint8_t coords_distance(Coords* a, Coords* b);
+
+#endif
diff --git a/applications/plugins/flappy_bird/application.fam b/applications/plugins/flappy_bird/application.fam
new file mode 100644
index 000000000..a7b53163c
--- /dev/null
+++ b/applications/plugins/flappy_bird/application.fam
@@ -0,0 +1,12 @@
+App(
+ appid="game_flappybird",
+ name="Flappy Bird",
+ apptype=FlipperAppType.PLUGIN,
+ entry_point="flappy_game_app",
+ cdefines=["APP_FLAPPY_GAME"],
+ requires=["gui"],
+ stack_size=4 * 1024,
+ order=100,
+ fap_icon="flappy_10px.png",
+ fap_category="Games",
+)
diff --git a/applications/plugins/flappy_bird/bird.h b/applications/plugins/flappy_bird/bird.h
new file mode 100644
index 000000000..8162080be
--- /dev/null
+++ b/applications/plugins/flappy_bird/bird.h
@@ -0,0 +1,54 @@
+#include
+
+uint8_t bird_array[3][15][11] = {
+ {
+ {0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0},
+ {0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0},
+ {0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0},
+ {0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0},
+ {0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0},
+ {0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1},
+ {1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1},
+ {1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1},
+ {1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1},
+ {1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0},
+ {1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0},
+ {0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0},
+ {0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0},
+ {0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0},
+ {0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0},
+ },
+ {
+ {0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0},
+ {0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0},
+ {0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0},
+ {0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0},
+ {0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0},
+ {0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1},
+ {1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1},
+ {1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1},
+ {1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1},
+ {1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0},
+ {1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0},
+ {0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0},
+ {0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0},
+ {0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0},
+ {0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0},
+ },
+ {
+ {0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0},
+ {0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0},
+ {0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0},
+ {0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0},
+ {0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0},
+ {0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1},
+ {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1},
+ {1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1},
+ {1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1},
+ {1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0},
+ {1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0},
+ {0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0},
+ {0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0},
+ {0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0},
+ {0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0},
+ }};
diff --git a/applications/plugins/flappy_bird/flappy_10px.png b/applications/plugins/flappy_bird/flappy_10px.png
new file mode 100644
index 000000000..636f5fc37
Binary files /dev/null and b/applications/plugins/flappy_bird/flappy_10px.png differ
diff --git a/applications/plugins/flappy_bird/flappy_bird.c b/applications/plugins/flappy_bird/flappy_bird.c
new file mode 100644
index 000000000..1eb6a717e
--- /dev/null
+++ b/applications/plugins/flappy_bird/flappy_bird.c
@@ -0,0 +1,357 @@
+#include
+#include
+#include
+#include
+
+#include "bird.h"
+
+#define TAG "Flappy"
+#define DEBUG false
+
+#define FLAPPY_BIRD_HEIGHT 15
+#define FLAPPY_BIRD_WIDTH 10
+
+#define FLAPPY_PILAR_MAX 6
+#define FLAPPY_PILAR_DIST 40
+
+#define FLAPPY_GAB_HEIGHT 25
+#define FLAPPY_GAB_WIDTH 5
+
+#define FLAPPY_GRAVITY_JUMP -1.1
+#define FLAPPY_GRAVITY_TICK 0.15
+
+#define FLIPPER_LCD_WIDTH 128
+#define FLIPPER_LCD_HEIGHT 64
+
+typedef enum {
+ EventTypeTick,
+ EventTypeKey,
+} EventType;
+
+typedef struct {
+ int x;
+ int y;
+} POINT;
+
+typedef struct {
+ float gravity;
+ POINT point;
+} BIRD;
+
+typedef struct {
+ POINT point;
+ int height;
+ int visible;
+ bool passed;
+} PILAR;
+
+typedef enum {
+ GameStateLife,
+ GameStateGameOver,
+} State;
+
+typedef struct {
+ BIRD bird;
+ int points;
+ int pilars_count;
+ PILAR pilars[FLAPPY_PILAR_MAX];
+ bool debug;
+ State state;
+} GameState;
+
+typedef struct {
+ EventType type;
+ InputEvent input;
+} GameEvent;
+
+typedef enum {
+ DirectionUp,
+ DirectionRight,
+ DirectionDown,
+ DirectionLeft,
+} Direction;
+
+static void flappy_game_random_pilar(GameState* const game_state) {
+ PILAR pilar;
+
+ pilar.passed = false;
+ pilar.visible = 1;
+ pilar.height = random() % (FLIPPER_LCD_HEIGHT - FLAPPY_GAB_HEIGHT) + 1;
+ pilar.point.y = 0;
+ pilar.point.x = FLIPPER_LCD_WIDTH + FLAPPY_GAB_WIDTH + 1;
+
+ game_state->pilars_count++;
+ game_state->pilars[game_state->pilars_count % FLAPPY_PILAR_MAX] = pilar;
+}
+
+static void flappy_game_state_init(GameState* const game_state) {
+ BIRD bird;
+ bird.gravity = 0.0f;
+ bird.point.x = 15;
+ bird.point.y = 32;
+
+ game_state->debug = DEBUG;
+ game_state->bird = bird;
+ game_state->pilars_count = 0;
+ game_state->points = 0;
+ game_state->state = GameStateLife;
+ memset(game_state->pilars, 0, sizeof(game_state->pilars));
+
+ flappy_game_random_pilar(game_state);
+}
+
+// static void flappy_game_reset(GameState* const game_state) {
+// FURI_LOG_I(TAG, "Reset Game State\r\n"); // Resetting State
+// }
+
+static void flappy_game_tick(GameState* const game_state) {
+ if(game_state->state == GameStateLife) {
+ if(!game_state->debug) {
+ game_state->bird.gravity += FLAPPY_GRAVITY_TICK;
+ game_state->bird.point.y += game_state->bird.gravity;
+ }
+
+ // Checking the location of the last respawned pilar.
+ PILAR* pilar = &game_state->pilars[game_state->pilars_count % FLAPPY_PILAR_MAX];
+ if(pilar->point.x == (FLIPPER_LCD_WIDTH - FLAPPY_PILAR_DIST))
+ flappy_game_random_pilar(game_state);
+
+ // Updating the position/status of the pilars (visiblity, posotion, game points)
+ // | | | | |
+ // | | | | |
+ // |__| | |__|
+ // _____X | X_____
+ // | | | | | // [Pos + Width of pilar] >= [Bird Pos]
+ // |_____| | |_____|
+ // X <----> | X <->
+ // Bird Pos + Lenght of the bird] >= [Pilar]
+ for(int i = 0; i < FLAPPY_PILAR_MAX; i++) {
+ PILAR* pilar = &game_state->pilars[i];
+ if(pilar != NULL && pilar->visible && game_state->state == GameStateLife) {
+ pilar->point.x--;
+ if(game_state->bird.point.x >= pilar->point.x + FLAPPY_GAB_WIDTH &&
+ pilar->passed == false) {
+ pilar->passed = true;
+ game_state->points++;
+ }
+ if(pilar->point.x < -FLAPPY_GAB_WIDTH) pilar->visible = 0;
+
+ // Checking out of bounds
+ if(game_state->bird.point.y < 0 - FLAPPY_BIRD_WIDTH ||
+ game_state->bird.point.y > FLIPPER_LCD_HEIGHT) {
+ game_state->state = GameStateGameOver;
+ break;
+ }
+
+ // Bird inbetween pipes
+ if((game_state->bird.point.x + FLAPPY_BIRD_HEIGHT >= pilar->point.x) &&
+ (game_state->bird.point.x <= pilar->point.x + FLAPPY_GAB_WIDTH)) {
+ // Bird below Bottom Pipe
+ if(game_state->bird.point.y + FLAPPY_BIRD_WIDTH - 2 >=
+ pilar->height + FLAPPY_GAB_HEIGHT) {
+ game_state->state = GameStateGameOver;
+ break;
+ }
+
+ // Bird above Upper Pipe
+ if(game_state->bird.point.y < pilar->height) {
+ game_state->state = GameStateGameOver;
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+static void flappy_game_flap(GameState* const game_state) {
+ game_state->bird.gravity = FLAPPY_GRAVITY_JUMP;
+}
+
+static void flappy_game_render_callback(Canvas* const canvas, void* ctx) {
+ const GameState* game_state = acquire_mutex((ValueMutex*)ctx, 25);
+ if(game_state == NULL) {
+ return;
+ }
+
+ canvas_draw_frame(canvas, 0, 0, 128, 64);
+
+ if(game_state->state == GameStateLife) {
+ // Pilars
+ for(int i = 0; i < FLAPPY_PILAR_MAX; i++) {
+ const PILAR* pilar = &game_state->pilars[i];
+ if(pilar != NULL && pilar->visible == 1) {
+ canvas_draw_frame(
+ canvas, pilar->point.x, pilar->point.y, FLAPPY_GAB_WIDTH, pilar->height);
+
+ canvas_draw_frame(
+ canvas,
+ pilar->point.x,
+ pilar->point.y + pilar->height + FLAPPY_GAB_HEIGHT,
+ FLAPPY_GAB_WIDTH,
+ FLIPPER_LCD_HEIGHT - pilar->height - FLAPPY_GAB_HEIGHT);
+ }
+ }
+ // Flappy
+ for(int h = 0; h < FLAPPY_BIRD_HEIGHT; h++) {
+ for(int w = 0; w < FLAPPY_BIRD_WIDTH; w++) {
+ // Switch animation
+ int bird = 0;
+ if(game_state->bird.gravity < -0.5)
+ bird = 1;
+ else
+ bird = 2;
+
+ // Draw bird pixels
+ if(bird_array[bird][h][w] == 1) {
+ int x = game_state->bird.point.x + h;
+ int y = game_state->bird.point.y + w;
+
+ canvas_draw_dot(canvas, x, y);
+ }
+ }
+ }
+
+ // Stats
+
+ canvas_set_font(canvas, FontSecondary);
+ char buffer[12];
+ snprintf(buffer, sizeof(buffer), "Score: %u", game_state->points);
+ canvas_draw_str_aligned(canvas, 100, 12, AlignCenter, AlignBottom, buffer);
+
+ if(game_state->debug) {
+ char coordinates[20];
+ snprintf(coordinates, sizeof(coordinates), "Y: %u", game_state->bird.point.y);
+ canvas_draw_str_aligned(canvas, 1, 12, AlignCenter, AlignBottom, coordinates);
+ }
+ }
+
+ if(game_state->state == GameStateGameOver) {
+ // Screen is 128x64 px
+ canvas_set_color(canvas, ColorWhite);
+ canvas_draw_box(canvas, 34, 20, 62, 24);
+
+ canvas_set_color(canvas, ColorBlack);
+ canvas_draw_frame(canvas, 34, 20, 62, 24);
+
+ canvas_set_font(canvas, FontPrimary);
+ canvas_draw_str(canvas, 37, 31, "Game Over");
+
+ /*if(game_state->points != 0 && game_state->points % 5 == 0) {
+ DOLPHIN_DEED(getRandomDeed());
+ }*/
+
+ canvas_set_font(canvas, FontSecondary);
+ char buffer[12];
+ snprintf(buffer, sizeof(buffer), "Score: %u", game_state->points);
+ canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignBottom, buffer);
+ }
+
+ release_mutex((ValueMutex*)ctx, game_state);
+}
+
+static void flappy_game_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
+ furi_assert(event_queue);
+
+ GameEvent event = {.type = EventTypeKey, .input = *input_event};
+ furi_message_queue_put(event_queue, &event, FuriWaitForever);
+}
+
+static void flappy_game_update_timer_callback(FuriMessageQueue* event_queue) {
+ furi_assert(event_queue);
+
+ GameEvent event = {.type = EventTypeTick};
+ furi_message_queue_put(event_queue, &event, 0);
+}
+
+int32_t flappy_game_app(void* p) {
+ UNUSED(p);
+ int32_t return_code = 0;
+
+ FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(GameEvent));
+
+ GameState* game_state = malloc(sizeof(GameState));
+ flappy_game_state_init(game_state);
+
+ ValueMutex state_mutex;
+ if(!init_mutex(&state_mutex, game_state, sizeof(GameState))) {
+ FURI_LOG_E(TAG, "cannot create mutex\r\n");
+ return_code = 255;
+ goto free_and_exit;
+ }
+
+ // Set system callbacks
+ ViewPort* view_port = view_port_alloc();
+ view_port_draw_callback_set(view_port, flappy_game_render_callback, &state_mutex);
+ view_port_input_callback_set(view_port, flappy_game_input_callback, event_queue);
+
+ FuriTimer* timer =
+ furi_timer_alloc(flappy_game_update_timer_callback, FuriTimerTypePeriodic, event_queue);
+ furi_timer_start(timer, furi_kernel_get_tick_frequency() / 25);
+
+ // Open GUI and register view_port
+ Gui* gui = furi_record_open(RECORD_GUI);
+ gui_add_view_port(gui, view_port, GuiLayerFullscreen);
+
+ GameEvent event;
+ for(bool processing = true; processing;) {
+ FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
+ GameState* game_state = (GameState*)acquire_mutex_block(&state_mutex);
+
+ if(event_status == FuriStatusOk) {
+ // press events
+ if(event.type == EventTypeKey) {
+ if(event.input.type == InputTypePress) {
+ switch(event.input.key) {
+ case InputKeyUp:
+ game_state->bird.point.y--;
+ break;
+ case InputKeyDown:
+ game_state->bird.point.y++;
+ break;
+ case InputKeyRight:
+ game_state->bird.point.x++;
+ break;
+ case InputKeyLeft:
+ game_state->bird.point.x--;
+ break;
+ case InputKeyOk:
+ if(game_state->state == GameStateGameOver) {
+ flappy_game_state_init(game_state);
+ }
+
+ if(game_state->state == GameStateLife) {
+ flappy_game_flap(game_state);
+ }
+
+ break;
+ case InputKeyBack:
+ processing = false;
+ break;
+ }
+ }
+ } else if(event.type == EventTypeTick) {
+ flappy_game_tick(game_state);
+ }
+ } else {
+ //FURI_LOG_D(TAG, "osMessageQueue: event timeout");
+ // event timeout
+ }
+
+ view_port_update(view_port);
+ release_mutex(&state_mutex, game_state);
+ }
+
+ furi_timer_free(timer);
+ view_port_enabled_set(view_port, false);
+ gui_remove_view_port(gui, view_port);
+ furi_record_close(RECORD_GUI);
+ view_port_free(view_port);
+ delete_mutex(&state_mutex);
+
+free_and_exit:
+ free(game_state);
+ furi_message_queue_free(event_queue);
+
+ return return_code;
+}
diff --git a/applications/plugins/music_player/application.fam b/applications/plugins/music_player/application.fam
index 12c7a2604..f0c319e8d 100644
--- a/applications/plugins/music_player/application.fam
+++ b/applications/plugins/music_player/application.fam
@@ -10,7 +10,7 @@ App(
],
provides=["music_player_start"],
stack_size=2 * 1024,
- order=20,
+ order=45,
fap_icon="../../../assets/icons/Archive/music_10px.png",
fap_category="Games",
)
diff --git a/applications/plugins/music_player/music_player_worker.h b/applications/plugins/music_player/music_player_worker.h
index 3aa99ea37..9958a9273 100644
--- a/applications/plugins/music_player/music_player_worker.h
+++ b/applications/plugins/music_player/music_player_worker.h
@@ -3,6 +3,10 @@
#include
#include
+#ifdef __cplusplus
+extern "C" {
+#endif
+
typedef void (*MusicPlayerWorkerCallback)(
uint8_t semitone,
uint8_t dots,
@@ -34,3 +38,7 @@ void music_player_worker_set_volume(MusicPlayerWorker* instance, float volume);
void music_player_worker_start(MusicPlayerWorker* instance);
void music_player_worker_stop(MusicPlayerWorker* instance);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/applications/plugins/subbrute/scene/subbrute_scene_entrypoint.c b/applications/plugins/subbrute/scene/subbrute_scene_entrypoint.c
index 267c1d82f..6f8497b9f 100644
--- a/applications/plugins/subbrute/scene/subbrute_scene_entrypoint.c
+++ b/applications/plugins/subbrute/scene/subbrute_scene_entrypoint.c
@@ -16,11 +16,11 @@ void subbrute_scene_entrypoint_menu_callback(SubBruteState* context, uint32_t in
case SubBruteAttackCAME12bit307:
case SubBruteAttackCAME12bit433:
case SubBruteAttackCAME12bit868:
- if (index == SubBruteAttackCAME12bit307) {
+ if(index == SubBruteAttackCAME12bit307) {
context->frequency = 307800000;
- } else if (index == SubBruteAttackCAME12bit433) {
+ } else if(index == SubBruteAttackCAME12bit433) {
context->frequency = 433920000;
- } else if (index == SubBruteAttackCAME12bit868) {
+ } else if(index == SubBruteAttackCAME12bit868) {
context->frequency = 868350000;
}
context->bit = 12;
diff --git a/applications/plugins/zombiez/application.fam b/applications/plugins/zombiez/application.fam
new file mode 100644
index 000000000..1d46f8bc0
--- /dev/null
+++ b/applications/plugins/zombiez/application.fam
@@ -0,0 +1,12 @@
+App(
+ appid="game_zombiez",
+ name="Zombiez",
+ apptype=FlipperAppType.PLUGIN,
+ entry_point="zombiez_game_app",
+ cdefines=["APP_ZOMBIEZ_GAME"],
+ requires=["gui"],
+ stack_size=2 * 1024,
+ order=280,
+ fap_icon="zombie_10px.png",
+ fap_category="Games",
+)
diff --git a/applications/plugins/zombiez/zombie_10px.png b/applications/plugins/zombiez/zombie_10px.png
new file mode 100644
index 000000000..37363ec04
Binary files /dev/null and b/applications/plugins/zombiez/zombie_10px.png differ
diff --git a/applications/plugins/zombiez/zombiez.c b/applications/plugins/zombiez/zombiez.c
new file mode 100644
index 000000000..b51bdc76c
--- /dev/null
+++ b/applications/plugins/zombiez/zombiez.c
@@ -0,0 +1,400 @@
+#include
+#include
+#include
+#include
+
+//ORIGINAL REPO: https://github.com/Dooskington/flipperzero-zombiez
+//AUTHORS: https://github.com/Dooskington | https://github.com/DevMilanIan
+
+#include "zombiez.h"
+
+#define ZOMBIES_MAX 3
+#define ZOMBIES_WIDTH 5
+#define ZOMBIES_HEIGHT 8
+#define PROJECTILES_MAX 10
+
+#define MIN_Y 5
+#define MAX_Y 58
+#define WALL_X 16
+#define PLAYER_START_X 8
+#define PLAYER_START_Y (MAX_Y - MIN_Y) / 2
+
+typedef enum {
+ EventTypeTick,
+ EventTypeKey,
+} EventType;
+
+typedef struct {
+ EventType type;
+ InputEvent input;
+} PluginEvent;
+
+typedef enum { GameStatePlaying, GameStateGameOver } GameState;
+
+typedef struct {
+ int x;
+ int y;
+} Point;
+
+typedef struct {
+ Point position;
+ int hp;
+} Player;
+
+typedef struct {
+ Point position;
+ int hp;
+} Zombie;
+
+typedef struct {
+ Point position;
+} Projectile;
+
+typedef struct {
+ GameState game_state;
+ Player player;
+
+ size_t zombies_count;
+ Zombie* zombies[ZOMBIES_MAX];
+
+ size_t projectiles_count;
+ Projectile* projectiles[PROJECTILES_MAX];
+
+ uint16_t score;
+ bool input_shoot;
+} PluginState;
+
+static void render_callback(Canvas* const canvas, void* ctx) {
+ const PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25);
+ if(plugin_state == NULL) {
+ return;
+ }
+
+ canvas_draw_frame(canvas, 0, 0, 128, 64);
+
+ canvas_set_font(canvas, FontPrimary);
+ canvas_draw_str_aligned(
+ canvas,
+ plugin_state->player.position.x,
+ plugin_state->player.position.y,
+ AlignCenter,
+ AlignCenter,
+ "@");
+
+ canvas_draw_line(canvas, WALL_X, 0, WALL_X, 64);
+ canvas_draw_line(canvas, WALL_X + 2, 4, WALL_X + 2, 59);
+
+ for(int i = 0; i < PROJECTILES_MAX; ++i) {
+ Projectile* p = plugin_state->projectiles[i];
+ if(p != NULL) {
+ canvas_draw_circle(canvas, p->position.x, p->position.y, 3);
+ }
+ }
+
+ for(int i = 0; i < ZOMBIES_MAX; ++i) {
+ Zombie* z = plugin_state->zombies[i];
+ if(z != NULL) {
+ for(int h = 0; h < ZOMBIES_HEIGHT; h++) {
+ for(int w = 0; w < ZOMBIES_WIDTH; w++) {
+ // Switch animation
+ int zIdx = 0;
+ if(z->position.x % 2 == 0) {
+ zIdx = 1;
+ }
+
+ // Draw zombie pixels
+ if(zombie_array[zIdx][h][w] == 1) {
+ int x = z->position.x + w;
+ int y = z->position.y + h;
+
+ canvas_draw_dot(canvas, x, y);
+ }
+ }
+ }
+ }
+ }
+
+ int heart;
+ if((plugin_state->player.hp - 10) > 5) { // 16, 17, 18, 19, 20
+ heart = 0;
+ } else if((plugin_state->player.hp - 5) > 5) { // 11, 12, 13, 14, 15
+ heart = 1;
+ } else if((plugin_state->player.hp - 3) > 2) { // 6, 7, 8, 9, 10
+ heart = 2;
+ } else if(plugin_state->player.hp > 0) { // 1, 2, 3, 4, 5
+ heart = 3;
+ } else { // 0
+ heart = 4;
+ }
+ // visual representation of health
+ for(int h = 0; h < 5; h++) {
+ for(int w = 0; w < 5; w++) {
+ if(heart_array[heart][h][w] == 1) {
+ int x = 124 - w;
+ int y = 56 + h;
+
+ canvas_draw_dot(canvas, x, y);
+ }
+ }
+ }
+
+ // buffer hp + score
+ char hpBuffer[8];
+ char scoreBuffer[14];
+
+ if(plugin_state->game_state == GameStatePlaying) {
+ // display ammo / reload
+ if(plugin_state->projectiles_count >= PROJECTILES_MAX) {
+ canvas_draw_str_aligned(canvas, 24, 10, AlignLeft, AlignCenter, "RELOAD");
+ } else {
+ for(uint8_t i = 0; i < (PROJECTILES_MAX - plugin_state->projectiles_count); i++) {
+ canvas_draw_box(canvas, 24 + (4 * i), 6, 2, 4);
+ }
+ }
+ // display hp + score
+ snprintf(hpBuffer, sizeof(hpBuffer), "%u", plugin_state->player.hp);
+ canvas_draw_str_aligned(canvas, 118, 62, AlignRight, AlignBottom, hpBuffer);
+
+ snprintf(scoreBuffer, sizeof(scoreBuffer), "%u", plugin_state->score);
+ canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, scoreBuffer);
+ }
+ // Game Over banner
+ if(plugin_state->game_state == GameStateGameOver) {
+ // Screen is 128x64 px
+ canvas_set_color(canvas, ColorWhite);
+ canvas_draw_box(canvas, 34, 20, 62, 24);
+
+ canvas_set_color(canvas, ColorBlack);
+ canvas_draw_frame(canvas, 34, 20, 62, 24);
+
+ canvas_set_font(canvas, FontPrimary);
+ canvas_draw_str(canvas, 37, 31, "Game Over");
+
+ canvas_set_font(canvas, FontSecondary);
+ snprintf(scoreBuffer, sizeof(scoreBuffer), "Score: %u", plugin_state->score);
+ canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignBottom, scoreBuffer);
+ }
+
+ //char* info = (char*)malloc(16 * sizeof(char));
+ //asprintf(&info, "%d, %d", plugin_state->x, plugin_state->y);
+ //canvas_draw_str_aligned(canvas, 32, 16, AlignLeft, AlignBottom, info);
+ //free(info);
+
+ release_mutex((ValueMutex*)ctx, plugin_state);
+}
+
+static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
+ furi_assert(event_queue);
+ PluginEvent event = {.type = EventTypeKey, .input = *input_event};
+ furi_message_queue_put(event_queue, &event, FuriWaitForever);
+}
+
+static void tick(PluginState* const plugin_state) {
+ if(plugin_state->input_shoot && (plugin_state->projectiles_count < PROJECTILES_MAX)) {
+ Projectile* p = (Projectile*)malloc(sizeof(Projectile));
+ p->position.x = plugin_state->player.position.x;
+ p->position.y = plugin_state->player.position.y;
+
+ size_t idx = plugin_state->projectiles_count;
+ plugin_state->projectiles[idx] = p;
+ plugin_state->projectiles_count += 1;
+ }
+
+ for(int i = 0; i < ZOMBIES_MAX; ++i) {
+ if(!plugin_state->zombies[i]) {
+ Zombie* z = (Zombie*)malloc(sizeof(Zombie));
+ //z->hp = 20;
+ z->position.x = 126;
+ z->position.y = MIN_Y + (rand() % (MAX_Y - MIN_Y));
+
+ plugin_state->zombies[i] = z;
+ plugin_state->zombies_count += 1;
+ }
+ }
+
+ for(int i = 0; i < PROJECTILES_MAX; ++i) {
+ Projectile* p = plugin_state->projectiles[i];
+ if(p != NULL) {
+ p->position.x += 2;
+
+ for(int i = 0; i < ZOMBIES_MAX; ++i) {
+ Zombie* z = plugin_state->zombies[i];
+ if(z != NULL) {
+ if( // projectile close enough to zombie
+ (((z->position.x - p->position.x) <= 2) &&
+ ((z->position.y - p->position.y) <= 4)) &&
+ (((p->position.x - z->position.x) <= 2) &&
+ ((p->position.y - z->position.y) <= 6))) {
+ //z->hp -= 5;
+ //if(z->hp <= 0) {
+ plugin_state->zombies_count -= 1;
+ free(z);
+ plugin_state->zombies[i] = NULL;
+ plugin_state->score++;
+ //if(plugin_state->score % 15 == 0) DOLPHIN_DEED(getRandomDeed());
+ //}
+ } else if(z->position.x <= WALL_X && z->position.x > 0) { // zombie got to the wall
+ plugin_state->zombies_count -= 1;
+ free(z);
+ plugin_state->zombies[i] = NULL;
+ if(plugin_state->player.hp > 0) {
+ plugin_state->player.hp--;
+ } else {
+ plugin_state->game_state = GameStateGameOver;
+ }
+ } else {
+ if(furi_get_tick() % 2 == 0) z->position.x--;
+ }
+ }
+ }
+
+ if(p->position.x >= 128) {
+ free(p);
+ plugin_state->projectiles[i] = NULL;
+ }
+ }
+ }
+
+ plugin_state->input_shoot = false;
+}
+
+static void timer_callback(void* ctx) {
+ furi_assert(ctx);
+ FuriMessageQueue* event_queue = ctx;
+ PluginEvent event = {.type = EventTypeTick};
+ furi_message_queue_put(event_queue, &event, 0);
+}
+
+static void zombiez_state_init(PluginState* const plugin_state) {
+ plugin_state->player.position.x = PLAYER_START_X;
+ plugin_state->player.position.y = PLAYER_START_Y;
+ plugin_state->player.hp = 20;
+
+ plugin_state->projectiles_count = 0;
+ plugin_state->zombies_count = 0;
+ plugin_state->score = 0;
+
+ for(int i = 0; i < PROJECTILES_MAX; i++) {
+ plugin_state->projectiles[i] = NULL;
+ }
+
+ for(int i = 0; i < ZOMBIES_MAX; i++) {
+ plugin_state->zombies[i] = NULL;
+ }
+
+ plugin_state->game_state = GameStatePlaying;
+ plugin_state->input_shoot = false;
+}
+
+int32_t zombiez_game_app(void* p) {
+ UNUSED(p);
+ uint32_t return_code = 0;
+ FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
+
+ PluginState* plugin_state = malloc(sizeof(PluginState));
+ zombiez_state_init(plugin_state);
+
+ ValueMutex state_mutex;
+ if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) {
+ FURI_LOG_E("Zombiez", "cannot create mutex\r\n");
+ return_code = 255;
+ goto free_and_exit;
+ }
+
+ // Set system callbacks
+ ViewPort* view_port = view_port_alloc();
+ view_port_draw_callback_set(view_port, render_callback, &state_mutex);
+ view_port_input_callback_set(view_port, input_callback, event_queue);
+
+ FuriTimer* timer = furi_timer_alloc(timer_callback, FuriTimerTypePeriodic, event_queue);
+ furi_timer_start(timer, furi_kernel_get_tick_frequency() / 22);
+
+ // Open GUI and register view_port
+ Gui* gui = furi_record_open(RECORD_GUI);
+ gui_add_view_port(gui, view_port, GuiLayerFullscreen);
+
+ PluginEvent event;
+ bool isRunning = true;
+ while(isRunning) {
+ FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
+ PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex);
+ if(event_status == FuriStatusOk) {
+ if(event.type == EventTypeKey) {
+ if(event.input.type == InputTypePress) {
+ switch(event.input.key) {
+ case InputKeyUp:
+ if(plugin_state->player.position.y > MIN_Y &&
+ plugin_state->game_state == GameStatePlaying) {
+ plugin_state->player.position.y--;
+ }
+ break;
+ case InputKeyDown:
+ if(plugin_state->player.position.y < MAX_Y &&
+ plugin_state->game_state == GameStatePlaying) {
+ plugin_state->player.position.y++;
+ }
+ break;
+ case InputKeyOk:
+ if(plugin_state->projectiles_count < PROJECTILES_MAX &&
+ plugin_state->game_state == GameStatePlaying) {
+ plugin_state->input_shoot = true;
+ }
+ break;
+ case InputKeyBack:
+ break;
+ default:
+ break;
+ }
+ } else if(
+ event.input.type == InputTypeRepeat &&
+ plugin_state->game_state == GameStatePlaying) {
+ switch(event.input.key) {
+ case InputKeyUp:
+ if(plugin_state->player.position.y > (MIN_Y + 1)) {
+ plugin_state->player.position.y -= 2;
+ }
+ break;
+ case InputKeyDown:
+ if(plugin_state->player.position.y < (MAX_Y - 1)) {
+ plugin_state->player.position.y += 2;
+ }
+ break;
+ default:
+ break;
+ }
+ } else if(event.input.type == InputTypeLong) {
+ if(event.input.key == InputKeyOk) {
+ if(plugin_state->game_state == GameStateGameOver) {
+ zombiez_state_init(plugin_state);
+ } else if(plugin_state->projectiles_count >= PROJECTILES_MAX) {
+ plugin_state->projectiles_count = 0;
+ plugin_state->player.hp++;
+ }
+ } else if(event.input.key == InputKeyBack) {
+ isRunning = false;
+ }
+ }
+ } else if(event.type == EventTypeTick) {
+ tick(plugin_state);
+ }
+ } else {
+ // event timeout
+ }
+
+ view_port_update(view_port);
+ release_mutex(&state_mutex, plugin_state);
+ }
+
+ furi_timer_free(timer);
+ view_port_enabled_set(view_port, false);
+ gui_remove_view_port(gui, view_port);
+ furi_record_close(RECORD_GUI);
+ view_port_free(view_port);
+ delete_mutex(&state_mutex);
+
+free_and_exit:
+ free(plugin_state);
+ furi_message_queue_free(event_queue);
+
+ return return_code;
+}
\ No newline at end of file
diff --git a/applications/plugins/zombiez/zombiez.h b/applications/plugins/zombiez/zombiez.h
new file mode 100644
index 000000000..eea71d707
--- /dev/null
+++ b/applications/plugins/zombiez/zombiez.h
@@ -0,0 +1,62 @@
+#include
+
+uint8_t zombie_array[2][8][5] = {
+ {
+ {0, 0, 1, 1, 1},
+ {0, 0, 1, 1, 1},
+ {1, 1, 1, 1, 1},
+ {0, 0, 1, 1, 1},
+ {0, 0, 1, 1, 1},
+ {0, 0, 1, 1, 1},
+ {0, 0, 1, 0, 0},
+ {0, 0, 1, 0, 0},
+ },
+ {
+ {0, 0, 1, 1, 1},
+ {0, 0, 1, 1, 1},
+ {1, 1, 1, 1, 1},
+ {0, 0, 1, 1, 1},
+ {0, 0, 1, 1, 1},
+ {0, 0, 1, 1, 1},
+ {0, 0, 0, 0, 1},
+ {0, 0, 0, 0, 1},
+ },
+};
+
+uint8_t heart_array[5][5][5] = {
+ {
+ {0, 1, 0, 1, 0},
+ {1, 1, 1, 1, 1},
+ {1, 1, 1, 1, 1},
+ {0, 1, 1, 1, 0},
+ {0, 0, 1, 0, 0},
+ },
+ {
+ {0, 0, 0, 0, 0},
+ {1, 1, 1, 1, 1},
+ {1, 1, 1, 1, 1},
+ {0, 1, 1, 1, 0},
+ {0, 0, 1, 0, 0},
+ },
+ {
+ {0, 0, 0, 0, 0},
+ {0, 0, 0, 0, 0},
+ {1, 1, 1, 1, 1},
+ {0, 1, 1, 1, 0},
+ {0, 0, 1, 0, 0},
+ },
+ {
+ {0, 0, 0, 0, 0},
+ {0, 0, 0, 0, 0},
+ {0, 0, 0, 0, 0},
+ {0, 1, 1, 1, 0},
+ {0, 0, 1, 0, 0},
+ },
+ {
+ {1, 0, 0, 0, 1},
+ {0, 1, 0, 1, 0},
+ {0, 0, 1, 0, 0},
+ {0, 1, 0, 1, 0},
+ {1, 0, 0, 0, 1},
+ },
+};
\ No newline at end of file
diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c
index 562f2e183..419b8b567 100644
--- a/applications/services/gui/canvas.c
+++ b/applications/services/gui/canvas.c
@@ -367,6 +367,23 @@ void canvas_draw_glyph(Canvas* canvas, uint8_t x, uint8_t y, uint16_t ch) {
u8g2_DrawGlyph(&canvas->fb, x, y, ch);
}
+void canvas_draw_icon_bitmap(
+ Canvas* canvas,
+ uint8_t x,
+ uint8_t y,
+ int16_t w,
+ int16_t h,
+ const Icon* icon) {
+ furi_assert(canvas);
+ furi_assert(icon);
+
+ x += canvas->offset_x;
+ y += canvas->offset_y;
+ uint8_t* icon_data = NULL;
+ furi_hal_compress_icon_decode(icon_get_data(icon), &icon_data);
+ u8g2_DrawXBM(&canvas->fb, x, y, w, h, icon_data);
+}
+
void canvas_set_bitmap_mode(Canvas* canvas, bool alpha) {
u8g2_SetBitmapMode(&canvas->fb, alpha ? 1 : 0);
}
diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h
index edde10a01..ccb8dc523 100644
--- a/applications/services/gui/canvas.h
+++ b/applications/services/gui/canvas.h
@@ -358,6 +358,14 @@ void canvas_draw_rbox(
uint8_t height,
uint8_t radius);
+void canvas_draw_icon_bitmap(
+ Canvas* canvas,
+ uint8_t x,
+ uint8_t y,
+ int16_t w,
+ int16_t h,
+ const Icon* icon);
+
#ifdef __cplusplus
}
#endif
diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv
index eb72be9bb..a3f1eaecb 100644
--- a/firmware/targets/f7/api_symbols.csv
+++ b/firmware/targets/f7/api_symbols.csv
@@ -1,5 +1,5 @@
entry,status,name,type,params
-Version,+,2.3,,
+Version,+,3.0,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/cli/cli.h,,
Header,+,applications/services/cli/cli_vcp.h,,
@@ -540,6 +540,7 @@ Function,+,canvas_draw_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t"
Function,+,canvas_draw_glyph,void,"Canvas*, uint8_t, uint8_t, uint16_t"
Function,+,canvas_draw_icon,void,"Canvas*, uint8_t, uint8_t, const Icon*"
Function,+,canvas_draw_icon_animation,void,"Canvas*, uint8_t, uint8_t, IconAnimation*"
+Function,+,canvas_draw_icon_bitmap,void,"Canvas*, uint8_t, uint8_t, int16_t, int16_t, const Icon*"
Function,+,canvas_draw_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t"
Function,+,canvas_draw_rbox,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t"
Function,+,canvas_draw_rframe,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t"
@@ -1479,7 +1480,7 @@ Function,-,isupper,int,int
Function,-,isupper_l,int,"int, locale_t"
Function,-,isxdigit,int,int
Function,-,isxdigit_l,int,"int, locale_t"
-Function,-,itoa,char*,"int, char*, int"
+Function,+,itoa,char*,"int, char*, int"
Function,-,j0,double,double
Function,-,j0f,float,float
Function,-,j1,double,double
diff --git a/site_scons/fbt/appmanifest.py b/site_scons/fbt/appmanifest.py
index c0770f7f3..a41717d6a 100644
--- a/site_scons/fbt/appmanifest.py
+++ b/site_scons/fbt/appmanifest.py
@@ -19,7 +19,6 @@ class FlipperAppType(Enum):
STARTUP = "StartupHook"
EXTERNAL = "External"
METAPACKAGE = "Package"
- GAME = "Game"
@dataclass