Initial commit

This commit is contained in:
Nacho 2025-05-24 15:14:47 +02:00
parent 043c4640d0
commit c0e829e4ca
56 changed files with 2611 additions and 1 deletions

12
.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
.vscode/
*.bin
*.line
*.cm
__pycache__/
venv/
sprites/

View File

@ -1,2 +1,18 @@
# nacapet # nacapet
Small VPet implementation using the ESP32, MPU6050, ST7789 TFT and a small speaker. Small implementation of a VPet (Virtual Pet) using an ESP32, an MPU6050, a speaker and an ST7789 display.
## Beware
This code has been written in Spanglish, not the best practice, I know. I just wanted to try out something and never planned to release it, so yeah. Sorry!
## Another beware
Graphical assets are missing, you will have to create your own. You can find more information as to how these assets are laid out in the file defs.h.
## Hardware
For this you will need an standard ESP32, an MPU6050, since this also counts steps, a speaker, a 240x240 TFT and 4 buttons. There is one display in AliExpress which also contains the buttons too in the display unit. You can see more information on how to connect all the devices in the `defs.h` in exception of the display, which has to be configured manually through TFT_eSPI.
## Assets
Assets are composed by 16x16 sprites. can be laid out in a PNG file all together. Once the assets are created in the PNG file, they have to be converted into an RGB565 following the data strucutre this thing wants. For that, check out `scripts/png_to_rgb565.py`. Aside from that you have both the menu assets and the ui assets (menu.bin and ui.bin). menu.bin is composed by 16x16 sprites, ui.bin is composed by 8x8 sprites. Then the background layer (bg.bin) there is a script for that one inside the scripts folder. Background ideally has to be 90x90, otherwise I will be running out of memory real quick. Finally the main character `sprite.bin`, which follows the same structure as the menu sprites, and can be generated through the same script.

37
include/README Normal file
View File

@ -0,0 +1,37 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the convention is to give header files names that end with `.h'.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

46
lib/README Normal file
View File

@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into the executable file.
The source code of each library should be placed in a separate directory
("lib/your_library_name/[Code]").
For example, see the structure of the following example libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
Example contents of `src/main.c` using Foo and Bar:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
The PlatformIO Library Dependency Finder will find automatically dependent
libraries by scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

104
linecreator/create_line.py Normal file
View File

@ -0,0 +1,104 @@
import re
import sys
import logging
def process_npet_file(origFp, outputName):
file_header = origFp.readline()
matches = re.findall(r"\[\s*([0-9a-fA-F]+)\s*\|\s*([^|]+?)\s*\|\s*([0-9a-fA-F]+)\s*\]", file_header)
if len(matches[0]) != 3:
logging.error("Cabecera invalida")
return
id, nombre, numChara = matches[0]
id = int(id, 16)
numChara = int(numChara, 16)
logging.info(f"Encontrado cabecera id={id}, nombre={nombre}, numchara={numChara}")
destFp = open(outputName, "wb")
destFp.write(b"NPET")
destFp.write(id.to_bytes(1, "big"))
destFp.write(nombre.encode("utf8").ljust(16, b"\0"))
destFp.write(numChara.to_bytes(1, "big"))
for i in range(numChara):
line = origFp.readline()
matches = re.findall(
r"\[\s*"
r"([0-9a-fA-F]+)\s*\|\s*"
r"([^|]+?)\s*\|\s*"
r"([0-9a-fA-F]+)\s*\|\s*"
r"([0-9a-fA-F]+)\s*\|\s*"
r"([0-9a-fA-F]+)\s*\|\s*"
r"([0-9a-fA-F]+)\s*\|\s*"
r"([0-9a-fA-F]+)\s*\|\s*"
r"([0-9a-fA-F]+)\s*\|\s*"
r"([0-9a-fA-F]+)\s*\|\s*"
r"([0-9a-fA-F]+)\s*\|\s*"
r"([0-9a-fA-F]+)\s*\|\s*"
r"([0-9a-fA-F]+)\s*\|\s*"
r"([0-9a-fA-F]+)\s*"
r"\]$", line
)
if len(matches[0]) != 13:
logging.error("Estructura incorrecta, necesita 13 parametros.")
return
charaId = int(matches[0][0], 16)
charaName = matches[0][1].encode("utf-8").ljust(16, b"\0")
charaHp = int(matches[0][2], 16)
charaBp = int(matches[0][3], 16)
charaAp = int(matches[0][4], 16)
charaStage = int(matches[0][5], 16)
charaAttribute = int(matches[0][6], 16)
charaAttackSprite = int(matches[0][7], 16)
charaSleepTime = int(matches[0][8], 16)
charaWakeupTime = int(matches[0][9], 16)
charaEvolutionTime = int(matches[0][10], 16)
charaReductionTime = int(matches[0][11], 16)
charaMinWeight = int(matches[0][12], 16)
destFp.write(charaId.to_bytes(1, "big"))
destFp.write(charaName)
destFp.write(charaHp.to_bytes(1, "big"))
destFp.write(charaBp.to_bytes(1, "big"))
destFp.write(charaAp.to_bytes(1, "big"))
destFp.write(charaStage.to_bytes(1, "big"))
destFp.write(charaAttribute.to_bytes(1, "big"))
destFp.write(charaAttackSprite.to_bytes(1, "big"))
destFp.write(charaSleepTime.to_bytes(4, "big"))
destFp.write(charaWakeupTime.to_bytes(4, "big"))
destFp.write(charaEvolutionTime.to_bytes(4, "big"))
destFp.write(charaReductionTime.to_bytes(2, "big"))
destFp.write(charaMinWeight.to_bytes(1, "big"))
logging.info(f"Añadiendo nueva entrada con nombre {matches[0][1]}")
destFp.close()
logging.info("Creado archivo de manera correcta")
if __name__ == "__main__":
if len(sys.argv) < 3:
logging.info(f"Uso: {sys.argv[0]} <nombre archivo texto plano> <archivo salida>")
sys.exit(-1)
origFile, newFile = sys.argv[1:]
logging.basicConfig(level=0, format="%(levelname)s - %(message)s")
origFp = open(origFile, "r")
header = origFp.readline()
if "NPET" in header:
logging.info("Cabecera NPET encontrada!")
process_npet_file(origFp, newFile)
else:
logging.error("No se ha encontrado una cabecera válida.")
sys.exit(0)

20
platformio.ini Normal file
View File

@ -0,0 +1,20 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
upload_port = /dev/ttyUSB0
upload_speed = 921600
monitor_speed = 115200
monitor_port = /dev/ttyUSB0
monitor_filters = esp32_exception_decoder
lib_deps = TFT_eSPI, fbiego/ESP32Time@^2.0.6, electroniccats/MPU6050@^1.4.3

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
pillow

54
scripts/bg_to_rgb565.py Normal file
View File

@ -0,0 +1,54 @@
from PIL import Image
def load_image(filename: str) -> Image.Image:
image = Image.open(filename)
image = image.convert("RGB")
return image
def convert_img_to_rgb565(image: Image.Image) -> bytearray:
width, height = image.size
rgb565_data = bytearray()
for y in range(height):
for x in range(width):
r, g, b = image.getpixel((x, y))
r = (r >> 3) & 0x1F
g = (g >> 2) & 0x3F
b = (b >> 3) & 0x1F
high_byte = (r << 3) | (g >> 3)
low_byte = ((g << 5) | b) & 0xFF
rgb565_data.append(high_byte)
rgb565_data.append(low_byte)
return width, height, rgb565_data
def save_rgb565(filename: str, rgb565_data: list[bytearray], width: int, height: int):
with open(filename, "wb") as f:
## A lo mejor cambio esta cabecera? ns
f.write(width.to_bytes(1, "little"))
f.write(height.to_bytes(1, "little"))
f.write(rgb565_data)
f.close()
if __name__ == "__main__":
import sys
if len(sys.argv) != 3:
print("Usage: python bg_to_rgb565.py <input_image> <output_file>")
sys.exit(1)
input_image = sys.argv[1]
output_file = sys.argv[2]
image = load_image(input_image)
width, height, rgb565_data = convert_img_to_rgb565(image)
save_rgb565(output_file, rgb565_data, width, height)

85
scripts/png_to_rgb565.py Normal file
View File

@ -0,0 +1,85 @@
from PIL import Image
import sys
def split_image(filename: str) -> list[bytearray]:
image = Image.open(filename)
image = image.convert("RGBA")
width, height = image.size
n_tiles_x = width // 16
n_tiles_y = height // 16
n_tiles = n_tiles_x * n_tiles_y
tiles = []
for i in range(n_tiles):
x = (i % n_tiles_x) * 16
y = (i // n_tiles_x) * 16
w = x + 16
h = y + 16
tile = image.crop((x, y, w, h))
tile.save(f"tile_{i}.png")
tiles.append(tile)
return tiles
def convert_image(tiles: list) -> tuple[list[bytearray], int, int]:
width, height = tiles[0].size
rgb565_tiles = []
for img in tiles:
rgb565_data = bytearray()
for y in range(height):
for x in range(width):
r, g, b, a = img.getpixel((x, y))
r = (r >> 3) & 0x1F
g = (g >> 2) & 0x3F
b = (b >> 3) & 0x1F
if a < 255:
high_byte = 0x01
low_byte = 0x20
else:
high_byte = (r << 3) | (g >> 3)
low_byte = ((g << 5) | b) & 0xFF
rgb565_data.append(high_byte)
rgb565_data.append(low_byte)
rgb565_tiles.append(rgb565_data)
return rgb565_tiles, width, height
def save_rgb565(filename: str, rgb565_data: list[bytearray], width: int, height: int):
with open(filename, "wb") as f:
## Y esta cabecera tambien
f.write(width.to_bytes(1, "little"))
f.write(height.to_bytes(1, "little"))
f.write(len(rgb565_data).to_bytes(1, "little"))
for rgb565_tile in enumerate(rgb565_data):
f.write(rgb565_tile[1])
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Usage: python png_to_rgb565.py <input.png> <output.rgb565>")
sys.exit(1)
filename = sys.argv[1]
output_filename = sys.argv[2]
tiles = split_image(filename)
rgb565_data, width, height = convert_image(tiles)
save_rgb565(output_filename, rgb565_data, width, height)
print(f"Converted {filename} to {output_filename} with dimensions {width}x{height}.")

View File

@ -0,0 +1,63 @@
#ifndef ANIMATIONS_H
#define ANIMATIONS_H
#include <TFT_eSPI.h>
const int numFrames = 39;
const int numFramesAngry = 2;
const int numFramesEating = 2;
const int numFramesHappy = 2;
const int numFramesRefuse = 4;
const int numFramesSleepy = 2;
// TODO: Cambiar a una animación mas atractiva
const int animationFrames[numFrames] = {
0, 1, 0, 1, 0, 1, 0, 1, 0, 7, 3, 7,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 7, 3, 7,
1, 0, 1, 0, 1, 0, 1
};
// Asumiendo que el buffer es 240x240, dejando margen y los sprites son 96x96 (6 de factor)
const int animationPositions[] = {
72, 66, 60, 54, 48, 42, 36, 30, 24, 18, 24, 18,
24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96, 102, 108, 114, 120, 126, 120, 126,
114, 108, 102, 96, 90, 84, 78
};
const bool animationFlipSprites[numFrames] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0
};
const int angryAnimationFrames[] = {
8, 9
};
const int eatingAnimationFrames[] = {
3, 7
};
const int refuseAnimationFrames[] = {
6, 6, 6, 6
};
const int refuseAnimationFlips[] = {
0, 1, 0, 1
};
const int sleepyAnimationFrames[] = {
4, 5
};
const int happyAnimationFrames[] = {
3, 7
};
void animate_performAnimation(TFT_eSprite &buffer, TFT_eSprite &spr, struct SpriteData* spriteData, uint8_t offsetX);
void animate_performAttentionAnimation(TFT_eSprite &buffer, TFT_eSprite &spr, struct SpriteData* spriteData);
void animate_performEatingAnimation(TFT_eSprite &buffer, TFT_eSprite &spr, struct SpriteData* spriteData);
void animate_performRefuseAnimation(TFT_eSprite &buffer, TFT_eSprite &spr, struct SpriteData* spriteData);
void animate_performSleepyAnimation(TFT_eSprite &buffer, TFT_eSprite &spr, struct SpriteData* spriteData);
void animate_performHappyAnimation(TFT_eSprite &buffer, TFT_eSprite &spr, struct SpriteData* spriteData);
#endif

View File

@ -0,0 +1,95 @@
#include "animations.h"
#include "draw/draw.h"
#include "display/display.h"
#include "defs/sprite_data.h"
const char* TAG_A = "[ANIMATION]";
const int spriteHeightOnScreen = 72;
int currentAnimationFrame = 0;
void animate_performAnimation(TFT_eSprite &buffer, TFT_eSprite &spr, struct SpriteData* spriteData, uint8_t offsetX) {
draw_drawSprite(
buffer,
spr,
animationPositions[currentAnimationFrame] - offsetX,
spriteHeightOnScreen,
spriteData,
animationFrames[currentAnimationFrame],
6,
animationFlipSprites[currentAnimationFrame]
);
currentAnimationFrame = (currentAnimationFrame + 1) % numFrames;
}
void animate_performAttentionAnimation(TFT_eSprite &buffer, TFT_eSprite &spr, struct SpriteData* spriteData) {
currentAnimationFrame = (currentAnimationFrame + 1) % numFramesAngry;
draw_drawSprite(
buffer,
spr,
spriteHeightOnScreen,
spriteHeightOnScreen,
spriteData,
angryAnimationFrames[currentAnimationFrame],
6,
animationFlipSprites[currentAnimationFrame]
);
}
void animate_performEatingAnimation(TFT_eSprite &buffer, TFT_eSprite &spr, struct SpriteData* spriteData) {
currentAnimationFrame = (currentAnimationFrame + 1) % numFramesEating;
draw_drawSprite(
buffer,
spr,
spriteHeightOnScreen,
spriteHeightOnScreen,
spriteData,
eatingAnimationFrames[currentAnimationFrame],
6,
false
);
}
void animate_performRefuseAnimation(TFT_eSprite &buffer, TFT_eSprite &spr, struct SpriteData* spriteData) {
currentAnimationFrame = (currentAnimationFrame + 1) % numFramesRefuse;
draw_drawSprite(
buffer,
spr,
spriteHeightOnScreen,
spriteHeightOnScreen,
spriteData,
refuseAnimationFrames[currentAnimationFrame],
6,
refuseAnimationFlips[currentAnimationFrame]
);
}
void animate_performSleepyAnimation(TFT_eSprite &buffer, TFT_eSprite &spr, struct SpriteData* spriteData) {
currentAnimationFrame = (currentAnimationFrame + 1) % numFramesSleepy;
draw_drawSprite(
buffer,
spr,
spriteHeightOnScreen,
spriteHeightOnScreen,
spriteData,
sleepyAnimationFrames[currentAnimationFrame],
6,
false
);
}
void animate_performHappyAnimation(TFT_eSprite &buffer, TFT_eSprite &spr, struct SpriteData* spriteData) {
currentAnimationFrame = (currentAnimationFrame + 1) % numFramesHappy;
draw_drawSprite(
buffer,
spr,
spriteHeightOnScreen,
spriteHeightOnScreen,
spriteData,
happyAnimationFrames[currentAnimationFrame],
6,
false
);
}

56
src/buttons/buttons.cpp Normal file
View File

@ -0,0 +1,56 @@
#include "defs/defs.h"
#include "buttons.h"
#include <Arduino.h>
uint64_t lastPressedButtonTime = esp_timer_get_time();
bool inactive = false;
bool screenOff = false;
bool k1_prev = HIGH;
bool k2_prev = HIGH;
bool k3_prev = HIGH;
bool k4_prev = HIGH;
void buttons_checkInactivity() {
uint64_t currentTime = esp_timer_get_time();
if (currentTime - lastPressedButtonTime > INACTIVITY_THRESHOLD_TIME_US && !screenOff) {
digitalWrite(BL_PIN, LOW);
screenKey = OFF_SCREEN;
screenOff = true;
} else if (currentTime - lastPressedButtonTime > LAST_PRESSED_BUTTON_THRESHOLD_TIME_US && !inactive) {
screenKey = IDLE_SCREEN;
inactive = true;
}
}
uint8_t buttons_getPressedButtons() {
bool k1_current = digitalRead(K1_PIN);
bool k2_current = digitalRead(K2_PIN);
bool k3_current = digitalRead(K3_PIN);
bool k4_current = digitalRead(K4_PIN);
uint8_t retV = (
(k1_prev == HIGH && k1_current == LOW) << 3 |
(k2_prev == HIGH && k2_current == LOW) << 2 |
(k3_prev == HIGH && k3_current == LOW) << 1 |
(k4_prev == HIGH && k4_current == LOW)
);
if (retV != 0) {
tone(SPK_PIN, BEEP_FREQ_HZ, BEEP_LEN_MS);
lastPressedButtonTime = esp_timer_get_time();
inactive = false;
screenOff = false;
}
k1_prev = k1_current;
k2_prev = k2_current;
k3_prev = k3_current;
k4_prev = k4_current;
delay(15);
return retV;
}

7
src/buttons/buttons.h Normal file
View File

@ -0,0 +1,7 @@
#ifndef BUTTONS_H
#define BUTTONS_H
uint8_t buttons_getPressedButtons();
void buttons_checkInactivity();
#endif

21
src/debug/debug.cpp Normal file
View File

@ -0,0 +1,21 @@
#include "debug.h"
#include <freertos/FreeRTOS.h>
void debug_printFreeMemory() {
size_t freeMemory = esp_get_free_heap_size();
printf("[DEBUG] Free memory: %zu bytes\n", freeMemory);
}
void debug_printAllSprites(uint16_t** sprite, uint8_t numSprites, uint8_t width, uint8_t height) {
for (uint8_t i = 0; i < numSprites; i++) {
printf("[DEBUG] Sprite %d:\n", i);
for (uint8_t y = 0; y < height; y++) {
printf("[DEBUG] Row %d: ", y);
for (uint8_t x = 0; x < width; x++) {
printf("%04X ", sprite[i][y * width + x]);
}
printf("\n");
}
}
}

9
src/debug/debug.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef DEBUG_H
#define DEBUG_H
#include <stdint.h>
void debug_printFreeMemory();
void debug_printAllSprites(uint16_t** sprite, uint8_t numSprites, uint8_t width, uint8_t height);
#endif

View File

@ -0,0 +1,13 @@
#ifndef BACKGROUND_DATA_H
#define BACKGROUND_DATA_H
#include <stdint.h>
struct BackgroundData {
uint8_t backgroundWidth;
uint8_t backgroundHeight;
uint8_t scaleFactor;
uint16_t* backgroundData;
};
#endif

50
src/defs/chara_data.h Normal file
View File

@ -0,0 +1,50 @@
#ifndef CHARA_DATA_H
#define CHARA_DATA_H
#include <stdint.h>
struct CharacterData {
// Calculated at runtime
uint8_t hunger;
uint8_t strength;
uint8_t effort;
uint8_t careMistakes;
uint8_t weight;
uint8_t age;
uint8_t poopNumber;
int32_t sleepCareMistakeCounter = 0;
int32_t evoLeftTimer;
int16_t hungerCareMistakeTimer;
int16_t strengthCareMistakeTimer;
bool gotLifeYearAdded = false;
bool hungerCareMistakeObtained = false;
bool strengthCareMistakeObtained = false;
bool sleepCareMistakeObtained = false;
bool careMistakeCallLight = false;
bool sleepy = false;
bool asleep = false;
bool injured = false;
bool dead = false;
// Obtained from structure
uint8_t idLine;
uint8_t idChara;
char charaName[40];
uint8_t hp;
uint8_t ap;
uint8_t bp;
uint8_t stage;
uint8_t attribute;
uint32_t sleepTime = 75600;
uint32_t wakeupTime = 25200;
uint32_t evoTime;
uint16_t initialStatsReductionTime = 600;
};
#endif

162
src/defs/defs.h Normal file
View File

@ -0,0 +1,162 @@
#ifndef DEFS_H
#define DEFS_H
#include <ESP32Time.h>
#include <MPU6050.h>
#include "defs/background_data.h"
#include "defs/sprite_data.h"
#define VERSION "Alpha v0.1"
// SCREEN PINOUT
#define SCL_PIN 34
#define SDA_PIN 35
#define RST_PIN 32
#define BL_PIN 25
// BUTTONS PINOUT
#define K1_PIN 33
#define K2_PIN 35
#define K3_PIN 34
#define K4_PIN 32
// SPEAKER PINOUT
#define SPK_PIN 21
// MPU6050 PINOUT
#define MPU_SCL_PIN 4
#define MPU_SDA_PIN 16
#define MPU_INT_PIN 12
// SPECIAL SCREEN THAT OPENS WHEN TIMERS ARE DONE
// RECEIVES AN EXTRA PARAMETER (INTERRUPTKEY)
#define TIMER_FINISHED_SCREEN 999
// ANIMATION FRAMERATE DEFINITION IN µS
#define ANIMATION_THRESHOLD_TIME_US 500000
#define ANIMATION_SLEEPY_THRESHOLD_TIME_US 2500000
// INACTIVITY TIMERS (LAST TIME A BUTTON WAS PRESSED)
#define LAST_PRESSED_BUTTON_THRESHOLD_TIME_US 10000000
#define INACTIVITY_THRESHOLD_TIME_US 30000000
// RTC TIMEOUT WHEN TIME NOT DEFINED
#define RTC_TIMEOUT_THRESHOLD_TIME_MS 100
// STANDARD BEEP WHEN PRESSING BUTTON
#define BEEP_FREQ_HZ 3000
#define BEEP_LEN_MS 50
// MENU ENTRIES
#define STATUS_SCREEN_MENU 0
#define FOOD_SCREEN_MENU 1
#define TRAIN_SCREEN_MENU 2
#define BATTLE_SCREEN_MENU 3
#define POOP_SCREEN_MENU 4
#define MEDICAL_SCREEN_MENU 5
#define SLEEP_SCREEN_MENU 6
#define SETTINGS_SCREEN_MENU 7
// SCREENS THAT OPEN AFTER CLICKING ON A MENU ENTRY
#define STATUS_SCREEN 10
#define FOOD_SCREEN 11
#define TRAIN_SCREEN 12
#define BATTLE_SCREEN 13
#define CLEAR_POOP_SCREEN 14
#define MEDICAL_SCREEN 15
#define SLEEP_SCREEN 16
#define SETTINGS_SCREEN 17
// ICONS FOR EACH MENU ENTRY (MENU.BIN)
#define STATUS_SCREEN_ICON 0
#define FOOD_SCREEN_ICON 1
#define TRAIN_SCREEN_ICON 2
#define BATTLE_SCREEN_ICON 3
#define CLEAR_POOP_ICON 4
#define MEDICAL_SCREEN_ICON 5
#define SLEEP_SCREEN_ICON 6
#define SETTINGS_SCREEN_ICON 7
#define CARE_MISTAKE_CALL_LIGHT 8
#define BED_SPRITE 9
// SCREENS
#define OFF_SCREEN -1
#define TITLE_SCREEN 0
#define CLOCK_EDIT_SCREEN 1
#define CLOCK_SCREEN 2
#define IDLE_SCREEN 3
#define MENU_SCREEN 4
#define FEEDING_SCREEN 20
#define REFUSING_SCREEN 21
#define SLEEPY_SCREEN 22
#define CARE_MISTAKE_SCREEN 23
#define POOPING_SCREEN 24
#define HAPPY_SCREEN 25
// SMALL UI ICONS (UI.BIN)
#define POOP_ICON 0
#define FOOD_ICON 1
#define PILL_ICON 2
#define ZZZ_ICON 3
#define AGE_ICON 4
#define SCALE_ICON 5
#define ARROW_ICON 6
#define FIREWORKS_ICON 7
#define COMPLAIN_ICON 8
#define FULL_HEART_ICON 9
#define EMPTY_HEART_ICON 10
#define CLEANER_ICON 11
// STANDARD VPET PARAMETER (CARE MISTAKES)
#define CARE_MISTAKE_COUNTER_MAX 60
#define SLEEP_CARE_MISTAKE_COUNTER_MAX 60
#define SLEEP_COUNTER_MAX 120
// ATTACK PATTERNS
#define ATTACK_PATTERN_MEDIOCRE 0
#define ATTACK_PATTERN_BAD 1
#define ATTACK_PATTERN_GOOD 2
#define ATTACK_PATTERN_GREAT 3
#define ATTACK_PATTERN_EXCELLENT 4
// BUTTON PRESSES DEFINITIONS
#define K1_PRESSED 8
#define K2_PRESSED 4
#define K3_PRESSED 2
#define K4_PRESSED 1
#define NONE_PRESSED 0
extern int screenKey;
extern int menuKey;
extern int submenuKey;
extern int interruptKey;
extern bool k1_prev;
extern bool k2_prev;
extern bool k3_prev;
extern bool k4_prev;
extern bool inactive;
extern bool screenOff;
extern uint64_t lastPressedButtonTime;
extern uint64_t lastUpdateTime;
extern uint64_t lastBeepTime;
extern struct CharacterData charaData;
extern struct tm timeInfo;
extern uint32_t dayUnixTime;
extern ESP32Time rtc;
extern MPU6050 mpu;
extern hw_timer_t *actionTimerDelta;
extern bool runVpetTasks;
extern uint8_t beepCounter;
extern uint16_t stepCounter;
#endif

16
src/defs/evo_data.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef EVO_DATA_H
#define EVO_DATA_H
#include <stdint.h>
struct EvoData {
uint8_t charaId;
uint8_t charaNextId;
uint8_t maxCareMistakes;
uint8_t maxSleepInterruptions;
uint8_t maxOverfeed;
uint8_t minWonBattles;
uint8_t minTotalBattles;
};
#endif

13
src/defs/sprite_data.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef SPRITE_DATA_H
#define SPRITE_DATA_H
#include <stdint.h>
struct SpriteData {
uint8_t spriteWidth;
uint8_t spriteHeight;
uint8_t spriteNumber;
uint16_t** spriteData;
};
#endif

30
src/display/display.cpp Normal file
View File

@ -0,0 +1,30 @@
#include "display.h"
void tft_initDisplay(TFT_eSPI &tft, uint16_t color) {
tft.init();
tft.setRotation(0);
tft.fillScreen(TFT_RED);
}
void tft_initScreenBuffer(TFT_eSprite &buffer, uint16_t color) {
buffer.createSprite(SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1);
buffer.fillSprite(color);
buffer.setTextColor(TFT_BLACK);
buffer.setTextSize(4);
buffer.pushSprite(0, 0);
}
void tft_drawBuffer(TFT_eSprite &buffer) {
buffer.pushSprite(0, 0);
}
void tft_clearBuffer(TFT_eSprite &buffer, uint16_t color) {
buffer.fillSprite(color);
}
void tft_drawCenteredText(TFT_eSprite &buffer, const char* text, int factor, int y) {
size_t textWidth = strlen(text) * factor * 6;
int x = (SCREEN_WIDTH - textWidth) / 2;
buffer.setTextSize(factor);
buffer.drawString(text, x, y);
}

15
src/display/display.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef DISPLAY_H
#define DISPLAY_H
#include <TFT_eSPI.h>
const int SCREEN_WIDTH = 240;
const int SCREEN_HEIGHT = 240;
void tft_initDisplay(TFT_eSPI &tft, uint16_t color = TFT_WHITE);
void tft_initScreenBuffer(TFT_eSprite &spr, uint16_t color = TFT_WHITE);
void tft_drawBuffer(TFT_eSprite &buffer);
void tft_clearBuffer(TFT_eSprite &buffer, uint16_t color = TFT_WHITE);
void tft_drawCenteredText(TFT_eSprite &buffer, const char* text, int factor, int y);
#endif

18
src/draw/draw.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef DRAW_H
#define DRAW_H
#include <TFT_eSPI.h>
void draw_drawSprite(
TFT_eSprite &buffer, TFT_eSprite &tft,
int x, int y,
struct SpriteData* spriteData, uint8_t spriteNumber,
uint8_t factor, bool flipHorizontal = false
);
void draw_drawBackground(TFT_eSprite &buffer, TFT_eSprite &bg, int spr_w, int spr_h, int factor);
void draw_drawSpriteCentered(
TFT_eSprite &buffer, TFT_eSprite &spr,
struct SpriteData* spriteData, uint8_t spriteNumber, uint8_t factor, bool flipped = false, int y = -1
);
#endif

View File

@ -0,0 +1,19 @@
#include "draw.h"
const char* TAG_DB = "[DRAW BG]";
void draw_drawBackground(TFT_eSprite &buffer, TFT_eSprite &bg, int spr_w, int spr_h, int factor) {
int scaledWidth = spr_w * factor;
int scaledHeight = spr_h * factor;
for (int sy = 0; sy < scaledHeight; sy++) {
for (int sx = 0; sx < scaledWidth; sx++) {
int srcX = sx / factor;
int srcY = sy / factor;
uint16_t color = bg.readPixel(srcX, srcY);
buffer.drawPixel(sx, sy, color);
}
}
}

54
src/draw/draw_sprites.cpp Normal file
View File

@ -0,0 +1,54 @@
#include "draw.h"
#include "defs/sprite_data.h"
const char* TAG_D = "[DRAW]";
void draw_drawSprite(
TFT_eSprite &buffer, TFT_eSprite &spr, int x, int y,
struct SpriteData* spriteData, uint8_t spriteNumber, uint8_t factor, bool flipHorizontal
) {
int scaledWidth = spriteData->spriteWidth * factor;
int scaledHeight = spriteData->spriteHeight * factor;
spr.createSprite(scaledWidth, scaledHeight);
for (int sy = 0; sy < scaledHeight; sy++) {
for (int sx = 0; sx < scaledWidth; sx++) {
int srcX = sx / factor;
if (flipHorizontal) {
srcX = (spriteData->spriteWidth - 1) - srcX;
}
int srcY = sy / factor;
uint16_t color = spriteData->spriteData
[spriteNumber]
[srcY * spriteData->spriteWidth + srcX];
spr.drawPixel(sx, sy, color);
}
}
spr.pushToSprite(&buffer, x, y, TFT_TRANSPARENT);
//printf("%s: Sprite %d drawn at (%d, %d) %s\n", TAG_D, spriteNumber, x, y, (flipHorizontal ? "flipped" : ""));
}
void draw_drawSpriteCentered(
TFT_eSprite &buffer, TFT_eSprite &spr,
struct SpriteData* spriteData, uint8_t spriteNumber, uint8_t factor, bool flipped, int y
) {
int x = (TFT_WIDTH - (spriteData->spriteWidth * factor)) / 2;
int new_y;
if (y == -1) {
new_y = (TFT_HEIGHT - (spriteData->spriteHeight * factor)) / 2;
} else {
new_y = y;
}
draw_drawSprite(
buffer, spr, x, new_y,
spriteData, spriteNumber, factor, flipped
);
}

161
src/main.cpp Normal file
View File

@ -0,0 +1,161 @@
#include <Arduino.h>
#include "display/display.h"
#include "memory/memory.h"
#include "storage/storage.h"
#include "animations/animations.h"
#include "debug/debug.h"
#include "defs/defs.h"
#include "defs/chara_data.h"
#include "menu/menu.h"
#include "buttons/buttons.h"
#include "vpet/vpet.h"
#include "vpet/steps/steps.h"
const char* TAG = "[MAIN]";
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite composite = TFT_eSprite(&tft);
TFT_eSprite sprite = TFT_eSprite(&tft);
TFT_eSprite bg = TFT_eSprite(&tft);
struct BackgroundData backgroundData;
struct SpriteData mainCharacterSprites;
struct SpriteData menuElementsData;
struct SpriteData uiElementsData;
struct CharacterData charaData;
int screenKey = TITLE_SCREEN;
int menuKey = STATUS_SCREEN_MENU;
int submenuKey = -1;
uint32_t dayUnixTime = 0;
uint16_t stepCounter = 0;
struct tm timeInfo;
MPU6050 mpu;
ESP32Time rtc(0);
TaskHandle_t secondLoop = NULL;
void loop2();
void secondCoreTask(void*);
void setup() {
Serial.begin(115200);
delay(100); // Give MPU6050 and ESP32 time to power up
Wire.begin(MPU_SDA_PIN, MPU_SCL_PIN); // I2C init before MPU6050
mpu.initialize();
tft_initDisplay(tft, TFT_BLACK);
tft_initScreenBuffer(composite, TFT_BLACK);
storage_init();
storage_readFile("/sprite.bin", &mainCharacterSprites);
storage_readFile("/menu.bin", &menuElementsData);
storage_readFile("/ui.bin", &uiElementsData);
storage_initBackground("/bg.bin", bg);
debug_printFreeMemory();
pinMode(K1_PIN, INPUT_PULLUP);
pinMode(K2_PIN, INPUT_PULLUP);
pinMode(K3_PIN, INPUT_PULLUP);
pinMode(K4_PIN, INPUT_PULLUP);
charaData.hunger = 4;
charaData.strength = 4;
charaData.effort = 4;
charaData.evoLeftTimer = 60;
charaData.hungerCareMistakeTimer = 60;
charaData.strengthCareMistakeTimer = 60;
xTaskCreatePinnedToCore(secondCoreTask, "VPET_EVAL", 4096, NULL, 0, &secondLoop, 0);
}
void loop() {
switch (screenKey) {
case TITLE_SCREEN:
menu_drawTitle(composite, bg);
break;
case CLOCK_EDIT_SCREEN:
menu_drawClockEdit(composite, bg);
break;
case CLOCK_SCREEN:
menu_drawClock(composite, bg, menuKey);
break;
case IDLE_SCREEN:
menu_drawIdleScreen(composite, bg, sprite, &mainCharacterSprites, &menuElementsData, &uiElementsData);
break;
case MENU_SCREEN:
menu_drawCurrentMenuOption(composite, bg, sprite, &menuElementsData);
break;
case STATUS_SCREEN:
menu_statusScreen(composite, bg, sprite, &uiElementsData, &charaData);
break;
case OFF_SCREEN:
menu_offScreen(composite);
break;
case TIMER_FINISHED_SCREEN:
menu_timerFinishedScreen(composite, bg, sprite, &mainCharacterSprites);
break;
case FOOD_SCREEN:
menu_foodScreen(composite, bg, sprite, &uiElementsData);
break;
case FEEDING_SCREEN:
menu_feedingScreen(composite, bg, sprite, &uiElementsData, &mainCharacterSprites, submenuKey);
break;
case REFUSING_SCREEN:
menu_refuseScreen(composite, bg, sprite, &mainCharacterSprites);
break;
case SLEEPY_SCREEN:
menu_sleepyScreen(composite, bg, sprite, &mainCharacterSprites, &menuElementsData);
break;
case SLEEP_SCREEN:
menu_sleepingScreen(composite, bg, sprite, &mainCharacterSprites, &menuElementsData, &uiElementsData);
break;
case CARE_MISTAKE_SCREEN:
menu_careMistakeScreen(composite, bg, sprite, &mainCharacterSprites, &menuElementsData);
break;
case POOPING_SCREEN:
menu_poopScreen(composite, bg, sprite, &mainCharacterSprites, &uiElementsData, &menuElementsData);
break;
case CLEAR_POOP_SCREEN:
menu_clearPoopScreen(composite, bg, sprite, &mainCharacterSprites, &menuElementsData, &uiElementsData);
break;
case HAPPY_SCREEN:
menu_drawHappyScreen(composite, bg, sprite, &mainCharacterSprites, &uiElementsData);
break;
}
}
void loop2() {
steps_countSteps();
buttons_checkInactivity();
vpet_runVpetTasks();
}
void secondCoreTask(void*) {
for (;;) { loop2(); }
}

41
src/memory/memory.cpp Normal file
View File

@ -0,0 +1,41 @@
#include "memory.h"
const char* TAG_M = "[MEMORY]";
uint16_t** memory_allocate(uint8_t numSprite, uint8_t width, uint8_t height) {
uint16_t** ptr = (uint16_t**) malloc(numSprite * sizeof(uint16_t*));
if (ptr == NULL) {
printf("%s Memory allocation failed\n", TAG_M);
return NULL;
}
for (uint8_t i = 0; i < numSprite; i++) {
ptr[i] = (uint16_t*) malloc(width * height * sizeof(uint16_t));
if (ptr[i] == NULL) {
printf("%s Memory allocation failed for sprite %d\n", TAG_M, i);
for (uint8_t j = 0; j < i; j++) {
free(ptr[j]);
}
free(ptr);
return NULL;
}
}
printf("%s Allocated %zu bytes at %p\n", TAG_M, numSprite * (width * height * 2), ptr);
return ptr;
}
void memory_free(uint8_t* ptr) {
if (ptr != NULL) {
free(ptr);
ptr = NULL;
} else {
printf("%s Pointer is already NULL\n", TAG_M);
}
}

18
src/memory/memory.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef MEMORY_H
#define MEMORY_H
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
struct spriteData {
uint8_t width;
uint8_t height;
uint8_t number;
uint16_t** data;
};
uint16_t** memory_allocate(uint8_t numSprite, uint8_t width, uint8_t height);
void memory_free(uint8_t* ptr);
#endif

View File

@ -0,0 +1,42 @@
#include "menu.h"
#include "buttons/buttons.h"
#include "animations/animations.h"
#include "display/display.h"
#include "defs/chara_data.h"
#include "draw/draw.h"
uint64_t lastBeepTime = esp_timer_get_time();
uint8_t beepCounter = 0;
void menu_careMistakeScreen(TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &sprite, struct SpriteData* spriteData, struct SpriteData* uiSpritesBig) {
uint64_t currentTime = esp_timer_get_time();
uint8_t pressedButtons = buttons_getPressedButtons();
if (currentTime - lastBeepTime > ANIMATION_THRESHOLD_TIME_US * 2 && beepCounter < 10) {
tone(SPK_PIN, 2500, 100);
tone(SPK_PIN, 5000, 100);
lastBeepTime = currentTime;
beepCounter++;
}
if (currentTime - lastUpdateTime > ANIMATION_THRESHOLD_TIME_US) {
draw_drawBackground(composite, bg, 90, 90, 3);
tft_clearBuffer(sprite, TFT_TRANSPARENT);
animate_performAttentionAnimation(composite, sprite, spriteData);
tft_clearBuffer(sprite, TFT_TRANSPARENT);
menu_uiOverlay(composite, sprite, uiSpritesBig);
lastUpdateTime = currentTime;
}
if (pressedButtons != 0) {
screenKey = IDLE_SCREEN;
beepCounter = 0;
}
tft_drawBuffer(composite);
}

77
src/menu/clock_screen.cpp Normal file
View File

@ -0,0 +1,77 @@
#include "menu.h"
#include "defs/defs.h"
#include "draw/draw.h"
#include "display/display.h"
#include "buttons/buttons.h"
#include "vpet/vpet.h"
void menu_drawClock(TFT_eSprite &composite, TFT_eSprite &bg, int menuOption) {
uint8_t pressedButtons = buttons_getPressedButtons();
switch (pressedButtons) {
case 4:
screenKey = IDLE_SCREEN;
break;
default:
break;
}
char hourBuffer[6];
draw_drawBackground(composite, bg, 90, 90, 3);
snprintf(hourBuffer, 6, "%02d:%02d", timeInfo.tm_hour, timeInfo.tm_min);
composite.setTextSize(4);
composite.drawString(hourBuffer, 40, 90);
sniprintf(hourBuffer, 3, "%02d", timeInfo.tm_sec);
composite.setTextSize(2);
composite.drawString(hourBuffer, 170, 104);
tft_drawBuffer(composite);
}
void menu_drawClockEdit(TFT_eSprite &composite, TFT_eSprite &bg) {
char textBuffer[6];
static int clockHourCount = 0;
static int clockMinuteCount = 0;
uint8_t pressedButtons = buttons_getPressedButtons();
lastPressedButtonTime = esp_timer_get_time();
switch (pressedButtons) {
case 8:
clockHourCount = (clockHourCount + 1) % 24;
break;
case 4:
clockMinuteCount = (clockMinuteCount + 1) % 60;
break;
case 2:
rtc.setTime(0, clockMinuteCount, clockHourCount, 1, 11, 2024);
getLocalTime(&timeInfo, 50);
dayUnixTime = mktime(&timeInfo) % 86400;
screenKey = CLOCK_SCREEN;
onActionTimerDelta();
vpet_initTimer();
break;
default:
break;
}
draw_drawBackground(composite, bg, 90, 90, 3);
snprintf(textBuffer, 6, "%02d:%02d", clockHourCount, clockMinuteCount);
composite.setTextSize(4);
composite.drawString(textBuffer, 40, 90);
composite.drawString("SET", 40, 122);
tft_drawBuffer(composite);
}

View File

@ -0,0 +1,49 @@
#include "menu.h"
#include "animations/animations.h"
#include "buttons/buttons.h"
#include "draw/draw.h"
#include "display/display.h"
void menu_feedingScreen(
TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &charaSprite,
struct SpriteData* uiSpriteData, struct SpriteData* charaSpriteData, uint8_t item
) {
static int currentAnimationFrame = 0;
uint64_t currentTime = esp_timer_get_time();
uint8_t pressedButtons = buttons_getPressedButtons();
switch (pressedButtons) {
case 8:
case 4:
screenKey = FOOD_SCREEN;
currentAnimationFrame = 0;
lastPressedButtonTime = currentTime;
submenuKey = -1;
break;
default:
break;
}
if (currentTime - lastUpdateTime > ANIMATION_THRESHOLD_TIME_US) {
draw_drawBackground(composite, bg, 90, 90, 3);
tft_clearBuffer(charaSprite, TFT_TRANSPARENT);
animate_performEatingAnimation(composite, charaSprite, charaSpriteData);
tft_clearBuffer(charaSprite, TFT_TRANSPARENT);
draw_drawSprite(composite, charaSprite, 24, 120, uiSpriteData, item, 6);
lastUpdateTime = currentTime;
currentAnimationFrame++;
}
if (currentAnimationFrame > 6) {
screenKey = FOOD_SCREEN;
lastPressedButtonTime = currentTime;
currentAnimationFrame = 0;
submenuKey = -1;
}
tft_drawBuffer(composite);
}

View File

@ -0,0 +1,83 @@
#include "menu.h"
#include "display/display.h"
#include "draw/draw.h"
#include "buttons/buttons.h"
#include "defs/chara_data.h"
#include "vpet/vpet.h"
void menu_foodScreen(TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &mainChara, struct SpriteData* spriteData) {
if (charaData.sleepy) {
tone(SPK_PIN, BEEP_FREQ_HZ, BEEP_LEN_MS);
delay(100);
tone(SPK_PIN, BEEP_FREQ_HZ, BEEP_LEN_MS);
screenKey = MENU_SCREEN;
return;
}
static uint8_t arrowPosition = 0;
uint8_t pressedButtons = buttons_getPressedButtons();
switch (pressedButtons) {
case 8:
arrowPosition = (arrowPosition + 1) % 2;
break;
case 2:
screenKey = MENU_SCREEN;
break;
default:
break;
}
if (pressedButtons == 4) {
switch(arrowPosition) {
case 0:
if (charaData.hunger < 8) {
charaData.weight++;
charaData.hunger++;
screenKey = FEEDING_SCREEN;
submenuKey = FOOD_ICON;
} else {
screenKey = REFUSING_SCREEN;
}
break;
case 1:
if (charaData.strength < 8) {
charaData.strength++;
charaData.weight += 2;
screenKey = FEEDING_SCREEN;
submenuKey = PILL_ICON;
} else {
screenKey = REFUSING_SCREEN;
}
break;
default:
break;
}
vpet_computeCallLight();
}
composite.setTextSize(4);
draw_drawBackground(composite, bg, 90, 90, 3);
menu_foodScreen_drawEntry(composite, mainChara, spriteData, 0, FOOD_ICON, "Meat");
menu_foodScreen_drawEntry(composite, mainChara, spriteData, 1, PILL_ICON, "Pill");
draw_drawSprite(composite, mainChara, 5, (arrowPosition * 34) + 5, spriteData, ARROW_ICON, 4);
tft_drawBuffer(composite);
}
void menu_foodScreen_drawEntry(
TFT_eSprite &composite, TFT_eSprite &mainChara, struct SpriteData* spriteData,
uint8_t entryId, uint8_t spriteNumber, const char* textEntry
) {
tft_clearBuffer(mainChara, TFT_TRANSPARENT);
draw_drawSprite(composite, mainChara, 45, (entryId * 34) + 5, spriteData, spriteNumber, 4);
composite.drawString(textEntry, 80, (entryId * 34) + 5);
}

46
src/menu/happy_screen.cpp Normal file
View File

@ -0,0 +1,46 @@
#include "menu.h"
#include "defs/defs.h"
#include "defs/sprite_data.h"
#include "display/display.h"
#include "draw/draw.h"
#include "animations/animations.h"
void menu_drawHappyScreen(
TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &sprite,
struct SpriteData* spriteData, struct SpriteData* smallUiElements
) {
uint8_t frameCounter = 0;
while (true) {
uint64_t currentTime = esp_timer_get_time();
if (currentTime - lastUpdateTime > ANIMATION_THRESHOLD_TIME_US) {
if (frameCounter > 3) {
screenKey = IDLE_SCREEN; // TODO: Change for while battling
menuKey = STATUS_SCREEN;
return;
}
draw_drawBackground(composite, bg, 90, 90, 3);
tft_clearBuffer(sprite, TFT_TRANSPARENT);
animate_performHappyAnimation(composite, sprite, spriteData);
if (frameCounter % 2 == 0) {
tone(SPK_PIN, 7500, 50);
tone(SPK_PIN, 5000, 50);
tone(SPK_PIN, 2500, 50);
tone(SPK_PIN, 1000, 50);
tft_clearBuffer(sprite, TFT_TRANSPARENT);
draw_drawSprite(composite, sprite, 18, 72, smallUiElements, FIREWORKS_ICON, 6);
draw_drawSprite(composite, sprite, 174, 72, smallUiElements, FIREWORKS_ICON, 6);
}
frameCounter++;
lastUpdateTime = currentTime;
}
tft_drawBuffer(composite);
}
}

54
src/menu/idle_screen.cpp Normal file
View File

@ -0,0 +1,54 @@
#include "menu.h"
#include "draw/draw.h"
#include "defs/defs.h"
#include "display/display.h"
#include "buttons/buttons.h"
#include "animations/animations.h"
#include "defs/chara_data.h"
uint64_t lastUpdateTime = esp_timer_get_time();
void menu_drawIdleScreen(TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &sprite, struct SpriteData* spriteData, struct SpriteData* bigUiElements, struct SpriteData* smallUiElements) {
if (charaData.sleepy && !charaData.asleep) {
screenKey = SLEEPY_SCREEN;
return;
} else if ((charaData.sleepy && charaData.asleep) || charaData.asleep) {
screenKey = SLEEP_SCREEN;
return;
}
uint8_t pressedButtons = buttons_getPressedButtons();
switch (pressedButtons) {
case 8:
screenKey = MENU_SCREEN;
menuKey = 0;
break;
case 4:
screenKey = CLOCK_SCREEN;
break;
default:
break;
}
uint64_t currentTime = esp_timer_get_time();
if (currentTime - lastUpdateTime > ANIMATION_THRESHOLD_TIME_US) {
draw_drawBackground(composite, bg, 90, 90, 3);
tft_clearBuffer(sprite, TFT_TRANSPARENT);
uint8_t offsetX = menu_poopOverlay(composite, sprite, smallUiElements);
tft_clearBuffer(sprite, TFT_TRANSPARENT);
animate_performAnimation(composite, sprite, spriteData, offsetX);
tft_clearBuffer(sprite, TFT_TRANSPARENT);
menu_uiOverlay(composite, sprite, bigUiElements);
lastUpdateTime = currentTime;
}
tft_drawBuffer(composite);
}

44
src/menu/menu.h Normal file
View File

@ -0,0 +1,44 @@
#ifndef MENU_H
#define MENU_H
#include <TFT_eSPI.h>
#include "defs/defs.h"
void menu_drawCurrentMenuOption(TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &icon, struct SpriteData* spriteData);
void menu_drawClock(TFT_eSprite &composite, TFT_eSprite &bg, int menuOption);
void menu_drawClockEdit(TFT_eSprite &composite, TFT_eSprite &bg);
void menu_drawTitle(TFT_eSprite &composite, TFT_eSprite &bg);
void menu_drawIdleScreen(TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &sprite, struct SpriteData* spriteData, struct SpriteData* bigUiElements, struct SpriteData* smallUiElements);
void menu_offScreen(TFT_eSprite &buffer);
void menu_statusScreen(TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &sprite, struct SpriteData* spriteData, struct CharacterData* charaData);
void menu_statusScreen_drawStat(TFT_eSprite &composite, TFT_eSprite &sprite, struct SpriteData* spriteData, int x, int y, const char* text, uint8_t statValue);
void menu_timerFinishedScreen(TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &sprite, struct SpriteData* spriteData);
void menu_uiOverlay(TFT_eSprite &composite, TFT_eSprite &charSprite, struct SpriteData* uiElements);
void menu_foodScreen(TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &mainChara, struct SpriteData* spriteData);
void menu_foodScreen_drawEntry(
TFT_eSprite &composite, TFT_eSprite &mainChara, struct SpriteData* spriteData,
uint8_t entryId, uint8_t spriteNumber, const char* textEntry
);
void menu_feedingScreen(
TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &charaSprite,
struct SpriteData* uiSpriteData, struct SpriteData* charaSpriteData, uint8_t item
);
void menu_refuseScreen(TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &mainChara, struct SpriteData* spriteData);
void menu_sleepyScreen(TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &sprite, struct SpriteData* charaSprites, struct SpriteData* uiSprites);
void menu_careMistakeScreen(TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &sprite, struct SpriteData* spriteData, struct SpriteData* uiSpritesBig);
void menu_sleepingScreen(
TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &sprite,
struct SpriteData* mainCharaData, struct SpriteData* bigUiElements, struct SpriteData* smallUIElements
);
void menu_poopScreen(
TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &sprite,
struct SpriteData* spriteData, struct SpriteData* smallUiElements, struct SpriteData* bigUiElements
);
uint8_t menu_poopOverlay(TFT_eSprite &composite, TFT_eSprite &sprite, struct SpriteData* smallUiElements);
void menu_clearPoopScreen(TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &sprite, struct SpriteData* spriteData, struct SpriteData* bigUiElements, struct SpriteData* smallUiElements);
void menu_drawHappyScreen(
TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &sprite,
struct SpriteData* spriteData, struct SpriteData* smallUiElements
);
#endif

109
src/menu/menu_screen.cpp Normal file
View File

@ -0,0 +1,109 @@
#include "menu.h"
#include "display/display.h"
#include "draw/draw.h"
#include "defs/defs.h"
#include "defs/sprite_data.h"
#include "defs/chara_data.h"
#include "buttons/buttons.h"
#include "vpet/vpet.h"
#include <string.h>
const int textXPos = 10;
const int textYPos = 180;
void menu_drawCurrentMenuOption(TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &icon, struct SpriteData* spriteData) {
draw_drawBackground(composite, bg, 90, 90, 3);
uint8_t pressedButtons = buttons_getPressedButtons();
switch (pressedButtons) {
case 8:
menuKey++;
break;
case 2:
screenKey = IDLE_SCREEN;
menuKey = STATUS_SCREEN_MENU;
return;
break;
default:
break;
}
if (pressedButtons == 4) {
switch (menuKey) {
case STATUS_SCREEN_MENU:
screenKey = STATUS_SCREEN;
break;
case FOOD_SCREEN_MENU:
screenKey = FOOD_SCREEN;
break;
case SLEEP_SCREEN_MENU:
charaData.asleep = true;
vpet_computeCallLight();
menuKey = STATUS_SCREEN;
screenKey = SLEEP_SCREEN;
break;
case POOP_SCREEN_MENU:
menuKey = STATUS_SCREEN;
screenKey = CLEAR_POOP_SCREEN;
return;
break;
default:
break;
}
return;
}
composite.setTextSize(4);
draw_drawSpriteCentered(composite, icon, spriteData, menuKey % 8, 6);
switch(menuKey % 9) {
case STATUS_SCREEN_MENU:
tft_drawCenteredText(composite, "Status", 4, textYPos);
break;
case FOOD_SCREEN_MENU:
tft_drawCenteredText(composite, "Food", 4, textYPos);
break;
case TRAIN_SCREEN_MENU:
tft_drawCenteredText(composite, "Train", 4, textYPos);
break;
case BATTLE_SCREEN_MENU:
tft_drawCenteredText(composite, "Battle", 4, textYPos);
break;
case POOP_SCREEN_MENU:
tft_drawCenteredText(composite, "Cleanup", 4, textYPos);
break;
case MEDICAL_SCREEN_MENU:
tft_drawCenteredText(composite, "Medical", 4, textYPos);
break;
case SLEEP_SCREEN_MENU:
tft_drawCenteredText(composite, "Sleep", 4, textYPos);
break;
case SETTINGS_SCREEN_MENU:
tft_drawCenteredText(composite, "Settings", 4, textYPos);
break;
case 8:
menuKey = 0;
screenKey = IDLE_SCREEN;
return;
break;
}
menu_uiOverlay(composite, icon, spriteData);
tft_drawBuffer(composite);
}

13
src/menu/off_screen.cpp Normal file
View File

@ -0,0 +1,13 @@
#include "menu.h"
#include "defs/defs.h"
#include "buttons/buttons.h"
#include "display/display.h"
void menu_offScreen(TFT_eSprite &buffer) {
uint8_t buttons = buttons_getPressedButtons();
if (buttons != 0) {
tft_drawBuffer(buffer);
digitalWrite(BL_PIN, HIGH);
screenKey = IDLE_SCREEN;
}
}

28
src/menu/poop_clean.cpp Normal file
View File

@ -0,0 +1,28 @@
#include "menu.h"
#include "draw/draw.h"
#include "display/display.h"
#include "defs/defs.h"
#include "defs/chara_data.h"
#include "defs/sprite_data.h"
void menu_clearPoopScreen(
TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &sprite, struct SpriteData* spriteData, struct SpriteData* bigUiElements, struct SpriteData* smallUiElements
) {
int cleanerXPos = 174;
menu_drawIdleScreen(composite, bg, sprite, spriteData, bigUiElements, smallUiElements);
tft_clearBuffer(sprite, TFT_TRANSPARENT);
while (cleanerXPos > 18) {
draw_drawSprite(composite, sprite, cleanerXPos, 72, smallUiElements, CLEANER_ICON, 6);
draw_drawSprite(composite, sprite, cleanerXPos, 120, smallUiElements, CLEANER_ICON, 6);
tft_drawBuffer(composite);
cleanerXPos -= 6;
delay(50);
}
screenKey = HAPPY_SCREEN;
charaData.poopNumber = 0;
return;
}

23
src/menu/poop_overlay.cpp Normal file
View File

@ -0,0 +1,23 @@
#include "menu.h"
#include "draw/draw.h"
#include "defs/defs.h"
#include "defs/sprite_data.h"
#include "defs/chara_data.h"
#include "display/display.h"
uint8_t menu_poopOverlay(TFT_eSprite &composite, TFT_eSprite &sprite, struct SpriteData* smallUiElements) {
static bool poopFlip = false;
const uint8_t poopStartY = 120;
uint8_t poopStartX = 174;
bool poopTop = false;
for (int i = 0; i < charaData.poopNumber; i++) {
draw_drawSprite(composite, sprite, poopStartX, poopStartY - (48 * poopTop), smallUiElements, POOP_ICON, 6, poopFlip);
poopStartX -= (i % 2) * 48;
poopTop = !poopTop;
}
poopFlip = !poopFlip;
return 222 - (poopStartX + ((charaData.poopNumber % 2 == 0) * 48));
}

81
src/menu/poop_screen.cpp Normal file
View File

@ -0,0 +1,81 @@
#include "menu.h"
#include "draw/draw.h"
#include "animations/animations.h"
#include "display/display.h"
#include "defs/defs.h"
#include "defs/chara_data.h"
#include "defs/sprite_data.h"
void menu_poopScreen(
TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &sprite,
struct SpriteData* spriteData, struct SpriteData* smallUiElements, struct SpriteData* bigUiElements
) {
uint8_t animationFrame = 0;
bool animationPosition = 0;
bool beepedAlready = false;
while (1) {
uint64_t currentTime = esp_timer_get_time();
if (currentTime - lastUpdateTime > ANIMATION_THRESHOLD_TIME_US && animationFrame < 4) {
draw_drawBackground(composite, bg, 90, 90, 3);
tft_clearBuffer(sprite, TFT_TRANSPARENT);
draw_drawSprite(composite, sprite, 72 + (animationPosition * 6), 72, spriteData, 6, 6);
tft_clearBuffer(sprite, TFT_TRANSPARENT);
menu_uiOverlay(composite, sprite, bigUiElements);
animationFrame++;
animationPosition = !animationPosition;
lastUpdateTime = currentTime;
} else if (currentTime - lastUpdateTime > ANIMATION_THRESHOLD_TIME_US && animationFrame < 6) {
if (!beepedAlready) {
tone(SPK_PIN, 2500, 50);
tone(SPK_PIN, 5000, 50);
tone(SPK_PIN, 2500, 50);
tone(SPK_PIN, 5000, 50);
beepedAlready = true;
}
draw_drawBackground(composite, bg, 90, 90, 3);
tft_clearBuffer(sprite, TFT_TRANSPARENT);
draw_drawSprite(composite, sprite, 174, 120, smallUiElements, POOP_ICON, 6);
tft_clearBuffer(sprite, TFT_TRANSPARENT);
menu_uiOverlay(composite, sprite, bigUiElements);
tft_clearBuffer(sprite, TFT_TRANSPARENT);
draw_drawSprite(composite, sprite, 72, 72, spriteData, 7, 6);
animationFrame++;
animationPosition = !animationPosition;
lastUpdateTime = currentTime;
} else if (animationFrame >= 6) {
if (
(charaData.hunger == 0 && !charaData.hungerCareMistakeObtained) ||
(charaData.strength == 0 && !charaData.strengthCareMistakeObtained) ||
(charaData.sleepy && !charaData.asleep && !charaData.sleepCareMistakeObtained)
) {
screenKey = CARE_MISTAKE_SCREEN;
} else {
screenKey = IDLE_SCREEN;
}
menuKey = 0;
animationFrame = 0;
animationPosition = 0;
break;
}
tft_drawBuffer(composite);
}
}

View File

@ -0,0 +1,50 @@
#include "menu.h"
#include "buttons/buttons.h"
#include "draw/draw.h"
#include "display/display.h"
#include "defs/defs.h"
#include "defs/sprite_data.h"
#include "animations/animations.h"
void menu_refuseScreen(TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &mainChara, struct SpriteData* spriteData) {
static uint8_t currentAnimationFrame = 0;
static bool soundPlayed = false;
uint64_t currentTime = esp_timer_get_time();
uint8_t pressedButtons = buttons_getPressedButtons();
switch (pressedButtons) {
case 8:
case 4:
currentAnimationFrame = 0;
screenKey = FOOD_SCREEN;
soundPlayed = false;
return;
break;
default:
break;
}
if (currentTime - lastUpdateTime > ANIMATION_THRESHOLD_TIME_US) {
draw_drawBackground(composite, bg, 90, 90, 3);
animate_performRefuseAnimation(composite, mainChara, spriteData);
lastUpdateTime = currentTime;
currentAnimationFrame++;
}
if (!soundPlayed) {
tone(SPK_PIN, 3000, 100);
tone(SPK_PIN, 1000, 100);
soundPlayed = true;
}
if (currentAnimationFrame > 4) {
currentAnimationFrame = 0;
soundPlayed = false;
screenKey = IDLE_SCREEN;
}
tft_drawBuffer(composite);
}

57
src/menu/sleep_screen.cpp Normal file
View File

@ -0,0 +1,57 @@
#include "menu.h"
#include "draw/draw.h"
#include "display/display.h"
#include "defs/defs.h"
#include "buttons/buttons.h"
#include "animations/animations.h"
#include "defs/chara_data.h"
void menu_sleepyScreen(TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &sprite, struct SpriteData* charaSprites, struct SpriteData* uiSprites) {
if (!charaData.asleep && !charaData.sleepy) {
screenKey = IDLE_SCREEN;
return;
} else if (charaData.asleep && charaData.sleepy) {
screenKey = SLEEP_SCREEN;
return;
}
uint64_t currentTime = esp_timer_get_time();
uint8_t pressedButtons = buttons_getPressedButtons();
switch (pressedButtons) {
case 8:
screenKey = MENU_SCREEN;
break;
case 4:
screenKey = CLOCK_SCREEN;
break;
default:
break;
}
if (currentTime - lastUpdateTime > ANIMATION_SLEEPY_THRESHOLD_TIME_US) {
draw_drawBackground(composite, bg, 90, 90, 3);
tft_clearBuffer(sprite, TFT_TRANSPARENT);
animate_performSleepyAnimation(composite, sprite, charaSprites);
tft_clearBuffer(sprite, TFT_TRANSPARENT);
menu_uiOverlay(composite, sprite, uiSprites);
tft_clearBuffer(sprite, TFT_TRANSPARENT);
menu_uiOverlay(composite, sprite, uiSprites);
lastUpdateTime = currentTime;
}
if (currentTime - lastBeepTime > ANIMATION_THRESHOLD_TIME_US * 2 && beepCounter < 10) {
tone(SPK_PIN, 2500, 100);
tone(SPK_PIN, 5000, 100);
lastBeepTime = currentTime;
beepCounter++;
}
tft_drawBuffer(composite);
}

View File

@ -0,0 +1,57 @@
#include "menu.h"
#include "defs/defs.h"
#include "draw/draw.h"
#include "display/display.h"
#include "buttons/buttons.h"
#include "animations/animations.h"
#include "defs/chara_data.h"
void menu_sleepingScreen(
TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &sprite,
struct SpriteData* mainCharaData, struct SpriteData* bigUiElements, struct SpriteData* smallUIElements
) {
if (charaData.sleepy && !charaData.asleep) {
screenKey = SLEEPY_SCREEN;
return;
} else if (!charaData.sleepy && !charaData.asleep) {
screenKey = IDLE_SCREEN;
return;
}
uint64_t currentTime = esp_timer_get_time();
static uint8_t frameCounter = 0;
uint8_t buttonsPressed = buttons_getPressedButtons();
switch (buttonsPressed) {
case 8:
screenKey = MENU_SCREEN;
break;
case 4:
screenKey = CLOCK_SCREEN;
break;
default:
break;
}
if (currentTime - lastUpdateTime > ANIMATION_SLEEPY_THRESHOLD_TIME_US) {
draw_drawBackground(composite, bg, 90, 90, 3);
tft_clearBuffer(sprite, TFT_TRANSPARENT);
animate_performSleepyAnimation(composite, sprite, mainCharaData);
tft_clearBuffer(sprite, TFT_TRANSPARENT);
draw_drawSprite(composite, sprite, 72, 72, bigUiElements, BED_SPRITE, 6);
tft_clearBuffer(sprite, TFT_TRANSPARENT);
draw_drawSprite(composite, sprite, 172, 72, smallUIElements, ZZZ_ICON, 6);
tft_clearBuffer(sprite, TFT_TRANSPARENT);
menu_uiOverlay(composite, sprite, bigUiElements);
lastUpdateTime = currentTime;
}
tft_drawBuffer(composite);
}

View File

@ -0,0 +1,52 @@
#include "menu.h"
#include "defs/defs.h"
#include "defs/sprite_data.h"
#include "defs/chara_data.h"
#include "buttons/buttons.h"
#include "display/display.h"
#include "draw/draw.h"
void menu_statusScreen(TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &sprite, struct SpriteData* spriteData, struct CharacterData* charaData) {
tft_clearBuffer(sprite, TFT_TRANSPARENT);
uint8_t pressedButtons = buttons_getPressedButtons();
switch (pressedButtons) {
case 2:
screenKey = MENU_SCREEN;
break;
default:
break;
}
draw_drawBackground(composite, bg, 90, 90, 3);
composite.setTextSize(4);
menu_statusScreen_drawStat(composite, sprite, spriteData, 10, 10, "Hunger", charaData->hunger);
menu_statusScreen_drawStat(composite, sprite, spriteData, 10, 80, "Strength", charaData->strength);
menu_statusScreen_drawStat(composite, sprite, spriteData, 10, 150, "Effort", charaData->effort);
tft_drawBuffer(composite);
}
void menu_statusScreen_drawStat(TFT_eSprite &composite, TFT_eSprite &sprite, struct SpriteData* spriteData, int x, int y, const char* text, uint8_t statValue) {
uint8_t icon;
composite.drawString(text, x, y);
for (int i = 0; i < 4; i++) {
if (i < statValue) { icon = FULL_HEART_ICON; }
else { icon = EMPTY_HEART_ICON; }
draw_drawSprite(
composite,
sprite,
15 + (i * 32),
y + 30,
spriteData,
icon,
4,
false
);
}
}

View File

@ -0,0 +1,15 @@
#include "menu.h"
int interruptKey = -1;
void menu_timerFinishedScreen(TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &sprite, struct SpriteData* spriteData) {
lastPressedButtonTime = esp_timer_get_time();
digitalWrite(BL_PIN, HIGH);
screenOff = false;
inactive = false;
beepCounter = 0;
screenKey = interruptKey;
}

24
src/menu/title_screen.cpp Normal file
View File

@ -0,0 +1,24 @@
#include "menu.h"
#include "display/display.h"
#include "draw/draw.h"
#include "defs/defs.h"
#include "buttons/buttons.h"
void menu_drawTitle(TFT_eSprite &composite, TFT_eSprite &bg) {
uint8_t pressedButtons = buttons_getPressedButtons();
lastPressedButtonTime = esp_timer_get_time();
if (pressedButtons == 8 || pressedButtons == 4) {
screenKey = CLOCK_EDIT_SCREEN;
}
draw_drawBackground(composite, bg, 90, 90, 3);
composite.setTextSize(4);
tft_drawCenteredText(composite, "NacaPet", 4, 40);
composite.setTextSize(2);
tft_drawCenteredText(composite, VERSION, 2, 80);
tft_drawBuffer(composite);
}

28
src/menu/ui_overlay.cpp Normal file
View File

@ -0,0 +1,28 @@
#include "menu.h"
#include "draw/draw.h"
#include "display/display.h"
#include "defs/chara_data.h"
void menu_uiOverlay(TFT_eSprite &composite, TFT_eSprite &charSprite, struct SpriteData* uiElements) {
struct tm timeInfo;
char hourBuffer[6];
composite.setTextSize(2);
composite.setTextColor(TFT_WHITE);
composite.fillRect(0, 0, 240, 24, TFT_BLACK);
getLocalTime(&timeInfo, RTC_TIMEOUT_THRESHOLD_TIME_MS);
snprintf(hourBuffer, 6, "%02d:%02d", timeInfo.tm_hour, timeInfo.tm_min);
composite.drawString(hourBuffer, 4, 4);
snprintf(hourBuffer, 6, "%05d", stepCounter);
composite.drawString(hourBuffer, 176, 4);
composite.setTextColor(TFT_BLACK);
if (charaData.careMistakeCallLight) {
tft_clearBuffer(charSprite, TFT_TRANSPARENT);
draw_drawSprite(composite, charSprite, 192, 192, uiElements, CARE_MISTAKE_CALL_LIGHT, 2);
}
}

106
src/storage/storage.cpp Normal file
View File

@ -0,0 +1,106 @@
#include "storage.h"
#include "memory/memory.h"
#include "defs/sprite_data.h"
const char* TAG_S = "[STORAGE]";
void storage_init() {
if (!SPIFFS.begin(true)) {
printf("%s Failed to mount file system\n", TAG_S);
} else {
printf("%s File system mounted successfully\n", TAG_S);
}
}
void storage_readFile(const char* path, struct SpriteData* spriteData) {
File file = SPIFFS.open(path, "r");
if (!file) {
printf("%s Failed to open file for reading\n", TAG_S);
return;
}
size_t bytesRead = 0;
size_t fileSize = file.size();
bytesRead += file.read(&spriteData->spriteWidth, 1);
bytesRead += file.read(&spriteData->spriteHeight, 1);
bytesRead += file.read(&spriteData->spriteNumber, 1);
printf("%s Read header: width=%d, height=%d, numSprites=%d\n",
TAG_S, spriteData->spriteWidth, spriteData->spriteHeight, spriteData->spriteNumber);
fileSize = (fileSize - 3) / sizeof(uint16_t);
spriteData->spriteData = memory_allocate(spriteData->spriteNumber, spriteData->spriteWidth, spriteData->spriteHeight);
size_t bufferSize = spriteData->spriteNumber * spriteData->spriteWidth * spriteData->spriteHeight;
uint8_t highByte;
uint8_t lowByte;
for (int sprN = 0; sprN < spriteData->spriteNumber; sprN++) {
for (int i = 0; i < spriteData->spriteWidth * spriteData->spriteHeight; i++) {
bytesRead += file.read(&highByte, 1);
bytesRead += file.read(&lowByte, 1);
uint16_t pixel = (highByte << 8) | lowByte;
if (i < bufferSize) {
(spriteData->spriteData)[sprN][i] = pixel;
} else {
printf("%s Buffer overflow, skipping pixel\n", TAG_S);
break;
}
}
}
printf("%s Read %zu bytes from file %s\n", TAG_S, bytesRead, path);
file.close();
}
void storage_initBackground(const char* path, TFT_eSprite& bg) {
File file = SPIFFS.open(path, "r");
if (!file) {
printf("%s Failed to open file for reading\n", TAG_S);
return;
}
size_t bytesRead = 0;
size_t fileSize = file.size();
uint8_t width;
uint8_t height;
bytesRead += file.read(&width, 1);
bytesRead += file.read(&height, 1);
printf("%s Read header: width=%d, height=%d\n", TAG_S, width, height);
fileSize = (fileSize - 2) / sizeof(uint16_t);
bg.createSprite(width, height);
for(int i = 0; i < width * height; i++) {
uint8_t highByte;
uint8_t lowByte;
bytesRead += file.read(&highByte, 1);
bytesRead += file.read(&lowByte, 1);
uint16_t pixel = (highByte << 8) | lowByte;
if (i < fileSize) {
bg.drawPixel(i % width, i / width, pixel);
} else {
printf("%s Buffer overflow, skipping pixel\n", TAG_S);
break;
}
}
printf("%s Read %zu bytes from file %s\n", TAG_S, bytesRead, path);
file.close();
}

14
src/storage/storage.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef STORAGE_H
#define STORAGE_H
#include <SPIFFS.h>
#include <stdio.h>
#include <TFT_eSPI.h>
#include "defs/sprite_data.h"
void storage_init();
void storage_readFile(const char* path, struct SpriteData* spriteData);
void storage_initBackground(const char* path, TFT_eSprite& bg);
#endif

29
src/vpet/steps/steps.cpp Normal file
View File

@ -0,0 +1,29 @@
#include "steps.h"
#include "defs/defs.h"
float gravity = 0.0;
const float alpha = 0.99;
const float thresh = 0.80;
uint64_t lastStepTime = esp_timer_get_time();
void steps_countSteps() {
int16_t ax, ay, az;
mpu.getAcceleration(&ax, &ay, &az);
float axg = ax / 16384.0;
float ayg = ay / 16384.0;
float azg = az / 16384.0;
float mag = sqrt(axg*axg + ayg*ayg + azg*azg);
gravity = alpha * gravity + (1 - alpha) * mag;
float dyn = mag - gravity;
unsigned long now = esp_timer_get_time();
if (dyn > thresh && (now - lastStepTime) > 500000) {
stepCounter++;
lastStepTime = now;
}
}

6
src/vpet/steps/steps.h Normal file
View File

@ -0,0 +1,6 @@
#ifndef STEPS_H
#define STEPS_H
void steps_countSteps();
#endif

View File

@ -0,0 +1,11 @@
#ifndef TRAINING_H
#define TRAINING_H
#include <TFT_eSPI.h>
void training_screenTraining1(
TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &sprite,
struct SpriteData* mainCharaData, struct SpriteData* attackSprites
);
#endif

View File

@ -0,0 +1,36 @@
#include "training.h"
#include "defs/defs.h"
#include "defs/chara_data.h"
#include "defs/sprite_data.h"
#include "buttons/buttons.h"
void training_screenTraining1(
TFT_eSprite &composite, TFT_eSprite &bg, TFT_eSprite &sprite,
struct SpriteData* mainCharaData, struct SpriteData* attackSprites
) {
uint8_t attackPower = 0;
while (true) {
uint8_t buttonsRead = buttons_getPressedButtons();
switch (buttonsRead)
{
case 8:
attackPower++;
break;
case 2:
screenKey = IDLE_SCREEN;
return;
break;
default:
break;
}
uint64_t currentTime = esp_timer_get_time();
if (currentTime - lastUpdateTime > 10000000) {
// Display attack animation...
// NOT YET SLOW DOWN
}
}
}

216
src/vpet/vpet.cpp Normal file
View File

@ -0,0 +1,216 @@
#include "vpet.h"
#include "defs/defs.h"
#include "defs/chara_data.h"
hw_timer_t *actionTimerDelta = NULL;
bool runVpetTasks = false;
void vpet_initTimer() {
printf("[TIMER] Timer Init.\n");
actionTimerDelta = timerBegin(0, 80, true);
timerAttachInterrupt(actionTimerDelta, &onActionTimerDelta, true);
timerAlarmWrite(actionTimerDelta, 1000000, true);
timerAlarmEnable(actionTimerDelta);
}
void vpet_computeCallLight() {
charaData.careMistakeCallLight = (
(charaData.hunger == 0 && !charaData.hungerCareMistakeObtained) ||
(charaData.strength == 0 && !charaData.strengthCareMistakeObtained) ||
(charaData.sleepy && !charaData.asleep && !charaData.sleepCareMistakeObtained)
);
}
bool vpet_evalSleep() {
// Se devuelve true si quieres pausar los otros contadores
// False ejecutara los contadores correspondientes
if (
dayUnixTime < charaData.sleepTime &&
dayUnixTime > charaData.wakeupTime &&
charaData.sleepy
// Esto se ejecuta cuando ya es hora de despertarse
// Resultado el personaje se despierta
) {
charaData.sleepCareMistakeCounter = 0;
charaData.sleepCareMistakeObtained = false;
charaData.gotLifeYearAdded = false;
charaData.sleepy = false;
charaData.asleep = false;
return false;
} else if (
dayUnixTime < charaData.sleepTime &&
dayUnixTime > charaData.wakeupTime &&
charaData.asleep &&
charaData.sleepCareMistakeCounter < 60
// Esto se ejecuta cuando mandamos a dormir al personaje
// durante el dia.
// Resultado, el personaje deberia de dormir una siesta
) {
charaData.sleepCareMistakeCounter++;
return true;
} else if (
dayUnixTime < charaData.sleepTime &&
dayUnixTime > charaData.wakeupTime &&
charaData.asleep &&
charaData.sleepCareMistakeCounter >= 60
// Esto se ejecuta cuando la siesta del personaje acaba
// Resultado, el personaje se despierta
) {
charaData.sleepCareMistakeCounter = 0;
charaData.asleep = false;
return false;
} else if (
(
dayUnixTime > charaData.sleepTime ||
dayUnixTime < charaData.wakeupTime
) &&
!charaData.sleepy
// Esto se ejecuta cuando la hora actual del sistema
// está en el intervalo temporal de las horas el las que el
// personaje duerme
// Resultado: el personaje se duerme, y se llama a la pantalla
// de se acabo el temporizador, ademas activa la call light
) {
charaData.sleepy = true;
charaData.careMistakeCallLight = true;
screenKey = TIMER_FINISHED_SCREEN;
interruptKey = SLEEPY_SCREEN;
return true;
} else if (
charaData.sleepy && !charaData.asleep &&
charaData.sleepCareMistakeCounter < 60 &&
!charaData.sleepCareMistakeObtained
// Esto se ejecuta cuando el personaje debería de estar durmiendo
// pero no se le ha mandado a dormir, empieza a contar para pasar
// un care mistake
) {
charaData.sleepCareMistakeCounter++;
return true;
} else if (
charaData.sleepy && !charaData.asleep &&
charaData.sleepCareMistakeCounter >= 60 &&
!charaData.sleepCareMistakeObtained
// Esto se ejecuta cuando el personaje deberia de estar durmiendo
// pero no se le ha mandado a dormir, y el contador ya ha llegado
// al tiempo maximo
// Resultado: se añade el care mistake y se activa la flag para
// evitar otro care mistake
) {
charaData.sleepCareMistakeObtained = true;
charaData.careMistakes++;
return true;
} else if (
!charaData.gotLifeYearAdded &&
dayUnixTime < 1 // This stinks
// Esto se ejecuta cuando es media noche.
// Resultado: se incrementa la edad por 1
) {
charaData.age++;
charaData.gotLifeYearAdded = true;
return true;
} else if (
charaData.sleepy
) {
return true;
}
return false;
}
void vpet_evalTimers() {
if (charaData.hungerCareMistakeTimer >= 0) {
charaData.hungerCareMistakeTimer -= 1;
}
if (charaData.strengthCareMistakeTimer >= 0) {
charaData.strengthCareMistakeTimer -= 1;
}
if (
charaData.hungerCareMistakeTimer < 0 ||
charaData.strengthCareMistakeTimer < 0
) {
if (charaData.hunger > 0) {
charaData.hunger--;
screenKey = TIMER_FINISHED_SCREEN;
interruptKey = POOPING_SCREEN;
charaData.poopNumber++;
charaData.hungerCareMistakeTimer = 60;
}
if (!charaData.hungerCareMistakeObtained) {
if (
charaData.hunger == 0 &&
charaData.hungerCareMistakeTimer < 0
) {
charaData.careMistakes++;
charaData.hungerCareMistakeObtained = true;
} else if (charaData.hunger == 0) {
interruptKey = POOPING_SCREEN;
screenKey = TIMER_FINISHED_SCREEN;
charaData.poopNumber++;
}
}
if (charaData.strength > 0) {
charaData.strength--;
charaData.strengthCareMistakeTimer = 60;
}
if (!charaData.strengthCareMistakeObtained) {
if (
charaData.strength == 0 &&
charaData.strengthCareMistakeTimer < 0
) {
charaData.careMistakes++;
charaData.strengthCareMistakeObtained = true;
} else if(charaData.strength == 0) {
if (interruptKey != POOPING_SCREEN) {
interruptKey = CARE_MISTAKE_SCREEN;
screenKey = TIMER_FINISHED_SCREEN;
}
}
}
}
}
void IRAM_ATTR onActionTimerDelta() {
runVpetTasks = true;
}
void vpet_runVpetTasks() {
if (runVpetTasks) {
vpet_computeCallLight();
if (!vpet_evalSleep()) {
vpet_evalTimers();
}
printf("[MAIN]: Hunger timer %d, hunger %d\n", charaData.hungerCareMistakeTimer, charaData.hunger);
printf("[MAIN]: Strength timer %d, strength %d\n", charaData.strengthCareMistakeTimer, charaData.strength);
printf("[MAIN]: Evo timer %d\n", charaData.evoLeftTimer);
printf("[MAIN]: RTC time is %d\n", dayUnixTime);
printf("[MAIN]: Sleep counter is %d\n", charaData.sleepCareMistakeCounter);
printf("[MAIN]: Care mistake count is %d\n", charaData.careMistakes);
printf("[MAIN]: Is sleep care mistake tripped? %d\n", charaData.sleepCareMistakeObtained);
runVpetTasks = false;
}
}

13
src/vpet/vpet.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef VPET_H
#define VPET_H
#include <Arduino.h>
void IRAM_ATTR onActionTimerDelta();
void vpet_initTimer();
void vpet_computeCallLight();
bool vpet_evalSleep();
void vpet_evalTimers();
void vpet_runVpetTasks();
#endif

11
test/README Normal file
View File

@ -0,0 +1,11 @@
This directory is intended for PlatformIO Test Runner and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html