diff --git a/assets/cGrove/Audio/BGM/Chowa_Grove_Meadow.mid b/assets/cGrove/Audio/BGM/Chowa_Grove_Meadow.mid new file mode 100644 index 000000000..cb588f066 Binary files /dev/null and b/assets/cGrove/Audio/BGM/Chowa_Grove_Meadow.mid differ diff --git a/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg1.png b/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg1.png new file mode 100644 index 000000000..46f11328e Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg1.png differ diff --git a/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg2.png b/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg2.png new file mode 100644 index 000000000..9b474b63f Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg2.png differ diff --git a/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg3.png b/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg3.png new file mode 100644 index 000000000..b19563f44 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg3.png differ diff --git a/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg4.png b/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg4.png new file mode 100644 index 000000000..da40594ec Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg4.png differ diff --git a/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg5.png b/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg5.png new file mode 100644 index 000000000..448c9440f Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg5.png differ diff --git a/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg6.png b/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg6.png new file mode 100644 index 000000000..cc5369daf Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg6.png differ diff --git a/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg_hatch1.png b/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg_hatch1.png new file mode 100644 index 000000000..6e53f21fd Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg_hatch1.png differ diff --git a/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg_hatch2.png b/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg_hatch2.png new file mode 100644 index 000000000..d5b2d3334 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg_hatch2.png differ diff --git a/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg_hatch3.png b/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg_hatch3.png new file mode 100644 index 000000000..108bdbf7a Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg_hatch3.png differ diff --git a/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg_hatch4.png b/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg_hatch4.png new file mode 100644 index 000000000..ebad5dd8c Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg_hatch4.png differ diff --git a/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg_hatch5.png b/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg_hatch5.png new file mode 100644 index 000000000..daca57c34 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg_hatch5.png differ diff --git a/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg_hatch6.png b/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg_hatch6.png new file mode 100644 index 000000000..8c4314af4 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/Eggs/chowa_egg_hatch6.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_angry-1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_angry-1.png new file mode 100644 index 000000000..263409ed8 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_angry-1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_angry-2.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_angry-2.png new file mode 100644 index 000000000..c94a18d05 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_angry-2.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_bkfall1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_bkfall1.png new file mode 100644 index 000000000..25a1466cf Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_bkfall1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_bkfall2.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_bkfall2.png new file mode 100644 index 000000000..201c9d9d6 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_bkfall2.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_bkfall3.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_bkfall3.png new file mode 100644 index 000000000..33949e2d9 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_bkfall3.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_bkfall4.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_bkfall4.png new file mode 100644 index 000000000..e19b442d2 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_bkfall4.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_bkwalk1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_bkwalk1.png new file mode 100644 index 000000000..9efa8b366 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_bkwalk1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_bkwalk2.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_bkwalk2.png new file mode 100644 index 000000000..c492ec78f Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_bkwalk2.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_bkwalk3.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_bkwalk3.png new file mode 100644 index 000000000..2e17f3890 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_bkwalk3.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_bkwalk4.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_bkwalk4.png new file mode 100644 index 000000000..fef27869c Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_bkwalk4.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_climb1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_climb1.png new file mode 100644 index 000000000..b8a03f623 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_climb1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_climb2.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_climb2.png new file mode 100644 index 000000000..ca91dd241 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_climb2.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_dance-1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_dance-1.png new file mode 100644 index 000000000..9eef4702e Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_dance-1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_dance-2.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_dance-2.png new file mode 100644 index 000000000..434441c40 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_dance-2.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_dance-3.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_dance-3.png new file mode 100644 index 000000000..49ab91c0a Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_dance-3.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_dance-4.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_dance-4.png new file mode 100644 index 000000000..434441c40 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_dance-4.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_disgust1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_disgust1.png new file mode 100644 index 000000000..d97edc668 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_disgust1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_disgust2.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_disgust2.png new file mode 100644 index 000000000..adb94690d Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_disgust2.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_draw-1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_draw-1.png new file mode 100644 index 000000000..d463149f8 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_draw-1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_draw-2.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_draw-2.png new file mode 100644 index 000000000..cf7d2818f Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_draw-2.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_eat-1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_eat-1.png new file mode 100644 index 000000000..382ecb39a Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_eat-1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_eat-2.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_eat-2.png new file mode 100644 index 000000000..781fccf1c Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_eat-2.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_eat-3.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_eat-3.png new file mode 100644 index 000000000..0ff7ba1ba Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_eat-3.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_eat-4.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_eat-4.png new file mode 100644 index 000000000..539128e31 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_eat-4.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_fallingbk-1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_fallingbk-1.png new file mode 100644 index 000000000..37f796dcb Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_fallingbk-1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_fallingbk-2.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_fallingbk-2.png new file mode 100644 index 000000000..7f6dfdd96 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_fallingbk-2.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_fear-1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_fear-1.png new file mode 100644 index 000000000..1f0cec5b5 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_fear-1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_fear-2.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_fear-2.png new file mode 100644 index 000000000..348be6890 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_fear-2.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_flail-1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_flail-1.png new file mode 100644 index 000000000..eea1573d8 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_flail-1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_flail-2.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_flail-2.png new file mode 100644 index 000000000..f15883158 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_flail-2.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_gift-1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_gift-1.png new file mode 100644 index 000000000..1b594397d Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_gift-1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_givup-1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_givup-1.png new file mode 100644 index 000000000..3be5b5a1e Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_givup-1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_givup-2.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_givup-2.png new file mode 100644 index 000000000..dca51fa92 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_givup-2.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_happy1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_happy1.png new file mode 100644 index 000000000..c922013cd Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_happy1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_happy2.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_happy2.png new file mode 100644 index 000000000..b64d70917 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_happy2.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_hdbtt-1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_hdbtt-1.png new file mode 100644 index 000000000..bd3b366ca Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_hdbtt-1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_hdbtt-2.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_hdbtt-2.png new file mode 100644 index 000000000..489d019c9 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_hdbtt-2.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_kick-1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_kick-1.png new file mode 100644 index 000000000..fc9f83412 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_kick-1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_kick-2.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_kick-2.png new file mode 100644 index 000000000..1d56acda1 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_kick-2.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_pet-1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_pet-1.png new file mode 100644 index 000000000..c892af89e Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_pet-1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_punch-1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_punch-1.png new file mode 100644 index 000000000..71d9401b9 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_punch-1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_punch-2.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_punch-2.png new file mode 100644 index 000000000..de79574f8 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_punch-2.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_read-1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_read-1.png new file mode 100644 index 000000000..e152fd361 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_read-1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_read-2.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_read-2.png new file mode 100644 index 000000000..402b98203 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_read-2.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_read-3.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_read-3.png new file mode 100644 index 000000000..6a92f3d9a Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_read-3.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sad-1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sad-1.png new file mode 100644 index 000000000..4068883df Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sad-1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sad-2.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sad-2.png new file mode 100644 index 000000000..cdfd48f83 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sad-2.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sdfall1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sdfall1.png new file mode 100644 index 000000000..5a8824896 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sdfall1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sdfall2.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sdfall2.png new file mode 100644 index 000000000..51fcfa4f7 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sdfall2.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sdfall3.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sdfall3.png new file mode 100644 index 000000000..a4854fbc2 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sdfall3.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sdwalk1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sdwalk1.png new file mode 100644 index 000000000..3505f7ba2 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sdwalk1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sdwalk2.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sdwalk2.png new file mode 100644 index 000000000..338c0a145 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sdwalk2.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sdwalk3.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sdwalk3.png new file mode 100644 index 000000000..b3baf7398 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sdwalk3.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sdwalk4.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sdwalk4.png new file mode 100644 index 000000000..20114458a Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sdwalk4.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sing-1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sing-1.png new file mode 100644 index 000000000..a3ba4f793 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sing-1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sing-2.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sing-2.png new file mode 100644 index 000000000..434441c40 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sing-2.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sing-3.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sing-3.png new file mode 100644 index 000000000..49d6a4025 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sing-3.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sing-4.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sing-4.png new file mode 100644 index 000000000..434441c40 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sing-4.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sit1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sit1.png new file mode 100644 index 000000000..4c9fe86b6 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_sit1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_swim-1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_swim-1.png new file mode 100644 index 000000000..6924d42b1 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_swim-1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_swim-2.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_swim-2.png new file mode 100644 index 000000000..1872e00c1 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_swim-2.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_swim-3.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_swim-3.png new file mode 100644 index 000000000..4f12e5fe8 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_swim-3.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_swim-4.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_swim-4.png new file mode 100644 index 000000000..950ed4e5a Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_swim-4.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_swordplay-1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_swordplay-1.png new file mode 100644 index 000000000..b8d9c81db Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_swordplay-1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_swordplay-2.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_swordplay-2.png new file mode 100644 index 000000000..8bd7b39ac Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_swordplay-2.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_swordplay-3.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_swordplay-3.png new file mode 100644 index 000000000..de457bd8d Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_swordplay-3.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_throw-1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_throw-1.png new file mode 100644 index 000000000..0f0dfa94a Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_throw-1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_throw-2.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_throw-2.png new file mode 100644 index 000000000..b9f596669 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_throw-2.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_throw-3.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_throw-3.png new file mode 100644 index 000000000..a3ab6cd88 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_throw-3.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_walk1.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_walk1.png new file mode 100644 index 000000000..1fcba7147 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_walk1.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_walk2.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_walk2.png new file mode 100644 index 000000000..acb1c75a8 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_walk2.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_walk3.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_walk3.png new file mode 100644 index 000000000..9e24a6b67 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_walk3.png differ diff --git a/assets/cGrove/Sprites/Chowa/KD/Child/ckd_walk4.png b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_walk4.png new file mode 100644 index 000000000..f12378aa4 Binary files /dev/null and b/assets/cGrove/Sprites/Chowa/KD/Child/ckd_walk4.png differ diff --git a/assets/cGrove/Sprites/Garden/garden_background.png b/assets/cGrove/Sprites/Garden/garden_background.png new file mode 100644 index 000000000..2927b26bb Binary files /dev/null and b/assets/cGrove/Sprites/Garden/garden_background.png differ diff --git a/assets/cGrove/Sprites/Items/Books/agi_book.png b/assets/cGrove/Sprites/Items/Books/agi_book.png new file mode 100644 index 000000000..87720d839 Binary files /dev/null and b/assets/cGrove/Sprites/Items/Books/agi_book.png differ diff --git a/assets/cGrove/Sprites/Items/Books/cha_book.png b/assets/cGrove/Sprites/Items/Books/cha_book.png new file mode 100644 index 000000000..8d8338b54 Binary files /dev/null and b/assets/cGrove/Sprites/Items/Books/cha_book.png differ diff --git a/assets/cGrove/Sprites/Items/Books/spd_book.png b/assets/cGrove/Sprites/Items/Books/spd_book.png new file mode 100644 index 000000000..85cbbf234 Binary files /dev/null and b/assets/cGrove/Sprites/Items/Books/spd_book.png differ diff --git a/assets/cGrove/Sprites/Items/Books/sta_book.png b/assets/cGrove/Sprites/Items/Books/sta_book.png new file mode 100644 index 000000000..af168418d Binary files /dev/null and b/assets/cGrove/Sprites/Items/Books/sta_book.png differ diff --git a/assets/cGrove/Sprites/Items/Books/str_book.png b/assets/cGrove/Sprites/Items/Books/str_book.png new file mode 100644 index 000000000..9ae3291f7 Binary files /dev/null and b/assets/cGrove/Sprites/Items/Books/str_book.png differ diff --git a/assets/cGrove/Sprites/Items/DonutRing.png b/assets/cGrove/Sprites/Items/DonutRing.png new file mode 100644 index 000000000..c70ad1da7 Binary files /dev/null and b/assets/cGrove/Sprites/Items/DonutRing.png differ diff --git a/assets/cGrove/Sprites/Items/Food/cake.png b/assets/cGrove/Sprites/Items/Food/cake.png new file mode 100644 index 000000000..76a98d735 Binary files /dev/null and b/assets/cGrove/Sprites/Items/Food/cake.png differ diff --git a/assets/cGrove/Sprites/Items/Food/souffle.png b/assets/cGrove/Sprites/Items/Food/souffle.png new file mode 100644 index 000000000..6907c0194 Binary files /dev/null and b/assets/cGrove/Sprites/Items/Food/souffle.png differ diff --git a/assets/cGrove/Sprites/Items/Toys/cg_ball.png b/assets/cGrove/Sprites/Items/Toys/cg_ball.png new file mode 100644 index 000000000..3093482c7 Binary files /dev/null and b/assets/cGrove/Sprites/Items/Toys/cg_ball.png differ diff --git a/assets/cGrove/Sprites/Items/Toys/cg_crayons.png b/assets/cGrove/Sprites/Items/Toys/cg_crayons.png new file mode 100644 index 000000000..0a59a5d2b Binary files /dev/null and b/assets/cGrove/Sprites/Items/Toys/cg_crayons.png differ diff --git a/assets/cGrove/Sprites/Items/Toys/cg_knife.png b/assets/cGrove/Sprites/Items/Toys/cg_knife.png new file mode 100644 index 000000000..f71ebe708 Binary files /dev/null and b/assets/cGrove/Sprites/Items/Toys/cg_knife.png differ diff --git a/assets/cGrove/Sprites/Items/Toys/cg_toy_sword.png b/assets/cGrove/Sprites/Items/Toys/cg_toy_sword.png new file mode 100644 index 000000000..e42cc75a5 Binary files /dev/null and b/assets/cGrove/Sprites/Items/Toys/cg_toy_sword.png differ diff --git a/assets/cGrove/Sprites/Moods/cGrove_Face_Angry.png b/assets/cGrove/Sprites/Moods/cGrove_Face_Angry.png new file mode 100644 index 000000000..7b2f718f3 Binary files /dev/null and b/assets/cGrove/Sprites/Moods/cGrove_Face_Angry.png differ diff --git a/assets/cGrove/Sprites/Moods/cGrove_Face_Confused.png b/assets/cGrove/Sprites/Moods/cGrove_Face_Confused.png new file mode 100644 index 000000000..9d60c6543 Binary files /dev/null and b/assets/cGrove/Sprites/Moods/cGrove_Face_Confused.png differ diff --git a/assets/cGrove/Sprites/Moods/cGrove_Face_Happy.png b/assets/cGrove/Sprites/Moods/cGrove_Face_Happy.png new file mode 100644 index 000000000..bec822944 Binary files /dev/null and b/assets/cGrove/Sprites/Moods/cGrove_Face_Happy.png differ diff --git a/assets/cGrove/Sprites/Moods/cGrove_Face_Neutral.png b/assets/cGrove/Sprites/Moods/cGrove_Face_Neutral.png new file mode 100644 index 000000000..f6dcf2a69 Binary files /dev/null and b/assets/cGrove/Sprites/Moods/cGrove_Face_Neutral.png differ diff --git a/assets/cGrove/Sprites/Moods/cGrove_Face_Sad.png b/assets/cGrove/Sprites/Moods/cGrove_Face_Sad.png new file mode 100644 index 000000000..2ebe0ab33 Binary files /dev/null and b/assets/cGrove/Sprites/Moods/cGrove_Face_Sad.png differ diff --git a/assets/cGrove/Sprites/Moods/cGrove_Face_Sick.png b/assets/cGrove/Sprites/Moods/cGrove_Face_Sick.png new file mode 100644 index 000000000..d604c2d41 Binary files /dev/null and b/assets/cGrove/Sprites/Moods/cGrove_Face_Sick.png differ diff --git a/assets/cGrove/Sprites/Moods/cGrove_Face_Surprised.png b/assets/cGrove/Sprites/Moods/cGrove_Face_Surprised.png new file mode 100644 index 000000000..54afc024b Binary files /dev/null and b/assets/cGrove/Sprites/Moods/cGrove_Face_Surprised.png differ diff --git a/assets/cGrove/Sprites/Spar/DojoBG.png b/assets/cGrove/Sprites/Spar/DojoBG.png new file mode 100644 index 000000000..2110d9c44 Binary files /dev/null and b/assets/cGrove/Sprites/Spar/DojoBG.png differ diff --git a/assets/cGrove/Sprites/Spar/Dojo_Gong.png b/assets/cGrove/Sprites/Spar/Dojo_Gong.png new file mode 100644 index 000000000..d9e820ba7 Binary files /dev/null and b/assets/cGrove/Sprites/Spar/Dojo_Gong.png differ diff --git a/assets/cGrove/Sprites/Spar/Dojo_PunchingBag.png b/assets/cGrove/Sprites/Spar/Dojo_PunchingBag.png new file mode 100644 index 000000000..37895fc63 Binary files /dev/null and b/assets/cGrove/Sprites/Spar/Dojo_PunchingBag.png differ diff --git a/assets/cGrove/Sprites/UI/Splash/cg_cloud.png b/assets/cGrove/Sprites/UI/Splash/cg_cloud.png new file mode 100644 index 000000000..4da6b859a Binary files /dev/null and b/assets/cGrove/Sprites/UI/Splash/cg_cloud.png differ diff --git a/assets/cGrove/Sprites/UI/Splash/cg_sky.png b/assets/cGrove/Sprites/UI/Splash/cg_sky.png new file mode 100644 index 000000000..74b002fcf Binary files /dev/null and b/assets/cGrove/Sprites/UI/Splash/cg_sky.png differ diff --git a/assets/cGrove/Sprites/UI/Splash/cg_title_1.png b/assets/cGrove/Sprites/UI/Splash/cg_title_1.png new file mode 100644 index 000000000..806b0c1c7 Binary files /dev/null and b/assets/cGrove/Sprites/UI/Splash/cg_title_1.png differ diff --git a/assets/cGrove/Sprites/UI/Splash/cg_title_2.png b/assets/cGrove/Sprites/UI/Splash/cg_title_2.png new file mode 100644 index 000000000..d1b8da3ec Binary files /dev/null and b/assets/cGrove/Sprites/UI/Splash/cg_title_2.png differ diff --git a/assets/cGrove/Sprites/UI/Splash/cg_title_middle_1.png b/assets/cGrove/Sprites/UI/Splash/cg_title_middle_1.png new file mode 100644 index 000000000..f9c847b55 Binary files /dev/null and b/assets/cGrove/Sprites/UI/Splash/cg_title_middle_1.png differ diff --git a/assets/cGrove/Sprites/UI/Splash/cg_title_middle_2.png b/assets/cGrove/Sprites/UI/Splash/cg_title_middle_2.png new file mode 100644 index 000000000..b87101086 Binary files /dev/null and b/assets/cGrove/Sprites/UI/Splash/cg_title_middle_2.png differ diff --git a/assets/cGrove/Sprites/UI/Splash/cg_title_middle_3.png b/assets/cGrove/Sprites/UI/Splash/cg_title_middle_3.png new file mode 100644 index 000000000..aa8f64b02 Binary files /dev/null and b/assets/cGrove/Sprites/UI/Splash/cg_title_middle_3.png differ diff --git a/assets/cGrove/Sprites/UI/Splash/cg_title_middle_4.png b/assets/cGrove/Sprites/UI/Splash/cg_title_middle_4.png new file mode 100644 index 000000000..98ec1e9eb Binary files /dev/null and b/assets/cGrove/Sprites/UI/Splash/cg_title_middle_4.png differ diff --git a/assets/cGrove/Sprites/UI/anger-1.png b/assets/cGrove/Sprites/UI/anger-1.png new file mode 100644 index 000000000..8c07610ee Binary files /dev/null and b/assets/cGrove/Sprites/UI/anger-1.png differ diff --git a/assets/cGrove/Sprites/UI/anger-2.png b/assets/cGrove/Sprites/UI/anger-2.png new file mode 100644 index 000000000..0fc4232e7 Binary files /dev/null and b/assets/cGrove/Sprites/UI/anger-2.png differ diff --git a/assets/cGrove/Sprites/UI/cg_Arrow.png b/assets/cGrove/Sprites/UI/cg_Arrow.png new file mode 100644 index 000000000..f0fb3223d Binary files /dev/null and b/assets/cGrove/Sprites/UI/cg_Arrow.png differ diff --git a/assets/cGrove/Sprites/UI/cg_note1.png b/assets/cGrove/Sprites/UI/cg_note1.png new file mode 100644 index 000000000..a8ada5b6f Binary files /dev/null and b/assets/cGrove/Sprites/UI/cg_note1.png differ diff --git a/assets/cGrove/Sprites/UI/cg_note2.png b/assets/cGrove/Sprites/UI/cg_note2.png new file mode 100644 index 000000000..0938a9d8c Binary files /dev/null and b/assets/cGrove/Sprites/UI/cg_note2.png differ diff --git a/assets/cGrove/Sprites/UI/cg_note3.png b/assets/cGrove/Sprites/UI/cg_note3.png new file mode 100644 index 000000000..4c993b078 Binary files /dev/null and b/assets/cGrove/Sprites/UI/cg_note3.png differ diff --git a/assets/cGrove/Sprites/UI/cg_text0.png b/assets/cGrove/Sprites/UI/cg_text0.png new file mode 100644 index 000000000..1737cda70 Binary files /dev/null and b/assets/cGrove/Sprites/UI/cg_text0.png differ diff --git a/assets/cGrove/Sprites/UI/cg_text1.png b/assets/cGrove/Sprites/UI/cg_text1.png new file mode 100644 index 000000000..e8413a12a Binary files /dev/null and b/assets/cGrove/Sprites/UI/cg_text1.png differ diff --git a/assets/cGrove/Sprites/UI/cg_text2.png b/assets/cGrove/Sprites/UI/cg_text2.png new file mode 100644 index 000000000..5e1566178 Binary files /dev/null and b/assets/cGrove/Sprites/UI/cg_text2.png differ diff --git a/assets/cGrove/Sprites/UI/cg_text3.png b/assets/cGrove/Sprites/UI/cg_text3.png new file mode 100644 index 000000000..2706f90ca Binary files /dev/null and b/assets/cGrove/Sprites/UI/cg_text3.png differ diff --git a/assets/cGrove/Sprites/UI/chowa_hand1.png b/assets/cGrove/Sprites/UI/chowa_hand1.png new file mode 100644 index 000000000..2a2fd6f69 Binary files /dev/null and b/assets/cGrove/Sprites/UI/chowa_hand1.png differ diff --git a/assets/cGrove/Sprites/UI/chowa_hand2.png b/assets/cGrove/Sprites/UI/chowa_hand2.png new file mode 100644 index 000000000..56532155d Binary files /dev/null and b/assets/cGrove/Sprites/UI/chowa_hand2.png differ diff --git a/assets/cGrove/Sprites/UI/chowa_hand3.png b/assets/cGrove/Sprites/UI/chowa_hand3.png new file mode 100644 index 000000000..fb1d9d60a Binary files /dev/null and b/assets/cGrove/Sprites/UI/chowa_hand3.png differ diff --git a/assets/cGrove/Sprites/UI/chowa_hand4.png b/assets/cGrove/Sprites/UI/chowa_hand4.png new file mode 100644 index 000000000..e52b991df Binary files /dev/null and b/assets/cGrove/Sprites/UI/chowa_hand4.png differ diff --git a/assets/cGrove/Sprites/UI/excpt-1.png b/assets/cGrove/Sprites/UI/excpt-1.png new file mode 100644 index 000000000..4bd62af4b Binary files /dev/null and b/assets/cGrove/Sprites/UI/excpt-1.png differ diff --git a/assets/cGrove/Sprites/UI/excpt-2.png b/assets/cGrove/Sprites/UI/excpt-2.png new file mode 100644 index 000000000..e79e96e0b Binary files /dev/null and b/assets/cGrove/Sprites/UI/excpt-2.png differ diff --git a/assets/cGrove/Sprites/UI/excpt-3.png b/assets/cGrove/Sprites/UI/excpt-3.png new file mode 100644 index 000000000..8717e08d3 Binary files /dev/null and b/assets/cGrove/Sprites/UI/excpt-3.png differ diff --git a/assets/cGrove/Sprites/UI/excpt-4.png b/assets/cGrove/Sprites/UI/excpt-4.png new file mode 100644 index 000000000..3c615e47e Binary files /dev/null and b/assets/cGrove/Sprites/UI/excpt-4.png differ diff --git a/assets/cGrove/Sprites/UI/excpt-5.png b/assets/cGrove/Sprites/UI/excpt-5.png new file mode 100644 index 000000000..32574507a Binary files /dev/null and b/assets/cGrove/Sprites/UI/excpt-5.png differ diff --git a/assets/cGrove/Sprites/UI/excpt-6.png b/assets/cGrove/Sprites/UI/excpt-6.png new file mode 100644 index 000000000..f2e00df6b Binary files /dev/null and b/assets/cGrove/Sprites/UI/excpt-6.png differ diff --git a/assets/cGrove/Sprites/UI/questmk-1.png b/assets/cGrove/Sprites/UI/questmk-1.png new file mode 100644 index 000000000..756234eb1 Binary files /dev/null and b/assets/cGrove/Sprites/UI/questmk-1.png differ diff --git a/assets/cGrove/Sprites/UI/questmk-2.png b/assets/cGrove/Sprites/UI/questmk-2.png new file mode 100644 index 000000000..9157c4694 Binary files /dev/null and b/assets/cGrove/Sprites/UI/questmk-2.png differ diff --git a/assets/cGrove/Sprites/UI/questmk-3.png b/assets/cGrove/Sprites/UI/questmk-3.png new file mode 100644 index 000000000..7f143f868 Binary files /dev/null and b/assets/cGrove/Sprites/UI/questmk-3.png differ diff --git a/assets/cGrove/Sprites/UI/questmk-4.png b/assets/cGrove/Sprites/UI/questmk-4.png new file mode 100644 index 000000000..aae84fd3c Binary files /dev/null and b/assets/cGrove/Sprites/UI/questmk-4.png differ diff --git a/assets/cGrove/Sprites/UI/questmk-5.png b/assets/cGrove/Sprites/UI/questmk-5.png new file mode 100644 index 000000000..255157b24 Binary files /dev/null and b/assets/cGrove/Sprites/UI/questmk-5.png differ diff --git a/assets/cGrove/Sprites/UI/questmk-6.png b/assets/cGrove/Sprites/UI/questmk-6.png new file mode 100644 index 000000000..abefa1466 Binary files /dev/null and b/assets/cGrove/Sprites/UI/questmk-6.png differ diff --git a/assets/cGrove/Sprites/UI/sparkles-1.png b/assets/cGrove/Sprites/UI/sparkles-1.png new file mode 100644 index 000000000..7e6435d1f Binary files /dev/null and b/assets/cGrove/Sprites/UI/sparkles-1.png differ diff --git a/assets/cGrove/Sprites/UI/sparkles-2.png b/assets/cGrove/Sprites/UI/sparkles-2.png new file mode 100644 index 000000000..0c77e2a34 Binary files /dev/null and b/assets/cGrove/Sprites/UI/sparkles-2.png differ diff --git a/assets/cGrove/Sprites/UI/sparkles-3.png b/assets/cGrove/Sprites/UI/sparkles-3.png new file mode 100644 index 000000000..a3be52d0a Binary files /dev/null and b/assets/cGrove/Sprites/UI/sparkles-3.png differ diff --git a/assets/cGrove/Sprites/UI/sparkles.gif b/assets/cGrove/Sprites/UI/sparkles.gif new file mode 100644 index 000000000..7d64dbcaa Binary files /dev/null and b/assets/cGrove/Sprites/UI/sparkles.gif differ diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index bdc7710eb..abb1cb200 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -61,6 +61,18 @@ idf_component_register(SRCS "asset_loaders/common/heatshrink_encoder.c" "modes/games/ultimateTTT/ultimateTTTmarkerSelect.c" "modes/games/ultimateTTT/ultimateTTTp2p.c" "modes/games/ultimateTTT/ultimateTTTresult.c" + "modes/games/cGrove/mode_cGrove.c" + "modes/games/cGrove/cg_Chowa.c" + "modes/games/cGrove/cg_Items.c" + "modes/games/cGrove/Garden/cg_Grove.c" + "modes/games/cGrove/Garden/cg_GroveAI.c" + "modes/games/cGrove/Garden/cg_GroveDraw.c" + "modes/games/cGrove/Sparring/cg_Match.c" + "modes/games/cGrove/Sparring/cg_Spar.c" + "modes/games/cGrove/Sparring/cg_SparDraw.c" + "modes/games/2048/2048_game.c" + "modes/games/2048/2048_menus.c" + "modes/games/2048/mode_2048.c" "modes/music/colorchord/colorchord.c" "modes/music/jukebox/jukebox.c" "modes/music/sequencer/sequencerHelp.c" @@ -132,6 +144,10 @@ idf_component_register(SRCS "asset_loaders/common/heatshrink_encoder.c" "./modes/games/soko" "./modes/games/swadgeHero" "./modes/games/ultimateTTT" + "./modes/games/cGrove" + "./modes/games/cGrove/Garden" + "./modes/games/cGrove/Sparring" + "./modes/games/2048/" "./modes/music" "./modes/music/colorchord" "./modes/music/jukebox" diff --git a/main/modes/games/cGrove/Docs/Chowa.md b/main/modes/games/cGrove/Docs/Chowa.md new file mode 100644 index 000000000..5d108c035 --- /dev/null +++ b/main/modes/games/cGrove/Docs/Chowa.md @@ -0,0 +1,268 @@ +# Chowa Grove +A small pet simulator for Swadge 2025. + +The game is a small one-screen game that allows you to raise a pet Chowa that you can put into competitions, hang out with friends, dress up, and more! + +Compare to Chao garden for SA2. + +## What is a Chowa? +A small, defenseless creature that needs your help to become... something. A fighter, maybe? A speed demon? A Singer? It's up to you to help them reach their full potential. + +[Chowa](Chowa.jpg) #FIXME: Need image + +Raise them well, as the evil Dr. Garbotnik is also raising his own Chowa, and he can't be up to any good. + +Be sure to take care of your Chowa or they might run away to a better home. + +Each Swadge can have up to 5 Chowa at once. + +Chowa may get along with others based on a complicated formula I have yet to dream up. Probably along the following lines: `Cha1 * Cha2 * Mood1 * Mood2 * Similar skills + personality modifiers` + +### Numbers + +#### Stats +- Color +- Stat block + - Age + - Egg (0.0-0.05) + - New Born (0.06-0.10) + - Child (0.11-0.35) + - Adult (0.36-0.85) + - Elderly (0.86-1.0) + - Age scale (0-1.0, min 3 days, max 4) + - Strength (0-1.0) + - Agility (0-1.0) + - Speed (0-1.0) + - Charisma (0-1.0) + - Stamina (0-1.0) + +#### Personality/Mood +Personality and mood determine how the Chowa interacts with the player and other Chowa. + +- Moods (Enum: text, expression, conditions) + - Happy + - Angry + - Sad + - Confused + - Fearful + - Surprised + - Disgusted +- Personality (Stat growth modifiers) + - Shy + - Brash + - Boring + - Dumb + - Cry Baby + - Smart + - Overly Cautious + - Careless + - Kind + - Aggressive + +### Raising Chowa +- Breeding +- Feeding (Fruits/Vegetables/Hard drives/Cooked meals) +- Schools to raise stats +- Shops (Gifts/food/Chowa eggs) +- - Affection + - Pet + - Kick/Toss + - Gifts + +## Items + +### Aesthetics +- Cosmetics (~20 Hats/Shoes) + - Cosmetics from previous games (Chozo gear?) + - Online/Event cosmetics + +### Gifts +- Crayons +- Ball +- Toy sword +- A *real* knife +- Cake +- Souffle +- Stat book (Str/Agi/Spd/Cha/Sta) + +## Competitions +Competitions need to have NPCs to play against, or you can play against other players. Final NPC is a barely disguised Garbotnik. + +### NPCs +- Pixel +- Poe +- Pango +- Garbotnik +- Add more from previous years + +### Fighting +Fighting is a glorified game of five move rock-paper-scissors. Moves: +- Punch +- Kick +- Headbutt +- Fast Punch +- Leaping kick + +Chart showing who wins +| Player 1\Player 2 | Punch | F. Punch | Kick | Headbutt | J. Kick | +| ----------------- | ----- | -------- | ---- | -------- | ------- | +| Punch | Draw | P2 | P1 | P2 | P1 | +| F. Punch | P1 | Draw | P2 | P1 | P2 | +| Kick | P2 | P1 | Draw | P2 | P1 | +| Headbutt | P1 | P2 | P1 | Draw | P2 | +| J. Kick | P2 | P1 | P2 | P1 | Draw | + +Stages: +- Both Chowa start completely unready +- Depending on speed stat, they get ready for an attack +- Once ready, they wait for a short time + - If both Chowa are ready before the time period ends, regular RPS happens + - If one is unready, the unready one takes extra damage +- RPS is checked +- Damage is calculated based on Strength stat of attacking Chowa +- Agility is checked to see if the losing Chowa dodges +- Based on activities, drain some stamina +- Once stamina runs out, sit down and attempt to recover some stamina +- Readiness is reset + +Player actions: +- Cheer Chowa: When regaining stamina, increase rate of regen +- Tell Chowa to get up: When regaining stamina, encourage them to stand back up and prepare to attack +- Pick fighting move: When preparing, influence move Chowa uses +- Dodge: When preparing, influence Chowa to dodge. Does less damage if preparing to dodge +- Forfeit: Any time, give up. Ask multiple times. + +### Performance +Have a small dance-off against a fellow Chowa. Plays like Simon, use arrow keys in an ever increasing pattern. Have a cap on max moves by difficulty. Player is guiding Chowa to do the routine, so sometimes the Chowa will mess up. + +There's a global timer, and once a sequence is input, a new sequence will spawn. + +Score is based on number of completed sequences. Stats adjust how likely Chowa is to follow directions. + +Sequences can use all four arrow keys and A and B. + +Sequence: +- Song starts, score is set to 0 +- Show a arrow/button on screen +- Buttons are "singing" +- Arrows are dancing +- Faster you press button, the higher the score +- Add anticipation? (DDR style, change from faster to more precise) + +Chowa Stats: +- Str: No effect +- Agi: Dance modifier +- Cha: Sing modifier +- Spd: How long you have to press buttons +- Sta: No effect + +### Racing +A footrace along an obstacle course. + +Controls: +- Player can cheer the Chowa a few times per race, determined by Mood +- Player can give up anytime + +Sequence: +- Running on path (Sta) + - Chowa runs along path. + - Cheering provides a boost in speed. + - Combined running value below 0.5 adds chance of tripping +- Balance beam (Agi) + - Chowa tightropes. + - Cheering provides a boost in speed. + - Agi score < 0.5 has a chance of falling off +- Swimming (Str+Sta) + - Chowa Swims in pool + - Cheering provides a boost in speed. + - Combined value below 0.5 adds chance of floundering +- Climbing (Str) + - Chowa Climbs a rock wall + - Cheering provides a boost in speed. + - Str score < 0.5 has a chance of stalling temporarily + +Chowa Stats: +- Str: Adds modifier to Swimming and Climbing +- Agi: Adds modifier to Balance beam +- Cha: No effect +- Spd: Speed modifier for all events +- Sta: Adds modifier to Running and Swimming + +## Other features + +### Online functionality +- Player ID (Name/Emotion tag/Fav food from list) +- Competitions + +### Text entry +- Player and Chowa name entry + +## Required Assets + +### Art +- NPCs + - Pixel + - Poe + - Pango + - Garbotnik + - Selected from previous games (+random Chowa) +- Chowa (All poses) + - Standing + - Walking + - Running + - Eating + - Jumping up and down + - Climbing + - Kick + - Punch + - Headbutt (reuse Run?) + - Trip (fall over) + - Floundering +- Chowa Expressions + - Happy + - Angry + - Sad + - Confused + - Fearful + - Surprised + - Disgusted +- Emotes + - Anger particles/Steam + - Sparkles + - Exclamation point +- Grove background +- Concert stage +- Stage for fights (Dojo) +- Race course (Track/Balance beam/Pool/Climbing wall) Needs to loop +- Distance markers for races +- UI art + - Message panel + - Font + - Buttons + - Face buttons (A, B, C, Up, Down, Left, Right) + - Title screen +- Hand icon +- Hand grab icon +- All items + +### Music +Borrow as much from other projects as possible. Combine race and fight music? If the sound circuit is created, might need to source some music. + +- Simple grove theme +- Action theme (Reuse from other games?) +- Song for singing competition (Use songs from previous years) + +### SFX +- Interface beep +- Chowa interaction "pok" +- Chowa effort noises (2-5) +- Chowa vocalizations (2-5) +- Chowa pain noise +- Chowa eating +- Water splashes +- Footsteps +- Buying things (Cha-ching) + +### Font +- Menu font +- Compact font for in Grove diff --git a/main/modes/games/cGrove/Docs/Tasks.md b/main/modes/games/cGrove/Docs/Tasks.md new file mode 100644 index 000000000..9599a9eb0 --- /dev/null +++ b/main/modes/games/cGrove/Docs/Tasks.md @@ -0,0 +1,45 @@ +# Tasks + +## General +- Add BGM +- Update menu colors +- Load data from NVM +- View recently interacted with players + +## Spar +- Add BGM +- Create/Add SFX +- Add background images that jump on crit +- Add sparring animations + - Splash screen + - Main game +- Add opponent selection screen +- Add summary screen +- Save and load contest results from NVM +- Add NPCs +- Add online functionality +- Get icons for punching, kicking, dodging, etc + +## Grove +- AI + - Go to an item + - Using item +- Add menu for buying items +- Stats can be increased +- Save and load from NVS +- Create/Add SFX + +## Race +- Build mode +- Add BGM +- Create/Add SFX + +## Performance +- build mode +- Add BGM +- Create/Add SFX + +## Chowa +- Add palette swapping to normal chowa (if they get made) +- Add all new Chowa sprites +- Prune unused moods, sprites, behaviors, etc \ No newline at end of file diff --git a/main/modes/games/cGrove/Garden/cg_Grove.c b/main/modes/games/cGrove/Garden/cg_Grove.c new file mode 100644 index 000000000..ccaef7091 --- /dev/null +++ b/main/modes/games/cGrove/Garden/cg_Grove.c @@ -0,0 +1,670 @@ +/** + * @file cg_Garden.c + * @author Jeremy Stintzcum (jeremy.stintzcum@gmail.com) + * @brief The main interation area with the Chowa + * @version 0.1 + * @date 2024-09-07 + * + * @copyright Copyright (c) 2024 + * + */ + +//============================================================================== +// Includes +//============================================================================== + +#include "cg_Grove.h" +#include "cg_GroveAI.h" +#include "cg_GroveDraw.h" +#include "cg_Items.h" +#include + +//============================================================================== +// Defines +//============================================================================== + +#define CG_CURSOR_SPEED 16 + +//============================================================================== +// Consts +//============================================================================== + +static const char* groveCursorSprites[] = { + "chowa_hand1.wsg", + "chowa_hand2.wsg", + "chowa_hand3.wsg", +}; + +static const char* questionMarkSprites[] = { + "questmk-1.wsg", "questmk-2.wsg", "questmk-3.wsg", "questmk-4.wsg", "questmk-5.wsg", "questmk-6.wsg", +}; + +static const char* angerParticles[] = { + "anger-1.wsg", + "anger-2.wsg", +}; + +static const char* musicNoteSprites[] = { + "cg_note1.wsg", + "cg_note2.wsg", + "cg_note3.wsg", +}; + +static const char* speechBubbleSprites[] = { + "cg_text0.wsg", + "cg_text1.wsg", + "cg_text2.wsg", + "cg_text3.wsg", +}; + +static const char* itemSprites[] = { + "agi_book.wsg", "cha_book.wsg", "spd_book.wsg", "sta_book.wsg", "str_book.wsg", "cg_ball.wsg", + "cg_crayons.wsg", "cg_knife.wsg", "cg_toy_sword.wsg", "cake.wsg", "souffle.wsg", "DonutRing.wsg", +}; + +static const char shopMenuTitle[] = "Ring Shop"; + +static const char* shopMenuItems[] = { + "Agility Stat Book", + "Charisma Stat Book", + "Strength Stat Book", + "Stamina Stat Book", + "Speed Stat Book", + "Souffle", + "Ball", + "Crayons", + "Toy Sword", + "Back", +}; + +//============================================================================== +// Function Declarations +//============================================================================== + +/** + * @brief Attempts to grab objects. Due to total amount being limited, no need to optimize + * + * @param cg Game Object + */ +static void cg_attemptGrab(cGrove_t* cg); + +/** + * @brief Input handling for garden + * + * @param cg Game Object + */ +static void cg_handleInputGarden(cGrove_t* cg); + +/** + * @brief Moves the view of the field byt eh provided x and y + * + * @param cg Game Object + * @param xChange Distance to move horizontally. Negative is right, positive if left. + * @param yChange Distance to move vertically. Negative is up, positive s down + */ +static void cg_moveCamera(cGrove_t* cg, int16_t xChange, int16_t yChange); + +/** + * @brief Initialize object boundaries + * + * @param cg Game Data + */ +static void cg_setupBorders(cGrove_t* cg); + +/** + * @brief Menu callback + * + * @param label Selection labels + * @param selected If selected + * @param settingVal Value of a changed setting + */ +static void shopMenuCb(const char* label, bool selected, uint32_t settingVal); + +/** + * @brief Callback to restart BGM + * + */ +static void cg_bgmCB(void); + +//============================================================================== +// Variables +//============================================================================== + +bool isBGMPlaying; +cGrove_t* cgr; + +//============================================================================== +// Functions +//============================================================================== + +/** + * @brief Initialize the Garden mode + * + * @param cg Game Object + */ +void cg_initGrove(cGrove_t* cg) +{ + // set cgr + cgr = cg; + + // Load assets + // WSGs + loadWsg("garden_background.wsg", &cg->grove.groveBG, true); + // Cursors + cg->grove.cursors = calloc(ARRAY_SIZE(groveCursorSprites), sizeof(wsg_t)); + for (int32_t idx = 0; idx < ARRAY_SIZE(groveCursorSprites); idx++) + { + loadWsg(groveCursorSprites[idx], &cg->grove.cursors[idx], true); + } + // Emotes + cg->grove.angerParticles = calloc(ARRAY_SIZE(angerParticles), sizeof(wsg_t)); + for (int32_t idx = 0; idx < ARRAY_SIZE(angerParticles); idx++) + { + loadWsg(angerParticles[idx], &cg->grove.angerParticles[idx], true); + } + cg->grove.questionMarks = calloc(ARRAY_SIZE(questionMarkSprites), sizeof(wsg_t)); + for (int32_t idx = 0; idx < ARRAY_SIZE(questionMarkSprites); idx++) + { + loadWsg(questionMarkSprites[idx], &cg->grove.questionMarks[idx], true); + } + cg->grove.notes = calloc(ARRAY_SIZE(musicNoteSprites), sizeof(wsg_t)); + for (int32_t idx = 0; idx < ARRAY_SIZE(musicNoteSprites); idx++) + { + loadWsg(musicNoteSprites[idx], &cg->grove.notes[idx], true); + } + cg->grove.speechBubbles = calloc(ARRAY_SIZE(speechBubbleSprites), sizeof(wsg_t)); + for (int32_t idx = 0; idx < ARRAY_SIZE(speechBubbleSprites); idx++) + { + loadWsg(speechBubbleSprites[idx], &cg->grove.speechBubbles[idx], true); + } + cg->grove.itemsWSGs = calloc(ARRAY_SIZE(itemSprites), sizeof(wsg_t)); + for (int32_t idx = 0; idx < ARRAY_SIZE(itemSprites); idx++) + { + loadWsg(itemSprites[idx], &cg->grove.itemsWSGs[idx], true); + } + // Audio + loadMidiFile("Chowa_Grove_Meadow.mid", &cg->grove.bgm, true); + + // Initialize viewport + cg->grove.camera.height = TFT_HEIGHT; // Used to check what objects should be drawn + cg->grove.camera.width = TFT_WIDTH; + cg->grove.camera.pos.x = (cg->grove.groveBG.w - TFT_HEIGHT) >> 1; + cg->grove.camera.pos.y = (cg->grove.groveBG.h - TFT_WIDTH) >> 1; + + // Initialize the cursor + cg->grove.cursor.height = cg->grove.cursors[0].h; + cg->grove.cursor.width = cg->grove.cursors[0].w; + cg->grove.cursor.pos.x = (TFT_WIDTH - cg->grove.cursors[0].w) >> 1; + cg->grove.cursor.pos.y = (TFT_HEIGHT - cg->grove.cursors[0].h) >> 1; + cg->grove.holdingChowa = false; + cg->grove.holdingItem = false; + + // Setup boundaries + cg_setupBorders(cg); + + // Initialize Chowa + for (int32_t i = 0; i < CG_MAX_CHOWA; i++) + { + cg->grove.chowa[i].chowa = &cg->chowa[i]; + if (cg->grove.chowa[i].chowa->active) + { + cg->grove.chowa[i].aabb.height = 24; + cg->grove.chowa[i].aabb.width = 24; + cg->grove.chowa[i].aabb.pos.x = 32 + esp_random() % (cg->grove.groveBG.w - 64); + cg->grove.chowa[i].aabb.pos.y = 32 + esp_random() % (cg->grove.groveBG.h - 64); + } + } + for (int32_t i = 0; i < CG_GROVE_MAX_GUEST_CHOWA; i++) + { + cg->grove.chowa[i + CG_MAX_CHOWA].chowa = &cg->guests[i]; + if (cg->grove.chowa[i + CG_MAX_CHOWA].chowa->active) + { + cg->grove.chowa[i + CG_MAX_CHOWA].aabb.height = 24; + cg->grove.chowa[i + CG_MAX_CHOWA].aabb.width = 24; + cg->grove.chowa[i + CG_MAX_CHOWA].aabb.pos.x = 32 + esp_random() % (cg->grove.groveBG.w - 64); + cg->grove.chowa[i + CG_MAX_CHOWA].aabb.pos.y = 32 + esp_random() % (cg->grove.groveBG.h - 64); + } + } + + // Initialize items + for (int idx = 0; idx < CG_GROVE_MAX_ITEMS; idx++) + { + // FIXME: Load from NVS + cg->grove.items[idx].active = true; + cg->grove.items[idx].spr = cg->grove.itemsWSGs[idx]; + cg->grove.items[idx].aabb.pos.x + = 32 + (esp_random() % (cg->grove.groveBG.w - (64 + cg->grove.itemsWSGs[idx].w))); + cg->grove.items[idx].aabb.pos.y + = 32 + (esp_random() % (cg->grove.groveBG.h - (64 + cg->grove.itemsWSGs[idx].h))); + cg->grove.items[idx].aabb.height = cg->grove.itemsWSGs[idx].h; + cg->grove.items[idx].aabb.width = cg->grove.itemsWSGs[idx].w; + char buffer[32]; + snprintf(buffer, sizeof(buffer) - 1, "Item_%d" PRId16, idx); + strcpy(cg->grove.items[idx].name, buffer); + } + + // Init Ring + cg->grove.ring.aabb.height = cg->grove.itemsWSGs[11].h; + cg->grove.ring.aabb.width = cg->grove.itemsWSGs[11].w; + + // Initialize shop menu + cg->grove.shop = initMenu(shopMenuTitle, shopMenuCb); + for (int idx = 0; idx < ARRAY_SIZE(shopMenuItems); idx++) + { + addSingleItemToMenu(cg->grove.shop, shopMenuItems[idx]); + } + // TODO: Recolor + cg->grove.renderer = initMenuManiaRenderer(NULL, NULL, NULL); + cg->grove.shopOpen = true; + + // TODO: Load inventory from NVS + cg->grove.inv.money = 0; +} + +/** + * @brief Destroys the grove data + * + * @param cg Game Data + */ +void cg_deInitGrove(cGrove_t* cg) +{ + deinitMenuManiaRenderer(cg->grove.renderer); + deinitMenu(cg->grove.shop); + + // Unload assets + // Audio + unloadMidiFile(&cg->grove.bgm); + // WSGs + for (uint8_t i = 0; i < ARRAY_SIZE(itemSprites); i++) + { + freeWsg(&cg->grove.itemsWSGs[i]); + } + free(cg->grove.itemsWSGs); + for (uint8_t i = 0; i < ARRAY_SIZE(speechBubbleSprites); i++) + { + freeWsg(&cg->grove.speechBubbles[i]); + } + free(cg->grove.speechBubbles); + for (uint8_t i = 0; i < ARRAY_SIZE(musicNoteSprites); i++) + { + freeWsg(&cg->grove.notes[i]); + } + free(cg->grove.notes); + for (uint8_t i = 0; i < ARRAY_SIZE(questionMarkSprites); i++) + { + freeWsg(&cg->grove.questionMarks[i]); + } + free(cg->grove.questionMarks); + for (uint8_t i = 0; i < ARRAY_SIZE(angerParticles); i++) + { + freeWsg(&cg->grove.angerParticles[i]); + } + free(cg->grove.angerParticles); + for (uint8_t i = 0; i < ARRAY_SIZE(groveCursorSprites); i++) + { + freeWsg(&cg->grove.cursors[i]); + } + free(cg->grove.cursors); + freeWsg(&cg->grove.groveBG); +} + +/** + * @brief Main loop for Garden mode + * + * @param cg Game Object + */ +void cg_runGrove(cGrove_t* cg, int64_t elapsedUS) +{ + // Play BGM if it's not playing + if (!isBGMPlaying) + { + soundPlayBgmCb(&cg->grove.bgm, MIDI_BGM, cg_bgmCB); + isBGMPlaying = true; + } + if (cg->grove.shopOpen) + { + buttonEvt_t evt = {0}; + while (checkButtonQueueWrapper(&evt)) + { + cg->grove.shop = menuButton(cg->grove.shop, evt); + } + drawMenuMania(cg->grove.shop, cg->grove.renderer, elapsedUS); + // TODO: Add ring count to menu + // TODO: Draw current inv count over selected item + return; + } + // Input + cg_handleInputGarden(cg); + + // Garden Logic + if (cg->grove.holdingItem) + { + cg->grove.heldItem->aabb.pos = addVec2d(cg->grove.cursor.pos, cg->grove.camera.pos); + } + if (cg->grove.holdingChowa) + { + cg->grove.heldChowa->aabb.pos = addVec2d(cg->grove.cursor.pos, cg->grove.camera.pos); + } + + // Resurface items dropped in the water + vec_t temp; + for (int idx = 0; idx < CG_GROVE_MAX_ITEMS; idx++) + { + if (&cg->grove.items[idx] != cg->grove.heldItem + && rectRectIntersection(cg->grove.items[idx].aabb, cg->grove.boundaries[CG_WATER], &temp)) + { + cg->grove.items[idx].aabb.pos.x + = 32 + (esp_random() % (cg->grove.groveBG.w - (64 + cg->grove.itemsWSGs[idx].w))); + cg->grove.items[idx].aabb.pos.y + = 32 + (esp_random() % (cg->grove.groveBG.h - (64 + cg->grove.itemsWSGs[idx].h))); + } + } + + // Spawn Rings + if (esp_random() % 512 == 0) + { + cg->grove.ring.aabb.pos.x = 32 + (esp_random() % (cg->grove.groveBG.w - (64 + cg->grove.itemsWSGs[11].w))); + cg->grove.ring.aabb.pos.y = 32 + (esp_random() % (cg->grove.groveBG.h - (64 + cg->grove.itemsWSGs[11].h))); + cg->grove.ring.active = true; + } + + // Chowa AI + for (int32_t idx = 0; idx < CG_MAX_CHOWA + CG_GROVE_MAX_GUEST_CHOWA; idx++) + { + if (cg->grove.chowa[idx].chowa->active) + { + cg_GroveAI(cg, &cg->grove.chowa[idx], elapsedUS); + } + } + + // Draw + cg_groveDraw(cg, elapsedUS); +} + +//============================================================================== +// Static functions +//============================================================================== + +static void cg_attemptGrab(cGrove_t* cg) +{ + vec_t collVec; + cg->grove.heldItem = NULL; + // Check if over a Chowa + for (int8_t c = 0; c < CG_MAX_CHOWA; c++) + { + if (cg->chowa[c].active) + { + rectangle_t translated = {.pos = subVec2d(cg->grove.chowa[c].aabb.pos, cg->grove.camera.pos), + .height = cg->grove.chowa[c].aabb.height, + .width = cg->grove.chowa[c].aabb.width}; + if (rectRectIntersection(cg->grove.cursor, translated, &collVec)) + { + cg->grove.holdingChowa = true; + cg->grove.heldChowa = &cg->grove.chowa[c]; + cg->grove.heldChowa->gState = CHOWA_HELD; + } + } + } + // Check if over an item + for (int8_t item = 0; item < CG_GROVE_MAX_ITEMS; item++) + { + if (cg->grove.items[item].active) + { + rectangle_t translated = {.pos = subVec2d(cg->grove.items[item].aabb.pos, cg->grove.camera.pos), + .height = cg->grove.items[item].aabb.height, + .width = cg->grove.items[item].aabb.width}; + if (rectRectIntersection(cg->grove.cursor, translated, &collVec)) + { + cg->grove.holdingItem = true; + cg->grove.heldItem = &cg->grove.items[item]; + } + } + } +} + +static void cg_handleInputGarden(cGrove_t* cg) +{ + buttonEvt_t evt; + // Touch pad for the hands + if (cg->touch) + { + int32_t phi, r, intensity; + if (getTouchJoystick(&phi, &r, &intensity)) + { + int16_t speed = phi >> 5; + if (!(speed <= 5)) + { + printf("touch center: %" PRIu32 ", intensity: %" PRIu32 ", intensity %" PRIu32 "\n", phi, r, intensity); + // Move hand + cg->grove.cursor.pos.x += (getCos1024(phi) * speed) / 1024; + cg->grove.cursor.pos.y -= (getSin1024(phi) * speed) / 1024; + } + } + while (checkButtonQueueWrapper(&evt)) + { + if (evt.button & PB_A && evt.down) + { + if (cg->grove.holdingItem) + { + cg->grove.holdingItem = false; + cg->grove.heldItem = NULL; + } + else if (cg->grove.holdingChowa) + { + cg->grove.holdingChowa = false; + cg->grove.heldChowa->gState = CHOWA_IDLE; + } + else + { + cg_attemptGrab(cg); + } + } + if (evt.button & PB_B && evt.down) + { + for (int idx = 0; idx < CG_MAX_CHOWA + CG_GROVE_MAX_GUEST_CHOWA; idx++) + { + vec_t temp; + rectangle_t rect = {.pos = addVec2d(cg->grove.cursor.pos, cg->grove.camera.pos), + .height = cg->grove.cursor.height, + .width = cg->grove.cursor.width}; + // Chowa + if (rectRectIntersection(rect, cg->grove.chowa[idx].aabb, &temp)) + { + cg->grove.chowa[idx].gState = CHOWA_PET; + cg->grove.chowa[idx].timeLeft = 1000000; + } + } + } + if (evt.button & PB_START && evt.down) + { + cg->grove.shopOpen = true; + } + } + } + else + { + vec_t temp; + rectangle_t rect = {.pos = addVec2d(cg->grove.cursor.pos, cg->grove.camera.pos), + .height = cg->grove.cursor.height, + .width = cg->grove.cursor.width}; + while (checkButtonQueueWrapper(&evt)) + { + if (evt.button & PB_RIGHT) + { + cg->grove.cursor.pos.x += CG_CURSOR_SPEED; + } + else if (evt.button & PB_LEFT) + { + cg->grove.cursor.pos.x -= CG_CURSOR_SPEED; + } + if (evt.button & PB_UP) + { + cg->grove.cursor.pos.y -= CG_CURSOR_SPEED; + } + else if (evt.button & PB_DOWN) + { + cg->grove.cursor.pos.y += CG_CURSOR_SPEED; + } + if (evt.button & PB_A && evt.down) + { + if (cg->grove.holdingItem) + { + cg->grove.holdingItem = false; + cg->grove.heldItem = NULL; + } + else if (cg->grove.holdingChowa) + { + cg->grove.holdingChowa = false; + cg->grove.heldChowa->gState = CHOWA_IDLE; + } + else if (cg->grove.ring.active && rectRectIntersection(rect, cg->grove.ring.aabb, &temp)) + { + cg->grove.ring.active = false; + cg->grove.inv.money += 1; + } + else + { + cg_attemptGrab(cg); + } + } + if (evt.button & PB_B && evt.down) + { + for (int idx = 0; idx < CG_MAX_CHOWA + CG_GROVE_MAX_GUEST_CHOWA; idx++) + { + // Chowa + if (rectRectIntersection(rect, cg->grove.chowa[idx].aabb, &temp)) + { + cg->grove.chowa[idx].gState = CHOWA_PET; + cg->grove.chowa[idx].timeLeft = 1000000; + } + } + } + if (evt.button & PB_START && evt.down) + { + cg->grove.shopOpen = true; + } + } + } + // Check if out of bounds + if (cg->grove.cursor.pos.x < CG_GROVE_SCREEN_BOUNDARY) + { + cg_moveCamera(cg, -CG_CURSOR_SPEED, 0); + cg->grove.cursor.pos.x = CG_GROVE_SCREEN_BOUNDARY; + } + else if (cg->grove.cursor.pos.x > TFT_WIDTH - (CG_GROVE_SCREEN_BOUNDARY + cg->grove.cursor.width)) + { + cg_moveCamera(cg, CG_CURSOR_SPEED, 0); + cg->grove.cursor.pos.x = TFT_WIDTH - (CG_GROVE_SCREEN_BOUNDARY + cg->grove.cursor.width); + } + if (cg->grove.cursor.pos.y < CG_GROVE_SCREEN_BOUNDARY) + { + cg_moveCamera(cg, 0, -CG_CURSOR_SPEED); + cg->grove.cursor.pos.y = CG_GROVE_SCREEN_BOUNDARY; + } + else if (cg->grove.cursor.pos.y > TFT_HEIGHT - (CG_GROVE_SCREEN_BOUNDARY + cg->grove.cursor.height)) + { + cg_moveCamera(cg, 0, CG_CURSOR_SPEED); + cg->grove.cursor.pos.y = TFT_HEIGHT - (CG_GROVE_SCREEN_BOUNDARY + cg->grove.cursor.height); + } +} + +static void cg_moveCamera(cGrove_t* cg, int16_t xChange, int16_t yChange) +{ + cg->grove.camera.pos.x += xChange; + cg->grove.camera.pos.y += yChange; + if (cg->grove.camera.pos.x < 0) + { + cg->grove.camera.pos.x = 0; + } + else if (cg->grove.camera.pos.x > (cg->grove.groveBG.w - TFT_WIDTH)) + { + cg->grove.camera.pos.x = (cg->grove.groveBG.w - TFT_WIDTH); + } + if (cg->grove.camera.pos.y < 0) + { + cg->grove.camera.pos.y = 0; + } + else if (cg->grove.camera.pos.y > (cg->grove.groveBG.h - TFT_HEIGHT)) + { + cg->grove.camera.pos.y = (cg->grove.groveBG.h - TFT_HEIGHT); + } +} + +static void cg_setupBorders(cGrove_t* cg) +{ + // Tree + cg->grove.boundaries[CG_TREE].pos.x = 0; + cg->grove.boundaries[CG_TREE].pos.y = 10; + cg->grove.boundaries[CG_TREE].width = 106; + cg->grove.boundaries[CG_TREE].height = 82; + + // Stump + cg->grove.boundaries[CG_STUMP].pos.x = 492; + cg->grove.boundaries[CG_STUMP].pos.y = 66; + cg->grove.boundaries[CG_STUMP].width = 48; + cg->grove.boundaries[CG_STUMP].height = 32; + + // Water + cg->grove.boundaries[CG_WATER].pos.x = 32; + cg->grove.boundaries[CG_WATER].pos.y = 350; + cg->grove.boundaries[CG_WATER].width = 280; + cg->grove.boundaries[CG_WATER].height = 108; +} + +static void shopMenuCb(const char* label, bool selected, uint32_t settingVal) +{ + // FIXME: implement prices + if (selected) + { + if (label == shopMenuItems[0]) + { + // Agi book + } + else if (label == shopMenuItems[1]) + { + // Cha book + } + else if (label == shopMenuItems[2]) + { + // Spd book + } + else if (label == shopMenuItems[3]) + { + // Sta book + } + else if (label == shopMenuItems[4]) + { + // Str book + } + else if (label == shopMenuItems[5]) + { + // Souffle + } + else if (label == shopMenuItems[6]) + { + // Ball + } + else if (label == shopMenuItems[7]) + { + // Crayons + } + else if (label == shopMenuItems[8]) + { + // Toy Sword + } + else if (label == shopMenuItems[9]) + { + // Back + cgr->grove.shopOpen = false; + } + else + { + // Unset item + } + } +} + +static void cg_bgmCB() +{ + isBGMPlaying = false; +} \ No newline at end of file diff --git a/main/modes/games/cGrove/Garden/cg_Grove.h b/main/modes/games/cGrove/Garden/cg_Grove.h new file mode 100644 index 000000000..2c88a00eb --- /dev/null +++ b/main/modes/games/cGrove/Garden/cg_Grove.h @@ -0,0 +1,27 @@ +/** + * @file cg_Garden.h + * @author Jeremy Stintzcum (jeremy.stintzcum@gmail.com) + * @brief The main interation area with the Chowa + * @version 0.1 + * @date 2024-09-07 + * + * @copyright Copyright (c) 2024 + * + */ +#pragma once + +//============================================================================== +// Includes +//============================================================================== + +#include "cg_Typedef.h" + +//============================================================================== +// Function declarations +//============================================================================== + +void cg_initGrove(cGrove_t* cg); + +void cg_deInitGrove(cGrove_t* cg); + +void cg_runGrove(cGrove_t* cg, int64_t elapsedUS); \ No newline at end of file diff --git a/main/modes/games/cGrove/Garden/cg_GroveAI.c b/main/modes/games/cGrove/Garden/cg_GroveAI.c new file mode 100644 index 000000000..c09494a76 --- /dev/null +++ b/main/modes/games/cGrove/Garden/cg_GroveAI.c @@ -0,0 +1,351 @@ +/** + * @file cg_GroveAI.c + * @author Jeremy Stintzcum (jeremy.stintzcum@gmail.com) + * @brief Chowa AI in the garden + * @version 0.1 + * @date 2024-10-13 + * + * @copyright Copyright (c) 2024 + * + */ + +//============================================================================== +// Includes +//============================================================================== + +#include "cg_GroveAI.h" +#include "trigonometry.h" +#include +#include + +//============================================================================== +// Defines +//============================================================================== + +#define SECOND 1000000 + +//============================================================================== +// Function Declarations +//============================================================================== + +/** + * @brief Gets a new target position to wander to + * + * @param cg Game Data + * @param c Pointer to CHowa to calc position for + */ +static void cg_GroveGetRandMovePoint(cGrove_t* cg, cgGroveChowa_t* c); + +/** + * @brief Sets a new task for the Chowa + * + * @param cg Game Data + * @param c Chowa Data + */ +static cgChowaStateGarden_t cg_getNewTask(cGrove_t* cg, cgGroveChowa_t* c); + +//============================================================================== +// Functions +//============================================================================== + +/** + * @brief + * + * @param cg Game data + * @param chowa The chowa to run AI on + * @param elapsedUs Time since last frame + */ +void cg_GroveAI(cGrove_t* cg, cgGroveChowa_t* c, int64_t elapsedUs) +{ + // - Go to place/item + // - Use item (Must be holding item) + + // Abort if not active + if (!c->chowa->active) + { + return; + } + + // Update timer + c->timeLeft -= elapsedUs; + + // The MONOLITH + switch (c->gState) + { + case CHOWA_IDLE: + { + // Chowa is essentially unset. Look for new behavior + c->gState = cg_getNewTask(cg, c); + c->animFrame = 0; // Reset animation frame to avoid displaying garbage data + break; + } + case CHOWA_STATIC: + { + // Chowa is standing around doing nothing + if (c->timeLeft <= 0) + { + c->gState = CHOWA_IDLE; + } + break; + } + case CHOWA_WALK: + { + // Calculate the distance to target + if (c->targetPos.x == 0 || c->targetPos.y == 0) + { + cg_GroveGetRandMovePoint(cg, c); + return; + } + vec_t difference = {.x = c->targetPos.x - c->aabb.pos.x, .y = c->targetPos.y - c->aabb.pos.y}; + if (sqMagVec2d(difference) < pow(c->precision, 2)) + { + c->timeLeft = c->nextTimeLeft; + c->gState = c->nextState; + } + fastNormVec(&difference.x, &difference.y); + int16_t angle = getAtan2(difference.y, difference.x); + c->angle = angle; + c->aabb.pos.x += difference.x / 128; + c->aabb.pos.y += difference.y / 128; + break; + } + case CHOWA_CHASE: + { + // Calculate the distance to target + c->targetPos = addVec2d(cg->grove.cursor.pos, cg->grove.camera.pos); + vec_t difference = {.x = c->targetPos.x - c->aabb.pos.x, .y = c->targetPos.y - c->aabb.pos.y}; + if (sqMagVec2d(difference) < pow(c->precision, 2)) + { + c->timeLeft = c->nextTimeLeft; + c->gState = c->nextState; + } + fastNormVec(&difference.x, &difference.y); + int16_t angle = getAtan2(difference.y, difference.x); + c->angle = angle; + c->aabb.pos.x += difference.x / 128; + c->aabb.pos.y += difference.y / 128; + break; + } + case CHOWA_USE_ITEM: + { + // Wait until item is done being used + break; + } + case CHOWA_BOX: + { + // Cycle between attack animations + if (c->timeLeft <= 0) + { + c->gState = CHOWA_IDLE; + } + else + { + // Update stats + } + break; + } + case CHOWA_SING: + { + // stand in place and sing + if (c->timeLeft <= 0) + { + c->gState = CHOWA_IDLE; + } + else + { + // Update stats + } + break; + } + case CHOWA_DANCE: + { + // stand in place and sing + if (c->timeLeft <= 0) + { + c->gState = CHOWA_IDLE; + } + else + { + // Update stats + } + break; + } + case CHOWA_TALK: + { + // talk to another chowa + if (c->timeLeft <= 0) + { + c->gState = CHOWA_IDLE; + } + else + { + // Update stats + } + break; + } + case CHOWA_HELD: + { + // Picked up by player + // If held for too long, start losing affinity + break; + } + case CHOWA_GIFT: + { + // stand in place with arms raised + if (c->timeLeft <= 0) + { + c->gState = CHOWA_IDLE; + } + break; + } + case CHOWA_PET: + { + // Chowa is being pet + break; + } + default: + { + break; + } + } +} + +//============================================================================== +// Static Functions +//============================================================================== + +static void cg_GroveGetRandMovePoint(cGrove_t* cg, cgGroveChowa_t* c) +{ + // Get a random point inside the bounds of the play area + // - Cannot be on stump/in tree/too close to edge + rectangle_t targetPos = {.height = 32, .width = 32}; + vec_t colVec; + + // Check if inside an object + do + { + targetPos.pos.x = 32 + (esp_random() % (cg->grove.groveBG.w - 64)); + targetPos.pos.y = 32 + (esp_random() % (cg->grove.groveBG.h - 64)); + } while (rectRectIntersection(targetPos, cg->grove.boundaries[CG_TREE], &colVec) + || rectRectIntersection(targetPos, cg->grove.boundaries[CG_STUMP], &colVec)); + + // Once a position is found, calculate angle and distance to point + c->targetPos = targetPos.pos; +} + +static cgChowaStateGarden_t cg_getNewTask(cGrove_t* cg, cgGroveChowa_t* c) +{ + // If in water, continue moving until outside the water + vec_t temp; + if (rectRectIntersection(c->aabb, cg->grove.boundaries[CG_WATER], &temp)) + { + cg_GroveGetRandMovePoint(cg, c); + c->precision = 10.0f; + c->frameTimer += esp_random() % SECOND; // Offset frame timer + c->nextState = CHOWA_IDLE; + return CHOWA_WALK; + } + // Check other behaviors + for (int32_t idx = 0; idx < CG_MAX_CHOWA + CG_GROVE_MAX_GUEST_CHOWA; idx++) + { + if ((cg->grove.chowa[idx].gState == CHOWA_SING || cg->grove.chowa[idx].gState == CHOWA_DANCE) + && (esp_random() % 6 == 0) && !(c->chowa->mood == CG_ANGRY || c->chowa->mood == CG_SAD)) + { + // Get the other Chowa's position + c->targetPos = cg->grove.chowa[idx].aabb.pos; + c->precision = 32.0f; + if (esp_random() % 2 == 0) + { + c->nextState = CHOWA_SING; + } + else + { + c->nextState = CHOWA_DANCE; + } + cg->grove.chowa[idx].timeLeft += SECOND; + c->nextTimeLeft = cg->grove.chowa[idx].timeLeft; + return CHOWA_WALK; + } + else if (cg->grove.chowa[idx].gState == CHOWA_BOX && esp_random() % 6 == 0 && !cg->grove.chowa[idx].hasPartner) + { + cg->grove.chowa[idx].hasPartner = true; + c->targetPos.x = cg->grove.chowa[idx].aabb.pos.x + 24; + c->targetPos.y = cg->grove.chowa[idx].aabb.pos.y; + c->precision = 10.0f; + c->nextState = CHOWA_BOX; + c->flip = true; + c->hasPartner = true; + cg->grove.chowa[idx].timeLeft += SECOND; + c->nextTimeLeft = cg->grove.chowa[idx].timeLeft; + return CHOWA_WALK; + } + else if (cg->grove.chowa[idx].gState == CHOWA_TALK && esp_random() % 6 == 0 && !cg->grove.chowa[idx].hasPartner) + { + cg->grove.chowa[idx].hasPartner = true; + c->targetPos.x = cg->grove.chowa[idx].aabb.pos.x + 24; + c->targetPos.y = cg->grove.chowa[idx].aabb.pos.y; + c->precision = 10.0f; + c->nextState = CHOWA_TALK; + c->flip = true; + c->hasPartner = true; + cg->grove.chowa[idx].timeLeft += SECOND; + c->nextTimeLeft = cg->grove.chowa[idx].timeLeft; + return CHOWA_WALK; + } + } + // Check mood + if (!(c->chowa->mood == CG_ANGRY || c->chowa->mood == CG_SAD) && (esp_random() % 6 == 0)) + { + c->timeLeft = SECOND * (10); + if (esp_random() % 2 == 0) + { + return CHOWA_SING; + } + return CHOWA_DANCE; + } + if (c->chowa->mood != CG_HAPPY && esp_random() % 6 == 0) + { + c->timeLeft = SECOND * (10); + c->animIdx = 0; + c->flip = false; + c->hasPartner = false; + return CHOWA_BOX; + } + // Check player affinity + if (c->chowa->playerAffinity > 100 && esp_random() % 6 == 0) + { + c->precision = 16.0f; + c->nextTimeLeft = SECOND * (1 + (esp_random() % 3)); + c->nextState = CHOWA_GIFT; + return CHOWA_CHASE; + } + // Check for held items + // - Much more likely to use held items + // Check if items present on map + // - If item is loose, will more likely go grab it than do anything else + // Otherwise, choose randomly + switch (esp_random() % 3) + { + case 0: + { + cg_GroveGetRandMovePoint(cg, c); + c->precision = 10.0f; + c->frameTimer += esp_random() % SECOND; // Offset frame timer + c->nextState = CHOWA_IDLE; + return CHOWA_WALK; + } + case 1: + default: + { + c->timeLeft = SECOND * (1 + (esp_random() % 10)); + return CHOWA_STATIC; + } + case 2: + { + c->timeLeft = SECOND * (10); + c->flip = false; + c->hasPartner = false; + return CHOWA_TALK; + } + } +} \ No newline at end of file diff --git a/main/modes/games/cGrove/Garden/cg_GroveAI.h b/main/modes/games/cGrove/Garden/cg_GroveAI.h new file mode 100644 index 000000000..ed837197b --- /dev/null +++ b/main/modes/games/cGrove/Garden/cg_GroveAI.h @@ -0,0 +1,23 @@ +/** + * @file cg_GroveAI.h + * @author Jeremy Stintzcum (jeremy.stintzcum@gmail.com) + * @brief Chowa AI in the garden + * @version 0.1 + * @date 2024-10-13 + * + * @copyright Copyright (c) 2024 + * + */ +#pragma once + +//============================================================================== +// Includes +//============================================================================== + +#include "cg_Typedef.h" + +//============================================================================== +// Function declarations +//============================================================================== + +void cg_GroveAI(cGrove_t* cg, cgGroveChowa_t* chowa, int64_t elapsedUs); \ No newline at end of file diff --git a/main/modes/games/cGrove/Garden/cg_GroveDraw.c b/main/modes/games/cGrove/Garden/cg_GroveDraw.c new file mode 100644 index 000000000..b6cebc6c4 --- /dev/null +++ b/main/modes/games/cGrove/Garden/cg_GroveDraw.c @@ -0,0 +1,498 @@ +/** + * @file cg_GroveDraw.c + * @author Jeremy Stintzcum (jeremy.stintzcum@gmail.com) + * @brief Drawing functions for the Grove mode of Chowa Grove + * @version 0.1 + * @date 2024-10-09 + * + * @copyright Copyright (c) 2024 + * + */ + +//============================================================================== +// Includes +//============================================================================== + +#include "cg_GroveDraw.h" +#include "cg_Chowa.h" +#include + +//============================================================================== +// Defines +//============================================================================== + +#define SECOND 1000000 + +//============================================================================== +// Function declarations +//============================================================================== + +/** + * @brief Draws the hand at the appropriate position + * + * @param cg Game object + */ +static void cg_drawHand(cGrove_t* cg); + +/** + * @brief Draws item at specified index + * + * @param cg Game Object + * @param idx index of item to draw + */ +static void cg_drawItem(cGrove_t* cg, int8_t idx); + +/** + * @brief Draws the ring + * + * @param cg Game Data + */ +static void cg_drawRing(cGrove_t* cg); + +/** + * @brief Draws a Chowa + * + * @param cg Game Data + * @param elapsedUS Time since last frame + */ +static void cg_drawChowaGrove(cGrove_t* cg, int64_t elapsedUS); + +/** + * @brief Debug visualization + * + * @param cg Game Data + */ +static void cg_groveDebug(cGrove_t* cg); + +/** + * @brief Draws the UI elements + * + * @param cg Game Data + */ +static void cg_drawUI(cGrove_t* cg); + +//============================================================================== +// Functions +//============================================================================== + +/** + * @brief Draws the grove in its current state + * + * @param cg Game Data + */ +void cg_groveDraw(cGrove_t* cg, int64_t elapsedUs) +{ + // Get camera offset + + // Draw BG + drawWsgSimple(&cg->grove.groveBG, -cg->grove.camera.pos.x, -cg->grove.camera.pos.y); + + // Draw Items + for (int8_t item = 0; item < CG_GROVE_MAX_ITEMS; item++) + { + if (cg->grove.items[item].active) + { + cg_drawItem(cg, item); + } + } + + cg_drawRing(cg); + + // Draw Chowa + cg_drawChowaGrove(cg, elapsedUs); + + // Draw UI + cg_drawHand(cg); + cg_drawUI(cg); + + // Debug draw + cg_groveDebug(cg); +} + +//============================================================================== +// Static Functions +//============================================================================== + +static void cg_drawHand(cGrove_t* cg) +{ + // If holding + if (cg->grove.holdingChowa || cg->grove.holdingItem) + { + drawWsgSimple(&cg->grove.cursors[2], cg->grove.cursor.pos.x, cg->grove.cursor.pos.y); + return; + } + vec_t temp; + rectangle_t rect = {.pos = addVec2d(cg->grove.cursor.pos, cg->grove.camera.pos), + .height = cg->grove.cursor.height, + .width = cg->grove.cursor.width}; + // If hovering over + for (int idx = 0; idx < CG_MAX_CHOWA + CG_GROVE_MAX_GUEST_CHOWA; idx++) + { + // Chowa + if (rectRectIntersection(rect, cg->grove.chowa[idx].aabb, &temp)) + { + drawWsgSimple(&cg->grove.cursors[1], cg->grove.cursor.pos.x, cg->grove.cursor.pos.y); + return; + } + } + for (int idx = 0; idx < CG_GROVE_MAX_ITEMS; idx++) + { + // Items + if (rectRectIntersection(rect, cg->grove.items[idx].aabb, &temp)) + { + drawWsgSimple(&cg->grove.cursors[1], cg->grove.cursor.pos.x, cg->grove.cursor.pos.y); + return; + } + } + // Ring + if (rectRectIntersection(rect, cg->grove.ring.aabb, &temp)) + { + drawWsgSimple(&cg->grove.cursors[1], cg->grove.cursor.pos.x, cg->grove.cursor.pos.y); + return; + } + // Otherwise + drawWsgSimple(&cg->grove.cursors[0], cg->grove.cursor.pos.x, cg->grove.cursor.pos.y); +} + +static void cg_drawItem(cGrove_t* cg, int8_t idx) +{ + int16_t xOffset = cg->grove.items[idx].aabb.pos.x - cg->grove.camera.pos.x; + int16_t yOffset = cg->grove.items[idx].aabb.pos.y - cg->grove.camera.pos.y; + drawWsgSimple(&cg->grove.items[idx].spr, xOffset, yOffset); + drawText(&cg->menuFont, c555, cg->grove.items[idx].name, xOffset, yOffset - 16); +} + +static void cg_drawRing(cGrove_t* cg) +{ + int16_t xOffset = cg->grove.ring.aabb.pos.x - cg->grove.camera.pos.x; + int16_t yOffset = cg->grove.ring.aabb.pos.y - cg->grove.camera.pos.y; + if (cg->grove.ring.active) + { + drawWsgSimple(&cg->grove.itemsWSGs[11], xOffset, yOffset); + } +} + +static void cg_drawChowaGrove(cGrove_t* cg, int64_t elapsedUS) +{ + for (int idx = 0; idx < CG_MAX_CHOWA + CG_GROVE_MAX_GUEST_CHOWA; idx++) + { + cgGroveChowa_t* c = &cg->grove.chowa[idx]; + if (!c->chowa->active) + { + continue; + } + int16_t xOffset = c->aabb.pos.x - cg->grove.camera.pos.x; + int16_t yOffset = c->aabb.pos.y - cg->grove.camera.pos.y; + wsg_t* spr; + spr = cg_getChowaWSG(cg, c->chowa, CG_ANIM_WALK_DOWN, 0); + switch (c->gState) + { + case CHOWA_STATIC: + { + // Update animation frame if enough time has passed + c->frameTimer += elapsedUS; + if (c->frameTimer > SECOND / 2) + { + c->frameTimer = 0; + c->animFrame = (c->animFrame + 1) % 2; + } + // Pick option + if (c->chowa->mood == CG_ANGRY) + { + spr = cg_getChowaWSG(cg, c->chowa, CG_ANIM_ANGRY, c->animFrame); + drawWsgSimple(spr, xOffset, yOffset); + if (c->animFrame == 0) + { + drawWsgSimple(&cg->grove.angerParticles[1], xOffset, yOffset); + } + else + { + drawWsg(&cg->grove.angerParticles[1], xOffset + 17, yOffset, true, false, 0); + drawWsgSimple(&cg->grove.angerParticles[0], xOffset + 5, yOffset + 3); + } + } + else if (c->chowa->mood == CG_SAD) + { + spr = cg_getChowaWSG(cg, c->chowa, CG_ANIM_SAD, c->animFrame); + drawWsgSimple(spr, xOffset, yOffset); + } + else if (c->chowa->mood == CG_HAPPY) + { + spr = cg_getChowaWSG(cg, c->chowa, CG_ANIM_PET, 0); + drawWsgSimple(spr, xOffset, yOffset); + } + else if (c->chowa->mood == CG_CONFUSED) + { + spr = cg_getChowaWSG(cg, c->chowa, CG_ANIM_SIT, 0); + drawWsgSimple(spr, xOffset, yOffset); + switch (c->chowa->type) + { + case CG_RED_LUMBERJACK: + default: + { + drawWsgSimple(&cg->grove.questionMarks[0], xOffset + 5, yOffset - 10); + break; + } + case CG_GREEN_LUMBERJACK: + { + drawWsgSimple(&cg->grove.questionMarks[1], xOffset + 5, yOffset - 10); + break; + } + case CG_CHO: + { + drawWsgSimple(&cg->grove.questionMarks[2], xOffset + 5, yOffset - 10); + break; + } + case CG_LILB: + { + drawWsgSimple(&cg->grove.questionMarks[3], xOffset + 5, yOffset - 10); + break; + } + case CG_KOSMO: + { + drawWsgSimple(&cg->grove.questionMarks[4], xOffset + 5, yOffset - 10); + break; + } + case CG_KING_DONUT: + { + drawWsgSimple(&cg->grove.questionMarks[5], xOffset + 5, yOffset - 10); + break; + } + } + } + else + { + spr = cg_getChowaWSG(cg, c->chowa, CG_ANIM_SIT, 0); + drawWsgSimple(spr, xOffset, yOffset); + } + break; + } + case CHOWA_WALK: + case CHOWA_CHASE: + { + // Update animation frame if enough time has passed + c->frameTimer += elapsedUS; + if (c->frameTimer > SECOND / 4) + { + c->frameTimer = 0; + c->animFrame = (c->animFrame + 1) % 4; + } + bool flip = false; + vec_t temp; + // Check if in the water + if (rectRectIntersection(c->aabb, cg->grove.boundaries[CG_WATER], &temp)) + { + if (c->angle <= 270 && c->angle > 90) + { + flip = true; + } + spr = cg_getChowaWSG(cg, c->chowa, CG_ANIM_SWIM, c->animFrame); + } + else + { + if (c->angle > 45 && c->angle <= 135) + { + spr = cg_getChowaWSG(cg, c->chowa, CG_ANIM_WALK_DOWN, c->animFrame); + } + else if (c->angle > 135 && c->angle <= 225) + { + spr = cg_getChowaWSG(cg, c->chowa, CG_ANIM_WALK_SIDE, c->animFrame); + flip = true; + } + else if (c->angle > 225 && c->angle <= 315) + { + spr = cg_getChowaWSG(cg, c->chowa, CG_ANIM_WALK_UP, c->animFrame); + } + else + { + spr = cg_getChowaWSG(cg, c->chowa, CG_ANIM_WALK_SIDE, c->animFrame); + } + } + drawWsg(spr, xOffset, yOffset, flip, false, 0); + break; + } + case CHOWA_SING: + { + // Update animation frame if enough time has passed + c->frameTimer += elapsedUS; + if (c->frameTimer > SECOND / 4) + { + c->frameTimer = 0; + c->animFrame = (c->animFrame + 1) % 4; + } + spr = cg_getChowaWSG(cg, c->chowa, CG_ANIM_SING, c->animFrame); + drawWsgSimple(spr, xOffset, yOffset); + switch (c->animFrame) + { + case 0: + { + drawWsgSimple(&cg->grove.notes[0], xOffset - 7, yOffset + 9); + break; + } + case 1: + { + drawWsgSimple(&cg->grove.notes[1], xOffset + 9, yOffset + 15); + break; + } + case 2: + { + drawWsgSimple(&cg->grove.notes[2], xOffset, yOffset - 10); + break; + } + case 3: + { + break; + } + } + break; + } + case CHOWA_DANCE: + { + // Update animation frame if enough time has passed + c->frameTimer += elapsedUS; + if (c->frameTimer > SECOND / 4) + { + c->frameTimer = 0; + c->animFrame = (c->animFrame + 1) % 4; + } + spr = cg_getChowaWSG(cg, c->chowa, CG_ANIM_DANCE, c->animFrame); + drawWsgSimple(spr, xOffset, yOffset); + break; + } + case CHOWA_BOX: + { + // Update animation frame if enough time has passed + c->frameTimer += elapsedUS; + if (c->frameTimer > SECOND / 4) + { + c->frameTimer = 0; + c->animFrame = (c->animFrame + 1) % 3; + if (c->animFrame == 2) + { + c->animIdx = esp_random() % 3; + c->animFrame = 0; + } + } + switch (c->animIdx) + { + case 0: + { + spr = cg_getChowaWSG(cg, c->chowa, CG_ANIM_PUNCH, c->animFrame); + break; + } + case 1: + { + spr = cg_getChowaWSG(cg, c->chowa, CG_ANIM_KICK, c->animFrame); + break; + } + case 2: + { + spr = cg_getChowaWSG(cg, c->chowa, CG_ANIM_HEADBUTT, c->animFrame); + break; + } + } + drawWsg(spr, xOffset, yOffset, c->flip, false, 0); + if (c->hasPartner && c->animFrame % 2 == 0) + { + if (c->flip) + { + drawWsg(&cg->grove.angerParticles[1], xOffset, yOffset, true, false, 0); + } + else + { + drawWsgSimple(&cg->grove.angerParticles[1], xOffset, yOffset); + } + } + break; + } + case CHOWA_HELD: + { + // Update animation frame if enough time has passed + c->frameTimer += elapsedUS; + if (c->frameTimer > SECOND / 6) + { + c->frameTimer = 0; + c->animFrame = (c->animFrame + 1) % 2; + } + spr = cg_getChowaWSG(cg, c->chowa, CG_ANIM_FLAIL, c->animFrame); + drawWsgSimple(spr, xOffset, yOffset); + break; + } + case CHOWA_TALK: + { + // Update animation frame if enough time has passed + c->frameTimer += elapsedUS; + if (c->frameTimer > SECOND / 6) + { + c->frameTimer = 0; + c->animFrame = (c->animFrame + 1) % 4; + } + spr = cg_getChowaWSG(cg, c->chowa, CG_ANIM_WALK_SIDE, 0); + drawWsg(spr, xOffset, yOffset, c->flip, false, 0); + // Draw Speech bubbles. Only animate if talking to other Chowa + if (!c->hasPartner) + { + drawWsgSimple(&cg->grove.speechBubbles[0], xOffset, yOffset - 16); + } + else + { + drawWsgSimple(&cg->grove.speechBubbles[c->animFrame], xOffset, yOffset - 16); + } + break; + } + case CHOWA_GIFT: + { + spr = cg_getChowaWSG(cg, c->chowa, CG_ANIM_GIFT, 0); + drawWsgSimple(spr, xOffset, yOffset); + break; + } + case CHOWA_PET: + { + spr = cg_getChowaWSG(cg, c->chowa, CG_ANIM_PET, 0); + drawWsgSimple(spr, xOffset, yOffset); + break; + } + default: + { + spr = cg_getChowaWSG(cg, c->chowa, CG_ANIM_WALK_DOWN, 0); + break; + } + } + } +} + +static void cg_drawUI(cGrove_t* cg) +{ + // Draw image + drawWsgSimple(&cg->grove.itemsWSGs[11], 15, 15); + // Draw amount of rings + char buffer[24]; + snprintf(buffer, sizeof(buffer) - 1, "%" PRId16, cg->grove.inv.money); + drawText(&cg->menuFont, c555, buffer, 45, 23); +} + +static void cg_groveDebug(cGrove_t* cg) +{ + int16_t xOffset = -cg->grove.camera.pos.x; + int16_t yOffset = -cg->grove.camera.pos.y; + // draw AABBs for grove + for (int32_t i = 0; i < 3; i++) + { + drawRect(cg->grove.boundaries[i].pos.x + xOffset, cg->grove.boundaries[i].pos.y + yOffset, + cg->grove.boundaries[i].pos.x + cg->grove.boundaries[i].width + xOffset, + cg->grove.boundaries[i].pos.y + cg->grove.boundaries[i].height + yOffset, c500); + } + for (int32_t i = 0; i < CG_MAX_CHOWA + CG_GROVE_MAX_GUEST_CHOWA; i++) + { + // Draw Chowa info + if (cg->grove.chowa[i].chowa->active) + { + drawRect(cg->grove.chowa[i].aabb.pos.x + xOffset, cg->grove.chowa[i].aabb.pos.y + yOffset, + cg->grove.chowa[i].aabb.pos.x + cg->grove.chowa[i].aabb.width + xOffset, + cg->grove.chowa[i].aabb.pos.y + cg->grove.chowa[i].aabb.height + yOffset, c500); + drawCircle(cg->grove.chowa[i].targetPos.x + xOffset, cg->grove.chowa[i].targetPos.y + yOffset, 12, c500); + drawLineFast(cg->grove.chowa[i].aabb.pos.x + xOffset, cg->grove.chowa[i].aabb.pos.y + yOffset, + cg->grove.cursor.pos.x, cg->grove.cursor.pos.y, c505); + } + } +} \ No newline at end of file diff --git a/main/modes/games/cGrove/Garden/cg_GroveDraw.h b/main/modes/games/cGrove/Garden/cg_GroveDraw.h new file mode 100644 index 000000000..b114298fc --- /dev/null +++ b/main/modes/games/cGrove/Garden/cg_GroveDraw.h @@ -0,0 +1,23 @@ +/** + * @file cg_GroveDraw.h + * @author Jeremy Stintzcum (jeremy.stintzcum@gmail.com) + * @brief Drawing functions for the Grove mode of Chowa Grove + * @version 0.1 + * @date 2024-10-09 + * + * @copyright Copyright (c) 2024 + * + */ +#pragma once + +//============================================================================== +// Includes +//============================================================================== + +#include "cg_Typedef.h" + +//============================================================================== +// Function declarations +//============================================================================== + +void cg_groveDraw(cGrove_t* cg, int64_t elapsedUs); \ No newline at end of file diff --git a/main/modes/games/cGrove/Sparring/cg_Match.c b/main/modes/games/cGrove/Sparring/cg_Match.c new file mode 100644 index 000000000..9147953c0 --- /dev/null +++ b/main/modes/games/cGrove/Sparring/cg_Match.c @@ -0,0 +1,1025 @@ +/** + * @file cg_Match.c + * @author Jeremy Stintzcum (jeremy.stintzcum@gmail.com) + * @brief Provide the individual match implementation for Chowa Grove spars + * @version 0.1 + * @date 2024-09-22 + * + * @copyright Copyright (c) 2024 + * + */ + +//============================================================================== +// Includes +//============================================================================== + +#include "cg_Match.h" +#include "esp_random.h" + +//============================================================================== +// Defines +//============================================================================== + +// Microsecond based +#define SECOND 1000000 // One second of us +#define STAMINA_SCALAR 3900 // How fast the stamina regenerates + +// Min values +#define MIN_STAMINA 40 // Minimum value for stamina +#define MIN_DAMAGE 10 // Minimum Damage an attack can do +#define MIN_HP 250 // Minimum HP for Chowa to have + +// Readiness penalty +#define READINESS_PENALTY 10 // How much changing the move costs + +// AI +#define CHEER 2 // Amount to cheer +#define TICKS_UNTIL_PEEK 8 // How fast the AI cheats + +// Stamina costs +#define MIN_STAMINA_COST 25 // Minumum Stamina loss +#define HEADBUTT_STAMINA_COST 20 // Cost to headbutt +#define PUNCH_STAMINA_COST 20 // Cost to Punch +#define FAST_PUNCH_STAMINA_COST 15 // Cost to Fast Punch +#define KICK_STAMINA_COST 15 // Cost to Kick +#define JUMPING_KICK_STAMINA_COST 25 // Cost to Jumping Kick +#define DODGE_STAMINA_COST 30 // Cost to Dodge + +//============================================================================== +// Static Functions +//============================================================================== + +static void cg_sparMatchPlayerInput(cGrove_t* cg); +static void cg_sparMatchAI(cGrove_t* cg, int64_t elapsedUs); +static void cg_sparMatchGetAdvantagedMove(cGrove_t* cg, cgRPSState_t move); +static void cg_sparMatchAITimer(cGrove_t* cg, int32_t tick); +static void cg_sparMatchAIEvalPrevMoves(cGrove_t* cg, int8_t numOfMove); +static void cg_sparMatchChowaState(cGrove_t* cg, int64_t elapsedUs); +static void cg_sparMatchResolve(cGrove_t* cg); +int8_t static cg_sparMatchStaminaCost(cgRPSState_t rps); +void static cg_sparMatchResolveState(cGrove_t* cg); +void static cg_sparMatchRPS(cGrove_t* cg); + +//============================================================================== +// Functions +//============================================================================== + +/** + * @brief Initializes the match based on given parameters + * + * @param cg Game data + * @param matchName Name of the match to display + * @param player1Chowa Pointer to the swadge player's chowa + * @param player2Chowa Point to the opposing chowa + * @param round What round of the spar this is + * @param maxTime When the timeout occurs + */ +void cg_initSparMatch(cGrove_t* cg, char* matchName, cgChowa_t* player1Chowa, cgChowa_t* player2Chowa, int8_t round, + int16_t maxTime, cgAIDifficulty_t ai) +{ + // Initialize + // Load tournament/match data + strcpy(cg->spar.match.matchName, matchName); + cg->spar.match.round = round; + cg->spar.match.maxTime = maxTime; + cg->spar.match.animDone = false; + cg->spar.match.done = false; + + // Timer is set to 0 + cg->spar.match.timer = 0; + + // AI + cg->spar.match.ai.aiDifficulty = ai; + + // Chowa + cg->spar.match.chowaData[CG_P1].chowa = player1Chowa; + cg->spar.match.chowaData[CG_P2].chowa = player2Chowa; + for (int32_t i = 0; i < 2; i++) + { + // FIXME: Setting stats for test purposes + cg->spar.match.chowaData[i].chowa->stats[CG_SPEED] = 128; + cg->spar.match.chowaData[i].chowa->stats[CG_STAMINA] = 128; + cg->spar.match.chowaData[i].chowa->stats[CG_STRENGTH] = 0; + cg->spar.match.chowaData[i].chowa->stats[CG_AGILITY] = 128; + cg->spar.match.chowaData[i].chowa->stats[CG_HEALTH] = 128; + cg->spar.match.chowaData[i].chowa->playerAffinity = 255; + // Charisma not needed for spar + + cg->spar.match.chowaData[i].currState = CG_SPAR_UNREADY; + cg->spar.match.chowaData[i].currMove = CG_SPAR_UNSET; + cg->spar.match.chowaData[i].maxStamina + = MIN_STAMINA + (cg->spar.match.chowaData[i].chowa->stats[CG_STAMINA] >> 1); + cg->spar.match.chowaData[i].stamina = cg->spar.match.chowaData[i].maxStamina; + cg->spar.match.chowaData[i].readiness = 0; + cg->spar.match.chowaData[i].readiness = 0; + cg->spar.match.chowaData[i].maxHP + = MIN_HP + cg->spar.match.chowaData[i].chowa->stats[CG_HEALTH] * 3; // 250 - 1000 + cg->spar.match.chowaData[i].HP = cg->spar.match.chowaData[i].maxHP; + cg->spar.match.chowaData[i].updateTimer = 0; + } +} + +/** + * @brief Executes the logic for the spar + * + * @param cg Game data + * @param elapsedUs time since last frame + */ +void cg_runSparMatch(cGrove_t* cg, int64_t elapsedUs) +{ + // Check if match has ended + if (cg->spar.match.done == true) + { + // TODO: wait until animations finish, then save data and load next match or show a summary + return; + } + + // Round timer + // Timer doesn't accumulate if paused and single player + if (!cg->spar.match.paused && !cg->spar.match.online) + { + cg->spar.match.usTimer += elapsedUs; + } + // If enough us have built up, increments seconds timer + if (cg->spar.match.usTimer >= SECOND) + { + cg->spar.match.usTimer -= SECOND; + cg->spar.match.timer += 1; + } + // If enough seconds have passed, end match + if (cg->spar.match.timer >= cg->spar.match.maxTime) + { + cg->spar.match.done = true; + cg->spar.match.finalResult = CG_DRAW; + return; + } + + // Loop over both Chowa + cg_sparMatchPlayerInput(cg); + if (cg->spar.match.online) + { + // TODO: Get input from other swadge + } + else + { + cg_sparMatchAI(cg, elapsedUs); + } + cg_sparMatchChowaState(cg, elapsedUs); + + // Resolve is both are ready or if one has been ready for a full second + if (cg->spar.match.resolve + || (cg->spar.match.chowaData[0].currState == CG_SPAR_READY + && cg->spar.match.chowaData[1].currState == CG_SPAR_READY)) + { + cg_sparMatchResolve(cg); + } + + // Animate Chowa + // Once animations are done, set state to unready or exhausted as appropriate + if (cg->spar.match.animDone) + { + cg->spar.match.animDone = false; + cg_sparMatchResolveState(cg); + } + + // End game if either health is Zero + if (cg->spar.match.chowaData[CG_P1].HP <= 0 || cg->spar.match.chowaData[CG_P2].HP <= 0) + { + if (cg->spar.match.chowaData[CG_P1].HP <= 0 && cg->spar.match.chowaData[CG_P2].HP <= 0) + { + cg->spar.match.finalResult = CG_DRAW; + } + else if (cg->spar.match.chowaData[CG_P1].HP <= 0) + { + cg->spar.match.finalResult = CG_P2_WIN; + } + else if (cg->spar.match.chowaData[CG_P2].HP <= 0) + { + cg->spar.match.finalResult = CG_P1_WIN; + } + + // End the game + cg->spar.match.done = true; + } +} + +/** + * @brief Gets the player's input during the match + * + * @param cg Game data + */ +static void cg_sparMatchPlayerInput(cGrove_t* cg) +{ + buttonEvt_t evt = {0}; + while (checkButtonQueueWrapper(&evt)) + { + // Pause or unpause regardless of state + if (evt.down && evt.button & PB_START) + { + cg->spar.match.paused = !cg->spar.match.paused; + } + + // Only accept correct inputs per state + switch (cg->spar.match.chowaData[0].currState) + { + case CG_SPAR_UNREADY: + { + if (!cg->spar.match.paused && evt.down && !(evt.button & PB_SELECT || evt.button & PB_START)) + { + // Increase readiness + cg->spar.match.chowaData[0].readiness + += 1 + (cg->spar.match.chowaData[CG_P1].chowa->playerAffinity >> 6); + + // Change action based on + switch (evt.button) + { + case PB_A: + { + if (cg->spar.match.chowaData[CG_P1].currMove != CG_SPAR_HEADBUTT + && cg->spar.match.chowaData[CG_P1].currMove != CG_SPAR_UNSET) + { + cg->spar.match.chowaData[CG_P1].readiness -= READINESS_PENALTY; + } + cg->spar.match.chowaData[CG_P1].currMove = CG_SPAR_HEADBUTT; + break; + } + case PB_B: + { + if (cg->spar.match.chowaData[CG_P1].currMove != CG_SPAR_DODGE + && cg->spar.match.chowaData[CG_P1].currMove != CG_SPAR_UNSET) + { + cg->spar.match.chowaData[CG_P1].readiness -= READINESS_PENALTY; + } + cg->spar.match.chowaData[CG_P1].currMove = CG_SPAR_DODGE; + break; + } + case PB_UP: + { + if (cg->spar.match.chowaData[CG_P1].currMove != CG_SPAR_PUNCH + && cg->spar.match.chowaData[CG_P1].currMove != CG_SPAR_UNSET) + { + cg->spar.match.chowaData[CG_P1].readiness -= READINESS_PENALTY; + } + cg->spar.match.chowaData[CG_P1].currMove = CG_SPAR_PUNCH; + break; + } + case PB_DOWN: + { + if (cg->spar.match.chowaData[CG_P1].currMove != CG_SPAR_FAST_PUNCH + && cg->spar.match.chowaData[CG_P1].currMove != CG_SPAR_UNSET) + { + cg->spar.match.chowaData[CG_P1].readiness -= READINESS_PENALTY; + } + cg->spar.match.chowaData[CG_P1].currMove = CG_SPAR_FAST_PUNCH; + break; + } + case PB_LEFT: + { + if (cg->spar.match.chowaData[CG_P1].currMove != CG_SPAR_KICK + && cg->spar.match.chowaData[CG_P1].currMove != CG_SPAR_UNSET) + { + cg->spar.match.chowaData[CG_P1].readiness -= READINESS_PENALTY; + } + cg->spar.match.chowaData[CG_P1].currMove = CG_SPAR_KICK; + break; + } + case PB_RIGHT: + { + if (cg->spar.match.chowaData[CG_P1].currMove != CG_SPAR_JUMP_KICK + && cg->spar.match.chowaData[CG_P1].currMove != CG_SPAR_UNSET) + { + cg->spar.match.chowaData[CG_P1].readiness -= READINESS_PENALTY; + } + cg->spar.match.chowaData[CG_P1].currMove = CG_SPAR_JUMP_KICK; + break; + } + default: + { + break; + } + } + } + break; + } + case CG_SPAR_EXHAUSTED: + { + // Steadily regain stamina and readiness to stand up + if (!cg->spar.match.paused && evt.down && !(evt.button & PB_SELECT || evt.button & PB_START)) + { + // Change action based on + switch (evt.button) + { + case PB_A: + { + cg->spar.match.chowaData[CG_P1].readiness + += 1 + (cg->spar.match.chowaData[CG_P1].chowa->playerAffinity >> 6); + break; + } + case PB_B: + { + cg->spar.match.chowaData[CG_P1].stamina + += 1 + (cg->spar.match.chowaData[CG_P1].chowa->playerAffinity >> 6); + break; + } + default: + { + break; + } + } + } + break; + } + default: + { + // If won, lost, or resolving + break; + } + } + } +} + +/** + * @brief Handles the AI's behavior + * + * @param cg Game data + * @param elapsedUs Time since last frame + */ +static void cg_sparMatchAI(cGrove_t* cg, int64_t elapsedUs) +{ + switch (cg->spar.match.ai.aiDifficulty) + { + case CG_BEGINNER: + { + // Sometimes pick a move. Otherwise, just sit there like a lump + if (!cg->spar.match.ai.pickedMove) + { + cg->spar.match.ai.pickedMove = true; + cg->spar.match.chowaData[CG_P2].currMove = (esp_random() % 2 == 0) ? esp_random() % 5 : CG_SPAR_UNSET; + } + break; + } + case CG_VERY_EASY: + { + // Rotates between all five moves in order, starting on a random one + if (!cg->spar.match.ai.pickedMove) + { + cg->spar.match.ai.pickedMove = true; + if (!cg->spar.match.ai.init) + { + cg->spar.match.ai.init = true; + cg->spar.match.ai.prevMoves[0] = esp_random() % 5; + cg->spar.match.chowaData[CG_P2].currMove = cg->spar.match.ai.prevMoves[0]; + } + cg->spar.match.chowaData[CG_P2].currMove = (cg->spar.match.ai.prevMoves[0]++) % 5; + } + break; + } + case CG_EASY: + { + // Randomly select three of the five moves, pick them randomly as well each time + if (!cg->spar.match.ai.pickedMove || cg->spar.match.chowaData[CG_P2].currMove == CG_SPAR_UNSET) + { + cg->spar.match.ai.pickedMove = true; + if (!cg->spar.match.ai.init || cg->spar.match.ai.movesPicked < 3) + { + cg->spar.match.ai.init = true; + cg->spar.match.chowaData[CG_P2].currMove = esp_random() % 5; + cg->spar.match.ai.prevMoves[cg->spar.match.ai.movesPicked] + = cg->spar.match.chowaData[CG_P2].currMove; + cg->spar.match.ai.movesPicked++; + } + else + { + cg->spar.match.chowaData[CG_P2].currMove = cg->spar.match.ai.prevMoves[esp_random() % 3]; + } + } + break; + } + case CG_MEDIUM: + { + // Pick move randomly unless player picked two in a row, Starts motivating lazily (1x a second) + // Rarely dodge + if (!cg->spar.match.ai.pickedMove || cg->spar.match.chowaData[CG_P2].currMove == CG_SPAR_UNSET) + { + cg->spar.match.ai.pickedMove = true; + if (!cg->spar.match.ai.init) + { + cg->spar.match.ai.init = true; + cg->spar.match.ai.prevMoves[0] = esp_random() % 5; + } + if (cg->spar.match.ai.prevMoves[1] == cg->spar.match.ai.prevMoves[0]) + { + cg_sparMatchGetAdvantagedMove(cg, cg->spar.match.ai.prevMoves[0]); + } + else + { + // Random + cg->spar.match.chowaData[CG_P2].currMove = esp_random() % 5; + } + // Dodge + if (esp_random() % 10 == 0) + { + cg->spar.match.chowaData[CG_P2].currMove = CG_SPAR_DODGE; + } + } + // Timer + cg->spar.match.ai.timer += elapsedUs; + cg_sparMatchAITimer(cg, SECOND); + break; + } + case CG_HARD: + { + // Pick a move that counters the most of the previous 10 player moves. Motivate 2x a sec + // Dodge if low on health + if (!cg->spar.match.ai.pickedMove || cg->spar.match.chowaData[CG_P2].currMove == CG_SPAR_UNSET) + { + cg->spar.match.ai.pickedMove = true; + if (!cg->spar.match.ai.init) + { + cg->spar.match.ai.init = true; + for (int32_t idx = 0; idx < 10; idx++) + { + cg->spar.match.ai.prevMoves[idx] = esp_random() % 5; + } + } + // Interpret data + cg_sparMatchAIEvalPrevMoves(cg, 10); + + // Dodge + if (esp_random() % 8 == 0) + { + cg->spar.match.chowaData[CG_P2].currMove = CG_SPAR_DODGE; + } + } + + // Timer + cg->spar.match.ai.timer += elapsedUs; + cg_sparMatchAITimer(cg, SECOND >> 1); + break; + } + case CG_VERY_HARD: + { + // Same as hard, but dodge chance is higher and ai motivates 3x a sec + if (!cg->spar.match.ai.pickedMove || cg->spar.match.chowaData[CG_P2].currMove == CG_SPAR_UNSET) + { + cg->spar.match.ai.pickedMove = true; + if (!cg->spar.match.ai.init) + { + cg->spar.match.ai.init = true; + for (int32_t idx = 0; idx < 20; idx++) + { + cg->spar.match.ai.prevMoves[idx] = esp_random() % 5; + } + } + // Interpret data + cg_sparMatchAIEvalPrevMoves(cg, 20); + + // Dodge + if (esp_random() % 6 == 0) + { + cg->spar.match.chowaData[CG_P2].currMove = CG_SPAR_DODGE; + } + } + // Timer + cg->spar.match.ai.timer += elapsedUs; + cg_sparMatchAITimer(cg, SECOND / 3); + break; + } + case CG_EXPERT: + { + // Cheats and reads players input. Checks every 4s what the player is doing and counter-picks. + // Timer + cg->spar.match.ai.timer += elapsedUs; + if (cg->spar.match.ai.timer >= SECOND >> 2) + { + cg->spar.match.ai.movesPicked++; // Used as a counter + } + cg_sparMatchAITimer(cg, SECOND >> 2); + + if (cg->spar.match.chowaData[CG_P2].currMove == CG_SPAR_UNSET) + { + cg->spar.match.chowaData[CG_P2].currMove = esp_random() % 6; + } + + if (cg->spar.match.ai.movesPicked >= TICKS_UNTIL_PEEK) + { + cg->spar.match.ai.movesPicked = 0; + cg_sparMatchGetAdvantagedMove(cg, cg->spar.match.chowaData[CG_P1].currMove); + } + + break; + } + default: + { + break; + } + } +} + +/** + * @brief Gets an advantaged move given an input move + * + * @param cg Game Data + * @param move Move to attempt to beat + */ +static void cg_sparMatchGetAdvantagedMove(cGrove_t* cg, cgRPSState_t move) +{ + // Pick a counter + switch (move) + { + case CG_SPAR_PUNCH: + { + cg->spar.match.chowaData[CG_P2].currMove = (esp_random() % 2 == 0) ? CG_SPAR_FAST_PUNCH : CG_SPAR_HEADBUTT; + break; + } + case CG_SPAR_FAST_PUNCH: + { + cg->spar.match.chowaData[CG_P2].currMove = (esp_random() % 2 == 0) ? CG_SPAR_KICK : CG_SPAR_JUMP_KICK; + break; + } + case CG_SPAR_KICK: + { + cg->spar.match.chowaData[CG_P2].currMove = (esp_random() % 2 == 0) ? CG_SPAR_PUNCH : CG_SPAR_HEADBUTT; + break; + } + case CG_SPAR_HEADBUTT: + { + cg->spar.match.chowaData[CG_P2].currMove = (esp_random() % 2 == 0) ? CG_SPAR_FAST_PUNCH : CG_SPAR_JUMP_KICK; + break; + } + case CG_SPAR_JUMP_KICK: + { + cg->spar.match.chowaData[CG_P2].currMove = (esp_random() % 2 == 0) ? CG_SPAR_PUNCH : CG_SPAR_KICK; + break; + } + default: + { + cg->spar.match.chowaData[CG_P2].currMove = esp_random() % 5; + break; + } + } +} + +/** + * @brief Handles updating the time and responding appropriately + * + * @param cg Game Data + * @param tick The amount of time between checks + */ +static void cg_sparMatchAITimer(cGrove_t* cg, int32_t tick) +{ + if (cg->spar.match.ai.timer >= tick) + { + cg->spar.match.ai.timer -= tick; + if (cg->spar.match.chowaData[CG_P2].currState == CG_SPAR_UNREADY) + { + cg->spar.match.chowaData[CG_P2].readiness += CHEER; + } + else if (cg->spar.match.chowaData[CG_P2].currState == CG_SPAR_EXHAUSTED) + { + cg->spar.match.chowaData[CG_P2].stamina += CHEER; + } + } +} + +/** + * @brief Evaluate past moves + * + * @param cg Game Data + * @param numOfMove Past moves to evaluate + */ +static void cg_sparMatchAIEvalPrevMoves(cGrove_t* cg, int8_t numOfMove) +{ + // Count how many of each are in the provided range + cgRPSState_t chosenMove = CG_SPAR_UNSET; + int8_t moveCount[7] = {0}; + + for (int32_t idx = 0; idx < numOfMove; idx++) + { + // Get counts + moveCount[cg->spar.match.ai.prevMoves[idx]]++; + } + for (int32_t idx = 0; idx < 5; idx++) + { + // Find largest + if (chosenMove < moveCount[idx]) + { + chosenMove = idx; + } + else if (chosenMove == moveCount[idx]) + { + chosenMove = (esp_random() % 2 == 0) ? chosenMove : idx; + } + } + + // Pick best option + cg_sparMatchGetAdvantagedMove(cg, chosenMove); +} + +/** + * @brief Updates the states of the Chowa + * + * @param cg Game data + * @param elapsedUs Time since last frame + */ +static void cg_sparMatchChowaState(cGrove_t* cg, int64_t elapsedUs) +{ + for (int32_t idx = 0; idx < 2; idx++) + { + switch (cg->spar.match.chowaData[idx].currState) + { + case CG_SPAR_UNREADY: + { + // Steadily becomes more ready + if (!cg->spar.match.paused && !cg->spar.match.online) + { + cg->spar.match.chowaData[idx].updateTimer += elapsedUs; + } + int32_t readinessTick + = SECOND - (STAMINA_SCALAR * cg->spar.match.chowaData[idx].chowa->stats[CG_SPEED]); + if (cg->spar.match.chowaData[idx].updateTimer >= readinessTick) + { + cg->spar.match.chowaData[idx].updateTimer = 0; + cg->spar.match.chowaData[idx].readiness += 1; + } + // Check if ready + if (cg->spar.match.chowaData[idx].readiness > CG_MAX_READY_VALUE) + { + cg->spar.match.chowaData[idx].readiness = CG_MAX_READY_VALUE; + cg->spar.match.chowaData[idx].updateTimer = 0; + cg->spar.match.chowaData[idx].currState = CG_SPAR_READY; + } + else if (cg->spar.match.chowaData[idx].readiness < 0) + { + cg->spar.match.chowaData[idx].readiness = 0; + } + break; + } + case CG_SPAR_READY: + { + // Runs timer until automatically resolve + if (!cg->spar.match.paused && !cg->spar.match.online) + { + cg->spar.match.chowaData[idx].updateTimer += elapsedUs; + } + int32_t readinessTick = SECOND; + if (cg->spar.match.chowaData[idx].updateTimer >= readinessTick) + { + // Resolve + cg->spar.match.resolve = true; + cg->spar.match.chowaData[idx].updateTimer = 0; + } + break; + } + case CG_SPAR_EXHAUSTED: + { + // Steadily regain stamina and readiness to stand up + // Steadily becomes more ready + if (!cg->spar.match.paused && !cg->spar.match.online) + { + cg->spar.match.chowaData[idx].updateTimer += elapsedUs; + } + int32_t readinessTick + = SECOND - (STAMINA_SCALAR * cg->spar.match.chowaData[idx].chowa->stats[CG_SPEED]); + if (cg->spar.match.chowaData[idx].updateTimer >= readinessTick) + { + cg->spar.match.chowaData[idx].updateTimer = 0; + cg->spar.match.chowaData[idx].readiness += 1; + cg->spar.match.chowaData[idx].stamina += 1; + } + // Check if ready + if (cg->spar.match.chowaData[idx].readiness > CG_MAX_READY_VALUE + || cg->spar.match.chowaData[idx].stamina >= cg->spar.match.chowaData[idx].maxStamina) + { + if (cg->spar.match.chowaData[idx].stamina > cg->spar.match.chowaData[idx].maxStamina) + { + cg->spar.match.chowaData[idx].stamina = cg->spar.match.chowaData[idx].maxStamina; + } + cg->spar.match.chowaData[idx].readiness = 0; + cg->spar.match.chowaData[idx].updateTimer = 0; + cg->spar.match.chowaData[idx].currState = CG_SPAR_UNREADY; + cg->spar.match.chowaData[idx].currMove = CG_SPAR_UNSET; + } + else if (cg->spar.match.chowaData[idx].readiness < 0) + { + cg->spar.match.chowaData[idx].readiness = 0; + } + break; + } + default: + { + // If won, lost, or resolving + break; + } + } + } +} + +/** + * @brief Evaluates the result of the preparation stage + * + * @param cg Game Data + */ +static void cg_sparMatchResolve(cGrove_t* cg) +{ + // Unset resolved + cg->spar.match.resolve = false; + + // FIXME: Only set animation to done when animations finish + cg->spar.match.animDone = true; + + // Check if both Chowa are ready + for (int32_t idx = 0; idx < 2; idx++) + { + if (cg->spar.match.chowaData[idx].currState != CG_SPAR_READY + && !(cg->spar.match.chowaData[(idx + 1) % 2].currMove == CG_SPAR_DODGE)) + { + // This Chowa is not ready + // Reduce health based on other Chowa's Str + cg->spar.match.chowaData[idx].HP + -= MIN_DAMAGE + (cg->spar.match.chowaData[(idx + 1) % 2].chowa->stats[CG_STRENGTH] << 1); + + // Reduce Stamina + cg->spar.match.chowaData[idx].stamina -= MIN_STAMINA_COST; + cg->spar.match.chowaData[(idx + 1) % 2].stamina + -= MIN_STAMINA_COST + cg_sparMatchStaminaCost(cg->spar.match.chowaData[(idx + 1) % 2].currMove); + + // Set state for animations + cg->spar.match.chowaData[idx].currState = CG_SPAR_HIT; + cg->spar.match.chowaData[(idx + 1) % 2].currState = CG_SPAR_ATTACK; + cg->spar.match.wasCrit = true; + return; + } + } + + // Check if either one is dodging + for (int32_t idx = 0; idx < 2; idx++) + { + if (cg->spar.match.chowaData[idx].currMove == CG_SPAR_DODGE) + { + // Adjust stamina + cg->spar.match.chowaData[idx].stamina -= (MIN_STAMINA_COST + DODGE_STAMINA_COST); + if (!(cg->spar.match.chowaData[(idx + 1) % 2].currMove == CG_SPAR_UNSET)) + { + cg->spar.match.chowaData[(idx + 1) % 2].stamina + -= MIN_STAMINA_COST + cg_sparMatchStaminaCost(cg->spar.match.chowaData[(idx + 1) % 2].currMove); + } + // Set state for animations + cg->spar.match.chowaData[idx].currState = CG_SPAR_DODGE_ST; + if (cg->spar.match.chowaData[(idx + 1) % 2].currMove == CG_SPAR_DODGE) + { + cg->spar.match.chowaData[(idx + 1) % 2].currState = CG_SPAR_DODGE_ST; + } + else + { + cg->spar.match.chowaData[(idx + 1) % 2].currState = CG_SPAR_ATTACK; + } + return; // Returns out of loop to avoid double counting + } + } + + // Run RPS + cg_sparMatchRPS(cg); +} + +/** + * @brief Converts the selected move into a stamina cost + * + * @param rps Move selected + * @return int8_t cost of the move to execute + */ +int8_t static cg_sparMatchStaminaCost(cgRPSState_t rps) +{ + switch (rps) + { + case CG_SPAR_PUNCH: + { + return PUNCH_STAMINA_COST; + } + case CG_SPAR_FAST_PUNCH: + { + return FAST_PUNCH_STAMINA_COST; + } + case CG_SPAR_KICK: + { + return KICK_STAMINA_COST; + } + case CG_SPAR_JUMP_KICK: + { + return JUMPING_KICK_STAMINA_COST; + } + case CG_SPAR_DODGE: + { + return DODGE_STAMINA_COST; + } + case CG_SPAR_HEADBUTT: + { + return HEADBUTT_STAMINA_COST; + } + default: + { + return 0; + } + } +} + +/** + * @brief Changes the CHowa to the appropriate state after resolving + * + * @param cg Game Data + */ +void static cg_sparMatchResolveState(cGrove_t* cg) +{ + // Update AI + if (!cg->spar.match.online) + { + cg->spar.match.ai.pickedMove = false; + if (cg->spar.match.ai.aiDifficulty == CG_MEDIUM) + { + cg->spar.match.ai.prevMoves[1] = cg->spar.match.ai.prevMoves[0]; + cg->spar.match.ai.prevMoves[0] = cg->spar.match.chowaData[CG_P1].currMove; + } + else if (cg->spar.match.ai.aiDifficulty == CG_HARD) + { + for (int32_t idx = 9; idx > 1; idx--) + { + cg->spar.match.ai.prevMoves[idx] = cg->spar.match.ai.prevMoves[idx - 1]; + } + cg->spar.match.ai.prevMoves[0] = cg->spar.match.chowaData[CG_P1].currMove; + } + else if (cg->spar.match.ai.aiDifficulty == CG_VERY_HARD) + { + for (int32_t idx = 19; idx > 1; idx--) + { + cg->spar.match.ai.prevMoves[idx] = cg->spar.match.ai.prevMoves[idx - 1]; + } + cg->spar.match.ai.prevMoves[0] = cg->spar.match.chowaData[CG_P1].currMove; + } + else if (cg->spar.match.ai.aiDifficulty == CG_EXPERT) + { + cg->spar.match.ai.movesPicked = 0; + } + } + + // Set the state + for (int32_t idx = 0; idx < 2; idx++) + { + // Change Chowa State + if (cg->spar.match.chowaData[idx].stamina <= 0) + { + cg->spar.match.chowaData[idx].stamina = 0; + cg->spar.match.chowaData[idx].currState = CG_SPAR_EXHAUSTED; + cg->spar.match.chowaData[idx].readiness = 0; + } + else + { + cg->spar.match.chowaData[idx].currState = CG_SPAR_UNREADY; + cg->spar.match.chowaData[idx].currMove = CG_SPAR_UNSET; + cg->spar.match.chowaData[idx].readiness = 0; + } + } +} + +/** + * @brief Evaluate RPS + * + * @param cg Game Data + */ +void static cg_sparMatchRPS(cGrove_t* cg) +{ + // Check RPS + cgRPSState_t player1Move = cg->spar.match.chowaData[CG_P1].currMove; + cgRPSState_t player2Move = cg->spar.match.chowaData[CG_P2].currMove; + + cgWinLoss_t winner = CG_DRAW; + + // DRAW + if (player1Move == player2Move) + { + winner = CG_DRAW; + if (player1Move == CG_SPAR_UNSET) + { + cg->spar.match.chowaData[0].currState = CG_SPAR_NOTHING; + cg->spar.match.chowaData[1].currState = CG_SPAR_NOTHING; + } + else + { + for (int idx = 0; idx < 2; idx++) + { + // Do a little damage + cg->spar.match.chowaData[idx].HP -= 10; + + // Drain stamina + cg->spar.match.chowaData[idx].stamina -= 10; + + // Both do attack animation + cg->spar.match.chowaData[idx].currState = CG_SPAR_ATTACK; + } + } + return; + } + + // Player one doesn't try to play + else if (player1Move == CG_SPAR_UNSET) + { + winner = CG_P2_WIN; + } + + // PUNCH + else if (player1Move == CG_SPAR_PUNCH + && (player2Move == CG_SPAR_PUNCH || player2Move == CG_SPAR_KICK || player2Move == CG_SPAR_JUMP_KICK)) + { + winner = CG_P1_WIN; + } + else if (player1Move == CG_SPAR_PUNCH && (player2Move == CG_SPAR_FAST_PUNCH || player2Move == CG_SPAR_HEADBUTT)) + { + winner = CG_P2_WIN; + } + + // FAST PUNCH + else if (player1Move == CG_SPAR_FAST_PUNCH + && (player2Move == CG_SPAR_PUNCH || player2Move == CG_SPAR_HEADBUTT || player2Move == CG_SPAR_UNSET)) + { + winner = CG_P1_WIN; + } + else if (player1Move == CG_SPAR_FAST_PUNCH && (player2Move == CG_SPAR_KICK || player2Move == CG_SPAR_JUMP_KICK)) + { + winner = CG_P2_WIN; + } + + // KICK + else if (player1Move == CG_SPAR_KICK + && (player2Move == CG_SPAR_FAST_PUNCH || player2Move == CG_SPAR_JUMP_KICK || player2Move == CG_SPAR_UNSET)) + { + winner = CG_P1_WIN; + } + else if (player1Move == CG_SPAR_KICK && (player2Move == CG_SPAR_PUNCH || player2Move == CG_SPAR_HEADBUTT)) + { + winner = CG_P2_WIN; + } + + // HEADBUTT + else if (player1Move == CG_SPAR_HEADBUTT + && (player2Move == CG_SPAR_PUNCH || player2Move == CG_SPAR_KICK || player2Move == CG_SPAR_UNSET)) + { + winner = CG_P1_WIN; + } + else if (player1Move == CG_SPAR_HEADBUTT && (player2Move == CG_SPAR_FAST_PUNCH || player2Move == CG_SPAR_JUMP_KICK)) + { + winner = CG_P2_WIN; + } + + // JUMP KICK + else if (player1Move == CG_SPAR_JUMP_KICK + && (player2Move == CG_SPAR_FAST_PUNCH || player2Move == CG_SPAR_HEADBUTT || player2Move == CG_SPAR_UNSET)) + { + winner = CG_P1_WIN; + } + else if (player1Move == CG_SPAR_JUMP_KICK && (player2Move == CG_SPAR_PUNCH || player2Move == CG_SPAR_KICK)) + { + winner = CG_P2_WIN; + } + + // Set index for winner and loser for data crunching + int8_t winIdx, loseIdx; + if (winner == CG_P1_WIN) + { + winIdx = CG_P1; + loseIdx = CG_P2; + } + else if (winner == CG_P2_WIN) + { + winIdx = CG_P2; + loseIdx = CG_P1; + } + else + { + // Something went wrong + printf("Draw propagated to far"); + return; + } + + // Damage + if ((esp_random() % 10) == 0) + { + // Crit + cg->spar.match.chowaData[loseIdx].HP + -= MIN_DAMAGE + (cg->spar.match.chowaData[winIdx].chowa->stats[CG_STRENGTH] << 1); + cg->spar.match.wasCrit = true; + } + else + { + cg->spar.match.chowaData[loseIdx].HP + -= MIN_DAMAGE + (cg->spar.match.chowaData[winIdx].chowa->stats[CG_STRENGTH]); + } + + // Stamina + cg->spar.match.chowaData[loseIdx].stamina + -= MIN_STAMINA_COST + cg_sparMatchStaminaCost(cg->spar.match.chowaData[loseIdx].currMove); + cg->spar.match.chowaData[winIdx].stamina + -= MIN_STAMINA_COST + cg_sparMatchStaminaCost(cg->spar.match.chowaData[winIdx].currMove); + + // Set state of both Chowa + cg->spar.match.chowaData[loseIdx].currState = CG_SPAR_HIT; + cg->spar.match.chowaData[winIdx].currState = CG_SPAR_ATTACK; +} \ No newline at end of file diff --git a/main/modes/games/cGrove/Sparring/cg_Match.h b/main/modes/games/cGrove/Sparring/cg_Match.h new file mode 100644 index 000000000..66d1f37ff --- /dev/null +++ b/main/modes/games/cGrove/Sparring/cg_Match.h @@ -0,0 +1,25 @@ +/** + * @file cg_Match.h + * @author Jeremy Stintzcum (jeremy.stintzcum@gmail.com) + * @brief Provide the individual match implementation for Chowa Grove spars + * @version 0.1 + * @date 2024-09-22 + * + * @copyright Copyright (c) 2024 + * + */ +#pragma once + +//============================================================================== +// Includes +//============================================================================== + +#include "cg_Typedef.h" + +//============================================================================== +// Functions +//============================================================================== + +void cg_initSparMatch(cGrove_t* cg, char* matchName, cgChowa_t* player1Chowa, cgChowa_t* player2Chowa, int8_t round, + int16_t maxTime, cgAIDifficulty_t ai); +void cg_runSparMatch(cGrove_t* cg, int64_t elapsedUs); \ No newline at end of file diff --git a/main/modes/games/cGrove/Sparring/cg_Spar.c b/main/modes/games/cGrove/Sparring/cg_Spar.c new file mode 100644 index 000000000..d87cabce6 --- /dev/null +++ b/main/modes/games/cGrove/Sparring/cg_Spar.c @@ -0,0 +1,376 @@ +/** + * @file cg_spar.c + * @author Jeremy Stintzcum (jeremy.stintzcum@gmail.com) + * @brief Provides the sparring implementation for Chowa Grove + * @version 0.1 + * @date 2024-09-19 + * + * @copyright Copyright (c) 2024 + * + */ + +//============================================================================== +// Includes +//============================================================================== + +#include "cg_Spar.h" +#include "cg_Match.h" +#include "cg_SparDraw.h" + +//============================================================================== +// Function Declarations +//============================================================================== + +static void sparMenuCb(const char* label, bool selected, uint32_t settingVal); +static void sparLoadBattleRecords(void); + +//============================================================================== +// Const variables +//============================================================================== + +static const char sparMenuName[] = "Chowa Sparring!"; + +static const char* sparMenuNames[] = {"Schedule Match", "View records", "Tutorial", "Settings", "Main Menu"}; + +static const char* sparDojoSprites[] = { + "Dojo_Gong.wsg", + "Dojo_PunchingBag.wsg", +}; + +//============================================================================== +// Variables +//============================================================================== + +cGrove_t* cg; + +//============================================================================== +// Functions +//============================================================================== + +/** + * @brief Initialize the spar + * + * @param cg Game data + */ +void cg_initSpar(cGrove_t* grove) +{ + cg = grove; + // WSGs + loadWsg("DojoBG.wsg", &cg->spar.dojoBG, true); + cg->spar.dojoBGItems = calloc(ARRAY_SIZE(sparDojoSprites), sizeof(wsg_t)); + for (int32_t idx = 0; idx < ARRAY_SIZE(sparDojoSprites); idx++) + { + loadWsg(sparDojoSprites[idx], &cg->spar.dojoBGItems[idx], true); + } + loadWsg("cg_Arrow.wsg", &cg->spar.arrow, true); + + // Fonts + loadFont("righteous_150.font", &cg->spar.sparTitleFont, true); + loadFont("eightbit_atari_grube2.font", &cg->spar.sparRegFont, true); + makeOutlineFont(&cg->spar.sparTitleFont, &cg->spar.sparTitleFontOutline, true); + + // Init menu + cg->spar.sparMenu = initMenu(sparMenuName, sparMenuCb); + addSingleItemToMenu(cg->spar.sparMenu, sparMenuNames[0]); // Start a match + addSingleItemToMenu(cg->spar.sparMenu, sparMenuNames[1]); // View combat records + addSingleItemToMenu(cg->spar.sparMenu, sparMenuNames[2]); // View tutorial + addSingleItemToMenu(cg->spar.sparMenu, sparMenuNames[3]); // Settings + addSingleItemToMenu(cg->spar.sparMenu, sparMenuNames[4]); // Go back to main menu + + cg->spar.renderer = initMenuManiaRenderer(NULL, NULL, &cg->spar.sparRegFont); + static const paletteColor_t shadowColors[] = {c110, c210, c220, c320, c330, c430, c330, c320, c220, c210}; + led_t ledColor = {.r = 128, .g = 128, .b = 0}; + recolorMenuManiaRenderer(cg->spar.renderer, c115, c335, c000, c110, c003, c004, c220, c335, shadowColors, + ARRAY_SIZE(shadowColors), ledColor); + + // Initialize battle record + sparLoadBattleRecords(); + + // Load the splash screen + cg->spar.state = CG_SPAR_SPLASH; +} + +/** + * @brief Deinit Spar + * + * @param cg Game data + */ +void cg_deInitSpar() +{ + // Free the menu + deinitMenu(cg->spar.sparMenu); + deinitMenuManiaRenderer(cg->spar.renderer); + + // Fonts + freeFont(&cg->spar.sparTitleFont); + freeFont(&cg->spar.sparTitleFontOutline); + freeFont(&cg->spar.sparRegFont); + + // Free assets + freeWsg(&cg->spar.arrow); + freeWsg(&cg->spar.dojoBG); + for (uint8_t i = 0; i < ARRAY_SIZE(sparDojoSprites); i++) + { + freeWsg(&cg->spar.dojoBGItems[i]); + } + free(cg->spar.dojoBGItems); +} + +/** + * @brief Runs the spar game mode + * + * @param cg Game data + * @param elapsedUs Time between this frame and the last + */ +void cg_runSpar(int64_t elapsedUs) +{ + // State + buttonEvt_t evt = {0}; + switch (cg->spar.state) + { + case CG_SPAR_SPLASH: + { + while (checkButtonQueueWrapper(&evt)) + { + if (evt.down) + { + cg->spar.state = CG_SPAR_MENU; + } + } + // Draw + cg_drawSparSplash(cg, elapsedUs); + break; + } + case CG_SPAR_MENU: + { + // Get input + while (checkButtonQueueWrapper(&evt)) + { + cg->spar.sparMenu = menuButton(cg->spar.sparMenu, evt); + } + drawMenuMania(cg->spar.sparMenu, cg->spar.renderer, elapsedUs); + break; + } + case CG_SPAR_SCHEDULE: + { + cg_drawSparMatchSetup(cg); + // FIXME: don't immediately drop through + if (true) + { + cg->spar.state = CG_SPAR_MATCH; + cg_initSparMatch(cg, "TestMatch", &cg->chowa[0], &cg->chowa[1], 0, 1200, CG_HARD); + } + break; + } + case CG_MATCH_PREP: + { + // TODO: Match preview + // Show the matchup, then handle countdown + break; + } + case CG_SPAR_MATCH: + { + cg_runSparMatch(cg, elapsedUs); + cg_drawSparMatch(cg, elapsedUs); + break; + } + case CG_SPAR_MATCH_RESULTS: + { + // TODO: Show match results, save to Swadge + // Show the final results + break; + } + case CG_SPAR_BATTLE_RECORD: + { + while (checkButtonQueueWrapper(&evt)) + { + if (evt.down) + { + switch (evt.button) + { + case PB_RIGHT: + { + cg->spar.recordSelect++; + if (cg->spar.recordSelect >= CG_SPAR_MAX_RECORDS) + { + cg->spar.recordSelect = 0; + } + break; + } + case PB_LEFT: + { + cg->spar.recordSelect--; + if (cg->spar.recordSelect < 0) + { + cg->spar.recordSelect = CG_SPAR_MAX_RECORDS - 1; + } + break; + } + case PB_UP: + { + cg->spar.roundSelect--; + if (cg->spar.roundSelect < 0) + { + cg->spar.roundSelect = 2; + } + break; + } + case PB_DOWN: + { + cg->spar.roundSelect++; + if (cg->spar.roundSelect >= 3) + { + cg->spar.roundSelect = 0; + } + break; + } + case PB_A: + case PB_B: + { + cg->spar.state = CG_SPAR_MENU; + break; + } + default: + { + break; + } + } + } + } + // Draw + cg_drawSparRecord(cg); + break; + } + case CG_SPAR_TUTORIAL: + { + break; + } + default: + { + // Should never hit + break; + } + } +} + +static void sparMenuCb(const char* label, bool selected, uint32_t settingVal) +{ + if (selected) + { + if (label == sparMenuNames[0]) + { + // Go to match setup + cg->spar.state = CG_SPAR_SCHEDULE; + } + else if (label == sparMenuNames[1]) + { + // View records + cg->spar.state = CG_SPAR_BATTLE_RECORD; + } + else if (label == sparMenuNames[2]) + { + // Tutorial + cg->spar.state = CG_SPAR_TUTORIAL; + } + else if (label == sparMenuNames[3]) + { + // Settings + } + else if (label == sparMenuNames[4]) + { + // Go to main menu + cg->state = CG_MAIN_MENU; + cg->spar.state = CG_SPAR_SPLASH; + } + else + { + // Something went wrong + } + } +} + +static void sparLoadBattleRecords() +{ + // FIXME: Load from disk + for (int32_t idx = 0; idx < CG_SPAR_MAX_RECORDS; idx++) + { + char buff[32]; + snprintf(buff, sizeof(buff) - 1, "Match %" PRId32, idx); + strcpy(cg->spar.sparRecord[idx].matchTitle, buff); + for (int32_t i = 0; i < 2; i++) + { + snprintf(buff, sizeof(buff) - 1, "Player %" PRId32, i); + strcpy(cg->spar.sparRecord[idx].playerNames[i], buff); + } + for (int32_t i = 0; i < 6; i++) + { + snprintf(buff, sizeof(buff) - 1, "TestChowa%" PRId32, i); + strcpy(cg->spar.sparRecord[idx].chowaNames[i], buff); + cg->spar.sparRecord[idx].colorType[i] = i; + } + for (int32_t i = 0; i < 3; i++) + { + cg->spar.sparRecord[idx].result[i] = i; + cg->spar.sparRecord[idx].timer[i] = (i + 1) * 50; + } + } +} + +/** + * @brief Initializes the high scores based either from NVS or predetermined scores to beat + * + */ +/* static void t48InitHighScores() +{ + // Init High scores + for (int8_t i = 0; i < T48_HS_COUNT; i++) + { + if (!readNvs32(highScoreKey[i], &t48->highScore[i])) + { + switch (i) + { + case 0: + t48->highScore[i] = 96880; + break; + case 1: + t48->highScore[i] = 69224; + break; + case 2: + t48->highScore[i] = 24244; + break; + case 3: + t48->highScore[i] = 11020; + break; + case 4: + t48->highScore[i] = 5176; + break; + } + writeNvs32(highScoreKey[i], t48->highScore[i]); + } + size_t len = 4; + if (!readNvsBlob(highScoreInitialsKey[i], &t48->hsInitials[i], &len)) + { + static char buff[5]; + switch (i) + { + case 0: + strcpy(buff, "JW"); + break; + case 1: + strcpy(buff, "Pan"); + break; + case 2: + strcpy(buff, "Pix"); + break; + case 3: + strcpy(buff, "Poe"); + break; + case 4: + strcpy(buff, "DrG"); + break; + } + strcpy(t48->hsInitials[i], buff); + writeNvsBlob(highScoreInitialsKey[i], &t48->hsInitials[i], len); + } + } +} */ \ No newline at end of file diff --git a/main/modes/games/cGrove/Sparring/cg_Spar.h b/main/modes/games/cGrove/Sparring/cg_Spar.h new file mode 100644 index 000000000..2e00d513a --- /dev/null +++ b/main/modes/games/cGrove/Sparring/cg_Spar.h @@ -0,0 +1,25 @@ +/** + * @file cg_spar.h + * @author Jeremy Stintzcum (jeremy.stintzcum@gmail.com) + * @brief Provides the sparring implementation for Chowa Grove + * @version 0.1 + * @date 2024-09-19 + * + * @copyright Copyright (c) 2024 + * + */ +#pragma once + +//============================================================================== +// Includes +//============================================================================== + +#include "cg_Typedef.h" + +//============================================================================== +// Functions +//============================================================================== + +void cg_initSpar(cGrove_t* cg); +void cg_deInitSpar(void); +void cg_runSpar(int64_t elapsedUs); diff --git a/main/modes/games/cGrove/Sparring/cg_SparDraw.c b/main/modes/games/cGrove/Sparring/cg_SparDraw.c new file mode 100644 index 000000000..358baac02 --- /dev/null +++ b/main/modes/games/cGrove/Sparring/cg_SparDraw.c @@ -0,0 +1,503 @@ +/** + * @file cg_sparDraw.c + * @author Jeremy Stintzcum (jeremy.stintzcum@gmail.com) + * @brief Draws the Chowa Garden Spar + * @version 0.1 + * @date 2024-09-19 + * + * @copyright Copyright (c) 2024 + * + */ + +//============================================================================== +// Includes +//============================================================================== + +#include "cg_SparDraw.h" + +//============================================================================== +// Defines +//============================================================================== + +#define STAT_BAR_WIDTH 4 +#define STAT_BAR_BASE 200 +#define PADDING 6 + +//============================================================================== +// Const Strings +//============================================================================== + +static const char sparSplashScreen[] = "Chowa Sparring"; +static const char sparSplashScreenPrompt[] = "Press any button to continue!"; + +//============================================================================== +// Function Declarations +//============================================================================== + +static void cg_drawSparField(cGrove_t* cg); +static void cg_drawSparBGObject(cGrove_t* cg, int64_t elapsedUs); +static void cg_drawSparChowa(cGrove_t* cg, int64_t elapsedUs); +static void cg_drawSparChowaUI(cGrove_t* cg); +static void cg_drawSparProgBars(cGrove_t* cg, int16_t maxVal, int16_t currVal, int16_t x, int16_t y, + paletteColor_t color, int8_t bitShift); + +//============================================================================== +// Functions +//============================================================================== + +/** + * @brief Draws the match + * + * @param cg Game data + * @param elapsedUs TIme since last frame + */ +void cg_drawMatch(cGrove_t* cg, int64_t elapsedUs) +{ + // Draw dojo + cg_drawSparField(cg); + + // Draw Objects + cg_drawSparBGObject(cg, elapsedUs); + + // Draw Chowa +} + +/** + * @brief Draws the splash screen + * + * @param cg Game data + * @param elapsedUs Time since last frame + */ +void cg_drawSparSplash(cGrove_t* cg, int64_t elapsedUs) +{ + // Draw dojo + cg_drawSparField(cg); + cg_drawSparBGObject(cg, elapsedUs); + + // TODO: Draw chowa sparring + + // Draw title text + // Get the text offset + int16_t xOff = (TFT_WIDTH - textWidth(&cg->spar.sparTitleFont, sparSplashScreen)) / 2; + int16_t yOff = TFT_HEIGHT / 2; + + // drawText(&t48->titleFont, color, mode, (TFT_WIDTH - textWidth(&t48->titleFont, mode)) / 2, TFT_HEIGHT / 2 - 12); + drawText(&cg->spar.sparTitleFont, c555, sparSplashScreen, xOff, yOff); + drawText(&cg->spar.sparTitleFontOutline, c000, sparSplashScreen, xOff, yOff); + + xOff = 32; + yOff = TFT_HEIGHT - 32; + // Draw "Press A to continue" prompt + cg->spar.timer += elapsedUs; + if (cg->spar.timer >= 500000) + { + cg->spar.timer = 0; + cg->spar.toggle = !cg->spar.toggle; + } + if (cg->spar.toggle) + { + drawTextWordWrap(&cg->spar.sparRegFont, c000, sparSplashScreenPrompt, &xOff, &yOff, TFT_WIDTH - 32, TFT_HEIGHT); + } +} + +/** + * @brief Draws the battle record + * + * @param cg Game data + */ +void cg_drawSparRecord(cGrove_t* cg) +{ + // Background + fillDisplayArea(0, 0, TFT_WIDTH, TFT_HEIGHT, c000); + + // Reference the record + cgRecord_t* record = &cg->spar.sparRecord[cg->spar.recordSelect]; + + // Draw Match title + Arrows + drawText(&cg->spar.sparRegFont, c555, record->matchTitle, + (TFT_WIDTH - textWidth(&cg->spar.sparRegFont, record->matchTitle)) / 2, 8); + drawWsg(&cg->spar.arrow, + (TFT_WIDTH - textWidth(&cg->spar.sparRegFont, record->matchTitle)) / 2 - (4 + cg->spar.arrow.w), 4, false, + false, 270); + drawWsg(&cg->spar.arrow, (TFT_WIDTH + textWidth(&cg->spar.sparRegFont, record->matchTitle)) / 2 + 4, 4, false, + false, 90); + + // Player names + vs label + int16_t vsStart = (TFT_WIDTH - textWidth(&cg->spar.sparRegFont, "VS")) / 2; + drawText(&cg->spar.sparRegFont, c333, "VS", vsStart, 24); + drawText(&cg->spar.sparRegFont, c533, record->playerNames[0], + vsStart - (textWidth(&cg->spar.sparRegFont, record->playerNames[0]) + 16), 24); + drawText(&cg->spar.sparRegFont, c335, record->playerNames[1], vsStart + textWidth(&cg->spar.sparRegFont, "VS") + 16, + 24); + + // Round number and arrows + char buffer[32]; + snprintf(buffer, sizeof(buffer) - 1, "Round %d", cg->spar.roundSelect); + drawWsgSimple(&cg->spar.arrow, (TFT_WIDTH - cg->spar.arrow.w) / 2, 40); + drawText(&cg->spar.sparRegFont, c444, buffer, (TFT_WIDTH - textWidth(&cg->spar.sparRegFont, buffer)) / 2, 60); + drawWsg(&cg->spar.arrow, (TFT_WIDTH - cg->spar.arrow.w) / 2, 76, false, true, 0); + + // Draw Chowa + // TODO: Draw Chowa + + // Draw time + snprintf(buffer, sizeof(buffer) - 1, "Time: %d", record->timer[cg->spar.roundSelect]); + drawText(&cg->spar.sparRegFont, c444, buffer, (TFT_WIDTH - textWidth(&cg->spar.sparRegFont, buffer)) / 2, 188); + + // Draw Chowa names + int8_t offset = cg->spar.roundSelect * 2; + drawText(&cg->spar.sparRegFont, c444, record->chowaNames[offset], 8, 204); + drawText(&cg->spar.sparRegFont, c444, record->chowaNames[offset + 1], + TFT_WIDTH - (8 + textWidth(&cg->spar.sparRegFont, record->chowaNames[offset + 1])), 204); + + // Draw result of the game + switch (record->result[cg->spar.roundSelect]) + { + case CG_P1_WIN: + { + snprintf(buffer, sizeof(buffer) - 1, "Winner: %s", record->playerNames[0]); + drawText(&cg->spar.sparRegFont, c533, buffer, (TFT_WIDTH - textWidth(&cg->spar.sparRegFont, buffer)) / 2, + 220); + break; + } + case CG_P2_WIN: + { + snprintf(buffer, sizeof(buffer) - 1, "Winner: %s", record->playerNames[1]); + drawText(&cg->spar.sparRegFont, c335, buffer, (TFT_WIDTH - textWidth(&cg->spar.sparRegFont, buffer)) / 2, + 220); + break; + } + case CG_DRAW: + { + drawText(&cg->spar.sparRegFont, c333, "Draw", (TFT_WIDTH - textWidth(&cg->spar.sparRegFont, "Draw")) / 2, + 220); + break; + } + } +} + +/** + * @brief Draws the match initialization screen + * + * @param cg Game data + */ +void cg_drawSparMatchSetup(cGrove_t* cg) +{ + // Provide options for Tournament, Single match, or online + + // Secondary selection. chooses a specific tournament, match, or online. + + // Allow a player to look at the tutorial +} + +/** + * @brief Draws the match based on current state + * + * @param cg Game Data + * @param elapsedUs TIme since last frame + */ +void cg_drawSparMatch(cGrove_t* cg, int64_t elapsedUs) +{ + // BG + cg_drawSparField(cg); + + // Draw Chowa + cg_drawSparChowa(cg, elapsedUs); + + // Draw BG objects + cg_drawSparBGObject(cg, elapsedUs); + + // Draw UI + // Buffer + char buffer[32]; + + // Draw match title + snprintf(buffer, sizeof(buffer) - 1, "%s, round %d", cg->spar.match.matchName, cg->spar.match.round); + drawText(&cg->spar.sparRegFont, c000, buffer, (TFT_WIDTH - textWidth(&cg->spar.sparRegFont, buffer)) / 2, 8); + + // Time + paletteColor_t color = c000; + if (cg->spar.match.maxTime <= (cg->spar.match.timer + 10)) + { + color = c500; + } + snprintf(buffer, sizeof(buffer) - 1, "Time: %d", cg->spar.match.maxTime - cg->spar.match.timer); + drawText(&cg->spar.sparRegFont, color, buffer, (TFT_WIDTH - textWidth(&cg->spar.sparRegFont, buffer)) / 2, 24); + + // Draw Chowa UI + cg_drawSparChowaUI(cg); + + // If paused, draw pause text + if (cg->spar.match.paused) + { + drawText(&cg->spar.sparTitleFont, c005, "--PAUSE--", + (TFT_WIDTH - textWidth(&cg->spar.sparTitleFont, "--PAUSE--")) / 2, TFT_HEIGHT / 2 - 16); + } + + // Draw match end + if (cg->spar.match.done) + { + drawText(&cg->spar.sparTitleFont, c005, "FINISHED", + (TFT_WIDTH - textWidth(&cg->spar.sparTitleFont, "FINISHED")) / 2, TFT_HEIGHT / 2 - 16); + } +} + +/** + * @brief Draws the background + * + * @param cg Game data + */ +static void cg_drawSparField(cGrove_t* cg) +{ + // Draw dojo + drawWsgSimple(&cg->spar.dojoBG, 0, 0); +} + +/** + * @brief Draws the background objects + * + * @param cg Game data + * @param elapsedUs Time since last frame + */ +static void cg_drawSparBGObject(cGrove_t* cg, int64_t elapsedUs) +{ + // If wasCrit == true, setg false and add impulses to items + // FIXME: Do bounce thing + for (int32_t i = 0; i < 2; i++) + { + drawWsgSimple(&cg->spar.dojoBGItems[i], 32 * (i + 1), 32 * (i + 1)); + } +} + +static void cg_drawSparChowa(cGrove_t* cg, int64_t elapsedUs) +{ + for (int32_t idx = 0; idx < 2; idx++) + { + switch (cg->spar.match.chowaData[idx].currState) + { + case CG_SPAR_UNREADY: + case CG_SPAR_NOTHING: + { + // Bounce back and forth as ready + break; + } + case CG_SPAR_READY: + { + // Pause on a specific frame + break; + } + case CG_SPAR_EXHAUSTED: + { + // Sitting on the floor, panting + break; + } + case CG_SPAR_HIT: + { + // Chowa flashes as they get hit + break; + } + case CG_SPAR_ATTACK: + { + // Draw the Chowa attacking + break; + } + case CG_SPAR_DODGE_ST: + { + // Draw chowa dodge + break; + } + case CG_SPAR_WIN: + { + // Draw Chowa cheering + break; + } + case CG_SPAR_LOSE: + { + // Draw Chowa crying + break; + } + } + } + // Draw Chowa + // - 2x size + // - Nametags / "You!" + // FIXME: Only set done when actually done + if (cg->spar.match.chowaData[0].doneAnimating && cg->spar.match.chowaData[1].doneAnimating) + { + cg->spar.match.animDone = true; + } + // +} + +/** + * @brief Draws the Chowa centric UI + * + * @param cg Game Data + */ +static void cg_drawSparChowaUI(cGrove_t* cg) +{ + // Player 1 + // Draw health bar + cg_drawSparProgBars(cg, cg->spar.match.chowaData[CG_P1].maxHP, cg->spar.match.chowaData[CG_P1].HP, PADDING, + STAT_BAR_BASE, c500, 3); + // Draw stamina bar + cg_drawSparProgBars(cg, cg->spar.match.chowaData[CG_P1].maxStamina, cg->spar.match.chowaData[CG_P1].stamina, + 1 * (PADDING + STAT_BAR_WIDTH) + PADDING, STAT_BAR_BASE, c550, 1); + // Draw Readiness bar + cg_drawSparProgBars(cg, CG_MAX_READY_VALUE, cg->spar.match.chowaData[CG_P1].readiness, + 2 * (PADDING + STAT_BAR_WIDTH) + PADDING, STAT_BAR_BASE, c050, -1); + + switch (cg->spar.match.chowaData[CG_P1].currState) + { + case CG_SPAR_UNREADY: + case CG_SPAR_READY: + { + // TODO: Draw attack icon + switch (cg->spar.match.chowaData[CG_P1].currMove) + { + case CG_SPAR_PUNCH: + { + drawText(&cg->spar.sparRegFont, c005, "P1: Punch", 100, 108); + drawWsgSimple(&cg->spar.arrow, 64, 64); + break; + } + case CG_SPAR_FAST_PUNCH: + { + drawText(&cg->spar.sparRegFont, c005, "P1: Fast Punch", 100, 108); + drawWsg(&cg->spar.arrow, 64, 64, false, true, 0); + break; + } + case CG_SPAR_KICK: + { + drawText(&cg->spar.sparRegFont, c005, "P1: Kick", 100, 108); + drawWsg(&cg->spar.arrow, 64, 64, false, false, 270); + break; + } + case CG_SPAR_JUMP_KICK: + { + drawText(&cg->spar.sparRegFont, c005, "P1: Jump Kick", 100, 108); + drawWsg(&cg->spar.arrow, 64, 64, false, false, 90); + break; + } + case CG_SPAR_HEADBUTT: + { + drawText(&cg->spar.sparRegFont, c005, "P1: Headbutt", 100, 108); + drawWsg(&cg->spar.arrow, 64, 64, false, false, 90); + drawWsg(&cg->spar.arrow, 64, 64, false, false, 270); + break; + } + case CG_SPAR_DODGE: + { + drawText(&cg->spar.sparRegFont, c005, "P1: Dodge", 100, 108); + drawWsg(&cg->spar.arrow, 64, 64, false, true, 0); + drawWsg(&cg->spar.arrow, 64, 64, false, false, 0); + break; + } + default: + { + drawText(&cg->spar.sparRegFont, c005, "P1: Unset", 100, 108); + break; + } + } + break; + } + default: + { + break; + } + } + + // Player 2 + // Draw health bar + cg_drawSparProgBars(cg, cg->spar.match.chowaData[CG_P2].maxHP, cg->spar.match.chowaData[CG_P2].HP, + TFT_WIDTH - PADDING, STAT_BAR_BASE, c500, 3); + // Draw stamina bar + cg_drawSparProgBars(cg, cg->spar.match.chowaData[CG_P2].maxStamina, cg->spar.match.chowaData[CG_P2].stamina, + TFT_WIDTH - (1 * (PADDING + STAT_BAR_WIDTH) + PADDING), STAT_BAR_BASE, c550, 1); + // Draw Readiness bar + cg_drawSparProgBars(cg, CG_MAX_READY_VALUE, cg->spar.match.chowaData[CG_P2].readiness, + TFT_WIDTH - (2 * (PADDING + STAT_BAR_WIDTH) + PADDING), STAT_BAR_BASE, c050, -1); + + // FIXME: For debug only + switch (cg->spar.match.chowaData[CG_P2].currState) + { + case CG_SPAR_UNREADY: + case CG_SPAR_READY: + { + // TODO: Draw attack icon + switch (cg->spar.match.chowaData[CG_P2].currMove) + { + case CG_SPAR_PUNCH: + { + drawText(&cg->spar.sparRegFont, c500, "AI: Punch", 100, 128); + break; + } + case CG_SPAR_FAST_PUNCH: + { + drawText(&cg->spar.sparRegFont, c500, "AI: Fast Punch", 100, 128); + break; + } + case CG_SPAR_KICK: + { + drawText(&cg->spar.sparRegFont, c500, "AI: Kick", 100, 128); + break; + } + case CG_SPAR_JUMP_KICK: + { + drawText(&cg->spar.sparRegFont, c500, "AI: Jump Kick", 100, 128); + break; + } + case CG_SPAR_HEADBUTT: + { + drawText(&cg->spar.sparRegFont, c500, "AI: Headbutt", 100, 128); + break; + } + case CG_SPAR_DODGE: + { + drawText(&cg->spar.sparRegFont, c500, "AI: Dodge", 100, 128); + break; + } + default: + { + drawText(&cg->spar.sparRegFont, c500, "AI: Unset", 100, 128); + break; + } + } + break; + } + default: + { + break; + } + } +} + +/** + * @brief Draws a progress bar facing vertically + * + * @param cg Game Data + * @param maxVal Height of the stat bar, bit shifted in half (To fit on screen) + * @param currVal Current value of the stat to track + * @param x X pos of the colored bar + * @param y Y pos base of the bar + * @param color Color of the bar + * @param bitShift How many bits to shift to ring bar into scale. Negative to expand bar. + */ +static void cg_drawSparProgBars(cGrove_t* cg, int16_t maxVal, int16_t currVal, int16_t x, int16_t y, + paletteColor_t color, int8_t bitShift) +{ + if (bitShift < 0) + { + // Draw background + fillDisplayArea(x - 1, y - (maxVal << abs(bitShift)) - 1, x + STAT_BAR_WIDTH + 1, y + 1, c000); + + // Draw bar in proper color + fillDisplayArea(x, y - (currVal << abs(bitShift)), x + STAT_BAR_WIDTH, y, color); + return; + } + // Draw background + fillDisplayArea(x - 1, y - (maxVal >> bitShift) - 1, x + STAT_BAR_WIDTH + 1, y + 1, c000); + + // Draw bar in proper color + fillDisplayArea(x, y - (currVal >> bitShift), x + STAT_BAR_WIDTH, y, color); +} \ No newline at end of file diff --git a/main/modes/games/cGrove/Sparring/cg_SparDraw.h b/main/modes/games/cGrove/Sparring/cg_SparDraw.h new file mode 100644 index 000000000..4827a2893 --- /dev/null +++ b/main/modes/games/cGrove/Sparring/cg_SparDraw.h @@ -0,0 +1,27 @@ +/** + * @file cg_sparDraw.h + * @author Jeremy Stintzcum (jeremy.stintzcum@gmail.com) + * @brief Draws the Chowa Garden Spar + * @version 0.1 + * @date 2024-09-19 + * + * @copyright Copyright (c) 2024 + * + */ +#pragma once + +//============================================================================== +// Includes +//============================================================================== + +#include "cg_Typedef.h" + +//============================================================================== +// Functions +//============================================================================== + +void cg_drawMatch(cGrove_t* cg, int64_t elapsedUs); +void cg_drawSparSplash(cGrove_t* cg, int64_t elapsedUs); +void cg_drawSparRecord(cGrove_t* cg); +void cg_drawSparMatchSetup(cGrove_t* cg); +void cg_drawSparMatch(cGrove_t* cg, int64_t elapsedUs); \ No newline at end of file diff --git a/main/modes/games/cGrove/cg_Chowa.c b/main/modes/games/cGrove/cg_Chowa.c new file mode 100644 index 000000000..d362772bf --- /dev/null +++ b/main/modes/games/cGrove/cg_Chowa.c @@ -0,0 +1,262 @@ +/** + * @file cg_chowa.c + * @author Jeremy Stintzcum (jeremy.stintzcum@gmail.com) + * @brief Pet behavior and appearance + * @version 0.1 + * @date 2024-09-08 + * + * @copyright Copyright (c) 2024 + * + */ + +//============================================================================== +// Includes +//============================================================================== + +#include "cg_Chowa.h" + +//============================================================================== +// Consts +//============================================================================== + +static const char* imageStrings[] = { + "_angry-1.wsg", "_angry-2.wsg", // Angry (0-1) + "_bkfall1.wsg", "_bkfall2.wsg", "_bkfall3.wsg", "_bkfall4.wsg", // Falling up (2-5) + "_bkwalk1.wsg", "_bkwalk2.wsg", "_bkwalk3.wsg", "_bkwalk4.wsg", // Walking up (6-9) + "_climb1.wsg", "_climb2.wsg", // Climbing (10-11) + "_dance-1.wsg", "_dance-2.wsg", "_dance-3.wsg", "_dance-4.wsg", // Dancing (12-15) + "_disgust1.wsg", "_disgust2.wsg", // Disgust (16-17) + "_draw-1.wsg", "_draw-2.wsg", // Drawing (18-19) + "_eat-1.wsg", "_eat-2.wsg", "_eat-3.wsg", "_eat-4.wsg", // Eating (20-23) + "_fallingbk-1.wsg", "_fallingbk-2.wsg", // Falling facing down (24-25) + "_fear-1.wsg", "_fear-2.wsg", // Afraid (26-27) + "_flail-1.wsg", "_flail-2.wsg", // Flailing (28-29) + "_gift-1.wsg", // Gift/Cheer (30) + "_givup-1.wsg", "_givup-2.wsg", // Depressed/Give up (31-32) + "_happy1.wsg", "_happy2.wsg", // Happy (33-34) + "_hdbtt-1.wsg", "_hdbtt-2.wsg", // Headbutt (35-36) + "_kick-1.wsg", "_kick-2.wsg", // Kick (37-38) + "_pet-1.wsg", // Being pet (39) + "_punch-1.wsg", "_punch-2.wsg", // Punching (40-41) + "_read-1.wsg", "_read-2.wsg", "_read-3.wsg", // Reading (42-44) + "_sad-1.wsg", "_sad-2.wsg", // Sad (45-46) + "_sdfall1.wsg", "_sdfall2.wsg", "_sdfall3.wsg", // Side Fall (47-49) + "_sdwalk1.wsg", "_sdwalk2.wsg", "_sdwalk3.wsg", "_sdwalk4.wsg", // Side walk (50-53) + "_sing-1.wsg", "_sing-2.wsg", "_sing-3.wsg", "_sing-4.wsg", // Sing (54-57) + "_sit1.wsg", // Sit (58) + "_swim-1.wsg", "_swim-2.wsg", "_swim-3.wsg", "_swim-4.wsg", // Swim (59-62) + "_swordplay-1.wsg", "_swordplay-2.wsg", "_swordplay-3.wsg", // Swords (63-65) + "_throw-1.wsg", "_throw-2.wsg", "_throw-3.wsg", // Throw item (66-68) + "_walk1.wsg", "_walk2.wsg", "_walk3.wsg", "_walk4.wsg", // Walk down (69-72) +}; + +//============================================================================== +// Function Declarations +//============================================================================== + +/** + * @brief Uses a prefix to load assets + * + * @param cg Game data + * @param type The type of Chowa + * @param adult If it's an adult or not + * @param prefix The string to append to load the file + */ +static void cg_initByPrefix(cGrove_t* cg, cgColorType_t type, int8_t adult, char* prefix); + +//============================================================================== +// Functions +//============================================================================== + +/** + * @brief Initialize all the Chowa sprites + * + * @param cg Game Data + */ +void cg_initChowaWSGs(cGrove_t* cg) +{ + for (int32_t idx = 0; idx < CG_NUM_TYPES; idx++) + { + for (int32_t idx2 = 0; idx2 < 2; idx2++) + { + cg->chowaWSGs[idx][idx2] = calloc(ARRAY_SIZE(imageStrings), sizeof(wsg_t)); + } + } + cg_initByPrefix(cg, CG_KING_DONUT, CG_CHILD, "ckd"); +} + +/** + * @brief De-Init all the CHowa Sprites + * + * @param cg Game Data + */ +void cg_deInitChowaWSGs(cGrove_t* cg) +{ + for (int32_t idx = 0; idx < CG_NUM_TYPES; idx++) + { + for (int32_t idx2 = 0; idx < 2; idx++) + { + for (int idx3 = 0; idx3 < ARRAY_SIZE(imageStrings); idx3++) + { + freeWsg(&cg->chowaWSGs[idx][idx2][idx3]); + } + free(cg->chowaWSGs[idx][idx2]); + } + } +} + +/** + * @brief Gets the sprite of a chowa for the animation provided. From there, is can be used in any normal WSG function + * + * @param cg Game Data + * @param c Chowa to get animation for + * @param anim Animation to grab + * @param idx Index of the animation + * @return wsg_t* Pointer to the expected WSG + */ +wsg_t* cg_getChowaWSG(cGrove_t* cg, cgChowa_t* c, cgChowaAnimIdx_t anim, int8_t idx) +{ + // Get type and age + cgChowaAnimAge_t age = CG_CHILD; + if (c->age > 64) + { + age = CG_ADULT; + } + wsg_t* spr = cg->chowaWSGs[c->type][age]; + + // Get spr from animation + switch (anim) + { + case CG_ANIM_ANGRY: + { + return &spr[0 + idx]; + } + case CG_ANIM_DISGUST: + { + return &spr[16 + idx]; + } + case CG_ANIM_FEAR: + { + return &spr[26 + idx]; + } + case CG_ANIM_FLAIL: + { + return &spr[28 + idx]; + } + case CG_ANIM_GIVE_UP: + { + return &spr[31 + idx]; + } + case CG_ANIM_HAPPY: + { + return &spr[33 + idx]; + } + case CG_ANIM_SAD: + { + return &spr[45 + idx]; + } + case CG_ANIM_WALK_DOWN: + { + return &spr[69 + idx]; + } + case CG_ANIM_WALK_SIDE: + { + return &spr[50 + idx]; + } + case CG_ANIM_WALK_UP: + { + return &spr[6 + idx]; + } + case CG_ANIM_SWIM: + { + return &spr[59 + idx]; + } + case CG_ANIM_CLIMB: + { + return &spr[10 + idx]; + } + case CG_ANIM_FALL_SIDE: + { + return &spr[47 + idx]; + } + case CG_ANIM_FALL_UP: + { + return &spr[2 + idx]; + } + case CG_ANIM_FALL_DOWN: + { + return &spr[24 + idx]; + } + case CG_ANIM_HEADBUTT: + { + return &spr[35 + idx]; + } + case CG_ANIM_KICK: + { + return &spr[37 + idx]; + } + case CG_ANIM_PUNCH: + { + return &spr[40 + idx]; + } + case CG_ANIM_DRAW: + { + return &spr[18 + idx]; + } + case CG_ANIM_EAT: + { + return &spr[20 + idx]; + } + case CG_ANIM_GIFT: + { + return &spr[30]; + } + case CG_ANIM_READ: + { + return &spr[42 + idx]; + } + case CG_ANIM_SWORD: + { + return &spr[63 + idx]; + } + case CG_ANIM_THROW: + { + return &spr[66 + idx]; + } + case CG_ANIM_DANCE: + { + return &spr[12 + idx]; + } + case CG_ANIM_PET: + { + return &spr[39]; + } + case CG_ANIM_SING: + { + return &spr[54 + idx]; + } + case CG_ANIM_SIT: + { + return &spr[58]; + } + default: + { + return &spr[69]; // Walk down frame 1 + } + } +} + +//============================================================================== +// Static Functions +//============================================================================== + +static void cg_initByPrefix(cGrove_t* cg, cgColorType_t type, int8_t adult, char* prefix) +{ + char buffer[64]; + for (int idx = 0; idx < ARRAY_SIZE(imageStrings); idx++) + { + strcpy(buffer, prefix); + strcat(buffer, imageStrings[idx]); + loadWsg(buffer, &cg->chowaWSGs[type][adult][idx], true); + } +} \ No newline at end of file diff --git a/main/modes/games/cGrove/cg_Chowa.h b/main/modes/games/cGrove/cg_Chowa.h new file mode 100644 index 000000000..f7c97ae80 --- /dev/null +++ b/main/modes/games/cGrove/cg_Chowa.h @@ -0,0 +1,25 @@ +/** + * @file cg_chowa.h + * @author Jeremy Stintzcum (jeremy.stintzcum@gmail.com) + * @brief Pet behavior and appearance + * @version 0.1 + * @date 2024-09-08 + * + * @copyright Copyright (c) 2024 + * + */ +#pragma once + +//============================================================================== +// Includes +//============================================================================== + +#include "cg_Typedef.h" + +//============================================================================== +// Function declarations +//============================================================================== + +void cg_initChowaWSGs(cGrove_t* cg); +void cg_deInitChowaWSGs(cGrove_t* cg); +wsg_t* cg_getChowaWSG(cGrove_t* cg, cgChowa_t* c, cgChowaAnimIdx_t anim, int8_t idx); \ No newline at end of file diff --git a/main/modes/games/cGrove/cg_Items.c b/main/modes/games/cGrove/cg_Items.c new file mode 100644 index 000000000..a6f8dcb85 --- /dev/null +++ b/main/modes/games/cGrove/cg_Items.c @@ -0,0 +1,35 @@ +/** + * @file cg_GardenItems.c + * @author Jeremy Stintzcum (jeremy.stintzcum@gmail.com) + * @brief Interactive items inside of the main garden + * @version 0.1 + * @date 2024-09-07 + * + * @copyright Copyright (c) 2024 + * + */ + +//============================================================================== +// Includes +//============================================================================== + +#include "cg_Items.h" + +//============================================================================== +// Functions +//============================================================================== + +void cgInitItem(cGrove_t* cg, int8_t idx, char* name, wsg_t spr, vec_t pos) +{ + strcpy(cg->grove.items[idx].name, name); + cg->grove.items[idx].spr = spr; + cg->grove.items[idx].aabb.pos = pos; + cg->grove.items[idx].aabb.height = spr.h; + cg->grove.items[idx].aabb.width = spr.w; + cg->grove.items[idx].active = true; +} + +void cgDeactivateItem(cGrove_t* cg, int8_t idx) +{ + cg->grove.items[idx].active = false; +} \ No newline at end of file diff --git a/main/modes/games/cGrove/cg_Items.h b/main/modes/games/cGrove/cg_Items.h new file mode 100644 index 000000000..5f5e2a7a2 --- /dev/null +++ b/main/modes/games/cGrove/cg_Items.h @@ -0,0 +1,40 @@ +/** + * @file cg_GardenItems.h + * @author Jeremy Stintzcum (jeremy.stintzcum@gmail.com) + * @brief Interactive items inside of the main garden + * @version 0.1 + * @date 2024-09-07 + * + * @copyright Copyright (c) 2024 + * + */ +#pragma once + +//============================================================================== +// Includes +//============================================================================== + +#include "cg_Typedef.h" + +//============================================================================== +// Function declarations +//============================================================================== + +/** + * @brief Initializes an item slot + * + * @param cg Game Object + * @param idx Index of item + * @param name Text string of item + * @param spr Sprite of item + * @param pos Position of item + */ +void cgInitItem(cGrove_t* cg, int8_t idx, char* name, wsg_t spr, vec_t pos); + +/** + * @brief Removes an item form the game field + * + * @param cg Game Object + * @param idx Index of item + */ +void cgDeactivateItem(cGrove_t* cg, int8_t idx); diff --git a/main/modes/games/cGrove/cg_Typedef.h b/main/modes/games/cGrove/cg_Typedef.h new file mode 100644 index 000000000..d48679b76 --- /dev/null +++ b/main/modes/games/cGrove/cg_Typedef.h @@ -0,0 +1,550 @@ +/** + * @file cg_Typedef.h + * @author Jeremy Stintzcum (jeremy.stintzcum@gmail.com) + * @brief Types for Chowa Grove + * @version 0.1 + * @date 2024-09-19 + * + * @copyright Copyright (c) 2024 + * + */ +#pragma once + +//============================================================================== +// Includes +//============================================================================== +#include "swadge2024.h" +#include "wsgPalette.h" + +//============================================================================== +// Defines +//============================================================================== +#define CG_MAX_STR_LEN 17 + +//============================================================================== +// Items +//============================================================================== + +// Structs ============================== +typedef struct +{ + char name[CG_MAX_STR_LEN]; ///< Name + rectangle_t aabb; ///< Position and bounding box + wsg_t spr; ///< Spr for item + bool active; ///< If item slot is being used +} cgItem_t; + +typedef struct +{ + char name[CG_MAX_STR_LEN]; ///< Name + wsg_t* spr; ///< Sprite +} cgWearable_t; + +//============================================================================== +// Chowa +//============================================================================== + +// Defines ============================= +#define CG_MAX_CHOWA 5 // Max number of Chowa allowed on a swadge +#define CG_STAT_COUNT 6 // Number of stats +#define CG_NUM_TYPES 7 // Total number of different types of Chowa + +// Enums =============================== +typedef enum +{ + CG_NEUTRAL, + CG_HAPPY, + CG_WORRIED, + CG_SAD, + CG_ANGRY, + CG_CONFUSED, + CG_SURPRISED, + CG_SICK, +} cgMoodEnum_t; + +typedef enum +{ + CG_AGGRESSIVE, + CG_BORING, + CG_BRASH, + CG_CARELESS, + CG_CRY_BABY, + CG_DUMB, + CG_KIND, + CG_OVERLY_CAUTIOUS, + CG_SHY, + CG_SMART, +} cgChowaPersonality_t; + +typedef enum +{ + // Emotes + CG_ANIM_ANGRY, + CG_ANIM_DISGUST, + CG_ANIM_FEAR, + CG_ANIM_FLAIL, + CG_ANIM_GIVE_UP, + CG_ANIM_HAPPY, + CG_ANIM_SAD, + + // Moving + CG_ANIM_WALK_DOWN, + CG_ANIM_WALK_SIDE, + CG_ANIM_WALK_UP, + CG_ANIM_SWIM, + CG_ANIM_CLIMB, + + // Falling + CG_ANIM_FALL_SIDE, + CG_ANIM_FALL_UP, + CG_ANIM_FALL_DOWN, + + // Attacking + CG_ANIM_HEADBUTT, + CG_ANIM_KICK, + CG_ANIM_PUNCH, + + // Using Items + CG_ANIM_DRAW, + CG_ANIM_EAT, + CG_ANIM_GIFT, + CG_ANIM_READ, + CG_ANIM_SWORD, + CG_ANIM_THROW, + + // Other + CG_ANIM_DANCE, + CG_ANIM_PET, + CG_ANIM_SING, + CG_ANIM_SIT, +} cgChowaAnimIdx_t; + +typedef enum +{ + CG_ADULT, + CG_CHILD, +} cgChowaAnimAge_t; + +typedef enum +{ + CG_HEALTH, + CG_STRENGTH, + CG_AGILITY, + CG_SPEED, + CG_CHARISMA, + CG_STAMINA, +} cgChowaStat_t; + +typedef enum +{ + CG_NORMAL, + CG_CHO, + CG_KING_DONUT, + CG_RED_LUMBERJACK, + CG_GREEN_LUMBERJACK, + CG_KOSMO, + CG_LILB, +} cgColorType_t; + +// Structs ============================== +typedef struct +{ + // System + bool active; ///< If Chowa slot is being used. Only set to true if data has been set. + + // Base data + char name[CG_MAX_STR_LEN]; + int8_t age; ///< Current age of the Chowa + int8_t maxAge; ///< Maximum Chowa age. 4 hours of in game time + uint8_t playerAffinity; ///< How much Chowa likes the player + cgMoodEnum_t mood; ///< Current mood of the Chowa + cgChowaPersonality_t pers; ///< Chowa's personality + cgChowaStat_t stats[CG_STAT_COUNT]; ///< Array containing stat information + + // Color data + // Note: Palette must be initialized for all Chowa, regardless or the colors will be screwy + cgColorType_t type; ///< Type of Chowa + wsgPalette_t color; ///< If Normal type, color scheme +} cgChowa_t; + +//============================================================================== +// NPCs +//============================================================================== + +typedef struct +{ + char name[CG_MAX_STR_LEN]; ///< Name of the NPC + wsg_t* icon; ///< Icons for dialogue + // Text for competitions + cgChowa_t chowa[3]; ///< NPCs CHowa +} cgNPC_t; + +//============================================================================== +// Submode generics +//============================================================================== + +// Enums =============================== +typedef enum +{ + CG_P1, + CG_P2, +} cgPlayers_t; + +typedef enum +{ + CG_P1_WIN, + CG_P2_WIN, + CG_DRAW, +} cgWinLoss_t; + +typedef enum +{ + CG_BEGINNER, + CG_VERY_EASY, + CG_EASY, + CG_MEDIUM, + CG_HARD, + CG_VERY_HARD, + CG_EXPERT, +} cgAIDifficulty_t; + +// Structs ============================== +typedef struct +{ + char matchTitle[CG_MAX_STR_LEN]; ///< Title of the match + char playerNames[2][CG_MAX_STR_LEN]; ///< Player names + char chowaNames[6][CG_MAX_STR_LEN]; ///< Up to 6 Chowa participate + cgColorType_t colorType[6]; ///< Type of Chowa + wsgPalette_t palettes[6]; ///< Colors of the Chowa for drawing + cgWinLoss_t result[3]; ///< Results of all three matches + int16_t timer[3]; ///< Time per round in seconds +} cgRecord_t; + +//============================================================================== +// Garden +//============================================================================== + +// Defines ============================= +#define CG_GROVE_MAX_ITEMS 11 ///< Max number of items. Cannot assume unique +#define CG_GROVE_MAX_GUEST_CHOWA 5 ///< Maximum number of Chowa allowed to be in the grove at once + +#define CG_GROVE_SCREEN_BOUNDARY 32 ///< How close the cursor can get to the edge of the screen + +// Enum ================================= + +typedef enum +{ + CG_TREE, + CG_STUMP, + CG_WATER +} cgBoundary_t; + +typedef enum +{ + CHOWA_IDLE, ///< Doing nothing. Get new behavior + CHOWA_STATIC, ///< Standing in place + CHOWA_WALK, ///< Walking, running, swimming, struggling to swim towards a target + CHOWA_CHASE, ///< Follow Other chowa, object, or cursor + CHOWA_USE_ITEM, ///< Use an item held in Chowa's possession + CHOWA_BOX, ///< Does sparring type moves + CHOWA_SING, ///< Sings + CHOWA_DANCE, ///< Dancing + CHOWA_TALK, ///< Talks with another Chowa + CHOWA_HELD, ///< Held, cannot move + CHOWA_GIFT, ///< Receiving a gift/head pats + CHOWA_PET, ///< Chowa is being pet +} cgChowaStateGarden_t; + +// Structs ============================== +typedef struct +{ + int16_t money; ///< Money + cgItem_t items[CG_GROVE_MAX_ITEMS]; ///< Item IDs + uint8_t quantities[CG_GROVE_MAX_ITEMS]; ///< Item qtys +} cgInventory_t; + +typedef struct +{ + rectangle_t aabb; ///< Position and bounding box + int64_t despawnTimer; ///< Time left until money despawns + int8_t animFrame; ///< Sparkle animation frames + bool active; ///< If the ring is active +} cgGroveMoney_t; + +typedef struct +{ + // Basic data + cgChowa_t* chowa; ///< Main Chowa data + rectangle_t aabb; ///< Position and bounding box for grabbing + + // Items + bool holdingItem; ///< If Chowa is holding an item + cgItem_t* heldItem; ///< Pointer to the held item + + // AI + cgChowaStateGarden_t gState; ///< Behavior state in the garden + cgChowaStateGarden_t nextState; ///< Cued state after arriving at target + int64_t timeLeft; ///< Set to number of seconds a behavior can take + int64_t nextTimeLeft; ///< Time left on next state + vec_t targetPos; ///< Position to head to + float precision; ///< How precise the position needs to be + + // Animations + int16_t angle; ///< Angle that the Chowa is moving at + int8_t animFrame; ///< Frame that the animation is on + int64_t frameTimer; ///< Timer until the next frame triggers + int8_t animIdx; ///< which animation is being played + bool flip; ///< If image needs to be flipped manually + bool hasPartner; ///< If Chowa has a partner for talking or boxing +} cgGroveChowa_t; + +typedef struct +{ + // Assets + // Grove WSGs + wsg_t groveBG; ///< Grove background + wsg_t* cursors; ///< Cursor sprites + wsg_t* angerParticles; ///< Anger particle sprites + wsg_t* questionMarks; ///< Question mark sprites + wsg_t* notes; ///< Musical notes + wsg_t* speechBubbles; ///< Speech Bubbles for Chowa + wsg_t* itemsWSGs; ///< Item sprites + // Audio + midiFile_t bgm; ///< Main BGM for the Grove + + // Field data + cgItem_t items[CG_GROVE_MAX_ITEMS]; ///< Items present in the Grove + rectangle_t boundaries[3]; ///< Boundary boxes + cgGroveChowa_t chowa[CG_MAX_CHOWA + CG_GROVE_MAX_GUEST_CHOWA]; ///< List of all chowa in the garden + bool bgmPlaying; ///< If the BGM is active + cgInventory_t inv; ///< Inventory struct + cgGroveMoney_t ring; ///< Rings available to collect + + // Menu + menu_t* shop; ///< Shop menu object + menuManiaRenderer_t* renderer; ///< Menu renderer + bool shopOpen; ///< If Shop is open and being drawn or not + + // Player resources + rectangle_t camera; ///< In-garden camera viewport + rectangle_t cursor; ///< Cursor position and bounding box + bool holdingItem; ///< If the player is holding an item + cgItem_t* heldItem; ///< The held item + bool holdingChowa; ///< If the player is holding a Chowa + cgGroveChowa_t* heldChowa; ///< The held Chowa +} cgGrove_t; + +//============================================================================== +// Sparring +//============================================================================== + +// Defines ============================= +#define CG_SPAR_MAX_RECORDS 10 ///< Max number of saved matches +#define CG_MAX_READY_VALUE 64 ///< How large the Ready bars are + +// Enums =============================== + +typedef enum +{ + CG_SPAR_SPLASH, + CG_SPAR_MENU, + CG_SPAR_SCHEDULE, + CG_MATCH_PREP, + CG_SPAR_MATCH, + CG_SPAR_MATCH_RESULTS, + CG_SPAR_TUTORIAL, + CG_SPAR_BATTLE_RECORD, +} cgSparState_t; + +typedef enum +{ + CG_SPAR_UNREADY, ///< Building up for an attack + CG_SPAR_READY, ///< Ready to attack + CG_SPAR_EXHAUSTED, ///< Gain Stamina + CG_SPAR_HIT, ///< State used to animate being hit + CG_SPAR_ATTACK, ///< State used to animate Attacking + CG_SPAR_DODGE_ST, ///< State used to animate dodging + CG_SPAR_NOTHING, ///< State for when teh CHowa do nothing + CG_SPAR_WIN, ///< Once round ends, Animate in victory pose + CG_SPAR_LOSE, ///< Once round ends, Animate in lose pose (Crying?) +} cgMatchChowaState_t; + +typedef enum +{ + CG_SPAR_PUNCH, + CG_SPAR_KICK, + CG_SPAR_FAST_PUNCH, + CG_SPAR_JUMP_KICK, + CG_SPAR_HEADBUTT, + CG_SPAR_DODGE, + CG_SPAR_UNSET, ///< Used to avoid any specific moves being prioritized in readiness system +} cgRPSState_t; + +// Structs ============================== +typedef struct +{ + wsg_t* spr; ///< Image + vec_t startPos; ///< Starting x and y positions + vec_t pos; ///< Position on screen + vec_t speed; ///< Speed +} cgSparBGObject_t; + +typedef struct +{ + cgChowa_t* chowa; ///< Chowa object + cgMatchChowaState_t currState; ///< Current Chowa state + cgRPSState_t currMove; ///< Current selected move + int16_t maxStamina; ///< Max stamina of each Chowa + int16_t stamina; ///< Stamina of both Chowa for stamina bars + int16_t readiness; ///< How ready each Chowa is + int16_t HP; ///< Current HP for this Chowa + int16_t maxHP; ///< Max HP for this Chowa + int32_t updateTimer; ///< Used for readiness updates + bool doneAnimating; ///< Currently animating +} cgSparChowaData_t; + +typedef struct +{ + cgAIDifficulty_t aiDifficulty; ///< Difficulty + bool pickedMove; ///< If a move has already been picked + bool init; ///< Initialized + int8_t movesPicked; ///< Num of move already picked + cgRPSState_t prevState; ///< Last move the AI chose + cgRPSState_t prevMoves[20]; ///< All the previous moves the player has taken + int64_t timer; ///< Timer for button presses +} cgSparAI_t; + +typedef struct +{ + // Data + char matchName[CG_MAX_STR_LEN]; ///< Name of the current match + int8_t round; ///< The round of the fight + + // State + cgSparChowaData_t chowaData[2]; ///< Extended Chowa data + bool paused; ///< If the match is paused + bool online; ///< If match is online + bool resolve; ///< Marks that the match should be resolved + + // AI + cgSparAI_t ai; + + // Match ended + bool done; ///< If match if finished + cgWinLoss_t finalResult; ///< The ultimate result of the match + + // Match time + int64_t usTimer; ///< Microsecond timer + int16_t timer; ///< Round timer + int16_t maxTime; ///< Max time allowed for the round + + // Animations + bool animDone; ///< If Animation is done + bool wasCrit; ///< If Chowa was hit while unready +} cgMatch_t; + +typedef struct +{ + // Assets + // Audio + // - BGM, menus + // - BGM, match + // - Combat sounds + // - Pain sounds + // - Impact sounds + // - Gong crash + // - Countdown noises + // - Cheer noises + + // BG Sprites + wsg_t dojoBG; ///< Dojo Background image + wsg_t* dojoBGItems; ///< Dojo BG items + // UI Sprites + wsg_t arrow; ///< Arrow sprite + // - Punch icon + // - Kick icon + // NPC sprites + + // Fonts + font_t sparTitleFont; ///< Font used for larger text + font_t sparTitleFontOutline; ///< Outline for title font + font_t sparRegFont; ///< Regular text + + // Spar + cgSparState_t state; ///< Active state + int64_t timer; ///< Timer for animations + bool toggle; ///< Toggles on timer + + // Menu + menu_t* sparMenu; ///< Menu object + menuManiaRenderer_t* renderer; ///< Renderer + + // Match + cgMatch_t match; ///< Match object + + // Battle Record + cgRecord_t sparRecord[CG_SPAR_MAX_RECORDS]; ///< List of battle records + int8_t recordSelect; ///< Which record is currently active + int8_t roundSelect; ///< Which round of the record is currently selected + + // Input + + // LEDs + +} cgSpar_t; + +//============================================================================== +// Mode Data +//============================================================================== + +// Defines ============================= + +// Enums =============================== +typedef enum +{ + CG_MAIN_MENU, + CG_GROVE, + CG_SPAR, + CG_RACE, + CG_PERFORMANCE, +} cgMainState_t; + +// Structs ============================= +typedef struct +{ + // Assets + // ======================================================================== + // Fonts + font_t menuFont; ///< Main font + + // WSGs + wsg_t* title; ///< Title screen sprites + wsg_t* chowaWSGs[CG_NUM_TYPES][2]; ///< Chowa sprites + + // Modes + cgGrove_t grove; ///< Garden data + cgSpar_t spar; ///< Spar data + + // State + cgMainState_t state; ///< Main mode state + bool unload; ///< if the state is ready to unload + + // title screen + bool titleActive; ///< If title screen is active + int64_t timer; ///< Timer for animations + vec_t cloudPos; ///< Position of the cloud + int8_t animFrame; ///< Current frame of the animation; + int8_t titleFrame; ///< Frame of title animation + + // Menu + menu_t* menu; ///< Main menu + menuManiaRenderer_t* renderer; ///< Menu renderer + + // Settings + bool touch; ///< Touch controls for Grove + bool online; ///< If online features are enabled + + // Chowa + cgChowa_t chowa[CG_MAX_CHOWA]; ///< List of Chowa + cgChowa_t guests[CG_GROVE_MAX_GUEST_CHOWA]; ///< Guest Chowa +} cGrove_t; \ No newline at end of file diff --git a/main/modes/games/cGrove/mode_cGrove.c b/main/modes/games/cGrove/mode_cGrove.c new file mode 100644 index 000000000..e7d506ba3 --- /dev/null +++ b/main/modes/games/cGrove/mode_cGrove.c @@ -0,0 +1,409 @@ +/** + * @file mode_cGrove.c + * @author Jeremy Stintzcum (Jeremy.Stintzcum@gmail.com) + * @brief A small game similar to the chao garden from the Sonic series by SEGA + * @version 0.1 + * @date 2024-09-07 + * + * @copyright Copyright (c) 2024 + * + */ + +//============================================================================== +// Includes +//============================================================================== + +#include "mode_cGrove.h" +#include "cg_Chowa.h" +#include "cg_Grove.h" +#include "cg_Spar.h" +#include +#include + +//============================================================================== +// Defines +//============================================================================== + +#define CG_FRAMERATE 16667 +#define SECOND 1000000 + +//============================================================================== +// Consts +//============================================================================== + +static const char cGroveTitle[] = "Chowa Grove"; // Game title + +static const char* cGroveMenuNames[] = {"Play with Chowa", "Spar", "Race", "Perform", "Player Profiles", "Settings"}; +static const char* cGroveSettingOpts[] = {"Grove Touch Scroll: ", "Online: "}; +static const char* const cGroveEnabledOptions[] = {"Enabled", "Disabled"}; +static const uint32_t cGroveEnabledVals[] = {true, false}; + +static const char* cGroveTitleSprites[] = {"cg_cloud.wsg", "cg_sky.wsg", + "cg_title_1.wsg", "cg_title_2.wsg", + "cg_title_middle_1.wsg", "cg_title_middle_2.wsg", + "cg_title_middle_3.wsg", "cg_title_middle_4.wsg"}; + +//============================================================================== +// Function declarations +//============================================================================== + +/** + * @brief Constructs the mode + * + */ +static void cGroveEnterMode(void); + +/** + * @brief Deconstructs the mode + * + */ +static void cGroveExitMode(void); + +/** + * @brief Main loop of Chowa Grove + * + * @param elapsedUs + */ +static void cGroveMainLoop(int64_t elapsedUs); + +/** + * @brief Menu callback for the main Chowa Grove menu + * + * @param label + * @param selected + * @param settingVal + */ +static void cg_menuCB(const char* label, bool selected, uint32_t settingVal); + +/** + * @brief Draw title screen + * + * @param elapsedUs Time since I last dunked my head in a bucket of acid + */ +static void cg_titleScreen(int64_t elapsedUs); + +//============================================================================== +// Variables +//============================================================================== + +swadgeMode_t cGroveMode = { + .modeName = cGroveTitle, + .wifiMode = ESP_NOW, + .overrideUsb = false, + .usesAccelerometer = false, + .usesThermometer = false, + .overrideSelectBtn = false, + .fnEnterMode = cGroveEnterMode, + .fnExitMode = cGroveExitMode, + .fnMainLoop = cGroveMainLoop, + .fnAudioCallback = NULL, + .fnBackgroundDrawCallback = NULL, + .fnEspNowRecvCb = NULL, + .fnEspNowSendCb = NULL, + .fnAdvancedUSB = NULL, + .fnDacCb = NULL, +}; + +static cGrove_t* cg = NULL; + +//============================================================================== +// Functions +//============================================================================== + +static void cGroveEnterMode(void) +{ + // Mode memory allocation + cg = heap_caps_calloc(1, sizeof(cGrove_t), MALLOC_CAP_SPIRAM); + setFrameRateUs(CG_FRAMERATE); + + // Load Chowa WSGs + cg_initChowaWSGs(cg); + + // Load a font + loadFont("ibm_vga8.font", &cg->menuFont, true); + + // Load title screen + cg->title = calloc(ARRAY_SIZE(cGroveTitleSprites), sizeof(wsg_t)); + for (int32_t idx = 0; idx < ARRAY_SIZE(cGroveTitleSprites); idx++) + { + loadWsg(cGroveTitleSprites[idx], &cg->title[idx], true); + } + + // Menu + cg->menu = initMenu(cGroveTitle, cg_menuCB); + cg->renderer = initMenuManiaRenderer(NULL, NULL, NULL); + static const paletteColor_t shadowColors[] = {c110, c210, c220, c320, c330, c430, c330, c320, c220, c210}; + led_t ledColor = {.r = 128, .g = 128, .b = 0}; + recolorMenuManiaRenderer(cg->renderer, c115, c335, c000, c110, c003, c004, c220, c335, shadowColors, + ARRAY_SIZE(shadowColors), ledColor); + addSingleItemToMenu(cg->menu, cGroveMenuNames[0]); // Go to Grove + addSingleItemToMenu(cg->menu, cGroveMenuNames[1]); // Go to Spar + addSingleItemToMenu(cg->menu, cGroveMenuNames[2]); // Go to Race + addSingleItemToMenu(cg->menu, cGroveMenuNames[3]); // Go to Performance + addSingleItemToMenu(cg->menu, cGroveMenuNames[4]); // View player profiles + cg->menu = startSubMenu(cg->menu, cGroveMenuNames[5]); // Settings + // FIXME: Load values from NVM + // TODO: Add more settings + addSettingsOptionsItemToMenu(cg->menu, cGroveSettingOpts[0], cGroveEnabledOptions, &cGroveEnabledVals, + ARRAY_SIZE(cGroveEnabledOptions), getScreensaverTimeSettingBounds(), + 0); // Enable/disable touch controls + addSettingsOptionsItemToMenu(cg->menu, cGroveSettingOpts[1], cGroveEnabledOptions, &cGroveEnabledVals, + ARRAY_SIZE(cGroveEnabledOptions), getScreensaverTimeSettingBounds(), + 0); // Enable/disable online functions + cg->menu = endSubMenu(cg->menu); + + // Init + cg->state = CG_MAIN_MENU; + cg->titleActive = true; + + // FIXME: test + for (int i = 0; i < CG_MAX_CHOWA; i++) + { + cg->chowa[i].active = true; + cg->chowa[i].type = CG_KING_DONUT; + switch (esp_random() % 4) + { + case 0: + cg->chowa[i].mood = CG_HAPPY; + break; + case 1: + cg->chowa[i].mood = CG_SAD; + break; + case 2: + cg->chowa[i].mood = CG_ANGRY; + break; + case 3: + cg->chowa[i].mood = CG_CONFUSED; + break; + } + cg->chowa[i].playerAffinity = 101; + } + for (int i = 0; i < CG_GROVE_MAX_GUEST_CHOWA; i++) + { + cg->guests[i].active = true; + cg->guests[i].type = CG_KING_DONUT; + switch (esp_random() % 4) + { + case 0: + cg->guests[i].mood = CG_HAPPY; + break; + case 1: + cg->guests[i].mood = CG_SAD; + break; + case 2: + cg->guests[i].mood = CG_ANGRY; + break; + case 3: + cg->guests[i].mood = CG_CONFUSED; + break; + } + cg->guests[i].playerAffinity = 1; + } +} + +static void cGroveExitMode(void) +{ + // Unload sub modes + switch (cg->state) + { + case CG_GROVE: + { + cg_deInitGrove(cg); + break; + } + case CG_SPAR: + { + cg_deInitSpar(); + break; + } + default: + { + break; + } + } + + // Menu + deinitMenu(cg->menu); + deinitMenuManiaRenderer(cg->renderer); + + // WSGs + for (uint8_t i = 0; i < ARRAY_SIZE(cGroveTitleSprites); i++) + { + freeWsg(&cg->title[i]); + } + free(cg->title); + + // Fonts + freeFont(&cg->menuFont); + + // WSGs + cg_deInitChowaWSGs(cg); + + // Main + free(cg); +} + +static void cGroveMainLoop(int64_t elapsedUs) +{ + // Draw title screen + if (cg->titleActive) + { + buttonEvt_t evt = {0}; + while (checkButtonQueueWrapper(&evt)) + { + if (evt.down) + { + cg->titleActive = false; + } + } + cg_titleScreen(elapsedUs); + return; + } + + // Unload old assets if they're not needed + if (cg->unload) + { + // Resetting back to the menu + switch (cg->state) + { + case CG_GROVE: + { + cg_deInitGrove(cg); + break; + } + case CG_SPAR: + { + cg_deInitSpar(); + break; + } + case CG_RACE: + { + break; + } + case CG_PERFORMANCE: + { + break; + } + + default: + { + // Something went wrong + break; + } + } + + cg->state = CG_MAIN_MENU; + } + + // Switch behaviors based on state + switch (cg->state) + { + case CG_MAIN_MENU: + { + // Menu + buttonEvt_t evt = {0}; + while (checkButtonQueueWrapper(&evt)) + { + cg->menu = menuButton(cg->menu, evt); + } + drawMenuMania(cg->menu, cg->renderer, elapsedUs); + break; + } + case CG_GROVE: + { + // Grove + cg_runGrove(cg, elapsedUs); + break; + } + case CG_SPAR: + { + cg_runSpar(elapsedUs); + break; + } + case CG_RACE: + { + // Race + break; + } + case CG_PERFORMANCE: + { + // Performance + break; + } + default: + { + break; + } + } +} + +static void cg_menuCB(const char* label, bool selected, uint32_t settingVal) +{ + if (selected) + { + if (label == cGroveMenuNames[0]) + { + // Start Grove + cg_initGrove(cg); + cg->state = CG_GROVE; + } + else if (label == cGroveMenuNames[1]) + { + // Start Sparring + cg_initSpar(cg); + cg->state = CG_SPAR; + } + else if (label == cGroveMenuNames[2]) + { + // Start Racing + // TODO: Racing + } + else if (label == cGroveMenuNames[3]) + { + // Start Performing + // TODO: Performances + } + else if (label == cGroveMenuNames[4]) + { + // View saved players + // TODO: View recently interacted players + } + else + { + // Something went wrong + } + } + else if (label == cGroveSettingOpts[0]) + { + // Grove C stick or buttons + cg->touch = settingVal; + } + else if (label == cGroveSettingOpts[1]) + { + // Online on or off + cg->online = settingVal; + } +} + +static void cg_titleScreen(int64_t elapsedUs) +{ + // Update + cg->timer += elapsedUs; + if (cg->timer >= SECOND) + { + cg->timer = 0; + cg->cloudPos.x += 1; + if (cg->cloudPos.x >= TFT_WIDTH) + { + cg->cloudPos.x = -cg->title[0].h; + } + } + cg->animFrame = (cg->animFrame + 1) % 32; + cg->titleFrame = (cg->titleFrame + 1) % 64; + + // Draw + drawWsgSimple(&cg->title[1], 0, 0); + drawWsgSimpleHalf(&cg->title[0], cg->cloudPos.x, -30); + drawWsgSimpleHalf(&cg->title[4 + (cg->animFrame >> 3)], 0, 84); + drawWsgSimpleHalf(&cg->title[2 + (cg->titleFrame >> 5)], 0, 0); +} \ No newline at end of file diff --git a/main/modes/games/cGrove/mode_cGrove.h b/main/modes/games/cGrove/mode_cGrove.h new file mode 100644 index 000000000..2e7cac1e9 --- /dev/null +++ b/main/modes/games/cGrove/mode_cGrove.h @@ -0,0 +1,21 @@ +/** + * @file mode_cGrove.h + * @author Jeremy Stintzcum (Jeremy.Stintzcum@gmail.com) + * @brief A small game similar to the chao garden from the Sonic seres by SEGA + * @version 0.1 + * @date 2024-09-07 + * + * @copyright Copyright (c) 2024 + * + */ + +#pragma once + +//============================================================================== +// Includes +//============================================================================== + +#include "cg_Typedef.h" + +// Make Swadgemode available +extern swadgeMode_t cGroveMode; \ No newline at end of file diff --git a/main/modes/system/mainMenu/mainMenu.c b/main/modes/system/mainMenu/mainMenu.c index 09e4c694f..db42b983b 100644 --- a/main/modes/system/mainMenu/mainMenu.c +++ b/main/modes/system/mainMenu/mainMenu.c @@ -23,6 +23,7 @@ #include "pango.h" #include "sequencerMode.h" #include "soko.h" +#include "mode_cGrove.h" #include "touchTest.h" #include "tunernome.h" #include "keebTest.h" @@ -157,6 +158,7 @@ static void mainMenuEnterMode(void) addSingleItemToMenu(mainMenu->menu, swadgeHeroMode.modeName); addSingleItemToMenu(mainMenu->menu, tttMode.modeName); addSingleItemToMenu(mainMenu->menu, pangoMode.modeName); + addSingleItemToMenu(mainMenu->menu, cGroveMode.modeName); addSingleItemToMenu(mainMenu->menu, t48Mode.modeName); addSingleItemToMenu(mainMenu->menu, bigbugMode.modeName); addSingleItemToMenu(mainMenu->menu, sokoMode.modeName); @@ -386,6 +388,10 @@ static void mainMenuCb(const char* label, bool selected, uint32_t settingVal) { switchToSwadgeMode(&pangoMode); } + else if (label == cGroveMode.modeName) + { + switchToSwadgeMode(&cGroveMode); + } else if (label == timerMode.modeName) { switchToSwadgeMode(&timerMode);