mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-14 02:29:29 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b91103118 | |||
| c0aa4a1738 | |||
| e26f77ee77 | |||
| f53262783a | |||
| b456c18008 | |||
| fa4b74fd6f | |||
| 18bc2cf11c | |||
| 20f28c8331 | |||
| ea38a0fe48 | |||
| fb2e576b34 | |||
| 1d79e30d4b | |||
| be372e12bc | |||
| 00853f526a | |||
| 37ca7a601c | |||
| b50d18eafc | |||
| 1070d951e6 | |||
| a980fe21ac | |||
| 9e96715176 | |||
| fecfe8b8fc | |||
| dfdd52c667 | |||
| 695e6d19f4 | |||
| 6e9ecf8e7b | |||
| fd158e873a | |||
| f90bd44617 |
@@ -143,7 +143,7 @@ class WFMAMAptOptionsView : public View {
|
||||
};
|
||||
OptionsField options_config{
|
||||
{3 * 8, 0 * 16},
|
||||
16, // Max option char length "80khz (NOAA Apt)" example.
|
||||
16, // Max option char length "80k-NOAA Apt LPF" , example.
|
||||
{
|
||||
// Using common messages from freqman_ui.cpp
|
||||
}};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyright (C) 2023 TJ Baginski
|
||||
* Copyright (C) 2025 Tommaso Ventafridda
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -83,6 +84,50 @@ void reverse_byte_array(uint8_t* arr, int length) {
|
||||
}
|
||||
}
|
||||
|
||||
MAC_VENDOR_STATUS lookup_mac_vendor_status(const uint8_t* mac_address, std::string& vendor_name) {
|
||||
static bool db_checked = false;
|
||||
static bool db_exists = false;
|
||||
|
||||
if (!db_checked) {
|
||||
database db;
|
||||
database::MacAddressDBRecord dummy_record;
|
||||
int test_result = db.retrieve_macaddress_record(&dummy_record, "000000");
|
||||
db_exists = (test_result != DATABASE_NOT_FOUND);
|
||||
db_checked = true;
|
||||
}
|
||||
|
||||
if (!db_exists) {
|
||||
vendor_name = "macaddress.db not found";
|
||||
return MAC_DB_NOT_FOUND;
|
||||
}
|
||||
|
||||
database db;
|
||||
database::MacAddressDBRecord record;
|
||||
|
||||
// Convert MAC address to hex string
|
||||
std::string mac_hex = "";
|
||||
for (int i = 0; i < 3; i++) {
|
||||
// Only need first 3 bytes for OUI
|
||||
mac_hex += to_string_hex(mac_address[i], 2);
|
||||
}
|
||||
|
||||
int result = db.retrieve_macaddress_record(&record, mac_hex);
|
||||
|
||||
if (result == DATABASE_RECORD_FOUND) {
|
||||
vendor_name = std::string(record.vendor_name);
|
||||
return MAC_VENDOR_FOUND;
|
||||
} else {
|
||||
vendor_name = "Unknown";
|
||||
return MAC_VENDOR_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
std::string lookup_mac_vendor(const uint8_t* mac_address) {
|
||||
std::string vendor_name;
|
||||
lookup_mac_vendor_status(mac_address, vendor_name);
|
||||
return vendor_name;
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
|
||||
std::string pdu_type_to_string(ADV_PDU_TYPE type) {
|
||||
@@ -165,7 +210,10 @@ void RecentEntriesTable<BleRecentEntries>::draw(
|
||||
line += pad_string_with_spaces(db_spacing) + dbStr;
|
||||
|
||||
line.resize(target_rect.width() / 8, ' ');
|
||||
painter.draw_string(target_rect.location(), style, line);
|
||||
|
||||
Style row_style = (entry.vendor_status == MAC_VENDOR_FOUND) ? style : Style{style.font, style.background, Color::grey()};
|
||||
|
||||
painter.draw_string(target_rect.location(), row_style, line);
|
||||
}
|
||||
|
||||
BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const BleRecentEntry& entry)
|
||||
@@ -178,10 +226,14 @@ BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const Bl
|
||||
&text_mac_address,
|
||||
&label_pdu_type,
|
||||
&text_pdu_type,
|
||||
&label_vendor,
|
||||
&text_vendor,
|
||||
&labels});
|
||||
|
||||
text_mac_address.set(to_string_mac_address(entry.packetData.macAddress, 6, false));
|
||||
text_pdu_type.set(pdu_type_to_string(entry.pduType));
|
||||
std::string vendor_name = lookup_mac_vendor(entry.packetData.macAddress);
|
||||
text_vendor.set(vendor_name);
|
||||
|
||||
button_done.on_select = [&nav](const ui::Button&) {
|
||||
nav.pop();
|
||||
@@ -370,6 +422,10 @@ void BleRecentEntryDetailView::paint(Painter& painter) {
|
||||
|
||||
void BleRecentEntryDetailView::set_entry(const BleRecentEntry& entry) {
|
||||
entry_ = entry;
|
||||
text_mac_address.set(to_string_mac_address(entry.packetData.macAddress, 6, false));
|
||||
text_pdu_type.set(pdu_type_to_string(entry.pduType));
|
||||
std::string vendor_name = lookup_mac_vendor(entry.packetData.macAddress);
|
||||
text_vendor.set(vendor_name);
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
@@ -952,6 +1008,11 @@ void BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry,
|
||||
entry.pduType = pdu_type;
|
||||
entry.channelNumber = channel_number;
|
||||
|
||||
if (entry.vendor_status == MAC_VENDOR_UNKNOWN) {
|
||||
std::string vendor_name;
|
||||
entry.vendor_status = lookup_mac_vendor_status(entry.packetData.macAddress, vendor_name);
|
||||
}
|
||||
|
||||
// Parse Data Section into buffer to be interpretted later.
|
||||
for (int i = 0; i < packet->dataLen; i++) {
|
||||
entry.packetData.data[i] = packet->data[i];
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyright (C) 2023 TJ Baginski
|
||||
* Copyright (C) 2025 Tommaso Ventafridda
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -33,6 +34,7 @@
|
||||
#include "ui_record_view.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "database.hpp"
|
||||
#include "log_file.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "usb_serial_thread.hpp"
|
||||
@@ -72,6 +74,13 @@ typedef enum {
|
||||
RESERVED8 = 15
|
||||
} ADV_PDU_TYPE;
|
||||
|
||||
typedef enum {
|
||||
MAC_VENDOR_UNKNOWN = 0,
|
||||
MAC_VENDOR_FOUND = 1,
|
||||
MAC_VENDOR_NOT_FOUND = 2,
|
||||
MAC_DB_NOT_FOUND = 3
|
||||
} MAC_VENDOR_STATUS;
|
||||
|
||||
struct BleRecentEntry {
|
||||
using Key = uint64_t;
|
||||
|
||||
@@ -87,6 +96,7 @@ struct BleRecentEntry {
|
||||
uint16_t numHits;
|
||||
ADV_PDU_TYPE pduType;
|
||||
uint8_t channelNumber;
|
||||
MAC_VENDOR_STATUS vendor_status;
|
||||
bool entryFound;
|
||||
|
||||
BleRecentEntry()
|
||||
@@ -105,6 +115,7 @@ struct BleRecentEntry {
|
||||
numHits{},
|
||||
pduType{},
|
||||
channelNumber{},
|
||||
vendor_status{MAC_VENDOR_UNKNOWN},
|
||||
entryFound{} {
|
||||
}
|
||||
|
||||
@@ -152,6 +163,13 @@ class BleRecentEntryDetailView : public View {
|
||||
{9 * 8, 1 * 16, 17 * 8, 16},
|
||||
"-"};
|
||||
|
||||
Labels label_vendor{
|
||||
{{0 * 8, 2 * 16}, "Vendor:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Text text_vendor{
|
||||
{7 * 8, 2 * 16, 23 * 8, 16},
|
||||
"-"};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 3 * 16}, "Len", Theme::getInstance()->fg_light->foreground},
|
||||
{{5 * 8, 3 * 16}, "Type", Theme::getInstance()->fg_light->foreground},
|
||||
|
||||
@@ -319,7 +319,8 @@ void BLETxView::on_tx_progress(const bool done) {
|
||||
|
||||
BLETxView::BLETxView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({&button_open,
|
||||
add_children({&dataEditView,
|
||||
&button_open,
|
||||
&text_filename,
|
||||
&progressbar,
|
||||
&check_rand_mac,
|
||||
@@ -340,7 +341,6 @@ BLETxView::BLETxView(NavigationView& nav)
|
||||
&label_mac_address,
|
||||
&text_mac_address,
|
||||
&label_data_packet,
|
||||
&dataEditView,
|
||||
&button_clear_marked,
|
||||
&button_save_packet,
|
||||
&button_switch});
|
||||
@@ -430,6 +430,23 @@ BLETxView::BLETxView(NavigationView& nav)
|
||||
}
|
||||
};
|
||||
|
||||
dataEditView.on_change = [this](uint8_t value) {
|
||||
// Reject setting newline at index 29.
|
||||
if (cursor_pos.col != 29) {
|
||||
uint16_t dataBytePos = (cursor_pos.line * 29) + cursor_pos.col;
|
||||
|
||||
packets[current_packet].advertisementData[dataBytePos] = uint_to_char(value, 16);
|
||||
|
||||
update_current_packet(packets[current_packet], current_packet);
|
||||
}
|
||||
};
|
||||
|
||||
dataEditView.on_cursor_moved = [this]() {
|
||||
// Save last selected cursor.
|
||||
cursor_pos.line = dataEditView.line();
|
||||
cursor_pos.col = dataEditView.col();
|
||||
};
|
||||
|
||||
button_clear_marked.on_select = [this](Button&) {
|
||||
marked_counter = 0;
|
||||
markedBytes.clear();
|
||||
@@ -531,7 +548,6 @@ void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex)
|
||||
for (const std::string& str : strings) {
|
||||
dataFile.write(str.c_str(), str.size());
|
||||
dataFile.write("\n", 1);
|
||||
;
|
||||
}
|
||||
|
||||
dataFile.~File();
|
||||
@@ -546,6 +562,7 @@ void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex)
|
||||
dataEditView.set_font_zoom(true);
|
||||
dataEditView.set_file(*dataFileWrapper);
|
||||
dataEditView.redraw(true, true);
|
||||
dataEditView.cursor_set(cursor_pos.line, cursor_pos.col);
|
||||
}
|
||||
|
||||
void BLETxView::set_parent_rect(const Rect new_parent_rect) {
|
||||
|
||||
@@ -287,9 +287,6 @@ class BLETxView : public View {
|
||||
Labels label_data_packet{
|
||||
{{0 * 8, 9 * 16}, "Packet Data:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Console console{
|
||||
{0, 9 * 18, screen_width, screen_height - 80}};
|
||||
|
||||
TextViewer dataEditView{
|
||||
{0, 9 * 18, screen_width, screen_height - 80}};
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ constexpr std::string_view mayhem_information_list[] = {
|
||||
"RedFox-Fr,nemanjan00,",
|
||||
"MichalLeonBorsuk,",
|
||||
"MatiasFernandez,Giorgiofox",
|
||||
"TommasoVentafridda",
|
||||
" ",
|
||||
"#Havoc:",
|
||||
"jboone,furrtek,eried,argilo,",
|
||||
|
||||
@@ -228,6 +228,8 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
|
||||
|
||||
text_current.set(dir_path.empty() ? "(sd root)" : truncate(dir_path, 24));
|
||||
|
||||
// Collect all entries first
|
||||
std::list<fileman_entry> all_entries;
|
||||
for (const auto& entry : fs::directory_iterator(dir_path, u"*")) {
|
||||
// Hide files starting with '.' (hidden / tmp).
|
||||
if (!show_hidden_files && is_hidden_file(entry.path()))
|
||||
@@ -235,36 +237,40 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
|
||||
|
||||
if (fs::is_regular_file(entry.status())) {
|
||||
if (!filtering || path_iequal(entry.path().extension(), extension_filter) || (cxx_file && is_cxx_capture_file(entry.path())))
|
||||
insert_sorted(entry_list, {entry.path().string(), (uint32_t)entry.size(), false});
|
||||
insert_sorted(all_entries, {entry.path().string(), (uint32_t)entry.size(), false});
|
||||
} else if (fs::is_directory(entry.status())) {
|
||||
insert_sorted(entry_list, {entry.path().string(), 0, true});
|
||||
insert_sorted(all_entries, {entry.path().string(), 0, true});
|
||||
}
|
||||
}
|
||||
|
||||
// paginating
|
||||
auto list_size = entry_list.size();
|
||||
nb_pages = 1 + (list_size / items_per_page);
|
||||
size_t start = pagination * items_per_page;
|
||||
size_t stop = start + items_per_page;
|
||||
if (list_size > start) {
|
||||
if (list_size < stop)
|
||||
stop = list_size;
|
||||
entry_list.erase(std::next(entry_list.begin(), stop), entry_list.end());
|
||||
entry_list.erase(entry_list.begin(), std::next(entry_list.begin(), start));
|
||||
// Calculate pagination
|
||||
nb_pages = (all_entries.size() + items_per_page - 1) / items_per_page;
|
||||
if (nb_pages == 0) nb_pages = 1;
|
||||
|
||||
size_t start_idx = pagination * items_per_page;
|
||||
size_t end_idx = std::min(start_idx + items_per_page, all_entries.size());
|
||||
|
||||
// Add "parent" directory if not at the root and on first page
|
||||
if (!dir_path.empty() && pagination == 0) {
|
||||
entry_list.push_back({parent_dir_path.string(), 0, true});
|
||||
}
|
||||
|
||||
// Add "parent" directory if not at the root.
|
||||
if (!dir_path.empty() && pagination == 0)
|
||||
entry_list.insert(entry_list.begin(), {parent_dir_path.string(), 0, true});
|
||||
|
||||
// add next page
|
||||
if (list_size > start + items_per_page) {
|
||||
entry_list.push_back({str_next, (uint32_t)pagination + 1, true});
|
||||
}
|
||||
|
||||
// add prev page
|
||||
// Add prev page navigation if not on first page
|
||||
if (pagination > 0) {
|
||||
entry_list.insert(entry_list.begin(), {str_back, (uint32_t)pagination - 1, true});
|
||||
entry_list.push_back({str_back, (uint32_t)pagination - 1, true});
|
||||
}
|
||||
|
||||
// Add entries for current page
|
||||
auto it = all_entries.begin();
|
||||
std::advance(it, start_idx);
|
||||
|
||||
for (size_t i = start_idx; i < end_idx && it != all_entries.end(); i++, ++it) {
|
||||
entry_list.push_back(*it);
|
||||
}
|
||||
|
||||
// Add next page navigation if not on last page
|
||||
if (end_idx < all_entries.size()) {
|
||||
entry_list.push_back({str_next, (uint32_t)pagination + 1, true});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,30 +326,49 @@ void FileManBaseView::focus() {
|
||||
} else {
|
||||
menu_view.focus();
|
||||
}
|
||||
|
||||
// Set menu to the correct page and select the correct item
|
||||
menu_view.set_highlighted(prev_highlight % items_per_page);
|
||||
}
|
||||
|
||||
// Push directory - store the global index (page * items_per_page + local_index)
|
||||
void FileManBaseView::push_dir(const fs::path& path) {
|
||||
if (path == parent_dir_path) {
|
||||
pop_dir();
|
||||
} else {
|
||||
// Save global index (combines page number and item position)
|
||||
saved_index_stack.push_back(menu_view.highlighted_index() + (pagination * items_per_page));
|
||||
|
||||
current_path /= path;
|
||||
saved_index_stack.push_back(menu_view.highlighted_index());
|
||||
menu_view.set_highlighted(0);
|
||||
reload_current(true);
|
||||
reload_current(true); // Reset pagination when entering new directory
|
||||
}
|
||||
}
|
||||
|
||||
void FileManBaseView::pop_dir() {
|
||||
if (saved_index_stack.empty())
|
||||
if (current_path.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Move to parent directory
|
||||
current_path = current_path.parent_path();
|
||||
reload_current(true);
|
||||
menu_view.set_highlighted(saved_index_stack.back());
|
||||
saved_index_stack.pop_back();
|
||||
|
||||
// Restore the previous global index if available
|
||||
if (!saved_index_stack.empty()) {
|
||||
uint32_t global_index = saved_index_stack.back();
|
||||
saved_index_stack.pop_back();
|
||||
|
||||
// Calculate pagination from global index
|
||||
pagination = global_index / items_per_page;
|
||||
|
||||
// Calculate local index within the page
|
||||
prev_highlight = global_index % items_per_page;
|
||||
restoring_navigation = true;
|
||||
}
|
||||
|
||||
reload_current(false); // Important: don't reset pagination
|
||||
}
|
||||
|
||||
std::string get_extension(std::string t) {
|
||||
std::string FileManBaseView::get_extension(const std::string& t) const {
|
||||
const auto index = t.find_last_of(u'.');
|
||||
if (index == t.npos) {
|
||||
return {};
|
||||
@@ -372,7 +397,9 @@ void FileManBaseView::refresh_list() {
|
||||
if (on_refresh_widgets)
|
||||
on_refresh_widgets(false);
|
||||
|
||||
prev_highlight = menu_view.highlighted_index();
|
||||
if (!restoring_navigation) {
|
||||
prev_highlight = menu_view.highlighted_index();
|
||||
}
|
||||
menu_view.clear();
|
||||
|
||||
for (const auto& entry : entry_list) {
|
||||
@@ -411,10 +438,14 @@ void FileManBaseView::refresh_list() {
|
||||
}
|
||||
|
||||
menu_view.set_highlighted(prev_highlight);
|
||||
restoring_navigation = false;
|
||||
}
|
||||
|
||||
void FileManBaseView::reload_current(bool reset_pagination) {
|
||||
if (reset_pagination) pagination = 0;
|
||||
// Only reset pagination if explicitly requested
|
||||
if (reset_pagination) {
|
||||
pagination = 0;
|
||||
}
|
||||
load_directory_contents(current_path);
|
||||
refresh_list();
|
||||
}
|
||||
@@ -489,63 +520,6 @@ void FileLoadView::refresh_widgets(const bool) {
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
/* FileSaveView **************************************************************/
|
||||
/*
|
||||
FileSaveView::FileSaveView(
|
||||
NavigationView& nav,
|
||||
const fs::path& path,
|
||||
const fs::path& file
|
||||
) : nav_{ nav },
|
||||
path_{ path },
|
||||
file_{ file }
|
||||
{
|
||||
add_children({
|
||||
&text_path,
|
||||
&button_edit_path,
|
||||
&text_name,
|
||||
&button_edit_name,
|
||||
&button_save,
|
||||
&button_cancel,
|
||||
});
|
||||
|
||||
button_edit_path.on_select = [this](Button&) {
|
||||
buffer_ = path_.string();
|
||||
text_prompt(nav_, buffer_, max_filename_length,ENTER_KEYBOARD_MODE_ALPHA,
|
||||
[this](std::string&) {
|
||||
path_ = buffer_;
|
||||
refresh_widgets();
|
||||
});
|
||||
};
|
||||
|
||||
button_edit_name.on_select = [this](Button&) {
|
||||
buffer_ = file_.string();
|
||||
text_prompt(nav_, buffer_, max_filename_length,ENTER_KEYBOARD_MODE_ALPHA,
|
||||
[this](std::string&) {
|
||||
file_ = buffer_;
|
||||
refresh_widgets();
|
||||
});
|
||||
};
|
||||
|
||||
button_save.on_select = [this](Button&) {
|
||||
if (on_save)
|
||||
on_save(path_ / file_);
|
||||
else
|
||||
nav_.pop();
|
||||
};
|
||||
|
||||
button_cancel.on_select = [this](Button&) {
|
||||
nav_.pop();
|
||||
};
|
||||
|
||||
refresh_widgets();
|
||||
}
|
||||
|
||||
void FileSaveView::refresh_widgets() {
|
||||
text_path.set(truncate(path_, 30));
|
||||
text_name.set(truncate(file_, 30));
|
||||
set_dirty();
|
||||
}
|
||||
*/
|
||||
/* FileManagerView ***********************************************************/
|
||||
|
||||
void FileManagerView::refresh_widgets(const bool v) {
|
||||
|
||||
@@ -64,6 +64,7 @@ class FileManBaseView : public View {
|
||||
uint32_t prev_highlight = 0;
|
||||
uint8_t pagination = 0;
|
||||
uint8_t nb_pages = 1;
|
||||
bool restoring_navigation = false;
|
||||
static constexpr size_t max_filename_length = 20;
|
||||
static constexpr size_t max_items_loaded = 75; // too memory hungry, so won't sort it
|
||||
static constexpr size_t items_per_page = 20;
|
||||
@@ -96,6 +97,7 @@ class FileManBaseView : public View {
|
||||
void load_directory_contents(const std::filesystem::path& dir_path);
|
||||
void load_directory_contents_unordered(const std::filesystem::path& dir_path, size_t file_cnt);
|
||||
const file_assoc_t& get_assoc(const std::filesystem::path& ext) const;
|
||||
std::string get_extension(const std::string& t) const;
|
||||
void copy_waterfall(std::filesystem::path path);
|
||||
|
||||
NavigationView& nav_;
|
||||
|
||||
@@ -954,6 +954,7 @@ class SetThemeView : public View {
|
||||
{"Aqua", 2},
|
||||
{"Green", 3},
|
||||
{"Red", 4},
|
||||
{"Dark", 5},
|
||||
},
|
||||
true};
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include "log_file.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
namespace fs = std::filesystem;
|
||||
@@ -81,21 +82,75 @@ void TextViewer::paint(Painter& painter) {
|
||||
paint_cursor(painter);
|
||||
}
|
||||
|
||||
void TextViewer::enable_long_press() {
|
||||
// Enable long press on "Select".
|
||||
SwitchesState config;
|
||||
config[toUType(Switch::Sel)] = true;
|
||||
set_switches_long_press_config(config);
|
||||
}
|
||||
|
||||
void TextViewer::on_focus() {
|
||||
enable_long_press();
|
||||
}
|
||||
|
||||
bool TextViewer::on_key(const KeyEvent key) {
|
||||
int16_t delta_col = 0;
|
||||
int16_t delta_line = 0;
|
||||
|
||||
if (key == KeyEvent::Left)
|
||||
delta_col = -1;
|
||||
else if (key == KeyEvent::Right)
|
||||
delta_col = 1;
|
||||
else if (key == KeyEvent::Up)
|
||||
delta_line = -1;
|
||||
else if (key == KeyEvent::Down)
|
||||
delta_line = 1;
|
||||
else if (key == KeyEvent::Select && on_select) {
|
||||
on_select();
|
||||
return true;
|
||||
if (key == KeyEvent::Select) {
|
||||
// Toggle 'digit' mode with long-press.
|
||||
if (digit_mode_ || key_is_long_pressed(key)) {
|
||||
digit_mode_ = !digit_mode_;
|
||||
set_dirty();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (digit_mode_) {
|
||||
switch (key) {
|
||||
case KeyEvent::Left:
|
||||
delta_col = -1;
|
||||
break;
|
||||
case KeyEvent::Right:
|
||||
delta_col = 1;
|
||||
break;
|
||||
case KeyEvent::Up:
|
||||
set_value(value_ == 0x0F ? 0x00 : value_ + 1);
|
||||
break;
|
||||
case KeyEvent::Down:
|
||||
set_value(value_ == 0x00 ? 0x0F : value_ - 1);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (delta_col == 0 && delta_line == 0) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
switch (key) {
|
||||
case KeyEvent::Left:
|
||||
delta_col = -1;
|
||||
break;
|
||||
case KeyEvent::Right:
|
||||
delta_col = 1;
|
||||
break;
|
||||
case KeyEvent::Up:
|
||||
delta_line = -1;
|
||||
break;
|
||||
case KeyEvent::Down:
|
||||
delta_line = 1;
|
||||
break;
|
||||
case KeyEvent::Select: {
|
||||
if (on_select) {
|
||||
on_select();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Always allow cursor direction to be updated.
|
||||
@@ -111,19 +166,42 @@ bool TextViewer::on_key(const KeyEvent key) {
|
||||
bool TextViewer::on_encoder(EncoderEvent delta) {
|
||||
bool updated = false;
|
||||
|
||||
if (cursor_.dir == ScrollDirection::Horizontal)
|
||||
updated = apply_scrolling_constraints(0, delta);
|
||||
else {
|
||||
delta *= 16;
|
||||
updated = apply_scrolling_constraints(delta, 0);
|
||||
if (digit_mode_) {
|
||||
if (delta > 0) {
|
||||
set_value(value_ == 0x0F ? 0x00 : value_ + 1);
|
||||
} else if (delta < 0) {
|
||||
set_value(value_ == 0x00 ? 0x0F : value_ - 1);
|
||||
}
|
||||
|
||||
updated = true;
|
||||
} else {
|
||||
if (cursor_.dir == ScrollDirection::Horizontal) {
|
||||
updated = apply_scrolling_constraints(0, delta);
|
||||
} else {
|
||||
delta *= 16;
|
||||
updated = apply_scrolling_constraints(delta, 0);
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
redraw();
|
||||
}
|
||||
}
|
||||
|
||||
if (updated)
|
||||
redraw();
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
void TextViewer::set_value(uint8_t new_value) {
|
||||
if (new_value != value_) {
|
||||
value_ = new_value;
|
||||
|
||||
if (on_change) {
|
||||
on_change(value_);
|
||||
}
|
||||
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
void TextViewer::redraw(bool redraw_text, bool redraw_marked) {
|
||||
paint_state_.redraw_text = redraw_text;
|
||||
paint_state_.redraw_marked = redraw_marked;
|
||||
|
||||
@@ -54,10 +54,13 @@ class TextViewer : public Widget {
|
||||
|
||||
std::function<void()> on_select{};
|
||||
std::function<void()> on_cursor_moved{};
|
||||
std::function<void(uint8_t)> on_change{};
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
bool on_key(KeyEvent key) override;
|
||||
bool on_encoder(EncoderEvent delta) override;
|
||||
void set_value(uint8_t new_value);
|
||||
void on_focus() override;
|
||||
|
||||
void redraw(bool redraw_text = false, bool redraw_marked = false);
|
||||
|
||||
@@ -74,6 +77,7 @@ class TextViewer : public Widget {
|
||||
void cursor_set(uint16_t line, uint16_t col);
|
||||
void cursor_mark_selected();
|
||||
void cursor_clear_marked();
|
||||
void enable_long_press();
|
||||
|
||||
typedef std::pair<uint16_t, uint16_t> LineColPair;
|
||||
std::vector<LineColPair> lineColPair{};
|
||||
@@ -95,6 +99,9 @@ class TextViewer : public Widget {
|
||||
int8_t char_height{};
|
||||
uint8_t max_line{};
|
||||
uint8_t max_col{};
|
||||
bool digit_mode_{false};
|
||||
uint8_t value_{0};
|
||||
bool allow_digit_mode_{true};
|
||||
|
||||
/* Returns true if the cursor was updated. */
|
||||
bool apply_scrolling_constraints(
|
||||
|
||||
@@ -105,10 +105,10 @@ void WFMConfig::apply() const {
|
||||
|
||||
void WFMAMConfig::apply() const {
|
||||
const WFMAMConfigureMessage message{
|
||||
decim_0, // Fixed 24 taps array : taps_16k0_decim_0
|
||||
decim_1, // Fixed 32 taps array : taps_84k_wfm_decim_1
|
||||
taps_64_lp_1875_2166, // Fixed channel audio filter , 64 taps array , to filter DSB AM 2k4 carrier before demod. AM .
|
||||
17000, // NOAA satellite tx , FM deviation = +-17Khz.
|
||||
decim_0, // Fixed 24 taps array : taps_16k0_decim_0
|
||||
decim_1, // Dynamic 32 taps array : taps_80k_wfmam_decim_1, 38k_wfmam
|
||||
taps_64_lp_bpf, // Dynamic 64 taps array , to filter modulated DSB AM 2k4 carrier before demod. AM .(LPF / BPF)
|
||||
17000, // NOAA satellite tx , FM deviation = +-17Khz.
|
||||
apt_audio_12k_notch_2k4_config,
|
||||
apt_audio_12k_lpf_2000hz_config};
|
||||
send_message(&message);
|
||||
|
||||
@@ -65,6 +65,7 @@ struct WFMConfig {
|
||||
struct WFMAMConfig {
|
||||
const fir_taps_real<24> decim_0; // To handle WFM filter BW=40K for NOAA APT
|
||||
const fir_taps_real<32> decim_1;
|
||||
const fir_taps_real<64> taps_64_lp_bpf; // to handle dynamically LPF / BPF .
|
||||
|
||||
void apply() const;
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2022 Arjan Onwezen
|
||||
* Copyright (C) 2025 Tommaso Ventafridda
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -56,6 +57,16 @@ int database::retrieve_aircraft_record(AircraftDBRecord* record, std::string sea
|
||||
return (result);
|
||||
}
|
||||
|
||||
int database::retrieve_macaddress_record(MacAddressDBRecord* record, std::string search_term) {
|
||||
file_path = macaddress_dir / u"macaddress.db";
|
||||
index_item_length = 7;
|
||||
record_length = 64;
|
||||
|
||||
result = retrieve_record(file_path, index_item_length, record_length, record, search_term);
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
int database::retrieve_record(std::filesystem::path file_path, int index_item_length, int record_length, void* record, std::string search_term) {
|
||||
if (search_term.empty())
|
||||
return DATABASE_RECORD_NOT_FOUND;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2022 Arjan Onwezen
|
||||
* Copyright (C) 2025 Tommaso Ventafridda
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -60,6 +61,12 @@ class database {
|
||||
|
||||
int retrieve_aircraft_record(AircraftDBRecord* record, std::string search_term);
|
||||
|
||||
struct MacAddressDBRecord {
|
||||
char vendor_name[64]; // vendor/manufacturer name
|
||||
};
|
||||
|
||||
int retrieve_macaddress_record(MacAddressDBRecord* record, std::string search_term);
|
||||
|
||||
private:
|
||||
std::filesystem::path file_path = ""; // path including filename
|
||||
int index_item_length = 0; // length of index item
|
||||
@@ -77,4 +84,4 @@ class database {
|
||||
int retrieve_record(std::filesystem::path file_path, int index_item_length, int record_length, void* record, std::string search_term);
|
||||
};
|
||||
|
||||
#endif /*__DATABASE_H__*/
|
||||
#endif /*__DATABASE_H__*/
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_detector_rx.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::detector_rx {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<DetectorRxView>();
|
||||
}
|
||||
} // namespace ui::external_app::detector_rx
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_detector_rx.application_information"), used)) application_information_t _application_information_detector_rx = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::detector_rx::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "Detector",
|
||||
/*.bitmap_data = */
|
||||
{
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x04,
|
||||
0x20,
|
||||
0x12,
|
||||
0x48,
|
||||
0x8A,
|
||||
0x51,
|
||||
0xCA,
|
||||
0x53,
|
||||
0xCA,
|
||||
0x53,
|
||||
0x8A,
|
||||
0x51,
|
||||
0x12,
|
||||
0x48,
|
||||
0x84,
|
||||
0x21,
|
||||
0xC0,
|
||||
0x03,
|
||||
0x40,
|
||||
0x02,
|
||||
0x60,
|
||||
0x06,
|
||||
0x20,
|
||||
0x04,
|
||||
0x30,
|
||||
0x0C,
|
||||
0xF0,
|
||||
0x0F},
|
||||
/*.icon_color = */ ui::Color::orange().v,
|
||||
/*.menu_location = */ app_location_t::RX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
// this has to be the biggest baseband used by the app. SPEC
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_nfm */ {'P', 'W', 'F', 'M'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2018 Furrtek
|
||||
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_detector_rx.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "ui_freqman.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "file.hpp"
|
||||
#include "oversample.hpp"
|
||||
#include "ui_font_fixed_8x16.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
using namespace tonekey;
|
||||
using portapack::memory::map::backup_ram;
|
||||
|
||||
namespace ui::external_app::detector_rx {
|
||||
|
||||
// Function to map the value from one range to another
|
||||
int32_t DetectorRxView::map(int32_t value, int32_t fromLow, int32_t fromHigh, int32_t toLow, int32_t toHigh) {
|
||||
return toLow + (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow);
|
||||
}
|
||||
|
||||
void DetectorRxView::focus() {
|
||||
field_lna.focus();
|
||||
}
|
||||
|
||||
DetectorRxView::~DetectorRxView() {
|
||||
// reset performance counters request to default
|
||||
shared_memory.request_m4_performance_counter = 0;
|
||||
receiver_model.disable();
|
||||
audio::output::stop();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void DetectorRxView::on_timer() {
|
||||
freq_index++;
|
||||
if (freq_mode == 0 && freq_index >= tetra_uplink_monitoring_frequencies_hz.size()) freq_index = 0; // TETRA UP
|
||||
if (freq_mode == 1 && freq_index >= lora_monitoring_frequencies_hz.size()) freq_index = 0; // Lora
|
||||
if (freq_mode == 2 && freq_index >= remotes_monitoring_frequencies_hz.size()) freq_index = 0; // Remotes
|
||||
|
||||
if (freq_mode == 2)
|
||||
freq_ = remotes_monitoring_frequencies_hz[freq_index];
|
||||
else if (freq_mode == 1)
|
||||
freq_ = lora_monitoring_frequencies_hz[freq_index];
|
||||
else
|
||||
freq_ = tetra_uplink_monitoring_frequencies_hz[freq_index];
|
||||
receiver_model.set_target_frequency(freq_);
|
||||
}
|
||||
|
||||
DetectorRxView::DetectorRxView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({
|
||||
&labels,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
&field_rf_amp,
|
||||
&field_volume,
|
||||
&text_frequency,
|
||||
&freq_stats_rssi,
|
||||
&freq_stats_db,
|
||||
&text_beep_squelch,
|
||||
&field_beep_squelch,
|
||||
&rssi,
|
||||
&rssi_graph,
|
||||
&field_mode,
|
||||
});
|
||||
|
||||
// activate vertical bar mode
|
||||
rssi.set_vertical_rssi(true);
|
||||
|
||||
freq_ = receiver_model.target_frequency();
|
||||
|
||||
field_beep_squelch.set_value(beep_squelch);
|
||||
field_beep_squelch.on_change = [this](int32_t v) {
|
||||
beep_squelch = v;
|
||||
};
|
||||
|
||||
rssi_graph.set_nb_columns(256);
|
||||
|
||||
change_mode();
|
||||
rssi.set_peak(true, 3000);
|
||||
|
||||
// FILL STEP OPTIONS
|
||||
freq_stats_rssi.set_style(Theme::getInstance()->bg_darkest);
|
||||
freq_stats_db.set_style(Theme::getInstance()->bg_darkest);
|
||||
|
||||
field_mode.on_change = [this](size_t, int32_t value) {
|
||||
freq_mode = value;
|
||||
freq_index = 0;
|
||||
switch (value) {
|
||||
case 1: // Lora
|
||||
text_frequency.set(" 433, 868, 915 Mhz");
|
||||
break;
|
||||
case 2: // Remotes
|
||||
text_frequency.set(" 433, 315 Mhz");
|
||||
break;
|
||||
default:
|
||||
case 0: // TETRA UP
|
||||
text_frequency.set(" 380-390 Mhz");
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void DetectorRxView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
static int16_t last_max_db = 0;
|
||||
static uint8_t last_min_rssi = 0;
|
||||
static uint8_t last_avg_rssi = 0;
|
||||
static uint8_t last_max_rssi = 0;
|
||||
|
||||
rssi_graph.add_values(rssi.get_min(), rssi.get_avg(), rssi.get_max(), statistics.max_db);
|
||||
|
||||
// refresh db
|
||||
if (last_max_db != statistics.max_db) {
|
||||
last_max_db = statistics.max_db;
|
||||
freq_stats_db.set("Power: " + to_string_dec_int(statistics.max_db) + " db");
|
||||
rssi.set_db(statistics.max_db);
|
||||
}
|
||||
// refresh rssi
|
||||
if (last_min_rssi != rssi_graph.get_graph_min() || last_avg_rssi != rssi_graph.get_graph_avg() || last_max_rssi != rssi_graph.get_graph_max()) {
|
||||
last_min_rssi = rssi_graph.get_graph_min();
|
||||
last_avg_rssi = rssi_graph.get_graph_avg();
|
||||
last_max_rssi = rssi_graph.get_graph_max();
|
||||
freq_stats_rssi.set("RSSI: " + to_string_dec_uint(last_min_rssi) + "/" + to_string_dec_uint(last_avg_rssi) + "/" + to_string_dec_uint(last_max_rssi));
|
||||
}
|
||||
|
||||
if (statistics.max_db > beep_squelch) {
|
||||
baseband::request_audio_beep(map(statistics.max_db, -100, 20, 400, 2600), 24000, 150);
|
||||
}
|
||||
|
||||
} /* on_statistic_updates */
|
||||
|
||||
size_t DetectorRxView::change_mode() {
|
||||
audio::output::stop();
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
|
||||
audio_sampling_rate = audio::Rate::Hz_24000;
|
||||
baseband::run_image(portapack::spi_flash::image_tag_capture);
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::Capture);
|
||||
|
||||
baseband::set_sample_rate(DETECTOR_BW, get_oversample_rate(DETECTOR_BW));
|
||||
// The radio needs to know the effective sampling rate.
|
||||
auto actual_sampling_rate = get_actual_sample_rate(DETECTOR_BW);
|
||||
receiver_model.set_sampling_rate(actual_sampling_rate);
|
||||
receiver_model.set_baseband_bandwidth(filter_bandwidth_for_sampling_rate(actual_sampling_rate));
|
||||
|
||||
audio::set_rate(audio_sampling_rate);
|
||||
audio::output::start();
|
||||
receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack.
|
||||
|
||||
receiver_model.enable();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::detector_rx
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2018 Furrtek
|
||||
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef _UI_DETECTOR_RX
|
||||
#define _UI_DETECTOR_RX
|
||||
|
||||
#include "analog_audio_app.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "file.hpp"
|
||||
#include "freqman_db.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "receiver_model.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "ui.hpp"
|
||||
#include "ui_mictx.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_spectrum.hpp"
|
||||
|
||||
namespace ui::external_app::detector_rx {
|
||||
|
||||
#define DETECTOR_BW 750000
|
||||
|
||||
class DetectorRxView : public View {
|
||||
public:
|
||||
DetectorRxView(NavigationView& nav);
|
||||
~DetectorRxView();
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Detector RX"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
RxRadioState radio_state_{};
|
||||
|
||||
int32_t map(int32_t value, int32_t fromLow, int32_t fromHigh, int32_t toLow, int32_t toHigh);
|
||||
size_t change_mode();
|
||||
void on_statistics_update(const ChannelStatistics& statistics);
|
||||
void set_display_freq(int64_t freq);
|
||||
void on_timer();
|
||||
|
||||
uint8_t freq_index = 0;
|
||||
rf::Frequency freq_ = {433920000};
|
||||
int32_t beep_squelch = 0;
|
||||
audio::Rate audio_sampling_rate = audio::Rate::Hz_48000;
|
||||
uint8_t freq_mode = 0;
|
||||
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_detector",
|
||||
app_settings::Mode::RX,
|
||||
{
|
||||
{"beep_squelch"sv, &beep_squelch},
|
||||
}};
|
||||
|
||||
Labels labels{
|
||||
{{UI_POS_X(0), UI_POS_Y(0)}, "LNA: VGA: AMP: VOL: ", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
LNAGainField field_lna{
|
||||
{UI_POS_X(4), UI_POS_Y(0)}};
|
||||
|
||||
VGAGainField field_vga{
|
||||
{UI_POS_X(11), UI_POS_Y(0)}};
|
||||
|
||||
RFAmpField field_rf_amp{
|
||||
{UI_POS_X(18), UI_POS_Y(0)}};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{UI_POS_X(24), UI_POS_Y(0)}};
|
||||
|
||||
OptionsField field_mode{
|
||||
{UI_POS_X(0), UI_POS_Y(1)},
|
||||
9,
|
||||
{
|
||||
{"TETRA UP", 0},
|
||||
{"Lora", 1},
|
||||
{"Remotes", 2},
|
||||
}};
|
||||
|
||||
Text text_frequency{
|
||||
{UI_POS_X_RIGHT(20), UI_POS_Y(1), UI_POS_WIDTH(20), UI_POS_DEFAULT_HEIGHT},
|
||||
""};
|
||||
|
||||
Text text_beep_squelch{
|
||||
{UI_POS_X_RIGHT(9), UI_POS_Y(2), UI_POS_WIDTH(4), UI_POS_DEFAULT_HEIGHT},
|
||||
"Bip>"};
|
||||
|
||||
NumberField field_beep_squelch{
|
||||
{UI_POS_X_RIGHT(5), UI_POS_Y(2)},
|
||||
4,
|
||||
{-100, 20},
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
// RSSI: XX/XX/XXX
|
||||
Text freq_stats_rssi{
|
||||
{UI_POS_X(0), UI_POS_Y(2), UI_POS_WIDTH(15), UI_POS_DEFAULT_HEIGHT},
|
||||
};
|
||||
|
||||
// Power: -XXX db
|
||||
Text freq_stats_db{
|
||||
{UI_POS_X(0), UI_POS_Y(3), UI_POS_WIDTH(15), UI_POS_DEFAULT_HEIGHT},
|
||||
};
|
||||
|
||||
RSSIGraph rssi_graph{
|
||||
{UI_POS_X(0), UI_POS_Y(5), UI_POS_WIDTH_REMAINING(5), UI_POS_HEIGHT_REMAINING(6)},
|
||||
};
|
||||
|
||||
RSSI rssi{
|
||||
{UI_POS_X_RIGHT(5), UI_POS_Y(5), UI_POS_WIDTH(5), UI_POS_HEIGHT_REMAINING(6)},
|
||||
};
|
||||
|
||||
MessageHandlerRegistration message_handler_stats{
|
||||
Message::ID::ChannelStatistics,
|
||||
[this](const Message* const p) {
|
||||
this->on_statistics_update(static_cast<const ChannelStatisticsMessage*>(p)->statistics);
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
this->on_timer();
|
||||
}};
|
||||
|
||||
const std::vector<uint32_t> remotes_monitoring_frequencies_hz = {
|
||||
// Around 315 MHz (common for older remotes, key fobs in some regions)
|
||||
// Window centered on 315 MHz, covers 314.625 - 315.375 MHz
|
||||
315000000,
|
||||
|
||||
// Around 433.92 MHz (very common for remotes, sensors, key fobs globally)
|
||||
// Window centered on 433.92 MHz, covers 433.545 - 434.295 MHz
|
||||
433920000,
|
||||
};
|
||||
const std::vector<uint32_t> lora_monitoring_frequencies_hz = {
|
||||
// EU433 Band (Europe, typically 433.05 MHz to 434.79 MHz)
|
||||
// Scanning the approximate range 433.0 MHz to 434.8 MHz with 750kHz steps
|
||||
433375000, // Covers 433.000 - 433.750 MHz
|
||||
434125000, // Covers 433.750 - 434.500 MHz (includes 433.92 MHz)
|
||||
434875000, // Covers 434.500 - 435.250 MHz (covers up to 434.79 MHz)
|
||||
|
||||
// EU868 Band (Europe, typically 863 MHz to 870 MHz, specific channels around 868 MHz)
|
||||
// Targeting common LoRaWAN channel groups (approx 867.0 - 868.6 MHz) with 750kHz steps
|
||||
867375000, // Covers 867.000 - 867.750 MHz
|
||||
868125000, // Covers 867.750 - 868.500 MHz
|
||||
868875000, // Covers 868.500 - 869.250 MHz (covers up to 868.6 MHz)
|
||||
|
||||
// US915 Band (North America, typically 902 MHz to 928 MHz, specific channels around 915 MHz)
|
||||
// Providing a few sample windows around the 915 MHz area with 750kHz steps.
|
||||
// This band is wide; a full scan would require many more frequencies.
|
||||
914250000, // Covers 913.875 - 914.625 MHz
|
||||
915000000, // Covers 914.625 - 915.375 MHz (Centered on 915 MHz)
|
||||
915750000, // Covers 915.375 - 916.125 MHz
|
||||
};
|
||||
const std::vector<uint32_t> tetra_uplink_monitoring_frequencies_hz = {
|
||||
// Band starts at 380,000,000 Hz, ends at 390,000,000 Hz.
|
||||
// First center: 380,000,000 + 375,000 = 380,375,000 Hz
|
||||
// Last center: 380,375,000 + 13 * 750,000 = 390,125,000 Hz (14 frequencies total for this band)
|
||||
380375000, // Covers 380.000 - 380.750 MHz
|
||||
381125000, // Covers 380.750 - 381.500 MHz
|
||||
381875000, // Covers 381.500 - 382.250 MHz
|
||||
382625000, // Covers 382.250 - 383.000 MHz
|
||||
383375000, // Covers 383.000 - 383.750 MHz
|
||||
384125000, // Covers 383.750 - 384.500 MHz
|
||||
384875000, // Covers 384.500 - 385.250 MHz
|
||||
385625000, // Covers 385.250 - 386.000 MHz
|
||||
386375000, // Covers 386.000 - 386.750 MHz
|
||||
387125000, // Covers 386.750 - 387.500 MHz
|
||||
387875000, // Covers 387.500 - 388.250 MHz
|
||||
388625000, // Covers 388.250 - 389.000 MHz
|
||||
389375000, // Covers 389.000 - 389.750 MHz
|
||||
390125000, // Covers 389.750 - 390.500 MHz
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::detector_rx
|
||||
|
||||
#endif
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/*
|
||||
* Chrome Dino Game for Portapack Mayhem
|
||||
* Based on the original DinoGame by various contributors
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_dinogame.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::dinogame {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<DinoGameView>();
|
||||
}
|
||||
} // namespace ui::external_app::dinogame
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_dinogame.application_information"), used)) application_information_t _application_information_dinogame = {
|
||||
(uint8_t*)0x00000000,
|
||||
ui::external_app::dinogame::initialize_app,
|
||||
CURRENT_HEADER_VERSION,
|
||||
VERSION_MD5,
|
||||
|
||||
"Dino Game",
|
||||
{
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0xF0,
|
||||
0xF8,
|
||||
0xFC,
|
||||
0xFE,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0x7F,
|
||||
0x7F,
|
||||
0x3F,
|
||||
0x3F,
|
||||
0x3E,
|
||||
0x3C,
|
||||
0x38,
|
||||
0x30,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
},
|
||||
ui::Color::green().v,
|
||||
app_location_t::GAMES,
|
||||
-1,
|
||||
|
||||
{0, 0, 0, 0},
|
||||
0x00000000,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,447 @@
|
||||
/*
|
||||
standing - 34x36
|
||||
ducking - 46x24
|
||||
*/
|
||||
const uint16_t dino_default[] PROGMEM = {
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0030 (48)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0040 (64)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0050 (80)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0060 (96)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0070 (112)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0080 (128)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0090 (144)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x00A0 (160)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00B0 (176)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00C0 (192)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00D0 (208)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x00E0 (224)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00F0 (240)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0100 (256)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x0110 (272)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0120 (288)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0130 (304)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0140 (320)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0150 (336)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0160 (352)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0170 (368)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0180 (384)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0190 (400)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01A0 (416)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01C0 (448)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x01E0 (480)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01F0 (496)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0200 (512)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0220 (544)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0240 (576)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0250 (592)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0260 (608)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0270 (624)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0280 (640)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0290 (656)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02B0 (688)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x02C0 (704)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02D0 (720)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02E0 (736)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x02F0 (752)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0300 (768)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0310 (784)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0320 (800)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0340 (832)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0360 (864)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0380 (896)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x03A0 (928)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03B0 (944)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03C0 (960)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03D0 (976)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03E0 (992)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03F0 (1008)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0400 (1024)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0410 (1040)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0420 (1056)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0430 (1072)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0440 (1088)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0450 (1104)
|
||||
0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0460 (1120)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0470 (1136)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0480 (1152)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0490 (1168)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04A0 (1184)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04B0 (1200)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04C0 (1216)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
|
||||
|
||||
const uint16_t dino_leftstep[] PROGMEM = {
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0030 (48)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0040 (64)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0050 (80)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0060 (96)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0070 (112)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0080 (128)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0090 (144)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x00A0 (160)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00B0 (176)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00C0 (192)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00D0 (208)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x00E0 (224)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00F0 (240)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0100 (256)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x0110 (272)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0120 (288)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0130 (304)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0140 (320)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0150 (336)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0160 (352)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0170 (368)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0180 (384)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0190 (400)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01A0 (416)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01C0 (448)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x01E0 (480)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01F0 (496)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0200 (512)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0220 (544)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0240 (576)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0250 (592)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0260 (608)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0270 (624)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0280 (640)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0290 (656)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02B0 (688)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x02C0 (704)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02D0 (720)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02E0 (736)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x02F0 (752)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0300 (768)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0310 (784)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0320 (800)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0340 (832)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0360 (864)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0380 (896)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x03A0 (928)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03B0 (944)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03C0 (960)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03D0 (976)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03E0 (992)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03F0 (1008)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0400 (1024)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0410 (1040)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0420 (1056)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0430 (1072)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0440 (1088)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0450 (1104)
|
||||
0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0460 (1120)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0470 (1136)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0480 (1152)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0490 (1168)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04A0 (1184)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04B0 (1200)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04C0 (1216)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
|
||||
|
||||
const uint16_t dino_rightstep[] PROGMEM = {
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0030 (48)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0040 (64)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0050 (80)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0060 (96)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0070 (112)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0080 (128)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0090 (144)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x00A0 (160)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00B0 (176)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00C0 (192)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00D0 (208)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x00E0 (224)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00F0 (240)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0100 (256)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x0110 (272)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0120 (288)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0130 (304)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0140 (320)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0150 (336)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0160 (352)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0170 (368)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0180 (384)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0190 (400)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01A0 (416)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01C0 (448)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x01E0 (480)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01F0 (496)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0200 (512)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0220 (544)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0240 (576)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0250 (592)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0260 (608)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0270 (624)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0280 (640)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0290 (656)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02B0 (688)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x02C0 (704)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02D0 (720)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02E0 (736)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x02F0 (752)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0300 (768)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0310 (784)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0320 (800)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0340 (832)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0360 (864)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0380 (896)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x03A0 (928)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03B0 (944)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03C0 (960)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03D0 (976)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03E0 (992)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03F0 (1008)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0400 (1024)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0410 (1040)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0420 (1056)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0430 (1072)
|
||||
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0440 (1088)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0450 (1104)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0460 (1120)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0470 (1136)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0480 (1152)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0490 (1168)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04A0 (1184)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04B0 (1200)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04C0 (1216)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
|
||||
|
||||
const uint16_t dino_ducking_leftstep[] PROGMEM = {
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0030 (48)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0040 (64)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0050 (80)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x0060 (96)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0070 (112)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0080 (128)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0090 (144)
|
||||
0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00A0 (160)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00B0 (176)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00C0 (192)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00D0 (208)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00E0 (224)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00F0 (240)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0100 (256)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0110 (272)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0120 (288)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0130 (304)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x0140 (320)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0150 (336)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0160 (352)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0170 (368)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0180 (384)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0190 (400)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01A0 (416)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01C0 (448)
|
||||
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01E0 (480)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01F0 (496)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0200 (512)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0220 (544)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0240 (576)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0250 (592)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0260 (608)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0270 (624)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0280 (640)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0290 (656)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x02B0 (688)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02C0 (704)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02D0 (720)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x02E0 (736)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02F0 (752)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0300 (768)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0310 (784)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0320 (800)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0340 (832)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0360 (864)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0380 (896)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
|
||||
0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03A0 (928)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03B0 (944)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x03C0 (960)
|
||||
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03D0 (976)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
|
||||
|
||||
const uint16_t dino_ducking_rightstep[] PROGMEM = {
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0030 (48)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0040 (64)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0050 (80)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x0060 (96)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0070 (112)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0080 (128)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0090 (144)
|
||||
0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00A0 (160)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00B0 (176)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00C0 (192)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00D0 (208)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00E0 (224)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00F0 (240)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0100 (256)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0110 (272)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0120 (288)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0130 (304)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x0140 (320)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0150 (336)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0160 (352)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0170 (368)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0180 (384)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0190 (400)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01A0 (416)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01C0 (448)
|
||||
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01E0 (480)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01F0 (496)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0200 (512)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0220 (544)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0240 (576)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0250 (592)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0260 (608)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0270 (624)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0280 (640)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0290 (656)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02B0 (688)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02C0 (704)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02D0 (720)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x02E0 (736)
|
||||
0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02F0 (752)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0300 (768)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x0310 (784)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0320 (800)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0340 (832)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0360 (864)
|
||||
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0380 (896)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0390 (912)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03A0 (928)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03B0 (944)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03C0 (960)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03D0 (976)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
|
||||
|
||||
const uint16_t dino_gameover[] PROGMEM = {
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0030 (48)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0040 (64)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0050 (80)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0060 (96)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0070 (112)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0080 (128)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0090 (144)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0xFFFF, 0x528A, // 0x00A0 (160)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00B0 (176)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x00C0 (192)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00D0 (208)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x00E0 (224)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00F0 (240)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0100 (256)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x0110 (272)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0120 (288)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0130 (304)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0140 (320)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0150 (336)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0160 (352)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0170 (368)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0180 (384)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0190 (400)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01A0 (416)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01C0 (448)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x01E0 (480)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01F0 (496)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0200 (512)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0220 (544)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0240 (576)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0250 (592)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0260 (608)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0270 (624)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0280 (640)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0290 (656)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02B0 (688)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x02C0 (704)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02D0 (720)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02E0 (736)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x02F0 (752)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0300 (768)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0310 (784)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0320 (800)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0340 (832)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0360 (864)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0380 (896)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x03A0 (928)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03B0 (944)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03C0 (960)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03D0 (976)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03E0 (992)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03F0 (1008)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0400 (1024)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0410 (1040)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0420 (1056)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0430 (1072)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0440 (1088)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0450 (1104)
|
||||
0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0460 (1120)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0470 (1136)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0480 (1152)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0490 (1168)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04A0 (1184)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04B0 (1200)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04C0 (1216)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
|
||||
@@ -0,0 +1,121 @@
|
||||
// 34x27
|
||||
|
||||
const uint16_t pterodactyl_upflop[] PROGMEM = {
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0010 (16)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0030 (48)
|
||||
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0040 (64)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0050 (80)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0060 (96)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0070 (112)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0080 (128)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x0090 (144)
|
||||
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00A0 (160)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x00B0 (176)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00C0 (192)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x00D0 (208)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x00E0 (224)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00F0 (240)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0100 (256)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0110 (272)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0120 (288)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0130 (304)
|
||||
0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0140 (320)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0150 (336)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0160 (352)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0170 (368)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0180 (384)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0190 (400)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01A0 (416)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01C0 (448)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01E0 (480)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01F0 (496)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0200 (512)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0220 (544)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0240 (576)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0250 (592)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0260 (608)
|
||||
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0270 (624)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0280 (640)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0290 (656)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02B0 (688)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02C0 (704)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02D0 (720)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02E0 (736)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02F0 (752)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0300 (768)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0310 (784)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0320 (800)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0340 (832)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0360 (864)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0380 (896)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
|
||||
|
||||
const uint16_t pterodactyl_downflop[] PROGMEM = {
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0030 (48)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0040 (64)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0050 (80)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0060 (96)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0070 (112)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0080 (128)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x0090 (144)
|
||||
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00A0 (160)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x00B0 (176)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00C0 (192)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x00D0 (208)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00E0 (224)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00F0 (240)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0100 (256)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0110 (272)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0120 (288)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0130 (304)
|
||||
0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0140 (320)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0150 (336)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0160 (352)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0170 (368)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0180 (384)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0190 (400)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01A0 (416)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01C0 (448)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01E0 (480)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01F0 (496)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0200 (512)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0220 (544)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0240 (576)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0250 (592)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0260 (608)
|
||||
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0270 (624)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0280 (640)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0290 (656)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02B0 (688)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02C0 (704)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02D0 (720)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02E0 (736)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02F0 (752)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0300 (768)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0310 (784)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0320 (800)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0340 (832)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0360 (864)
|
||||
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0380 (896)
|
||||
0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
|
||||
@@ -0,0 +1,808 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/*
|
||||
* Chrome Dino Game for Portapack Mayhem
|
||||
* Based on the original DinoGame by various contributors
|
||||
*/
|
||||
|
||||
#include "ui_dinogame.hpp"
|
||||
|
||||
namespace ui::external_app::dinogame {
|
||||
|
||||
#define PROGMEM
|
||||
#include "sprites/dino.h"
|
||||
#include "sprites/pterodactyl.h"
|
||||
|
||||
#undef PROGMEM
|
||||
|
||||
// Global variables
|
||||
Ticker game_timer;
|
||||
Painter painter;
|
||||
static Callback game_update_callback = nullptr;
|
||||
static uint32_t game_update_timeout = 0;
|
||||
static uint32_t game_update_counter = 0;
|
||||
static DinoGameView* current_instance = nullptr;
|
||||
|
||||
const Color pp_colors[] = {
|
||||
Color::white(),
|
||||
Color::blue(),
|
||||
Color::yellow(),
|
||||
Color::purple(),
|
||||
Color::green(),
|
||||
Color::red(),
|
||||
Color::magenta(),
|
||||
Color::orange(),
|
||||
Color::black(),
|
||||
};
|
||||
|
||||
// Drawing functions
|
||||
void cls() {
|
||||
painter.fill_rectangle({0, 0, portapack::display.width(), portapack::display.height()}, Color::black());
|
||||
}
|
||||
|
||||
void fillrect(int x1, int y1, int x2, int y2, int color) {
|
||||
painter.fill_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
|
||||
}
|
||||
|
||||
void rect(int x1, int y1, int x2, int y2, int color) {
|
||||
painter.draw_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
|
||||
}
|
||||
|
||||
// Timer implementation
|
||||
void check_game_timer() {
|
||||
if (game_update_callback) {
|
||||
if (++game_update_counter >= game_update_timeout) {
|
||||
game_update_counter = 0;
|
||||
game_update_callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Ticker::attach(Callback func, double delay_sec) {
|
||||
game_update_callback = func;
|
||||
game_update_timeout = delay_sec * 60;
|
||||
}
|
||||
|
||||
void Ticker::detach() {
|
||||
game_update_callback = nullptr;
|
||||
}
|
||||
|
||||
// String helper
|
||||
std::string DinoGameView::score_to_string(uint32_t score) {
|
||||
std::string score_s = std::to_string(score);
|
||||
if (score_s.length() < 5) {
|
||||
std::string temp = "";
|
||||
for (uint8_t i = 0; i < 5 - score_s.length(); ++i) temp += "0";
|
||||
temp += score_s;
|
||||
score_s = temp;
|
||||
}
|
||||
return score_s;
|
||||
}
|
||||
|
||||
// Game timer callback
|
||||
void game_timer_check() {
|
||||
if (current_instance) {
|
||||
if (current_instance->game_state == GameState::PLAYING) {
|
||||
current_instance->game_loop();
|
||||
} else if (current_instance->game_state == GameState::MENU) {
|
||||
current_instance->show_menu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DinoGameView::DinoGameView(NavigationView& nav)
|
||||
: nav_{nav}, bird_info{}, game_timer{} {
|
||||
add_children({&dummy, &button_difficulty});
|
||||
current_instance = this;
|
||||
game_timer.attach(&game_timer_check, 1.0 / 60.0);
|
||||
|
||||
button_difficulty.on_select = [this](Button&) {
|
||||
easy_mode = !easy_mode;
|
||||
button_difficulty.set_text(easy_mode ? "Mode: EASY" : "Mode: HARD");
|
||||
};
|
||||
}
|
||||
|
||||
void DinoGameView::on_show() {
|
||||
}
|
||||
|
||||
void DinoGameView::paint(Painter& painter) {
|
||||
(void)painter;
|
||||
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
std::srand(LPC_RTC->CTIME0);
|
||||
init_game();
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::frame_sync() {
|
||||
check_game_timer();
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void DinoGameView::init_game() {
|
||||
game_state = GameState::MENU;
|
||||
menu_initialized = false;
|
||||
blink_state = true;
|
||||
blink_counter = 0;
|
||||
new_game();
|
||||
}
|
||||
|
||||
void DinoGameView::new_game() {
|
||||
steps = 0;
|
||||
score = 0;
|
||||
collided = false;
|
||||
ducking = false;
|
||||
duck_timer = 0;
|
||||
displayedGameOver = false;
|
||||
bird_info.inGame = false;
|
||||
bird_info.x_offset = 320;
|
||||
bird_info.y_offset = 0;
|
||||
bird_info.y_velocity = 0;
|
||||
jumping = false;
|
||||
falling = false;
|
||||
jumpHeight = 0;
|
||||
last_dino_y = DINO_Y;
|
||||
last_bird_x = -1;
|
||||
last_bird_y = -1;
|
||||
last_ducking = false;
|
||||
last_runstate = false;
|
||||
ground_offset = 0;
|
||||
speed_modifier = 0;
|
||||
runstate = false;
|
||||
score_drawn = false;
|
||||
last_score = 999999;
|
||||
obstacle_spawn_timer = 100; // Initial delay before first obstacle
|
||||
|
||||
// Initialize obstacles
|
||||
for (int i = 0; i < MAX_OBSTACLES; i++) {
|
||||
obstacles[i].active = false;
|
||||
obstacles[i].x = -100;
|
||||
obstacles[i].last_x = -100;
|
||||
}
|
||||
|
||||
cls();
|
||||
}
|
||||
|
||||
void DinoGameView::show_menu() {
|
||||
if (!menu_initialized) {
|
||||
cls();
|
||||
|
||||
auto style = *ui::Theme::getInstance()->fg_medium;
|
||||
auto style_title = *ui::Theme::getInstance()->fg_light;
|
||||
|
||||
// Draw title
|
||||
painter.draw_string({80, 60}, style_title, "DINO GAME");
|
||||
|
||||
// Draw instructions
|
||||
painter.draw_string({45, 130}, style, "SELECT: Jump/Start");
|
||||
painter.draw_string({65, 150}, style, "DOWN: Duck");
|
||||
painter.draw_string({50, 170}, style, "Avoid obstacles!");
|
||||
|
||||
// Draw high score
|
||||
draw_high_score();
|
||||
|
||||
menu_initialized = true;
|
||||
}
|
||||
|
||||
// Show difficulty button
|
||||
button_difficulty.hidden(false);
|
||||
|
||||
// Animate the menu dino
|
||||
bool menu_run_frame = (blink_counter / 15) % 2;
|
||||
|
||||
// Clear previous dino position
|
||||
fillrect(103, 90, 103 + DINO_WIDTH, 90 + DINO_HEIGHT, Black);
|
||||
|
||||
// Draw animated dino
|
||||
draw_dino_at(103, 90, false, menu_run_frame);
|
||||
|
||||
// Blinking start prompt
|
||||
auto style_prompt = *ui::Theme::getInstance()->fg_light;
|
||||
if (++blink_counter >= 30) {
|
||||
blink_counter = 0;
|
||||
blink_state = !blink_state;
|
||||
|
||||
painter.fill_rectangle({55, 258, 130, 20}, Color::black());
|
||||
if (blink_state) {
|
||||
painter.draw_string({55, 260}, style_prompt, "* PRESS SELECT *");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::show_game_over() {
|
||||
if (!displayedGameOver) {
|
||||
displayedGameOver = true;
|
||||
|
||||
// Clear the last normal dino position
|
||||
if (last_ducking) {
|
||||
fillrect(DINO_X, last_dino_y, DINO_X + DINO_DUCK_WIDTH, last_dino_y + DINO_DUCK_HEIGHT, Black);
|
||||
} else {
|
||||
fillrect(DINO_X, last_dino_y, DINO_X + DINO_WIDTH, last_dino_y + DINO_HEIGHT, Black);
|
||||
}
|
||||
|
||||
// Draw the game over dino sprite
|
||||
draw_dino_sprite(DINO_X, DINO_Y, dino_gameover);
|
||||
|
||||
auto style = *ui::Theme::getInstance()->fg_light;
|
||||
auto style_score = *ui::Theme::getInstance()->fg_medium;
|
||||
|
||||
// Game over text
|
||||
painter.draw_string({85, 70}, style, "GAME OVER");
|
||||
|
||||
// Show final score
|
||||
std::string score_text = "SCORE: " + score_to_string(score);
|
||||
int score_x = (240 - score_text.length() * 8) / 2;
|
||||
painter.draw_string({score_x, 90}, style_score, score_text);
|
||||
|
||||
painter.draw_string({65, 110}, style, "SELECT TO RETRY");
|
||||
|
||||
// Update high score
|
||||
if (score > highScore) {
|
||||
highScore = score;
|
||||
painter.draw_string({55, 130}, style, "NEW HIGH SCORE!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::game_loop() {
|
||||
button_difficulty.hidden(true);
|
||||
|
||||
if (collided && game_state == GameState::PLAYING) {
|
||||
game_state = GameState::GAME_OVER;
|
||||
show_game_over();
|
||||
return;
|
||||
}
|
||||
|
||||
// Update ground animation
|
||||
ground_offset = (ground_offset + GAME_SPEED_BASE + speed_modifier) % 20;
|
||||
|
||||
// Clear only the game area (not the whole screen to reduce flicker)
|
||||
draw_ground();
|
||||
|
||||
// Update and draw obstacles
|
||||
update_obstacles();
|
||||
|
||||
// Manage bird
|
||||
manage_bird();
|
||||
|
||||
// Handle jumping
|
||||
if (jumping) {
|
||||
if (!falling) jumpHeight += JUMP_SPEED + speed_modifier;
|
||||
if (jumpHeight > JUMP_MAX_HEIGHT && !falling) falling = true;
|
||||
if (falling) jumpHeight -= JUMP_SPEED + speed_modifier;
|
||||
if (jumpHeight < 0) {
|
||||
falling = false;
|
||||
jumping = false;
|
||||
jumpHeight = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle auto-stand from duck
|
||||
if (ducking && duck_timer > 0) {
|
||||
duck_timer--;
|
||||
if (duck_timer == 0) {
|
||||
stand();
|
||||
}
|
||||
}
|
||||
|
||||
// Update run animation
|
||||
if (get_steps() % (10 - speed_modifier) == 0) runstate = !runstate;
|
||||
|
||||
// Draw dino with minimal redraw
|
||||
int current_dino_y = jumping ? (DINO_Y - jumpHeight) : (ducking ? DINO_DUCK_Y : DINO_Y);
|
||||
|
||||
// Clear old dino position more precisely
|
||||
if (current_dino_y != last_dino_y || runstate != last_runstate || ducking != last_ducking) {
|
||||
// Clear based on last state
|
||||
if (last_ducking) {
|
||||
fillrect(DINO_X, last_dino_y, DINO_X + DINO_DUCK_WIDTH, last_dino_y + DINO_DUCK_HEIGHT, Black);
|
||||
} else {
|
||||
fillrect(DINO_X, last_dino_y, DINO_X + DINO_WIDTH, last_dino_y + DINO_HEIGHT, Black);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw dino at new position
|
||||
if (jumping) {
|
||||
draw_dino_sprite(DINO_X, current_dino_y, dino_default);
|
||||
} else if (ducking) {
|
||||
if (runstate)
|
||||
draw_dino_sprite(DINO_X, current_dino_y, dino_ducking_leftstep);
|
||||
else
|
||||
draw_dino_sprite(DINO_X, current_dino_y, dino_ducking_rightstep);
|
||||
} else {
|
||||
if (runstate)
|
||||
draw_dino_sprite(DINO_X, current_dino_y, dino_leftstep);
|
||||
else
|
||||
draw_dino_sprite(DINO_X, current_dino_y, dino_rightstep);
|
||||
}
|
||||
|
||||
// Update last state
|
||||
last_dino_y = current_dino_y;
|
||||
last_ducking = ducking;
|
||||
last_runstate = runstate;
|
||||
|
||||
// Check collisions
|
||||
check_collision();
|
||||
|
||||
// Update score
|
||||
score = get_steps() / 10;
|
||||
if (score % 100 == 0 && score > 0 && get_steps() % 1000 == 0) {
|
||||
if (speed_modifier < 5) speed_modifier++;
|
||||
}
|
||||
|
||||
step();
|
||||
draw_current_score();
|
||||
draw_high_score();
|
||||
}
|
||||
|
||||
void DinoGameView::draw_ground() {
|
||||
int ground_y = GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT;
|
||||
|
||||
// Clear ground area
|
||||
fillrect(0, ground_y, 320, ground_y + GROUND_HEIGHT, Black);
|
||||
|
||||
// Draw ground line
|
||||
painter.draw_hline({0, ground_y}, 320, Color::white());
|
||||
painter.draw_hline({0, ground_y + 1}, 320, Color::dark_grey());
|
||||
|
||||
// Draw ground texture
|
||||
for (int x = -ground_offset; x < 320; x += 20) {
|
||||
painter.draw_hline({x, ground_y + 3}, 10, Color::dark_grey());
|
||||
painter.draw_hline({x + 5, ground_y + 5}, 5, Color::dark_grey());
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::update_obstacles() {
|
||||
// Move obstacles
|
||||
for (int i = 0; i < MAX_OBSTACLES; i++) {
|
||||
if (obstacles[i].active) {
|
||||
// Clear old position
|
||||
if (obstacles[i].last_x != obstacles[i].x && obstacles[i].last_x < 320) {
|
||||
clear_obstacle_area(obstacles[i].last_x, obstacles[i].width + 10, obstacles[i].height + 10);
|
||||
}
|
||||
|
||||
obstacles[i].last_x = obstacles[i].x;
|
||||
obstacles[i].x -= GAME_SPEED_BASE + speed_modifier;
|
||||
|
||||
// Remove off-screen obstacles
|
||||
if (obstacles[i].x < -50) {
|
||||
obstacles[i].active = false;
|
||||
clear_obstacle_area(obstacles[i].last_x, obstacles[i].width + 10, obstacles[i].height + 10);
|
||||
} else {
|
||||
// Draw obstacle at new position
|
||||
draw_obstacle(obstacles[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Decrement spawn timer
|
||||
if (obstacle_spawn_timer > 0) {
|
||||
obstacle_spawn_timer -= GAME_SPEED_BASE + speed_modifier;
|
||||
}
|
||||
|
||||
// Check if we should spawn a new obstacle
|
||||
if (obstacle_spawn_timer <= 0) {
|
||||
// Try to spawn an obstacle
|
||||
bool spawned = false;
|
||||
for (int i = 0; i < MAX_OBSTACLES; i++) {
|
||||
if (!obstacles[i].active) {
|
||||
obstacles[i].active = true;
|
||||
obstacles[i].x = 320;
|
||||
obstacles[i].last_x = 320;
|
||||
obstacles[i].type = rand() % 4;
|
||||
|
||||
// Set obstacle dimensions based on type
|
||||
switch (obstacles[i].type) {
|
||||
case 0: // Small cactus
|
||||
obstacles[i].width = 15;
|
||||
obstacles[i].height = 25;
|
||||
break;
|
||||
case 1: // Large cactus
|
||||
obstacles[i].width = 20;
|
||||
obstacles[i].height = 35;
|
||||
break;
|
||||
case 2: // Double cactus
|
||||
obstacles[i].width = 35;
|
||||
obstacles[i].height = 30;
|
||||
break;
|
||||
case 3: // Triple cactus
|
||||
obstacles[i].width = 45;
|
||||
obstacles[i].height = 28;
|
||||
break;
|
||||
}
|
||||
|
||||
spawned = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (spawned) {
|
||||
// Set timer for next obstacle with random variation
|
||||
obstacle_spawn_timer = MIN_OBSTACLE_DISTANCE + (rand() % (MAX_OBSTACLE_DISTANCE - MIN_OBSTACLE_DISTANCE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::clear_obstacle_area(int x, int width, int height) {
|
||||
int ground_y = GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT;
|
||||
// Only clear the obstacle area, not the entire vertical space
|
||||
fillrect(x - 5, ground_y - height - 5, x + width + 5, ground_y, Black);
|
||||
}
|
||||
|
||||
void DinoGameView::draw_obstacle(const SimpleObstacle& obstacle) {
|
||||
if (!obstacle.active) return;
|
||||
|
||||
int ground_y = GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT;
|
||||
int y = ground_y - obstacle.height;
|
||||
|
||||
// Draw cactus with green color
|
||||
switch (obstacle.type) {
|
||||
case 0: // Small single cactus
|
||||
fillrect(obstacle.x + 5, y, obstacle.x + 10, ground_y, Green);
|
||||
fillrect(obstacle.x, y + 8, obstacle.x + 5, y + 15, Green);
|
||||
fillrect(obstacle.x + 10, y + 12, obstacle.x + 15, y + 19, Green);
|
||||
// Add darker edges for definition
|
||||
painter.draw_vline({obstacle.x + 4, y + 8}, 7, Color::dark_green());
|
||||
painter.draw_vline({obstacle.x + 10, y + 12}, 7, Color::dark_green());
|
||||
break;
|
||||
|
||||
case 1: // Large single cactus
|
||||
fillrect(obstacle.x + 7, y, obstacle.x + 13, ground_y, Green);
|
||||
fillrect(obstacle.x, y + 10, obstacle.x + 7, y + 20, Green);
|
||||
fillrect(obstacle.x + 13, y + 15, obstacle.x + 20, y + 25, Green);
|
||||
fillrect(obstacle.x + 5, y + 5, obstacle.x + 7, y + 12, Green);
|
||||
fillrect(obstacle.x + 13, y + 8, obstacle.x + 15, y + 15, Green);
|
||||
// Add darker edges
|
||||
painter.draw_vline({obstacle.x + 6, y}, ground_y - y, Color::dark_green());
|
||||
painter.draw_vline({obstacle.x + 13, y}, ground_y - y, Color::dark_green());
|
||||
break;
|
||||
|
||||
case 2: // Double cactus
|
||||
fillrect(obstacle.x + 5, y + 5, obstacle.x + 10, ground_y, Green);
|
||||
fillrect(obstacle.x + 20, y, obstacle.x + 25, ground_y, Green);
|
||||
fillrect(obstacle.x, y + 12, obstacle.x + 5, y + 18, Green);
|
||||
fillrect(obstacle.x + 10, y + 15, obstacle.x + 15, y + 22, Green);
|
||||
fillrect(obstacle.x + 25, y + 10, obstacle.x + 30, y + 17, Green);
|
||||
// Darker outlines
|
||||
painter.draw_vline({obstacle.x + 4, y + 5}, ground_y - y - 5, Color::dark_green());
|
||||
painter.draw_vline({obstacle.x + 19, y}, ground_y - y, Color::dark_green());
|
||||
break;
|
||||
|
||||
case 3: // Triple cactus
|
||||
fillrect(obstacle.x + 5, y + 8, obstacle.x + 10, ground_y, Green);
|
||||
fillrect(obstacle.x + 20, y, obstacle.x + 25, ground_y, Green);
|
||||
fillrect(obstacle.x + 35, y + 5, obstacle.x + 40, ground_y, Green);
|
||||
fillrect(obstacle.x, y + 15, obstacle.x + 5, y + 20, Green);
|
||||
fillrect(obstacle.x + 25, y + 8, obstacle.x + 30, y + 15, Green);
|
||||
fillrect(obstacle.x + 40, y + 12, obstacle.x + 45, y + 18, Green);
|
||||
// Darker outlines
|
||||
painter.draw_vline({obstacle.x + 4, y + 8}, ground_y - y - 8, Color::dark_green());
|
||||
painter.draw_vline({obstacle.x + 19, y}, ground_y - y, Color::dark_green());
|
||||
painter.draw_vline({obstacle.x + 34, y + 5}, ground_y - y - 5, Color::dark_green());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::manage_bird() {
|
||||
if (!bird_info.inGame) {
|
||||
// Only spawn bird if no obstacles are too close
|
||||
bool obstacle_nearby = false;
|
||||
for (int i = 0; i < MAX_OBSTACLES; i++) {
|
||||
if (obstacles[i].active && obstacles[i].x > 200) {
|
||||
obstacle_nearby = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Randomly spawn bird if no nearby obstacles
|
||||
if (!obstacle_nearby && rand() % 400 == 0 && get_steps() > 100) {
|
||||
bird_info.inGame = true;
|
||||
bird_info.x_offset = 320;
|
||||
bird_info.y_offset = 0;
|
||||
bird_info.y_velocity = easy_mode ? 0 : (rand() % 3) - 1; // No vertical movement in easy mode
|
||||
bird_info.y_position = BirdPosition::DOWN; // Always spawn at standing height in easy mode
|
||||
}
|
||||
} else {
|
||||
// Calculate bird Y position
|
||||
int base_y = (bird_info.y_position == BirdPosition::UP) ? BIRD_Y_UP : BIRD_Y_DOWN;
|
||||
int current_y = base_y + bird_info.y_offset;
|
||||
|
||||
// Clear previous bird position
|
||||
if (last_bird_x < 320 && last_bird_x >= -BIRD_WIDTH) {
|
||||
int clear_start_x = last_bird_x;
|
||||
int clear_end_x = last_bird_x + BIRD_WIDTH;
|
||||
|
||||
if (clear_start_x < 0) clear_start_x = 0;
|
||||
if (clear_end_x > 320) clear_end_x = 320;
|
||||
|
||||
if (clear_end_x > clear_start_x) {
|
||||
fillrect(clear_start_x, last_bird_y, clear_end_x, last_bird_y + BIRD_HEIGHT, Black);
|
||||
}
|
||||
}
|
||||
|
||||
// Move bird
|
||||
bird_info.x_offset -= GAME_SPEED_BASE + speed_modifier + 1;
|
||||
|
||||
// Update vertical position only in hard mode
|
||||
if (!easy_mode) {
|
||||
bird_info.y_offset += bird_info.y_velocity;
|
||||
if (rand() % 30 == 0) {
|
||||
bird_info.y_velocity = (rand() % 3) - 1;
|
||||
}
|
||||
|
||||
// Keep bird within bounds
|
||||
if (current_y < GAME_AREA_TOP + 10) {
|
||||
current_y = GAME_AREA_TOP + 10;
|
||||
bird_info.y_offset = current_y - base_y;
|
||||
bird_info.y_velocity = 1;
|
||||
} else if (current_y > GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT - BIRD_HEIGHT - 10) {
|
||||
current_y = GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT - BIRD_HEIGHT - 10;
|
||||
bird_info.y_offset = current_y - base_y;
|
||||
bird_info.y_velocity = -1;
|
||||
}
|
||||
} else {
|
||||
// In easy mode, keep bird at perfect ducking height
|
||||
// Bird should hit standing dino but miss ducking dino
|
||||
current_y = DINO_Y - 10; // Position bird just above ducking height
|
||||
}
|
||||
|
||||
// Animate wings
|
||||
if (get_steps() % 15 == 0) bird_info.flop = !bird_info.flop;
|
||||
|
||||
// Draw bird only if any part is visible
|
||||
if (bird_info.x_offset > -BIRD_WIDTH && bird_info.x_offset < 320) {
|
||||
if (bird_info.flop) {
|
||||
draw_bird_sprite(bird_info.x_offset, current_y, pterodactyl_upflop);
|
||||
} else {
|
||||
draw_bird_sprite(bird_info.x_offset, current_y, pterodactyl_downflop);
|
||||
}
|
||||
}
|
||||
|
||||
// Update last position
|
||||
last_bird_x = bird_info.x_offset;
|
||||
last_bird_y = current_y;
|
||||
|
||||
// Remove bird only when completely off screen
|
||||
if (bird_info.x_offset < -BIRD_WIDTH - 5) {
|
||||
bird_info.inGame = false;
|
||||
last_bird_x = -1;
|
||||
last_bird_y = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::draw_dino_sprite(int x, int y, const uint16_t* sprite) {
|
||||
int height, width;
|
||||
|
||||
// Determine dimensions based on which sprite we're drawing
|
||||
if (sprite == dino_ducking_leftstep || sprite == dino_ducking_rightstep) {
|
||||
height = DINO_DUCK_HEIGHT;
|
||||
width = DINO_DUCK_WIDTH;
|
||||
} else {
|
||||
height = DINO_HEIGHT;
|
||||
width = DINO_WIDTH;
|
||||
}
|
||||
|
||||
for (int dy = 0; dy < height; dy++) {
|
||||
int run_start = -1;
|
||||
uint16_t run_color = 0;
|
||||
|
||||
for (int dx = 0; dx < width; dx++) {
|
||||
uint16_t pixel = sprite[dy * width + dx];
|
||||
|
||||
if (pixel != TRANSPARENT_COLOR) {
|
||||
if (run_start == -1 || pixel != run_color) {
|
||||
// Draw previous run if any
|
||||
if (run_start != -1) {
|
||||
painter.fill_rectangle({x + run_start, y + dy, dx - run_start, 1}, Color(run_color));
|
||||
}
|
||||
run_start = dx;
|
||||
run_color = pixel;
|
||||
}
|
||||
} else {
|
||||
// Draw previous run if any
|
||||
if (run_start != -1) {
|
||||
painter.fill_rectangle({x + run_start, y + dy, dx - run_start, 1}, Color(run_color));
|
||||
run_start = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw final run if any
|
||||
if (run_start != -1) {
|
||||
painter.fill_rectangle({x + run_start, y + dy, width - run_start, 1}, Color(run_color));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::draw_bird_sprite(int x, int y, const uint16_t* sprite) {
|
||||
for (int dy = 0; dy < BIRD_HEIGHT; dy++) {
|
||||
int run_start = -1;
|
||||
uint16_t run_color = 0;
|
||||
|
||||
for (int dx = 0; dx < BIRD_WIDTH; dx++) {
|
||||
// Skip pixels that would be off-screen
|
||||
if (x + dx < 0 || x + dx >= 320) {
|
||||
// End any current run
|
||||
if (run_start != -1) {
|
||||
painter.fill_rectangle({x + run_start, y + dy, dx - run_start, 1}, Color(run_color));
|
||||
run_start = -1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
uint16_t pixel = sprite[dy * BIRD_WIDTH + dx];
|
||||
|
||||
if (pixel != TRANSPARENT_COLOR) {
|
||||
if (pixel == SPRITE_COLOR) {
|
||||
pixel = Color::red().v;
|
||||
}
|
||||
|
||||
if (run_start == -1 || pixel != run_color) {
|
||||
if (run_start != -1) {
|
||||
painter.fill_rectangle({x + run_start, y + dy, dx - run_start, 1}, Color(run_color));
|
||||
}
|
||||
run_start = dx;
|
||||
run_color = pixel;
|
||||
}
|
||||
} else {
|
||||
if (run_start != -1) {
|
||||
painter.fill_rectangle({x + run_start, y + dy, dx - run_start, 1}, Color(run_color));
|
||||
run_start = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (run_start != -1 && x + run_start < 320) {
|
||||
int width = BIRD_WIDTH - run_start;
|
||||
if (x + run_start + width > 320) {
|
||||
width = 320 - (x + run_start);
|
||||
}
|
||||
if (width > 0) {
|
||||
painter.fill_rectangle({x + run_start, y + dy, width, 1}, Color(run_color));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::draw_dino_at(int x, int y, bool is_ducking, bool run_frame) {
|
||||
if (is_ducking) {
|
||||
if (run_frame) {
|
||||
draw_dino_sprite(x, y, dino_ducking_leftstep);
|
||||
} else {
|
||||
draw_dino_sprite(x, y, dino_ducking_rightstep);
|
||||
}
|
||||
} else {
|
||||
if (run_frame) {
|
||||
draw_dino_sprite(x, y, dino_leftstep);
|
||||
} else {
|
||||
draw_dino_sprite(x, y, dino_default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::check_collision() {
|
||||
int dino_left = DINO_X;
|
||||
int dino_right = DINO_X + (ducking ? DINO_DUCK_WIDTH : DINO_WIDTH);
|
||||
int dino_top = ducking ? DINO_DUCK_Y : (DINO_Y - jumpHeight);
|
||||
int dino_bottom = dino_top + (ducking ? DINO_DUCK_HEIGHT : DINO_HEIGHT);
|
||||
|
||||
// Give a small hitbox reduction for fairness
|
||||
dino_left += 5;
|
||||
dino_right -= 5;
|
||||
dino_top += 5;
|
||||
dino_bottom -= 5;
|
||||
|
||||
// Check cactus collisions
|
||||
for (int i = 0; i < MAX_OBSTACLES; i++) {
|
||||
if (obstacles[i].active) {
|
||||
int ground_y = GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT;
|
||||
int obs_top = ground_y - obstacles[i].height;
|
||||
int obs_bottom = ground_y;
|
||||
|
||||
if (dino_right > obstacles[i].x &&
|
||||
dino_left < obstacles[i].x + obstacles[i].width &&
|
||||
dino_bottom > obs_top &&
|
||||
dino_top < obs_bottom) {
|
||||
collided = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check bird collision
|
||||
if (bird_info.inGame) {
|
||||
int base_y = (bird_info.y_position == BirdPosition::UP) ? BIRD_Y_UP : BIRD_Y_DOWN;
|
||||
int bird_y = base_y + bird_info.y_offset;
|
||||
|
||||
if (dino_right > bird_info.x_offset + 5 &&
|
||||
dino_left < bird_info.x_offset + BIRD_WIDTH - 5 &&
|
||||
dino_bottom > bird_y + 5 &&
|
||||
dino_top < bird_y + BIRD_HEIGHT - 5) {
|
||||
collided = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::draw_current_score() {
|
||||
auto style = *ui::Theme::getInstance()->fg_medium;
|
||||
|
||||
if (last_score != score) {
|
||||
painter.fill_rectangle({10, 28, 60, 14}, Color::black());
|
||||
painter.draw_string({10, 30}, style, score_to_string(score));
|
||||
last_score = score;
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::draw_high_score() {
|
||||
auto style = *ui::Theme::getInstance()->fg_light;
|
||||
painter.fill_rectangle({152, 28, 100, 14}, Color::black());
|
||||
painter.draw_string({152, 30}, style, "HI " + score_to_string(highScore));
|
||||
}
|
||||
|
||||
void DinoGameView::jump() {
|
||||
if (!jumping) {
|
||||
jumping = true;
|
||||
// If we're ducking when we jump, stand up
|
||||
if (ducking) {
|
||||
stand();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::duck() {
|
||||
if (!jumping) {
|
||||
ducking = true;
|
||||
duck_timer = 60; // 1 second at 60 FPS
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::stand() {
|
||||
ducking = false;
|
||||
duck_timer = 0;
|
||||
}
|
||||
|
||||
void DinoGameView::step() {
|
||||
++steps;
|
||||
}
|
||||
|
||||
bool DinoGameView::on_key(const KeyEvent key) {
|
||||
if (key == KeyEvent::Select) {
|
||||
if (game_state == GameState::MENU) {
|
||||
game_state = GameState::PLAYING;
|
||||
new_game();
|
||||
draw_high_score();
|
||||
} else if (game_state == GameState::PLAYING && !collided) {
|
||||
jump();
|
||||
} else if (game_state == GameState::GAME_OVER || collided) {
|
||||
game_state = GameState::PLAYING; // Restart immediately
|
||||
new_game();
|
||||
draw_high_score();
|
||||
}
|
||||
} else if (key == KeyEvent::Down) {
|
||||
if (game_state == GameState::PLAYING && !collided) {
|
||||
duck();
|
||||
}
|
||||
} else if (key == KeyEvent::Up) {
|
||||
if (game_state == GameState::PLAYING && !collided) {
|
||||
stand();
|
||||
}
|
||||
}
|
||||
|
||||
set_dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DinoGameView::on_encoder(const EncoderEvent delta) {
|
||||
(void)delta;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::dinogame
|
||||
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/*
|
||||
* Chrome Dino Game for Portapack Mayhem
|
||||
* Based on the original DinoGame by various contributors
|
||||
*/
|
||||
|
||||
#ifndef __UI_DINOGAME_H__
|
||||
#define __UI_DINOGAME_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "event_m0.hpp"
|
||||
#include "message.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
#include "random.hpp"
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "file.hpp"
|
||||
#include <string>
|
||||
#include "app_settings.hpp"
|
||||
|
||||
namespace ui::external_app::dinogame {
|
||||
|
||||
// Color definitions
|
||||
enum {
|
||||
White,
|
||||
Blue,
|
||||
Yellow,
|
||||
Purple,
|
||||
Green,
|
||||
Red,
|
||||
Maroon,
|
||||
Orange,
|
||||
Black,
|
||||
};
|
||||
|
||||
// Game constants
|
||||
#define DINO_WIDTH 34
|
||||
#define DINO_HEIGHT 36
|
||||
#define DINO_DUCK_WIDTH 45
|
||||
#define DINO_DUCK_HEIGHT 22
|
||||
#define BIRD_WIDTH 34
|
||||
#define BIRD_HEIGHT 27
|
||||
#define GROUND_HEIGHT 10
|
||||
#define GAME_AREA_TOP 78
|
||||
#define GAME_AREA_HEIGHT 160
|
||||
#define DINO_X 30
|
||||
#define DINO_Y (GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT - DINO_HEIGHT)
|
||||
#define DINO_DUCK_Y (GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT - DINO_DUCK_HEIGHT)
|
||||
#define BIRD_Y_UP (GAME_AREA_TOP + 20)
|
||||
#define BIRD_Y_DOWN (GAME_AREA_TOP + 60)
|
||||
#define JUMP_MAX_HEIGHT 70
|
||||
#define JUMP_SPEED 3
|
||||
#define GAME_SPEED_BASE 3
|
||||
#define SPRITE_COLOR 0x528A
|
||||
#define TRANSPARENT_COLOR 0xFFFF
|
||||
#define MIN_OBSTACLE_DISTANCE 300
|
||||
#define MAX_OBSTACLE_DISTANCE 600
|
||||
|
||||
// Game states
|
||||
enum class GameState {
|
||||
MENU,
|
||||
PLAYING,
|
||||
GAME_OVER
|
||||
};
|
||||
|
||||
// Walk styles
|
||||
enum class WalkStyle {
|
||||
WALKING,
|
||||
DUCKING
|
||||
};
|
||||
|
||||
// Bird position
|
||||
enum class BirdPosition {
|
||||
UP,
|
||||
DOWN
|
||||
};
|
||||
|
||||
// Structures
|
||||
struct Pterodactyl {
|
||||
bool inGame = false;
|
||||
BirdPosition y_position = BirdPosition::UP;
|
||||
int16_t x_offset = 320;
|
||||
int16_t y_offset = 0;
|
||||
int8_t y_velocity = 0;
|
||||
bool flop = false;
|
||||
};
|
||||
|
||||
// Simple obstacle structure with proper initialization
|
||||
struct SimpleObstacle {
|
||||
bool active;
|
||||
int16_t x;
|
||||
int16_t last_x;
|
||||
uint8_t width;
|
||||
uint8_t height;
|
||||
uint8_t type; // 0=small, 1=large, 2=double, 3=triple
|
||||
|
||||
SimpleObstacle()
|
||||
: active(false), x(0), last_x(0), width(0), height(0), type(0) {}
|
||||
};
|
||||
|
||||
// External references to sprite data
|
||||
extern const uint16_t dino_default[];
|
||||
extern const uint16_t dino_leftstep[];
|
||||
extern const uint16_t dino_rightstep[];
|
||||
extern const uint16_t dino_ducking_leftstep[];
|
||||
extern const uint16_t dino_ducking_rightstep[];
|
||||
extern const uint16_t dino_gameover[];
|
||||
extern const uint16_t pterodactyl_upflop[];
|
||||
extern const uint16_t pterodactyl_downflop[];
|
||||
|
||||
// Global painter
|
||||
extern Painter painter;
|
||||
|
||||
// Function declarations
|
||||
void cls();
|
||||
void fillrect(int x1, int y1, int x2, int y2, int color);
|
||||
void rect(int x1, int y1, int x2, int y2, int color);
|
||||
void check_game_timer();
|
||||
void game_timer_check();
|
||||
|
||||
// Ticker class
|
||||
using Callback = void (*)(void);
|
||||
|
||||
class Ticker {
|
||||
public:
|
||||
Ticker() = default;
|
||||
void attach(Callback func, double delay_sec);
|
||||
void detach();
|
||||
};
|
||||
|
||||
// Main view class
|
||||
class DinoGameView : public View {
|
||||
public:
|
||||
DinoGameView(NavigationView& nav);
|
||||
void on_show() override;
|
||||
|
||||
std::string title() const override { return "Dino Game"; }
|
||||
|
||||
void focus() override { dummy.focus(); }
|
||||
void paint(Painter& painter) override;
|
||||
void frame_sync();
|
||||
bool on_encoder(const EncoderEvent event) override;
|
||||
bool on_key(KeyEvent key) override;
|
||||
|
||||
// Public for timer callback
|
||||
GameState game_state = GameState::MENU;
|
||||
void game_loop();
|
||||
void show_menu();
|
||||
|
||||
private:
|
||||
bool initialized = false;
|
||||
NavigationView& nav_;
|
||||
|
||||
// Game variables
|
||||
static constexpr uint8_t MAX_OBSTACLES = 1;
|
||||
SimpleObstacle obstacles[MAX_OBSTACLES];
|
||||
Pterodactyl bird_info;
|
||||
|
||||
int16_t jumpHeight = 0;
|
||||
uint8_t ground_offset = 0;
|
||||
uint8_t speed_modifier = 0;
|
||||
bool runstate = false;
|
||||
bool jumping = false;
|
||||
bool falling = false;
|
||||
bool collided = false;
|
||||
bool ducking = false;
|
||||
bool displayedGameOver = false;
|
||||
bool last_ducking = false;
|
||||
bool last_runstate = false;
|
||||
|
||||
uint32_t steps = 0;
|
||||
uint32_t score = 0;
|
||||
uint32_t highScore = 0;
|
||||
uint32_t last_score = 999999; // Initialize to impossible value
|
||||
int32_t next_obstacle_distance = 100;
|
||||
int16_t last_obstacle_x = 320; // Track position of last spawned obstacle
|
||||
int32_t obstacle_spawn_timer = 0;
|
||||
|
||||
// Position tracking for minimal redraw
|
||||
int16_t last_dino_y = DINO_Y;
|
||||
int16_t last_bird_x = -1;
|
||||
int16_t last_bird_y = -1;
|
||||
uint8_t duck_timer = 0;
|
||||
|
||||
// Menu animation
|
||||
bool menu_initialized = false;
|
||||
bool blink_state = true;
|
||||
uint32_t blink_counter = 0;
|
||||
|
||||
// Current Score
|
||||
bool score_drawn = false;
|
||||
|
||||
// Game timer
|
||||
Ticker game_timer;
|
||||
|
||||
// Private methods
|
||||
void init_game();
|
||||
void new_game();
|
||||
void update_obstacles();
|
||||
void spawn_obstacle();
|
||||
void draw_screen();
|
||||
void draw_ground();
|
||||
void draw_obstacle(const SimpleObstacle& obstacle);
|
||||
void clear_obstacle_area(int x, int width, int height);
|
||||
void draw_dino_sprite(int x, int y, const uint16_t* sprite);
|
||||
void draw_bird_sprite(int x, int y, const uint16_t* sprite);
|
||||
void draw_dino_at(int x, int y, bool is_ducking, bool run_frame);
|
||||
void manage_bird();
|
||||
void draw_score();
|
||||
void draw_high_score();
|
||||
void draw_current_score();
|
||||
void show_game_over();
|
||||
void jump();
|
||||
void duck();
|
||||
void stand();
|
||||
void check_collision();
|
||||
void step();
|
||||
uint32_t get_steps() { return steps; }
|
||||
std::string score_to_string(uint32_t score);
|
||||
|
||||
bool easy_mode = false;
|
||||
|
||||
Button button_difficulty{
|
||||
{70, 195, 100, 20},
|
||||
"Mode: HARD"};
|
||||
|
||||
app_settings::SettingsManager settings_{
|
||||
"dinogame",
|
||||
app_settings::Mode::NO_RF,
|
||||
{{"highscore"sv, &highScore},
|
||||
{"easy_mode"sv, &easy_mode}}};
|
||||
|
||||
Button dummy{
|
||||
{screen_width, 0, 0, 0},
|
||||
""};
|
||||
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
this->frame_sync();
|
||||
}};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::dinogame
|
||||
|
||||
#endif /* __UI_DINOGAME_H__ */
|
||||
+15
@@ -193,6 +193,10 @@ set(EXTCPPSRC
|
||||
external/breakout/main.cpp
|
||||
external/breakout/ui_breakout.cpp
|
||||
|
||||
#dinogame
|
||||
external/dinogame/main.cpp
|
||||
external/dinogame/ui_dinogame.cpp
|
||||
|
||||
#doom
|
||||
external/doom/main.cpp
|
||||
external/doom/ui_doom.cpp
|
||||
@@ -212,6 +216,14 @@ set(EXTCPPSRC
|
||||
#gfxEQ
|
||||
external/gfxeq/main.cpp
|
||||
external/gfxeq/ui_gfxeq.cpp
|
||||
|
||||
#detector_rx
|
||||
external/detector_rx/main.cpp
|
||||
external/detector_rx/ui_detector_rx.cpp
|
||||
|
||||
#space_invaders
|
||||
external/spaceinv/main.cpp
|
||||
external/spaceinv/ui_spaceinv.cpp
|
||||
)
|
||||
|
||||
set(EXTAPPLIST
|
||||
@@ -261,9 +273,12 @@ set(EXTAPPLIST
|
||||
snake
|
||||
stopwatch
|
||||
breakout
|
||||
dinogame
|
||||
doom
|
||||
debug_pmem
|
||||
scanner
|
||||
level
|
||||
gfxeq
|
||||
detector_rx
|
||||
spaceinv
|
||||
)
|
||||
|
||||
+23
@@ -74,6 +74,9 @@ MEMORY
|
||||
ram_external_app_level (rwx) : org = 0xADE10000, len = 32k
|
||||
ram_external_app_gfxeq (rwx) : org = 0xADE20000, len = 32k
|
||||
ram_external_app_noaaapt_rx (rwx) : org = 0xADE30000, len = 32k
|
||||
ram_external_app_detector_rx (rwx) : org = 0xADE40000, len = 32k
|
||||
ram_external_app_dinogame (rwx) : org = 0xADE50000, len = 32k
|
||||
ram_external_app_spaceinv (rwx) : org = 0xADE60000, len = 32k
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
@@ -383,4 +386,24 @@ SECTIONS
|
||||
KEEP(*(.external_app.app_gfxeq.application_information));
|
||||
*(*ui*external_app*gfxeq*);
|
||||
} > ram_external_app_gfxeq
|
||||
|
||||
.external_app_detector_rx : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_detector_rx.application_information));
|
||||
*(*ui*external_app*detector_rx*);
|
||||
} > ram_external_app_detector_rx
|
||||
|
||||
.external_app_dinogame : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_dinogame.application_information));
|
||||
*(*ui*external_app*dinogame*);
|
||||
} > ram_external_app_dinogame
|
||||
|
||||
.external_app_spaceinv : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_spaceinv.application_information));
|
||||
*(*ui*external_app*spaceinv*);
|
||||
} > ram_external_app_spaceinv
|
||||
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ __attribute__((section(".external_app.app_fmradio.application_information"), use
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "FM Radio",
|
||||
/*.app_name = */ "Radio",
|
||||
/*.bitmap_data = */ {
|
||||
0x00,
|
||||
0x00,
|
||||
|
||||
+168
-54
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2024 HTotoo
|
||||
* Copyright (C) 2025 RocketGod
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -26,6 +27,7 @@
|
||||
#include "baseband_api.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "oversample.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
using namespace modems;
|
||||
@@ -33,20 +35,118 @@ using namespace ui;
|
||||
|
||||
namespace ui::external_app::fmradio {
|
||||
|
||||
#include "external/ui_grapheq.cpi"
|
||||
|
||||
void FmRadioView::focus() {
|
||||
field_frequency.focus();
|
||||
}
|
||||
|
||||
void FmRadioView::show_hide_gfx(bool show) {
|
||||
gr.hidden(!show);
|
||||
gr.set_paused(!show);
|
||||
waveform.set_paused(show);
|
||||
btn_fav_0.hidden(show);
|
||||
btn_fav_1.hidden(show);
|
||||
btn_fav_2.hidden(show);
|
||||
btn_fav_3.hidden(show);
|
||||
btn_fav_4.hidden(show);
|
||||
btn_fav_5.hidden(show);
|
||||
btn_fav_6.hidden(show);
|
||||
btn_fav_7.hidden(show);
|
||||
btn_fav_8.hidden(show);
|
||||
btn_fav_9.hidden(show);
|
||||
txt_save_help.hidden(show);
|
||||
btn_fav_save.hidden(show);
|
||||
field_bw.hidden(show);
|
||||
field_modulation.hidden(show);
|
||||
text_mode_label.hidden(show);
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void FmRadioView::change_mode(int32_t mod) {
|
||||
field_bw.on_change = [this](size_t n, OptionsField::value_t) { (void)n; };
|
||||
|
||||
audio::output::stop();
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
|
||||
audio_spectrum_update = false; // Reset spectrum update flag
|
||||
std::fill(audio_spectrum, audio_spectrum + 128, 0); // Clear spectrum buffer
|
||||
waveform.set_dirty();
|
||||
receiver_mode = static_cast<ReceiverModel::Mode>(mod);
|
||||
bool is_ssb = (mod == static_cast<int32_t>(ReceiverModel::Mode::AMAudio) &&
|
||||
(field_modulation.selected_index() == 3 || field_modulation.selected_index() == 4));
|
||||
|
||||
switch (mod) {
|
||||
case static_cast<int32_t>(ReceiverModel::Mode::AMAudio):
|
||||
audio_sampling_rate = audio::Rate::Hz_24000; // Increased to 24 kHz for better AM/SSB audio
|
||||
freqman_set_bandwidth_option(0, field_bw); // AM_MODULATION
|
||||
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
|
||||
receiver_mode = ReceiverModel::Mode::AMAudio;
|
||||
field_bw.set_by_value(0); // DSB default
|
||||
receiver_model.set_modulation(receiver_mode);
|
||||
if (is_ssb) {
|
||||
receiver_model.set_am_configuration(field_modulation.selected_index() == 3 ? 1 : 2); // 1=USB, 2=LSB
|
||||
} else {
|
||||
receiver_model.set_am_configuration(0); // DSB
|
||||
}
|
||||
field_bw.on_change = [this](size_t index, OptionsField::value_t n) {
|
||||
radio_bw = index;
|
||||
receiver_model.set_am_configuration(n);
|
||||
};
|
||||
show_hide_gfx(false);
|
||||
break;
|
||||
case static_cast<int32_t>(ReceiverModel::Mode::NarrowbandFMAudio):
|
||||
audio_sampling_rate = audio::Rate::Hz_24000;
|
||||
freqman_set_bandwidth_option(1, field_bw); // NFM_MODULATION
|
||||
baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
|
||||
receiver_mode = ReceiverModel::Mode::NarrowbandFMAudio;
|
||||
field_bw.set_by_value(2); // 16k default
|
||||
receiver_model.set_nbfm_configuration(field_bw.selected_index_value());
|
||||
field_bw.on_change = [this](size_t index, OptionsField::value_t n) {
|
||||
radio_bw = index;
|
||||
receiver_model.set_nbfm_configuration(n);
|
||||
};
|
||||
show_hide_gfx(false);
|
||||
break;
|
||||
case static_cast<int32_t>(ReceiverModel::Mode::WidebandFMAudio):
|
||||
audio_sampling_rate = audio::Rate::Hz_48000;
|
||||
freqman_set_bandwidth_option(2, field_bw); // WFM_MODULATION
|
||||
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
|
||||
receiver_mode = ReceiverModel::Mode::WidebandFMAudio;
|
||||
field_bw.set_by_value(0); // 200k default
|
||||
receiver_model.set_wfm_configuration(field_bw.selected_index_value());
|
||||
field_bw.on_change = [this](size_t index, OptionsField::value_t n) {
|
||||
radio_bw = index;
|
||||
receiver_model.set_wfm_configuration(n);
|
||||
};
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
receiver_model.set_modulation(receiver_mode);
|
||||
|
||||
receiver_model.set_sampling_rate(3072000);
|
||||
receiver_model.set_baseband_bandwidth(1750000);
|
||||
audio::set_rate(audio_sampling_rate);
|
||||
audio::output::start();
|
||||
receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack
|
||||
receiver_model.enable();
|
||||
}
|
||||
|
||||
FmRadioView::FmRadioView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
|
||||
|
||||
add_children({&rssi,
|
||||
&field_rf_amp,
|
||||
add_children({&field_rf_amp,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
&field_volume,
|
||||
&field_frequency,
|
||||
&field_bw,
|
||||
&text_mode_label,
|
||||
&field_modulation,
|
||||
&btn_fav_save,
|
||||
&txt_save_help,
|
||||
&btn_fav_0,
|
||||
@@ -60,12 +160,16 @@ FmRadioView::FmRadioView(NavigationView& nav)
|
||||
&btn_fav_8,
|
||||
&btn_fav_9,
|
||||
&audio,
|
||||
&waveform});
|
||||
&waveform,
|
||||
&rssi,
|
||||
&gr});
|
||||
|
||||
txt_save_help.set_focusable(false);
|
||||
txt_save_help.visible(false);
|
||||
for (uint8_t i = 0; i < 12; ++i) {
|
||||
if (freq_fav_list[i] == 0) {
|
||||
freq_fav_list[i] = 87000000;
|
||||
if (freq_fav_list[i].frequency == 0) {
|
||||
freq_fav_list[i].frequency = 87000000;
|
||||
freq_fav_list[i].modulation = static_cast<int32_t>(ReceiverModel::Mode::WidebandFMAudio);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,42 +177,20 @@ FmRadioView::FmRadioView(NavigationView& nav)
|
||||
field_frequency.set_value(87000000);
|
||||
}
|
||||
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
|
||||
|
||||
field_frequency.set_step(25000);
|
||||
receiver_model.enable();
|
||||
audio::output::start();
|
||||
change_mode(static_cast<int32_t>(ReceiverModel::Mode::WidebandFMAudio));
|
||||
field_modulation.set_by_value(static_cast<int32_t>(ReceiverModel::Mode::WidebandFMAudio));
|
||||
|
||||
btn_fav_0.on_select = [this](Button&) {
|
||||
on_btn_clicked(0);
|
||||
};
|
||||
btn_fav_1.on_select = [this](Button&) {
|
||||
on_btn_clicked(1);
|
||||
};
|
||||
btn_fav_2.on_select = [this](Button&) {
|
||||
on_btn_clicked(2);
|
||||
};
|
||||
btn_fav_3.on_select = [this](Button&) {
|
||||
on_btn_clicked(3);
|
||||
};
|
||||
btn_fav_4.on_select = [this](Button&) {
|
||||
on_btn_clicked(4);
|
||||
};
|
||||
btn_fav_5.on_select = [this](Button&) {
|
||||
on_btn_clicked(5);
|
||||
};
|
||||
btn_fav_6.on_select = [this](Button&) {
|
||||
on_btn_clicked(6);
|
||||
};
|
||||
btn_fav_7.on_select = [this](Button&) {
|
||||
on_btn_clicked(7);
|
||||
};
|
||||
btn_fav_8.on_select = [this](Button&) {
|
||||
on_btn_clicked(8);
|
||||
};
|
||||
btn_fav_9.on_select = [this](Button&) {
|
||||
on_btn_clicked(9);
|
||||
};
|
||||
btn_fav_0.on_select = [this](Button&) { on_btn_clicked(0); };
|
||||
btn_fav_1.on_select = [this](Button&) { on_btn_clicked(1); };
|
||||
btn_fav_2.on_select = [this](Button&) { on_btn_clicked(2); };
|
||||
btn_fav_3.on_select = [this](Button&) { on_btn_clicked(3); };
|
||||
btn_fav_4.on_select = [this](Button&) { on_btn_clicked(4); };
|
||||
btn_fav_5.on_select = [this](Button&) { on_btn_clicked(5); };
|
||||
btn_fav_6.on_select = [this](Button&) { on_btn_clicked(6); };
|
||||
btn_fav_7.on_select = [this](Button&) { on_btn_clicked(7); };
|
||||
btn_fav_8.on_select = [this](Button&) { on_btn_clicked(8); };
|
||||
btn_fav_9.on_select = [this](Button&) { on_btn_clicked(9); };
|
||||
|
||||
btn_fav_save.on_select = [this](Button&) {
|
||||
save_fav = !save_fav;
|
||||
@@ -117,20 +199,44 @@ FmRadioView::FmRadioView(NavigationView& nav)
|
||||
txt_save_help.set_dirty();
|
||||
};
|
||||
|
||||
field_modulation.on_change = [this](size_t index, int32_t mod) {
|
||||
change_mode(mod);
|
||||
if (index == 3 || index == 4) { // USB or LSB
|
||||
receiver_model.set_am_configuration(index == 3 ? 1 : 2); // 1=USB, 2=LSB
|
||||
}
|
||||
};
|
||||
|
||||
waveform.on_select = [this](Waveform&) {
|
||||
if (receiver_mode != ReceiverModel::Mode::WidebandFMAudio) { // only there is spectrum message
|
||||
return;
|
||||
}
|
||||
show_hide_gfx(!btn_fav_0.hidden());
|
||||
};
|
||||
gr.set_theme(themes[current_theme].base_color, themes[current_theme].peak_color);
|
||||
gr.on_select = [this](GraphEq&) {
|
||||
current_theme = (current_theme + 1) % themes.size();
|
||||
gr.set_theme(themes[current_theme].base_color, themes[current_theme].peak_color);
|
||||
gr.set_paused(false);
|
||||
};
|
||||
update_fav_btn_texts();
|
||||
show_hide_gfx(false);
|
||||
}
|
||||
|
||||
void FmRadioView::on_btn_clicked(uint8_t i) {
|
||||
if (save_fav) {
|
||||
save_fav = false;
|
||||
freq_fav_list[i] = field_frequency.value();
|
||||
freq_fav_list[i].frequency = field_frequency.value();
|
||||
freq_fav_list[i].modulation = field_modulation.selected_index_value();
|
||||
freq_fav_list[i].bandwidth = radio_bw;
|
||||
update_fav_btn_texts();
|
||||
txt_save_help.visible(save_fav);
|
||||
txt_save_help.set_text("");
|
||||
txt_save_help.set_dirty();
|
||||
return;
|
||||
}
|
||||
field_frequency.set_value(freq_fav_list[i]);
|
||||
field_frequency.set_value(freq_fav_list[i].frequency);
|
||||
field_modulation.set_by_value(freq_fav_list[i].modulation);
|
||||
change_mode(freq_fav_list[i].modulation);
|
||||
}
|
||||
|
||||
std::string FmRadioView::to_nice_freq(rf::Frequency freq) {
|
||||
@@ -141,16 +247,16 @@ std::string FmRadioView::to_nice_freq(rf::Frequency freq) {
|
||||
}
|
||||
|
||||
void FmRadioView::update_fav_btn_texts() {
|
||||
btn_fav_0.set_text(to_nice_freq(freq_fav_list[0]));
|
||||
btn_fav_1.set_text(to_nice_freq(freq_fav_list[1]));
|
||||
btn_fav_2.set_text(to_nice_freq(freq_fav_list[2]));
|
||||
btn_fav_3.set_text(to_nice_freq(freq_fav_list[3]));
|
||||
btn_fav_4.set_text(to_nice_freq(freq_fav_list[4]));
|
||||
btn_fav_5.set_text(to_nice_freq(freq_fav_list[5]));
|
||||
btn_fav_6.set_text(to_nice_freq(freq_fav_list[6]));
|
||||
btn_fav_7.set_text(to_nice_freq(freq_fav_list[7]));
|
||||
btn_fav_8.set_text(to_nice_freq(freq_fav_list[8]));
|
||||
btn_fav_9.set_text(to_nice_freq(freq_fav_list[9]));
|
||||
btn_fav_0.set_text(to_nice_freq(freq_fav_list[0].frequency));
|
||||
btn_fav_1.set_text(to_nice_freq(freq_fav_list[1].frequency));
|
||||
btn_fav_2.set_text(to_nice_freq(freq_fav_list[2].frequency));
|
||||
btn_fav_3.set_text(to_nice_freq(freq_fav_list[3].frequency));
|
||||
btn_fav_4.set_text(to_nice_freq(freq_fav_list[4].frequency));
|
||||
btn_fav_5.set_text(to_nice_freq(freq_fav_list[5].frequency));
|
||||
btn_fav_6.set_text(to_nice_freq(freq_fav_list[6].frequency));
|
||||
btn_fav_7.set_text(to_nice_freq(freq_fav_list[7].frequency));
|
||||
btn_fav_8.set_text(to_nice_freq(freq_fav_list[8].frequency));
|
||||
btn_fav_9.set_text(to_nice_freq(freq_fav_list[9].frequency));
|
||||
}
|
||||
|
||||
FmRadioView::~FmRadioView() {
|
||||
@@ -160,9 +266,17 @@ FmRadioView::~FmRadioView() {
|
||||
}
|
||||
|
||||
void FmRadioView::on_audio_spectrum() {
|
||||
for (size_t i = 0; i < audio_spectrum_data->db.size(); i++)
|
||||
audio_spectrum[i] = ((int16_t)audio_spectrum_data->db[i] - 127) * 256;
|
||||
waveform.set_dirty();
|
||||
if (gr.visible() && audio_spectrum_data) gr.update_audio_spectrum(*audio_spectrum_data);
|
||||
if (audio_spectrum_data && audio_spectrum_data->db.size() <= 128) {
|
||||
for (size_t i = 0; i < audio_spectrum_data->db.size(); ++i) {
|
||||
audio_spectrum[i] = ((int16_t)audio_spectrum_data->db[i] - 127) * 256;
|
||||
}
|
||||
waveform.set_dirty();
|
||||
} else {
|
||||
// Fallback: Clear waveform if no valid data
|
||||
std::fill(audio_spectrum, audio_spectrum + 128, 0);
|
||||
waveform.set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::fmradio
|
||||
|
||||
+107
-16
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2024 HTotoo
|
||||
* Copyright (C) 2025 RocketGod
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -38,11 +39,16 @@
|
||||
#include "radio_state.hpp"
|
||||
#include "log_file.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "freqman_db.hpp"
|
||||
#include "ui_freqman.hpp"
|
||||
|
||||
using namespace ui;
|
||||
|
||||
namespace ui::external_app::fmradio {
|
||||
|
||||
#include "external/ui_grapheq.hpp"
|
||||
|
||||
#define FMR_BTNGRID_TOP 60
|
||||
|
||||
class FmRadioView : public View {
|
||||
@@ -54,31 +60,65 @@ class FmRadioView : public View {
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "FM radio"; };
|
||||
std::string title() const override { return "Radio"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{};
|
||||
int16_t audio_spectrum[128]{0};
|
||||
bool audio_spectrum_update = false;
|
||||
ReceiverModel::Mode receiver_mode = ReceiverModel::Mode::WidebandFMAudio;
|
||||
AudioSpectrum* audio_spectrum_data{nullptr};
|
||||
rf::Frequency freq_fav_list[12] = {0};
|
||||
|
||||
struct Favorite {
|
||||
rf::Frequency frequency = 0;
|
||||
int32_t modulation = static_cast<int32_t>(ReceiverModel::Mode::WidebandFMAudio);
|
||||
uint8_t bandwidth = 0;
|
||||
};
|
||||
Favorite freq_fav_list[12];
|
||||
audio::Rate audio_sampling_rate = audio::Rate::Hz_48000;
|
||||
uint8_t radio_bw = 0;
|
||||
uint32_t current_theme{0};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_fmradio",
|
||||
app_settings::Mode::RX,
|
||||
{{"favlist0"sv, &freq_fav_list[0]},
|
||||
{"favlist1"sv, &freq_fav_list[1]},
|
||||
{"favlist2"sv, &freq_fav_list[2]},
|
||||
{"favlist3"sv, &freq_fav_list[3]},
|
||||
{"favlist4"sv, &freq_fav_list[4]},
|
||||
{"favlist5"sv, &freq_fav_list[5]},
|
||||
{"favlist6"sv, &freq_fav_list[6]},
|
||||
{"favlist7"sv, &freq_fav_list[7]},
|
||||
{"favlist8"sv, &freq_fav_list[8]},
|
||||
{"favlist9"sv, &freq_fav_list[9]},
|
||||
{"favlist10"sv, &freq_fav_list[10]},
|
||||
{"favlist11"sv, &freq_fav_list[11]}}};
|
||||
{{"favlist0_freq"sv, &freq_fav_list[0].frequency},
|
||||
{"favlist1_freq"sv, &freq_fav_list[1].frequency},
|
||||
{"favlist2_freq"sv, &freq_fav_list[2].frequency},
|
||||
{"favlist3_freq"sv, &freq_fav_list[3].frequency},
|
||||
{"favlist4_freq"sv, &freq_fav_list[4].frequency},
|
||||
{"favlist5_freq"sv, &freq_fav_list[5].frequency},
|
||||
{"favlist6_freq"sv, &freq_fav_list[6].frequency},
|
||||
{"favlist7_freq"sv, &freq_fav_list[7].frequency},
|
||||
{"favlist8_freq"sv, &freq_fav_list[8].frequency},
|
||||
{"favlist9_freq"sv, &freq_fav_list[9].frequency},
|
||||
{"favlist10_freq"sv, &freq_fav_list[10].frequency},
|
||||
{"favlist11_freq"sv, &freq_fav_list[11].frequency},
|
||||
{"favlist0_mod"sv, &freq_fav_list[0].modulation},
|
||||
{"favlist1_mod"sv, &freq_fav_list[1].modulation},
|
||||
{"favlist2_mod"sv, &freq_fav_list[2].modulation},
|
||||
{"favlist3_mod"sv, &freq_fav_list[3].modulation},
|
||||
{"favlist4_mod"sv, &freq_fav_list[4].modulation},
|
||||
{"favlist5_mod"sv, &freq_fav_list[5].modulation},
|
||||
{"favlist6_mod"sv, &freq_fav_list[6].modulation},
|
||||
{"favlist7_mod"sv, &freq_fav_list[7].modulation},
|
||||
{"favlist8_mod"sv, &freq_fav_list[8].modulation},
|
||||
{"favlist9_mod"sv, &freq_fav_list[9].modulation},
|
||||
{"favlist10_mod"sv, &freq_fav_list[10].modulation},
|
||||
{"favlist11_mod"sv, &freq_fav_list[11].modulation},
|
||||
{"favlist0_bw"sv, &freq_fav_list[0].bandwidth},
|
||||
{"favlist1_bw"sv, &freq_fav_list[1].bandwidth},
|
||||
{"favlist2_bw"sv, &freq_fav_list[2].bandwidth},
|
||||
{"favlist3_bw"sv, &freq_fav_list[3].bandwidth},
|
||||
{"favlist4_bw"sv, &freq_fav_list[4].bandwidth},
|
||||
{"favlist5_bw"sv, &freq_fav_list[5].bandwidth},
|
||||
{"favlist6_bw"sv, &freq_fav_list[6].bandwidth},
|
||||
{"favlist7_bw"sv, &freq_fav_list[7].bandwidth},
|
||||
{"favlist8_bw"sv, &freq_fav_list[8].bandwidth},
|
||||
{"favlist9_bw"sv, &freq_fav_list[9].bandwidth},
|
||||
{"favlist10_bw"sv, &freq_fav_list[10].bandwidth},
|
||||
{"favlist11_bw"sv, &freq_fav_list[11].bandwidth},
|
||||
{"radio_bw"sv, &radio_bw},
|
||||
{"theme"sv, ¤t_theme}}};
|
||||
|
||||
RFAmpField field_rf_amp{
|
||||
{13 * 8, 0 * 16}};
|
||||
@@ -95,6 +135,24 @@ class FmRadioView : public View {
|
||||
{0 * 8, 0 * 16},
|
||||
nav_};
|
||||
|
||||
OptionsField field_bw{
|
||||
{10 * 8, FMR_BTNGRID_TOP + 6 * 34},
|
||||
6,
|
||||
{}};
|
||||
|
||||
Text text_mode_label{
|
||||
{20 * 8, FMR_BTNGRID_TOP + 6 * 34, 5 * 8, 1 * 28},
|
||||
"MODE:"};
|
||||
|
||||
OptionsField field_modulation{
|
||||
{26 * 8, FMR_BTNGRID_TOP + 6 * 34},
|
||||
4,
|
||||
{{"AM", static_cast<int32_t>(ReceiverModel::Mode::AMAudio)},
|
||||
{"NFM", static_cast<int32_t>(ReceiverModel::Mode::NarrowbandFMAudio)},
|
||||
{"WFM", static_cast<int32_t>(ReceiverModel::Mode::WidebandFMAudio)},
|
||||
{"USB", static_cast<int32_t>(ReceiverModel::Mode::AMAudio)},
|
||||
{"LSB", static_cast<int32_t>(ReceiverModel::Mode::AMAudio)}}};
|
||||
|
||||
TextField txt_save_help{
|
||||
{2, FMR_BTNGRID_TOP + 6 * 34 - 20, 12 * 8, 16},
|
||||
" "};
|
||||
@@ -103,7 +161,7 @@ class FmRadioView : public View {
|
||||
{21 * 8, 10, 6 * 8, 4}};
|
||||
|
||||
Waveform waveform{
|
||||
{0, 20, screen_width, 2 * 16},
|
||||
{0, 20, UI_POS_MAXWIDTH, 2 * 16},
|
||||
audio_spectrum,
|
||||
128,
|
||||
0,
|
||||
@@ -111,6 +169,8 @@ class FmRadioView : public View {
|
||||
Theme::getInstance()->bg_darkest->foreground,
|
||||
true};
|
||||
|
||||
GraphEq gr{{2, FMR_BTNGRID_TOP, UI_POS_MAXWIDTH - 4, UI_POS_MAXHEIGHT - FMR_BTNGRID_TOP}, true};
|
||||
|
||||
Button btn_fav_0{{2, FMR_BTNGRID_TOP + 0 * 34, 10 * 8, 28}, "---"};
|
||||
Button btn_fav_1{{2 + 15 * 8, FMR_BTNGRID_TOP + 0 * 34, 10 * 8, 28}, "---"};
|
||||
Button btn_fav_2{{2, FMR_BTNGRID_TOP + 1 * 34, 10 * 8, 28}, "---"};
|
||||
@@ -124,10 +184,41 @@ class FmRadioView : public View {
|
||||
|
||||
Button btn_fav_save{{2, FMR_BTNGRID_TOP + 6 * 34, 7 * 8, 1 * 28}, "Save"};
|
||||
bool save_fav = false;
|
||||
|
||||
void on_btn_clicked(uint8_t i);
|
||||
void update_fav_btn_texts();
|
||||
std::string to_nice_freq(rf::Frequency freq);
|
||||
void on_audio_spectrum();
|
||||
void change_mode(int32_t mod);
|
||||
|
||||
void show_hide_gfx(bool show);
|
||||
|
||||
struct ColorTheme {
|
||||
Color base_color;
|
||||
Color peak_color;
|
||||
};
|
||||
|
||||
const std::array<ColorTheme, 20> themes{
|
||||
ColorTheme{Color(255, 0, 255), Color(255, 255, 255)},
|
||||
ColorTheme{Color(0, 255, 0), Color(255, 0, 0)},
|
||||
ColorTheme{Color(0, 0, 255), Color(255, 255, 0)},
|
||||
ColorTheme{Color(255, 128, 0), Color(255, 0, 128)},
|
||||
ColorTheme{Color(128, 0, 255), Color(0, 255, 255)},
|
||||
ColorTheme{Color(255, 255, 0), Color(0, 255, 128)},
|
||||
ColorTheme{Color(255, 0, 0), Color(0, 128, 255)},
|
||||
ColorTheme{Color(0, 255, 128), Color(255, 128, 255)},
|
||||
ColorTheme{Color(128, 128, 128), Color(255, 255, 255)},
|
||||
ColorTheme{Color(255, 64, 0), Color(0, 255, 64)},
|
||||
ColorTheme{Color(0, 128, 128), Color(255, 192, 0)},
|
||||
ColorTheme{Color(0, 255, 0), Color(0, 128, 0)},
|
||||
ColorTheme{Color(32, 64, 32), Color(0, 255, 0)},
|
||||
ColorTheme{Color(64, 0, 128), Color(255, 0, 255)},
|
||||
ColorTheme{Color(0, 64, 0), Color(0, 255, 128)},
|
||||
ColorTheme{Color(255, 255, 255), Color(0, 0, 255)},
|
||||
ColorTheme{Color(128, 0, 0), Color(255, 128, 0)},
|
||||
ColorTheme{Color(0, 128, 255), Color(255, 255, 128)},
|
||||
ColorTheme{Color(64, 64, 64), Color(255, 0, 0)},
|
||||
ColorTheme{Color(255, 192, 0), Color(0, 64, 128)}};
|
||||
|
||||
MessageHandlerRegistration message_handler_audio_spectrum{
|
||||
Message::ID::AudioSpectrum,
|
||||
|
||||
+6
-104
@@ -20,10 +20,12 @@ using namespace portapack;
|
||||
|
||||
namespace ui::external_app::gfxeq {
|
||||
|
||||
#include "external/ui_grapheq.cpi"
|
||||
|
||||
gfxEQView::gfxEQView(NavigationView& nav)
|
||||
: nav_{nav}, bar_heights(NUM_BARS, 0), prev_bar_heights(NUM_BARS, 0) {
|
||||
: nav_{nav} {
|
||||
add_children({&button_frequency, &field_rf_amp, &field_lna, &field_vga,
|
||||
&button_mood, &field_volume});
|
||||
&button_mood, &field_volume, &gr});
|
||||
|
||||
audio::output::stop();
|
||||
receiver_model.disable();
|
||||
@@ -69,6 +71,7 @@ gfxEQView::gfxEQView(NavigationView& nav)
|
||||
};
|
||||
|
||||
button_mood.on_select = [this](Button&) { this->cycle_theme(); };
|
||||
gr.set_theme(themes[current_theme].base_color, themes[current_theme].peak_color);
|
||||
}
|
||||
|
||||
// needed to answer usb serial frequency set
|
||||
@@ -87,110 +90,9 @@ void gfxEQView::focus() {
|
||||
button_frequency.focus();
|
||||
}
|
||||
|
||||
void gfxEQView::on_show() {
|
||||
needs_background_redraw = true;
|
||||
}
|
||||
|
||||
void gfxEQView::on_hide() {
|
||||
needs_background_redraw = true;
|
||||
}
|
||||
|
||||
void gfxEQView::update_audio_spectrum(const AudioSpectrum& spectrum) {
|
||||
const float bin_frequency_size = 48000.0f / 128;
|
||||
|
||||
for (int bar = 0; bar < NUM_BARS; bar++) {
|
||||
float start_freq = FREQUENCY_BANDS[bar];
|
||||
float end_freq = FREQUENCY_BANDS[bar + 1];
|
||||
|
||||
int start_bin = std::max(1, (int)(start_freq / bin_frequency_size));
|
||||
int end_bin = std::min(127, (int)(end_freq / bin_frequency_size));
|
||||
|
||||
if (start_bin >= end_bin) {
|
||||
end_bin = start_bin + 1;
|
||||
}
|
||||
|
||||
float total_energy = 0;
|
||||
int bin_count = 0;
|
||||
|
||||
for (int bin = start_bin; bin <= end_bin; bin++) {
|
||||
total_energy += spectrum.db[bin];
|
||||
bin_count++;
|
||||
}
|
||||
|
||||
float avg_db = bin_count > 0 ? (total_energy / bin_count) : 0;
|
||||
|
||||
// Manually boost highs for better visual balance
|
||||
float treble_boost = 1.0f;
|
||||
if (bar == 10)
|
||||
treble_boost = 1.7f;
|
||||
else if (bar >= 9)
|
||||
treble_boost = 1.3f;
|
||||
else if (bar >= 7)
|
||||
treble_boost = 1.3f;
|
||||
|
||||
// Mid emphasis for a V-shape effect
|
||||
float mid_boost = 1.0f;
|
||||
if (bar == 4 || bar == 5 || bar == 6) mid_boost = 1.2f;
|
||||
|
||||
float amplified_db = avg_db * treble_boost * mid_boost;
|
||||
|
||||
if (amplified_db > 255) amplified_db = 255;
|
||||
|
||||
float band_scale = 1.0f;
|
||||
int target_height = (amplified_db * RENDER_HEIGHT * band_scale) / 255;
|
||||
|
||||
if (target_height > RENDER_HEIGHT) {
|
||||
target_height = RENDER_HEIGHT;
|
||||
}
|
||||
|
||||
// Adjusted to look nice to my eyes
|
||||
float rise_speed = 0.8f;
|
||||
float fall_speed = 1.0f;
|
||||
|
||||
if (target_height > bar_heights[bar]) {
|
||||
bar_heights[bar] = bar_heights[bar] * (1.0f - rise_speed) + target_height * rise_speed;
|
||||
} else {
|
||||
bar_heights[bar] = bar_heights[bar] * (1.0f - fall_speed) + target_height * fall_speed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void gfxEQView::render_equalizer(Painter& painter) {
|
||||
const int num_segments = RENDER_HEIGHT / SEGMENT_HEIGHT;
|
||||
const ColorTheme& theme = themes[current_theme];
|
||||
|
||||
for (int bar = 0; bar < NUM_BARS; bar++) {
|
||||
int x = HORIZONTAL_OFFSET + bar * (BAR_WIDTH + BAR_SPACING);
|
||||
int active_segments = (bar_heights[bar] * num_segments) / RENDER_HEIGHT;
|
||||
|
||||
if (prev_bar_heights[bar] > active_segments) {
|
||||
int clear_height = (prev_bar_heights[bar] - active_segments) * SEGMENT_HEIGHT;
|
||||
int clear_y = screen_height - prev_bar_heights[bar] * SEGMENT_HEIGHT;
|
||||
painter.fill_rectangle({x, clear_y, BAR_WIDTH, clear_height}, Color(0, 0, 0));
|
||||
}
|
||||
|
||||
for (int seg = 0; seg < active_segments; seg++) {
|
||||
int y = screen_height - (seg + 1) * SEGMENT_HEIGHT;
|
||||
if (y < header_height) break;
|
||||
|
||||
Color segment_color = (seg >= active_segments - 2 && seg < active_segments) ? theme.peak_color : theme.base_color;
|
||||
painter.fill_rectangle({x, y, BAR_WIDTH, SEGMENT_HEIGHT - 1}, segment_color);
|
||||
}
|
||||
|
||||
prev_bar_heights[bar] = active_segments;
|
||||
}
|
||||
}
|
||||
|
||||
void gfxEQView::paint(Painter& painter) {
|
||||
if (needs_background_redraw) {
|
||||
painter.fill_rectangle({0, header_height, screen_width, RENDER_HEIGHT}, Color(0, 0, 0));
|
||||
needs_background_redraw = false;
|
||||
}
|
||||
render_equalizer(painter);
|
||||
}
|
||||
|
||||
void gfxEQView::cycle_theme() {
|
||||
current_theme = (current_theme + 1) % themes.size();
|
||||
gr.set_theme(themes[current_theme].base_color, themes[current_theme].peak_color);
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::gfxeq
|
||||
+5
-33
@@ -22,6 +22,8 @@
|
||||
|
||||
namespace ui::external_app::gfxeq {
|
||||
|
||||
#include "external/ui_grapheq.hpp"
|
||||
|
||||
class gfxEQView : public View {
|
||||
public:
|
||||
gfxEQView(NavigationView& nav);
|
||||
@@ -32,45 +34,17 @@ class gfxEQView : public View {
|
||||
|
||||
void focus() override;
|
||||
std::string title() const override { return "gfxEQ"; }
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
void on_freqchg(int64_t freq);
|
||||
|
||||
private:
|
||||
static constexpr ui::Dim header_height = 2 * 16;
|
||||
static constexpr int RENDER_HEIGHT = 288;
|
||||
static constexpr int NUM_BARS = 11;
|
||||
static constexpr int BAR_SPACING = 2;
|
||||
int BAR_WIDTH = (screen_width - (BAR_SPACING * (NUM_BARS - 1))) / NUM_BARS;
|
||||
static constexpr int HORIZONTAL_OFFSET = 2;
|
||||
static constexpr int SEGMENT_HEIGHT = 10;
|
||||
|
||||
static constexpr std::array<int, NUM_BARS + 1> FREQUENCY_BANDS = {
|
||||
375, // Bass warmth and low rumble (e.g., deep basslines, kick drum body)
|
||||
750, // Upper bass punch (e.g., bass guitar punch, kick drum attack)
|
||||
1500, // Lower midrange fullness (e.g., warmth in vocals, guitar body)
|
||||
2250, // Midrange clarity (e.g., vocal presence, snare crack)
|
||||
3375, // Upper midrange bite (e.g., instrument definition, vocal articulation)
|
||||
4875, // Presence and edge (e.g., guitar bite, vocal sibilance start)
|
||||
6750, // Lower brilliance (e.g., cymbal shimmer, vocal clarity)
|
||||
9375, // Brilliance and air (e.g., hi-hat crispness, breathy vocals)
|
||||
13125, // High treble sparkle (e.g., subtle overtones, synth shimmer)
|
||||
16875, // Upper treble airiness (e.g., faint harmonics, room ambiance)
|
||||
20625, // Top-end sheen (e.g., ultra-high harmonics, noise floor)
|
||||
24375 // Extreme treble limit (e.g., inaudible overtones, signal cutoff, static)
|
||||
};
|
||||
|
||||
struct ColorTheme {
|
||||
Color base_color;
|
||||
Color peak_color;
|
||||
};
|
||||
|
||||
NavigationView& nav_;
|
||||
bool needs_background_redraw{false};
|
||||
std::vector<int> bar_heights;
|
||||
std::vector<int> prev_bar_heights;
|
||||
|
||||
uint32_t current_theme{0};
|
||||
const std::array<ColorTheme, 20> themes{
|
||||
ColorTheme{Color(255, 0, 255), Color(255, 255, 255)},
|
||||
@@ -100,6 +74,7 @@ class gfxEQView : public View {
|
||||
VGAGainField field_vga{{18 * 8, 0 * 16}};
|
||||
Button button_mood{{21 * 8, 0, 6 * 8, 16}, "MOOD"};
|
||||
AudioVolumeField field_volume{{screen_width - 2 * 8, 0 * 16}};
|
||||
GraphEq gr{{2, UI_POS_DEFAULT_HEIGHT, UI_POS_MAXWIDTH - 4, UI_POS_HEIGHT_REMAINING(2)}, false};
|
||||
|
||||
rf::Frequency frequency_value{93100000};
|
||||
|
||||
@@ -111,16 +86,13 @@ class gfxEQView : public View {
|
||||
{{"theme", ¤t_theme},
|
||||
{"frequency", &frequency_value}}};
|
||||
|
||||
void update_audio_spectrum(const AudioSpectrum& spectrum);
|
||||
void render_equalizer(Painter& painter);
|
||||
void cycle_theme();
|
||||
|
||||
MessageHandlerRegistration message_handler_audio_spectrum{
|
||||
Message::ID::AudioSpectrum,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const AudioSpectrumMessage*>(p);
|
||||
this->update_audio_spectrum(*message.data);
|
||||
this->set_dirty();
|
||||
this->gr.update_audio_spectrum(*message.data);
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_freqchg{
|
||||
|
||||
+20
-5
@@ -78,7 +78,7 @@ void HopperView::set_hopper_channel(uint32_t i, uint32_t width, uint64_t center,
|
||||
hopper_channels[i].enabled = true;
|
||||
hopper_channels[i].width = (width * 0xFFFFFFULL) / 1536000;
|
||||
hopper_channels[i].center = center;
|
||||
hopper_channels[i].duration = 30720 * duration;
|
||||
hopper_channels[i].duration = duration ? 3072 * duration : 10;
|
||||
}
|
||||
|
||||
void HopperView::start_tx() {
|
||||
@@ -274,7 +274,7 @@ HopperView::HopperView(
|
||||
|
||||
options_type.set_selected_index(3); // Rand CW
|
||||
options_speed.set_selected_index(3); // 10kHz
|
||||
options_hop.set_selected_index(1); // 50ms
|
||||
options_hop.set_selected_index(3); // 50ms
|
||||
button_transmit.set_style(&style_val);
|
||||
|
||||
field_timetx.set_value(30);
|
||||
@@ -327,10 +327,25 @@ HopperView::HopperView(
|
||||
};
|
||||
|
||||
button_transmit.on_select = [this](Button&) {
|
||||
if (jamming || cooling)
|
||||
if (jamming || cooling) {
|
||||
stop_tx();
|
||||
else
|
||||
start_tx();
|
||||
} else {
|
||||
// if hop speed is 0, alert the user that this will cause a freeze on UI
|
||||
if (options_hop.selected_index_value() == 0) {
|
||||
nav_.display_modal(
|
||||
"Warning", "Hopping set to 0ms (fastest).\n\nTHIS WILL FREEZE THE HACKRF,\npress RESET button to stop\n\nAre you sure?", YESNO, [this](bool choice) {
|
||||
if (choice) {
|
||||
// Wait for UI update before the freeze
|
||||
chThdSleepMilliseconds(50);
|
||||
start_tx();
|
||||
}
|
||||
},
|
||||
TRUE);
|
||||
} else {
|
||||
// if hop speed is not 0, just start the transmission
|
||||
start_tx();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
menu_freq_list.on_left = [this]() {
|
||||
|
||||
+17
-8
@@ -143,6 +143,13 @@ class HopperView : public View {
|
||||
{"FM tone", 1},
|
||||
{"CW sweep", 2},
|
||||
{"Rand CW", 3},
|
||||
{"Sine", 4},
|
||||
{"Square", 5},
|
||||
{"Sawtooth", 6},
|
||||
{"Triangle", 7},
|
||||
{"Chirp", 8},
|
||||
{"Gauss", 9},
|
||||
{"Brute", 10},
|
||||
}};
|
||||
|
||||
Text text_range_number{
|
||||
@@ -163,14 +170,16 @@ class HopperView : public View {
|
||||
|
||||
OptionsField options_hop{
|
||||
{7 * 8, 27 * 8},
|
||||
5,
|
||||
{{"10ms ", 1},
|
||||
{"50ms ", 5},
|
||||
{"100ms", 10},
|
||||
{"1s ", 100},
|
||||
{"2s ", 200},
|
||||
{"5s ", 500},
|
||||
{"10s ", 1000}}};
|
||||
6,
|
||||
{{"0ms !!", 0},
|
||||
{"1ms ", 1},
|
||||
{"10ms ", 10},
|
||||
{"50ms ", 50},
|
||||
{"100ms", 100},
|
||||
{"1s ", 1000},
|
||||
{"2s ", 2000},
|
||||
{"5s ", 5000},
|
||||
{"10s ", 10000}}};
|
||||
|
||||
NumberField field_timetx{
|
||||
{7 * 8, 29 * 8},
|
||||
|
||||
+129
-48
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2025 RocketGod - Added modes from my Flipper Zero RF Jammer App - https://betaskynet.com
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -36,7 +37,6 @@ void RangeView::focus() {
|
||||
}
|
||||
|
||||
void RangeView::update_start(rf::Frequency f) {
|
||||
// Change everything except max
|
||||
frequency_range.min = f;
|
||||
button_start.set_text(to_string_short_freq(f));
|
||||
|
||||
@@ -48,7 +48,6 @@ void RangeView::update_start(rf::Frequency f) {
|
||||
}
|
||||
|
||||
void RangeView::update_stop(rf::Frequency f) {
|
||||
// Change everything except min
|
||||
frequency_range.max = f;
|
||||
button_stop.set_text(to_string_short_freq(f));
|
||||
|
||||
@@ -60,7 +59,6 @@ void RangeView::update_stop(rf::Frequency f) {
|
||||
}
|
||||
|
||||
void RangeView::update_center(rf::Frequency f) {
|
||||
// Change min/max/center, keep width
|
||||
center = f;
|
||||
button_center.set_text(to_string_short_freq(center));
|
||||
|
||||
@@ -75,7 +73,6 @@ void RangeView::update_center(rf::Frequency f) {
|
||||
}
|
||||
|
||||
void RangeView::update_width(uint32_t w) {
|
||||
// Change min/max/width, keep center
|
||||
width = w;
|
||||
|
||||
button_width.set_text(to_string_short_freq(width));
|
||||
@@ -91,7 +88,6 @@ void RangeView::update_width(uint32_t w) {
|
||||
}
|
||||
|
||||
void RangeView::paint(Painter&) {
|
||||
// Draw lines and arrows
|
||||
Rect r;
|
||||
Point p;
|
||||
Coord c;
|
||||
@@ -162,7 +158,7 @@ RangeView::RangeView(NavigationView& nav) {
|
||||
auto load_view = nav.push<FrequencyLoadView>();
|
||||
load_view->on_frequency_loaded = [this](rf::Frequency value) {
|
||||
update_center(value);
|
||||
update_width(100000); // 100kHz default jamming bandwidth when loading unique frequency
|
||||
update_width(100000);
|
||||
};
|
||||
load_view->on_range_loaded = [this](rf::Frequency start, rf::Frequency stop) {
|
||||
update_start(start);
|
||||
@@ -193,10 +189,37 @@ void JammerView::set_jammer_channel(uint32_t i, uint32_t width, uint64_t center,
|
||||
jammer_channels[i].enabled = true;
|
||||
jammer_channels[i].width = (width * 0xFFFFFFULL) / 1536000;
|
||||
jammer_channels[i].center = center;
|
||||
jammer_channels[i].duration = 30720 * duration;
|
||||
jammer_channels[i].duration = duration ? 30720 * duration : 3000;
|
||||
}
|
||||
|
||||
void JammerView::start_tx() {
|
||||
if (update_config()) {
|
||||
jamming = true;
|
||||
button_transmit.set_style(&style_cancel);
|
||||
button_transmit.set_text("STOP");
|
||||
|
||||
transmitter_model.set_rf_amp(field_amp.value());
|
||||
transmitter_model.set_tx_gain(field_gain.value());
|
||||
transmitter_model.set_baseband_bandwidth(28'000'000);
|
||||
transmitter_model.enable();
|
||||
|
||||
baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
|
||||
mscounter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void JammerView::stop_tx() {
|
||||
button_transmit.set_style(&style_val);
|
||||
button_transmit.set_text("START");
|
||||
transmitter_model.disable();
|
||||
baseband::set_jammer(false, JammerType::TYPE_FSK, 0);
|
||||
jamming = false;
|
||||
cooling = false;
|
||||
seconds = 0;
|
||||
mscounter = 0;
|
||||
}
|
||||
|
||||
bool JammerView::update_config() {
|
||||
uint32_t c, i = 0;
|
||||
size_t num_channels;
|
||||
rf::Frequency start_freq, range_bw, range_bw_sub, ch_width;
|
||||
@@ -204,24 +227,19 @@ void JammerView::start_tx() {
|
||||
|
||||
size_t hop_value = options_hop.selected_index_value();
|
||||
|
||||
// Disable all channels by default
|
||||
for (c = 0; c < JAMMER_MAX_CH; c++)
|
||||
jammer_channels[c].enabled = false;
|
||||
|
||||
// Generate jamming channels with JAMMER_MAX_CH maximum width
|
||||
// Convert ranges min/max to center/bw
|
||||
for (size_t r = 0; r < 3; r++) {
|
||||
if (range_views[r]->frequency_range.enabled) {
|
||||
range_bw = abs(range_views[r]->frequency_range.max - range_views[r]->frequency_range.min);
|
||||
|
||||
// Get lower bound
|
||||
if (range_views[r]->frequency_range.min < range_views[r]->frequency_range.max)
|
||||
start_freq = range_views[r]->frequency_range.min;
|
||||
else
|
||||
start_freq = range_views[r]->frequency_range.max;
|
||||
|
||||
if (range_bw >= JAMMER_CH_WIDTH) {
|
||||
// Split range in multiple channels
|
||||
num_channels = 0;
|
||||
range_bw_sub = range_bw;
|
||||
|
||||
@@ -241,7 +259,6 @@ void JammerView::start_tx() {
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
// Range fits in a single channel
|
||||
if (i >= JAMMER_MAX_CH) {
|
||||
out_of_ranges = true;
|
||||
} else {
|
||||
@@ -254,43 +271,24 @@ void JammerView::start_tx() {
|
||||
|
||||
if (!out_of_ranges && i) {
|
||||
text_range_total.set("/" + to_string_dec_uint(i, 2));
|
||||
|
||||
jamming = true;
|
||||
button_transmit.set_style(&style_cancel);
|
||||
button_transmit.set_text("STOP");
|
||||
|
||||
transmitter_model.set_rf_amp(field_amp.value());
|
||||
transmitter_model.set_tx_gain(field_gain.value());
|
||||
transmitter_model.set_baseband_bandwidth(28'000'000); // Although tx is narrowband , let's use Max TX LPF .
|
||||
transmitter_model.enable();
|
||||
|
||||
baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
|
||||
mscounter = 0; // euquiq: Reset internal ms counter for do_timer()
|
||||
return true;
|
||||
} else {
|
||||
if (out_of_ranges)
|
||||
nav_.display_modal("Error", "Jamming bandwidth too large.\nMust be less than 24MHz.");
|
||||
else
|
||||
nav_.display_modal("Error", "No range enabled.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void JammerView::stop_tx() {
|
||||
button_transmit.set_style(&style_val);
|
||||
button_transmit.set_text("START");
|
||||
transmitter_model.disable();
|
||||
baseband::set_jammer(false, JammerType::TYPE_FSK, 0);
|
||||
jamming = false;
|
||||
cooling = false;
|
||||
}
|
||||
|
||||
// called each 1/60th of second
|
||||
void JammerView::on_timer() {
|
||||
if (++mscounter == 60) {
|
||||
if (++mscounter >= 60) {
|
||||
mscounter = 0;
|
||||
if (jamming) {
|
||||
int32_t timepause = field_timepause.value();
|
||||
if (cooling) {
|
||||
if (++seconds >= field_timepause.value()) { // Re-start TX
|
||||
transmitter_model.set_baseband_bandwidth(28'000'000); // Although tx is narrowband , let's use Max TX LPF .
|
||||
if (timepause == 0 || ++seconds >= timepause) {
|
||||
transmitter_model.set_baseband_bandwidth(28'000'000);
|
||||
transmitter_model.enable();
|
||||
button_transmit.set_text("STOP");
|
||||
baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
|
||||
@@ -306,8 +304,7 @@ void JammerView::on_timer() {
|
||||
seconds = 0;
|
||||
}
|
||||
} else {
|
||||
if (++seconds >= field_timetx.value()) // Start cooling period:
|
||||
{
|
||||
if (timepause && ++seconds >= field_timetx.value()) {
|
||||
transmitter_model.disable();
|
||||
button_transmit.set_text("PAUSED");
|
||||
baseband::set_jammer(false, JammerType::TYPE_FSK, 0);
|
||||
@@ -327,11 +324,9 @@ void JammerView::on_timer() {
|
||||
}
|
||||
}
|
||||
|
||||
JammerView::JammerView(
|
||||
NavigationView& nav)
|
||||
JammerView::JammerView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
Rect view_rect = {0, 3 * 8, screen_width, 80};
|
||||
// baseband::run_image(portapack::spi_flash::image_tag_jammer);
|
||||
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
|
||||
|
||||
add_children({&tab_view,
|
||||
@@ -355,15 +350,101 @@ JammerView::JammerView(
|
||||
view_range_b.set_parent_rect(view_rect);
|
||||
view_range_c.set_parent_rect(view_rect);
|
||||
|
||||
options_type.set_selected_index(3); // Rand CW
|
||||
options_speed.set_selected_index(3); // 10kHz
|
||||
options_hop.set_selected_index(1); // 50ms
|
||||
button_transmit.set_style(&style_val);
|
||||
view_range_a.check_enabled.set_value(true);
|
||||
view_range_a.frequency_range.enabled = true;
|
||||
view_range_a.update_center(315'000'000);
|
||||
view_range_a.update_width(1'000'000);
|
||||
|
||||
options_type.set_selected_index(3);
|
||||
options_speed.set_selected_index(3);
|
||||
options_hop.set_selected_index(0);
|
||||
field_timetx.set_value(30);
|
||||
field_timepause.set_value(1);
|
||||
field_timepause.set_value(0);
|
||||
field_jitter.set_value(0);
|
||||
field_gain.set_value(transmitter_model.tx_gain());
|
||||
field_amp.set_value(transmitter_model.rf_amp());
|
||||
button_transmit.set_style(&style_val);
|
||||
|
||||
options_type.on_change = [this](size_t, OptionsField::value_t) {
|
||||
if (jamming) update_config();
|
||||
if (jamming && !cooling) baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
|
||||
};
|
||||
|
||||
options_speed.on_change = [this](size_t, OptionsField::value_t) {
|
||||
if (jamming) update_config();
|
||||
if (jamming && !cooling) baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
|
||||
};
|
||||
|
||||
options_hop.on_change = [this](size_t, OptionsField::value_t) {
|
||||
if (jamming) update_config();
|
||||
};
|
||||
|
||||
field_timetx.on_change = [this](int32_t) {
|
||||
if (jamming) update_config();
|
||||
};
|
||||
|
||||
field_timepause.on_change = [this](int32_t) {
|
||||
if (jamming) update_config();
|
||||
};
|
||||
|
||||
field_jitter.on_change = [this](int32_t) {
|
||||
if (jamming) update_config();
|
||||
};
|
||||
|
||||
field_gain.on_change = [this](int32_t v) {
|
||||
if (jamming) transmitter_model.set_tx_gain(v);
|
||||
};
|
||||
|
||||
field_amp.on_change = [this](int32_t v) {
|
||||
if (jamming) transmitter_model.set_rf_amp(v);
|
||||
};
|
||||
|
||||
for (auto range_view : range_views) {
|
||||
range_view->check_enabled.on_select = [this](Checkbox&, bool) {
|
||||
if (jamming) update_config();
|
||||
};
|
||||
range_view->button_start.on_select = [this, range_view](Button&) {
|
||||
auto new_view = nav_.push<FrequencyKeypadView>(range_view->frequency_range.min);
|
||||
new_view->on_changed = [this, range_view](rf::Frequency f) {
|
||||
range_view->update_start(f);
|
||||
if (jamming) update_config();
|
||||
};
|
||||
};
|
||||
range_view->button_stop.on_select = [this, range_view](Button&) {
|
||||
auto new_view = nav_.push<FrequencyKeypadView>(range_view->frequency_range.max);
|
||||
new_view->on_changed = [this, range_view](rf::Frequency f) {
|
||||
range_view->update_stop(f);
|
||||
if (jamming) update_config();
|
||||
};
|
||||
};
|
||||
range_view->button_center.on_select = [this, range_view](Button&) {
|
||||
auto new_view = nav_.push<FrequencyKeypadView>(range_view->center);
|
||||
new_view->on_changed = [this, range_view](rf::Frequency f) {
|
||||
range_view->update_center(f);
|
||||
if (jamming) update_config();
|
||||
};
|
||||
};
|
||||
range_view->button_width.on_select = [this, range_view](Button&) {
|
||||
auto new_view = nav_.push<FrequencyKeypadView>(range_view->width);
|
||||
new_view->on_changed = [this, range_view](rf::Frequency f) {
|
||||
range_view->update_width(f);
|
||||
if (jamming) update_config();
|
||||
};
|
||||
};
|
||||
range_view->button_load_range.on_select = [this, range_view](Button&) {
|
||||
auto load_view = nav_.push<FrequencyLoadView>();
|
||||
load_view->on_frequency_loaded = [this, range_view](rf::Frequency value) {
|
||||
range_view->update_center(value);
|
||||
range_view->update_width(100000);
|
||||
if (jamming) update_config();
|
||||
};
|
||||
load_view->on_range_loaded = [this, range_view](rf::Frequency start, rf::Frequency stop) {
|
||||
range_view->update_start(start);
|
||||
range_view->update_stop(stop);
|
||||
if (jamming) update_config();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
button_transmit.on_select = [this](Button&) {
|
||||
if (jamming || cooling)
|
||||
|
||||
+29
-25
@@ -41,27 +41,12 @@ class RangeView : public View {
|
||||
RangeView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
void paint(Painter&) override;
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
jammer_range_t frequency_range{false, 0, 0};
|
||||
|
||||
private:
|
||||
void update_start(rf::Frequency f);
|
||||
void update_stop(rf::Frequency f);
|
||||
void update_center(rf::Frequency f);
|
||||
void update_width(uint32_t w);
|
||||
|
||||
uint32_t width{};
|
||||
rf::Frequency center{};
|
||||
|
||||
const Style& style_info = *Theme::getInstance()->fg_medium;
|
||||
|
||||
Labels labels{
|
||||
{{2 * 8, 8 * 8 + 4}, LanguageHelper::currentMessages[LANG_START], Theme::getInstance()->fg_light->foreground},
|
||||
{{23 * 8, 8 * 8 + 4}, LanguageHelper::currentMessages[LANG_STOP], Theme::getInstance()->fg_light->foreground},
|
||||
{{12 * 8, 5 * 8 - 4}, "Center", Theme::getInstance()->fg_light->foreground},
|
||||
{{12 * 8 + 4, 13 * 8}, "Width", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Checkbox check_enabled{
|
||||
{1 * 8, 4},
|
||||
12,
|
||||
@@ -74,15 +59,32 @@ class RangeView : public View {
|
||||
Button button_start{
|
||||
{0 * 8, 11 * 8, 11 * 8, 28},
|
||||
""};
|
||||
|
||||
Button button_stop{
|
||||
{19 * 8, 11 * 8, 11 * 8, 28},
|
||||
""};
|
||||
|
||||
Button button_center{
|
||||
{76, 4 * 15 - 4, 11 * 8, 28},
|
||||
""};
|
||||
|
||||
Button button_width{
|
||||
{76, 8 * 15, 11 * 8, 28},
|
||||
""};
|
||||
|
||||
void update_start(rf::Frequency f);
|
||||
void update_stop(rf::Frequency f);
|
||||
void update_center(rf::Frequency f);
|
||||
void update_width(uint32_t w);
|
||||
|
||||
private:
|
||||
const Style& style_info = *Theme::getInstance()->fg_medium;
|
||||
|
||||
Labels labels{
|
||||
{{2 * 8, 8 * 8 + 4}, LanguageHelper::currentMessages[LANG_START], Theme::getInstance()->fg_light->foreground},
|
||||
{{23 * 8, 8 * 8 + 4}, LanguageHelper::currentMessages[LANG_STOP], Theme::getInstance()->fg_light->foreground},
|
||||
{{12 * 8, 5 * 8 - 4}, "Center", Theme::getInstance()->fg_light->foreground},
|
||||
{{12 * 8 + 4, 13 * 8}, "Width", Theme::getInstance()->fg_light->foreground}};
|
||||
};
|
||||
|
||||
class JammerView : public View {
|
||||
@@ -110,15 +112,16 @@ class JammerView : public View {
|
||||
void start_tx();
|
||||
void on_timer();
|
||||
void stop_tx();
|
||||
bool update_config();
|
||||
void set_jammer_channel(uint32_t i, uint32_t width, uint64_t center, uint32_t duration);
|
||||
void on_retune(const rf::Frequency freq, const uint32_t range);
|
||||
|
||||
JammerChannel* jammer_channels = (JammerChannel*)shared_memory.bb_data.data;
|
||||
bool jamming{false};
|
||||
bool cooling{false}; // euquiq: Indicates jammer in cooldown
|
||||
uint16_t seconds = 0; // euquiq: seconds counter for toggling tx / cooldown
|
||||
int16_t mscounter = 0; // euquiq: Internal ms counter for do_timer()
|
||||
lfsr_word_t lfsr_v = 1; // euquiq: Used to generate "random" Jitter
|
||||
bool cooling{false};
|
||||
uint16_t seconds{0};
|
||||
int16_t mscounter{0};
|
||||
lfsr_word_t lfsr_v{1};
|
||||
|
||||
const Style& style_val = *Theme::getInstance()->fg_green;
|
||||
const Style& style_cancel = *Theme::getInstance()->fg_red;
|
||||
@@ -140,8 +143,8 @@ class JammerView : public View {
|
||||
{{1 * 8, 25 * 8}, "Speed:", Theme::getInstance()->fg_light->foreground},
|
||||
{{3 * 8, 27 * 8}, "Hop:", Theme::getInstance()->fg_light->foreground},
|
||||
{{4 * 8, 29 * 8}, "TX:", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 31 * 8}, "Sle3p:", Theme::getInstance()->fg_light->foreground}, // euquiq: Token of appreciation to TheSle3p, which made this ehnancement a reality with his bounty.
|
||||
{{0 * 8, 33 * 8}, "Jitter:", Theme::getInstance()->fg_light->foreground}, // Maybe the repository curator can keep the "mystype" for some versions.
|
||||
{{1 * 8, 31 * 8}, "Sleep:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 33 * 8}, "Jitter:", Theme::getInstance()->fg_light->foreground},
|
||||
{{11 * 8, 29 * 8}, "Secs.", Theme::getInstance()->fg_light->foreground},
|
||||
{{11 * 8, 31 * 8}, "Secs.", Theme::getInstance()->fg_light->foreground},
|
||||
{{11 * 8, 33 * 8}, "/60", Theme::getInstance()->fg_light->foreground},
|
||||
@@ -182,7 +185,8 @@ class JammerView : public View {
|
||||
OptionsField options_hop{
|
||||
{7 * 8, 27 * 8},
|
||||
5,
|
||||
{{"10ms ", 1},
|
||||
{{"Off ", 0},
|
||||
{"10ms ", 1},
|
||||
{"50ms ", 5},
|
||||
{"100ms", 10},
|
||||
{"1s ", 100},
|
||||
@@ -201,7 +205,7 @@ class JammerView : public View {
|
||||
NumberField field_timepause{
|
||||
{8 * 8, 31 * 8},
|
||||
2,
|
||||
{1, 60},
|
||||
{0, 60},
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
@@ -209,7 +213,7 @@ class JammerView : public View {
|
||||
NumberField field_jitter{
|
||||
{8 * 8, 33 * 8},
|
||||
2,
|
||||
{1, 60},
|
||||
{0, 60},
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
@@ -98,6 +98,31 @@ static msg_t loopthread_fn(void* arg) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MorseView::on_set_tone(NavigationView& nav) {
|
||||
tone_input_buffer = to_string_dec_uint(tone);
|
||||
|
||||
text_prompt(nav, tone_input_buffer, 4, ENTER_KEYBOARD_MODE_DIGITS, [this](std::string& buffer) {
|
||||
bool is_digit_only = true;
|
||||
for (size_t i = 0; i < tone_input_buffer.size(); ++i) {
|
||||
if (tone_input_buffer[i] < '0' || tone_input_buffer[i] > '9') {
|
||||
is_digit_only = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!buffer.empty() && is_digit_only) {
|
||||
int new_tone = atoi(buffer.c_str());
|
||||
if (new_tone >= 100 && new_tone <= 9999) {
|
||||
tone = new_tone;
|
||||
field_tone.set_value(tone);
|
||||
update_tx_duration();
|
||||
} else {
|
||||
nav_.display_modal("Out of range", "Tone must be between 100 and 9999 Hz");
|
||||
}
|
||||
} else {
|
||||
nav_.display_modal("Invalid input", "Please enter digits only");
|
||||
}
|
||||
});
|
||||
}
|
||||
void MorseView::on_set_text(NavigationView& nav) {
|
||||
text_prompt(nav, buffer, 28, ENTER_KEYBOARD_MODE_ALPHA);
|
||||
}
|
||||
@@ -250,6 +275,10 @@ MorseView::MorseView(
|
||||
tone = value;
|
||||
};
|
||||
|
||||
field_tone.on_select = [this, &nav](NumberField&) {
|
||||
this->on_set_tone(nav);
|
||||
};
|
||||
|
||||
button_message.on_select = [this, &nav](Button&) {
|
||||
this->on_set_text(nav);
|
||||
};
|
||||
|
||||
@@ -72,6 +72,7 @@ class MorseView : public View {
|
||||
NavigationView& nav_;
|
||||
std::string message{};
|
||||
uint32_t time_units{0};
|
||||
std::string tone_input_buffer{}; // Holds the tone value while the text prompt is open
|
||||
|
||||
TxRadioState radio_state_{
|
||||
0 /* frequency */,
|
||||
@@ -100,6 +101,7 @@ class MorseView : public View {
|
||||
|
||||
bool start_tx();
|
||||
void update_tx_duration();
|
||||
void on_set_tone(NavigationView& nav);
|
||||
void on_set_text(NavigationView& nav);
|
||||
void set_foxhunt(size_t i);
|
||||
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ __attribute__((section(".external_app.app_random_password.application_informatio
|
||||
0x01,
|
||||
},
|
||||
/*.icon_color = */ ui::Color::yellow().v,
|
||||
/*.menu_location = */ app_location_t::RX,
|
||||
/*.menu_location = */ app_location_t::UTILITIES,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_afsk_rx */ {'P', 'A', 'F', 'R'},
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_spaceinv.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::spaceinv {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<SpaceInvadersView>();
|
||||
}
|
||||
} // namespace ui::external_app::spaceinv
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_spaceinv.application_information"), used)) application_information_t _application_information_spaceinv = {
|
||||
(uint8_t*)0x00000000,
|
||||
ui::external_app::spaceinv::initialize_app,
|
||||
CURRENT_HEADER_VERSION,
|
||||
VERSION_MD5,
|
||||
|
||||
"Space Invaders",
|
||||
{
|
||||
0x18,
|
||||
0x3C,
|
||||
0x7E,
|
||||
0xDB,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0x24,
|
||||
0x66,
|
||||
0x42,
|
||||
0x00,
|
||||
0x24,
|
||||
0x18,
|
||||
0x24,
|
||||
0x42,
|
||||
0x81,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
},
|
||||
ui::Color::yellow().v,
|
||||
app_location_t::GAMES,
|
||||
-1,
|
||||
|
||||
{0, 0, 0, 0},
|
||||
0x00000000,
|
||||
};
|
||||
} // namespace ui::external_app::spaceinv
|
||||
@@ -0,0 +1,879 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "ui_spaceinv.hpp"
|
||||
|
||||
namespace ui::external_app::spaceinv {
|
||||
|
||||
// Game constants
|
||||
#define PLAYER_WIDTH 26
|
||||
#define PLAYER_HEIGHT 16
|
||||
#define PLAYER_Y 280
|
||||
#define BULLET_WIDTH 3
|
||||
#define BULLET_HEIGHT 10
|
||||
#define BULLET_SPEED 8
|
||||
#define MAX_BULLETS 3
|
||||
#define INVADER_WIDTH 20
|
||||
#define INVADER_HEIGHT 16
|
||||
#define INVADER_ROWS 5 // Classic 5 rows
|
||||
#define INVADER_COLS 6 // Reduced for more movement space on narrow PP screen
|
||||
#define INVADER_GAP_X 10
|
||||
#define INVADER_GAP_Y 8 // Gap between invaders
|
||||
#define MAX_ENEMY_BULLETS 3
|
||||
#define ENEMY_BULLET_SPEED 3
|
||||
|
||||
// Game state
|
||||
static int game_state = 0; // 0=menu, 1=playing, 2=game over, 3=wave_complete
|
||||
static bool initialized = false;
|
||||
static int player_x = 120;
|
||||
static uint32_t score = 0;
|
||||
static int lives = 3;
|
||||
static uint32_t wave = 1;
|
||||
static uint32_t speed_bonus = 0;
|
||||
static uint32_t wave_complete_timer = 0;
|
||||
|
||||
// Cannon Bullets
|
||||
static int bullet_x[MAX_BULLETS] = {0, 0, 0};
|
||||
static int bullet_y[MAX_BULLETS] = {0, 0, 0};
|
||||
static bool bullet_active[MAX_BULLETS] = {false, false, false};
|
||||
|
||||
// Enemy bullets
|
||||
static int enemy_bullet_x[MAX_ENEMY_BULLETS] = {0, 0, 0};
|
||||
static int enemy_bullet_y[MAX_ENEMY_BULLETS] = {0, 0, 0};
|
||||
static bool enemy_bullet_active[MAX_ENEMY_BULLETS] = {false, false, false};
|
||||
static uint32_t enemy_fire_counter = 0;
|
||||
|
||||
// Invaders
|
||||
static bool invaders[INVADER_ROWS][INVADER_COLS];
|
||||
static int invaders_x = 40;
|
||||
static int invaders_y = 60;
|
||||
static int invader_direction = 1;
|
||||
static uint32_t invader_move_counter = 0;
|
||||
static uint32_t invader_move_delay = 30;
|
||||
static bool invader_animation_frame = false;
|
||||
|
||||
// Menu state
|
||||
static bool menu_initialized = false;
|
||||
static bool game_over_initialized = false;
|
||||
static bool blink_state = true;
|
||||
static uint32_t blink_counter = 0;
|
||||
static int16_t prompt_x = 0;
|
||||
|
||||
// Timer
|
||||
static Ticker game_timer;
|
||||
static Callback game_update_callback = nullptr;
|
||||
static uint32_t game_update_timeout = 0;
|
||||
static uint32_t game_update_counter = 0;
|
||||
|
||||
// Painter
|
||||
static Painter painter;
|
||||
|
||||
// For high score access
|
||||
static SpaceInvadersView* current_instance = nullptr;
|
||||
|
||||
void check_game_timer() {
|
||||
if (game_update_callback) {
|
||||
if (++game_update_counter >= game_update_timeout) {
|
||||
game_update_counter = 0;
|
||||
game_update_callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Ticker::attach(Callback func, double delay_sec) {
|
||||
game_update_callback = func;
|
||||
game_update_timeout = delay_sec * 60;
|
||||
}
|
||||
|
||||
void Ticker::detach() {
|
||||
game_update_callback = nullptr;
|
||||
}
|
||||
|
||||
static void init_invaders() {
|
||||
// Initialize invader array
|
||||
for (int row = 0; row < INVADER_ROWS; row++) {
|
||||
for (int col = 0; col < INVADER_COLS; col++) {
|
||||
invaders[row][col] = true;
|
||||
}
|
||||
}
|
||||
invaders_x = 40;
|
||||
invaders_y = 60;
|
||||
invader_direction = 1;
|
||||
invader_move_counter = 0;
|
||||
|
||||
// Clear any active enemy bullets
|
||||
for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
|
||||
enemy_bullet_active[i] = false;
|
||||
}
|
||||
|
||||
// Clear any active player bullets
|
||||
for (int i = 0; i < MAX_BULLETS; i++) {
|
||||
bullet_active[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void save_high_score() {
|
||||
if (current_instance && score > current_instance->highScore) {
|
||||
current_instance->highScore = score;
|
||||
// Settings are automatically saved when the view is destroyed
|
||||
}
|
||||
}
|
||||
|
||||
static void init_menu() {
|
||||
painter.fill_rectangle({0, 0, 240, 320}, Color::black());
|
||||
|
||||
auto style_green = *ui::Theme::getInstance()->fg_green;
|
||||
auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
|
||||
auto style_cyan = *ui::Theme::getInstance()->fg_cyan;
|
||||
|
||||
int16_t screen_width = 240;
|
||||
int16_t title_x = (screen_width - 15 * 8) / 2;
|
||||
int16_t divider_width = 24 * 8;
|
||||
int16_t divider_x = (screen_width - divider_width) / 2;
|
||||
int16_t instruction_width = 22 * 8;
|
||||
int16_t instruction_x = (screen_width - instruction_width) / 2;
|
||||
int16_t prompt_width = 12 * 8;
|
||||
prompt_x = (screen_width - prompt_width) / 2;
|
||||
|
||||
painter.fill_rectangle({0, 30, screen_width, 30}, Color::black());
|
||||
painter.draw_string({title_x, 42}, style_green, "SPACE INVADERS");
|
||||
|
||||
painter.draw_string({divider_x, 70}, style_yellow, "========================");
|
||||
|
||||
painter.fill_rectangle({instruction_x - 5, 100, instruction_width + 10, 80}, Color::black());
|
||||
painter.draw_rectangle({instruction_x - 5, 100, instruction_width + 10, 80}, Color::white());
|
||||
|
||||
painter.draw_string({instruction_x, 110}, style_cyan, " ROTARY: MOVE SHIP");
|
||||
painter.draw_string({instruction_x, 130}, style_cyan, " SELECT: FIRE");
|
||||
painter.draw_string({instruction_x, 150}, style_cyan, " DEFEND EARTH!");
|
||||
|
||||
// Draw point values
|
||||
auto style_purple = *ui::Theme::getInstance()->fg_magenta;
|
||||
painter.draw_string({50, 190}, style_purple, "TOP ROW = 30 PTS");
|
||||
painter.draw_string({50, 205}, style_yellow, "MID ROW = 20 PTS");
|
||||
painter.draw_string({50, 220}, style_green, "BOT ROW = 10 PTS");
|
||||
|
||||
// Draw high score
|
||||
if (current_instance) {
|
||||
auto style_white = *ui::Theme::getInstance()->fg_light;
|
||||
std::string high_score_text = "HIGH SCORE: " + std::to_string(current_instance->highScore);
|
||||
int16_t high_score_x = (screen_width - high_score_text.length() * 8) / 2;
|
||||
painter.draw_string({high_score_x, 240}, style_white, high_score_text);
|
||||
}
|
||||
|
||||
menu_initialized = true;
|
||||
}
|
||||
|
||||
static void init_game_over() {
|
||||
painter.fill_rectangle({0, 0, 240, 320}, Color::black());
|
||||
|
||||
auto style_red = *ui::Theme::getInstance()->fg_red;
|
||||
auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
|
||||
auto style_cyan = *ui::Theme::getInstance()->fg_cyan;
|
||||
auto style_white = *ui::Theme::getInstance()->fg_light;
|
||||
|
||||
int16_t screen_width = 240;
|
||||
|
||||
// Game Over title
|
||||
int16_t title_x = (screen_width - 9 * 8) / 2;
|
||||
painter.draw_string({title_x, 42}, style_red, "GAME OVER");
|
||||
|
||||
// Divider
|
||||
int16_t divider_width = 24 * 8;
|
||||
int16_t divider_x = (screen_width - divider_width) / 2;
|
||||
painter.draw_string({divider_x, 70}, style_yellow, "========================");
|
||||
|
||||
// Score box
|
||||
int16_t box_width = 22 * 8;
|
||||
int16_t box_x = (screen_width - box_width) / 2;
|
||||
painter.fill_rectangle({box_x - 5, 100, box_width + 10, 80}, Color::black());
|
||||
painter.draw_rectangle({box_x - 5, 100, box_width + 10, 80}, Color::white());
|
||||
|
||||
// Display scores
|
||||
std::string score_text = "SCORE: " + std::to_string(score);
|
||||
int16_t score_x = (screen_width - score_text.length() * 8) / 2;
|
||||
painter.draw_string({score_x, 120}, style_cyan, score_text);
|
||||
|
||||
std::string wave_text = "WAVE: " + std::to_string(wave);
|
||||
int16_t wave_x = (screen_width - wave_text.length() * 8) / 2;
|
||||
painter.draw_string({wave_x, 140}, style_cyan, wave_text);
|
||||
|
||||
// High score section
|
||||
if (current_instance) {
|
||||
if (score > current_instance->highScore) {
|
||||
// New high score!
|
||||
std::string new_high = "NEW HIGH SCORE!";
|
||||
int16_t new_high_x = (screen_width - new_high.length() * 8) / 2;
|
||||
painter.draw_string({new_high_x, 200}, style_yellow, new_high);
|
||||
|
||||
std::string high_score_text = std::to_string(score);
|
||||
int16_t high_score_x = (screen_width - high_score_text.length() * 8) / 2;
|
||||
painter.draw_string({high_score_x, 220}, style_yellow, high_score_text);
|
||||
} else {
|
||||
// Show existing high score
|
||||
std::string high_label = "HIGH SCORE:";
|
||||
int16_t label_x = (screen_width - high_label.length() * 8) / 2;
|
||||
painter.draw_string({label_x, 200}, style_white, high_label);
|
||||
|
||||
std::string high_score_text = std::to_string(current_instance->highScore);
|
||||
int16_t high_score_x = (screen_width - high_score_text.length() * 8) / 2;
|
||||
painter.draw_string({high_score_x, 220}, style_white, high_score_text);
|
||||
}
|
||||
}
|
||||
|
||||
game_over_initialized = true;
|
||||
}
|
||||
|
||||
static void draw_player() {
|
||||
// Clear the entire player area
|
||||
painter.fill_rectangle({0, PLAYER_Y - 4, 240, PLAYER_HEIGHT + 8}, Color::black());
|
||||
|
||||
// Draw the classic Space Invaders cannon
|
||||
Color green = Color::green();
|
||||
|
||||
// Top part - the cannon barrel
|
||||
painter.draw_hline({player_x + 12, PLAYER_Y}, 2, green);
|
||||
painter.draw_hline({player_x + 12, PLAYER_Y + 1}, 2, green);
|
||||
|
||||
// Upper body
|
||||
painter.draw_hline({player_x + 11, PLAYER_Y + 2}, 4, green);
|
||||
painter.draw_hline({player_x + 11, PLAYER_Y + 3}, 4, green);
|
||||
painter.draw_hline({player_x + 10, PLAYER_Y + 4}, 6, green);
|
||||
painter.draw_hline({player_x + 10, PLAYER_Y + 5}, 6, green);
|
||||
|
||||
// Main body
|
||||
painter.draw_hline({player_x + 2, PLAYER_Y + 6}, 22, green);
|
||||
painter.draw_hline({player_x + 2, PLAYER_Y + 7}, 22, green);
|
||||
painter.draw_hline({player_x + 1, PLAYER_Y + 8}, 24, green);
|
||||
painter.draw_hline({player_x + 1, PLAYER_Y + 9}, 24, green);
|
||||
painter.draw_hline({player_x, PLAYER_Y + 10}, 26, green);
|
||||
painter.draw_hline({player_x, PLAYER_Y + 11}, 26, green);
|
||||
painter.draw_hline({player_x, PLAYER_Y + 12}, 26, green);
|
||||
painter.draw_hline({player_x, PLAYER_Y + 13}, 26, green);
|
||||
painter.draw_hline({player_x, PLAYER_Y + 14}, 26, green);
|
||||
painter.draw_hline({player_x, PLAYER_Y + 15}, 26, green);
|
||||
}
|
||||
|
||||
static void draw_invader(int row, int col) {
|
||||
int x = invaders_x + col * (INVADER_WIDTH + INVADER_GAP_X);
|
||||
int y = invaders_y + row * (INVADER_HEIGHT + INVADER_GAP_Y);
|
||||
|
||||
// Clear the area first
|
||||
painter.fill_rectangle({x, y, INVADER_WIDTH, INVADER_HEIGHT}, Color::black());
|
||||
|
||||
// Different colors and shapes for different rows
|
||||
Color color;
|
||||
if (row == 0) {
|
||||
color = Color::red(); // Top row - 30 points
|
||||
} else if (row == 1 || row == 2) {
|
||||
color = Color::yellow(); // Middle rows - 20 points
|
||||
} else {
|
||||
color = Color::green(); // Bottom rows - 10 points
|
||||
}
|
||||
|
||||
// Draw different invader types based on row
|
||||
if (row == 0) {
|
||||
// Top row - squid-like invader (30 points)
|
||||
if (invader_animation_frame) {
|
||||
// Frame 1 - arms down
|
||||
painter.draw_hline({x + 8, y + 2}, 4, color);
|
||||
painter.draw_hline({x + 6, y + 3}, 8, color);
|
||||
painter.draw_hline({x + 4, y + 4}, 12, color);
|
||||
painter.draw_hline({x + 2, y + 5}, 16, color);
|
||||
painter.draw_hline({x + 2, y + 6}, 16, color);
|
||||
painter.draw_hline({x, y + 7}, 20, color);
|
||||
painter.draw_hline({x, y + 8}, 20, color);
|
||||
painter.draw_hline({x + 2, y + 9}, 2, color);
|
||||
painter.draw_hline({x + 6, y + 9}, 8, color);
|
||||
painter.draw_hline({x + 16, y + 9}, 2, color);
|
||||
painter.draw_hline({x + 4, y + 10}, 2, color);
|
||||
painter.draw_hline({x + 14, y + 10}, 2, color);
|
||||
} else {
|
||||
// Frame 2 - arms up
|
||||
painter.draw_hline({x + 8, y + 2}, 4, color);
|
||||
painter.draw_hline({x + 6, y + 3}, 8, color);
|
||||
painter.draw_hline({x + 4, y + 4}, 12, color);
|
||||
painter.draw_hline({x + 2, y + 5}, 16, color);
|
||||
painter.draw_hline({x + 2, y + 6}, 16, color);
|
||||
painter.draw_hline({x, y + 7}, 20, color);
|
||||
painter.draw_hline({x, y + 8}, 20, color);
|
||||
painter.draw_hline({x + 4, y + 9}, 2, color);
|
||||
painter.draw_hline({x + 8, y + 9}, 4, color);
|
||||
painter.draw_hline({x + 14, y + 9}, 2, color);
|
||||
painter.draw_hline({x + 2, y + 10}, 2, color);
|
||||
painter.draw_hline({x + 16, y + 10}, 2, color);
|
||||
}
|
||||
} else if (row == 1 || row == 2) {
|
||||
// Middle rows - crab-like invader (20 points)
|
||||
if (invader_animation_frame) {
|
||||
// Frame 1 - arms in
|
||||
painter.draw_hline({x + 4, y + 2}, 2, color);
|
||||
painter.draw_hline({x + 14, y + 2}, 2, color);
|
||||
painter.draw_hline({x + 2, y + 3}, 2, color);
|
||||
painter.draw_hline({x + 6, y + 3}, 8, color);
|
||||
painter.draw_hline({x + 16, y + 3}, 2, color);
|
||||
painter.draw_hline({x + 2, y + 4}, 16, color);
|
||||
painter.draw_hline({x, y + 5}, 6, color);
|
||||
painter.draw_hline({x + 8, y + 5}, 4, color);
|
||||
painter.draw_hline({x + 14, y + 5}, 6, color);
|
||||
painter.draw_hline({x, y + 6}, 20, color);
|
||||
painter.draw_hline({x + 2, y + 7}, 16, color);
|
||||
painter.draw_hline({x + 4, y + 8}, 2, color);
|
||||
painter.draw_hline({x + 14, y + 8}, 2, color);
|
||||
painter.draw_hline({x + 2, y + 9}, 4, color);
|
||||
painter.draw_hline({x + 14, y + 9}, 4, color);
|
||||
} else {
|
||||
// Frame 2 - arms out
|
||||
painter.draw_hline({x + 4, y + 2}, 2, color);
|
||||
painter.draw_hline({x + 14, y + 2}, 2, color);
|
||||
painter.draw_hline({x + 2, y + 3}, 2, color);
|
||||
painter.draw_hline({x + 6, y + 3}, 8, color);
|
||||
painter.draw_hline({x + 16, y + 3}, 2, color);
|
||||
painter.draw_hline({x + 2, y + 4}, 16, color);
|
||||
painter.draw_hline({x, y + 5}, 6, color);
|
||||
painter.draw_hline({x + 8, y + 5}, 4, color);
|
||||
painter.draw_hline({x + 14, y + 5}, 6, color);
|
||||
painter.draw_hline({x, y + 6}, 20, color);
|
||||
painter.draw_hline({x + 2, y + 7}, 16, color);
|
||||
painter.draw_hline({x + 4, y + 8}, 2, color);
|
||||
painter.draw_hline({x + 14, y + 8}, 2, color);
|
||||
painter.draw_hline({x, y + 9}, 2, color);
|
||||
painter.draw_hline({x + 6, y + 9}, 2, color);
|
||||
painter.draw_hline({x + 12, y + 9}, 2, color);
|
||||
painter.draw_hline({x + 18, y + 9}, 2, color);
|
||||
}
|
||||
} else {
|
||||
// Bottom rows - octopus-like invader (10 points)
|
||||
if (invader_animation_frame) {
|
||||
// Frame 1
|
||||
painter.draw_hline({x + 6, y + 2}, 8, color);
|
||||
painter.draw_hline({x + 4, y + 3}, 12, color);
|
||||
painter.draw_hline({x + 2, y + 4}, 16, color);
|
||||
painter.draw_hline({x, y + 5}, 20, color);
|
||||
painter.draw_hline({x, y + 6}, 20, color);
|
||||
painter.draw_hline({x + 4, y + 7}, 4, color);
|
||||
painter.draw_hline({x + 12, y + 7}, 4, color);
|
||||
painter.draw_hline({x + 2, y + 8}, 4, color);
|
||||
painter.draw_hline({x + 8, y + 8}, 4, color);
|
||||
painter.draw_hline({x + 14, y + 8}, 4, color);
|
||||
painter.draw_hline({x + 4, y + 9}, 2, color);
|
||||
painter.draw_hline({x + 14, y + 9}, 2, color);
|
||||
} else {
|
||||
// Frame 2
|
||||
painter.draw_hline({x + 6, y + 2}, 8, color);
|
||||
painter.draw_hline({x + 4, y + 3}, 12, color);
|
||||
painter.draw_hline({x + 2, y + 4}, 16, color);
|
||||
painter.draw_hline({x, y + 5}, 20, color);
|
||||
painter.draw_hline({x, y + 6}, 20, color);
|
||||
painter.draw_hline({x + 4, y + 7}, 4, color);
|
||||
painter.draw_hline({x + 12, y + 7}, 4, color);
|
||||
painter.draw_hline({x + 2, y + 8}, 4, color);
|
||||
painter.draw_hline({x + 8, y + 8}, 4, color);
|
||||
painter.draw_hline({x + 14, y + 8}, 4, color);
|
||||
painter.draw_hline({x + 2, y + 9}, 2, color);
|
||||
painter.draw_hline({x + 16, y + 9}, 2, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_all_invaders() {
|
||||
for (int row = 0; row < INVADER_ROWS; row++) {
|
||||
for (int col = 0; col < INVADER_COLS; col++) {
|
||||
if (invaders[row][col]) {
|
||||
draw_invader(row, col);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void clear_all_invaders() {
|
||||
for (int row = 0; row < INVADER_ROWS; row++) {
|
||||
for (int col = 0; col < INVADER_COLS; col++) {
|
||||
if (invaders[row][col]) {
|
||||
int x = invaders_x + col * (INVADER_WIDTH + INVADER_GAP_X);
|
||||
int y = invaders_y + row * (INVADER_HEIGHT + INVADER_GAP_Y);
|
||||
painter.fill_rectangle({x, y, INVADER_WIDTH, INVADER_HEIGHT}, Color::black());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void clear_all_enemy_bullets() {
|
||||
// Clear any visible enemy bullets
|
||||
for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
|
||||
if (enemy_bullet_active[i]) {
|
||||
painter.fill_rectangle({enemy_bullet_x[i], enemy_bullet_y[i], BULLET_WIDTH, BULLET_HEIGHT}, Color::black());
|
||||
enemy_bullet_active[i] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void fire_enemy_bullet() {
|
||||
// Find a random invader to fire from
|
||||
int active_count = 0;
|
||||
for (int row = 0; row < INVADER_ROWS; row++) {
|
||||
for (int col = 0; col < INVADER_COLS; col++) {
|
||||
if (invaders[row][col]) active_count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (active_count == 0) return;
|
||||
|
||||
int shooter = rand() % active_count;
|
||||
int count = 0;
|
||||
|
||||
for (int row = 0; row < INVADER_ROWS; row++) {
|
||||
for (int col = 0; col < INVADER_COLS; col++) {
|
||||
if (invaders[row][col]) {
|
||||
if (count == shooter) {
|
||||
// Fire from this invader
|
||||
for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
|
||||
if (!enemy_bullet_active[i]) {
|
||||
int x = invaders_x + col * (INVADER_WIDTH + INVADER_GAP_X);
|
||||
int y = invaders_y + row * (INVADER_HEIGHT + INVADER_GAP_Y);
|
||||
enemy_bullet_x[i] = x + INVADER_WIDTH / 2;
|
||||
enemy_bullet_y[i] = y + INVADER_HEIGHT;
|
||||
enemy_bullet_active[i] = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void update_enemy_bullets() {
|
||||
for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
|
||||
if (enemy_bullet_active[i]) {
|
||||
// Clear old position - but protect the border line
|
||||
if (enemy_bullet_y[i] != 49) { // Don't clear if we're exactly on the border line
|
||||
painter.fill_rectangle({enemy_bullet_x[i], enemy_bullet_y[i], BULLET_WIDTH, BULLET_HEIGHT}, Color::black());
|
||||
}
|
||||
|
||||
enemy_bullet_y[i] += ENEMY_BULLET_SPEED;
|
||||
|
||||
if (enemy_bullet_y[i] > 320) {
|
||||
enemy_bullet_active[i] = false;
|
||||
} else {
|
||||
// Draw at new position
|
||||
painter.fill_rectangle({enemy_bullet_x[i], enemy_bullet_y[i], BULLET_WIDTH, BULLET_HEIGHT}, Color::red());
|
||||
|
||||
// Check collision with player
|
||||
if (enemy_bullet_x[i] >= player_x &&
|
||||
enemy_bullet_x[i] <= player_x + PLAYER_WIDTH &&
|
||||
enemy_bullet_y[i] >= PLAYER_Y &&
|
||||
enemy_bullet_y[i] <= PLAYER_Y + PLAYER_HEIGHT) {
|
||||
// Player hit!
|
||||
enemy_bullet_active[i] = false;
|
||||
lives--;
|
||||
|
||||
// Update lives display
|
||||
auto style = *ui::Theme::getInstance()->fg_green;
|
||||
painter.fill_rectangle({5, 30, 100, 20}, Color::black());
|
||||
painter.draw_string({5, 30}, style, "Lives: " + std::to_string(lives));
|
||||
|
||||
if (lives <= 0) {
|
||||
game_state = 2; // Game over
|
||||
save_high_score();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void update_invaders() {
|
||||
uint32_t adjusted_delay = (speed_bonus >= invader_move_delay) ? 1 : invader_move_delay - speed_bonus;
|
||||
|
||||
if (++invader_move_counter >= adjusted_delay) {
|
||||
invader_move_counter = 0;
|
||||
|
||||
// Clear old positions
|
||||
clear_all_invaders();
|
||||
|
||||
// Check bounds - find the actual leftmost and rightmost invaders
|
||||
int leftmost = INVADER_COLS;
|
||||
int rightmost = -1;
|
||||
|
||||
for (int col = 0; col < INVADER_COLS; col++) {
|
||||
for (int row = 0; row < INVADER_ROWS; row++) {
|
||||
if (invaders[row][col]) {
|
||||
if (col < leftmost) leftmost = col;
|
||||
if (col > rightmost) rightmost = col;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool hit_edge = false;
|
||||
if (invader_direction > 0) {
|
||||
// Moving right - check rightmost invader
|
||||
int right_edge = invaders_x + rightmost * (INVADER_WIDTH + INVADER_GAP_X) + INVADER_WIDTH;
|
||||
if (right_edge >= 240) {
|
||||
hit_edge = true;
|
||||
}
|
||||
} else {
|
||||
// Moving left - check leftmost invader
|
||||
int left_edge = invaders_x + leftmost * (INVADER_WIDTH + INVADER_GAP_X);
|
||||
if (left_edge <= 0) {
|
||||
hit_edge = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Move invaders
|
||||
if (hit_edge) {
|
||||
invaders_y += 15; // Move down
|
||||
invader_direction = -invader_direction;
|
||||
} else {
|
||||
invaders_x += invader_direction * 8; // Horizontal movement
|
||||
}
|
||||
|
||||
// Toggle animation frame
|
||||
invader_animation_frame = !invader_animation_frame;
|
||||
|
||||
// Draw new positions
|
||||
draw_all_invaders();
|
||||
}
|
||||
|
||||
// Enemy firing logic - only in hard mode or always with lower frequency
|
||||
if (!current_instance || !current_instance->easy_mode) {
|
||||
if (++enemy_fire_counter >= 120) { // Fire every 2 seconds at 60fps
|
||||
enemy_fire_counter = 0;
|
||||
fire_enemy_bullet();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void fire_bullet() {
|
||||
for (int i = 0; i < MAX_BULLETS; i++) {
|
||||
if (!bullet_active[i]) {
|
||||
bullet_active[i] = true;
|
||||
bullet_x[i] = player_x + PLAYER_WIDTH / 2 - BULLET_WIDTH / 2;
|
||||
bullet_y[i] = PLAYER_Y - BULLET_HEIGHT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void update_bullets() {
|
||||
for (int i = 0; i < MAX_BULLETS; i++) {
|
||||
if (bullet_active[i]) {
|
||||
painter.fill_rectangle({bullet_x[i], bullet_y[i], BULLET_WIDTH, BULLET_HEIGHT}, Color::black());
|
||||
|
||||
bullet_y[i] -= BULLET_SPEED;
|
||||
|
||||
if (bullet_y[i] < 50) {
|
||||
bullet_active[i] = false;
|
||||
} else {
|
||||
painter.fill_rectangle({bullet_x[i], bullet_y[i], BULLET_WIDTH, BULLET_HEIGHT}, Color::white());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void check_collisions() {
|
||||
for (int i = 0; i < MAX_BULLETS; i++) {
|
||||
if (!bullet_active[i]) continue;
|
||||
|
||||
for (int row = 0; row < INVADER_ROWS; row++) {
|
||||
for (int col = 0; col < INVADER_COLS; col++) {
|
||||
if (!invaders[row][col]) continue;
|
||||
|
||||
int inv_x = invaders_x + col * (INVADER_WIDTH + INVADER_GAP_X);
|
||||
int inv_y = invaders_y + row * (INVADER_HEIGHT + INVADER_GAP_Y);
|
||||
|
||||
// Simple collision check
|
||||
if (bullet_x[i] < inv_x + INVADER_WIDTH &&
|
||||
bullet_x[i] + BULLET_WIDTH > inv_x &&
|
||||
bullet_y[i] < inv_y + INVADER_HEIGHT &&
|
||||
bullet_y[i] + BULLET_HEIGHT > inv_y) {
|
||||
// Hit!
|
||||
bullet_active[i] = false;
|
||||
painter.fill_rectangle({bullet_x[i], bullet_y[i], BULLET_WIDTH, BULLET_HEIGHT}, Color::black());
|
||||
|
||||
invaders[row][col] = false;
|
||||
painter.fill_rectangle({inv_x, inv_y, INVADER_WIDTH, INVADER_HEIGHT}, Color::black());
|
||||
|
||||
// Score based on row: top=30, middle=20, bottom=10
|
||||
if (row == 0) {
|
||||
score += 30;
|
||||
} else if (row == 1 || row == 2) {
|
||||
score += 20;
|
||||
} else {
|
||||
score += 10;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void check_wave_complete() {
|
||||
// Check if all invaders are destroyed
|
||||
bool all_destroyed = true;
|
||||
for (int row = 0; row < INVADER_ROWS; row++) {
|
||||
for (int col = 0; col < INVADER_COLS; col++) {
|
||||
if (invaders[row][col]) {
|
||||
all_destroyed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!all_destroyed) break;
|
||||
}
|
||||
|
||||
if (all_destroyed) {
|
||||
// Next wave!
|
||||
wave++;
|
||||
speed_bonus = (wave - 1) * 3; // Each wave is slightly faster
|
||||
if (speed_bonus > 15) speed_bonus = 15; // Cap the speed increase
|
||||
|
||||
// Clear any enemy bullets before transitioning
|
||||
clear_all_enemy_bullets();
|
||||
|
||||
// Set state to wave complete and start timer
|
||||
game_state = 3;
|
||||
wave_complete_timer = 60; // 1 second at 60fps
|
||||
|
||||
// Clear screen and show wave message - but preserve the border line
|
||||
painter.fill_rectangle({0, 51, 240, 269}, Color::black()); // Start at 51 to preserve line at 49
|
||||
|
||||
// Redraw the border line to ensure it's intact - stupid bullet clearing wants to damage it
|
||||
painter.draw_hline({0, 49}, 240, Color::white());
|
||||
|
||||
auto style = *ui::Theme::getInstance()->fg_green;
|
||||
std::string wave_text = "WAVE " + std::to_string(wave);
|
||||
int wave_x = (240 - wave_text.length() * 8) / 2;
|
||||
painter.draw_string({wave_x, 150}, style, wave_text);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if invaders reached player
|
||||
for (int row = 0; row < INVADER_ROWS; row++) {
|
||||
for (int col = 0; col < INVADER_COLS; col++) {
|
||||
if (invaders[row][col]) {
|
||||
int y = invaders_y + row * (INVADER_HEIGHT + INVADER_GAP_Y);
|
||||
if (y + INVADER_HEIGHT >= PLAYER_Y) { // Actual collision with player
|
||||
game_state = 2; // Game over
|
||||
save_high_score();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void game_timer_check() {
|
||||
if (game_state == 0) {
|
||||
// Menu state
|
||||
if (!menu_initialized) {
|
||||
init_menu();
|
||||
}
|
||||
|
||||
if (++blink_counter >= 30) {
|
||||
blink_counter = 0;
|
||||
blink_state = !blink_state;
|
||||
|
||||
auto style = *ui::Theme::getInstance()->fg_red;
|
||||
if (blink_state) {
|
||||
painter.draw_string({prompt_x, 260}, style, "PRESS SELECT");
|
||||
} else {
|
||||
painter.fill_rectangle({prompt_x, 260, 16 * 8, 20}, Color::black());
|
||||
}
|
||||
}
|
||||
} else if (game_state == 1) {
|
||||
// Playing state
|
||||
update_bullets();
|
||||
update_enemy_bullets();
|
||||
update_invaders();
|
||||
check_collisions();
|
||||
check_wave_complete();
|
||||
|
||||
// Update score display
|
||||
static uint32_t last_score = 999999;
|
||||
if (score != last_score) {
|
||||
last_score = score;
|
||||
auto style = *ui::Theme::getInstance()->fg_green;
|
||||
painter.fill_rectangle({5, 10, 100, 20}, Color::black());
|
||||
painter.draw_string({5, 10}, style, "Score: " + std::to_string(score));
|
||||
|
||||
// Redraw border line after score update (in case it was damaged) - stupid bullet clearing wants to damage it
|
||||
painter.draw_hline({0, 49}, 240, Color::white());
|
||||
}
|
||||
|
||||
// Periodically redraw the border line to fix any damage because it's a pain in the ass
|
||||
static uint32_t border_redraw_counter = 0;
|
||||
if (++border_redraw_counter >= 60) { // Once per second - might make less if causing flickering
|
||||
border_redraw_counter = 0;
|
||||
painter.draw_hline({0, 49}, 240, Color::white());
|
||||
}
|
||||
} else if (game_state == 2) {
|
||||
// Game over state
|
||||
if (!game_over_initialized) {
|
||||
init_game_over();
|
||||
}
|
||||
|
||||
if (++blink_counter >= 30) {
|
||||
blink_counter = 0;
|
||||
blink_state = !blink_state;
|
||||
|
||||
auto style = *ui::Theme::getInstance()->fg_red;
|
||||
if (blink_state) {
|
||||
painter.draw_string({prompt_x, 260}, style, "PRESS SELECT");
|
||||
} else {
|
||||
painter.fill_rectangle({prompt_x, 260, 16 * 8, 20}, Color::black());
|
||||
}
|
||||
}
|
||||
} else if (game_state == 3) {
|
||||
// Wave complete state - wait for timer
|
||||
if (--wave_complete_timer <= 0) {
|
||||
// Timer expired, start next wave
|
||||
game_state = 1;
|
||||
|
||||
// Clear and redraw game area - preserve border line
|
||||
painter.fill_rectangle({0, 51, 240, 269}, Color::black()); // Start at 51 to preserve line
|
||||
|
||||
// Restore UI
|
||||
auto style = *ui::Theme::getInstance()->fg_green;
|
||||
painter.draw_string({5, 10}, style, "Score: " + std::to_string(score));
|
||||
painter.draw_string({5, 30}, style, "Lives: " + std::to_string(lives));
|
||||
|
||||
// Redraw the border line in case it was damaged again
|
||||
painter.draw_hline({0, 49}, 240, Color::white());
|
||||
|
||||
// Initialize new wave of invaders
|
||||
init_invaders();
|
||||
draw_all_invaders();
|
||||
draw_player();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SpaceInvadersView::SpaceInvadersView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({&dummy, &button_difficulty});
|
||||
current_instance = this;
|
||||
game_timer.attach(&game_timer_check, 1.0 / 60.0);
|
||||
|
||||
// Update button text based on loaded setting
|
||||
button_difficulty.set_text(easy_mode ? "Mode: EASY" : "Mode: HARD");
|
||||
|
||||
button_difficulty.on_select = [this](Button&) {
|
||||
easy_mode = !easy_mode;
|
||||
button_difficulty.set_text(easy_mode ? "Mode: EASY" : "Mode: HARD");
|
||||
// Settings will be saved when the view is destroyed
|
||||
};
|
||||
}
|
||||
|
||||
SpaceInvadersView::~SpaceInvadersView() {
|
||||
// Settings are automatically saved when destroyed
|
||||
current_instance = nullptr;
|
||||
}
|
||||
|
||||
void SpaceInvadersView::on_show() {
|
||||
}
|
||||
|
||||
void SpaceInvadersView::paint(Painter& painter) {
|
||||
(void)painter;
|
||||
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
game_state = 0;
|
||||
menu_initialized = false;
|
||||
game_over_initialized = false;
|
||||
blink_state = true;
|
||||
blink_counter = 0;
|
||||
player_x = 107;
|
||||
score = 0;
|
||||
lives = 3;
|
||||
wave = 1;
|
||||
speed_bonus = 0;
|
||||
|
||||
// Initialize arrays
|
||||
for (int i = 0; i < MAX_BULLETS; i++) {
|
||||
bullet_active[i] = false;
|
||||
bullet_x[i] = 0;
|
||||
bullet_y[i] = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
|
||||
enemy_bullet_active[i] = false;
|
||||
enemy_bullet_x[i] = 0;
|
||||
enemy_bullet_y[i] = 0;
|
||||
}
|
||||
|
||||
init_invaders();
|
||||
}
|
||||
}
|
||||
|
||||
void SpaceInvadersView::frame_sync() {
|
||||
check_game_timer();
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
bool SpaceInvadersView::on_encoder(const EncoderEvent delta) {
|
||||
if (game_state == 1) {
|
||||
if (delta > 0) {
|
||||
player_x += 5;
|
||||
if (player_x > 214) player_x = 214;
|
||||
draw_player();
|
||||
} else if (delta < 0) {
|
||||
player_x -= 5;
|
||||
if (player_x < 0) player_x = 0;
|
||||
draw_player();
|
||||
}
|
||||
|
||||
set_dirty();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SpaceInvadersView::on_key(const KeyEvent key) {
|
||||
if (key == KeyEvent::Select) {
|
||||
if (game_state == 0) {
|
||||
// Start game
|
||||
game_state = 1;
|
||||
button_difficulty.hidden(true);
|
||||
score = 0;
|
||||
lives = 3;
|
||||
wave = 1;
|
||||
speed_bonus = 0;
|
||||
invader_move_delay = 30;
|
||||
enemy_fire_counter = 0;
|
||||
init_invaders();
|
||||
|
||||
painter.fill_rectangle({0, 0, 240, 320}, Color::black());
|
||||
painter.draw_hline({0, 49}, 240, Color::white());
|
||||
|
||||
auto style = *ui::Theme::getInstance()->fg_green;
|
||||
painter.draw_string({5, 10}, style, "Score: 0");
|
||||
painter.draw_string({5, 30}, style, "Lives: 3");
|
||||
|
||||
draw_all_invaders();
|
||||
draw_player();
|
||||
|
||||
set_dirty();
|
||||
} else if (game_state == 1) {
|
||||
fire_bullet();
|
||||
} else if (game_state == 2) {
|
||||
// Return to menu
|
||||
game_state = 0;
|
||||
menu_initialized = false;
|
||||
game_over_initialized = false;
|
||||
button_difficulty.hidden(false);
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::spaceinv
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef __UI_SPACEINV_H__
|
||||
#define __UI_SPACEINV_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "event_m0.hpp"
|
||||
#include "message.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
#include "random.hpp"
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "app_settings.hpp"
|
||||
|
||||
namespace ui::external_app::spaceinv {
|
||||
|
||||
using Callback = void (*)(void);
|
||||
|
||||
class Ticker {
|
||||
public:
|
||||
Ticker() = default;
|
||||
void attach(Callback func, double delay_sec);
|
||||
void detach();
|
||||
};
|
||||
|
||||
void check_game_timer();
|
||||
void game_timer_check();
|
||||
|
||||
class SpaceInvadersView : public View {
|
||||
public:
|
||||
SpaceInvadersView(NavigationView& nav);
|
||||
~SpaceInvadersView(); // Destructor will trigger settings save
|
||||
void on_show() override;
|
||||
|
||||
std::string title() const override { return "Space Invaders"; }
|
||||
|
||||
void focus() override { dummy.focus(); }
|
||||
void paint(Painter& painter) override;
|
||||
void frame_sync();
|
||||
bool on_encoder(const EncoderEvent event) override;
|
||||
bool on_key(KeyEvent key) override;
|
||||
|
||||
uint32_t highScore = 0;
|
||||
bool easy_mode = false;
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
Button button_difficulty{
|
||||
{70, 285, 100, 20},
|
||||
"Mode: HARD"};
|
||||
|
||||
app_settings::SettingsManager settings_{
|
||||
"spaceinv",
|
||||
app_settings::Mode::NO_RF,
|
||||
{{"highscore"sv, &highScore},
|
||||
{"easy_mode"sv, &easy_mode}}};
|
||||
|
||||
Button dummy{
|
||||
{240, 0, 0, 0},
|
||||
""};
|
||||
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
this->frame_sync();
|
||||
}};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::spaceinv
|
||||
|
||||
#endif /* __UI_SPACEINV_H__ */
|
||||
@@ -52,3 +52,4 @@ const std::filesystem::path ook_editor_dir = u"OOKFILES";
|
||||
const std::filesystem::path hopper_dir = u"HOPPER";
|
||||
const std::filesystem::path subghz_dir = u"SUBGHZ";
|
||||
const std::filesystem::path waterfalls_dir = u"WATERFALLS";
|
||||
const std::filesystem::path macaddress_dir = u"MACADDRESS";
|
||||
|
||||
@@ -54,5 +54,6 @@ extern const std::filesystem::path ook_editor_dir;
|
||||
extern const std::filesystem::path hopper_dir;
|
||||
extern const std::filesystem::path subghz_dir;
|
||||
extern const std::filesystem::path waterfalls_dir;
|
||||
extern const std::filesystem::path macaddress_dir;
|
||||
|
||||
#endif /* __FILE_PATH_H__ */
|
||||
|
||||
@@ -108,8 +108,9 @@ options_t freqman_bandwidths[6] = {
|
||||
},
|
||||
{
|
||||
// WFMAM for NOAA satellites, 137 Mhz band
|
||||
{"80kHz (NOAA Apt)", 0}, // Fixed RX demod- WFM config Index 1 : FM+AM for Audio NOAA APT ones.
|
||||
{"38kHz (NOAA Apt)", 1},
|
||||
{"80k-NOAA Apt LPF", 0}, // Captured RF IQ filtered BW 80K, APT baseband filtered with Low Pass Filter 4k5 fc -3dB
|
||||
{"38k-NOAA Apt LPF", 1}, // Captured RF IQ filtered BW 38K, APT baseband filtered with Low Pass Filter 4k5 fc -3dB
|
||||
{"38k-NOAA Apt BPF", 2}, // Captured RF IQ filtered BW 38K, APT baseband filtered BPF centred to the 2k4 AM subcarrier, BW = 2KHz
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -69,9 +69,10 @@ static constexpr std::array<baseband::WFMConfig, 3> wfm_configs{{
|
||||
{taps_80k_wfm_decim_0, taps_80k_wfm_decim_1},
|
||||
}};
|
||||
|
||||
static constexpr std::array<baseband::WFMAMConfig, 2> wfmam_configs{{
|
||||
{taps_16k0_decim_0, taps_80k_wfmam_decim_1},
|
||||
{taps_16k0_decim_0, taps_38k_wfmam_decim_1},
|
||||
static constexpr std::array<baseband::WFMAMConfig, 3> wfmam_configs{{
|
||||
{taps_16k0_decim_0, taps_80k_wfmam_decim_1, taps_64_lp_1875_2166},
|
||||
{taps_16k0_decim_0, taps_38k_wfmam_decim_1, taps_64_lp_1875_2166},
|
||||
{taps_16k0_decim_0, taps_38k_wfmam_decim_1, taps_64_bpf_2k4_bw_2k},
|
||||
}};
|
||||
|
||||
} /* namespace */
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
#include "theme.hpp"
|
||||
|
||||
namespace ui {
|
||||
@@ -25,6 +24,9 @@ void Theme::SetTheme(ThemeId theme) {
|
||||
case Red:
|
||||
current = new ThemeRed();
|
||||
break;
|
||||
case Dark:
|
||||
current = new ThemeDark();
|
||||
break;
|
||||
case DefaultGrey:
|
||||
default:
|
||||
current = new ThemeDefault();
|
||||
@@ -725,4 +727,137 @@ ThemeRed::ThemeRed() {
|
||||
bg_table_header = new Color{205, 30, 0};
|
||||
}
|
||||
|
||||
ThemeDark::ThemeDark() {
|
||||
bg_lightest = new Style{
|
||||
.font = font::fixed_8x16,
|
||||
.background = {32, 32, 32},
|
||||
.foreground = Color::white(),
|
||||
};
|
||||
bg_lightest_small = new Style{
|
||||
.font = font::fixed_5x8,
|
||||
.background = {32, 32, 32},
|
||||
.foreground = Color::white(),
|
||||
};
|
||||
bg_light = new Style{
|
||||
.font = font::fixed_8x16,
|
||||
.background = {24, 24, 24},
|
||||
.foreground = Color::white(),
|
||||
};
|
||||
bg_medium = new Style{
|
||||
.font = font::fixed_8x16,
|
||||
.background = {16, 16, 16},
|
||||
.foreground = Color::white(),
|
||||
};
|
||||
bg_dark = new Style{
|
||||
.font = font::fixed_8x16,
|
||||
.background = {8, 8, 8},
|
||||
.foreground = Color::white(),
|
||||
};
|
||||
bg_darker = new Style{
|
||||
.font = font::fixed_8x16,
|
||||
.background = {4, 4, 4},
|
||||
.foreground = Color::white(),
|
||||
};
|
||||
|
||||
bg_darkest = new Style{
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::white(),
|
||||
};
|
||||
bg_darkest_small = new Style{
|
||||
.font = font::fixed_5x8,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::white(),
|
||||
};
|
||||
|
||||
bg_important_small = new Style{
|
||||
.font = font::fixed_5x8,
|
||||
.background = {64, 64, 64},
|
||||
.foreground = Color::white(),
|
||||
};
|
||||
|
||||
error_dark = new Style{
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::red(),
|
||||
};
|
||||
warning_dark = new Style{
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::yellow(),
|
||||
};
|
||||
ok_dark = new Style{
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::green(),
|
||||
};
|
||||
|
||||
fg_dark = new Style{
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = {96, 96, 96},
|
||||
};
|
||||
fg_medium = new Style{
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = {128, 128, 128},
|
||||
};
|
||||
fg_light = new Style{
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::white(),
|
||||
};
|
||||
|
||||
fg_red = new Style{
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::red(),
|
||||
};
|
||||
fg_green = new Style{
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::green(),
|
||||
};
|
||||
fg_yellow = new Style{
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::yellow(),
|
||||
};
|
||||
fg_orange = new Style{
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::orange(),
|
||||
};
|
||||
fg_blue = new Style{
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::blue(),
|
||||
};
|
||||
fg_cyan = new Style{
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::cyan(),
|
||||
};
|
||||
fg_darkcyan = new Style{
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::dark_cyan(),
|
||||
};
|
||||
fg_magenta = new Style{
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::magenta(),
|
||||
};
|
||||
|
||||
option_active = new Style{
|
||||
.font = font::fixed_8x16,
|
||||
.background = {64, 64, 64},
|
||||
.foreground = Color::white(),
|
||||
};
|
||||
|
||||
status_active = new Color{0, 255, 0};
|
||||
|
||||
bg_table_header = new Color{48, 48, 48};
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
@@ -93,6 +93,11 @@ class ThemeRed : public ThemeTemplate {
|
||||
ThemeRed();
|
||||
};
|
||||
|
||||
class ThemeDark : public ThemeTemplate {
|
||||
public:
|
||||
ThemeDark();
|
||||
};
|
||||
|
||||
class Theme {
|
||||
public:
|
||||
enum ThemeId {
|
||||
@@ -101,6 +106,7 @@ class Theme {
|
||||
Aqua = 2,
|
||||
Green = 3,
|
||||
Red = 4,
|
||||
Dark = 5,
|
||||
MAX
|
||||
};
|
||||
static ThemeTemplate* getInstance();
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
|
||||
Copyright (C) 2025 HTotoo
|
||||
|
||||
|
||||
# External UI elements
|
||||
|
||||
|
||||
## Read carefully
|
||||
|
||||
|
||||
These widgets are only used in external apps!
|
||||
The concept is, we move these widgets out of FW space, and compile them with external apps. To all separately. This is multiple include, but since we compile the widget under different namespaces (and those namespaces will be under the external_app namespace) these all will be removed from the base firmware.
|
||||
|
||||
This way we can free up some spaces, and still reuse the same widgets in different apps easily.
|
||||
|
||||
## How to create external widget
|
||||
|
||||
You create a **hpp** file indet the ui/external folder, and include everything you need for your widget as you normally wanted to do.
|
||||
**Don't forget the ifdef guards!**
|
||||
**Never use any namespace for your class there!** The namespace of the actual ext app will be used where you include this.
|
||||
|
||||
Then create a **cpi** file, where you include your hpp file, and create the implementation of your widget class. Still, don't use namespace.
|
||||
|
||||
You won't need to include these files in any cmake file! (see usage, why)
|
||||
|
||||
## How to use these widgets
|
||||
|
||||
This is important, so follow the rules carefully. If it works, doesn't mean, you did it good!
|
||||
In your external app's hpp file, you need to include the widget's hpp file.
|
||||
**But be careful**, you must include it under the ext app's namespace, before your first class. For examlpe:
|
||||
|
||||
namespace ui::external_app::gfxeq {
|
||||
#include "external/ui_grapheq.hpp"
|
||||
class gfxEQView : public View {
|
||||
Then you must include the cpi file in the external app's cpp file. Again, it must be under the ext app's namespace!
|
||||
|
||||
using namespace portapack;
|
||||
namespace ui::external_app::gfxeq {
|
||||
#include "external/ui_grapheq.cpi"
|
||||
gfxEQView::gfxEQView(NavigationView& nav)
|
||||
|
||||
This must be done under each ext app that uses the same widget.
|
||||
This way, the widget will be compiled multiple times under the external_app namespace, but those will be removed from the base. Each app will be able to use it, easy to handle, easy to create.
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright (C) 2025 RocketGod
|
||||
* Copyright (C) 2025 HTotoo
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_grapheq.hpp"
|
||||
|
||||
/* GraphEq *************************************************************/
|
||||
|
||||
GraphEq::GraphEq(
|
||||
Rect parent_rect,
|
||||
bool clickable)
|
||||
: Widget{parent_rect},
|
||||
clickable_{clickable},
|
||||
bar_heights(NUM_BARS, 0),
|
||||
prev_bar_heights(NUM_BARS, 0) {
|
||||
if (clickable) {
|
||||
set_focusable(true);
|
||||
// previous_data.resize(length_, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphEq::set_parent_rect(const Rect new_parent_rect) {
|
||||
Widget::set_parent_rect(new_parent_rect);
|
||||
calculate_params();
|
||||
}
|
||||
|
||||
void GraphEq::calculate_params() {
|
||||
y_top = screen_rect().top();
|
||||
RENDER_HEIGHT = parent_rect().height();
|
||||
BAR_WIDTH = (parent_rect().width() - (BAR_SPACING * (NUM_BARS - 1))) / NUM_BARS;
|
||||
HORIZONTAL_OFFSET = screen_rect().left();
|
||||
}
|
||||
|
||||
bool GraphEq::is_paused() const {
|
||||
return paused_;
|
||||
}
|
||||
|
||||
void GraphEq::set_paused(bool paused) {
|
||||
paused_ = paused;
|
||||
needs_background_redraw = true;
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
bool GraphEq::is_clickable() const {
|
||||
return clickable_;
|
||||
}
|
||||
|
||||
void GraphEq::getAccessibilityText(std::string& result) {
|
||||
result = paused_ ? "paused GraphEq" : "GraphEq";
|
||||
}
|
||||
|
||||
void GraphEq::getWidgetName(std::string& result) {
|
||||
result = "GraphEq";
|
||||
}
|
||||
|
||||
bool GraphEq::on_key(const KeyEvent key) {
|
||||
if (!clickable_) return false;
|
||||
|
||||
if (key == KeyEvent::Select) {
|
||||
set_paused(!paused_);
|
||||
if (on_select) {
|
||||
on_select(*this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GraphEq::on_keyboard(const KeyboardEvent key) {
|
||||
if (!clickable_) return false;
|
||||
|
||||
if (key == 32 || key == 10) {
|
||||
set_paused(!paused_);
|
||||
if (on_select) {
|
||||
on_select(*this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GraphEq::on_touch(const TouchEvent event) {
|
||||
if (!clickable_) return false;
|
||||
|
||||
switch (event.type) {
|
||||
case TouchEvent::Type::Start:
|
||||
focus();
|
||||
return true;
|
||||
|
||||
case TouchEvent::Type::End:
|
||||
set_paused(!paused_);
|
||||
if (on_select) {
|
||||
on_select(*this);
|
||||
}
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void GraphEq::set_theme(Color base_color_, Color peak_color_) {
|
||||
base_color = base_color_;
|
||||
peak_color = peak_color_;
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void GraphEq::update_audio_spectrum(const AudioSpectrum& spectrum) {
|
||||
const float bin_frequency_size = 48000.0f / 128;
|
||||
|
||||
for (int bar = 0; bar < NUM_BARS; bar++) {
|
||||
float start_freq = FREQUENCY_BANDS[bar];
|
||||
float end_freq = FREQUENCY_BANDS[bar + 1];
|
||||
|
||||
int start_bin = std::max(1, (int)(start_freq / bin_frequency_size));
|
||||
int end_bin = std::min(127, (int)(end_freq / bin_frequency_size));
|
||||
|
||||
if (start_bin >= end_bin) {
|
||||
end_bin = start_bin + 1;
|
||||
}
|
||||
|
||||
float total_energy = 0;
|
||||
int bin_count = 0;
|
||||
|
||||
for (int bin = start_bin; bin <= end_bin; bin++) {
|
||||
total_energy += spectrum.db[bin];
|
||||
bin_count++;
|
||||
}
|
||||
|
||||
float avg_db = bin_count > 0 ? (total_energy / bin_count) : 0;
|
||||
|
||||
// Manually boost highs for better visual balance
|
||||
float treble_boost = 1.0f;
|
||||
if (bar == 10)
|
||||
treble_boost = 1.7f;
|
||||
else if (bar >= 9)
|
||||
treble_boost = 1.3f;
|
||||
else if (bar >= 7)
|
||||
treble_boost = 1.3f;
|
||||
|
||||
// Mid emphasis for a V-shape effect
|
||||
float mid_boost = 1.0f;
|
||||
if (bar == 4 || bar == 5 || bar == 6) mid_boost = 1.2f;
|
||||
|
||||
float amplified_db = avg_db * treble_boost * mid_boost;
|
||||
|
||||
if (amplified_db > 255) amplified_db = 255;
|
||||
|
||||
float band_scale = 1.0f;
|
||||
int target_height = (amplified_db * RENDER_HEIGHT * band_scale) / 255;
|
||||
|
||||
if (target_height > RENDER_HEIGHT) {
|
||||
target_height = RENDER_HEIGHT;
|
||||
}
|
||||
|
||||
// Adjusted to look nice to my eyes
|
||||
float rise_speed = 0.8f;
|
||||
float fall_speed = 1.0f;
|
||||
|
||||
if (target_height > bar_heights[bar]) {
|
||||
bar_heights[bar] = bar_heights[bar] * (1.0f - rise_speed) + target_height * rise_speed;
|
||||
} else {
|
||||
bar_heights[bar] = bar_heights[bar] * (1.0f - fall_speed) + target_height * fall_speed;
|
||||
}
|
||||
}
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void GraphEq::paint(Painter& painter) {
|
||||
if (!visible()) return;
|
||||
if (!is_calculated) { // calc positions first
|
||||
calculate_params();
|
||||
is_calculated = true;
|
||||
}
|
||||
if (needs_background_redraw) {
|
||||
painter.fill_rectangle(screen_rect(), Theme::getInstance()->bg_darkest->background);
|
||||
needs_background_redraw = false;
|
||||
}
|
||||
if (paused_) {
|
||||
return;
|
||||
}
|
||||
const int num_segments = RENDER_HEIGHT / SEGMENT_HEIGHT;
|
||||
uint16_t bottom = screen_rect().bottom();
|
||||
for (int bar = 0; bar < NUM_BARS; bar++) {
|
||||
int x = HORIZONTAL_OFFSET + bar * (BAR_WIDTH + BAR_SPACING);
|
||||
int active_segments = (bar_heights[bar] * num_segments) / RENDER_HEIGHT;
|
||||
|
||||
if (prev_bar_heights[bar] > active_segments) {
|
||||
int clear_height = (prev_bar_heights[bar] - active_segments) * SEGMENT_HEIGHT;
|
||||
int clear_y = bottom - prev_bar_heights[bar] * SEGMENT_HEIGHT;
|
||||
painter.fill_rectangle({x, clear_y, BAR_WIDTH, clear_height}, Theme::getInstance()->bg_darkest->background);
|
||||
}
|
||||
|
||||
for (int seg = 0; seg < active_segments; seg++) {
|
||||
int y = bottom - (seg + 1) * SEGMENT_HEIGHT;
|
||||
if (y < y_top) break;
|
||||
|
||||
Color segment_color = (seg >= active_segments - 2 && seg < active_segments) ? peak_color : base_color;
|
||||
painter.fill_rectangle({x, y, BAR_WIDTH, SEGMENT_HEIGHT - 1}, segment_color);
|
||||
}
|
||||
prev_bar_heights[bar] = active_segments;
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (C) 2025 RocketGod
|
||||
* Copyright (C) 2025 HTotoo
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __UI_GRAPHEQ_H__
|
||||
#define __UI_GRAPHEQ_H__
|
||||
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
class GraphEq : public Widget {
|
||||
public:
|
||||
std::function<void(GraphEq&)> on_select{};
|
||||
|
||||
GraphEq(Rect parent_rect, bool clickable = false);
|
||||
GraphEq(const GraphEq&) = delete;
|
||||
GraphEq(GraphEq&&) = delete;
|
||||
GraphEq& operator=(const GraphEq&) = delete;
|
||||
GraphEq& operator=(GraphEq&&) = delete;
|
||||
|
||||
bool is_paused() const;
|
||||
void set_paused(bool paused);
|
||||
bool is_clickable() const;
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
bool on_key(const KeyEvent key) override;
|
||||
bool on_touch(const TouchEvent event) override;
|
||||
bool on_keyboard(const KeyboardEvent event) override;
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
|
||||
void getAccessibilityText(std::string& result) override;
|
||||
void getWidgetName(std::string& result) override;
|
||||
void update_audio_spectrum(const AudioSpectrum& spectrum);
|
||||
void set_theme(Color base_color, Color peak_color);
|
||||
|
||||
private:
|
||||
bool is_calculated{false};
|
||||
bool paused_{false};
|
||||
bool clickable_{false};
|
||||
bool needs_background_redraw{true}; // Redraw background only when needed.
|
||||
Color base_color = Color(255, 0, 255);
|
||||
Color peak_color = Color(255, 255, 255);
|
||||
std::vector<ui::Dim> bar_heights;
|
||||
std::vector<ui::Dim> prev_bar_heights;
|
||||
|
||||
ui::Dim y_top = 2 * 16;
|
||||
ui::Dim RENDER_HEIGHT = 288;
|
||||
ui::Dim BAR_WIDTH = 20;
|
||||
ui::Dim HORIZONTAL_OFFSET = 2;
|
||||
static const int NUM_BARS = 11;
|
||||
static const int BAR_SPACING = 2;
|
||||
static const int SEGMENT_HEIGHT = 10;
|
||||
static constexpr std::array<int16_t, NUM_BARS + 1> FREQUENCY_BANDS = {
|
||||
375, // Bass warmth and low rumble (e.g., deep basslines, kick drum body)
|
||||
750, // Upper bass punch (e.g., bass guitar punch, kick drum attack)
|
||||
1500, // Lower midrange fullness (e.g., warmth in vocals, guitar body)
|
||||
2250, // Midrange clarity (e.g., vocal presence, snare crack)
|
||||
3375, // Upper midrange bite (e.g., instrument definition, vocal articulation)
|
||||
4875, // Presence and edge (e.g., guitar bite, vocal sibilance start)
|
||||
6750, // Lower brilliance (e.g., cymbal shimmer, vocal clarity)
|
||||
9375, // Brilliance and air (e.g., hi-hat crispness, breathy vocals)
|
||||
13125, // High treble sparkle (e.g., subtle overtones, synth shimmer)
|
||||
16875, // Upper treble airiness (e.g., faint harmonics, room ambiance)
|
||||
20625, // Top-end sheen (e.g., ultra-high harmonics, noise floor)
|
||||
24375 // Extreme treble limit (e.g., inaudible overtones, signal cutoff, static)
|
||||
};
|
||||
|
||||
void calculate_params(); // re calculate some parameters based on parent_rect()
|
||||
};
|
||||
|
||||
#endif /*__UI_GRAPHEQ_H__*/
|
||||
@@ -157,7 +157,7 @@ void NoaaAptRx::configure(const NoaaAptRxConfigureMessage& message) {
|
||||
channel_filter_high_f = taps_38k_wfmam_decim_1.high_frequency_normalized * decim_1_input_fs;
|
||||
channel_filter_transition = taps_38k_wfmam_decim_1.transition_normalized * decim_1_input_fs;
|
||||
demod.configure(demod_input_fs, 17000);
|
||||
audio_filter.configure(taps_64_lp_1875_2166.taps);
|
||||
audio_filter.configure(taps_64_bpf_2k4_bw_2k.taps);
|
||||
audio_output.configure(apt_audio_12k_notch_2k4_config, apt_audio_12k_lpf_2000hz_config);
|
||||
// channel_spectrum.set_decimation_factor(1);
|
||||
update_params();
|
||||
|
||||
@@ -1491,6 +1491,26 @@ constexpr fir_taps_real<64> taps_64_lp_1875_2166{
|
||||
}},
|
||||
};
|
||||
|
||||
/* 1st Wideband FM demod baseband filter of audio AM tones ,
|
||||
to pass all DSB band of AM fsubcarrier 2.4Khz mod. with APT */
|
||||
/* 24kHz int16_t input
|
||||
* -> FIR filter, BPF center 2k4 carrier ,APT BW 2kHz
|
||||
* -> 12kHz int16_t output, gain of 1.0 (I think).
|
||||
*/
|
||||
constexpr fir_taps_real<64> taps_64_bpf_2k4_bw_2k{
|
||||
.low_frequency_normalized = -0.1875f, // not updated, this is just for LPF , waterfall GUI, we are not using in BPF NOAA app.
|
||||
.high_frequency_normalized = 0.1875f, // not used GUI in NOAA App.
|
||||
.transition_normalized = 0.03f, // not used GUI in NOAA app.
|
||||
.taps = {{-45, -29, 32, 63, 0, -125, -181, -81, 61,
|
||||
0, -329, -635, -551, -147, 0, -547, -1404, -1625,
|
||||
-849, 0, -414, -2118, -3358, -2422, 0, 911, -1792,
|
||||
-6126, -6773, 0, 11839, 21131, 21131, 11839, 0, -6773,
|
||||
-6126, -1792, 911, 0, -2422, -3358, -2118, -414, 0,
|
||||
-849, -1625, -1404, -547, 0, -147, -551, -635, -329,
|
||||
0, 61, -81, -181, -125, 0, 63, 32, -29,
|
||||
-45}},
|
||||
};
|
||||
|
||||
// TPMS decimation filters ////////////////////////////////////////////////
|
||||
|
||||
// IFIR image-reject filter: fs=2457600, pass=100000, stop=407200, decim=4, fout=614400
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#define __JAMMER_H__
|
||||
|
||||
#define JAMMER_CH_WIDTH 1000000
|
||||
#define JAMMER_MAX_CH 24
|
||||
#define JAMMER_MAX_CH 80
|
||||
|
||||
namespace jammer {
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ struct SharedMemory {
|
||||
union {
|
||||
ToneData tones_data;
|
||||
struct {
|
||||
JammerChannel jammer_channels[24];
|
||||
JammerChannel jammer_channels[80];
|
||||
HopperChannel hopper_channels[24];
|
||||
} dummy_seperate;
|
||||
uint8_t data[512];
|
||||
|
||||
@@ -31,6 +31,8 @@ namespace ui {
|
||||
|
||||
// default font height
|
||||
#define UI_POS_DEFAULT_HEIGHT 16
|
||||
// small height
|
||||
#define UI_POS_DEFAULT_HEIGHT_SMALL 8
|
||||
// default font width
|
||||
#define UI_POS_DEFAULT_WIDTH 8
|
||||
// px position of the linenum-th character (Y)
|
||||
@@ -55,6 +57,8 @@ namespace ui {
|
||||
#define UI_POS_HEIGHT_REMAINING(linenum) ((int)(screen_height - ((linenum)*UI_POS_DEFAULT_HEIGHT)))
|
||||
// remaining px from the charnum-th character to the right of the screen
|
||||
#define UI_POS_WIDTH_REMAINING(charnum) ((int)(screen_width - ((charnum)*UI_POS_DEFAULT_WIDTH)))
|
||||
// px width of the screen
|
||||
#define UI_POS_MAXHEIGHT (screen_height)
|
||||
|
||||
// Escape sequences for colored text; second character is index into term_colors[]
|
||||
#define STR_COLOR_BLACK "\x1B\x00"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2025 RocketGod
|
||||
* Copyright (C) 2025 HTotoo
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -2667,7 +2669,6 @@ bool Waveform::is_clickable() const {
|
||||
}
|
||||
|
||||
void Waveform::getAccessibilityText(std::string& result) {
|
||||
// no idea what this is in use in any places, but others have it
|
||||
result = paused_ ? "paused waveform" : "waveform";
|
||||
}
|
||||
|
||||
@@ -2689,7 +2690,6 @@ bool Waveform::on_key(const KeyEvent key) {
|
||||
}
|
||||
|
||||
bool Waveform::on_keyboard(const KeyboardEvent key) {
|
||||
// no idea what this is for, but others have it
|
||||
if (!clickable_) return false;
|
||||
|
||||
if (key == 32 || key == 10) {
|
||||
@@ -2822,6 +2822,204 @@ void Waveform::paint(Painter& painter) {
|
||||
}
|
||||
}
|
||||
|
||||
/* GraphEq *************************************************************/
|
||||
|
||||
GraphEq::GraphEq(
|
||||
Rect parent_rect,
|
||||
bool clickable)
|
||||
: Widget{parent_rect},
|
||||
clickable_{clickable},
|
||||
bar_heights(NUM_BARS, 0),
|
||||
prev_bar_heights(NUM_BARS, 0) {
|
||||
if (clickable) {
|
||||
set_focusable(true);
|
||||
// previous_data.resize(length_, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphEq::set_parent_rect(const Rect new_parent_rect) {
|
||||
Widget::set_parent_rect(new_parent_rect);
|
||||
calculate_params();
|
||||
}
|
||||
|
||||
void GraphEq::calculate_params() {
|
||||
y_top = screen_rect().top();
|
||||
RENDER_HEIGHT = parent_rect().height();
|
||||
BAR_WIDTH = (parent_rect().width() - (BAR_SPACING * (NUM_BARS - 1))) / NUM_BARS;
|
||||
HORIZONTAL_OFFSET = screen_rect().left();
|
||||
}
|
||||
|
||||
bool GraphEq::is_paused() const {
|
||||
return paused_;
|
||||
}
|
||||
|
||||
void GraphEq::set_paused(bool paused) {
|
||||
paused_ = paused;
|
||||
needs_background_redraw = true;
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
bool GraphEq::is_clickable() const {
|
||||
return clickable_;
|
||||
}
|
||||
|
||||
void GraphEq::getAccessibilityText(std::string& result) {
|
||||
result = paused_ ? "paused GraphEq" : "GraphEq";
|
||||
}
|
||||
|
||||
void GraphEq::getWidgetName(std::string& result) {
|
||||
result = "GraphEq";
|
||||
}
|
||||
|
||||
bool GraphEq::on_key(const KeyEvent key) {
|
||||
if (!clickable_) return false;
|
||||
|
||||
if (key == KeyEvent::Select) {
|
||||
set_paused(!paused_);
|
||||
if (on_select) {
|
||||
on_select(*this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GraphEq::on_keyboard(const KeyboardEvent key) {
|
||||
if (!clickable_) return false;
|
||||
|
||||
if (key == 32 || key == 10) {
|
||||
set_paused(!paused_);
|
||||
if (on_select) {
|
||||
on_select(*this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GraphEq::on_touch(const TouchEvent event) {
|
||||
if (!clickable_) return false;
|
||||
|
||||
switch (event.type) {
|
||||
case TouchEvent::Type::Start:
|
||||
focus();
|
||||
return true;
|
||||
|
||||
case TouchEvent::Type::End:
|
||||
set_paused(!paused_);
|
||||
if (on_select) {
|
||||
on_select(*this);
|
||||
}
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void GraphEq::set_theme(Color base_color_, Color peak_color_) {
|
||||
base_color = base_color_;
|
||||
peak_color = peak_color_;
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void GraphEq::update_audio_spectrum(const AudioSpectrum& spectrum) {
|
||||
const float bin_frequency_size = 48000.0f / 128;
|
||||
|
||||
for (int bar = 0; bar < NUM_BARS; bar++) {
|
||||
float start_freq = FREQUENCY_BANDS[bar];
|
||||
float end_freq = FREQUENCY_BANDS[bar + 1];
|
||||
|
||||
int start_bin = std::max(1, (int)(start_freq / bin_frequency_size));
|
||||
int end_bin = std::min(127, (int)(end_freq / bin_frequency_size));
|
||||
|
||||
if (start_bin >= end_bin) {
|
||||
end_bin = start_bin + 1;
|
||||
}
|
||||
|
||||
float total_energy = 0;
|
||||
int bin_count = 0;
|
||||
|
||||
for (int bin = start_bin; bin <= end_bin; bin++) {
|
||||
total_energy += spectrum.db[bin];
|
||||
bin_count++;
|
||||
}
|
||||
|
||||
float avg_db = bin_count > 0 ? (total_energy / bin_count) : 0;
|
||||
|
||||
// Manually boost highs for better visual balance
|
||||
float treble_boost = 1.0f;
|
||||
if (bar == 10)
|
||||
treble_boost = 1.7f;
|
||||
else if (bar >= 9)
|
||||
treble_boost = 1.3f;
|
||||
else if (bar >= 7)
|
||||
treble_boost = 1.3f;
|
||||
|
||||
// Mid emphasis for a V-shape effect
|
||||
float mid_boost = 1.0f;
|
||||
if (bar == 4 || bar == 5 || bar == 6) mid_boost = 1.2f;
|
||||
|
||||
float amplified_db = avg_db * treble_boost * mid_boost;
|
||||
|
||||
if (amplified_db > 255) amplified_db = 255;
|
||||
|
||||
float band_scale = 1.0f;
|
||||
int target_height = (amplified_db * RENDER_HEIGHT * band_scale) / 255;
|
||||
|
||||
if (target_height > RENDER_HEIGHT) {
|
||||
target_height = RENDER_HEIGHT;
|
||||
}
|
||||
|
||||
// Adjusted to look nice to my eyes
|
||||
float rise_speed = 0.8f;
|
||||
float fall_speed = 1.0f;
|
||||
|
||||
if (target_height > bar_heights[bar]) {
|
||||
bar_heights[bar] = bar_heights[bar] * (1.0f - rise_speed) + target_height * rise_speed;
|
||||
} else {
|
||||
bar_heights[bar] = bar_heights[bar] * (1.0f - fall_speed) + target_height * fall_speed;
|
||||
}
|
||||
}
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void GraphEq::paint(Painter& painter) {
|
||||
if (!visible()) return;
|
||||
if (!is_calculated) { // calc positions first
|
||||
calculate_params();
|
||||
is_calculated = true;
|
||||
}
|
||||
if (needs_background_redraw) {
|
||||
painter.fill_rectangle(screen_rect(), Theme::getInstance()->bg_darkest->background);
|
||||
needs_background_redraw = false;
|
||||
}
|
||||
if (paused_) {
|
||||
return;
|
||||
}
|
||||
const int num_segments = RENDER_HEIGHT / SEGMENT_HEIGHT;
|
||||
uint16_t bottom = screen_rect().bottom();
|
||||
for (int bar = 0; bar < NUM_BARS; bar++) {
|
||||
int x = HORIZONTAL_OFFSET + bar * (BAR_WIDTH + BAR_SPACING);
|
||||
int active_segments = (bar_heights[bar] * num_segments) / RENDER_HEIGHT;
|
||||
|
||||
if (prev_bar_heights[bar] > active_segments) {
|
||||
int clear_height = (prev_bar_heights[bar] - active_segments) * SEGMENT_HEIGHT;
|
||||
int clear_y = bottom - prev_bar_heights[bar] * SEGMENT_HEIGHT;
|
||||
painter.fill_rectangle({x, clear_y, BAR_WIDTH, clear_height}, Theme::getInstance()->bg_darkest->background);
|
||||
}
|
||||
|
||||
for (int seg = 0; seg < active_segments; seg++) {
|
||||
int y = bottom - (seg + 1) * SEGMENT_HEIGHT;
|
||||
if (y < y_top) break;
|
||||
|
||||
Color segment_color = (seg >= active_segments - 2 && seg < active_segments) ? peak_color : base_color;
|
||||
painter.fill_rectangle({x, y, BAR_WIDTH, SEGMENT_HEIGHT - 1}, segment_color);
|
||||
}
|
||||
prev_bar_heights[bar] = active_segments;
|
||||
}
|
||||
}
|
||||
|
||||
/* VuMeter **************************************************************/
|
||||
|
||||
VuMeter::VuMeter(
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2025 RocketGod
|
||||
* Copyright (C) 2025 HTotoo
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -1015,6 +1017,66 @@ class Waveform : public Widget {
|
||||
bool if_ever_painted_pause{false}; // for prevent the "hidden" label keeps painting and being expensive
|
||||
};
|
||||
|
||||
class GraphEq : public Widget {
|
||||
public:
|
||||
std::function<void(GraphEq&)> on_select{};
|
||||
|
||||
GraphEq(Rect parent_rect, bool clickable = false);
|
||||
GraphEq(const GraphEq&) = delete;
|
||||
GraphEq(GraphEq&&) = delete;
|
||||
GraphEq& operator=(const GraphEq&) = delete;
|
||||
GraphEq& operator=(GraphEq&&) = delete;
|
||||
|
||||
bool is_paused() const;
|
||||
void set_paused(bool paused);
|
||||
bool is_clickable() const;
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
bool on_key(const KeyEvent key) override;
|
||||
bool on_touch(const TouchEvent event) override;
|
||||
bool on_keyboard(const KeyboardEvent event) override;
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
|
||||
void getAccessibilityText(std::string& result) override;
|
||||
void getWidgetName(std::string& result) override;
|
||||
void update_audio_spectrum(const AudioSpectrum& spectrum);
|
||||
void set_theme(Color base_color, Color peak_color);
|
||||
|
||||
private:
|
||||
bool is_calculated{false};
|
||||
bool paused_{false};
|
||||
bool clickable_{false};
|
||||
bool needs_background_redraw{true}; // Redraw background only when needed.
|
||||
Color base_color = Color(255, 0, 255);
|
||||
Color peak_color = Color(255, 255, 255);
|
||||
std::vector<ui::Dim> bar_heights;
|
||||
std::vector<ui::Dim> prev_bar_heights;
|
||||
|
||||
ui::Dim y_top = 2 * 16;
|
||||
ui::Dim RENDER_HEIGHT = 288;
|
||||
ui::Dim BAR_WIDTH = 20;
|
||||
ui::Dim HORIZONTAL_OFFSET = 2;
|
||||
static const int NUM_BARS = 11;
|
||||
static const int BAR_SPACING = 2;
|
||||
static const int SEGMENT_HEIGHT = 10;
|
||||
static constexpr std::array<int16_t, NUM_BARS + 1> FREQUENCY_BANDS = {
|
||||
375, // Bass warmth and low rumble (e.g., deep basslines, kick drum body)
|
||||
750, // Upper bass punch (e.g., bass guitar punch, kick drum attack)
|
||||
1500, // Lower midrange fullness (e.g., warmth in vocals, guitar body)
|
||||
2250, // Midrange clarity (e.g., vocal presence, snare crack)
|
||||
3375, // Upper midrange bite (e.g., instrument definition, vocal articulation)
|
||||
4875, // Presence and edge (e.g., guitar bite, vocal sibilance start)
|
||||
6750, // Lower brilliance (e.g., cymbal shimmer, vocal clarity)
|
||||
9375, // Brilliance and air (e.g., hi-hat crispness, breathy vocals)
|
||||
13125, // High treble sparkle (e.g., subtle overtones, synth shimmer)
|
||||
16875, // Upper treble airiness (e.g., faint harmonics, room ambiance)
|
||||
20625, // Top-end sheen (e.g., ultra-high harmonics, noise floor)
|
||||
24375 // Extreme treble limit (e.g., inaudible overtones, signal cutoff, static)
|
||||
};
|
||||
|
||||
void calculate_params(); // re calculate some parameters based on parent_rect()
|
||||
};
|
||||
|
||||
class VuMeter : public Widget {
|
||||
public:
|
||||
VuMeter(Rect parent_rect, uint32_t LEDs, bool show_max);
|
||||
|
||||
@@ -3,6 +3,5 @@
|
||||
Licensed under [GNU GPL v3](../../../LICENSE)
|
||||
|
||||
USAGE:
|
||||
- Copy file from: https://raw.githubusercontent.com/kx1t/planefence-airlinecodes/main/airlinecodes.txt
|
||||
- Run Python 3 script: `./make_airlines_db.py`
|
||||
- Copy file to /ADSB folder on SDCARD
|
||||
- Move file "airlines.db" to /ADSB folder on SDCARD
|
||||
|
||||
@@ -325,7 +325,7 @@ AMT,ATA Airlines,,United States
|
||||
AMU,Air Macau,AIR MACAO,Macao
|
||||
AMV,AMC Airlines,,Egypt
|
||||
AMW,Air Midwest,,United States
|
||||
AMX,Aeroméxico,AEROMEXICO,Mexico
|
||||
AMX,Aeromexico,AEROMEXICO,Mexico
|
||||
AMY,Air Ambar,AIR AMBAR,Dominican Republic
|
||||
AMZ,Amiya Airline,AMIYA AIR,Nigeria
|
||||
ANA,All Nippon Airways,ALL NIPPON,Japan
|
||||
@@ -408,7 +408,7 @@ ARC,Air Routing International Corp.,,United States
|
||||
ARD,Aerocondor,,Portugal
|
||||
ARE,Aires Aerovías de Integración Regional S.A.,AIRES,Colombia
|
||||
ARF,Aero Flight,,Germany
|
||||
ARG,Aerolíneas Argentinas,ARGENTINA,Argentina
|
||||
ARG,Aerolineas Argentinas,ARGENTINA,Argentina
|
||||
ARH,Aerohelicopteros,AEROHELCA,Venezuela
|
||||
ARI,Aero Vics,AEROVICS,Mexico
|
||||
ARJ,Aerojet de Costa Rica S.A.,,Costa Rica
|
||||
@@ -820,7 +820,7 @@ BNT,Bentiu Air Transport,BENTIU AIR,Sudan
|
||||
BNV,Benane Aviation Corporation,BENANE,Mauritania
|
||||
BNW,British North West Airlines,BRITISH NORTH,United Kingdom
|
||||
BNX,LAI - Línea Aérea IAACA,AIR BARINAS,Venezuela
|
||||
BNZ,Aerolíneas Bonanza,AERO BONANZA,Mexico
|
||||
BNZ,Bonza,BONZA,Australia
|
||||
BOA,Boniair,KUMANOVO,Macedonia
|
||||
BOB,Backbone A/S,BACKBONE,Denmark
|
||||
BOC,Aerobona,AEROBONA,Mexico
|
||||
@@ -1465,6 +1465,7 @@ DET,DETA Air,SAMAL,Kazakhstan
|
||||
DEV,Red Devils Parachute Display Team,RED DEVILS,United Kingdom
|
||||
DFA,Aero Coach Aviation,AERO COACH,United States
|
||||
DFC,Aeropartner,DARK BLUE,Czech Republic
|
||||
DFL,Babcock AirAmbulance,MEDIFLIGHT,Sweden
|
||||
DFS,Dwyer Aircraft Services,DWYAIR,United States
|
||||
DGA,Yellow River Delta General Aviation,YELLOW RIVER,China
|
||||
DGO,DGO Jet,DGO JET,Mexico
|
||||
@@ -1592,8 +1593,10 @@ DVA,Discovery Airways,DISCOVERY AIRWAYS,United States
|
||||
DVB,Don Avia,DONSEBAI,Kazakhstan
|
||||
DVI,Aero Davinci International,AERO DAVINCI,Mexico
|
||||
DVN,Adventia,,Spain
|
||||
DVY,Legends Airways,DEVIL RAY,United States
|
||||
DWA,Dense Airways,,United States
|
||||
DWD,Deutsche Luftverkehrsgesellschaft (DLT),,Germany
|
||||
DWI,Arajet,DOMINICAN,Dominican Republic
|
||||
DWN,Dawn Air,DAWN AIR,United States
|
||||
DWR,Delaware Skyways,DELAWARE,United States
|
||||
DWT,Darwin Airline,DARWIN,Switzerland
|
||||
@@ -1905,6 +1908,7 @@ EZJ,Ezjet GT,GUYANA JET,Guyana
|
||||
EZS,easyJet Switzerland,TOPSWISS,Switzerland
|
||||
EZX,Eagle Express Air Charter,EAGLEXPRESS,Malaysia
|
||||
EZY,easyJet UK,EASY,United Kingdom
|
||||
EZZ,ETF Airways,ENTERPRIZE,Croatia
|
||||
FAB,First Air,FIRST AIR,Canada
|
||||
FAC,Atlantic Helicopters,FAROECOPTER,Denmark
|
||||
FAD,Rog-Air,AIR FRONTIER,Canada
|
||||
@@ -2034,7 +2038,7 @@ FJI,Fiji Airways,PACIFIC,Fiji
|
||||
FJK,Fly Jet Kz,,Kazakhstan
|
||||
FJM,Fly Jamaica Airways,GREENHEART,Jamaica
|
||||
FJO,Flexjet Operations Malta Limited,FLEX MALTA,Malta
|
||||
FRJ,Fly Jordan,SOLITAIRE AIR,Jordan
|
||||
FJR,Fly Jordan,SOLITAIRE AIR,Jordan
|
||||
FJS,Florida Jet Service,FLORIDAJET,United States
|
||||
FKA,Flying kangaroo Airline,,Australia
|
||||
FKI,FLM Aviation Mohrdieck,KIEL AIR,Germany
|
||||
@@ -2397,7 +2401,7 @@ GPL,DLR,GERMAN POLAR,Germany
|
||||
GPM,Grup Air-Med,GRUPOMED,Spain
|
||||
GPR,GPM Aeroservicio,GPM AEROSERVICIO,Mexico
|
||||
GPX,GP Aviation,,Bulgaria
|
||||
GRA,Guardian Air Asset Management,FLEX,South Africa
|
||||
GRA,Costa Rica Green Airways,,,Costa Rica
|
||||
GRB,Phoenix Air Group,GREY BIRD,United States
|
||||
GRD,National Grid plc,GRID,United Kingdom
|
||||
GRE,Air Scotland,GREECE AIRWAYS,Greece
|
||||
@@ -2459,7 +2463,7 @@ GWN,Gwyn Aviation,GWYN,United Kingdom
|
||||
GWR,Aura Airlines,LEMON,Spain
|
||||
GWS,General Airways,GENAIR,South Africa
|
||||
GWY,USA3000 Airlines,GETAWAY,United States
|
||||
GXA,Grixona,GRIXONA,Moldova
|
||||
GXA,GlobalX Airlines,GEMINI,United States
|
||||
GXG,GermanXL,,Germany
|
||||
GXL,Star XL German Airlines,STARDUST,Germany
|
||||
GXY,Galaxy Airlines,GALAX,Japan
|
||||
@@ -2608,6 +2612,7 @@ HMC,Heliamerica De Mexico,HELIAMERICA,Mexico
|
||||
HMD,Charlie Hammonds Flying Service,HAMMOND,United States
|
||||
HME,Babcock MCS,HELIMED,United Kingdom
|
||||
HMF,Norrlandsflyg,LIFEGUARD SWEDEN,Sweden
|
||||
HMJ,Harmony Jets,HARMONG JETS,Malta
|
||||
HMM,Hamra Air,HAMRA,United Arab Emirates
|
||||
HMP,Papair Terminal,PAPAIR TERMINAL,Haiti
|
||||
HMR,North American Charters,HAMMER,Canada
|
||||
@@ -2623,6 +2628,7 @@ HNR,Haiti National Airlines (HANA),HANAIR,Haiti
|
||||
HNT,Helicopteros Internacionales,HELICOP INTER,Mexico
|
||||
HNX,Hankook Airline,,South Korea
|
||||
HOA,Hola Airlines,HOLA,Spain
|
||||
HOB,HL Aircraft LLC,HOBBY JET,United States
|
||||
HOG,Mahogany Air Charters,HOGAN AIR,Zambia
|
||||
HOL,Holiday Airlines (US Airline),HOLIDAY,United States
|
||||
HOM,Aero Homex,AERO HOMEX,Mexico
|
||||
@@ -3077,7 +3083,7 @@ JSM,Jet Stream,,Moldova
|
||||
JSN,Suncor Energy Inc,JETSUN,Canada
|
||||
JSP,Palmer Aviation,PALMER,United Kingdom
|
||||
JSR,Jusur airways,,Egypt
|
||||
JST,Eastern Australia Airlines,,Australia
|
||||
JST,Jetstar Airways,,Australia
|
||||
JSV,Japan Aircraft Service,,Japan
|
||||
JSW,Jigsaw Project,,United Kingdom
|
||||
JSX,JetSuiteX Air,BIGSTRIPE,United States
|
||||
@@ -3617,6 +3623,7 @@ MAK,MAT Macedonian Airlines,MAKAVIO,Macedonia
|
||||
MAL,Morningstar Air Express,MORNINGSTAR,Canada
|
||||
MAM,Aeródromo De La Mancha,AEROMAN,Spain
|
||||
MAN,Mannion Air Charter,MANNION,United States
|
||||
MAO,Mayo Clinic,MAYO,United States
|
||||
MAQ,Mac Aviation,MAC AVIATION,Spain
|
||||
MAR,March Helicopters,MARCH,United Kingdom
|
||||
MAS,Malaysia Airlines,MALAYSIAN,Malaysia
|
||||
@@ -3633,6 +3640,7 @@ MBC,Airjet Exploracao Aerea de Carga,MABECO,Angola
|
||||
MBE,Martin-Baker,MARTIN,United Kingdom
|
||||
MBG,Zephyr Aviation,CHALGROVE,United Kingdom
|
||||
MBI,Mountain Bird,MOUNTAIN BIRD,United States
|
||||
MBK,Chrono Jet,MATBLACK,Canada
|
||||
MBL,First City Air,FIRST CITY,United Kingdom
|
||||
MBN,Zambian Airways,ZAMBIANA,Zambia
|
||||
MBO,Mobil Oil,MOBIL,Canada
|
||||
@@ -4441,6 +4449,7 @@ PDI,Paradise Island Airways,PARADISE ISLAND,United States
|
||||
PDQ,PDQ Air Charter,DISPATCH,United States
|
||||
PDR,COMAV,SPEEDSTER,Namibia
|
||||
PDT,Piedmont Airlines,PIEDMONT,United States
|
||||
PDU,Purdue University,PURDUE,United States
|
||||
PDV,Elicar,ELICAR,Italy
|
||||
PDY,Pen-Avia,PENDLEY,United Kingdom
|
||||
PEA,Pan Europeenne Air Service,,France
|
||||
@@ -4564,6 +4573,7 @@ PNH,Panh,KUBAN LIK,Russia
|
||||
PNK,Air Pink,AIRPINK,Serbia
|
||||
PNL,Aero Personal,AEROPERSONAL,Mexico
|
||||
PNM,Panorama,PANORAMA,Spain
|
||||
PNO,Panorama Aviation,PANO,Canada
|
||||
PNP,Pineapple Air,PINEAPPLE AIR,Bahamas
|
||||
PNR,PAN Air,SKYJET,Spain
|
||||
PNS,Survey Udara (Penas),PENAS,Indonesia
|
||||
@@ -4931,6 +4941,7 @@ RNB,Rosneft-Baltika,ROSBALT,Russia
|
||||
RND,Rutland Aviation,RUTLAND,United Kingdom
|
||||
RNE,Air Salone,AIR SALONE,Sierra Leone
|
||||
RNG,Orange Aircraft Leasing,ORANGE,Netherlands
|
||||
RNI,Rennia Aviation,RENNIA,United States
|
||||
RNM,Aeronem Air Cargo,AEROMNEM,Ecuador
|
||||
RNR,Air Cargo Masters,RUNNER,United States
|
||||
RNS,Ronso,RONSO,Mexico
|
||||
@@ -5216,6 +5227,7 @@ SFU,Solent Flight,SAINTS,United Kingdom
|
||||
SFW,Safi Airways,SAFI,AF
|
||||
SFX,S.K. Logistics,SWAMP FOX,United States
|
||||
SFY,Gulf Flite Center,SKY FLITE,United States
|
||||
SFZ,Pionair,SKYFORCE,Australia
|
||||
SGA,Air Saigon,AIR SAIGON,Vietnam
|
||||
SGB,Sky King Inc.,SONGBIRD,United States
|
||||
SGC,SGC Aviation,SAINT GEORGE,Austria
|
||||
@@ -5270,7 +5282,7 @@ SIE,Sierra Express,SEREX,United States
|
||||
SIH,Skynet Airlines,BLUEJET,Ireland
|
||||
SII,Aero Servicios Ejecutivos Internacionales,ASEISA,Mexico
|
||||
SIJ,Seco International,,Japan
|
||||
SIL,Servicios Aeronáuticos Integrales,SERVICIOS INTEGRALES,Mexico
|
||||
SIL,Silver Airways,SILVER WINGS,United States
|
||||
SIM,Star Air,,Sierra Leone
|
||||
SIO,Sirio,SIRIO,Italy
|
||||
SIP,Air Spirit,AIR SPIRIT,United States
|
||||
@@ -5802,6 +5814,7 @@ TKC,Tikal Jets Airlines,TIKAL,Guatemala
|
||||
TKE,Take Air Line,ISLAND BIRD,France
|
||||
TKJ,Tarkim Aviation,TARKIM AVIATION,Turkey
|
||||
TKK,FlyADVANCED,,United States
|
||||
TKM,JM Family Aviation,TACOMA,United States
|
||||
TKR,Canadian Interagency Forest Fire Centre,TANKER,Canada
|
||||
TKS,Tomsk-Avia,,Russia
|
||||
TKX,Tropical International Airways,TROPEXPRESS,Saint Kitts and Nevis
|
||||
@@ -5827,6 +5840,7 @@ TLW,Teamline Air,Teamline,Austria
|
||||
TLX,Telesis Transair,TELESIS,United States
|
||||
TLY,Top Fly,TOPFLY,Spain
|
||||
TMA,Trans Mediterranean Airlines,TANGO LIMA,Lebanon
|
||||
TMB,Volato,TOMBO,United States
|
||||
TMC,Travel Management Company,TRAIL BLAZER,United States
|
||||
TMD,Transmandu,TRANSMANDU,Venezuela
|
||||
TME,Aero Taxi del Centro de Mexico,TAXICENTRO,Mexico
|
||||
@@ -6086,6 +6100,7 @@ UDC,DonbassAero,DONBASS AERO,Ukraine
|
||||
UDN,Dniproavia,DNIEPRO,Ukraine
|
||||
UEA,United Eagle Airlines,UNITED EAGLE,China
|
||||
UED,Air LA,AIR L-A,United States
|
||||
UEE,United Eagle,EAGLE SLOVENIA,Slovenia
|
||||
UEJ,Jetcorp,,United States
|
||||
UES,Ues-Avia Aircompany,AVIASYSTEM,Ukraine
|
||||
UEU,United European Airlines,UNITED EUROPEAN,Romania
|
||||
@@ -6200,7 +6215,7 @@ VAL,Voyageur Airways,VOYAGEUR,Canada
|
||||
VAM,Ameravia,AMERAVIA,Uruguay
|
||||
VAN,Caravan Air,CAMEL,Mauritania
|
||||
VAP,Phuket Air,PHUKET AIR,Thailand
|
||||
VAR,Veca Airlines,VECA,El Salvador
|
||||
VAR,United Aviate Academy,VARNEY,United States
|
||||
VAS,ATRAN Cargo Airlines,ATRAN,Russian Federation
|
||||
VAT,Visionair,VISIONAIR,Ireland
|
||||
VAU,V Australia Airlines,,Australia
|
||||
@@ -6278,7 +6293,7 @@ VIS,Vision Air International,,Pakistan
|
||||
VIT,Aviastar Mandiri,AVIASTAR,Indonesia
|
||||
VIV,Aeroenlaces Nacionales,AEROENLACES,Mexico
|
||||
VIZ,Aerovis Airlines,AEROVIZ,Ukraine
|
||||
VJA,ValuJet Airlines,CRITTER,United States
|
||||
VJA,Vista America,ICONIC,United States
|
||||
VJC,Vietjet Air,VIETJET,Vietnam
|
||||
VJE,AvJet Routing,,United Arab Emirates
|
||||
VJM,Viajes Ejecutivos Mexicanos,VIAJES MEXICANOS,Mexico
|
||||
@@ -6320,7 +6335,7 @@ VNG,Aero Servicios Vanguardia,VANGUARDIA,Mexico
|
||||
VNK,Vipport Joint Stock Company,,Russia
|
||||
VNL,Vanilla Air,VANILLA,Japan
|
||||
VNP,Virgin Pacific,,Fiji
|
||||
VNT,Avient Air Zambia,AVIENT,Zambia
|
||||
VNT,Ventura,VENTURA,United States
|
||||
VNX,Fly Advance,VANCE,United States
|
||||
VNZ,Tbilaviamsheni,TBILAVIA,Georgia
|
||||
VOA,Viaggio Air,VIAGGIO,Bulgaria
|
||||
@@ -6331,7 +6346,7 @@ VOI,Volaris,VOLARIS,Mexico
|
||||
VOL,Blue Chip Jet,BLUE SPEED,Sweden
|
||||
VOO,Volotea,,Spain
|
||||
VOR,Flight Calibration Services Ltd.,FLIGHT CAL,United Kingdom
|
||||
VOS,Rovos Air,ROVOS,South Africa
|
||||
VOS,Volaris El Salvador,JETSAL,South Africa
|
||||
VOZ,Virgin Australia,VELOCITY,Australia
|
||||
VPA,DanubeWings,VIP TAXI,Slovakia
|
||||
VPB,Veteran Air,VETERAN,Ukraine
|
||||
@@ -6422,7 +6437,7 @@ WAU,Wizz Air Ukraine,,Ukraine
|
||||
WAV,Warbelow's Air Ventures,WARBELOW,United States
|
||||
WAW,Wings Airways,WING SHUTTLE,United States
|
||||
WAY,Airways,GARONNE,France
|
||||
WAZ,Wizz Air,WIZZ SKY,United Arab Emirates
|
||||
WAZ,Wizz Air Abu Dabi,WIZZ SKY,United Arab Emirates
|
||||
WBA,Finncomm Airlines,WESTBIRD,Finland
|
||||
WBR,Multi-Aero,WEBER,United States
|
||||
WCA,West Coast Airways,WEST-LEONE,Sierra Leone
|
||||
@@ -6499,6 +6514,8 @@ WLX,West Air Luxembourg,WEST LUX,Luxembourg
|
||||
WMA,Watermakers Air,WATERMAKERS,United States
|
||||
WML,Chantilly Air,MARLIN,United States
|
||||
WMN,Trident Aircraft,WATERMAN,United States
|
||||
WMT,Wizz Air Malta,WIZZAIR MALTA,Malta
|
||||
WMU,W.M. Uni. College of Aviation,SKYBRONCO,United States
|
||||
WNA,Winair,WINAIR,United States
|
||||
WNR,Wondair on Demand Aviation,WONDAIR,Spain
|
||||
WOA,World Airways,WORLD,United States
|
||||
@@ -6535,6 +6552,7 @@ WTP,Westpoint Air,WESTPOINT,Canada
|
||||
WTV,Western Aviators,WESTAVIA,United States
|
||||
WUE,Adolf Wurth GmbH,FASTY,Germany
|
||||
WUK,Wizz Air UK,WIZZ GO,United Kingdom
|
||||
WUP,Wheels Up,UPJET,United States
|
||||
WVA,Hand D Aviation,WABASH VALLEY,United States
|
||||
WVL,Wizz Air Bulgaria,WIZZBUL,Bulgaria
|
||||
WVZ,Winair,,HR
|
||||
@@ -6548,7 +6566,7 @@ WYC,Wycombe Air Centre,WYCOMBE,United Kingdom
|
||||
WYG,Wyoming Airlines,WYOMING,United States
|
||||
WYT,2 Sqn No 1 Elementary Flying Training School,WYTON,United Kingdom
|
||||
WZP,Zip,ZIPPER,Canada
|
||||
WZZ,Wizz Air,WIZZAIR,Hungary
|
||||
WZZ,Wizz Air Hungary,WIZZAIR,Hungary
|
||||
X9F,FRA Air,,Germany
|
||||
XAA,Aeronautical Radio Inc,,United States
|
||||
XAB,Xabre Aerolineas,AERO XABRE,Mexico
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (C) 2021 ArjanOnwezen
|
||||
# Copyright (C) 2025 Tommaso Ventafridda
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -22,26 +23,251 @@
|
||||
# as a source.
|
||||
# -------------------------------------------------------------------------------------
|
||||
import csv
|
||||
import os
|
||||
import shutil
|
||||
import unicodedata
|
||||
icao_codes=bytearray()
|
||||
airlines_countries=bytearray()
|
||||
row_count=0
|
||||
database=open("airlines.db", "wb")
|
||||
with open('airlinecodes.txt', 'rt', encoding="utf-8") as csv_file:
|
||||
sorted_lines=sorted(csv_file.readlines()[1:])
|
||||
for row in csv.reader(sorted_lines, quotechar='"', delimiter=',', quoting=csv.QUOTE_ALL, skipinitialspace=True):
|
||||
icao_code=row[0]
|
||||
# Normalize some unicode characters
|
||||
airline=unicodedata.normalize('NFKD', row[1][:32]).encode('ascii', 'ignore')
|
||||
country=unicodedata.normalize('NFKD', row[3][:32]).encode('ascii', 'ignore')
|
||||
if len(icao_code) == 3 :
|
||||
airline_padding=bytearray()
|
||||
country_padding=bytearray()
|
||||
icao_codes=icao_codes+bytearray(icao_code+'\0', encoding='ascii')
|
||||
airline_padding=bytearray('\0' * (32 - len(airline)), encoding='ascii')
|
||||
country_padding=bytearray('\0' * (32 - len(country)), encoding='ascii')
|
||||
airlines_countries=airlines_countries+bytearray(airline+airline_padding+country+country_padding)
|
||||
row_count+=1
|
||||
database.write(icao_codes+airlines_countries)
|
||||
print("Total of", row_count, "ICAO codes stored in database")
|
||||
import urllib.request
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Set, Tuple
|
||||
|
||||
|
||||
@dataclass
|
||||
class AirlineRecord:
|
||||
"""Represents an airline record with all relevant fields"""
|
||||
|
||||
icao_code: str
|
||||
airline: str
|
||||
country: str
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.icao_code)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, AirlineRecord):
|
||||
return False
|
||||
return self.icao_code == other.icao_code
|
||||
|
||||
def data_equals(self, other):
|
||||
"""Check if all data fields are equal (excluding ICAO code which is the key)"""
|
||||
if not isinstance(other, AirlineRecord):
|
||||
return False
|
||||
return self.airline == other.airline and self.country == other.country
|
||||
|
||||
|
||||
def backup_previous_file() -> bool:
|
||||
"""Backup the existing airlinecodes.txt file if it exists. Returns True if backup was made."""
|
||||
if os.path.exists("airlinecodes.txt"):
|
||||
shutil.copy2("airlinecodes.txt", "airlinecodes_previous.txt")
|
||||
print("Backed up previous airline codes file")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def download_airline_codes() -> None:
|
||||
"""Download the airline codes file from GitHub"""
|
||||
url = "https://raw.githubusercontent.com/kx1t/planefence-airlinecodes/main/airlinecodes.txt"
|
||||
print(f"Downloading airline codes database from {url}...")
|
||||
try:
|
||||
urllib.request.urlretrieve(url, "airlinecodes.txt")
|
||||
print("Download completed successfully.")
|
||||
except Exception as e:
|
||||
print(f"Error downloading airline codes file: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def parse_airline_file(filename: str) -> Dict[str, AirlineRecord]:
|
||||
"""Parse airline codes file and return a dictionary of records"""
|
||||
records = {}
|
||||
|
||||
if not os.path.exists(filename):
|
||||
return records
|
||||
|
||||
try:
|
||||
with open(filename, "rt", encoding="utf-8") as csv_file:
|
||||
sorted_lines = sorted(csv_file.readlines()[1:])
|
||||
for row in csv.reader(
|
||||
sorted_lines,
|
||||
quotechar='"',
|
||||
delimiter=",",
|
||||
quoting=csv.QUOTE_ALL,
|
||||
skipinitialspace=True,
|
||||
):
|
||||
if len(row) >= 4: # Ensure we have enough columns
|
||||
icao_code = row[0]
|
||||
# Normalize some unicode characters
|
||||
airline = (
|
||||
unicodedata.normalize("NFKD", row[1][:32])
|
||||
.encode("ascii", "ignore")
|
||||
.decode("ascii")
|
||||
)
|
||||
country = (
|
||||
unicodedata.normalize("NFKD", row[3][:32])
|
||||
.encode("ascii", "ignore")
|
||||
.decode("ascii")
|
||||
)
|
||||
|
||||
if len(icao_code) == 3:
|
||||
records[icao_code] = AirlineRecord(
|
||||
icao_code=icao_code, airline=airline, country=country
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not parse airline file {filename}: {e}")
|
||||
return {}
|
||||
|
||||
return records
|
||||
|
||||
|
||||
def compare_records(
|
||||
old_records: Dict[str, AirlineRecord], new_records: Dict[str, AirlineRecord]
|
||||
) -> Tuple[Set[str], Set[str], Set[str]]:
|
||||
"""Compare old and new records, return sets of new, deleted, and changed ICAO codes"""
|
||||
old_keys = set(old_records.keys())
|
||||
new_keys = set(new_records.keys())
|
||||
|
||||
new_icao_codes = new_keys - old_keys
|
||||
deleted_icao_codes = old_keys - new_keys
|
||||
|
||||
# Check for changes in existing records
|
||||
changed_icao_codes = set()
|
||||
for icao in old_keys & new_keys:
|
||||
if not old_records[icao].data_equals(new_records[icao]):
|
||||
changed_icao_codes.add(icao)
|
||||
|
||||
return new_icao_codes, deleted_icao_codes, changed_icao_codes
|
||||
|
||||
|
||||
def write_database(records: Dict[str, AirlineRecord]) -> int:
|
||||
"""Write records to database file using original format"""
|
||||
icao_codes = bytearray()
|
||||
airlines_countries = bytearray()
|
||||
row_count = 0
|
||||
|
||||
# Sort by ICAO code for consistency
|
||||
sorted_records = sorted(records.values(), key=lambda r: r.icao_code)
|
||||
|
||||
for record in sorted_records:
|
||||
# Normalize and encode data (same as original)
|
||||
airline = unicodedata.normalize("NFKD", record.airline[:32]).encode(
|
||||
"ascii", "ignore"
|
||||
)
|
||||
country = unicodedata.normalize("NFKD", record.country[:32]).encode(
|
||||
"ascii", "ignore"
|
||||
)
|
||||
|
||||
# Add ICAO code with null terminator (original format)
|
||||
icao_codes = icao_codes + bytearray(record.icao_code + "\0", encoding="ascii")
|
||||
|
||||
# Add padded data fields (original format)
|
||||
airline_padding = bytearray("\0" * (32 - len(airline)), encoding="ascii")
|
||||
country_padding = bytearray("\0" * (32 - len(country)), encoding="ascii")
|
||||
|
||||
airlines_countries = airlines_countries + bytearray(
|
||||
airline + airline_padding + country + country_padding
|
||||
)
|
||||
row_count += 1
|
||||
|
||||
with open("airlines.db", "wb") as database:
|
||||
database.write(icao_codes + airlines_countries)
|
||||
|
||||
return row_count
|
||||
|
||||
|
||||
def create_database() -> None:
|
||||
"""Create the airline database from the downloaded file"""
|
||||
# Backup existing file before downloading new one
|
||||
has_previous = backup_previous_file()
|
||||
|
||||
# Download new file
|
||||
download_airline_codes()
|
||||
|
||||
# Parse both files for comparison
|
||||
old_records = {}
|
||||
if has_previous:
|
||||
print("Parsing previous airline codes file...")
|
||||
old_records = parse_airline_file("airlinecodes_previous.txt")
|
||||
print(f"Found {len(old_records)} records in previous file")
|
||||
|
||||
print("Parsing new airline codes file...")
|
||||
new_records = parse_airline_file("airlinecodes.txt")
|
||||
print(f"Found {len(new_records)} records in new file")
|
||||
|
||||
# Compare records if we have a previous version
|
||||
if old_records:
|
||||
new_icao_codes, deleted_icao_codes, changed_icao_codes = compare_records(
|
||||
old_records, new_records
|
||||
)
|
||||
|
||||
# Print change statistics
|
||||
print("\n" + "=" * 50)
|
||||
print("AIRLINE CODES CHANGE SUMMARY")
|
||||
print("=" * 50)
|
||||
print(f"New records: {len(new_icao_codes):>8}")
|
||||
print(f"Deleted records: {len(deleted_icao_codes):>8}")
|
||||
print(f"Changed records: {len(changed_icao_codes):>8}")
|
||||
print(f"Total records: {len(new_records):>8}")
|
||||
print("=" * 50)
|
||||
|
||||
# Show examples of changes (limited to avoid spam)
|
||||
if new_icao_codes and len(new_icao_codes) <= 15:
|
||||
print(f"\nNew ICAO codes: {', '.join(sorted(new_icao_codes))}")
|
||||
elif new_icao_codes:
|
||||
sample_new = sorted(list(new_icao_codes))[:10]
|
||||
print(
|
||||
f"\nSample new ICAO codes: {', '.join(sample_new)} (and {len(new_icao_codes)-10} more)"
|
||||
)
|
||||
|
||||
if deleted_icao_codes and len(deleted_icao_codes) <= 15:
|
||||
print(f"Deleted ICAO codes: {', '.join(sorted(deleted_icao_codes))}")
|
||||
elif deleted_icao_codes:
|
||||
sample_deleted = sorted(list(deleted_icao_codes))[:10]
|
||||
print(
|
||||
f"Sample deleted ICAO codes: {', '.join(sample_deleted)} (and {len(deleted_icao_codes)-10} more)"
|
||||
)
|
||||
|
||||
if changed_icao_codes and len(changed_icao_codes) <= 15:
|
||||
print(f"Changed ICAO codes: {', '.join(sorted(changed_icao_codes))}")
|
||||
elif changed_icao_codes:
|
||||
sample_changed = sorted(list(changed_icao_codes))[:10]
|
||||
print(
|
||||
f"Sample changed ICAO codes: {', '.join(sample_changed)} (and {len(changed_icao_codes)-10} more)"
|
||||
)
|
||||
|
||||
# Show some specific examples of changes
|
||||
if changed_icao_codes:
|
||||
print(f"\nExample changes:")
|
||||
for icao in sorted(list(changed_icao_codes))[:3]:
|
||||
old_rec = old_records[icao]
|
||||
new_rec = new_records[icao]
|
||||
if old_rec.airline != new_rec.airline:
|
||||
print(
|
||||
f" {icao}: Airline '{old_rec.airline}' → '{new_rec.airline}'"
|
||||
)
|
||||
if old_rec.country != new_rec.country:
|
||||
print(
|
||||
f" {icao}: Country '{old_rec.country}' → '{new_rec.country}'"
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"\nNo previous airline codes file found - this appears to be the first run"
|
||||
)
|
||||
|
||||
# Create database from new records
|
||||
print("\nCreating airline database...")
|
||||
row_count = write_database(new_records)
|
||||
print("Total of", row_count, "ICAO codes stored in database")
|
||||
|
||||
|
||||
def cleanup_temp() -> None:
|
||||
"""Cleanup temporary files created during the process"""
|
||||
if os.path.exists("airlinecodes_previous.txt"):
|
||||
os.remove("airlinecodes_previous.txt")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
create_database()
|
||||
cleanup_temp()
|
||||
except Exception as e:
|
||||
print(f"Error creating airline database: {e}")
|
||||
else:
|
||||
print("Airline database created successfully.")
|
||||
|
||||
@@ -3,6 +3,5 @@
|
||||
Licensed under [GNU GPL v3](../../../LICENSE)
|
||||
|
||||
USAGE:
|
||||
- Copy file from: https://opensky-network.org/datasets/metadata/aircraftDatabase.csv
|
||||
- Run Python 3 script: `./make_icao24_db.py`
|
||||
- Copy file to /ADSB folder on SDCARD
|
||||
- Move "icao24.db" file to /ADSB folder on SDCARD
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (C) 2021 ArjanOnwezen
|
||||
# Copyright (C) 2025 Tommaso Ventafridda
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -20,43 +21,275 @@
|
||||
|
||||
# -------------------------------------------------------------------------------------
|
||||
# Create icao24.db, used for ADS-B receiver application, using
|
||||
# https://opensky-network.org/datasets/metadata/aircraftDatabase.csv
|
||||
# https://opensky-network.org/datasets/metadata/aircraftDatabase.csv
|
||||
# as a source.
|
||||
# -------------------------------------------------------------------------------------
|
||||
import csv
|
||||
import unicodedata
|
||||
icao24_codes=bytearray()
|
||||
data=bytearray()
|
||||
row_count=0
|
||||
import os
|
||||
import shutil
|
||||
import urllib.request
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Set, Tuple
|
||||
|
||||
database=open("icao24.db", "wb")
|
||||
|
||||
with open('aircraftDatabase.csv', 'rt') as csv_file:
|
||||
sorted_lines=sorted(csv_file.readlines()[1:])
|
||||
for row in csv.reader(sorted_lines, quotechar='"', delimiter=',', quoting=csv.QUOTE_ALL, skipinitialspace=True):
|
||||
# only store in case enough info is available
|
||||
if len(row) == 27 and len(row[0]) == 6 and len(row[1]) > 0:
|
||||
icao24_code=row[0][:6].upper()
|
||||
registration=row[1][:8].encode('ascii', 'ignore')
|
||||
manufacturer=row[3][:32].encode('ascii', 'ignore')
|
||||
model=row[4][:32].encode('ascii', 'ignore')
|
||||
# in case icao aircraft type isn't, use ac type like BALL for balloon
|
||||
if len(row[8]) == 3:
|
||||
actype=row[8][:3].encode('ascii', 'ignore')
|
||||
else:
|
||||
actype=row[5][:4].encode('ascii', 'ignore')
|
||||
owner=row[13][:32].encode('ascii', 'ignore')
|
||||
operator=row[9][:32].encode('ascii', 'ignore')
|
||||
#padding
|
||||
icao24_codes.extend(bytearray(icao24_code+'\0', encoding='ascii'))
|
||||
registration_padding=bytearray('\0' * (9 - len(registration)), encoding='ascii')
|
||||
manufacturer_padding=bytearray('\0' * (33 - len(manufacturer)), encoding='ascii')
|
||||
model_padding=bytearray('\0' * (33 - len(model)), encoding='ascii')
|
||||
actype_padding=bytearray('\0' * (5 - len(actype)), encoding='ascii')
|
||||
owner_padding=bytearray('\0' * (33 - len(owner)), encoding='ascii')
|
||||
operator_padding=bytearray('\0' * (33 - len(operator)), encoding='ascii')
|
||||
data.extend(bytearray(registration+registration_padding+manufacturer+manufacturer_padding+model+model_padding+actype+actype_padding+owner+owner_padding+operator+operator_padding))
|
||||
row_count+=1
|
||||
database.write(icao24_codes+data)
|
||||
print("Total of", row_count, "ICAO codes stored in database")
|
||||
@dataclass
|
||||
class AircraftRecord:
|
||||
"""Represents an aircraft record with all relevant fields"""
|
||||
|
||||
icao24: str
|
||||
registration: str
|
||||
manufacturer: str
|
||||
model: str
|
||||
actype: str
|
||||
owner: str
|
||||
operator: str
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.icao24)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, AircraftRecord):
|
||||
return False
|
||||
return self.icao24 == other.icao24
|
||||
|
||||
def data_equals(self, other):
|
||||
"""Check if all data fields are equal (excluding ICAO24 which is the key)"""
|
||||
if not isinstance(other, AircraftRecord):
|
||||
return False
|
||||
return (
|
||||
self.registration == other.registration
|
||||
and self.manufacturer == other.manufacturer
|
||||
and self.model == other.model
|
||||
and self.actype == other.actype
|
||||
and self.owner == other.owner
|
||||
and self.operator == other.operator
|
||||
)
|
||||
|
||||
|
||||
def backup_previous_csv() -> bool:
|
||||
"""Backup the existing CSV file if it exists. Returns True if backup was made."""
|
||||
if os.path.exists("aircraftDatabase.csv"):
|
||||
shutil.copy2("aircraftDatabase.csv", "aircraftDatabase_previous.csv")
|
||||
print("Backed up previous CSV file")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def download_aircraft_database() -> None:
|
||||
"""Download the aircraft database file from OpenSky Network"""
|
||||
url = "https://opensky-network.org/datasets/metadata/aircraftDatabase.csv"
|
||||
print(f"Downloading aircraft database from {url}...")
|
||||
try:
|
||||
urllib.request.urlretrieve(url, "aircraftDatabase.csv")
|
||||
print("Download completed successfully.")
|
||||
except Exception as e:
|
||||
print(f"Error downloading aircraft database file: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def parse_csv_file(filename: str) -> Dict[str, AircraftRecord]:
|
||||
"""Parse CSV file and return a dictionary of records"""
|
||||
records = {}
|
||||
|
||||
if not os.path.exists(filename):
|
||||
return records
|
||||
|
||||
try:
|
||||
with open(filename, "rt", encoding="utf-8") as csv_file:
|
||||
sorted_lines = sorted(csv_file.readlines()[1:])
|
||||
for row in csv.reader(
|
||||
sorted_lines,
|
||||
quotechar='"',
|
||||
delimiter=",",
|
||||
quoting=csv.QUOTE_ALL,
|
||||
skipinitialspace=True,
|
||||
):
|
||||
# only store in case enough info is available
|
||||
if len(row) == 27 and len(row[0]) == 6 and len(row[1]) > 0:
|
||||
icao24_code = row[0][:6].upper()
|
||||
registration = row[1][:8]
|
||||
manufacturer = row[3][:32]
|
||||
model = row[4][:32]
|
||||
# in case icao aircraft type isn't, use ac type like BALL for balloon
|
||||
if len(row[8]) == 3:
|
||||
actype = row[8][:3]
|
||||
else:
|
||||
actype = row[5][:4]
|
||||
owner = row[13][:32]
|
||||
operator = row[9][:32]
|
||||
|
||||
records[icao24_code] = AircraftRecord(
|
||||
icao24=icao24_code,
|
||||
registration=registration,
|
||||
manufacturer=manufacturer,
|
||||
model=model,
|
||||
actype=actype,
|
||||
owner=owner,
|
||||
operator=operator,
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not parse CSV file {filename}: {e}")
|
||||
return {}
|
||||
|
||||
return records
|
||||
|
||||
|
||||
def compare_records(
|
||||
old_records: Dict[str, AircraftRecord], new_records: Dict[str, AircraftRecord]
|
||||
) -> Tuple[Set[str], Set[str], Set[str]]:
|
||||
"""Compare old and new records, return sets of new, deleted, and changed ICAO codes"""
|
||||
old_keys = set(old_records.keys())
|
||||
new_keys = set(new_records.keys())
|
||||
|
||||
new_icao_codes = new_keys - old_keys
|
||||
deleted_icao_codes = old_keys - new_keys
|
||||
|
||||
# Check for changes in existing records
|
||||
changed_icao_codes = set()
|
||||
for icao in old_keys & new_keys:
|
||||
if not old_records[icao].data_equals(new_records[icao]):
|
||||
changed_icao_codes.add(icao)
|
||||
|
||||
return new_icao_codes, deleted_icao_codes, changed_icao_codes
|
||||
|
||||
|
||||
def write_database(records: Dict[str, AircraftRecord]) -> int:
|
||||
"""Write records to database file using original format"""
|
||||
# bytearrays to store icao24 codes and data
|
||||
icao24_codes = bytearray()
|
||||
data = bytearray()
|
||||
row_count = 0
|
||||
|
||||
# Sort by ICAO code for consistency
|
||||
sorted_records = sorted(records.values(), key=lambda r: r.icao24)
|
||||
|
||||
for record in sorted_records:
|
||||
# Encode data fields
|
||||
registration = record.registration.encode("ascii", "ignore")
|
||||
manufacturer = record.manufacturer.encode("ascii", "ignore")
|
||||
model = record.model.encode("ascii", "ignore")
|
||||
actype = record.actype.encode("ascii", "ignore")
|
||||
owner = record.owner.encode("ascii", "ignore")
|
||||
operator = record.operator.encode("ascii", "ignore")
|
||||
|
||||
# Add ICAO code with null terminator (original format)
|
||||
icao24_codes.extend(bytearray(record.icao24 + "\0", encoding="ascii"))
|
||||
|
||||
# Add padded data fields (original format)
|
||||
registration_padding = bytearray(
|
||||
"\0" * (9 - len(registration)), encoding="ascii"
|
||||
)
|
||||
manufacturer_padding = bytearray(
|
||||
"\0" * (33 - len(manufacturer)), encoding="ascii"
|
||||
)
|
||||
model_padding = bytearray("\0" * (33 - len(model)), encoding="ascii")
|
||||
actype_padding = bytearray("\0" * (5 - len(actype)), encoding="ascii")
|
||||
owner_padding = bytearray("\0" * (33 - len(owner)), encoding="ascii")
|
||||
operator_padding = bytearray("\0" * (33 - len(operator)), encoding="ascii")
|
||||
|
||||
data.extend(
|
||||
bytearray(
|
||||
registration
|
||||
+ registration_padding
|
||||
+ manufacturer
|
||||
+ manufacturer_padding
|
||||
+ model
|
||||
+ model_padding
|
||||
+ actype
|
||||
+ actype_padding
|
||||
+ owner
|
||||
+ owner_padding
|
||||
+ operator
|
||||
+ operator_padding
|
||||
)
|
||||
)
|
||||
row_count += 1
|
||||
|
||||
with open("icao24.db", "wb") as database:
|
||||
database.write(icao24_codes + data)
|
||||
|
||||
return row_count
|
||||
|
||||
|
||||
def create_database() -> None:
|
||||
"""Create the ICAO24 database from the downloaded file"""
|
||||
# Backup existing CSV before downloading new one
|
||||
has_previous = backup_previous_csv()
|
||||
|
||||
# Download new CSV
|
||||
download_aircraft_database()
|
||||
|
||||
# Parse both CSV files for comparison
|
||||
old_records = {}
|
||||
if has_previous:
|
||||
print("Parsing previous CSV file...")
|
||||
old_records = parse_csv_file("aircraftDatabase_previous.csv")
|
||||
print(f"Found {len(old_records)} records in previous CSV")
|
||||
|
||||
print("Parsing new CSV file...")
|
||||
new_records = parse_csv_file("aircraftDatabase.csv")
|
||||
print(f"Found {len(new_records)} records in new CSV")
|
||||
|
||||
# Compare records if we have a previous version
|
||||
if old_records:
|
||||
new_icao_codes, deleted_icao_codes, changed_icao_codes = compare_records(
|
||||
old_records, new_records
|
||||
)
|
||||
|
||||
# Print change statistics
|
||||
print("\n" + "=" * 50)
|
||||
print("CSV CHANGE SUMMARY")
|
||||
print("=" * 50)
|
||||
print(f"New records: {len(new_icao_codes):>8}")
|
||||
print(f"Deleted records: {len(deleted_icao_codes):>8}")
|
||||
print(f"Changed records: {len(changed_icao_codes):>8}")
|
||||
print(f"Total records: {len(new_records):>8}")
|
||||
print("=" * 50)
|
||||
|
||||
# Show examples of changes (limited to avoid spam)
|
||||
if new_icao_codes and len(new_icao_codes) <= 10:
|
||||
print(f"\nNew ICAO24 codes: {', '.join(sorted(new_icao_codes))}")
|
||||
elif new_icao_codes:
|
||||
sample_new = sorted(list(new_icao_codes))[:5]
|
||||
print(
|
||||
f"\nSample new ICAO24 codes: {', '.join(sample_new)} (and {len(new_icao_codes)-5} more)"
|
||||
)
|
||||
|
||||
if deleted_icao_codes and len(deleted_icao_codes) <= 10:
|
||||
print(f"Deleted ICAO24 codes: {', '.join(sorted(deleted_icao_codes))}")
|
||||
elif deleted_icao_codes:
|
||||
sample_deleted = sorted(list(deleted_icao_codes))[:5]
|
||||
print(
|
||||
f"Sample deleted ICAO24 codes: {', '.join(sample_deleted)} (and {len(deleted_icao_codes)-5} more)"
|
||||
)
|
||||
|
||||
if changed_icao_codes and len(changed_icao_codes) <= 10:
|
||||
print(f"Changed ICAO24 codes: {', '.join(sorted(changed_icao_codes))}")
|
||||
elif changed_icao_codes:
|
||||
sample_changed = sorted(list(changed_icao_codes))[:5]
|
||||
print(
|
||||
f"Sample changed ICAO24 codes: {', '.join(sample_changed)} (and {len(changed_icao_codes)-5} more)"
|
||||
)
|
||||
else:
|
||||
print("\nNo previous CSV found - this appears to be the first run")
|
||||
|
||||
# Create database from new records
|
||||
print("\nCreating ICAO24 database...")
|
||||
row_count = write_database(new_records)
|
||||
print("Total of", row_count, "ICAO codes stored in database")
|
||||
|
||||
|
||||
def cleanup_previous_csv() -> None:
|
||||
"""Remove the previous CSV file if it exists"""
|
||||
if os.path.exists("aircraftDatabase_previous.csv"):
|
||||
os.remove("aircraftDatabase_previous.csv")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
create_database()
|
||||
cleanup_previous_csv()
|
||||
except Exception as e:
|
||||
print(f"Error creating ICAO24 database: {e}")
|
||||
else:
|
||||
print("ICAO24 database created successfully.")
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (C) 2025 Tommaso Ventafridda
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; see the file COPYING. If not, write to
|
||||
# the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# -------------------------------------------------------------------------------------
|
||||
# Create macaddress.db, used for BLE receiver application, using
|
||||
# https://standards-oui.ieee.org/oui/oui.txt as a source.
|
||||
# -------------------------------------------------------------------------------------
|
||||
|
||||
import urllib.request
|
||||
import unicodedata
|
||||
import re
|
||||
from typing import List, Tuple
|
||||
|
||||
|
||||
def download_oui_file() -> str:
|
||||
"""Download the OUI file from IEEE"""
|
||||
url = "https://standards-oui.ieee.org/oui/oui.txt"
|
||||
print(f"Downloading OUI database from {url}...")
|
||||
try:
|
||||
with urllib.request.urlopen(url) as response:
|
||||
content = response.read().decode("utf-8")
|
||||
print("Download completed successfully.")
|
||||
return content
|
||||
except Exception as e:
|
||||
print(f"Error downloading OUI file: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def parse_oui_data(content: str) -> List[Tuple[str, str]]:
|
||||
"""Parse the OUI file content and extract MAC prefixes and vendor names"""
|
||||
entries = []
|
||||
lines = content.split("\n")
|
||||
|
||||
for _, line in enumerate(lines):
|
||||
# Look for lines with (hex) pattern
|
||||
if "(hex)" in line:
|
||||
# Extract MAC prefix and vendor name
|
||||
match = re.match(
|
||||
r"^([0-9A-F]{2}-[0-9A-F]{2}-[0-9A-F]{2})\s+\(hex\)\s+(.+)$",
|
||||
line.strip(),
|
||||
)
|
||||
if match:
|
||||
# Remove dashes: "28-6F-B9" -> "286FB9"
|
||||
mac_prefix = match.group(1).replace(
|
||||
"-", ""
|
||||
)
|
||||
vendor_name = match.group(2).strip()
|
||||
|
||||
# Normalize vendor name and limit to 63 characters
|
||||
vendor_name = (
|
||||
unicodedata.normalize("NFKD", vendor_name[:63])
|
||||
.encode("ascii", "ignore")
|
||||
.decode("ascii")
|
||||
)
|
||||
|
||||
if mac_prefix and vendor_name:
|
||||
entries.append((mac_prefix, vendor_name))
|
||||
|
||||
print(f"Parsed {len(entries)} MAC address entries.")
|
||||
return entries
|
||||
|
||||
|
||||
def create_database(
|
||||
entries: List[Tuple[str, str]], output_filename: str = "macaddress.db"
|
||||
):
|
||||
"""Create the binary database file."""
|
||||
# Sort entries by MAC prefix for binary search
|
||||
entries.sort(key=lambda x: x[0])
|
||||
|
||||
mac_prefixes = bytearray()
|
||||
vendor_data = bytearray()
|
||||
row_count = 0
|
||||
|
||||
for mac_prefix, vendor_name in entries:
|
||||
# MAC prefix: 6 hex chars + null terminator = 7 bytes
|
||||
mac_prefixes += bytearray(mac_prefix + "\0", encoding="ascii")
|
||||
|
||||
# Vendor name: pad to 64 bytes
|
||||
vendor_bytes = vendor_name.encode("ascii", "ignore")
|
||||
vendor_padding = bytearray("\0" * (64 - len(vendor_bytes)), encoding="ascii")
|
||||
vendor_data += vendor_bytes + vendor_padding
|
||||
|
||||
row_count += 1
|
||||
|
||||
# Write database: index section followed by data section
|
||||
with open(output_filename, "wb") as database:
|
||||
database.write(mac_prefixes + vendor_data)
|
||||
|
||||
print(f"Created database '{output_filename}' with {row_count} MAC address entries.")
|
||||
print(f"Index section: {len(mac_prefixes)} bytes")
|
||||
print(f"Data section: {len(vendor_data)} bytes")
|
||||
print(f"Total database size: {len(mac_prefixes) + len(vendor_data)} bytes")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function to create the MAC address database."""
|
||||
try:
|
||||
oui_content = download_oui_file()
|
||||
entries = parse_oui_data(oui_content)
|
||||
|
||||
if not entries:
|
||||
print("No valid entries found in OUI file!")
|
||||
return
|
||||
|
||||
create_database(entries)
|
||||
|
||||
print("MAC address database creation completed successfully!")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error creating MAC address database: {e}")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,4 @@
|
||||
# Drone Analog FPV Channels
|
||||
# https://oscarliang.com/fpv-channels/
|
||||
# 5.8GHz Analog FPV Channels
|
||||
f=5865000000,d=FPV Ch A-1
|
||||
f=5845000000,d=FPV Ch A-2
|
||||
f=5825000000,d=FPV Ch A-3
|
||||
@@ -40,30 +39,6 @@ f=5806000000,d=FPV Ch R-5
|
||||
f=5843000000,d=FPV Ch R-6
|
||||
f=5880000000,d=FPV Ch R-7
|
||||
f=5917000000,d=FPV Ch R-8
|
||||
f=5362000000,d=FPV Ch D-1
|
||||
f=5399000000,d=FPV Ch D-2
|
||||
f=5436000000,d=FPV Ch D-3
|
||||
f=5473000000,d=FPV Ch D-4
|
||||
f=5510000000,d=FPV Ch D-5
|
||||
f=5547000000,d=FPV Ch D-6
|
||||
f=5584000000,d=FPV Ch D-7
|
||||
f=5621000000,d=FPV Ch D-8
|
||||
f=5325000000,d=FPV Ch U-1
|
||||
f=5348000000,d=FPV Ch U-2
|
||||
f=5366000000,d=FPV Ch U-3
|
||||
f=5384000000,d=FPV Ch U-4
|
||||
f=5402000000,d=FPV Ch U-5
|
||||
f=5420000000,d=FPV Ch U-6
|
||||
f=5438000000,d=FPV Ch U-7
|
||||
f=5456000000,d=FPV Ch U-8
|
||||
f=5474000000,d=FPV Ch O-1
|
||||
f=5492000000,d=FPV Ch O-2
|
||||
f=5510000000,d=FPV Ch O-3
|
||||
f=5528000000,d=FPV Ch O-4
|
||||
f=5546000000,d=FPV Ch O-5
|
||||
f=5564000000,d=FPV Ch O-6
|
||||
f=5582000000,d=FPV Ch O-7
|
||||
f=5600000000,d=FPV Ch O-8
|
||||
f=5333000000,d=FPV Ch L-1
|
||||
f=5373000000,d=FPV Ch L-2
|
||||
f=5413000000,d=FPV Ch L-3
|
||||
@@ -72,11 +47,14 @@ f=5493000000,d=FPV Ch L-5
|
||||
f=5533000000,d=FPV Ch L-6
|
||||
f=5573000000,d=FPV Ch L-7
|
||||
f=5613000000,d=FPV Ch L-8
|
||||
f=5653000000,d=FPV Ch H-1
|
||||
f=5693000000,d=FPV Ch H-2
|
||||
f=5733000000,d=FPV Ch H-3
|
||||
f=5773000000,d=FPV Ch H-4
|
||||
f=5813000000,d=FPV Ch H-5
|
||||
f=5853000000,d=FPV Ch H-6
|
||||
f=5893000000,d=FPV Ch H-7
|
||||
f=5933000000,d=FPV Ch H-8
|
||||
|
||||
# 1.2-1.3GHz Analog Channels
|
||||
f=1080000000,d=FPV Ch 1
|
||||
f=1120000000,d=FPV Ch 2
|
||||
f=1160000000,d=FPV Ch 3
|
||||
f=1200000000,d=FPV Ch 4
|
||||
f=1240000000,d=FPV Ch 5
|
||||
f=1258000000,d=FPV Ch 6
|
||||
f=1280000000,d=FPV Ch 7
|
||||
f=1320000000,d=FPV Ch 8
|
||||
f=1360000000,d=FPV Ch 9
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
87,108,FM BROADCAST USA
|
||||
76,108,FM BROADCAST BRAZIL
|
||||
76,95,FM BROADCAST JAPAN
|
||||
65,74,FM BROADCAST RUSSIA
|
||||
65,74,FM OIRT EAST EU
|
||||
# CITIZENS BAND RADIO
|
||||
26,28,CB RADIO
|
||||
# COMMON PUBLIC SERVICE BANDS
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user