diff --git a/.gitignore b/.gitignore index 4985256c1..f1290f11f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ # ESP-IDF auto generated by build sdkconfig sdkconfig.old +dependencies.lock +**/dependencies.lock # compiled output /build/ @@ -1044,8 +1046,8 @@ size_info_*.txt # pytest-embedded log folder pytest_embedded_log/ -# idf-component-manager output -dependencies.lock +# idf-component-manager lock: TRACKED for reproducible builds (pins the exact +# managed-component versions so every clone resolves the same deps). !assets/ !assets/** diff --git a/AGENTS.md b/AGENTS.md index d2f416f06..41a5214aa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -61,7 +61,7 @@ firmware_p4/ # ESP32-P4 master firmware (embeds C5 binary) Drivers/ # Hardware drivers (SPI, buttons, display, radio, USB) Service/ # Support services (OTA, WiFi, console, storage, SPI bridge) Core/ # System core and main managers - Applications/ # User-facing apps (UI, bad_usb, SubGhz) + Applications/ # User-facing apps (UI, bad_usb, SubGhz, gameboy, nfc) Drivers/spi_bridge_phy/ # SPI bridge physical layer (P4 side) main/main.c # Entry point common/metadata/ # Shared metadata diff --git a/README.md b/README.md index 36f6cb9b8..af0765420 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- HighBoy Banner + HighBoy Banner

@@ -45,7 +45,7 @@ See the general project architecture: ## How to use this project -We recommend that this project serves as a basis for custom projects with ESP32-S3. +We recommend that this project serves as a basis for custom projects with the ESP32-P4 and ESP32-C5. To start a new project with ESP-IDF, follow the official guide: [ESP-IDF Documentation - Create a new project](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html#start-a-new-project) diff --git a/README.pt.md b/README.pt.md index a98a519b0..f798feda7 100644 --- a/README.pt.md +++ b/README.pt.md @@ -1,5 +1,5 @@

- HighBoy Banner + HighBoy Banner

@@ -47,7 +47,7 @@ Veja a arquitetura geral do projeto: ## Como utilizar este projeto -Recomendamos que este projeto sirva como base para projetos personalizados com ESP32-S3. +Recomendamos que este projeto sirva como base para projetos personalizados com o ESP32-P4 e o ESP32-C5. Para começar um novo projeto com ESP-IDF, siga o guia oficial: [Documentação ESP-IDF - Criar novo projeto](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html#start-a-new-project) diff --git a/docs/LoRa/README.md b/docs/LoRa/README.md new file mode 100644 index 000000000..a88038d35 --- /dev/null +++ b/docs/LoRa/README.md @@ -0,0 +1,244 @@ +# LoRa Application + +This component implements the LoRa mesh application layer on top of the `sx1262` driver. It hosts two full mesh protocol stacks - **Meshtastic** and **MeshCore** - behind a unified session manager, plus an **RNode**-compatible KISS modem. It also bridges a companion phone app over BLE and drives the on-device chat UI. + +## Overview + +- **Location:** `components/Applications/LoRa/` +- **Sub-modules:** `session/`, `meshtastic/`, `meshcore/`, `rnode/` +- **Radio:** single SX1262 (see `docs/sx1262/README.md`) +- **UI screens:** `components/Applications/ui/screens/lora/` +- **BLE / Wi-Fi:** terminated on the **C5 co-processor**, reached over the SPI bridge + +## Single-Owner Radio + +The SX1262 is a single-owner resource. Meshtastic, MeshCore, and RNode each register their own radio callbacks and cannot run at the same time; whichever stack starts first owns the radio. **Switching protocols requires a reboot** - there is no clean whole-stack teardown. The session manager enforces this: once a protocol is running, starting a different one is refused. + +## Split-Chip Architecture (P4 + C5) + +The design is consistent across both mesh stacks: + +- **The P4** owns the protocol logic, the radio, and the PhoneAPI/command state. +- **The C5** owns NimBLE (GATT service, pairing, CCCD subscribe) and the Wi-Fi/TCP server. + +The two communicate over the **SPI bridge**. Each mesh stack has a "phone bridge" on the P4 that: + +- Reassembles chunked StreamAPI/companion frames (`spi_mesh_chunk_hdr_t`: seq / chunk_idx / total_chunks / flags) arriving from the C5 and forwards them to the P4 PhoneAPI. +- Fragments outbound frames and pushes them to the C5. +- Polls a cached C5 status struct (`spi_mesh_status_t` / `spi_mcore_status_t`: `ble_connected`, `ble_subscribed`, `tcp_clients`, `logradio_subscribed`). +- Runs a **want-vs-actual reconcile loop** so BLE re-arms automatically after a C5 reboot. + +**"Connected" vs "linked":** `ble_connected` is the raw BLE link (true before the pairing PIN is even shown). The UI/session truth is **"linked" = subscribed after encryption** - the phone has enabled notifications on the notify characteristic (a CCCD subscribe on the C5). The session and UI gate on the *subscribed* predicate, not the *connected* one. + +## Session Manager (`session/`) + +`lora_session` is a thin, protocol-agnostic facade the chat hub reads. It never calls the SX1262 directly - each stack's `app_start` brings the radio up. The session layer: + +- Tracks which protocol owns the radio. +- Holds a **local chat ring** of 24 messages (in PSRAM), guarded by a mutex, with a monotonic sequence used by a cursor-based read API. +- Maps a unified node/contact list onto either Meshtastic's nodedb or MeshCore's contacts. +- Fronts the phone-bridge connect/subscribe state. + +### Types + +```c +typedef enum { LORA_PROTO_NONE = 0, LORA_PROTO_MESHTASTIC, LORA_PROTO_MESHCORE } lora_proto_t; +typedef struct { bool outgoing; char who[24]; char text[160]; } lora_msg_t; +typedef struct { char name[32]; int16_t rssi; float snr; } lora_node_t; +``` + +### API + +```c +esp_err_t lora_session_start(lora_proto_t proto); +lora_proto_t lora_session_active(void); +esp_err_t lora_session_send_text(const char *text); +uint16_t lora_session_msg_count(void); +uint16_t lora_session_msg_since(uint32_t *io_seq, lora_msg_t *out, uint16_t max); +uint16_t lora_session_node_count(void); +bool lora_session_node_get(uint16_t idx, lora_node_t *out); +void lora_session_on_rx_text(const char *who, const char *text); +bool lora_session_app_connected(void); +esp_err_t lora_session_app_connect(void); +``` + +- `lora_session_start` is idempotent for the same protocol; a request for a different protocol while one is running returns `ESP_ERR_INVALID_STATE`. +- `lora_session_send_text` sends a broadcast/public-channel text on the active stack (Meshtastic `0xFFFFFFFF`, MeshCore public channel `0`) and, on success, appends it to the ring as outgoing ("me"). +- `lora_session_msg_since` copies messages newer than a caller-held cursor under a single lock; passing `*io_seq == 0` snaps to the oldest retained message. +- `lora_session_on_rx_text` is called by the backend RX taps from the mesh poll task (not the LVGL thread). +- `lora_session_app_connected` maps to the active stack's **`_is_subscribed()`** predicate (linked, not merely connected). `lora_session_app_connect` maps to `_phone_bridge_ble_start()`. + +## Meshtastic Stack (`meshtastic/`) + +Entry point: `esp_err_t meshtastic_app_start(void)` (call **after** `bridge_manager_init()`). + +Bring-up derives `node_num` from the MAC, loads persisted preset/region (default LONG_FAST + US), brings up the SX1262 (region center frequency, preset SF/BW/CR), starts the IRQ task, initializes PKI (X25519 / Curve25519 via mbedTLS), the nodedb, the module hub, the mesh core, the PhoneAPI, and the phone bridge, then starts a poll task (mesh poll at 50 ms, module tick at 1 Hz) and requests C5 BLE advertising. + +### Mesh core (`meshtastic_mesh`) + +```c +esp_err_t meshtastic_mesh_init(uint32_t node_num); +esp_err_t meshtastic_mesh_start(void); +void meshtastic_mesh_stop(void); +esp_err_t meshtastic_mesh_send(const uint8_t *pb_data, uint16_t pb_len); +void meshtastic_mesh_poll(void); +esp_err_t meshtastic_mesh_send_nodeinfo(void); +esp_err_t meshtastic_mesh_send_text(const char *text, uint32_t to); +esp_err_t meshtastic_mesh_send_data(uint32_t to, uint8_t channel, uint8_t hop_limit, + uint8_t portnum, const uint8_t *payload, uint16_t plen, + uint32_t request_id, bool want_ack, bool want_response); +bool meshtastic_mesh_retry_ack(uint32_t pkt_id, bool is_implicit); +``` + +### Modules (`mt_modules` + `mt_mod_*`) + +The module hub dispatches decoded packets by portnum. Modules present: Admin, NodeInfo, Position, Text, Routing, TraceRoute, Telemetry, KeyVerify, NeighborInfo. Each exposes `_init(node_num)` and `_on_received(meta, payload, len)`; timer-driven ones add a tick. + +```c +esp_err_t mt_modules_init(uint32_t node_num); +void mt_modules_dispatch(const mt_packet_meta_t *meta, const uint8_t *data, uint16_t len); +void mt_modules_tick(void); // 1 Hz +bool mt_parse_data(const uint8_t *data, uint16_t len, uint32_t *out_portnum, + const uint8_t **out_payload, uint16_t *out_payload_len, + uint32_t *out_request_id); +``` + +Per-packet metadata (`mt_packet_meta_t`) carries `from`, `to`, `id`, `channel`, `hop_limit`, `hop_start`, `rssi_dbm`, `snr_db`, `want_ack`, `want_response`, `request_id`. + +Supporting modules: `meshtastic_nodedb` (node database, favorites/ignore/mute, next-hop learning), `meshtastic_channels` (channel PSKs and hashes), `meshtastic_pki` / `meshtastic_crypto_pki` (Curve25519 encrypt/decrypt), `meshtastic_pkt_history` (dedup / relayer tracking), `meshtastic_mqtt`, `meshtastic_wifi`, `meshtastic_regions`, `meshtastic_presets`, `meshtastic_roles`, plus vendored `unishox2` text compression. + +### Traceroute (`mt_mod_traceroute`) + +A traceroute request is a zero-payload Data packet on the TraceRoute port with `want_response` set. + +```c +#define MT_TRACE_MAX_HOPS 8 +void mt_mod_traceroute_init(uint32_t node_num); +void mt_mod_traceroute_on_received(const mt_packet_meta_t *meta, const uint8_t *payload, uint16_t len); +void mt_mod_traceroute_start(uint32_t to); +bool mt_mod_traceroute_is_pending(void); +bool mt_mod_traceroute_get_result(uint32_t *out_hops, int *out_count, uint32_t *out_target); +``` + +- `mt_mod_traceroute_start(to)` marks a pending request, clears any prior result, and sends the request via `meshtastic_mesh_send_data(...)`. +- `mt_mod_traceroute_on_received` decodes the `RouteDiscovery` protobuf hop list. If it matches the pending request (reply addressed to us, from the target), the hops are captured into module-static state and `_get_result` reports them. If this node is itself the request destination, it appends its own node number to the route and sends the reply. Non-terminal hop forwarding relies on the mesh core's flood rebroadcast. +- There is no dedicated result struct; results are returned through the out-parameters of `mt_mod_traceroute_get_result` and held internally until the next request. The traceroute screen is `lora_traceroute_ui`. + +### PhoneAPI (`meshtastic_phoneapi`) + +Implements the official PhoneAPI handshake as a state machine (`phoneapi_state_t`: MyInfo -> Metadata -> DeviceUIConfig -> Channels -> Config -> ModuleConfig -> FileManifest -> own NodeInfo -> other NodeInfos -> complete-id -> packets). + +```c +esp_err_t phoneapi_init(uint32_t node_num); +esp_err_t phoneapi_on_toradio(const uint8_t *pb_data, uint16_t pb_len); +uint16_t phoneapi_poll_fromradio(uint8_t *out_buf, uint16_t max_len); +bool phoneapi_has_data(void); +void phoneapi_push_packet(const uint8_t *mp_bytes, uint16_t mp_len); +void phoneapi_disconnect(void); +``` + +### BLE phone-app connectivity + +On the P4, `meshtastic_ble` is a thin shim forwarding to `meshtastic_phone_bridge`; the real NimBLE host and GATT service run on the C5. + +```c +esp_err_t meshtastic_phone_bridge_init(void); +esp_err_t meshtastic_phone_bridge_ble_start(void); +esp_err_t meshtastic_phone_bridge_ble_stop(void); +bool meshtastic_phone_bridge_is_connected(void); // ble_connected || tcp_clients +bool meshtastic_phone_bridge_is_subscribed(void); // ble_subscribed || tcp_clients +``` + +- **NimBLE arbitration:** the P4 never owns NimBLE. `_ble_start()` sends a BLE-init request to the C5 over the SPI bridge; a notify task reconciles desired vs actual C5 state (re-arming BLE after a C5 reboot) and polls status. Because only one LoRa stack runs at a time, only one bridge ever issues BLE-init; cross-app BLE arbitration (mesh vs host-link vs HID) lives on the C5, which avoids tearing NimBLE out from under an active app. +- **Pairing PIN:** for Meshtastic the passkey is generated randomly on the C5 per pairing (display-only IO capability, MITM + bonding) and logged there. It is **not** shown on the P4 chat screen. +- **Linked-on-subscribe gating:** `_is_connected()` is true at the raw BLE link; `_is_subscribed()` is true only after the phone subscribes (CCCD notify enabled on the FromNum characteristic, after encryption). Fromradio data is only pushed to the phone while a BLE client is connected or a TCP client is present. + +Other module surfaces (used by the UI/config screens): `meshtastic_mqtt` (`_init/_publish/_is_connected`), `meshtastic_wifi`, region/preset/role selection (`mt_region_*`, `mt_preset_*`, `mt_role_*` with `MT_ROLE_ROUTER` / `MT_ROLE_CLIENT`). + +## MeshCore Stack (`meshcore/`) + +Entry point: `esp_err_t meshcore_app_start(void)` (call **after** `bridge_manager_init()`). + +Bring-up initializes libsodium, brings up the SX1262 with MeshCore defaults (915 MHz, SF10, BW 250 kHz, CR 4/5, +20 dBm, preamble 8), loads or creates an Ed25519 identity (32-byte seed persisted in NVS, default node name "Highboy"), initializes the core with five router callbacks, the companion PhoneAPI, and the phone bridge, then starts a poll task (50 ms) and requests C5 BLE advertising. + +The five router callbacks bridge radio events to both the phone and the local ring: new advert, group text (`lora_session_on_rx_text("ch%u", ...)`), direct message (`lora_session_on_rx_text(contact-name or "dm", ...)`), ACK confirmation, and path update. + +### Core (`meshcore`) + +Constants include `MESHCORE_MAX_CONTACTS 32`, `MESHCORE_MAX_CHANNELS 8`, `MESHCORE_PUBLIC_CHANNEL 0`, `MESHCORE_TEXT_MAX 160`. Key surface used by the app/session: + +- `meshcore_init`, `meshcore_start` / `_stop` / `_poll` +- `meshcore_set_node_name`, `meshcore_set_advert_latlon`, `meshcore_send_advert` / `_throttled` (10 s cooldown) +- `meshcore_channel_get` / `_set`, `meshcore_send_grp_txt(channel_idx, text)` +- Contacts: `meshcore_contacts_array`, `meshcore_contacts_count`, `meshcore_contact_find` / `_find_by_hash` / `_upsert` / `_remove` / `_reset_path` +- `meshcore_send_direct_msg(peer_pub_key, text, timestamp, attempt, out_expected_ack)` - X25519 ECDH + AES-128 + HMAC-SHA256 +- `meshcore_set_radio_params` / `_get_radio_prefs`, `meshcore_set_unix_time` / `_get_unix_time`, `meshcore_set_relay` / `_get_relay` + +Identity/contact/channel structs (`meshcore_identity_t`, `meshcore_contact_t`, `meshcore_channel_t`) are defined in `meshcore.h`. + +### Companion PhoneAPI (`meshcore_phoneapi`) + +Unlike Meshtastic's FSM, the outbound path is a **registered callback** invoked synchronously from command handlers and push helpers. + +```c +esp_err_t meshcore_phoneapi_init(void); // loads/generates the 6-digit PIN from NVS +void meshcore_phoneapi_set_outbound(meshcore_phoneapi_outbound_cb_t cb, void *ctx); +uint32_t meshcore_phoneapi_get_pin(void); // 6-digit BLE pairing PIN +void meshcore_phoneapi_on_disconnect(void); +void meshcore_phoneapi_on_inbound(const uint8_t *buf, uint16_t len); // dispatches CMD_* opcodes +void meshcore_phoneapi_push_new_advert(const meshcore_contact_t *contact); +void meshcore_phoneapi_push_channel_msg(uint8_t ch, uint8_t path_len, uint32_t ts, int8_t snr, const char *text); +void meshcore_phoneapi_push_contact_msg(const uint8_t peer_pub_key[32], uint8_t path_len, + uint8_t txt_type, uint32_t ts, int8_t snr, const char *text); +void meshcore_phoneapi_push_send_confirmed(uint32_t ack_crc, uint8_t snr); +void meshcore_phoneapi_push_path_updated(const meshcore_contact_t *contact); +``` + +### Phone bridge (`meshcore_phone_bridge`) + +```c +esp_err_t meshcore_phone_bridge_init(const char *name_prefix); // NULL -> "Highboy-MC" +void meshcore_phone_bridge_stop(void); +esp_err_t meshcore_phone_bridge_ble_start(void); +esp_err_t meshcore_phone_bridge_ble_stop(void); +bool meshcore_phone_bridge_is_connected(void); // ble_connected +bool meshcore_phone_bridge_is_subscribed(void); // ble_subscribed +``` + +Same C5-terminated model as Meshtastic (chunked reassembly, status polling task, reconcile loop that survives C5 reboots). Two differences worth noting: + +- **Pairing PIN:** MeshCore uses a **fixed, device-persisted 6-digit PIN** (from `meshcore_phoneapi_get_pin()`), passed to the C5 at BLE-init and injected on the passkey-display event. This PIN **is** rendered on the P4 chat connect screen. +- **Linked-on-subscribe gating:** `ble_connected` flips at the raw link; `ble_subscribed` flips only after encryption and the app subscribing to the notify characteristic (CCCD subscribe on the TX characteristic, on the C5). + +## Chat UI (`ui/screens/lora/`) + +`lora_chat_ui` is a single-screen state machine. + +```c +void ui_lora_chat_open(void); // opens on the protocol picker +void ui_lora_chat_open_chat(void); // opens directly on the chat conversation +``` + +Views: **Protocol picker** (MeshCore / Meshtastic) -> **Home** -> { Connect App, Nodes, Chat, Configs }. Home also links out to the sibling screens: Channels, Position, Telemetry, Secure DM, Traceroute (each its own screen under `ui/screens/lora/`). + +- **Home** shows region + preset (Meshtastic only) and a status banner driven by the linked state. +- **Nodes** shows either a radial "map" of node pins with RSSI-colored links or a scrolling list, both fed by `lora_session_node_count()` / `lora_session_node_get()`. +- **Chat** renders message bubbles ("MESH BROADCAST" for Meshtastic, "PUBLIC CHANNEL" for MeshCore). An LVGL timer drains new messages every 500 ms via `lora_session_msg_since(...)`; the on-screen keyboard submits through `lora_session_send_text(...)`. +- **Configs** (Meshtastic only) offers Region / Preset selectors and a Router-mode toggle. +- **Connect** calls `lora_session_app_connect()`, polls `lora_session_app_connected()` every second, and shows the pairing PIN (MeshCore only). + +Starting a protocol runs `lora_session_start(proto)` on a background task pinned to the radio core; if a different protocol already owns the radio the UI prompts to reboot to switch. + +## RNode Modem (`rnode/`) + +`rnode/` shares the SX1262 driver but is **not** a mesh protocol - it is an **RNode-compatible KISS/serial modem** (Reticulum RNode firmware emulation, reporting v1.52). A host running Reticulum drives the radio over KISS-framed serial. It is mutually exclusive with the mesh stacks (same single-owner radio), but it is started independently of `lora_session` (it is not one of the `lora_proto_t` values). + +```c +esp_err_t rnode_init(const sx1262_hal_t *hal); // KISS engine + serial + SX1262 hookup +esp_err_t rnode_start(void); // IRQ task + continuous RX +void rnode_poll(void); // drains serial, runs KISS decoder, dispatches +const rnode_radio_cfg_t *rnode_get_radio_cfg(void); +const rnode_stats_t *rnode_get_stats(void); +``` + +Defaults: 915 MHz, BW 125 kHz, SF8, CR 4/5, +17 dBm. The on-device screen is `lora_rnode_ui`. diff --git a/docs/README.md b/docs/README.md index 4a7f1569d..28434744a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -14,21 +14,33 @@ README.md, separated by `---`. | Component | Docs | |-----------|------| +| `audio_i2s` | [README.md](audio_i2s/README.md) - I2S audio output driver | | `bad_usb` | [README.md](bad_usb/README.md) | | `bluetooth` | [README.md](bluetooth/README.md) | | `boot_report` | [README.md](boot_report/README.md) | +| `bq25896` | [README.md](bq25896/README.md) - TI BQ25896 battery charger / PMIC driver | +| `bridge_manager` | [README.md](bridge_manager/README.md) - P4<->C5 bridge lifecycle and coordination service | | `buttons_gpio` | [README.md](buttons_gpio/README.md) | | `c5_flasher` | [README.md](c5_flasher/README.md) | | `cc1101` | [README.md](cc1101/README.md) | | `console` | [README.md](console/README.md) | | `dns_server` | [README.md](dns_server/README.md) | +| `drv2605l` | [README.md](drv2605l/README.md) - TI DRV2605L haptic motor driver | | `esp_now` | [README.md](esp_now/README.md) | | `espnow_chat` | [README.md](espnow_chat/README.md) | +| `gameboy` | [README.md](gameboy/README.md) - Game Boy emulator application | | `host_link` | [app-guide.md](host_link/app-guide.md) [protocol.md](host_link/protocol.md) [README.md](host_link/README.md) | | `http_server` | [README.md](http_server/README.md) | +| `i2c_init` | [README.md](i2c_init/README.md) - shared I2C bus initialization driver | | `input_manager` | [README.md](input_manager/README.md) | -| `lvgl_port` | [README.md](lvgl_port/README.md) | +| `ir` | [README.md](ir/README.md) - infrared transmit/receive service | +| `led` | [README.md](led/README.md) - status/RGB LED driver | +| `LoRa` | [README.md](LoRa/README.md) - LoRa messaging application | +| `lvgl` | [README.md](lvgl/README.md) | +| `nfc` | [README.md](nfc/README.md) - NFC read/write application | | `ota` | [README.md](ota/README.md) | +| `pins` | [README.md](pins/README.md) - board pin assignment definitions | +| `power_manager` | [README.md](power_manager/README.md) - battery and power-state management service | | `recovery` | [README.md](recovery/README.md) | | `sd_card` | [README.md](sd_card/README.md) | | `spi` | [README.md](spi/README.md) | @@ -38,8 +50,11 @@ README.md, separated by `---`. | `storage_assets` | [README.md](storage_assets/README.md) | | `storage_vfs` | [README.md](storage_vfs/README.md) | | `SubGhz` | [README.md](SubGhz/README.md) | +| `sx1262` | [README.md](sx1262/README.md) - Semtech SX1262 LoRa radio transceiver driver | | `sys_monitor` | [README.md](sys_monitor/README.md) | | `sys_prio` | [README.md](sys_prio/README.md) | +| `sys_time` | [README.md](sys_time/README.md) - system clock / RTC time service | | `tusb_desc` | [README.md](tusb_desc/README.md) | | `ui` | [README.md](ui/README.md) [input-migration.md](ui/input-migration.md) | | `wifi` | [README.md](wifi/README.md) | +| `ys_rfid2` | [README.md](ys_rfid2/README.md) - YS-RFID2 125 kHz RFID reader driver | diff --git a/docs/SubGhz/README.md b/docs/SubGhz/README.md index e8b2c7376..87535b93b 100644 --- a/docs/SubGhz/README.md +++ b/docs/SubGhz/README.md @@ -60,6 +60,8 @@ Captures RF signals via the CC1101 GDO0 pin routed to the ESP32 RMT RX periphera 4. **SCAN mode:** Protocol registry tries all decoders -> Analyzer for unknowns 5. **RAW mode:** Direct save to storage +**LED feedback:** The RX pipeline drives the status LED as it processes signals: `led_signal_info()` fires when a known protocol is decoded, and `led_signal_warning()` fires when RF is captured but no registered protocol matches. + #### API ```c @@ -120,7 +122,7 @@ void subghz_spectrum_start(uint32_t center_freq, uint32_t span_hz); void subghz_spectrum_stop(void); bool subghz_spectrum_get_line(subghz_spectrum_line_t *out_line); ``` -- Task stack: 4096 bytes, priority 1, Core 1. +- Task stack: 4096 bytes, Core 1. Priority comes from `sys_prio.h`: the `SYS_PRIO_BACKGROUND_LO` band (lowest non-idle background work), so the sweep never contends with the renderer or latency-sensitive services. - Thread-safe reads via `subghz_spectrum_get_line`. ### Signal Analyzer (`subghz_analyzer`) diff --git a/docs/audio_i2s/README.md b/docs/audio_i2s/README.md new file mode 100644 index 000000000..fdc309311 --- /dev/null +++ b/docs/audio_i2s/README.md @@ -0,0 +1,151 @@ +# Audio I2S Driver (P4) + +This component drives the on-board audio path of the HighBoy V2 (ESP32-P4 firmware only): a class-D speaker amplifier for playback (TX) and a PDM microphone for capture (RX), both on a single I2S port. It owns one persistent TX channel used for UI chimes, tones, melodies, raw PCM and continuous streaming, and opens independent PDM-RX channels for mic recording and live streaming. + +## Overview + +- **Location:** `firmware_p4/components/Drivers/audio_i2s/` +- **Header:** `include/audio_i2s.h` +- **Firmware:** `firmware_p4` only (no C5 counterpart) +- **Dependencies:** `driver/i2s_std`, `driver/i2s_pdm`, `driver/gpio`, `freertos`, `pin_def`, `sys_prio` +- **I2S port:** `I2S_NUM_0` (master role). The single TX channel and the PDM-RX channels share this one port. + +> **Part naming note:** the public header docstrings refer to a **MAX98357A** amplifier, while the driver source (`audio_i2s.c`) and the pin map (`pin_def.h`) name an **NS4168** amp and an **MSM261** PDM mic. The code and pin comments are the authoritative wiring reference; the amplifier is enabled by taking its shutdown pin high, and its hardware gain is fixed, so volume is applied digitally. + +## Signal Path + +### TX (speaker / amplifier) + +- **Mode:** I2S standard (Philips) slot format, **16-bit**, **mono**, master. +- **Default sample rate:** `44100` Hz (`SAMPLE_RATE_HZ`). PCM and stream calls may pass any rate; the driver reconfigures the TX clock only when the requested rate actually changes. +- **One persistent channel:** `I2S_NUM_0` has exactly one TX channel. It is created once at init and never deleted; playback only enables/disables it and reconfigures its clock. Creating/deleting a channel per sound churned internal DMA RAM and stuttered the UI, so that is avoided. +- **DMA depth:** `AUDIO_DMA_DESC_NUM` = 8 descriptors x `AUDIO_DMA_FRAME_NUM` = 256 frames (~46 ms, ~8 KB internal RAM), deeper than the IDF default for underrun slack. +- **Underrun behaviour:** `auto_clear = true`, so a starved channel emits zeros instead of looping the stale buffer. +- **Anti-click:** each note gets a raised-cosine attack (`ENV_ATTACK_MS` = 4 ms) / release (`ENV_RELEASE_MS` = 8 ms) envelope, and playback brackets sounds with short silence; melodies use a continuous phase accumulator for gapless rendering. +- **Amp enable:** `GPIO_AUDIO_EN_PIN` is driven high (out of shutdown) at init; idempotent. + +### RX (PDM microphone) + +- **Mode:** PDM RX, **16-bit**, **mono**, master. +- **Filtering:** high-pass filter enabled with a 50 Hz cut-off (`hp_cut_off_freq_hz`), digital gain `amplify_num = 3`. A ~120 ms settle delay is applied after enable to let the HP filter converge. +- **Channels:** the mic uses its own RX channel on `I2S_NUM_0`, independent of the TX side. `audio_i2s_mic_record()` opens/closes a channel per call; the streaming API keeps a persistent RX channel (`s_rx_stream`). + +## Concurrency + +- Playback runs on a pinned task `"audio_i2s"`: stack `AUDIO_TASK_STACK_SIZE` = 4096, priority `SYS_PRIO_SERVICE_LO`, core `SYS_CORE_RADIO`. It is deliberately **not** on the UI core, because the LVGL renderer at `SYS_PRIO_RENDER` would starve the I2S DMA during a full-frame redraw. +- A single mutex (`s_tx_mux`) guards the shared TX channel. Callers from the LVGL thread use finite timeouts and drop the sound rather than stall the UI: + - `TX_LOCK_WAIT_MS` = 250 ms for blocking playback / stream claims. + - `FX_TX_WAIT_MS` = 0 for queued UI effects (never wait behind a melody). +- Queued UI effects (`audio_play_chime`, `audio_click`) post `fx_t` entries to a depth-4 queue (`QUEUE_DEPTH`); overflow is dropped so button mashing never piles up. +- While a TX stream is active (`s_streaming`), one-shot playback calls become no-ops so they do not fight the stream. + +## Pins (from `pin_def.h`) + +| Signal | Define | GPIO | +| :--- | :--- | :--- | +| Amp enable / shutdown | `GPIO_AUDIO_EN_PIN` | 49 | +| I2S bit clock (BCLK) | `GPIO_AUDIO_BCLK_PIN` | 28 | +| I2S word select (LRCLK / WS) | `GPIO_AUDIO_LRCLK_PIN` | 29 | +| I2S data out (DOUT) | `GPIO_AUDIO_DIN_PIN` | 27 | +| Mic PDM clock | `GPIO_MIC_PDM_CLK_PIN` | 54 | +| Mic PDM data | `GPIO_MIC_PDM_DATA_PIN` | 53 | + +MCLK and the TX-side DIN are `I2S_GPIO_UNUSED`. + +## API Reference + +### Initialization + +#### `audio_i2s_init` +```c +esp_err_t audio_i2s_init(void); +``` +Brings up `I2S_NUM_0` on the amp pins, allocates the DMA chunk buffer, mutex and effect queue, and starts the playback task. Idempotent; safe to call multiple times. + +#### `audio_i2s_set_volume` +```c +void audio_i2s_set_volume(uint8_t pct); +``` +Sets global output volume (0..100), clamped. Applied as a perceptual (square-law) digital scale on top of each sound's own amplitude, because the amplifier's hardware gain is fixed. + +### Tones and Melodies + +#### `audio_i2s_play_tone` +```c +esp_err_t audio_i2s_play_tone(float freq_hz, int dur_ms, float amp); +``` +Plays a single blocking sine tone at 44.1 kHz mono. `amp` is in [0..1]. Returns `ESP_ERR_TIMEOUT` if the channel is busy (sound dropped), `ESP_OK` otherwise (also when not ready or a stream is active). + +#### `audio_i2s_play_song` / `audio_i2s_play_song_cb` +```c +esp_err_t audio_i2s_play_song(const audio_note_t *notes, int count, float amp); +esp_err_t audio_i2s_play_song_cb(const audio_note_t *notes, int count, float amp, + audio_song_progress_cb_t cb, void *ctx); +``` +Plays a melody blocking and gapless: opens the channel once, renders all notes back-to-back with per-note anti-click envelopes and a continuous phase accumulator, then ends. The `_cb` variant invokes `cb` before each note (in the worker thread) with the note index, total count and frequency; returning `false` cancels playback cooperatively after the current note. Pass `cb = NULL` for plain playback. + +`audio_note_t` is `{ uint16_t freq_hz; uint16_t dur_ms; }`; `freq_hz == 0` is a rest (silence). + +`audio_song_progress_cb_t`: +```c +typedef bool (*audio_song_progress_cb_t)(int note_index, int note_count, + uint16_t freq_hz, void *ctx); +``` +Must be trivial (touch only volatile scalars); never call `lv_*` from it. + +### Raw PCM Playback + +#### `audio_i2s_play_pcm` +```c +esp_err_t audio_i2s_play_pcm(const int16_t *pcm, size_t n_samples, uint32_t sample_rate); +``` +Plays a raw 16-bit mono PCM buffer (blocking) at `sample_rate`. Reconfigures the TX clock to the given rate and appends a short tail-silence. + +### Continuous TX Streaming + +#### `audio_i2s_stream_start` / `audio_i2s_stream_write` / `audio_i2s_stream_stop` +```c +esp_err_t audio_i2s_stream_start(uint32_t sample_rate); +int audio_i2s_stream_write(const int16_t *pcm, int n_samples); +void audio_i2s_stream_stop(void); +``` +Begins a continuous 16-bit mono TX stream (e.g. WAV playback), reusing the shared TX channel and suppressing UI chimes/clicks for its duration. `stream_write` applies global volume, blocks and paces to real time via the I2S DMA, and returns samples written (or -1 if the stream is not active). `stream_stop` drains the DMA, restores the default clock and re-enables UI sounds; safe when not started. + +### PDM Microphone Capture + +#### `audio_i2s_mic_record` +```c +esp_err_t audio_i2s_mic_record(int16_t *out, size_t max_samples, uint32_t sample_rate, + size_t *out_captured, audio_mic_level_cb_t cb, void *ctx); +``` +Captures up to `max_samples` of 16-bit mono PCM from the PDM mic (blocking): opens a PDM-RX channel, lets the HP filter settle (~120 ms), fills `out`, then closes. `*out_captured` receives the real sample count. If `cb` is non-NULL it is invoked after each chunk with that chunk's peak and RMS level for a live VU meter. + +`audio_mic_level_cb_t`: +```c +typedef void (*audio_mic_level_cb_t)(int peak, int rms, void *ctx); +``` +`peak` is max |sample| (0..32767) of the last chunk. Runs in the recording task's context; keep it trivial. + +#### `audio_i2s_mic_stream_start` / `audio_i2s_mic_stream_read` / `audio_i2s_mic_stream_stop` +```c +esp_err_t audio_i2s_mic_stream_start(uint32_t sample_rate); +int audio_i2s_mic_stream_read(int16_t *buf, int max_samples); +void audio_i2s_mic_stream_stop(void); +``` +Opens a persistent PDM-RX stream at `sample_rate` (idempotent). `mic_stream_read` reads up to `max_samples` 16-bit mono samples, returning the count read (0 on error/timeout/not-started). `mic_stream_stop` stops and frees the RX channel; safe when not started. + +### UI Sound Effects + +#### `audio_play_chime` +```c +void audio_play_chime(void); +``` +Queues a 3-note boot chime (C5 -> E5 -> G5, ~130-180 ms each). Non-blocking; dropped if the queue is full. + +#### `audio_click` +```c +void audio_click(void); +``` +Queues a short click (~30 ms, ~2 kHz). Non-blocking; dropped if the queue already holds pending entries so rapid button presses never pile up. + + diff --git a/docs/bad_usb/README.md b/docs/bad_usb/README.md index b89fc7086..c4a58fc9a 100644 --- a/docs/bad_usb/README.md +++ b/docs/bad_usb/README.md @@ -51,26 +51,29 @@ This component implements a modular HID injection tool capable of emulating keyb esp_err_t bad_usb_init(void); esp_err_t bad_usb_deinit(void); void bad_usb_wait_for_connection(void); +bool bad_usb_wait_for_connection_ex(bad_usb_abort_cb_t should_abort); ``` -- `bad_usb_init` initializes TinyUSB and registers USB HID callbacks into the HAL. +- `bad_usb_init` initializes TinyUSB (via `busb_init`) and registers USB HID callbacks (keyboard, mouse, wait, report-ready) into the HAL. - `bad_usb_deinit` unregisters callbacks and uninstalls the TinyUSB driver. -- `bad_usb_wait_for_connection` blocks until the USB host mounts the device, then waits 2 seconds for enumeration. +- `bad_usb_wait_for_connection` blocks until the USB host mounts the device, then waits 2 seconds (`USB_SETTLE_DELAY_MS`) for the host OS to enumerate. +- `bad_usb_wait_for_connection_ex` is the abortable variant. It polls `tud_mounted()` with an **8 second mount timeout** (`USB_MOUNT_TIMEOUT_MS`), calling `should_abort` (a `bad_usb_abort_cb_t`) between polls and during the post-mount settle. It returns `true` if the host mounted and settled, `false` if the timeout elapsed or the abort predicate fired. `bad_usb_wait_for_connection` is just `_ex(NULL)`, which never times out on the abort path but still stops after the mount timeout. Use `_ex` so a run waiting for a host that never arrives can be cancelled. ### HID HAL (`hid_hal.h`) ```c void hid_hal_register_callback(hid_send_cb_t send_cb, hid_mouse_cb_t mouse_cb, - hid_wait_cb_t wait_cb); + hid_wait_cb_t wait_cb, + hid_ready_cb_t ready_cb); void hid_hal_press_key(uint8_t keycode, uint8_t modifiers); void hid_hal_mouse_move(int8_t x, int8_t y); void hid_hal_mouse_click(uint8_t buttons); void hid_hal_mouse_scroll(int8_t wheel); void hid_hal_wait_for_connection(void); ``` -- `hid_hal_press_key` sends a key-down + key-up report with ~5 ms per phase. -- Mouse functions use ~2 ms delay for moves and ~5 ms for clicks. -- All functions yield to the scheduler (`vTaskDelay(0)`) to prevent WDT starvation. +- Reports are paced by a report-readiness callback (`ready_cb`), not fixed delays. Before and after each report the HAL calls `wait_report_ready`, which spins on `tud_hid_ready()` (yielding with `vTaskDelay(1)` between checks) until the previous report has been delivered to the host, so an unpolled report is never overwritten (which would drop keys). This paces output to the host's ~1 ms poll rate. +- The wait is capped by `REPORT_READY_TIMEOUT_MS` (1000 ms): it must exceed how long a busy host/editor can stall HID polling, so only a real disconnect hits it. Raising it to 1 s is what keeps keys (e.g. a leading `HOME`) from being dropped when the host stalls. +- If no `ready_cb` is registered, the HAL falls back to fixed `ets_delay_us` delays (`FALLBACK_KEY_DELAY_US` = 5000 us for keys, `FALLBACK_MOUSE_DELAY_US` = 2000 us for mouse). ### Keyboard Layouts (`hid_layouts.h`) @@ -79,7 +82,12 @@ void hid_layouts_type_string_us(const char *str); void hid_layouts_type_string_abnt2(const char *str); ``` - `hid_layouts_type_string_us` maps ASCII characters to US keyboard HID keycodes. -- `hid_layouts_type_string_abnt2` handles Brazilian Portuguese layout including UTF-8 dead-key sequences for accented characters (e.g. a, e, c, a, o). +- `hid_layouts_type_string_abnt2` types the Brazilian Portuguese (ABNT2) layout with full accented-character coverage, not just a few dead-key accents. It decodes UTF-8 two-byte sequences via a lookup table (`ABNT2_UTF8_MAP`) that covers: + - Accented vowels **a e i o u**, lowercase and uppercase, in all five diacritics: acute, grave, circumflex, tilde, and diaeresis (each emitted as the ABNT2 dead key + base letter, with Shift for uppercase). + - **c-cedilha** (c) and **n-tilde** (n), both lowercase and uppercase. + - AltGr (RightAlt) symbols: cent, pound, section, not-sign, and superscripts 1 / 2 / 3. + - Bracket/brace/backslash/pipe (`[ ] { } \ |`) via RightAlt (with Shift for the brace/pipe forms). + - The symbols that a naive US mapping gets wrong on ABNT2 hardware are fixed: `^` and `~` are typed as dead key + space to emit the literal, and `<` / `>` use the correct ABNT2 keys. Apostrophe / double-quote / backtick are also remapped to their ABNT2 positions. ### DuckyScript Parser (`ducky_parser.h`) @@ -106,18 +114,20 @@ void ducky_abort(void); | `DELAY` | [ms] | Pause execution for N milliseconds | | `STRING` | [text] | Type text using the active keyboard layout | | `ENTER` / `RETURN` | - | Press Enter | -| `GUI` / `WINDOWS` / `COMMAND` | [key] | Windows/Command key (optionally with a key) | +| `GUI` / `WINDOWS` / `SUPER` / `COMMAND` | [key] | GUI (Windows/Command/Super) modifier, optionally with a key | | `CTRL` / `CONTROL` | [key] | Control + key | | `SHIFT` | [key] | Shift + key | | `ALT` | [key] | Alt + key | | `TAB` | - | Tab key | | `ESC` / `ESCAPE` | - | Escape key | +| `SPACE` | - | Space bar | +| `BACKSPACE` | - | Backspace | | `F1` - `F12` | - | Function keys | -| `UP` / `DOWN` / `LEFT` / `RIGHT` | - | Arrow keys | +| `UP` / `DOWN` / `LEFT` / `RIGHT` (and `UPARROW` / `DOWNARROW` / `LEFTARROW` / `RIGHTARROW`) | - | Arrow keys | | `HOME` / `END` / `INSERT` / `DELETE` | - | Navigation keys | | `PAGEUP` / `PAGEDOWN` | - | Page navigation | | `CAPSLOCK` / `NUMLOCK` / `SCROLLLOCK` | - | Lock keys | -| `PRINTSCREEN` / `PAUSE` / `APP` / `MENU` | - | Special system keys | +| `PRINTSCREEN` / `PAUSE` / `APP` / `MENU` | - | Special system keys (`APP` and `MENU` both map to the Application key) | | `MOUSE_MOVE` | [x] [y] | Move mouse relative (-127 to 127) | | `MOUSE_CLICK` / `LCLICK` | - | Left mouse click | | `MOUSE_RIGHT_CLICK` / `RCLICK` | - | Right mouse click | @@ -130,5 +140,14 @@ Modifier keys can be combined: `CTRL SHIFT ESC`, `GUI r`, `ALT F4`. | Layout | Enum | Notes | |--------|------|-------| | US (QWERTY) | `DUCKY_LAYOUT_US` | Default. Standard ASCII mapping. | -| ABNT2 (Brazil) | `DUCKY_LAYOUT_ABNT2` | Dead-key accent support, remapped punctuation. | +| ABNT2 (Brazil) | `DUCKY_LAYOUT_ABNT2` | Full accented-character coverage (see the layouts API above), remapped punctuation, AltGr symbols. | + +## Payload Sources + +Scripts run either from the internal flash asset partition (`ducky_run_from_assets`) or from the SD card (`ducky_run_from_sdcard`, max 8 KB per script). In the UI menu, SD payloads are scanned **recursively across subfolders** and listed together in a single view, so scripts organised into folders on the card are all discoverable. + +Example payloads ship in the asset partition under `firmware_p4/assets/storage/bad_usb_scripts/`: + +- `rickroll.txt` +- `amiga.txt` diff --git a/docs/bluetooth/README.md b/docs/bluetooth/README.md index 249e7f91c..3ae42d66f 100644 --- a/docs/bluetooth/README.md +++ b/docs/bluetooth/README.md @@ -59,6 +59,37 @@ Performs a blocking discovery procedure for the specified duration. Results are - `bluetooth_service_get_scan_count()`: Returns the number of unique devices found. - `bluetooth_service_get_scan_result(uint16_t index)`: Returns a pointer to a `bluetooth_service_scan_result_t` structure containing name, RSSI, and MAC address. +### Sniffer & Tracker + +#### `bluetooth_service_start_sniffer` +```c +typedef void (*bluetooth_service_sniffer_cb_t)( + const uint8_t *addr, uint8_t addr_type, int rssi, const uint8_t *data, uint16_t len); + +esp_err_t bluetooth_service_start_sniffer(bluetooth_service_sniffer_cb_t cb); +``` +Starts a passive BLE sniffer (raw advertisement capture). `cb` is invoked for each received advertisement with the advertiser address, address type, RSSI, and the raw advertisement bytes. + +#### `bluetooth_service_stop_sniffer` +```c +void bluetooth_service_stop_sniffer(void); +``` +Stops the BLE sniffer. + +#### `bluetooth_service_start_tracker` +```c +typedef void (*bluetooth_service_tracker_cb_t)(int rssi); + +esp_err_t bluetooth_service_start_tracker(const uint8_t *addr, bluetooth_service_tracker_cb_t cb); +``` +Starts RSSI tracking for a specific BLE device (6-byte `addr`). `cb` is invoked with the RSSI of the tracked device on each update. + +#### `bluetooth_service_stop_tracker` +```c +void bluetooth_service_stop_tracker(void); +``` +Stops the RSSI tracker. + ### Advertising Management #### `bluetooth_service_start_advertising` / `stop_advertising` @@ -66,6 +97,14 @@ Standard connectable advertising using the configured device name. Advertising a ### Connection Management +#### `bluetooth_service_connect` +```c +esp_err_t bluetooth_service_connect(const uint8_t *addr, + uint8_t addr_type, + int (*cb)(struct ble_gap_event *event, void *arg)); +``` +Initiates a BLE connection to a remote device. `addr` is the 6-byte target address, `addr_type` its address type, and `cb` a GAP event callback that receives connection events for this link. + #### `bluetooth_service_disconnect_all` ```c void bluetooth_service_disconnect_all(void); diff --git a/docs/boot_report/README.md b/docs/boot_report/README.md index a7ee0c0d4..1ff3a35cb 100644 --- a/docs/boot_report/README.md +++ b/docs/boot_report/README.md @@ -26,9 +26,12 @@ const boot_stage_t *boot_report_stages(int *out_count); bool boot_report_all_required_ok(void); ``` -Recorded stages with their real return codes: `sd-storage` (optional, absent SD -is fine), `assets` (required), `battery` (optional), `display` (required), -`nvs` (required). A **required** failure makes `kernel_init` drop into +Recorded stages with their real return codes, in the order `kernel_init` runs +them: `nvs` (required), `i2c` (optional, degrades charger/haptic/LED only), +`sd-storage` (optional, absent SD is fine), `sd-health` (optional, recorded only +when the SD is mounted), `assets` (required), `battery` (optional), `audio` +(optional), `display` (required), `lvgl` (required, recorded only when the panel +came up). A **required** failure makes `kernel_init` drop into [safe mode](../recovery/README.md) instead of booting blind, and the function returns `ESP_FAIL`. @@ -95,6 +98,7 @@ void boot_report_track_bootloop(void); // FIRST thing in app_main bool boot_report_in_bootloop(void); uint32_t boot_report_abnormal_boots(void); uint32_t boot_report_panic_total(void); +uint32_t boot_report_boot_count(void); void boot_report_mark_stable(void); ``` @@ -113,9 +117,13 @@ void boot_report_mark_stable(void); [safe mode](../recovery/README.md) - the degraded target (no radios, no SD theme, minimal UI). The safe-mode footer shows the reset reason, and the crash viewer shows `Panics total` and `Abnormal boots N/3`. -- Only a **summary** is persisted to NVS (namespace `boot_report`: last reason + - running panic total), written on abnormal boots only, so flash wear is - negligible. The RTC counter carries the loop state itself. +- A **summary** is persisted to NVS (namespace `boot_report`). A real + **total boot count** (`boot_total`, exposed via `boot_report_boot_count()`) is + bumped once every boot from `boot_report_capture_crash` - one small write per + boot. The **last reason** + running **panic total** (`boot_report_panic_total()`) + are written only on abnormal boots, so panic-path flash wear stays negligible. + The RTC counter carries the consecutive-loop state itself; NVS holds the + lifetime totals. ## Follow-ups diff --git a/docs/bq25896/README.md b/docs/bq25896/README.md new file mode 100644 index 000000000..8e8711de9 --- /dev/null +++ b/docs/bq25896/README.md @@ -0,0 +1,172 @@ +# BQ25896 Charger / PMIC Driver + +This component drives the Texas Instruments BQ25896 single-cell battery charger and power-path IC over I2C. It exposes battery voltage, charge and VBUS status, charge enable control, and a real ship-mode power-off. A thin `battery_service` layer sits on top and publishes a single smoothed snapshot that the UI reads instead of polling the charger directly. + +## Overview + +- **Location:** `components/Drivers/bq25896/` (firmware_p4 only) +- **Headers:** `include/bq25896.h`, `include/battery_service.h` +- **Sources:** `bq25896.c` (register I/O + controls), `bq25896_ext.c` (telemetry aggregator), `battery_service.c` (poll task + smoothing) +- **Dependencies:** `i2c_init`, `pins` (`pin_def.h`), `sys_prio`, `driver/gpio`, `freertos` +- **Interface:** I2C master bus `I2C_NUM_0` (via `i2c_init`, shared), 100 kHz standard-mode +- **I2C address:** `0x6B` (`BQ25896_I2C_ADDR`, 7-bit) +- **CE pin:** `GPIO_CHARGER_CE_PIN` (GPIO33), active-low charge enable, driven at init + +## Charge Status (`bq25896_charge_status_t`) + +| Value | Meaning | +|-------|---------| +| `CHARGE_STATUS_NOT_CHARGING` | Not charging | +| `CHARGE_STATUS_PRECHARGE` | Pre-charge phase | +| `CHARGE_STATUS_FAST_CHARGE` | Fast-charge phase | +| `CHARGE_STATUS_CHARGE_DONE` | Charge complete | + +## VBUS Status (`bq25896_vbus_status_t`) + +| Value | Meaning | +|-------|---------| +| `VBUS_STATUS_UNKNOWN` | No/unknown VBUS source | +| `VBUS_STATUS_USB_HOST` | USB host (SDP) input | +| `VBUS_STATUS_ADAPTER_PORT` | Dedicated adapter input | +| `VBUS_STATUS_OTG` | OTG (boost) output | + +## BQ25896 API Reference + +### Initialization + +#### `bq25896_init` +```c +esp_err_t bq25896_init(void); +``` +Adds the charger to the shared I2C bus, probes it (REG14), and marks the chip present. Drives CE (GPIO33) low to enable charging, disables the charge watchdog and sets `JEITA_ISET=0` (REG07), lowers `SYS_MIN` to 3.0 V (REG03) so the battery ADC keeps converting down to the 3.0 V knee, and enables the continuous ADC (REG02). Returns `ESP_OK` on success, or the failing `esp_err_t`. + +#### `bq25896_is_present` +```c +bool bq25896_is_present(void); +``` +True once the charger has answered on I2C at init. + +### Battery State + +#### `bq25896_get_battery_voltage` +```c +uint16_t bq25896_get_battery_voltage(void); +``` +Battery voltage in mV from REG0E (base 2304 mV, 20 mV/step), or 0 on read failure. + +#### `bq25896_get_battery_percentage` +```c +int bq25896_get_battery_percentage(uint16_t voltage_mv); +``` +Linear voltage-to-percent estimate (0-100) between 3200 mV and 4200 mV. + +#### `bq25896_get_charge_status` / `bq25896_get_vbus_status` +```c +bq25896_charge_status_t bq25896_get_charge_status(void); +bq25896_vbus_status_t bq25896_get_vbus_status(void); +``` +Decode the CHG_STAT and VBUS_STAT fields of the status register (REG0B). + +#### `bq25896_is_charging` +```c +bool bq25896_is_charging(void); +``` +True while pre-charging or fast charging. + +#### `bq25896_get_fault` +```c +uint8_t bq25896_get_fault(void); +``` +Raw fault register (REG0C): `CHRG_FAULT[5:4]`, `BAT_FAULT[3]`, `NTC_FAULT[2:0]`. + +### Charge Control + +#### `bq25896_get_charge_enable` +```c +bool bq25896_get_charge_enable(void); +``` +Reads REG03 `CHG_CONFIG` (bit 4). + +#### `bq25896_set_charge_enable` +```c +esp_err_t bq25896_set_charge_enable(bool enable); +``` +Drives both gates: the active-low CE pin (GPIO33) and REG03 `CHG_CONFIG`, so the state is unambiguous. + +### Ship Mode / Power Off + +#### `bq25896_power_off` +```c +esp_err_t bq25896_power_off(void); +``` +Real power-off: sets `BATFET_DIS` (REG09 bit 5) to disconnect the battery. Also sets `BATFET_DLY` (bit 3) so the MCU finishes the I2C write before the rail collapses, and clears `BATFET_RST_EN` (bit 2) to disarm the /QON auto-reset. Without disarming, a /QON pulse (the BACK button on this board) would cycle BATFET back on and cold-boot the device a few seconds later. Has no effect while VBUS (USB) is present: the part keeps the system powered from USB. + +### Telemetry + +#### `bq25896_read_telemetry` +```c +esp_err_t bq25896_read_telemetry(bq25896_telem_t *out); +``` +Fills a `bq25896_telem_t` snapshot. Battery voltage/percent/charge/VBUS/fault fields are real; the `vsys_mv`, `vbus_mv`, `ichg_ma`, and `iinlim_ma` diagnostic fields are currently 0 (approximate/unpopulated). Returns `ESP_ERR_INVALID_ARG` if `out` is NULL. + +`bq25896_telem_t` fields: `vbat_mv`, `vsys_mv`, `vbus_mv`, `ichg_ma`, `iinlim_ma`, `fault`, `soc`, `chg`, `vbus`, `charging`, `power_good`. + +#### `bq25896_reg_raw` +```c +uint8_t bq25896_reg_raw(uint8_t reg); +``` +Raw single-register read, or 0 on failure. + +## Battery Service API Reference + +A background poll task (`battery_svc`, core `SYS_CORE_RADIO`, priority `SYS_PRIO_BACKGROUND`) keeps a mutex-guarded smoothed snapshot so screens do not each poll the charger. + +#### `battery_service_init` +```c +void battery_service_init(void); +``` +Idempotent. Requires `bq25896_init()` first. Takes one synchronous reading so the first UI paint has real data, then polls in the background. + +#### `battery_service_get` +```c +bool battery_service_get(battery_snapshot_t *out); +``` +Copies the latest snapshot into `out`. Returns true if a valid reading is available. + +#### `battery_service_soc` / `battery_service_is_low` +```c +int battery_service_soc(void); +bool battery_service_is_low(void); +``` +Convenience accessors: smoothed SoC (0-100, or -1 if no valid reading yet) and the latched low-battery flag. + +`battery_snapshot_t` fields: `soc`, `vbat_mv`, `present`, `charging`, `vbus_present`, `low`, `valid`, `chg`. + +### Filtered SoC and Smoothing + +The raw voltage-derived SoC is filtered before it reaches the UI: + +- **EMA:** `ema = (ema*3 + raw) / 4` (`SOC_EMA_DEN = 4`) rejects the transient voltage sag when a radio transmits. +- **Slew limit:** at most `SOC_STEP_MAX = 3` % change applied per poll. +- **Monotonic on battery:** off external power, SoC never climbs (a recovering voltage after a load sag would otherwise bounce it up); it only rises while charging or on external power. +- **Low-battery hysteresis:** latches `low` at/below `LOW_ENTER_PCT = 15`, releases at/above `LOW_EXIT_PCT = 20`; never low while charging or on VBUS. +- **Charging debounce:** turns the charging indicator on immediately, off immediately when unplugged, but requires `CHG_OFF_DEBOUNCE = 3` consecutive not-charging polls to drop it while still on external power. + +## Key Config / Tunables + +| Macro | Location | Value | Meaning | +|-------|----------|-------|---------| +| `BQ25896_I2C_ADDR` | `bq25896.h` | `0x6B` | I2C 7-bit address | +| `I2C_TIMEOUT_MS` | `bq25896.c` | 100 | Per-transfer timeout | +| `I2C_FAIL_RECOVER` | `bq25896.c` | 3 | Consecutive failures before `i2c_bus_recover()` | +| `BATV_BASE_MV` / `BATV_STEP_MV` | `bq25896.c` | 2304 / 20 | REG0E voltage decode | +| `BATTERY_MIN_MV` / `BATTERY_MAX_MV` | `bq25896.c` | 3200 / 4200 | Percent-estimate endpoints | +| `POLL_INTERVAL_MS` | `battery_service.c` | 1000 | Steady poll cadence (first sample at ~1200 ms) | +| `SOC_STEP_MAX` | `battery_service.c` | 3 | Max SoC delta per poll | +| `SOC_EMA_DEN` | `battery_service.c` | 4 | EMA denominator | +| `LOW_ENTER_PCT` / `LOW_EXIT_PCT` | `battery_service.c` | 15 / 20 | Low-battery hysteresis | +| `CHG_OFF_DEBOUNCE` | `battery_service.c` | 3 | Not-charging polls before clearing the indicator | + +### Bus Recovery + +Reads and writes track a failure streak; after `I2C_FAIL_RECOVER` consecutive failures the driver calls `i2c_bus_recover()` (from `i2c_init`) to clock out a slave that has wedged the bus, so the charger does not stay unreadable for the rest of the session. diff --git a/docs/bridge_manager/README.md b/docs/bridge_manager/README.md new file mode 100644 index 000000000..2b866512d --- /dev/null +++ b/docs/bridge_manager/README.md @@ -0,0 +1,71 @@ +# Bridge Manager - P4 + +The **P4-side lifecycle / link manager for the P4<->C5 SPI bridge**. It brings the +bridge up in the right handshake mode, checks that the two chips agree on the wire +contract, keeps the link status honest while the C5 comes and goes, and is the +entry point that triggers a C5 firmware update. + +- Location: `firmware_p4/components/Service/bridge_manager/` +- Transport it drives: [`../spi_bridge/README.md`](../spi_bridge/README.md) +- Update path it triggers: [`../c5_flasher/README.md`](../c5_flasher/README.md) + +## API + +```c +esp_err_t bridge_manager_init(void); // bring up the bridge + run the boot checks +esp_err_t bridge_manager_force_update(void); // push a C5 app OTA (SD image, SPI transport) +``` + +## What `bridge_manager_init()` does + +1. **Bridge init (POLL mode).** The HighBoy V2 PCB has no bridge IRQ trace + (`GPIO_BRIDGE_IRQ_PIN == -1`), so the master polls the bus instead of waiting + on an IRQ: `spi_bridge_master_init_mode(SPI_BRIDGE_MODE_POLL)`. The C5 slave + must be initialized in the matching mode. +2. **App-version check.** Reads the C5 firmware version (`SPI_ID_SYSTEM_VERSION`) + and compares it against the P4's expected `FIRMWARE_VERSION` (generated from + `common/metadata/version_info.txt`, currently **1.4.0**). This is + **detection-only**: a mismatch just logs "update available" and a silent C5 + marks the bridge down - the P4 never auto-flashes. The user updates explicitly + with the `c5` console command. +3. **Proto-version match check (SPI-2).** `check_c5_protocol()` reads the C5's + `SPI_ID_SYSTEM_PROTO_VERSION` and compares it against `SPI_PROTOCOL_VERSION` + (**2**). This is the second integrity layer, distinct from the per-frame CRC + (SPI-1): a drifted `spi_protocol.h` leaves the bytes intact but makes the two + ends assign them different meaning, which the CRC cannot catch. On mismatch + (or an older C5 that answers `UNSUPPORTED`) it logs loudly and calls + `led_signal_error()`, but keeps the bridge up - detection, not enforcement. +4. **Starts the C5 link monitor task** (below). + +## C5 link monitor + +A background task (`c5_link_monitor`), pinned to `SYS_CORE_RADIO` at +`SYS_PRIO_SERVICE_LO`, that re-detects a C5 which booted late or rebooted after an +OTA: + +- Idles while `spi_bridge_is_alive()` - it never pokes the bus once the link is up. +- While the bridge is dead, every ~1.5 s it optimistically probes + `SPI_ID_SYSTEM_VERSION` (500 ms timeout). On success it marks the bridge alive, + calls `led_signal_info()` to clear the degraded indicator, and re-runs + `check_c5_protocol()`. On failure it drops back to dead and retries later. + +## C5-link LED signalling + +The manager reflects bridge health on the status LED via `led_control`: + +- `led_signal_error()` on a proto-version mismatch / unavailable (headers out of + sync - reflash both). +- `led_signal_info()` when the link is (re)established, clearing the warning state. + +## OTA triggering + +`bridge_manager_force_update()` runs `c5_flasher_init()` then +`c5_flasher_update(NULL, 0, SPI_OTA_TRANSPORT_SPI)` - i.e. the C5 app OTA streamed +from the SD image over the SPI transport (control always on the bridge). See the +c5_flasher doc for the OTA flow. + +## Safe reads (out_capacity clamp) + +Every `spi_bridge_send_command()` call passes an explicit `out_capacity` equal to +the receiving buffer's `sizeof`, so a slave that announces (or a corrupted length +that inflates) more bytes than the buffer holds is clamped and cannot overflow it. diff --git a/docs/c5_flasher/README.md b/docs/c5_flasher/README.md index dfa9fc968..436d673b6 100644 --- a/docs/c5_flasher/README.md +++ b/docs/c5_flasher/README.md @@ -1,21 +1,103 @@ # C5 Flasher Service - P4 Master -This service allows the ESP32-P4 to update the firmware of the ESP32-C5 using an embedded binary image. +Lets the ESP32-P4 update or recover the ESP32-C5 firmware. There are two paths: -## Features -- **Embedded Binary**: The C5 firmware is embedded directly into the P4 executable during the build process. -- **Bootloader Control**: Automatically puts the C5 into serial bootloader mode using the BOOT and RESET pins. -- **Serial Protocol**: Implements the Espressif Serial Protocol (SLIP framing) to write blocks to the C5 flash. +- **App OTA (primary):** the C5 keeps running its app; the P4 hands it a new + image, which it writes to its inactive OTA slot and boots. The control plane + (begin / status / per-chunk acks) always rides the SPI bridge; the image bytes + travel over the transport the caller selects. The image is normally **streamed + from the SD card** (`/sdcard/c5/TentacleOS_C5.bin`), so it is no longer embedded + in the P4 binary for the everyday update. +- **ROM serial-flash (fallback / bricked C5):** the P4 speaks the ROM + serial-bootloader protocol over UART itself (via `esp-serial-flasher`) to a C5 + that is in ROM download mode. Only this path uses the **embedded** C5 images + (`c5_rom_flasher.c`, guarded by `C5_ROM_IMAGES_EMBEDDED`). -## Usage -1. **Initial Sync**: On boot, the `bridge_manager` checks the C5 version. -2. **Auto-Update**: If the C5 is unresponsive or outdated, `c5_flasher_update(NULL, 0)` is called. -3. **Execution**: The P4 stops the SPI bridge, initializes the Flasher UART, pulses the Reset pin with Boot LOW, and starts streaming the binary. +## App OTA + +```c +esp_err_t c5_flasher_update(const uint8_t *bin_data, uint32_t bin_size, uint8_t transport); +``` + +- `bin_data == NULL` -> the image is streamed from `/sdcard/c5/TentacleOS_C5.bin` + (`bin_size` is taken from the file). Otherwise the caller's buffer is used. +- `transport` is a `spi_ota_transport_t` selector: + - `SPI_OTA_TRANSPORT_SPI` - image bytes arrive as `SPI_ID_SYSTEM_OTA_DATA` + chunks (240 firmware bytes per chunk). No UART wiring needed. + - `SPI_OTA_TRANSPORT_UART` - image bytes arrive raw over UART1 -> C5 UART0 + (needs `c5_flasher_init()`). **Non-functional on the HighBoy V2** (no direct + P4<->C5 UART on that board); use SPI there. + +Flow (`c5_flasher_update`): +1. Send `SPI_ID_SYSTEM_OTA_BEGIN` with `spi_ota_begin_t { size, transport }`. The + BEGIN ack often coincides with the C5's erase and is lost, so the P4 confirms + the C5 actually started by polling `SPI_ID_SYSTEM_OTA_STATUS` + (`spi_ota_status_t { state, bytes_written }`) instead of trusting the ack. +2. Poll `OTA_STATUS` until the C5 reaches `SPI_OTA_STATE_READY` (erase done). +3. Stream the image in blocks (SPI: 240 B `OTA_DATA` chunks; UART: 4 KB blocks), + advancing the live progress counters read by `c5_flasher_progress()`. +4. The C5 validates, sets the new slot to boot and reboots. A failed transfer is + harmless: the old slot still boots. + +`spi_ota_state_t` progression: `IDLE -> ERASING -> READY -> RECEIVING -> DONE` +(or `ERROR`). + +## ROM download entry points + +For a blank or bricked C5 (no working app / bridge), reflash over the ROM +bootloader: + +```c +esp_err_t c5_flasher_enter_download(void); // ask the running C5 to reboot into ROM download mode +esp_err_t c5_flasher_rom_flash(void); // reflash from embedded images +void c5_passthrough_run(void); // forward host esptool <-> C5 ROM (never returns) +``` + +- `c5_flasher_enter_download()` sends `SPI_ID_SYSTEM_ENTER_DOWNLOAD` over the SPI + bridge; the C5 acks and reboots into ROM serial-download mode. After this the + C5 is no longer running its app (the OTA receiver is gone), so recovery must + continue via `c5_flasher_rom_flash()` or passthrough. +- `c5_flasher_rom_flash()` writes bootloader + partition table + otadata + app - + all embedded in the P4 - and MD5-verifies each region. **Precondition:** the C5 + must already be in ROM download mode (strap it manually, or call + `c5_flasher_enter_download()` first). Returns `ESP_ERR_NOT_FOUND` unless + `C5_ROM_IMAGES_EMBEDDED` is set. +- `c5_passthrough_run()` bridges the P4's console UART (host PC USB-serial) to the + C5 UART0 so the host can run `esptool` directly against the C5 ROM bootloader. + Kills the REPL, disables UART logs and never returns; reboot the P4 (or press + BACK) to exit. + +## UART helpers + +```c +esp_err_t c5_flasher_init(void); // bring up UART1 (only needed for the UART OTA transport) +void c5_flasher_release_uart(void); // delete UART1 + tri-state the C5-UART pins for an external programmer +``` + +## Bridge / status helpers + +```c +esp_err_t c5_flasher_ping(void); // SPI_ID_SYSTEM_PING +esp_err_t c5_flasher_info(void); // SPI_ID_SYSTEM_INFO -> chip model / rev / MAC / free heap +esp_err_t c5_flasher_sync(void); // re-probe the C5 and mark the bridge alive iff it answers a ping +void c5_flasher_progress(uint32_t *sent, uint32_t *total); // live OTA progress for the UI bar +``` + +## Callers + +`bridge_manager` triggers the app OTA (`bridge_manager_force_update()` calls +`c5_flasher_update(NULL, 0, SPI_OTA_TRANSPORT_SPI)`). Version mismatch is +**detection-only** - the user starts the update explicitly (the `c5` console +command); the P4 never auto-flashes. See +[`../bridge_manager/README.md`](../bridge_manager/README.md). ## Symbols -The embedded binary is accessed via: -- `_binary_firmware_c5_bin_start` -- `_binary_firmware_c5_bin_end` + +The ROM-flash fallback references the embedded C5 app image via: +- `_binary_TentacleOS_C5_bin_start` +- `_binary_TentacleOS_C5_bin_end` ## Build Automation -Use the `./tools/build_and_flash.sh` script to ensure the C5 binary is updated and embedded correctly before flashing the P4. + +Use `./tools/build_and_flash.sh` to keep the C5 binary current (SD image and, for +the ROM fallback, the embedded copy) before flashing the P4. diff --git a/docs/console/README.md b/docs/console/README.md index 25203d31b..4afffafa6 100644 --- a/docs/console/README.md +++ b/docs/console/README.md @@ -21,10 +21,14 @@ The prompt `highboy>` indicates the system is ready. | Command | Description | Usage | | :--- | :--- | :--- | | `help` | Lists all available commands. | `help [command]` | -| `free` | Displays available internal and PSRAM memory. | `free` | -| `tasks` | Lists running FreeRTOS tasks and stack usage. | `tasks` | +| `free` | Displays free internal and PSRAM (SPIRAM) memory, current and minimum. | `free` | +| `tasks` | Lists running FreeRTOS tasks (state, priority, stack high-water, num). | `tasks` | +| `stack` | Stack budget report: allocated vs high-water free per task, with reclaim/low flags. | `stack` | +| `date` | Shows the UTC wall-clock (with sync state and source), or sets it. | `date [ ]` | +| `ip` | Shows current network interfaces (IP, Mask, GW, MAC) for STA and AP. | `ip` | | `restart` | Reboots the system. | `restart` | -| `ip` | Shows current network interfaces (IP, Mask, GW, MAC). | `ip` | +| `firstboot` | Clears the first-boot onboarding + screen-tips flags so they run on the next reset. | `firstboot` | +| `capprep` | Skips onboarding (wizard + tips) and restarts clean, for screen capture. | `capprep` | ### File System Commands @@ -55,6 +59,71 @@ The `wifi` command is a wrapper for all wireless functions. | `portscan`| Scans TCP/UDP ports on target. | `-i `: Target IP
`-min`: Start Port
`-max`: End Port | `wifi portscan -i 192.168.1.1` | | `status` | Shows active attacks and state. | None | `wifi status` | +### BadUSB Commands (`badusb`) + +HID injection via DuckyScript. Hint: `badusb ...`. + +| Subcommand | Description | Arguments | Example | +| :--- | :--- | :--- | :--- | +| `run` | Runs a DuckyScript. | `-a `: internal asset (under `bad_usb_scripts/`)
`-f `: script from SD card | `badusb run -a rickroll.txt` | +| `type` | Types a literal string over HID. | `` | `badusb type hello world` | +| `layout` | Sets the keyboard layout. | `` | `badusb layout abnt2` | +| `stop` | Aborts the running script (stops at the next line). | None | `badusb stop` | +| `status` | Shows whether the USB host has mounted the device. | None | `badusb status` | + +### Host Link Commands (`hostlink`) + +Companion (BLE) host-link pairing and status. + +| Subcommand | Description | Arguments | Example | +| :--- | :--- | :--- | :--- | +| `psk` | Shows the pairing PSK to provision the companion app. | None | `hostlink psk` | +| `regen` | Generates a new PSK (invalidates existing pairings), then prints it. | None | `hostlink regen` | +| `ble` | Turns companion BLE advertising on/off. | `` | `hostlink ble on` | +| `status` | Shows the companion session (authenticated) and BLE connection state. | None | `hostlink status` | + +### C5 Co-processor Commands (`c5`) + +Manages the C5 radio co-processor over the SPI bridge (and UART for recovery). + +| Subcommand | Description | Example | +| :--- | :--- | :--- | +| `ota ` | Pushes the C5 image; control on SPI, image bytes over SPI (default) or UART. | `c5 ota spi` | +| `ping` | Pings the running C5 over the SPI bridge. | `c5 ping` | +| `info` | Reads the C5 chip info over the SPI bridge. | `c5 info` | +| `sync` | Re-probes and reconnects the bridge (e.g. after a C5 reboot). | `c5 sync` | +| `download` | Asks the running C5 to enter ROM download mode (over SPI). | `c5 download` | +| `rom` | Serial-flashes a C5 already in download mode (recovery). | `c5 rom` | +| `passthrough` | Bridges host `esptool` to the C5 (never returns; reboot/BACK exits). | `c5 passthrough` | +| `release` | Tri-states the C5 UART lines for an external programmer. | `c5 release` | + +### Infrared Commands (`ir`) + +RMT-backed IR receive/transmit. The `rx`/`send` channels are brought up on demand and released after each operation; `flash` stays on until turned off. + +| Subcommand | Description | Arguments | Example | +| :--- | :--- | :--- | :--- | +| `rx` | Waits for one IR frame and decodes it. | `[timeout_ms]` (default 5000) | `ir rx 10000` | +| `send` | Transmits a code (address/command accept `0x` hex). | ` [repeat]` | `ir send NEC 0x00 0x45` | +| `flash` | Drives the IR emitter at full power (torch) / off. | `` | `ir flash on` | + +### Hardware / Diagnostics + +| Command | Description | Usage | +| :--- | :--- | :--- | +| `battery` | Shows battery/charger (BQ25896) status: SoC (raw + smoothed), voltage, charge state, VBUS source and limits, faults. | `battery` | +| `i2cscan` | Scans the I2C bus (`0x08`-`0x77`) and lists responding addresses. | `i2cscan` | + +### UI / Screen Commands + +Debug/capture helpers registered as top-level commands (from the screen command group). Used for automated screenshotting and UI navigation. + +| Command | Description | Usage | +| :--- | :--- | :--- | +| `goto` | Switches the UI to a screen by enum id. | `goto ` (1..`SCREEN_COUNT`-1) | +| `key` | Injects a simulated button press. | `key [-t ]`
idx: 0=UP 1=DOWN 2=LEFT 3=RIGHT 4=OK 5=BACK; hold default 120 ms | +| `screenshot` | Captures the active screen as base64 RGB565 strips. | `screenshot [id]` (optionally switch to `id` first) | + ## Developing New Commands To add a new command to the console, follow these steps: diff --git a/docs/drv2605l/README.md b/docs/drv2605l/README.md new file mode 100644 index 000000000..f476de210 --- /dev/null +++ b/docs/drv2605l/README.md @@ -0,0 +1,84 @@ +# DRV2605L Haptic Driver + +This component is a minimal driver for the Texas Instruments DRV2605L haptic motor driver over I2C. It uses the chip's internal ROM waveform library for named effects and exposes RTP (real-time playback) for variable-intensity feedback. The driver is tuned for an ERM (eccentric rotating mass) actuator. + +## Overview + +- **Location:** `components/Drivers/drv2605l/` (firmware_p4 only) +- **Header:** `include/drv2605l.h` +- **Source:** `drv2605l.c` +- **Dependencies:** `i2c_init`, `pins` (`pin_def.h`), `driver/gpio`, `freertos` +- **Interface:** I2C master bus `I2C_NUM_0` (via `i2c_init`, shared), 100 kHz standard-mode +- **I2C address:** `0x5A` (`DRV2605L_I2C_ADDR`, 7-bit) +- **Enable line:** per the header, EN is on GPIO37 (the driver itself does not toggle it) +- **Effect library:** internal ROM library `TS2200 Library A` (`LIB_SEL = 0x01`) + +## Configuration Applied at Init + +`drv2605l_init()` adds the device to the shared bus, reads the status register (deriving `DEVICE_ID` from bits [7:5]), and programs: + +- Mode: internal-trigger (`0x00`) +- Library select: TS2200 Library A (`0x01`) +- Rated voltage: `0x90` (ERM), overdrive clamp: `0xCC` (ERM) +- Feedback control `0x35`, Control1 `0x93`, Control2 `0xF5`, Control3 `0xA0` + +Auto-calibration is not run at boot (it is opt-in via `drv2605l_autocal()`) so it never regresses the working manual tune. + +## API Reference + +### Initialization + +#### `drv2605l_init` +```c +esp_err_t drv2605l_init(void); +``` +Initializes the driver as described above. Returns `ESP_OK` on success, otherwise the failing `esp_err_t`. Marks the driver ready; all other calls return `ESP_ERR_INVALID_STATE` until it succeeds. + +#### `drv2605l_device_id` +```c +uint8_t drv2605l_device_id(void); +``` +Last-read `DEVICE_ID` register value (0 if not yet probed). + +### Playback + +#### `drv2605l_play_effect` +```c +esp_err_t drv2605l_play_effect(uint8_t effect); +``` +Plays a single ROM library waveform effect (1..123) once. Leaves RTP mode first if active, writes the effect into waveform-sequence slot 0 with a terminator in slot 1 (skipping the write when the effect is unchanged from the last call), then strobes GO. + +#### `drv2605l_stop` +```c +esp_err_t drv2605l_stop(void); +``` +Stops any currently-playing waveform (clears GO). + +#### `drv2605l_set_rtp` +```c +esp_err_t drv2605l_set_rtp(uint8_t intensity); +``` +Enters RTP mode and applies an intensity value (useful range 0..127 for forward drive). + +### Calibration + +#### `drv2605l_autocal` +```c +esp_err_t drv2605l_autocal(void); +``` +Runs ERM auto-calibration (MODE `0x07`) against the actuator, using the rated/overdrive/feedback registers already programmed at init, then returns to internal-trigger mode. Blocks up to ~1.5 s while polling GO. Returns `ESP_OK` if `DIAG_RESULT` passed (status bit 3 clear), else `ESP_FAIL`. Opt-in; not run at boot. + +## Key Config / Tunables + +| Macro | Location | Value | Meaning | +|-------|----------|-------|---------| +| `DRV2605L_I2C_ADDR` | `drv2605l.h` | `0x5A` | I2C 7-bit address | +| `MODE_INTERNAL_TRIG` | `drv2605l.c` | `0x00` | Internal-trigger playback mode | +| `MODE_AUTO_CAL` | `drv2605l.c` | `0x07` | Auto-calibration mode | +| `MODE_RTP` | `drv2605l.c` | `0x05` | Real-time playback mode | +| `LIB_TS2200_A` | `drv2605l.c` | `0x01` | TS2200 Library A (ERM) | +| `RATED_V_ERM` | `drv2605l.c` | `0x90` | Rated voltage (ERM) | +| `OD_CLAMP_ERM` | `drv2605l.c` | `0xCC` | Overdrive clamp (ERM) | +| `I2C_TIMEOUT_MS` | `drv2605l.c` | 50 | Per-transfer timeout | +| `AUTOCAL_POLL_INTERVAL_MS` | `drv2605l.c` | 10 | Auto-cal GO poll interval | +| `AUTOCAL_POLL_MAX` | `drv2605l.c` | 150 | Max auto-cal poll iterations (~1.5 s) | diff --git a/docs/gameboy/README.md b/docs/gameboy/README.md new file mode 100644 index 000000000..33337bb9e --- /dev/null +++ b/docs/gameboy/README.md @@ -0,0 +1,166 @@ +# Game Boy Emulator Application + +This component is a Game Boy (DMG) emulator built on the single-header +[Peanut-GB](https://github.com/deltabeard/Peanut-GB) core plus a HighBoy +platform layer. It runs `.gb` / `.gbc` ROMs from the SD card, takes over the +ST7789 panel in landscape, plays audio through the I2S codec, and persists +battery-backed cartridge RAM back to the SD card. It is reached from the games +menu through a ROM picker screen. + +## Overview + +- **Emulator location:** `components/Applications/gameboy/` +- **ROM picker location:** `components/Applications/ui/screens/games/gb_ui.c` +- **Core:** Peanut-GB (`peanut_gb.h`, single-header) + `minigb_apu` (APU) +- **Dependencies:** `Drivers`, `Service`, `lvgl`, `esp_lcd`, `esp_timer`, `esp_system`, `driver` +- **Build flags:** `ENABLE_SOUND=1`, `MINIGB_APU_AUDIO_FORMAT_S16SYS`, compiled `-O2 -w` +- **Native GB resolution:** 160 x 144 (`LCD_WIDTH` x `LCD_HEIGHT`) +- **Output resolution:** 320 x 240 (full screen, landscape) + +## Architecture + +``` +┌──────────────────────────────────────────────────────────┐ +│ ROM Picker (gb_ui.c) │ +│ Scans SD recursively, lists ROMs, launches emulator, │ +│ polls highboy_gb_finished(), restores panel + returns │ +└───────────────────────────┬──────────────────────────────┘ + │ highboy_gb_start(path) + ▼ +┌──────────────────────────────────────────────────────────┐ +│ HighBoy Platform (gb_highboy.c) │ +│ │ +│ ┌────────────┐ ┌──────────────┐ ┌────────────────┐ │ +│ │ gb_main │ │ Peanut-GB │ │ gb_audio │ │ +│ │ task │──▶│ core │ │ task │ │ +│ │ (SYS_CORE_ │ │ (peanut_gb.h)│ │ (SYS_CORE_ │ │ +│ │ UI) │ └──────┬───────┘ │ RADIO) │ │ +│ └─────┬──────┘ │ └───────┬────────┘ │ +│ │ rom/cram callbacks │ │ +│ ▼ ▼ ▼ │ +│ ┌──────────┐ ┌────────────────┐ ┌────────────────┐ │ +│ │ scale + │ │ CRAM save/load │ │ minigb_apu │ │ +│ │ blit │ │ .sav (SD) │ │ 32768 Hz stereo│ │ +│ │ (strips) │ └────────────────┘ │ -> mono I2S │ │ +│ └────┬─────┘ └────────────────┘ │ +└───────┼──────────────────────────────────────────────────┘ + ▼ + ST7789 panel (esp_lcd_panel_draw_bitmap, under LVGL lock) +``` + +## ROM Picker (`gb_ui.c`) + +`ui_gb_open()` builds an LVGL list of the ROMs found on the SD card. + +- **Where ROMs come from:** `scan_dir()` walks `/sdcard` recursively to a depth + of `MAX_DEPTH` (3), collecting up to `MAX_ROMS` (64) files whose name ends in + `.gb` or `.gbc` (case-insensitive). Dot-files are skipped. Path and name + tables live in PSRAM (`EXT_RAM_BSS_ATTR`). +- **Navigation:** a repeating LVGL timer (`NAV_MS` = 80 ms) reads buttons. UP / + DOWN move the selection, OK launches the highlighted ROM, BACK / LEFT return + to `SCREEN_GAMES_MENU`. With no ROMs found, the screen shows a "copy games + anywhere on the SD card" message. +- **Launch / return handshake:** OK calls `highboy_gb_start(path)` and sets a + `launched` flag. The nav timer then polls `highboy_gb_finished()`; once the + emulator has torn down it restores the panel orientation + (`lcd_set_rotation(lcd_get_rotation())`) and switches back to the games menu - + no firmware reboot. + +## Emulator Core (`gb_highboy.c`) + +### Public API (`gb_highboy.h`) + +```c +void highboy_gb_start(const char *rompath); +bool highboy_gb_finished(void); +``` + +- `highboy_gb_start` resets state and spawns the emulator task. `rompath` is the + full SD path of the ROM, or `NULL`/`""` to auto-discover the first ROM. +- `highboy_gb_finished` returns `true` only once the task has fully torn down and + released the panel; it is polled by the ROM picker. + +### Tasks + +| Task | Stack | Priority | Core | +|------|-------|----------|------| +| `gameboy` (main loop) | 32768 | `SYS_PRIO_SERVICE_HI` | `SYS_CORE_UI` | +| `gb_audio` | 4096 | `SYS_PRIO_SERVICE_HI` | `SYS_CORE_RADIO` | + +Both tasks and their buffers are created with `MALLOC_CAP_SPIRAM` caps. + +### ROM loading and save RAM + +- If no path was supplied, `find_gb_rom()` scans `/sdcard`, `/sdcard/gb`, and + `/sdcard/roms` for the first `.gb`/`.gbc` file. +- The full ROM is read into PSRAM. `gb_init()` wires the `rom_read` / `cram_read` + / `cram_write` callbacks. +- Cartridge RAM size comes from `gb_get_save_size_s()`. The save file path is the + ROM path with its extension replaced by `.sav`, next to the ROM on the SD card. + CRAM is loaded on start (size must match or it is ignored) and written back + whenever it has been dirty for `GB_AUTOSAVE_MS` (3 s), plus once more on exit. + +### Display integration + +- The GB frame is written by the core into an 8-bit shade buffer + (160 x 144, PSRAM). The DMG palette is four classic greens + (`0xE0F8D0`, `0x88C070`, `0x346856`, `0x081820`) converted to big-endian + RGB565 (`to565be`). +- Scaling to 320 x 240 is nearest-neighbor via precomputed `s_sx` / `s_sy` + lookup tables (no interpolation). +- Output is pushed in 24-row strips (`GB_STRIP_ROWS`) through + `esp_lcd_panel_draw_bitmap()`, each strip followed by `lvgl_glue_wait_flush()`. +- The emulator takes exclusive control of the panel: it holds the LVGL lock + (`lvgl_glue_lock(-1)`), enters direct-draw mode (`lvgl_glue_direct_begin()`), + and rotates to landscape with `esp_lcd_panel_swap_xy(true)` + + `esp_lcd_panel_mirror(true, false)`. The strip DMA buffer is the only buffer in + internal DMA RAM (`MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL`). +- `ui_render_beat_kick()` is called every loop iteration so `sys_monitor` sees + the render path as alive while the standard LVGL render loop is suspended. + +### Audio integration + +- `minigb_apu` renders at `AUDIO_SAMPLE_RATE` (32768 Hz). The `gb_audio` task + pulls stereo interleaved samples, downmixes to mono, and writes them to the + I2S stream (`audio_i2s_stream_*`). Sample buffers are allocated in PSRAM. +- If the APU or its buffers cannot be allocated, the emulator runs silently. + +### Frame pacing + +The loop targets 60 fps (`GB_FRAME_US`). When it falls behind it sets the core's +`frame_skip` and skips the blit; a blit happens at most once every two frames +(so the panel refresh is capped near 30 fps). If it drifts more than +`GB_RESYNC_US` (250 ms) behind, the frame clock is resynced. + +### Controls / input mapping + +Input is polled from the GPIO buttons each frame. Because the panel is rotated +to landscape, the D-pad is remapped accordingly: + +| Physical button | Game Boy input | +|-----------------|----------------| +| RIGHT | D-pad UP | +| LEFT | D-pad DOWN | +| UP | D-pad LEFT | +| DOWN | D-pad RIGHT | +| OK | A | +| BACK | B | +| OK + BACK | START | +| hold BACK ~1.2 s | exit emulator | + +### Teardown + +On exit the task saves CRAM, stops the audio task and I2S stream, drains DMA, +frees all buffers, leaves direct-draw mode, sets the finished flag, releases the +LVGL lock, and deletes itself. If the core buffers cannot be allocated at start, +the task calls `esp_restart()` rather than continuing. + +## Performance / PSRAM notes + +- ROM image, the `gb_s` state struct, the shade framebuffer, the APU context, and + cartridge RAM all live in PSRAM (`MALLOC_CAP_SPIRAM`); only the 320 x 24 strip + buffer is in internal DMA RAM. +- The core is performance-critical, so the whole component is built at `-O2` with + warnings silenced (`-w`). +- The main task uses a large 32 KB stack; the emulator pins to the UI core and + the audio task to the radio core (see `sys_prio.h`). diff --git a/docs/i2c_init/README.md b/docs/i2c_init/README.md new file mode 100644 index 000000000..04957ed50 --- /dev/null +++ b/docs/i2c_init/README.md @@ -0,0 +1,107 @@ +# I2C Master Bus Init + +This component brings up the shared I2C master bus used by every on-board I2C +device (charger, haptic, LED, etc.). The two firmwares are at different stages of +the ESP-IDF I2C driver migration: P4 uses the new `driver/i2c_master.h` bus-object +API and adds stuck-bus recovery (at init and reactively at runtime), while C5 still +uses the legacy `driver/i2c.h` driver with a simpler init and no recovery. + +# P4 + +Shared I2C master bus on the new `i2c_master.h` driver, with stuck-bus recovery. + +## Overview + +- **Location:** `components/Drivers/i2c_init/` +- **Header:** `include/i2c_init.h` +- **Driver:** `driver/i2c_master.h` (new bus-object API) +- **Port:** `I2C_NUM_0` +- **Pins:** SDA `GPIO_I2C_SDA_PIN` (GPIO 31), SCL `GPIO_I2C_SCL_PIN` (GPIO 30), from `pin_def.h` +- **Speed:** `I2C_MASTER_FREQ_HZ` = 100 kHz (standard mode) +- **Dependencies:** `pins`, `driver/gpio`, `esp_rom_sys` + +## Bus / Hardware Notes + +The bus runs at 100 kHz standard mode because the V2 board pulls SDA/SCL up with +10k (R12/R13/R14 to 3.3VF) plus 22R series (R9/R10): too weak for 400 kHz +fast-mode (rise time can't reach VIH in a bit period), which is why the BQ25896 +NACKs at 400 kHz. Bus config uses `I2C_CLK_SRC_DEFAULT`, `glitch_ignore_cnt = 7`, +and internal pull-ups enabled. + +## Recovery Mechanisms + +**Stuck-bus recovery at init.** Before the I2C driver claims the pins, `init_i2c()` +runs `bus_recover()`: it drives SCL as open-drain and reads SDA. If a slave is +holding SDA low (stuck mid-transfer after a partial reset or power glitch), it +bit-bangs up to `I2C_RECOVER_CLOCKS` (9) SCL pulses at ~100 kHz +(`I2C_RECOVER_HALF_US` = 5 us half period) to walk the slave past its ACK, then +issues a STOP (SDA rising while SCL is high). It is a no-op when SDA is already +released. This keeps a stuck bus from leaving the charger/haptic/LED unreachable +for the whole session. + +**Reactive recovery at runtime.** Consumers that see repeated transfer failures +call `i2c_bus_recover()`, which calls `i2c_master_bus_reset()` on the shared bus +and increments the recovery counter. `i2c_recover_count()` exposes that counter +for `sys_monitor` health reporting. + +## API Reference + +#### `init_i2c` +```c +esp_err_t init_i2c(void); +``` +Runs the init-time stuck-bus recovery sequence, then creates the I2C master bus on +`I2C_NUM_0`. Returns `ESP_OK`, or the failing `esp_err_t`. + +#### `i2c_get_bus` +```c +i2c_master_bus_handle_t i2c_get_bus(void); +``` +Returns the shared master bus handle; each device driver adds itself to it via +`i2c_master_bus_add_device()`. + +#### `i2c_bus_recover` +```c +esp_err_t i2c_bus_recover(void); +``` +Resets a wedged bus at runtime (reactive recovery) and increments the recovery +counter. Returns `ESP_ERR_INVALID_STATE` if the bus is not initialized, otherwise +the result of `i2c_master_bus_reset()`. + +#### `i2c_recover_count` +```c +uint32_t i2c_recover_count(void); +``` +Number of runtime bus recoveries performed since boot, for `sys_monitor` health. + +--- + +# C5 + +Shared I2C master bus on the legacy `driver/i2c.h` driver. No bus recovery and no +recovery counter; the bus handle is not exposed. + +## Overview + +- **Location:** `components/Drivers/i2c_init/` +- **Header:** `include/i2c_init.h` +- **Driver:** `driver/i2c.h` (legacy driver) +- **Port:** `I2C_NUM_0` +- **Pins:** SDA `GPIO_I2C_SDA_PIN` (GPIO 8), SCL `GPIO_I2C_SCL_PIN` (GPIO 9), from `pin_def.h` +- **Speed:** `I2C_MASTER_FREQ_HZ` = 400 kHz (fast mode) +- **Dependencies:** `pins`, `driver/i2c` + +## Configuration + +Configured as `I2C_MODE_MASTER` with SDA/SCL internal pull-ups enabled and a +400 kHz clock. Init calls `i2c_param_config()` then `i2c_driver_install()` with no +RX/TX buffers. + +## API Reference + +#### `init_i2c` +```c +void init_i2c(void); +``` +Configures and installs the legacy I2C master driver on `I2C_NUM_0`. Logs and +returns early on failure; no value is returned. diff --git a/docs/ir/README.md b/docs/ir/README.md new file mode 100644 index 000000000..fcf3dfea7 --- /dev/null +++ b/docs/ir/README.md @@ -0,0 +1,223 @@ +# IR TX/RX Service + +This component provides the infrared transmit/receive service for TentacleOS on the ESP32-P4. It drives the IR emitter and detector through the RMT peripheral, ships a full protocol decode/encode library (consumer remotes and air-conditioner state frames), a Flipper-Zero-compatible file format, a console command, and a full-power torch (flashlight) mode. + +## Overview + +- **Location:** `firmware_p4/components/Service/ir/` +- **Target:** `firmware_p4` only (ESP32-P4) +- **Main header:** `include/ir.h` +- **Companion headers:** `include/ir_protocol.h` (remote protocols), `include/ir_ac.h` (air-conditioner protocols), `include/ir_file.h` (Flipper IR file format) +- **Dependencies:** `pin_def`, `led_control`, `driver/rmt_tx`, `driver/rmt_rx`, `driver/rmt_encoder`, `driver/gpio`, `freertos` +- **Peripheral:** RMT (one RX channel + one TX channel), non-DMA +- **Console command:** `ir` (registered in `Service/console/commands/cmd_ir.c`) + +The service is brought up lazily - there is no boot-time init. Callers init the RX or TX channel on demand and release it when done, so RMT channels are not held for the whole session. + +## Hardware / Bus + +| Item | Value | Source | +|------|-------|--------| +| TX pin | `GPIO_IR_TX_PIN` = GPIO 0 | `pin_def.h` | +| RX pin | `GPIO_IR_RX_PIN` = GPIO 1 | `pin_def.h` | +| RMT resolution | 1 MHz (1 tick = 1 us) | `IR_RMT_RESOLUTION_HZ` | +| TX carrier duty | 0.33 | `IR_CARRIER_DUTY_CYCLE` | +| Default carrier | 38 kHz (protocol-dependent) | `ir_carrier_freq()` | + +The emitter is infrared and invisible to the eye: verify output with a phone camera. The TX pin rests low via an external pull-down (R90). + +### RMT tuning constants (`ir.h`) + +| Constant | Value | Meaning | +|----------|-------|---------| +| `IR_RMT_MEM_SYMBOLS` | 128 | Encode buffer size for a single frame | +| `IR_MAX_SYMBOLS` | 512 | User RX/last-frame buffer depth | +| `IR_RX_MEM_BLOCK_SYMBOLS` | 96 | Non-DMA RX FIFO depth (2x the P4 48-word block) | +| `IR_TX_MEM_BLOCK_SYMBOLS` | 64 | TX channel FIFO (2 P4 channels) | +| `IR_RX_MIN_NS` / `IR_RX_MAX_NS` | 1250 / 12000000 | RX glitch/timeout window | +| `IR_TX_QUEUE_DEPTH` | 4 | TX transaction queue depth | +| `IR_TX_WAIT_MS` | 1000 | Blocking wait for a transmit to finish | +| `IR_PRINT_MAX_SYMBOLS` | 40 | Cap on symbols logged by `ir_print_raw` | + +RX and TX are non-DMA on this board: DMA-backed `rmt_receive()` failed here, so the RX channel uses a ping-ponged FIFO into the `IR_MAX_SYMBOLS` user buffer. + +## API Reference + +### Channel lifecycle + +#### `ir_rx_init` / `ir_rx_deinit` +```c +esp_err_t ir_rx_init(void); +void ir_rx_deinit(void); +``` +`ir_rx_init` creates the RMT RX channel on `GPIO_IR_RX_PIN`, its event queue and callback, enables it, and arms a one-shot receive. Returns `ESP_OK` (idempotent if already inited), `ESP_ERR_NO_MEM` on mutex/queue failure, or an RMT error. `ir_rx_deinit` disables and deletes the channel and queue (no-op if RX was never inited). + +#### `ir_rx_prime` +```c +void ir_rx_prime(void); +``` +Resets the RX queue (drops a stale/ambient frame) and re-arms the one-shot receive if a prior frame left it idle. Call at the start of every capture: a one-shot `rmt_receive()` is consumed by every frame, including NEC repeat frames, so without priming the next `ir_receive()` could wait forever. + +#### `ir_tx_init` / `ir_tx_deinit` +```c +esp_err_t ir_tx_init(void); +void ir_tx_deinit(void); +``` +`ir_tx_init` creates the RMT TX channel on `GPIO_IR_TX_PIN`, a copy encoder, and enables the channel. Returns `ESP_OK` (idempotent) or an RMT error. `ir_tx_deinit` disables and deletes the channel and encoder and resets the cached carrier (no-op if TX was never inited). + +### Receive / decode + +#### `ir_receive` +```c +esp_err_t ir_receive(ir_data_t *out_data, uint32_t timeout_ms); +``` +Waits up to `timeout_ms` for a frame, snapshots the raw symbols (for `ir_get_last_raw`), decodes with `ir_decode`, and re-arms the channel. Signals the RGB LED (info on decode, warning on timeout/undecoded). Returns `ESP_OK` on decode, `ESP_ERR_TIMEOUT` if nothing arrived, `ESP_ERR_NOT_FOUND` if a frame arrived but did not decode, or an RMT error. + +#### `ir_get_last_raw` +```c +esp_err_t ir_get_last_raw(rmt_symbol_word_t *out_buf, size_t buf_max, size_t *out_count); +``` +Copies the raw RMT symbols from the most recent received frame (mutex-guarded). `out_count` may be NULL. Returns `ESP_ERR_INVALID_ARG` on a NULL/zero buffer, `ESP_ERR_TIMEOUT` if the mutex could not be taken. + +### Transmit / encode + +#### `ir_send` +```c +esp_err_t ir_send(const ir_data_t *data); +``` +Encodes `data` with `ir_encode` and transmits it at the protocol's carrier frequency. Requires `ir_tx_init`. Returns `ESP_ERR_INVALID_ARG` if encoding produced no symbols, else the transmit result. + +#### `ir_send_raw` +```c +esp_err_t ir_send_raw(const rmt_symbol_word_t *symbols, size_t count, uint32_t carrier_hz); +``` +Transmits a raw symbol array. Pass `carrier_hz = 0` to disable the carrier. Returns `ESP_ERR_INVALID_ARG` on a NULL/empty array. + +### Torch / flashlight + +#### `ir_flash_on` / `ir_flash_off` +```c +esp_err_t ir_flash_on(void); +esp_err_t ir_flash_off(void); +``` +`ir_flash_on` drives the emitter fully on as DC with no carrier: it releases the RMT TX channel (`ir_tx_deinit`) so it does not fight for the pin, reconfigures `GPIO_IR_TX_PIN` as a plain output, and holds it high at full power. This is continuous full-power drive - use it for tests or short bursts, do not leave it on long. `ir_flash_off` drives the pin low again (a later `ir_tx_init` re-routes it back to RMT). Both return `ESP_OK` or a GPIO driver error. + +### Logging helpers + +```c +void ir_print_raw(const rmt_symbol_word_t *symbols, size_t count); // DEBUG level, capped at IR_PRINT_MAX_SYMBOLS +void ir_print_data(const ir_data_t *data); // INFO level: protocol, address, command, repeat +``` + +## Decoded Frame (`ir_data_t`) + +```c +typedef struct { + ir_protocol_t protocol; + uint32_t address; + uint32_t command; + bool repeat; +} ir_data_t; +``` + +## Protocol Library (`ir_protocol.h`) + +The library decodes and encodes consumer-remote protocols. `ir_decode` tries all known protocols against a symbol buffer; `ir_encode` produces symbols for a given `ir_data_t`. Pulse matching uses `IR_TOLERANCE` (25%) with a strict `IR_TOLERANCE_STRICT` (6%) for protocols whose preambles are close (Pioneer vs NEC). + +| Protocol (`ir_protocol_t`) | Source | Carrier | +|----------------------------|--------|---------| +| `IR_PROTO_NEC` | `ir_protocol_nec.c` | 38 kHz | +| `IR_PROTO_NEC42` | `ir_protocol_nec42.c` | 38 kHz | +| `IR_PROTO_SAMSUNG` | `ir_protocol_samsung.c` | 38 kHz | +| `IR_PROTO_SONY` | `ir_protocol_sony.c` | 40 kHz | +| `IR_PROTO_RC5` | `ir_protocol_rc5.c` | 36 kHz | +| `IR_PROTO_RC6` | `ir_protocol_rc6.c` | 36 kHz | +| `IR_PROTO_RCA` | `ir_protocol_rca.c` | 38 kHz | +| `IR_PROTO_JVC` | `ir_protocol_jvc.c` | 38 kHz | +| `IR_PROTO_LG` | `ir_protocol_lg.c` | 38 kHz | +| `IR_PROTO_DENON` | `ir_protocol_denon.c` | 38 kHz | +| `IR_PROTO_PANASONIC` | `ir_protocol_panasonic.c` | 37 kHz | +| `IR_PROTO_PIONEER` | `ir_protocol_pioneer.c` | 40 kHz | + +Carrier constants: `IR_CARRIER_HZ_DEFAULT` (38 kHz - NEC, Samsung, LG, JVC, Denon and others), `IR_CARRIER_HZ_RC5_RC6` (36 kHz), `IR_CARRIER_HZ_SONY` (40 kHz), `IR_CARRIER_HZ_PANASONIC` (37 kHz), `IR_CARRIER_HZ_PIONEER` (40 kHz). Use `ir_carrier_freq(proto)` to resolve a protocol's carrier and `ir_protocol_name(proto)` for its display name. + +### Low-level codec building blocks + +The pulse-distance and pulse-width primitives that the per-protocol files build on are also public: + +```c +uint64_t ir_decode_pulse_distance(const rmt_symbol_word_t *symbols, size_t offset, + size_t num_bits, const ir_pulse_distance_cfg_t *cfg); +uint64_t ir_decode_pulse_width(const rmt_symbol_word_t *symbols, size_t offset, + size_t num_bits, const ir_pulse_width_cfg_t *cfg); +size_t ir_encode_pulse_distance(rmt_symbol_word_t *symbols, uint64_t data, + size_t num_bits, const ir_encode_distance_cfg_t *cfg); +size_t ir_encode_pulse_width(rmt_symbol_word_t *symbols, uint64_t data, + size_t num_bits, const ir_encode_width_cfg_t *cfg); +bool ir_match(uint32_t measured_us, uint32_t expected_us); +bool ir_match_tol(uint32_t measured_us, uint32_t expected_us, uint32_t tol_percent); +``` + +## Air-Conditioner Library (`ir_ac.h`) + +Unlike remote protocols, each AC frame carries the full appliance state (power, mode, temperature, fan) rather than a single command. The full state is captured in `ir_ac_state_t`: + +```c +typedef struct { + ir_ac_protocol_t protocol; + bool power; + ir_ac_mode_t mode; // AUTO, COOL, DRY, HEAT, FAN + uint8_t temp_c; // clamped to the target protocol's range at encode time + ir_ac_fan_t fan; // AUTO, LOW, MED, HIGH +} ir_ac_state_t; +``` + +| AC protocol (`ir_ac_protocol_t`) | Source | +|----------------------------------|--------| +| `IR_AC_PROTO_COOLIX` | `ir_ac_coolix.c` | +| `IR_AC_PROTO_GREE` | `ir_ac_gree.c` | +| `IR_AC_PROTO_LG` | `ir_ac_lg.c` | +| `IR_AC_PROTO_MIDEA` | `ir_ac_midea.c` | +| `IR_AC_PROTO_TOSHIBA` | `ir_ac_toshiba.c` | +| `IR_AC_PROTO_HAIER` | `ir_ac_haier.c` | + +```c +size_t ir_ac_encode(const ir_ac_state_t *state, rmt_symbol_word_t *symbols, size_t max); +esp_err_t ir_ac_send(const ir_ac_state_t *state); // requires ir_tx_init() +bool ir_ac_decode(const rmt_symbol_word_t *symbols, size_t count, ir_ac_state_t *out_state); +const char *ir_ac_protocol_name(ir_ac_protocol_t proto); +const char *ir_ac_mode_name(ir_ac_mode_t mode); +const char *ir_ac_fan_name(ir_ac_fan_t fan); +uint32_t ir_ac_carrier_freq(ir_ac_protocol_t proto); +``` + +## Flipper IR File Format (`ir_file.h`) + +Parses and serializes Flipper Zero `.ir` files. A file (`ir_file_t`) holds a grown-on-demand array of `ir_signal_t`, each either a decoded `ir_data_t` or a raw symbol/timing sequence. + +```c +void ir_file_init(ir_file_t *file); +void ir_file_free(ir_file_t *file); +esp_err_t ir_file_parse(const char *content, ir_file_t *file); // append parsed signals +size_t ir_file_to_string(const ir_file_t *file, char *buf, size_t buf_size); +ir_signal_t *ir_file_find(const ir_file_t *file, const char *name); +esp_err_t ir_file_send(const ir_signal_t *signal); // dispatches ir_send_raw / ir_send +esp_err_t ir_file_add_parsed(ir_file_t *file, const char *name, const ir_data_t *data); +esp_err_t ir_file_add_raw(ir_file_t *file, const ir_file_add_raw_cfg_t *cfg); +``` + +Buffer ownership: `ir_file_parse` and `ir_file_add_raw` allocate the raw buffers, and `ir_file_free` releases everything. Limits: `IR_FILE_NAME_MAX` (32), `IR_FILE_PROTO_NAME_MAX` (32), `IR_FILE_LINE_BUF_SIZE` (1024), `IR_FILE_INITIAL_CAP` (8). + +## Console Command + +Registered by `register_ir_commands()` in `Service/console/commands/cmd_ir.c`. The `rx` and `send` subcommands are one-shot: they init the RMT channel on demand and release it when done. `flash` is the exception - it stays on until `flash off`. + +``` +ir rx [timeout_ms] wait for one frame and decode it (default 5000 ms) +ir send [repeat] transmit a code (addr/cmd accept 0x hex) +ir flash drive the emitter at full power (torch) / off +``` + +- `ir rx` calls `ir_rx_init` + `ir_rx_prime`, waits for a frame, prints protocol/address/command (and `[repeat]`), then `ir_rx_deinit`. Reports timeout and undecoded-frame cases distinctly. +- `ir send` resolves the protocol name (case-insensitive), parses `addr`/`cmd` (0x hex accepted), inits TX, sends, then deinits TX. +- `ir flash on` drives the emitter to full power (`ir_flash_on`); `ir flash off` turns it off. The output is infrared - check with a phone camera. diff --git a/docs/led/README.md b/docs/led/README.md new file mode 100644 index 000000000..a7c061e8a --- /dev/null +++ b/docs/led/README.md @@ -0,0 +1,158 @@ +# LED Status Driver + +This component drives the single RGB status LED. The two firmwares use different +hardware, so the driver differs between them: on P4 the LED is a Texas Instruments +**LP5816** I2C current-sink driver, while on C5 it is an addressable RMT LED strip +(single pixel). The P4 build additionally exposes a semantic signal API +(`led_signal_info/warning/error`) with a dedicated blink-off task; the C5 build +only exposes the fixed-color blink helpers. + +# P4 + +RGB status LED backed by the LP5816 4-channel I2C current-sink driver. + +## Overview + +- **Location:** `components/Drivers/led/` +- **Header:** `include/led_control.h` +- **Chip:** Texas Instruments LP5816 (U22), 4-channel I2C current-sink LED driver +- **Interface:** I2C (shared bus via the `i2c_init` component, `i2c_get_bus()`) +- **I2C address:** `0x2C` (7-bit) +- **I2C speed:** `I2C_MASTER_FREQ_HZ` (100 kHz, from `i2c_init.h`) +- **I2C timeout:** 50 ms per transfer +- **Dependencies:** `i2c_init`, `sys_prio`, `esp_timer`, `freertos` + +## Hardware + +The LEDs are common-anode (anode to 3.3V); the LP5816 sinks each cathode. Channel +map (schematic sheet 6): + +| Channel | Color | LED | +| :--- | :--- | :--- | +| OUT0 | Red | D10.1 | +| OUT1 | Green | D10.2 | +| OUT2 | Blue | D10.3 | +| OUT3 | (unused) | D11 | + +Only OUT0..OUT2 (RGB) are enabled. Channels run in manual 8-bit PWM mode with the +dot-current ceiling set to full scale (25.5 mA MAX_CURRENT) for saturated color; +fade and exponential dimming are disabled so PWM updates take effect immediately. + +## Configuration / Tunables + +- `LP5816_ADDR` = `0x2C`: I2C target address. +- `I2C_TIMEOUT_MS` = `50`: per-transfer timeout. +- `OUT_ENABLE_RGB` = `0x07`: enables OUT0..OUT2. +- `LED_DC_LEVEL` = `0xFF`: per-channel dot-current ceiling (full scale). +- `SIGNAL_BLINK_US` = `250000`: semantic-signal flash duration (250 ms). +- Signal defaults: info `0xFF00FF`, warning `0xFFFF00`, error `0xFF0000`, + brightness `10`. These are overridden via `led_set_signal_config()`. + +## Blink-off Task + +Turning the LED off is a blocking I2C write, so it does not run in an +`esp_timer` callback (that task has a ~3.5 KB stack the I2C path overflows, and +blocking there stalls other timers). Instead, the first semantic signal lazily +creates a dedicated task `led_sig` (3072-byte stack, `SYS_PRIO_BACKGROUND`, +pinned to `SYS_CORE_RADIO`). Each signal sets the color and an off-deadline, then +notifies the task; the task sleeps until the (possibly re-armed) deadline and then +clears the LED once. The flash itself is therefore non-blocking to the caller. + +## API Reference + +### Initialization + +#### `led_rgb_init` +```c +esp_err_t led_rgb_init(void); +``` +Assumes the shared I2C bus is already up. Adds the LP5816 to the bus, resets it, +enables the RGB channels in manual PWM mode, and starts with the LED off. Returns +`ESP_OK`, or an `esp_err_t` if the LP5816 does not respond on I2C. + +### Direct Color Control + +```c +void led_set_color(uint8_t r, uint8_t g, uint8_t b); +void led_clear(void); +void led_blink(uint8_t r, uint8_t g, uint8_t b, int duration_ms); +``` +- `led_set_color`: sets the RGB PWM duty (0-255 per channel). +- `led_clear`: turns the LED off (all channels to 0). +- `led_blink`: sets the color, blocks for `duration_ms`, then clears. + +### Fixed-color Blink Helpers + +```c +void led_blink_red(void); // 255,0,0 for 500 ms (error) +void led_blink_green(void); // 0,150,0 for 220 ms (success) +void led_blink_blue(void); // 0,0,255 for 500 ms (info) +void led_blink_purple(void); // 200,0,220 for 500 ms (info) +``` +Each blocks for its duration. + +### Semantic Signal API + +```c +void led_set_signal_config(uint32_t info, uint32_t warning, uint32_t error, int brightness); +void led_signal_info(void); +void led_signal_warning(void); +void led_signal_error(void); +``` +- `led_set_signal_config`: pushes the signal colors (0xRRGGBB) and global + brightness (0-100) in from the Service layer, so the driver never depends on the + config module. The brightness scales each channel before it is written. +- `led_signal_info` / `led_signal_warning` / `led_signal_error`: flash the status + LED once in the configured color, then turn it off. Non-blocking; the off runs on + the dedicated `led_sig` task described above. + +--- + +# C5 + +RGB status LED backed by an addressable RMT LED strip (single pixel), driven +through the `led_strip` component. This is not the LP5816 used on P4, and there is +no I2C, signal API, or dedicated off task. + +## Overview + +- **Location:** `components/Drivers/led/` +- **Header:** `include/led_control.h` +- **Interface:** RMT (single-pixel addressable LED via the `led_strip` component) +- **Data pin:** `GPIO_LED_RGB_PIN` (GPIO 27, from `pin_def.h`) +- **Dependencies:** `led_strip`, `pin_def`, `freertos` + +## Configuration / Tunables + +- `RMT_RESOLUTION_HZ` = `10 MHz`: RMT channel resolution. +- Strip: `max_leds = 1`, `RMT_CLK_SRC_DEFAULT`, DMA disabled. +- Blink colors/durations: + +| Helper | RGB | Duration | +| :--- | :--- | :--- | +| `led_blink_red` | 255,0,0 | 500 ms | +| `led_blink_green` | 0,150,0 | 220 ms | +| `led_blink_blue` | 0,0,255 | 500 ms | +| `led_blink_purple` | 200,0,220 | 500 ms | + +## API Reference + +### Initialization + +#### `led_rgb_init` +```c +void led_rgb_init(void); +``` +Creates the single-pixel RMT LED strip device on `GPIO_LED_RGB_PIN` and clears it. +Uses `ESP_ERROR_CHECK`, so a failure aborts. + +### Fixed-color Blink Helpers + +```c +void led_blink_red(void); +void led_blink_green(void); +void led_blink_blue(void); +void led_blink_purple(void); +``` +Each sets the pixel to its color, refreshes, blocks for the color's duration, then +clears and refreshes. No-op if the strip is not initialized. diff --git a/docs/lvgl/README.md b/docs/lvgl/README.md index b5c7a68a3..004213a65 100644 --- a/docs/lvgl/README.md +++ b/docs/lvgl/README.md @@ -53,8 +53,15 @@ esp_err_t lvgl_glue_init(void); are not ready. 2. **`lvgl_port_init`:** starts the managed LVGL task (calls `lv_init` internally), the periodic tick, and a recursive mutex (the lock). -3. **`lvgl_port_add_disp`:** registers the display - DMA double buffering (20 - lines), RGB565 with byte swap, internal RAM (no PSRAM). +3. **`lvgl_port_add_disp`:** registers the display - a partial DMA double buffer + of `LVGL_BUF_LINES` (`LCD_PANEL_H / 4` = 80) lines in internal DMA-capable + RAM, RGB565 with byte swap. +4. **PSRAM draw buffers:** overrides LVGL's draw and image buffer handlers with + `draw_buf_psram_malloc`, which routes allocations larger than + `DRAWBUF_PSRAM_THRESHOLD` (48 KB) to PSRAM (`MALLOC_CAP_SPIRAM`) and falls + back to the default heap otherwise. This keeps big off-screen / image buffers + off the scarce internal heap. The DMA render double buffer above stays in + internal DMA RAM (PSRAM is not DMA-reachable by the LCD). ### Thread safety ```c @@ -81,6 +88,35 @@ wrap it. > run inside the LVGL task already under the lock. See > [ui: long-running work](../ui/README.md#long-running-work-and-the-watchdog). +### Direct draw (panel takeover) +```c +void lvgl_glue_direct_begin(void); +void lvgl_glue_direct_end(void); +void lvgl_glue_wait_flush(uint32_t timeout_ms); +``` +For a full-screen app (e.g. the Game Boy emulator) that holds the LVGL lock and +drives the ST7789 itself with raw `esp_lcd_panel_draw_bitmap()` calls. The panel +SPI is async - `draw_bitmap` queues the transfer and returns - so reusing one +blit buffer for the next strip while the previous DMA is still reading it +corrupts the image. Between `_begin()` and `_end()` the shared +color-transfer-done ISR raises a semaphore instead of signalling LVGL flush-ready; +the app calls `lvgl_glue_wait_flush()` after each draw to block until that +strip's DMA finished, making single-buffer reuse safe. Call `_begin()` right +after taking the lock and `_end()` before releasing it; outside this window the +ISR drives LVGL exactly as before. + +### Screenshot capture +```c +typedef void (*lvgl_glue_strip_cb_t)( + int32_t x1, int32_t y1, int32_t x2, int32_t y2, const uint8_t *data, int32_t stride); +void lvgl_glue_capture_begin(lvgl_glue_strip_cb_t cb); +void lvgl_glue_capture_end(void); +``` +Tees each rendered strip to `cb` from the LVGL flush path, handing over the +strip's area plus the active buffer in native RGB565 (pre byte-swap). +`_capture_begin(cb)` starts the tee (pass `NULL` to disable); `_capture_end()` +stops it. + ### Rotation ```c bool lvgl_glue_toggle_rotation(void); // returns true if now landscape diff --git a/docs/nfc/README.md b/docs/nfc/README.md new file mode 100644 index 000000000..2f620115d --- /dev/null +++ b/docs/nfc/README.md @@ -0,0 +1,209 @@ +# NFC Application + +The NFC feature has two halves in the tree: + +1. A complete ST25R3916-based NFC stack under + `components/Applications/nfc/` (manager, scanner, reader, listener, device / + store persistence, and a full protocol tree), talking to the + `st25r3916` driver (`components/Drivers/st25r3916/`, "HighBoy NFC"). +2. An on-device UI suite under `components/Applications/ui/screens/nfc/`. + +**Important:** the shipping UI currently runs as a **simulation**. As documented +in `nfc_sim.h`, the ST25R3916 shares the SPI3 bus whose MISO line is tied to +LCD-RST by a board jumper and cannot be read reliably, so the NFC screens use a +faithful simulation model (`nfc_sim`) instead of driving the radio. The protocol +stack and driver are compiled and complete, but the current screens do not call +them (the only wiring is a defensive `nfc_manager_stop` close-hook registered in +`ui_manager.c`). + +## Overview + +- **App location:** `components/Applications/nfc/` +- **UI location:** `components/Applications/ui/screens/nfc/` +- **Driver:** `components/Drivers/st25r3916/` (`highboy_nfc_*`) +- **Target IC:** ST25R3916 / ST25R3916B (SPI mode 1, clock <= 6 MHz) +- **Simulation model:** `ui/screens/nfc/nfc_sim.c` (saved library persisted in NVS) + +## Architecture + +``` +┌──────────────────────────────────────────────────────────┐ +│ UI suite (ui/screens/nfc/*) │ +│ Menu + Read / Identify / Emulate / Write / Saved / │ +│ Config / per-family detail screens │ +│ event-driven input, rotation-aware layout │ +│ │ │ +│ ▼ (currently) nfc_sim model + NVS │ +│ ┌───────────────────────────┐ │ +│ │ nfc_sim: card model + │ │ +│ │ saved library (NVS) │ │ +│ └───────────────────────────┘ │ +└──────────────────────────────────────────────────────────┘ + · · · · · · (not wired in current UI) · · · · · · · · · +┌──────────────────────────────────────────────────────────┐ +│ NFC stack (Applications/nfc/) │ +│ manager (state machine + scan task) │ +│ scanner · reader · listener (emulation) │ +│ device / store (NVS card profiles + entries) │ +│ protocols/ (see table below) │ +└───────────────────────────┬──────────────────────────────┘ + ▼ +┌──────────────────────────────────────────────────────────┐ +│ st25r3916 driver (highboy_nfc, SPI2/SPI3) │ +│ core · fifo · irq · aat + HAL (gpio / spi / timer) │ +└──────────────────────────────────────────────────────────┘ +``` + +## UI Suite (`ui/screens/nfc/`) + +`ui_nfc_menu_open()` (`nfc_menu_ui.c`) builds the NFC menu. Selecting an item +switches to the matching screen via the `screen_id_t` enum in `ui_manager.h`. + +| Menu item | Screen | File | +|-----------|--------|------| +| READ TAGS | `SCREEN_NFC_READ` | `nfc_read_ui.c` | +| SCAN / IDENTIFY | `SCREEN_NFC_SCAN` | `nfc_scan_ui.c` | +| EMULATE | `SCREEN_CARD_EMU` | `card_emu_ui.c` | +| WRITE | `SCREEN_NFC_WRITE` | `nfc_write_ui.c` | +| CONFIGURATIONS | `SCREEN_NFC_CONFIG` | `nfc_config_ui.c` | +| SAVED | `SCREEN_NFC_SAVED` | `nfc_saved_ui.c` | +| BANK CARD | `SCREEN_NFC_BANKCARD` | `nfc_bankcard_ui.c` | +| DESFIRE | `SCREEN_NFC_DESFIRE` | `nfc_desfire_ui.c` | +| NFC-V / 15693 | `SCREEN_NFC_ISO15693` | `nfc_iso15693_ui.c` | +| ULTRALIGHT/NTAG | `SCREEN_NFC_ULTRALIGHT` | `nfc_ultralight_ui.c` | +| NDEF | `SCREEN_NFC_NDEF` | `nfc_ndef_ui.c` | +| FELICA | `SCREEN_NFC_FELICA` | `nfc_felica_ui.c` | +| SHARE (P2P) | `SCREEN_NFC_P2P` | `nfc_p2p_ui.c` | +| KEY DICTIONARY | `SCREEN_NFC_KEYDICT` | `nfc_keydict_ui.c` | + +There is also a dedicated emulate screen (`nfc_emulate_ui.c`, +`SCREEN_NFC_EMULATE`) reached from the read flow. All of these screens are driven +by the `nfc_sim` model and LVGL animations; none currently drive the ST25R3916. + +### Event-driven input + +Each screen registers a handler with `ui_input_set_screen_handler()` and reacts +to `input_event_t` events (`INPUT_ACTION_PRESS` / `INPUT_ACTION_REPEAT` for +`INPUT_BTN_UP/DOWN/LEFT/RIGHT/OK/BACK`). The central input pump only calls a +handler while input is unlocked and no modal overlay is up, so screens carry no +per-frame edge-detection of their own. Held UP/DOWN auto-scroll via the REPEAT +action. + +### Rotation-aware layout + +Screens size and place elements with the live logical dimensions from +`ui_metrics.h` (`ui_screen_w()` / `ui_screen_h()`), which follow +`lv_display_set_rotation` rather than the fixed 240 x 320 panel constants, so the +layout survives rotation (e.g. the read screen clamps its dump-list Y against +`ui_screen_h()` and the footer height). + +### Shared visual kit (`nfc_ui_common`) + +`nfc_ui_common.h` provides the common look used by Read / Saved / Write / +Emulate: an accent header + underline (`nfc_ui_header`), a credit-card-style +panel that renders a card (`nfc_ui_card_panel`), an expanding concentric-ring +"broadcasting field" animation (`nfc_ui_field_create` / `nfc_ui_field_tick`), and +fire-and-forget speaker cues (`nfc_ui_play_sound`: a rising blip on tag found, a +tick on save). The Identify screen animates a per-technology checklist +(NFC-A / NFC-B / NFC-F / NFC-V). + +### Simulation model (`nfc_sim`) + +`nfc_sim.c` is the shared card record (`nfc_sim_card_t`: name, type, UID, ATQA, +SAK) plus a saved "library" (up to `NFC_SIM_MAX_SAVED` = 16 cards) persisted in +NVS and seeded with presets on first run. It can synthesize a random discovered +tag (`nfc_sim_random_card`), build a card from a specific template +(`nfc_sim_make_card`), and format UIDs as `DE:AD:BE:EF`. + +## NFC Stack (`Applications/nfc/`) + +The stack is a full reader/emulator implementation that targets the ST25R3916. + +### Manager (`nfc_manager`) + +A scan-task state machine over the driver: + +```c +typedef enum { + NFC_MANAGER_STATE_IDLE, NFC_MANAGER_STATE_SCANNING, + NFC_MANAGER_STATE_READING, NFC_MANAGER_STATE_EMULATING, + NFC_MANAGER_STATE_ERROR, NFC_MANAGER_STATE_COUNT +} nfc_manager_state_t; + +hb_nfc_err_t nfc_manager_start(nfc_manager_card_found_cb_t cb, void *ctx); +void nfc_manager_stop(void); +nfc_manager_state_t nfc_manager_get_state(void); +``` + +Hardware must be pre-initialized with `highboy_nfc_init()`. The card-found +callback delivers an `hb_nfc_card_data_t`. + +### Scanner / Reader / Listener + +- `nfc_scanner` (`nfc_scanner_alloc/start/stop`) polls the field and reports the + detected protocol list (`nfc_scanner_event_t`, up to + `NFC_SCANNER_MAX_PROTOCOLS` = 4). +- `nfc_reader` implements the concrete read/write flows: + `mf_classic_read_full()` (dumps all sectors into the global emulation card + `s_emu_card`), `mf_classic_write_all()` (writes data blocks back, trailers + guarded), `mfp_probe_and_dump()` (MIFARE Plus), `mful_dump_card()` + (Ultralight), and `t4t_dump_ndef()` (Type 4 NDEF). +- `nfc_listener` starts card emulation from generic card data + (`nfc_listener_start`) or from a pre-loaded MIFARE Classic dump + (`nfc_listener_start_emu`). + +### Persistence (`nfc_device`, `nfc_store`) + +- `nfc_device` stores MIFARE Classic card profiles (with keys) in NVS namespace + `nfc_cards`, up to `NFC_DEVICE_MAX_PROFILES` (8), with an active-profile + selector for emulation and legacy generic-card wrappers. +- `nfc_store` stores generic card entries (name, protocol, UID, ATQA, SAK, and a + protocol-specific payload up to `NFC_STORE_PAYLOAD_MAX` = 2048 B) in NVS, up to + `NFC_STORE_MAX_ENTRIES` (16), plus NTAG/Ultralight pack/unpack helpers. + +### Card families / protocols (`protocols/`) + +| Family | Directory | Notes | +|--------|-----------|-------| +| Common | `common/` | APDU, crypto, RF, tag, ISO-DEP TCL layer | +| ISO 14443-A | `iso14443a/` | anti-collision, ISO-DEP, poller, NDEF, T4T + T4T emulation | +| ISO 14443-B | `iso14443b/` | reader + emulation | +| ISO 15693 (NFC-V) | `iso15693/` | reader + emulation | +| FeliCa (NFC-F) | `felica/` | reader + emulation | +| EMV | `emv/` | bank card / payment applications | +| LLCP / SNEP | `llcp/` | NFC P2P link + SNEP exchange | +| MIFARE | `mifare/` | Classic (+ emu, + writer), Ultralight, Plus, DESFire (+ emu), crypto1, nested attack (`mf_nested`), `mfkey`, key cache, key dictionary loader, known cards | +| Topaz / Type 1 | `t1t/` | Type 1 tag | +| Type 2 | `t2t/` | Type 2 tag emulation | + +The supported protocol identifiers are enumerated in +`highboy_nfc_protocol_t` (`highboy_nfc_types.h`): ISO14443-3A/-3B/-4A/-4B, +FeliCa, ISO15693, MIFARE Classic / Ultralight / Plus / DESFire, ST25TB, and SLIX. + +### Key dictionaries (`assets/nfc/dict/`) + +MIFARE key dictionaries shipped as assets and loaded by `nfc_dict_loader`: +`mf_classic_default.dic`, `mf_classic_user.dic` (Classic key A/B lists) and +`mf_ulc_default.dic` (Ultralight-C keys). + +## Driver: HighBoy NFC (`st25r3916`) + +`highboy_nfc.h` is the driver front end for the ST25R3916 / ST25R3916B: + +```c +esp_err_t highboy_nfc_init(const highboy_nfc_config_t *config); +void highboy_nfc_deinit(void); +esp_err_t highboy_nfc_ping(uint8_t *out_chip_id); +esp_err_t highboy_nfc_field_on(void); +esp_err_t highboy_nfc_field_off(void); +uint8_t highboy_nfc_measure_amplitude(void); +bool highboy_nfc_field_detected(uint8_t *out_aux_display); +``` + +- Configuration is a `highboy_nfc_config_t` (SPI pins, host, mode 1, clock). + `HIGHBOY_NFC_CONFIG_DEFAULT()` provides the ESP32-P4 reference wiring. +- The driver is split into `st25r3916_core.c`, `st25r3916_fifo.c`, + `st25r3916_irq.c`, `st25r3916_aat.c` (antenna auto-tuning) and a HAL layer + (`hal/hb_nfc_gpio.c`, `hb_nfc_spi.c`, `hb_nfc_timer.c`). +- Capacity constants live in `highboy_nfc_types.h` (UID up to 10 bytes for + triple cascade, ATS up to 64 bytes, 512-byte hardware FIFO). diff --git a/docs/ota/README.md b/docs/ota/README.md index 8c7438411..539bda9b6 100644 --- a/docs/ota/README.md +++ b/docs/ota/README.md @@ -4,9 +4,20 @@ Handles firmware updates for TentacleOS via MicroSD card. Uses A/B OTA partition ## How It Works -The C5 firmware is embedded inside the P4 binary at build time. A single `.bin` file updates both chips. +The P4 and C5 update through **two separate flows**: -### Update Flow +- **P4 self-OTA** - the P4 flashes its own inactive OTA slot from an image on the + SD card, then reboots and self-validates (this document's main subject). +- **C5 app OTA** - a separate flow (`c5_flasher`) reads + `/sdcard/c5/TentacleOS_C5.bin` and pushes it to the C5 over the SPI bridge; the + C5 writes its own inactive slot and reboots. See [C5 App OTA](#c5-app-ota-over-the-spi-bridge). + +> **Note:** embedding the C5 firmware inside the P4 binary now applies **only** to +> the ROM-download recovery path (used to reflash a bricked/blank C5 over UART/ROM); +> the normal C5 app update is the separate SPI-bridge flow above, not a single +> combined `.bin`. + +### Update Flow (P4 self-OTA) 1. Place firmware at `/sdcard/update/tentacleos.bin` 2. Trigger `ota_start_update()` from UI or console @@ -45,6 +56,37 @@ Scenarios: fails, P4 does not confirm, bootloader rolls back - **Healthy new image** - confirmed within a few seconds of boot +### C5 App OTA (over the SPI bridge) + +The normal C5 firmware update is independent of the P4 self-OTA above. It is +driven from the P4 by `c5_flasher` (`components/Service/c5_flasher/c5_flasher.c`): + +1. The image is read from `/sdcard/c5/TentacleOS_C5.bin` (not embedded in the P4 + binary). +2. The P4 sends `SPI_ID_SYSTEM_OTA_BEGIN` (size + transport) over the SPI bridge. + The C5 erases its inactive OTA slot asynchronously and reports `READY` via + `SPI_ID_SYSTEM_OTA_STATUS`. +3. The P4 streams the image as `SPI_ID_SYSTEM_OTA_DATA` chunks; the C5 writes each + sequentially and acks. `bytes_written` (from the STATUS poll) is the resync + point if a chunk ack is lost. +4. When the last chunk lands, the C5 finalizes, sets the boot slot to `DONE`, and + reboots into the new firmware. The P4 marks the bridge link down so the link + monitor re-probes and reconnects the new C5. + +> On the HighBoy V2 the transport is **SPI** (`SPI_OTA_TRANSPORT_SPI`); the UART +> transport path in `c5_flasher` is kept for a future board that routes a real +> P4->C5 UART and does not work on V2. + +**C5-side rollback (validated by P4 bridge health).** On the C5, after a fresh OTA +image boots, `ota_post_boot_check()` +(`firmware_c5/components/Service/ota/include/ota_service.h`) sees the running +partition is pending verification and **waits for the P4 to reach it over the +bridge** before marking the app valid; if the P4 does not establish the link +within the validation window, the C5 does not confirm and the bootloader rolls +back to the previous C5 image. It must be called after the C5's `kernel_init` so +the bridge slave is already listening. This is the inverse of the P4 side, whose +confirmation depends only on **local** health (see below) and never on the C5. + ### Partition Table See [boot_report](../boot_report/README.md) for the full current layout (the OTA @@ -59,7 +101,21 @@ slots were resized to `0x270000` to make room for a `coredump` partition). ### Versioning -Version is read from `assets/config/OTA/firmware.json`. Both P4 and C5 share the same version string. The C5 responds its version via `SPI_ID_SYSTEM_VERSION` (0x04). +The build-time version string is `common/metadata/version_info.txt` (a single +line, e.g. `1.4.0`). `firmware_p4/components/Service/CMakeLists.txt` reads it and +configures `common/metadata/ota_version.h.in` into a generated `ota_version.h` +that defines `FIRMWARE_VERSION`, which `ota_service.c` compiles in and returns +from `ota_get_current_version()`. (The header is generated at build time from +`version_info.txt`; there is no checked-in `ota_version.h`.) + +`assets/config/OTA/firmware.json` is **only the runtime/synced copy**, not the +source of truth: `firmware_p4/CMakeLists.txt` stamps the same `version_info.txt` +value into the assets image at build time, and at boot `ota_sync_version_to_assets()` +(called from `ota_post_boot_check()`) rewrites `firmware.json` to the running +`FIRMWARE_VERSION` if they differ. + +The C5 reports its own version over the SPI bridge via `SPI_ID_SYSTEM_VERSION` +(SYSTEM subcommand `0x04`). ## API diff --git a/docs/pins/README.md b/docs/pins/README.md new file mode 100644 index 000000000..206e0defd --- /dev/null +++ b/docs/pins/README.md @@ -0,0 +1,128 @@ +# Pins (Board GPIO Map) + +`pin_def.h` is the central, header-only map of every GPIO assignment for a board. No driver should hardcode a GPIO number: all of them include this header and use the defines. The P4 and C5 firmwares each ship their own `pin_def.h` with different pin numbers and different peripheral sets, so the two are documented as separate sections below. + +## Overview + +- **Location:** `components/Drivers/pins/include/pin_def.h` (per firmware) +- **Header only:** no `.c` file; pure `#define`s inside an `extern "C"` guard. +- **Rule:** hardware pin numbers live here and only here. + +A `-1` value marks a signal that is not wired on the board (or is driven by something other than that MCU), and `LED_COUNT` gives the addressable-LED count. + +--- + +# P4 (ESP32-P4, HighBoy V2) + +Header: `firmware_p4/components/Drivers/pins/include/pin_def.h` + +## Pin Groups + +### Shared SPI bus +One SPI bus is shared by the display, CC1101, NFC, IMU and LoRa. + +| Signal | Define | GPIO | +| :--- | :--- | :--- | +| MOSI | `GPIO_SPI_MOSI_PIN` | 22 | +| SCLK | `GPIO_SPI_SCLK_PIN` | 21 | +| MISO | `GPIO_SPI_MISO_PIN` | 23 | + +The SX1262 LoRa reuses these same lines: `GPIO_LORA_SCLK_PIN` (21), `GPIO_LORA_MOSI_PIN` (22) and `GPIO_LORA_MISO_PIN` (23) alias the shared bus, with its own `GPIO_LORA_CS_PIN` (26), `GPIO_LORA_BUSY_PIN` (4) and `GPIO_LORA_DIO1_PIN` (5). `GPIO_LORA_RESET_PIN`, `GPIO_LORA_TXEN_PIN` and `GPIO_LORA_RXEN_PIN` are `-1` (not wired). + +### I2C bus +Charger, fuel gauge, LED driver and haptic share one I2C bus: `GPIO_I2C_SDA_PIN` (31), `GPIO_I2C_SCL_PIN` (30). + +### Chip selects and IRQs on the shared SPI bus + +| Peripheral | Signal | Define | GPIO | +| :--- | :--- | :--- | :--- | +| CC1101 | CS | `GPIO_CC1101_CS_PIN` | 20 | +| CC1101 | GDO0 | `GPIO_CC1101_GDO0_PIN` | 8 | +| CC1101 | GDO2 | `GPIO_CC1101_GDO2_PIN` | 9 | +| ST7789 | CS / DC / RST / BL | `GPIO_ST7789_CS_PIN` / `_DC_PIN` / `_RST_PIN` / `_BL_PIN` | 51 / 50 / 52 / 14 | +| IMU QMI8658 | CS | `GPIO_QMI8658A_CS_PIN` | 36 | +| NFC ST25R3916 | CS / IRQ | `GPIO_NFC_CS_PIN` / `GPIO_NFC_IRQ_PIN` | 24 / 10 | + +### Sub-GHz antenna switch (PE613050) +`GPIO_RF_SW_V1_PIN` (17), `GPIO_RF_SW_V2_PIN` (18). + +### Buttons (active-low) + +| Button | Define | GPIO | +| :--- | :--- | :--- | +| LEFT | `GPIO_BTN_LEFT_PIN` | 35 | +| RIGHT | `GPIO_BTN_RIGHT_PIN` | 13 | +| UP | `GPIO_BTN_UP_PIN` | 3 | +| DOWN | `GPIO_BTN_DOWN_PIN` | 7 | +| OK | `GPIO_BTN_OK_PIN` | 25 | +| BACK | `GPIO_BTN_BACK_PIN` | 6 | + +**BACK / LEFT are on the hardware power path.** As documented in `power_policy.c`, BACK and LEFT are wired to the charger `/QON` and the P4 `CHIP_PU` respectively, so holding **BACK + LEFT** together resets the P4 in hardware before firmware can react. Because of this the graceful power-off combo is deliberately **OK + LEFT** (OK is not on that path), letting firmware stay alive to issue the ship-mode command. Keep this in mind when picking or documenting button combos. + +Note also that `GPIO_BTN_LEFT_PIN` (35) is the same pin as the P4 boot strap `GPIO_P4_BOOT_PIN` (35, "button B5"), so LEFT doubles as a boot-strapping pin. + +### 125 kHz LF RFID +`GPIO_RFID_LF_CARRIER_PIN` (15), `GPIO_RFID_LF_DATA_PIN` (16), `GPIO_RFID_LF_MOD_PIN` (11), `GPIO_RFID_LF_COIL_PIN` (2). + +### IR +`GPIO_IR_TX_PIN` (0), `GPIO_IR_RX_PIN` (1) (TSOP receiver + LED). + +### SDMMC (4-bit SDIO) +`GPIO_SDMMC_CLK_PIN` (43), `GPIO_SDMMC_CMD_PIN` (44), `GPIO_SDMMC_D0_PIN` (39), `GPIO_SDMMC_D1_PIN` (40), `GPIO_SDMMC_D2_PIN` (41), `GPIO_SDMMC_D3_PIN` (42), card-detect `GPIO_SD_CD_PIN` (34). + +### P4 to C5 bridge (SPI master) +`GPIO_BRIDGE_SCLK_PIN` (45), `GPIO_BRIDGE_MOSI_PIN` (46), `GPIO_BRIDGE_MISO_PIN` (47), `GPIO_BRIDGE_CS_PIN` (48). `GPIO_BRIDGE_IRQ_PIN` is `-1`. C5 reset/boot (`GPIO_C5_RESET_PIN`, `GPIO_C5_BOOT_PIN`) are `-1` because the CP2105, not the P4, drives them. + +### Audio (I2S) and mic (PDM) +`GPIO_AUDIO_EN_PIN` (49), `GPIO_AUDIO_LRCLK_PIN` (29), `GPIO_AUDIO_BCLK_PIN` (28), `GPIO_AUDIO_DIN_PIN` (27) for the amp (commented NS4168); `GPIO_MIC_PDM_CLK_PIN` (54), `GPIO_MIC_PDM_DATA_PIN` (53) for the PDM mic (commented MSM261). See [docs/audio_i2s/README.md](../audio_i2s/README.md). + +### Miscellaneous +`GPIO_HAPTIC_TRIG_PIN` (32, AW8623 trigger), `GPIO_CHARGER_CE_PIN` (33, BQ25896 OTG/CE), `GPIO_USB_MUX_SEL_PIN` (19, TS3USB221 D+/D- mux), console UART0 `GPIO_P4_UART0_RX_PIN` (37) / `GPIO_P4_UART0_TX_PIN` (38) to the CP2105 SCI, and `GPIO_LED_RGB_PIN` `-1` with `LED_COUNT` 1 (the LP5816 RGB LED is I2C-driven in V2, no data pin). + +### No direct P4 to C5 UART +On the HighBoy V2 there is **no** direct P4 to C5 UART. The schematic routes the P4 UART0 (GPIO37/38) and the C5 UART0 (GPIO11/12) to two **separate** channels of the CP2105 USB bridge; they never meet. `GPIO38` is the P4's own console TX to the CP2105 (SCI), not a wire to the C5. The legacy defines `GPIO_C5_UART_TX_PIN` (38), `GPIO_C5_UART_RX_PIN` (39), `GPIO_RFID_UART_TX_PIN` (24) and `GPIO_RFID_UART_RX_PIN` (25) are kept only so unported V1 drivers still build. Consequently a "C5 OTA over UART" path cannot reach the C5 (it just writes to the P4's USB serial); the only P4 to C5 data path is the SPI bridge. + +--- + +# C5 (ESP32-C5) + +Header: `firmware_c5/components/Drivers/pins/include/pin_def.h` + +The C5 carries a smaller peripheral set than the P4 and uses entirely different GPIO numbers. + +## Pin Groups + +### SPI bus +`GPIO_SPI_MOSI_PIN` (11), `GPIO_SPI_SCLK_PIN` (12), `GPIO_SPI_MISO_PIN` (13). + +### CC1101 Sub-GHz radio +`GPIO_CC1101_CS_PIN` (3), `GPIO_CC1101_GDO0_PIN` (8), `GPIO_CC1101_GDO2_PIN` (9). + +### SD card (SPI mode) +`GPIO_SD_CARD_CS_PIN` (14). Unlike the P4 (4-bit SDIO), the C5 uses SPI-mode SD. + +### ST7789 display +`GPIO_ST7789_CS_PIN` (48), `GPIO_ST7789_DC_PIN` (47), `GPIO_ST7789_RST_PIN` (21), `GPIO_ST7789_BL_PIN` (38). + +### Buttons + +| Button | Define | GPIO | +| :--- | :--- | :--- | +| LEFT | `GPIO_BTN_LEFT_PIN` | 5 | +| BACK | `GPIO_BTN_BACK_PIN` | 7 | +| UP | `GPIO_BTN_UP_PIN` | 15 | +| DOWN | `GPIO_BTN_DOWN_PIN` | 6 | +| OK | `GPIO_BTN_OK_PIN` | 4 | +| RIGHT | `GPIO_BTN_RIGHT_PIN` | 16 | + +The C5 button pins are independent GPIOs and are not tied to the charger `/QON` or `CHIP_PU` power path that the P4's BACK/LEFT are on. + +### I2C bus +`GPIO_I2C_SDA_PIN` (8), `GPIO_I2C_SCL_PIN` (9). These share the same GPIO numbers as the CC1101 GDO0/GDO2 defines above. + +### RGB LED +`GPIO_LED_RGB_PIN` (27), `LED_COUNT` 1 (WS2812 / SK6812). Unlike the P4, the C5 has a real addressable-LED data pin. + +### P4 to C5 bridge (SPI slave) +The C5 side of the bridge is the SPI **slave**: `GPIO_BRIDGE_SCLK_PIN` (26), `GPIO_BRIDGE_MOSI_PIN` (25), `GPIO_BRIDGE_MISO_PIN` (24), `GPIO_BRIDGE_CS_PIN` (23), `GPIO_BRIDGE_IRQ_PIN` (3). As noted on the P4 side, this SPI bridge is the only data path between the two chips; there is no direct P4 to C5 UART on the V2 board. + diff --git a/docs/power_manager/README.md b/docs/power_manager/README.md new file mode 100644 index 000000000..eafc618a9 --- /dev/null +++ b/docs/power_manager/README.md @@ -0,0 +1,79 @@ +# Power Manager - P4 (esp_pm DFS / light-sleep) + +System power manager for `firmware_p4`. It owns `esp_pm` and drives automatic light sleep gated by a shared `NO_LIGHT_SLEEP` lock, so the CPU only powers off when the device is genuinely idle (screen off, no active resource, not plugged in). + +## Overview + +- **Location:** `firmware_p4/components/Service/power_manager/` +- **Header:** `include/power_manager.h` +- **Sources:** `power_manager.c` (esp_pm setup + lock), `power_manager_usb.c` (TinyUSB bus callbacks) +- **Dependencies:** `esp_pm`, `esp_log`, `esp_err`, `tusb` (for the USB callbacks) +- **Gated by:** `CONFIG_PM_ENABLE`. When PM is off, all entry points are inert no-ops (USB state is still tracked); the CPU stays at a fixed frequency and never sleeps. +- **Why Service (not Core):** `power_policy` lives in Applications, which cannot depend on Core (Core already REQUIRES Applications, so that edge would be a cycle). Both Core (transitively) and Applications reach Service. + +## Power model + +- **Frequency is pinned:** `esp_pm_configure` is called with `min_freq_mhz == max_freq_mhz == 360`, so there is **no DFS**. DFS would scale the APB clock and break the console UART baud (the IDF UART driver excludes the console UART from PM). The power win here is light sleep (CPU fully off when idle), not frequency scaling. +- **Light sleep** (`light_sleep_enable = true`) engages only when nobody holds the shared `ESP_PM_NO_LIGHT_SLEEP` lock **and** all tasks are blocked. +- **The `NO_LIGHT_SLEEP` lock** is created once at init and is not held by the manager itself. Resource owners that need the CPU awake acquire it and release it themselves (screen on, active radio, host-link/OTA session, ...). It is ref-counted: while the count is `> 0` the system never light-sleeps. + +## USB / external-power gating (item 41) + +Two independent inputs feed a single internal wake hold: + +- **VBUS / external power** from the charger (battery service) via `power_manager_set_external_power`. +- **USB bus lifecycle** via `power_manager_set_usb_suspended`. + +The internal rule is `want = external_power && !usb_suspended`. When that transitions true the manager takes one `NO_LIGHT_SLEEP` hold; when it transitions false it releases it. So the device never light-sleeps while plugged in and the host has not suspended the bus (mid charge, host-link, or OTA session). + +`power_manager_usb.c` provides the strong TinyUSB bus-lifecycle callbacks (TinyUSB declares them weak). They only fire when native P4 USB is up (mux switched to native); on the default UART-bridge path TinyUSB is not installed and plugged-in state comes solely from VBUS. Mapping: + +| TinyUSB callback | USB suspended input | Effect | +|------------------|---------------------|--------| +| `tud_mount_cb` / `tud_resume_cb` | not suspended | hold wake lock (if external power) | +| `tud_umount_cb` / `tud_suspend_cb` | suspended | release wake lock | + +## API Reference + +### `power_manager_init` +```c +void power_manager_init(void); +``` +Call once early in boot. Configures `esp_pm` (pinned frequency + light sleep) and creates the shared `NO_LIGHT_SLEEP` lock. Does not acquire the lock. No-op when `CONFIG_PM_ENABLE` is off. + +### `power_manager_no_sleep_acquire` +```c +esp_err_t power_manager_no_sleep_acquire(void); +``` +Hold the CPU out of light sleep. Ref-counted: every acquire needs a matching release. Returns `ESP_ERR_INVALID_STATE` if the lock was never created; `ESP_OK` otherwise (also `ESP_OK` as a no-op when PM is disabled). + +### `power_manager_no_sleep_release` +```c +esp_err_t power_manager_no_sleep_release(void); +``` +Release one hold taken with `power_manager_no_sleep_acquire`. Same return contract as acquire. + +### `power_manager_set_external_power` +```c +void power_manager_set_external_power(bool present); +``` +Report external power (USB/charger VBUS) presence. Idempotent; recomputes the internal USB wake hold. + +### `power_manager_set_usb_suspended` +```c +void power_manager_set_usb_suspended(bool suspended); +``` +Report USB bus suspend state (from `tud_suspend`/`tud_resume`, and treated as suspended on umount). A suspended host drops the plugged-in hold. Idempotent. + +### `power_manager_external_power` +```c +bool power_manager_external_power(void); +``` +True while external power (VBUS) is present. Always valid regardless of `CONFIG_PM_ENABLE`, for battery-vs-plugged policy decisions. + +## Tunables + +| Symbol | Location | Value | Meaning | +|--------|----------|-------|---------| +| `PM_FREQ_MHZ` | `power_manager.c` | `360` | Pinned CPU frequency (min == max, no DFS). | +| `CONFIG_PM_ENABLE` | sdkconfig | - | Master gate. Off: all calls are inert no-ops. | diff --git a/docs/spi/README.md b/docs/spi/README.md index d234b6341..f2f5ac577 100644 --- a/docs/spi/README.md +++ b/docs/spi/README.md @@ -12,8 +12,11 @@ This component acts as a central manager for the SPI bus, allowing multiple devi ## Supported Devices (`spi_device_id_t`) 1. **SPI_DEVICE_ST7789:** Display Driver -2. **SPI_DEVICE_CC1101:** Sub-GHz Radio +2. **SPI_DEVICE_CC1101:** Sub-GHz radio. The enum name is **legacy**: the SPI3 + radio is now the **SX1262 LoRa** part, which shares the SPI3 bus with the + ST7789 display behind a bus lock (see `spi_bus_lock_take` / `spi_bus_lock_give`). 3. **SPI_DEVICE_SD_CARD:** Storage +4. **SPI_DEVICE_BRIDGE:** The P4->C5 SPI bridge device (P4 only). ## API Reference @@ -69,7 +72,7 @@ This component acts as a central manager for the SPI bus, allowing multiple devi ## Supported Devices (`spi_device_id_t`) 1. **SPI_DEVICE_ST7789:** Display Driver -2. **SPI_DEVICE_CC1101:** Sub-GHz Radio +2. **SPI_DEVICE_CC1101:** Sub-GHz radio (legacy enum name for the LoRa part). 3. **SPI_DEVICE_SD_CARD:** Storage ## API Reference diff --git a/docs/spi_bridge/README.md b/docs/spi_bridge/README.md index f2daa92b6..de3fa5740 100644 --- a/docs/spi_bridge/README.md +++ b/docs/spi_bridge/README.md @@ -74,20 +74,47 @@ The P4 flashes the C5's firmware over a separate UART link using the official ## 3. Frame format -Every packet on the SPI bus starts with a fixed **5-byte header**: +Every packet on the SPI bus starts with a fixed **7-byte header** (5 bytes of +framing + a 2-byte CRC-16): ```c -typedef struct { +typedef struct __attribute__((packed)) { uint8_t sync; // 0xAA uint8_t type; // 0x01 CMD, 0x02 RESP, 0x03 STREAM uint8_t category; // spi_cat_t - subsystem uint8_t op; // operation within the category uint8_t length; // payload bytes that follow (0-255) + uint16_t crc; // CRC-16 over [type,category,op,length] + data } spi_header_t; ``` -`SPI_FRAME_SIZE` = header + 256 B payload, **rounded up to a multiple of 4** for -DMA = **264 B**. The command/response path always transfers `SPI_FRAME_SIZE`. +`SPI_MAX_PAYLOAD` is **255** (not 256): the header's `length` is a `uint8_t`, so +256 was never representable. For a RESP, `length` also counts the status byte, so +its data maxes at 254. + +`SPI_FRAME_SIZE` = header + `SPI_MAX_PAYLOAD`, **rounded up to a multiple of 4** +for DMA = **264 B** (unchanged: the header grew by 2 and the payload cap shrank by +1). The command/response path always transfers `SPI_FRAME_SIZE`. + +### Frame-integrity CRC (SPI-1) + +Every frame carries a **CRC-16/CCITT** (the ESP-ROM `esp_rom_crc16_le` +implementation) over the header's `[type,category,op,length]` plus the data bytes. +The `sync` byte (framing marker) and the `crc` field itself are excluded. Inline +helpers in `spi_protocol.h`: + +```c +uint16_t spi_frame_crc(const spi_header_t *h, uint16_t data_len); // compute +void spi_frame_seal(const spi_header_t *h, uint16_t data_len); // stamp into a fresh frame +bool spi_frame_valid(const spi_header_t *h, uint16_t data_len);// verify on receive +``` + +`data_len` is `header.length` for CMD/RESP frames, or `2 + batch_len` for STREAM +frames (whose `header.length` is 0 and whose real size lives in the leading `u16` +of the payload). The receiver drops any frame that fails `spi_frame_valid` - a +mismatch means the bus corrupted it. The CRC catches bus corruption; it does +**not** catch a drifted `spi_protocol.h` (intact bytes, different meaning) - that +is the job of the proto-version check (SPI-2, §8). ### Command identifier = `category` + `op` @@ -104,6 +131,7 @@ alone; `op` selects the operation within it. | `SPI_CAT_MESH` | `0x04` | meshtastic (split BLE/WiFi transport) | | `SPI_CAT_MCORE` | `0x05` | meshcore → `bt_dispatcher` | | `SPI_CAT_HOST` | `0x06` | companion host-link BLE relay → `bt_dispatcher` | +| `SPI_CAT_SCREEN` | `0x07` | P4-native screen sharing over the USB host link (handled locally, never relayed) | | `SPI_CAT_SESSION` | `0xFF` | inline session handlers | In C, the `SPI_ID_*` constants stay single named values (e.g. @@ -142,6 +170,24 @@ P4 (master) C5 (slave) - A per-command **mutex** on the P4 serialises commands; long radio ops get longer timeouts (`SPI_TIMEOUT_WIFI_MS = 20 s`, default `1 s`). +### Handshake modes: IRQ vs POLL + +The handshake is a physical-layer choice (`spi_bridge_mode_t`); the wire protocol +is identical either way. Both ends must be initialized in the **same** mode. + +- **`SPI_BRIDGE_MODE_IRQ`** (default) - the C5 pulses the IRQ GPIO when a + response/stream frame is armed; the P4 catches the rising edge. Needs the IRQ + trace wired. +- **`SPI_BRIDGE_MODE_POLL`** - no IRQ line, so the P4 re-clocks the bus until the + slave answers with a valid frame. Used on boards without an IRQ trace (the + HighBoy V2 PCB has `GPIO_BRIDGE_IRQ_PIN == -1`, so `bridge_manager` initializes + the master in POLL mode). + +```c +esp_err_t spi_bridge_master_init(void); // == IRQ mode +esp_err_t spi_bridge_master_init_mode(spi_bridge_mode_t mode); // pick IRQ or POLL +``` + --- ## 5. Generic data pipe (pulling lists) @@ -174,7 +220,7 @@ transfer** (`SPI_STREAM_FRAME_SIZE = 2048 B`) instead of one record per round-trip: ``` -STREAM frame payload (after the 5-byte header, type = STREAM): +STREAM frame payload (after the 7-byte header, type = STREAM): [u16 batch_len][record][record]... record = [u16 op][u8 len][len bytes] ``` @@ -218,14 +264,76 @@ running into the void if the P4 crashes or stops listening: --- +## Async scans (non-blocking) + +A radio scan can take seconds. Rather than hold the per-command bridge mutex for +its whole duration, a scan is run **asynchronously**: fire the scan command (the +C5 starts it and returns immediately), then poll a lightweight status id until it +reports the scan finished. Results are fetched afterwards through the generic data +pipe as usual. + +```c +esp_err_t spi_bridge_run_scan(spi_id_t scan_id, spi_id_t status_id, + const uint8_t *payload, uint8_t len); +``` + +Returns `ESP_OK` when the status id reports done, or `ESP_ERR_TIMEOUT` if it never +cleared. The status ids are `SPI_ID_WIFI_SCAN_STATUS` (`0x0150`) and +`SPI_ID_BT_SCAN_STATUS` (`0x027F`) - each responds `1` while its scan is running. + +--- + +## Power management + +The P4 tells the C5 its power state so the co-processor can drop its radio when +the P4 is idle or asleep instead of running full RX all the time. It sends +`SPI_ID_SYSTEM_POWER_STATE` (op `0x4A`) with a one-byte `spi_power_state_t`: + +| State | Value | Meaning | +|-------|-------|---------| +| `SPI_POWER_ACTIVE` | 0 | Normal operation: full radio. | +| `SPI_POWER_IDLE` | 1 | Screen dimmed: deeper modem sleep (MAX_MODEM). | +| `SPI_POWER_SLEEP` | 2 | Device asleep: drop the radio (unless a capture is running). | + +--- + ## 8. Firmware versioning & flashing -The C5 firmware is **embedded in the P4 firmware** at build time (bootloader + -partition table + app). On boot, `bridge_manager` queries the C5's version -(`SPI_ID_SYSTEM_VERSION`) and compares it against the P4's expected version -(`FIRMWARE_VERSION`, currently **1.3.0**). On mismatch (or no response) the P4 -re-flashes the C5 over the UART link using `esp-serial-flasher`, writing the -full image: +Two independent checks run at boot in `bridge_manager`, both **detection-only** - +the P4 never auto-flashes (the user updates explicitly with the `c5` console +command): + +- **App version.** Query the C5's version (`SPI_ID_SYSTEM_VERSION`) and compare it + against the P4's expected `FIRMWARE_VERSION` (generated from + `common/metadata/version_info.txt`, currently **1.4.0**). A mismatch logs + "update available"; a silent C5 marks the bridge down. +- **Wire-protocol version (SPI-2).** Read the C5's `SPI_ID_SYSTEM_PROTO_VERSION` + (op `0x0D`) and compare it against `SPI_PROTOCOL_VERSION` (**2**). This catches a + drifted `spi_protocol.h` that the per-frame CRC cannot (intact bytes, different + meaning). A mismatch logs loudly and lights the error LED but keeps the bridge + up. Bump `SPI_PROTOCOL_VERSION` in **both** copies of `spi_protocol.h` on any + change to the contract (an id, a struct, or the header layout). + +### Update path + +The everyday update is an **app OTA**: the C5 keeps running its app and receives a +new image into its inactive OTA slot. The image is **streamed from the SD card** +(`/sdcard/c5/TentacleOS_C5.bin`) - it is no longer embedded in the P4 binary for +this path. The OTA control plane (begin / status / per-chunk acks) always rides +this SPI bridge: + +| Op | `spi_id_t` | Purpose | +|----|------------|---------| +| `SPI_ID_SYSTEM_OTA_BEGIN` | `0x0009` | begin OTA; payload `spi_ota_begin_t { size, transport }` | +| `SPI_ID_SYSTEM_OTA_STATUS` | `0x000A` | poll `spi_ota_status_t { state, bytes_written }` | +| `SPI_ID_SYSTEM_OTA_DATA` | `0x000B` | one firmware chunk (SPI transport) | + +The image bytes travel over the transport chosen in `spi_ota_begin_t.transport` +(`spi_ota_transport_t`): `SPI_OTA_TRANSPORT_SPI` (as `OTA_DATA` chunks) or +`SPI_OTA_TRANSPORT_UART` (raw over UART). For a blank/bricked C5 that has no +running app, the P4 first sends `SPI_ID_SYSTEM_ENTER_DOWNLOAD` (op `0x08`) to boot +the C5 into ROM download mode, then reflashes over UART with `esp-serial-flasher` +using the **embedded** images (the fallback path): | Image | C5 flash offset | |-------|-----------------| @@ -233,9 +341,8 @@ full image: | partition table | `0x8000` | | app | `0x10000` | -Any breaking change to the wire format must bump **both** versions -(`FIRMWARE_VERSION` on the P4 and `SPI_FW_VERSION_STRING` on the C5) to the same -new value, forcing a re-sync. +Full detail: [`../c5_flasher/README.md`](../c5_flasher/README.md) and +[`../bridge_manager/README.md`](../bridge_manager/README.md). --- @@ -245,8 +352,10 @@ new value, forcing a re-sync. - `components/Service/spi_bridge/` - `spi_bridge.c` (send command, stream task), `spi_session.c` (session/heartbeat), `spi_protocol.h` (shared contract) - `components/Drivers/spi_bridge_phy/` - SPI master PHY + IRQ edge ISR -- `components/Service/bridge_manager/` - version check + C5 recovery -- `components/Service/c5_flasher/` - `esp-serial-flasher` wrapper +- `components/Service/bridge_manager/` - bridge lifecycle, app + proto-version + checks, C5 link monitor, OTA trigger +- `components/Service/c5_flasher/` - app OTA (SD image over SPI/UART) + + `esp-serial-flasher` ROM fallback **C5 (slave)** - `components/Service/spi_bridge/` - `spi_bridge.c` (`bridge_task` routing + @@ -280,17 +389,18 @@ This component manages the high-speed communication link between the **ESP32-P4 The P4 acts as the **SPI Master**. It is responsible for: 1. Generating the SCLK and managing the CS line. 2. Initiating all command transfers. -3. Handling the **IRQ (Handshake)** signal from the C5 to know when response data is ready. -4. Managing the C5 lifecycle (Reset, Boot mode, and Firmware Updates via UART). +3. Handling the **IRQ (Handshake)** signal from the C5 to know when response data is ready (or re-clocking the bus in POLL mode on boards without an IRQ trace). +4. Managing the C5 lifecycle: reset/boot control, the app OTA (streamed from the SD image over the SPI bridge), and ROM serial-flash recovery over UART. ## Protocol Specification -Every packet follows a 5-byte fixed header: +Every packet follows a 7-byte fixed header (5 framing bytes + a 2-byte CRC-16): - `Sync (0xAA)`: Packet synchronization. - `Type`: `0x01` (Command), `0x02` (Response), `0x03` (Stream). - `Category`: Subsystem selector (`spi_cat_t`: WiFi `0x01`, BT `0x02`, …). The C5 routes a command to a dispatcher by this byte alone. - `Op`: Operation within the category. -- `Length`: Size of the following payload (0-255 bytes). +- `Length`: Size of the following payload (0-255 bytes; `SPI_MAX_PAYLOAD = 255`). +- `CRC`: CRC-16 over `[type,category,op,length]` + data (see §3, Frame-integrity CRC). `Category` + `Op` together form the packed command identifier (`spi_id_t`), built via `SPI_CMD(cat, op)`. Use `spi_header_cmd()` / `spi_header_set_cmd()` to @@ -311,10 +421,29 @@ Every command's `spi_id_t` packs `Category` (high byte) and `Op` (low byte) via | `SPI_ID_SYSTEM_DATA` | `0x05` | `0x0005` | | `SPI_ID_SYSTEM_STREAM` | `0x06` | `0x0006` | | `SPI_ID_SYSTEM_LOG` | `0x07` | `0x0007` | +| `SPI_ID_SYSTEM_ENTER_DOWNLOAD` | `0x08` | `0x0008` | +| `SPI_ID_SYSTEM_OTA_BEGIN` | `0x09` | `0x0009` | +| `SPI_ID_SYSTEM_OTA_STATUS` | `0x0A` | `0x000A` | +| `SPI_ID_SYSTEM_OTA_DATA` | `0x0B` | `0x000B` | +| `SPI_ID_SYSTEM_INFO` | `0x0C` | `0x000C` | +| `SPI_ID_SYSTEM_PROTO_VERSION` | `0x0D` | `0x000D` | +| `SPI_ID_SYSTEM_POWER_STATE` | `0x4A` | `0x004A` | `SPI_ID_SYSTEM_LOG` is a C5→P4 stream carrying log lines (`[level u8][utf-8]`) for the companion's C5 console (see the host-link docs). +- `ENTER_DOWNLOAD` (`0x08`): P4→C5 - reboot into ROM serial-download mode for + serial-flash recovery. +- `OTA_BEGIN` / `OTA_STATUS` / `OTA_DATA` (`0x09`-`0x0B`): P4→C5 app-OTA control + plane (§8). BEGIN payload `spi_ota_begin_t { u32 size, u8 transport }`; STATUS + response `spi_ota_status_t { u8 state, u32 bytes_written }`; DATA carries one + firmware chunk on the SPI transport. +- `SYSTEM_INFO` (`0x0C`): P4→C5 - read chip identity (`spi_sys_info_t`: + model / revision / MAC / free heap). +- `PROTO_VERSION` (`0x0D`): P4→C5 - read the C5's `SPI_PROTOCOL_VERSION` (u16). + Checked at bridge init (§8). +- `POWER_STATE` (`0x4A`): P4→C5 - device power state (see Power management below). + System ops `0x40`-`0x49` (`FILE_*`, `SYSTEM_DEVICE_STATE`, `SYSTEM_CONSOLE_EXEC`, `SYSTEM_GET_SETTINGS`, `SYSTEM_SET_SETTINGS`) are **P4-local host-link commands**: they share the `spi_id_t` space so the companion app and P4 agree, but they are @@ -326,6 +455,7 @@ in [`../host_link/protocol.md`](../host_link/protocol.md). | Command | Op | `spi_id_t` | |---------|----|------------| | `SPI_ID_WIFI_SCAN` | `0x10` | `0x0110` | +| `SPI_ID_WIFI_SCAN_STATUS` | `0x50` | `0x0150` | | `SPI_ID_WIFI_CONNECT` | `0x11` | `0x0111` | | `SPI_ID_WIFI_DISCONNECT` | `0x12` | `0x0112` | | `SPI_ID_WIFI_GET_STA_INFO` | `0x13` | `0x0113` | @@ -396,6 +526,7 @@ in [`../host_link/protocol.md`](../host_link/protocol.md). | Command | Op | `spi_id_t` | |---------|----|------------| | `SPI_ID_BT_SCAN` | `0x50` | `0x0250` | +| `SPI_ID_BT_SCAN_STATUS` | `0x7F` | `0x027F` | | `SPI_ID_BT_CONNECT` | `0x51` | `0x0251` | | `SPI_ID_BT_DISCONNECT` | `0x52` | `0x0252` | | `SPI_ID_BT_GET_INFO` | `0x53` | `0x0253` | @@ -475,6 +606,27 @@ this category to `bt_dispatcher`. See [`../host_link/`](../host_link/README.md). | `SPI_ID_HOST_RX` | `0xA3` | `0x06A3` | C5→P4 stream: app→device (BLE write) | | `SPI_ID_HOST_STATUS` | `0xA4` | `0x06A4` | P4→C5 cmd: poll BLE connection state | +### Screen (`0x07`) + +P4-native screen sharing over the USB host link. These are **handled locally on +the P4 and never relayed to the C5**; they share the `spi_id_t` space so the +companion app and P4 agree on the ids. `START`/`STOP`/`KEY` are app→device +commands; `FRAME` is a device→app STREAM. + +| Command | Op | `spi_id_t` | Direction | +|---------|----|------------|-----------| +| `SPI_ID_SCREEN_START` | `0x01` | `0x0701` | app→device: start streaming the live screen | +| `SPI_ID_SCREEN_STOP` | `0x02` | `0x0702` | app→device: stop streaming | +| `SPI_ID_SCREEN_KEY` | `0x03` | `0x0703` | app→device: inject a key (`spi_screen_key_t`) | +| `SPI_ID_SCREEN_FRAME` | `0x04` | `0x0704` | device→app STREAM: RGB565 row-strip | + +- `spi_screen_key_t`: `UP` (0), `DOWN` (1), `LEFT` (2), `RIGHT` (3), `OK` (4), + `BACK` (5) - mapped to the LVGL keypad. +- `SPI_ID_SCREEN_FRAME` payload starts with `spi_screen_strip_t { u16 y, u16 rows, + u16 width }`, followed by `rows * width` little-endian RGB565 pixels. The screen + is streamed as horizontal row-strips; `y == 0` marks the first strip of a new + frame. + ### Session (`0xFF`) | Command | Op | `spi_id_t` | @@ -485,15 +637,17 @@ this category to `bt_dispatcher`. See [`../host_link/`](../host_link/README.md). ## Frame Example -The 5-byte header maps directly to `spi_header_t`: +The 7-byte header maps directly to `spi_header_t` (`cc cc` below is the little- +endian CRC-16, computed over `[type,category,op,length]` + data - see §3): ```c -typedef struct { +typedef struct __attribute__((packed)) { uint8_t sync; // 0xAA uint8_t type; // spi_type_t: CMD 0x01 / RESP 0x02 / STREAM 0x03 uint8_t category; // spi_cat_t uint8_t op; // operation within the category uint8_t length; // payload bytes that follow (0-255) + uint16_t crc; // CRC-16 over [type,category,op,length] + data } spi_header_t; ``` @@ -501,23 +655,25 @@ typedef struct { ``` P4 -> C5 (command) - AA 01 01 10 00 - ^ ^ ^ ^ ^ - | | | | +-- length = 0 - | | | +----- op = 0x10 - | | +-------- category = 0x01 (WiFi) - | +----------- type = 0x01 (CMD) - +-------------- sync = 0xAA - -C5 -> P4 (response, after raising IRQ) - payload byte 0 is the status - AA 02 01 10 01 00 - ^ ^ ^ ^ ^ ^ - | | | | | +-- status = 0x00 (SPI_STATUS_OK) - | | | | +----- length = 1 - | | | +-------- op = 0x10 - | | +----------- category = 0x01 - | +-------------- type = 0x02 (RESP) - +----------------- sync = 0xAA + AA 01 01 10 00 cc cc + ^ ^ ^ ^ ^ ^--^ + | | | | | +-- crc = CRC-16 (little-endian) + | | | | +------- length = 0 + | | | +---------- op = 0x10 + | | +------------- category = 0x01 (WiFi) + | +---------------- type = 0x01 (CMD) + +------------------- sync = 0xAA + +C5 -> P4 (response, after the handshake) - payload byte 0 is the status + AA 02 01 10 01 cc cc 00 + ^ ^ ^ ^ ^ ^--^ ^ + | | | | | | +-- status = 0x00 (SPI_STATUS_OK) [payload byte 0] + | | | | | +------- crc = CRC-16 (little-endian) + | | | | +---------- length = 1 + | | | +------------- op = 0x10 + | | +---------------- category = 0x01 + | +------------------- type = 0x02 (RESP) + +---------------------- sync = 0xAA ``` Scan results are then pulled item-by-item through the **Generic Data Pipe** (`SPI_ID_SYSTEM_DATA`) described below. @@ -538,7 +694,7 @@ per round-trip: - The C5 buffers records in a ring (depth `SPI_STREAM_QUEUE_LEN = 64`). On a `SPI_ID_SYSTEM_STREAM` poll it packs as many as fit into a single large frame of `SPI_STREAM_FRAME_SIZE` (2048 B) and the P4 always clocks that fixed size. -- Stream frame layout (after the 5-byte header, `type = STREAM`): +- Stream frame layout (after the 7-byte header, `type = STREAM`): `[u16 batch_len]` then `batch_len` bytes of records, each `[u16 op][u8 len][len bytes]`. `batch_len = 0` means "no data" → the P4 backs off and polls again later. @@ -805,7 +961,8 @@ This component transforms the **ESP32-C5** into a high-performance radio co-proc ## How it Works The C5 runs a background task (`spi_bridge_task`) that stays in a blocked state waiting for the P4 to send SPI bytes. -1. **Reception**: When bytes arrive, the task validates the `0xAA` sync byte. +1. **Reception**: When bytes arrive, the task validates the `0xAA` sync byte and + the frame CRC-16 (`spi_frame_valid`), dropping any corrupted frame. 2. **Routing**: It switches on the `Category` byte and routes the payload to the appropriate **Dispatcher** (WiFi or Bluetooth); the `Op` byte selects the operation within that dispatcher. 3. **Execution**: The Dispatcher executes the radio command (e.g., starts a scan). 4. **Notification**: Once the command is done (or results are ready), the C5 raises the **IRQ (Handshake)** pin. diff --git a/docs/st7789/README.md b/docs/st7789/README.md index 3e3af18c3..877477baa 100644 --- a/docs/st7789/README.md +++ b/docs/st7789/README.md @@ -9,28 +9,38 @@ This component initializes and manages the ST7789 LCD controller using the ESP-I - **Dependencies:** `esp_lcd`, `driver/gpio`, `driver/ledc`, `spi` ## Hardware Configuration -- **Resolution:** 240x240 +- **Resolution:** 240x320 (`LCD_H_RES` 240 / `LCD_V_RES` 320; `LCD_PANEL_W`/`LCD_PANEL_H` match). - **Color Depth:** 16-bit (RGB565) -- **Interface:** SPI (via `spi` component driver) +- **Interface:** SPI on `SPI3_HOST`, 20 MHz pixel clock (`LCD_PIXEL_CLOCK_HZ`). + +## SPI drive-strength hardening +`st7789_init` bumps the SPI3 `SCLK`/`MOSI` pins to `LCD_SPI_DRIVE_CAP` +(`GPIO_DRIVE_CAP_3`, the strongest) via `gpio_set_drive_capability`. The display +FFC is long, capacitive and unterminated: at the IDF default the 20 MHz edges +barely settle, so a radio's EMI corrupts the still-settling edge and garbles long +transfers. Do not lower this - a weaker cap starves the FFC so hard the panel +will not even init. ## Internal Backlight Control -Although a separate `backlight` component exists, this driver currently includes its own internal PWM initialization (`init_backlight_pwm`) and control logic using `LEDC_TIMER_0` / `LEDC_CHANNEL_0`. -*Note: This overlaps with the standalone `backlight` component. Verify project integration to avoid timer conflicts.* +This driver owns its own PWM backlight init (`init_backlight_pwm`) and control +logic using `LEDC_TIMER_0` / `LEDC_CHANNEL_0`, 13-bit resolution at 5 kHz on +`GPIO_ST7789_BL_PIN`. ## API Reference ### `st7789_init` ```c -void st7789_init(void); +esp_err_t st7789_init(void); ``` -Initializes the display. -1. Creates the SPI device interface on `SPI3_HOST`. -2. Configures the ST7789 panel (Reset pin, RGB order, etc.). -3. Resets and initializes the panel. -4. Inverts colors (standard for many ST7789 IPS panels). -5. Turns the display ON. -6. Initializes the backlight PWM and **applies** (does not re-save) the saved - brightness/rotation. +Initializes the display. Returns `ESP_OK`, or the failing `esp_err_t` from the +panel IO / panel create / reset / init step (cleaning up any handles it created). +1. Creates the SPI panel IO on `SPI3_HOST`. +2. Hardens the SPI3 `SCLK`/`MOSI` drive strength (see above). +3. Configures the ST7789 panel (Reset pin, RGB order, etc.). +4. Resets and initializes the panel. +5. Inverts colors (standard for many ST7789 IPS panels). +6. Turns the display ON. +7. Initializes the backlight PWM and applies the saved brightness/rotation. ### `lcd_apply_brightness` ```c @@ -54,6 +64,20 @@ uint8_t lcd_get_brightness(void); ``` Reads the persisted brightness back from the config file. +### `lcd_set_rotation` +```c +void lcd_set_rotation(uint8_t rotation); +``` +Sets the panel rotation (index `1`-`4`, clamped). Applies the matching +mirror / swap-xy / gap for the 240x320 panel and persists the value. Rotations +3 and 4 apply a `ROTATION_GAP_OFFSET` (80) to line the visible window up. + +### `lcd_get_rotation` +```c +uint8_t lcd_get_rotation(void); +``` +Reads the persisted rotation index (1-4) back from the config file. + ### `lcd_display_sleep` ```c void lcd_display_sleep(bool sleep); diff --git a/docs/storage_api/README.md b/docs/storage_api/README.md index 4bfe5897d..d7a77c689 100644 --- a/docs/storage_api/README.md +++ b/docs/storage_api/README.md @@ -291,6 +291,38 @@ storage_append_csv_row("/data/sensors.csv", row, 3); --- +## Atomic Writes + +Header: `storage_atomic.h` + +For config files where a crash mid-write must never leave a half-written file, +use the atomic replace. It writes to `.tmp`, flushes and fsyncs it, then +renames it over the destination. On FAT (where rename cannot replace an existing +target) it unlinks the target first and retries. The original file is untouched +on any failure before the rename, and if power is lost between the unlink and the +rename the complete `.tmp` survives for the loader to recover. Works on both +FAT (SD) and LittleFS (flash) mounts. + +```c +#include "storage_atomic.h" + +esp_err_t storage_write_atomic(const char *path, const void *data, size_t len); +``` + +| Return | Meaning | +|--------|---------| +| `ESP_OK` | File replaced atomically | +| `ESP_ERR_INVALID_ARG` | Bad arguments (`path` NULL, or `data` NULL with non-zero `len`) | +| `ESP_ERR_INVALID_SIZE` | The temp path does not fit | +| `ESP_FAIL` | I/O failure (destination preserved unless recovery applies) | + +```c +// Replace a config file so a crash never leaves it half-written +storage_write_atomic(TOS_PATH_CONFIG_SYSTEM, json, strlen(json)); +``` + +--- + ## Stream I/O Header: `storage_stream.h` diff --git a/docs/storage_assets/README.md b/docs/storage_assets/README.md index 0d84294fc..66d3b9bef 100644 --- a/docs/storage_assets/README.md +++ b/docs/storage_assets/README.md @@ -32,20 +32,52 @@ This component provides read-only access to a dedicated LittleFS partition for s ### Partition Table -The assets partition must be defined in your partition table (`partitions.csv`): +The assets partition is defined in the firmware partition table +(`firmware_p4/partitions.csv` and `firmware_c5/partitions.csv`). The layout is +OTA-based: there is no `factory` app partition and no separate `storage` data +partition. Two app slots (`ota_0` / `ota_1`) hold the A/B firmware images, a +`coredump` partition captures crash dumps, and `assets` is a read-only LittleFS +image. The P4 and C5 differ in slot sizes and ordering. + +**P4 (`firmware_p4/partitions.csv`):** ```csv -# Name, Type, SubType, Offset, Size, Flags -nvs, data, nvs, 0x9000, 0x6000, -phy_init, data, phy, 0xf000, 0x1000, -factory, app, factory, 0x10000, 1M, -assets, data, spiffs, 0x110000, 512K, -storage, data, spiffs, 0x190000, 1M, +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 24K, +otadata, data, ota, 0xf000, 8K, +phy_init, data, phy, 0x11000, 4K, +ota_0, app, ota_0, 0x20000, 0x270000, +ota_1, app, ota_1, 0x290000, 0x270000, +coredump, data, coredump, 0x500000, 64K, +assets, data, littlefs, 0x510000, 0x2E0000, ``` +`assets` is `0x2E0000` bytes (~2.9 MB) at offset `0x510000`. + +**C5 (`firmware_c5/partitions.csv`):** + +```csv +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 24K, +otadata, data, ota, 0xf000, 8K, +phy_init, data, phy, 0x11000, 4K, +ota_0, app, ota_0, 0x20000, 2M, +ota_1, app, ota_1, 0x220000, 2M, +assets, data, littlefs, 0x420000, 0x1E0000, +coredump, data, coredump, 0x600000, 64K, +``` + +`assets` is `0x1E0000` bytes (~1.9 MB) at offset `0x420000`. Note the ordering +differs from the P4: on the C5, `assets` comes before `coredump`. + **Important Notes:** -- The SubType must be `spiffs` (even though we use LittleFS - this is an ESP-IDF quirk). -- Size should be sufficient for all your assets (adjust as needed). +- The SubType is `littlefs` (the image is mounted with esp_littlefs). This + project uses the native `littlefs` subtype, not the older `spiffs`-subtype + workaround. +- A `coredump` partition (64K) is reserved for crash dumps; it is separate from + the assets and app storage. +- There is no `factory` app partition and no separate `storage` data partition; + firmware lives in the `ota_0` / `ota_1` slots. - The partition must be flashed before use. ### Constants @@ -102,7 +134,7 @@ void app_main(void) { ``` I (1234) storage_assets: Initializing LittleFS for assets partition I (1245) storage_assets: Assets ready at /assets -I (1246) storage_assets: Partition size: 524288 bytes, used: 12345 bytes +I (1246) storage_assets: Partition size: 3014656 bytes, used: 12345 bytes I (1247) storage_assets: === Files in assets partition === I (1248) storage_assets: [1] logo.bin (1200 bytes) I (1249) storage_assets: [DIR] fonts/ @@ -304,10 +336,10 @@ Prints detailed information about the assets partition to the console. I (1234) storage_assets: === Assets Partition Info === I (1235) storage_assets: Mount point: /assets I (1236) storage_assets: Partition: assets -I (1237) storage_assets: Total size: 524288 bytes (512.00 KB) +I (1237) storage_assets: Total size: 3014656 bytes (2944.00 KB) I (1238) storage_assets: Used: 98765 bytes (96.45 KB) -I (1239) storage_assets: Free: 425523 bytes (415.55 KB) -I (1240) storage_assets: Usage: 18.8% +I (1239) storage_assets: Free: 2915891 bytes (2847.55 KB) +I (1240) storage_assets: Usage: 3.3% ``` **Usage:** @@ -548,7 +580,7 @@ project/ **Solution:** 1. Add partition to `partitions.csv`: ```csv - assets, data, spiffs, 0x110000, 512K, + assets, data, littlefs, 0x510000, 0x2E0000, ``` 2. Set partition table in `sdkconfig`: ``` @@ -655,20 +687,52 @@ This component provides read-only access to a dedicated LittleFS partition for s ### Partition Table -The assets partition must be defined in your partition table (`partitions.csv`): +The assets partition is defined in the firmware partition table +(`firmware_p4/partitions.csv` and `firmware_c5/partitions.csv`). The layout is +OTA-based: there is no `factory` app partition and no separate `storage` data +partition. Two app slots (`ota_0` / `ota_1`) hold the A/B firmware images, a +`coredump` partition captures crash dumps, and `assets` is a read-only LittleFS +image. The P4 and C5 differ in slot sizes and ordering. + +**P4 (`firmware_p4/partitions.csv`):** ```csv -# Name, Type, SubType, Offset, Size, Flags -nvs, data, nvs, 0x9000, 0x6000, -phy_init, data, phy, 0xf000, 0x1000, -factory, app, factory, 0x10000, 1M, -assets, data, spiffs, 0x110000, 512K, -storage, data, spiffs, 0x190000, 1M, +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 24K, +otadata, data, ota, 0xf000, 8K, +phy_init, data, phy, 0x11000, 4K, +ota_0, app, ota_0, 0x20000, 0x270000, +ota_1, app, ota_1, 0x290000, 0x270000, +coredump, data, coredump, 0x500000, 64K, +assets, data, littlefs, 0x510000, 0x2E0000, ``` +`assets` is `0x2E0000` bytes (~2.9 MB) at offset `0x510000`. + +**C5 (`firmware_c5/partitions.csv`):** + +```csv +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 24K, +otadata, data, ota, 0xf000, 8K, +phy_init, data, phy, 0x11000, 4K, +ota_0, app, ota_0, 0x20000, 2M, +ota_1, app, ota_1, 0x220000, 2M, +assets, data, littlefs, 0x420000, 0x1E0000, +coredump, data, coredump, 0x600000, 64K, +``` + +`assets` is `0x1E0000` bytes (~1.9 MB) at offset `0x420000`. Note the ordering +differs from the P4: on the C5, `assets` comes before `coredump`. + **Important Notes:** -- The SubType must be `spiffs` (even though we use LittleFS - this is an ESP-IDF quirk). -- Size should be sufficient for all your assets (adjust as needed). +- The SubType is `littlefs` (the image is mounted with esp_littlefs). This + project uses the native `littlefs` subtype, not the older `spiffs`-subtype + workaround. +- A `coredump` partition (64K) is reserved for crash dumps; it is separate from + the assets and app storage. +- There is no `factory` app partition and no separate `storage` data partition; + firmware lives in the `ota_0` / `ota_1` slots. - The partition must be flashed before use. ### Constants @@ -725,7 +789,7 @@ void app_main(void) { ``` I (1234) storage_assets: Initializing LittleFS for assets partition I (1245) storage_assets: Assets ready at /assets -I (1246) storage_assets: Partition size: 524288 bytes, used: 12345 bytes +I (1246) storage_assets: Partition size: 3014656 bytes, used: 12345 bytes I (1247) storage_assets: === Files in assets partition === I (1248) storage_assets: [1] logo.bin (1200 bytes) I (1249) storage_assets: [DIR] fonts/ @@ -927,10 +991,10 @@ Prints detailed information about the assets partition to the console. I (1234) storage_assets: === Assets Partition Info === I (1235) storage_assets: Mount point: /assets I (1236) storage_assets: Partition: assets -I (1237) storage_assets: Total size: 524288 bytes (512.00 KB) +I (1237) storage_assets: Total size: 3014656 bytes (2944.00 KB) I (1238) storage_assets: Used: 98765 bytes (96.45 KB) -I (1239) storage_assets: Free: 425523 bytes (415.55 KB) -I (1240) storage_assets: Usage: 18.8% +I (1239) storage_assets: Free: 2915891 bytes (2847.55 KB) +I (1240) storage_assets: Usage: 3.3% ``` **Usage:** @@ -1171,7 +1235,7 @@ project/ **Solution:** 1. Add partition to `partitions.csv`: ```csv - assets, data, spiffs, 0x110000, 512K, + assets, data, littlefs, 0x510000, 0x2E0000, ``` 2. Set partition table in `sdkconfig`: ``` diff --git a/docs/storage_vfs/README.md b/docs/storage_vfs/README.md index 68faedef5..401b17ab3 100644 --- a/docs/storage_vfs/README.md +++ b/docs/storage_vfs/README.md @@ -504,7 +504,14 @@ esp_err_t vfs_sdcard_init(void); esp_err_t vfs_sdcard_deinit(void); bool vfs_sdcard_is_mounted(void); void vfs_sdcard_print_info(void); +bool vfs_sdcard_get_name(char *out, size_t n); esp_err_t vfs_sdcard_format(void); +esp_err_t vfs_register_sd_backend(void); +esp_err_t vfs_unregister_sd_backend(void); + +// Raw handoff for USB Mass Storage (see "USB Mass Storage (MSC)" below) +esp_err_t vfs_sdcard_detach_for_msc(void **out_card); +esp_err_t vfs_sdcard_reattach_after_msc(void *card); ``` ### LittleFS Backend @@ -521,6 +528,70 @@ esp_err_t vfs_littlefs_format(void); --- +## USB Mass Storage (MSC) + +> P4 only. The MSC path lives in `storage_vfs` +> (`usb_msc.c` / `vfs_sdcard.c`, headers `include/usb_msc.h` and +> `include/vfs_sdcard.h`). The C5 has no USB connector or SD card and does not +> build these. + +USB Mass Storage mode exposes the microSD directly to a host PC as a USB drive. +Because a host filesystem and the firmware's FAT mount cannot own the card at +the same time, entering MSC hands the raw block device over to the USB stack and +tears down the app's `/sdcard` mount; leaving MSC does the reverse and resumes +normal operation **without a reboot** (it only reboots if the remount fails). + +### Control API (`usb_msc.h`) + +```c +#include "usb_msc.h" + +typedef enum { + USB_MSC_IDLE = 0, // Not in USB-storage mode + USB_MSC_ENTERING, // Detaching SD / bringing USB up + USB_MSC_ACTIVE, // SD exposed to the host as a USB drive + USB_MSC_ERROR, // Could not enter (SD restored, safe to leave) + USB_MSC_EXITING, // Tearing down / remounting SD +} usb_msc_state_t; + +usb_msc_state_t usb_msc_get_state(void); // poll from the UI +bool usb_msc_host_connected(void); // true while the host has it mounted +void usb_msc_enter(void); // blocking - run on a worker task +void usb_msc_exit(void); // blocking - run on a worker task +``` + +- `usb_msc_enter()` detaches the app's `/sdcard` FAT, exposes the raw card to the + host, and switches the USB mux to native. On failure the SD is restored and the + state goes to `USB_MSC_ERROR`. +- `usb_msc_exit()` stops exposing the card, remounts `/sdcard`, and routes the USB + connector back to the UART bridge. It resumes without a reboot; only a failed + remount forces a reboot to recover. +- Both `usb_msc_enter()` and `usb_msc_exit()` are **blocking** - never call them + from the LVGL thread; drive them from a worker task and poll `usb_msc_get_state()`. + +### Raw SD handoff (`vfs_sdcard.h`) + +MSC is implemented on top of two raw-handoff helpers in the SD backend that give +up and reclaim the physical card around the USB stack: + +```c +// Give up the app's FAT mount and hand back the SD as a RAW, still-powered block +// device for USB MSC. The card is re-initialized (never formatted). +// out_card receives an sdmmc_card_t* (as void*) on success. +esp_err_t vfs_sdcard_detach_for_msc(void **out_card); + +// Undo the detach: release the raw SDMMC card+host handed to MSC and remount the +// app FAT at /sdcard. Call AFTER the MSC storage layer is torn down. Lets the +// firmware resume without a reboot. `card` is the handle from the detach call. +esp_err_t vfs_sdcard_reattach_after_msc(void *card); +``` + +`vfs_sdcard_detach_for_msc()` leaves the app FAT mount detached on failure; +`vfs_sdcard_reattach_after_msc()` performs the remount that returns the card to +the firmware. + +--- + ## Switching Backends To switch between storage backends, edit `vfs_config.h`: diff --git a/docs/sx1262/README.md b/docs/sx1262/README.md new file mode 100644 index 000000000..cabf359df --- /dev/null +++ b/docs/sx1262/README.md @@ -0,0 +1,164 @@ +# SX1262 LoRa Transceiver Driver + +This component provides a driver for the Semtech SX1262 sub-GHz LoRa transceiver. It handles the full SX1262 command set over SPI, LoRa modulation/packet configuration, TX/RX (single, continuous, and duty-cycle), Channel Activity Detection (CAD), sleep/wake power management, and interrupt-driven packet reception with a ring buffer. + +The driver core is platform-agnostic: all hardware access is delegated through a HAL callback struct (`sx1262_hal_t`), and the ESP32 port lives in a single file. This keeps the register/command logic portable and testable. + +## Overview + +- **Location:** `components/Drivers/sx1262/` +- **Public header:** `include/sx1262.h` (types in `include/sx1262_types.h`) +- **Dependencies:** `spi`, `pins` (`pin_def.h`), `sys_prio`, `driver/spi_master`, `driver/gpio`, `freertos`, `esp_log` +- **Interface:** SPI3_HOST at 4 MHz, SPI mode 0 (shared bus - see below) +- **Reference:** register/opcode comments cite the SX1262 datasheet (DS) sections + +## File Layout + +| File | Responsibility | +|------|----------------| +| `sx1262.c` | Public API, init/bring-up sequence, config, IRQ task, recovery | +| `sx1262_cmd.c` | Low-level SPI opcode/register/buffer access and BUSY-pin waiting | +| `sx1262_fsm.c` | Radio state machine (tracks `sx1262_state_t`) | +| `sx1262_hal.c` | ESP32 HAL port: SPI device, GPIO, mutex, bus lock, antenna switch | +| `sx1262_irq.c` | IRQ dispatch, RX packet read, RX ring buffer | +| `sx1262_radio.c` | TX/RX/CAD/sleep/wake/duty-cycle operations | +| `sx1262_regs.h` | Opcodes, register addresses, LoRa parameter constants | + +## Shared SPI3 Bus Contract (important) + +The SX1262 sits on **SPI3_HOST**, the same bus as the **ST7789 display**. Because two independent drivers share one bus, every SX1262 transaction is serialized through a layered lock in the HAL (`hal_lock` / `hal_unlock` in `sx1262_hal.c`): + +1. Take the SX1262 device mutex (`spi_mutex`, `portMAX_DELAY`). +2. Take the shared SPI3 bus lock via `spi_bus_lock_take(SPI3_BUS_LOCK_TIMEOUT_MS)` (1000 ms) - this is the cross-component handshake with the display driver. +3. `spi_device_acquire_bus()` for the duration of the transaction. + +Unlock reverses the order (`release_bus` -> `spi_bus_give` -> give mutex). The bus lock is taken/released per transaction so the display is never starved. + +Bus lifecycle: + +- `sx1262_hal_create()` calls `spi_bus_initialize(SPI3_HOST, ...)`. If the bus is **already initialized** by another driver (the ST7789 display, via the kernel `spi_init`), the returned `ESP_ERR_INVALID_STATE` is treated as success - the SX1262 simply adds itself as a second device. +- `sx1262_hal_destroy()` removes only the SX1262 device and deletes the device mutex. **It never frees the SPI3 bus**, because the display still needs it. `sx1262_deinit()` -> `sx1262_hal_destroy()` therefore leaves the display fully functional. + +## RX Robustness / Hardening + +The RX path is hardened against errored packets and a flaky/contended SPI3 bus: + +- **Retry bring-up until STDBY_RC.** `sx1262_init()` runs `sx1262_hw_bringup()` up to `SX1262_INIT_MAX_ATTEMPTS` (3) times. Bring-up performs reset -> standby -> DCDC regulator -> full calibration -> image calibration -> workarounds -> LoRa config, then reads the chip status and **only succeeds if the chip actually landed in STDBY_RC** (`chip_mode == STDBY_RC`). Any device error reported after calibration also fails the attempt, triggering a retry. +- **IRQ-task self-recovery.** The IRQ task (`irq_task`) counts consecutive `sx1262_irq_process()` failures; after `SX1262_IRQ_FAIL_RECOVER` (5) in a row it calls `sx1262_recover()`, which does a hardware reset + full reconfigure (`sx1262_hw_bringup`) and resumes continuous RX. A single success resets the streak. +- **Errored packets do not read the FIFO.** In `read_rx_packet()`, if the RxDone IRQ arrives with CRC-error or header-error flags set, the payload buffer is **not** read (`len = 0`), avoiding acting on corrupt FIFO contents. RSSI/SNR and the `has_crc_error` / `has_header_error` flags are still populated so the caller can observe the failure. Standalone CRC/header errors (no RxDone) are surfaced through the `on_error` callback. +- **Demoted RX-error hot-path logs.** RxDone, CRC error, header error, timeout, CAD, and ring-buffer-full messages log at `ESP_LOGD` (debug), not error/warning. This keeps the hot path quiet under noisy conditions instead of flooding the console on every errored packet. +- **Bounded RX ring buffer.** Received packets are pushed into a fixed ring of `SX1262_RX_RING_SIZE` (8) entries; a full ring drops the newest packet (debug log). Ring access is guarded by the HAL critical section, so `sx1262_get_packet()` is safe against the IRQ task. + +## Radio Parameters (`sx1262_config_t`) + +```c +typedef struct { + sx1262_hal_t hal; // Platform HAL - all callbacks + uint32_t frequency_hz; // 150_000_000 .. 960_000_000 Hz + uint8_t sf; // SF5 .. SF12 + uint8_t bw; // BW_7 .. BW_500 + uint8_t cr; // CR_4_5 .. CR_4_8 + int8_t tx_power_dbm; // -9 .. +22 dBm + uint16_t preamble_len; // preamble symbols (min 2, 12+ recommended) + bool is_crc_on; + bool is_inverted_iq; // true = LoRaWAN downlink (activates workaround W4) + bool is_implicit_hdr; // true = implicit header (activates W3); SF6 requires this + bool is_public_network; // sync word 0x3444 (public) vs 0x1424 (private/Meshtastic) +} sx1262_config_t; +``` + +Validation (`validate_config`) rejects out-of-range values before any SPI transaction. Notable rules: `BW_500` requires `SF >= 6`, and `SF6` requires implicit-header mode. LDRO (low data rate optimize) is derived automatically for `SF11/SF12` at `BW <= 125 kHz`. + +## Pin / Hardware Configuration + +Pins come from `pin_def.h` (`components/Drivers/pins`): + +| Signal | Macro | GPIO | +|--------|-------|------| +| SCLK | `GPIO_LORA_SCLK_PIN` | 21 | +| MOSI | `GPIO_LORA_MOSI_PIN` | 22 | +| MISO | `GPIO_LORA_MISO_PIN` | 23 | +| NSS/CS | `GPIO_LORA_CS_PIN` | 26 | +| BUSY | `GPIO_LORA_BUSY_PIN` | 4 | +| DIO1 | `GPIO_LORA_DIO1_PIN` | 5 | +| NRESET | `GPIO_LORA_RESET_PIN` | -1 (not wired) | +| TXEN | `GPIO_LORA_TXEN_PIN` | -1 (not wired) | +| RXEN | `GPIO_LORA_RXEN_PIN` | -1 (not wired) | + +`-1` pins are skipped by the HAL. With no discrete TX/RX antenna-switch pins, the RF switch is driven by the chip itself via **DIO2 as RF switch** (`SetDIO2AsRfSwitch`, enabled during bring-up). NSS is toggled in software (the SPI device is configured with `spics_io_num = -1`). BUSY and DIO1 are inputs; DIO1 signals IRQs. Reset/wait timings: `SX1262_RESET_HOLD_MS` (2), `SX1262_RESET_WAIT_MS` (20); BUSY polling caps at `SX1262_WAIT_BUSY_TIMEOUT_MS` (100). + +## HAL Contract (`sx1262_hal_t`) + +The driver core never includes platform headers. Porting means implementing the callback struct: `spi_transfer`, `cs_low` / `cs_high`, `reset_write`, `busy_read`, `delay_ms`, `get_tick_ms`, `lock` / `unlock`, `enter_critical` / `exit_critical`, `set_antenna`, and an opaque `ctx`. `sx1262_hal_create()` populates it for the ESP32; a different platform supplies its own file. + +## API Reference + +### Lifecycle + +```c +esp_err_t sx1262_init(const sx1262_config_t *config); +esp_err_t sx1262_deinit(void); +esp_err_t sx1262_start(void); +void sx1262_stop(void); +esp_err_t sx1262_config_lora(const sx1262_config_t *config); +esp_err_t sx1262_set_callbacks(const sx1262_callbacks_t *cbs); +bool sx1262_is_running(void); +``` + +- `sx1262_init` validates HAL + config, then runs the retrying bring-up (see hardening). Returns `ESP_ERR_INVALID_ARG` on bad config/HAL, or the last bring-up error after all attempts fail. +- `sx1262_start` creates the DIO1 IRQ processing task (stack 4096, `SYS_PRIO_REALTIME`, `SYS_CORE_RADIO` = core 0). `ESP_ERR_INVALID_STATE` if already running or not initialized; `ESP_ERR_NO_MEM` on task-create failure. +- `sx1262_stop` signals the IRQ task and waits (up to 500 ms) for it to exit via task notification, then turns the antenna switch off. +- `sx1262_config_lora` reconfigures LoRa parameters at runtime; re-applies workaround W4 (IQ polarity) every call. The stored HAL is preserved across the config copy. + +### Callbacks (`sx1262_callbacks_t`) + +```c +esp_err_t sx1262_set_callbacks(const sx1262_callbacks_t *cbs); +``` + +Registers `on_tx_done`, `on_rx_done`, `on_cad_done`, `on_timeout`, `on_error` (each may be NULL) plus a shared `cb_ctx`. Callbacks fire from the IRQ task, not an ISR. + +### TX / RX + +```c +esp_err_t sx1262_transmit(const uint8_t *data, uint8_t len, uint32_t timeout_ms); +esp_err_t sx1262_receive_single(uint32_t timeout_ms); +esp_err_t sx1262_receive_continuous(void); +void sx1262_stop_rx(void); +esp_err_t sx1262_get_packet(sx1262_packet_t *out_packet); +``` + +- `sx1262_transmit` is non-blocking (payload 1..255 bytes); completion arrives via `on_tx_done`. Applies workaround W1 (BW500 sensitivity) before each TX. +- `sx1262_receive_single` returns to STDBY after one packet or timeout (`timeout_ms = 0` = wait forever); `sx1262_receive_continuous` stays in RX until `sx1262_stop_rx()`. +- `sx1262_get_packet` dequeues from the RX ring; `ESP_ERR_NOT_FOUND` when empty. + +Received packet (`sx1262_packet_t`) carries `buf[256]`, `len`, `rssi_pkt_dbm`, `snr_pkt_db`, `signal_rssi_dbm`, `has_crc_error`, and `has_header_error`. + +### CAD & Power + +```c +esp_err_t sx1262_cad_start(void); +esp_err_t sx1262_sleep(bool is_warm); +esp_err_t sx1262_wakeup(void); +esp_err_t sx1262_set_rx_duty_cycle(uint32_t rx_ms, uint32_t sleep_ms); +``` + +- `sx1262_sleep(false)` (cold start) marks the driver as needing re-init before the next TX/RX; `true` is a warm start that retains config. +- `sx1262_set_rx_duty_cycle` runs wake-on-radio (RX window then sleep window, repeating in hardware). + +### Status / Diagnostics + +```c +sx1262_state_t sx1262_get_state(void); +esp_err_t sx1262_get_status(uint8_t *out_status); +esp_err_t sx1262_get_device_errors(uint16_t *out_errors); +esp_err_t sx1262_get_rssi_inst(int16_t *out_rssi_dbm); +esp_err_t sx1262_get_stats(sx1262_stats_t *out_stats); +esp_err_t sx1262_process_irq(void); +``` + +`sx1262_get_stats` returns cumulative counters (`nb_pkt_received`, `nb_crc_error`, `nb_header_error`). `sx1262_process_irq` is called by the IRQ task when DIO1 rises; it is not for direct ISR use. + +## Chip Workarounds + +The driver applies the datasheet errata workarounds automatically: **W1** (BW500 sensitivity, before each TX), **W2** (TX clamp config, during bring-up), **W3** (implicit-header RX, when `is_implicit_hdr`), and **W4** (IQ polarity, on every `config_lora`). diff --git a/docs/sys_monitor/README.md b/docs/sys_monitor/README.md index bd813037e..1c23bd983 100644 --- a/docs/sys_monitor/README.md +++ b/docs/sys_monitor/README.md @@ -22,12 +22,29 @@ kills tasks. (`esp_restart()`), preceded by a log, a UI alert and a short grace delay. 5. Tasks that recover or exit drop out of the table, so a fresh dip starts a new streak instead of inheriting a stale one. -6. It is also the **Task Watchdog heartbeat** (`esp_task_wdt_add`/`reset` each +6. **Heap watch** (`check_heap()`): reads internal free and the largest + contiguous internal block. When either drops low + (`HEAP_WARN_FREE_B` / `HEAP_WARN_LARGEST_B`) it warns once, evicts the image + cache via `assets_manager_evict_cache()` to reclaim RAM, then shows one UI + alert. If total internal free stays under `HEAP_CRIT_FREE_B` for + `HEAP_CRIT_CYCLES` consecutive cycles it does a controlled restart. Both the + warn latch and the critical streak reset once free RAM recovers. +7. **SD health watch** (`check_storage_health()`, every `STORAGE_CHECK_CYCLES` + cycles ~= 60 s): when the SD is mounted and `storage_check_health()` fails, it + requests a remount via `header_ui_request_sd_remount()`. +8. **I2C recovery watch** (`check_i2c_health()`): polls `i2c_recover_count()` and + logs when the bus recovery count advances (the I2C driver self-recovered a + stuck bus). Observe-and-report only; no escalation. +9. It is also the **Task Watchdog heartbeat** (`esp_task_wdt_add`/`reset` each cycle) and the **UI render supervisor**: it polls `ui_render_beat()` (a beat - bumped by an `lv_timer` inside the LVGL task) and, if the beat stalls for a - couple of cycles, does the same controlled restart. This replaces the old - "watch a task called `UI Task` by name" logic - it watches the renderer's - actual progress, not a task name. + bumped by an `lv_timer` inside the LVGL task) and, if the beat does not move + for `UI_STALL_ESCALATE_CYCLES` cycles (8, ~16 s), does the same controlled + restart. The tolerance is intentionally generous: a legitimately slow but + blocking op on the UI thread (e.g. a multi-second `wifi scan` run under the + LVGL lock) freezes the renderer for a few seconds without being deadlocked, so + only a genuinely stuck UI should reboot. This replaces the old "watch a task + called `UI Task` by name" logic - it watches the renderer's actual progress, + not a task name. `usStackHighWaterMark` is monotonic (it records the lowest free stack ever seen, and never rises again), so a sustained streak means the task is alive and running @@ -56,12 +73,14 @@ escalation is the controlled restart above. | `CRITICAL_STACK_THRESHOLD` | 256 | Free-stack floor (bytes) that flags a task | | `STACK_ESCALATE_CYCLES` | 5 | Consecutive critical cycles before a controlled restart | | `STACK_WATCH_MAX` | 8 | Distinct critical tasks tracked for persistence | +| `UI_STALL_ESCALATE_CYCLES` | 8 | Cycles with no render-beat progress (~16 s) before a controlled restart | +| `HEAP_WARN_FREE_B` | 24576 | Internal free below this warns once | +| `HEAP_WARN_LARGEST_B` | 12288 | Largest contiguous internal block below this warns once | +| `HEAP_CRIT_FREE_B` | 8192 | Internal free below this (sustained) escalates | +| `HEAP_CRIT_CYCLES` | 3 | Consecutive critical-heap cycles before a controlled restart | +| `STORAGE_CHECK_CYCLES` | 30 | SD-health probe cadence (cycles, ~60 s) | | `REBOOT_GRACE_MS` | 1500 | Delay after the alert so it renders / logs flush | -> The controlled restart has a `TODO(item 31)` hook: once the graceful shutdown -> path exists it should run there so the filesystem and radios flush their state -> before `esp_restart()`. - --- ## ESP32-C5 (`firmware_c5`) diff --git a/docs/sys_time/README.md b/docs/sys_time/README.md new file mode 100644 index 000000000..520447564 --- /dev/null +++ b/docs/sys_time/README.md @@ -0,0 +1,92 @@ +# System Time - P4 + +Central wall-clock owner for `firmware_p4`. The HighBoy V2 has no battery-backed RTC, so wall-clock time is volatile across power cycles: the P4's internal RTC timer runs while powered but resets on power-off. Time must be injected from an external source every power cycle (console, companion app over `host_link`, or SNTP via the C5) and is maintained by the internal timer until the next power loss. The whole system runs in UTC. + +## Overview + +- **Location:** `firmware_p4/components/Service/sys_time/` +- **Header:** `include/sys_time.h` +- **Source:** `sys_time.c` +- **Dependencies:** `esp_log`, `nvs`, `esp_err`, libc `` / `` +- **Persistence:** NVS namespace `systime` (keys `last_epoch`, `last_src`) + +## Build-date baseline + +So nothing is ever dated to 1970, `sys_time_init` seeds the clock with a baseline instead of leaving it at epoch 0: + +1. It derives a build-date epoch from the compiler `__DATE__` / `__TIME__` macros (`sys_time_build_epoch`). The device cannot predate its own image, so the build date is a safe floor. If parsing the macros fails, it falls back to `SYS_TIME_EPOCH_MIN`. +2. It reads the last-known time persisted in NVS (`sys_time_saved_epoch`). +3. The baseline is raised to the NVS value **only if** the saved epoch is `>= SYS_TIME_EPOCH_MIN`, otherwise the build-date floor stands. +4. The timezone is fixed to `UTC0`, `settimeofday` applies the baseline, and the state is left at `SYS_TIME_STATE_ESTIMATED` with source `SYS_TIME_SOURCE_NONE`. + +The clock stays `ESTIMATED` (a floor, not a real time) until a real source calls `sys_time_set`, which promotes it to `SYS_TIME_STATE_SYNCED`. + +## The `SYS_TIME_EPOCH_MIN` guard + +`SYS_TIME_EPOCH_MIN` is `1735689600` (2025-01-01 00:00:00 UTC). It is used in two places: + +- **Baseline selection:** a persisted NVS epoch below the guard is treated as garbage and ignored, keeping the build-date floor. +- **Rejecting bad sets:** `sys_time_set` returns `ESP_ERR_INVALID_ARG` for any `epoch < SYS_TIME_EPOCH_MIN`, so an implausibly early value can never sync the clock. + +## API Reference + +### States and sources + +```c +typedef enum { + SYS_TIME_STATE_ESTIMATED = 0, // baseline (build date or last-known NVS); a floor + SYS_TIME_STATE_SYNCED, // set from a real external source; time() trustworthy +} sys_time_state_t; + +typedef enum { + SYS_TIME_SOURCE_NONE = 0, + SYS_TIME_SOURCE_MANUAL, // console `date` command + SYS_TIME_SOURCE_HOST, // companion app over host_link + SYS_TIME_SOURCE_SNTP, // NTP over WiFi (via the C5) +} sys_time_source_t; +``` + +### `sys_time_init` +```c +void sys_time_init(void); +``` +Fixes the timezone to UTC and seeds the clock to the later of the firmware build date and the last-known NVS time, leaving state at `SYS_TIME_STATE_ESTIMATED`. Call once at boot. + +### `sys_time_set` +```c +esp_err_t sys_time_set(time_t epoch, sys_time_source_t source); +``` +Apply a wall-clock time from a real source. Sets the clock, marks it `SYNCED`, records `source`, and persists the value to NVS. Returns `ESP_ERR_INVALID_ARG` if `epoch` is below `SYS_TIME_EPOCH_MIN`, `ESP_FAIL` if `settimeofday` fails, `ESP_OK` on success. + +### `sys_time_state` +```c +sys_time_state_t sys_time_state(void); +``` +Current trust level of the clock. + +### `sys_time_source` +```c +sys_time_source_t sys_time_source(void); +``` +Source of the last successful sync. + +### `sys_time_now` +```c +time_t sys_time_now(void); +``` +Current wall-clock (UTC); at least the boot baseline. + +### `sys_time_format` +```c +bool sys_time_format(char *out, size_t out_size, const char *fmt); +``` +Format the current time with `strftime` (UTC fields) into `out`. Sets `out` to `""` on error. Returns `false` on a bad argument or if nothing was written. + +## Tunables + +| Symbol | Value | Meaning | +|--------|-------|---------| +| `SYS_TIME_EPOCH_MIN` | `1735689600` | Plausibility floor (2025-01-01 UTC): rejects early sets and stale NVS baselines. | +| `SYS_TIME_NVS_NS` | `"systime"` | NVS namespace for last-known time. | +| `SYS_TIME_NVS_KEY_EPOCH` | `"last_epoch"` | NVS key: last synced epoch (`u64`). | +| `SYS_TIME_NVS_KEY_SRC` | `"last_src"` | NVS key: last sync source (`u8`). | diff --git a/docs/tusb_desc/README.md b/docs/tusb_desc/README.md index 580cd0735..cc8918e46 100644 --- a/docs/tusb_desc/README.md +++ b/docs/tusb_desc/README.md @@ -1,6 +1,6 @@ -# TinyUSB Descriptors (HID Composite) +# TinyUSB Descriptors (HID + CDC + optional MSC Composite) -This component defines the USB descriptors required to enumerate the ESP32-P4 as a USB HID Composite Device (Keyboard + Mouse) and provides the initialization routine for the TinyUSB driver. +This component defines the USB descriptors required to enumerate the ESP32-P4 as a USB composite device and provides the initialization routine for the TinyUSB driver. The composite is HID (Keyboard + Mouse, for BadUSB) plus a CDC-ACM interface (the companion host link), with an optional mass-storage (MSC) interface exposed only while the SD card is handed to the host. ## Overview @@ -15,20 +15,39 @@ This component defines the USB descriptors required to enumerate the ESP32-P4 as | Field | Value | |-------|-------| -| USB Version | 2.0 | +| USB Version | 2.0 (`bcdUSB` `0x0200`) | | Vendor ID | `0xCAFE` | | Product ID | `0x4001` | -| Device Class | Defined at interface level | +| Device Class | Miscellaneous / Common / IAD (`TUSB_CLASS_MISC`) - required so the host groups the CDC interfaces | | Configurations | 1 | ### Configuration Descriptor +The config is runtime-selected. `tud_descriptor_configuration_cb` picks a variant +by both link speed and whether MSC is currently exposed, all built from a single +`HID_CDC_BLOCK` macro (optionally followed by an `MSC_INTERFACE`). + | Field | Value | |-------|-------| -| Interfaces | 1 (HID) | +| Interfaces | 3 (HID + CDC comm + CDC data), or 4 with MSC (`TUSB_DESC_ITF_NUM_TOTAL`) | | Max Power | 100 mA | | Attributes | Remote Wakeup | +The CDC bulk (data) endpoint size is speed-dependent: USB requires exactly 512 +bytes at High Speed and 64 at Full Speed. The P4 USB is High Speed, but two +descriptor variants are built per mode (HS/FS) and the matching one is served so +`tu_edpt_validate` accepts the config. A wrong size fails `SET_CONFIGURATION`, +which would also take the HID keyboard down. + +### Interfaces and Endpoints + +| Interface | Number | Endpoint(s) | +|-----------|--------|-------------| +| HID (keyboard + mouse) | 0 | IN `0x81` | +| CDC-ACM comm | 1 | notification IN `0x82` | +| CDC-ACM data | 2 | OUT `0x03`, IN `0x83` | +| MSC (optional) | 3 | OUT `0x04`, IN `0x84` | + ### HID Report Descriptor Single HID interface with two reports using Report IDs: @@ -46,6 +65,8 @@ Single HID interface with two reports using Report IDs: | 1 | Manufacturer: "HighCode" | | 2 | Product: "BadUSB Device" | | 3 | Serial: "123456" | +| 4 | CDC interface: "TentacleOS Companion" | +| 5 | MSC interface: "TentacleOS SD" (only present when MSC is compiled in) | ## API Reference @@ -60,7 +81,30 @@ Initializes the TinyUSB driver with the defined descriptors. Must be called before any HID report transmission. It does **not** touch the USB-C data mux (see below) - installing the driver and routing the connector are -separate steps. +separate steps. The HID (BadUSB) and CDC (companion) sides share one TinyUSB +install: whichever calls first brings the composite up, later calls are no-ops. + +### `busb_set_msc_exposed` +```c +void busb_set_msc_exposed(bool exposed); +``` +Advertises (or hides) the mass-storage interface on the composite at runtime. + +MSC is off by default, so plain native-USB bring-ups enumerate as HID + CDC only. +`tud_descriptor_configuration_cb` reads `s_msc_exposed` at request time and serves +the 4-interface (MSC) config variant only while the flag is set. Behavior: + +- If TinyUSB is not up yet, the call just latches the flag so the first + enumeration advertises the right layout. +- If it is already up, the device detaches (`tud_disconnect`), waits + `BUSB_REENUM_DELAY_MS` (100 ms) so the host notices, swaps the advertised + config, and re-attaches (`tud_connect`) so the host re-reads the descriptor. + +**Rationale:** an MSC LUN that is advertised but not backed by an initialized +storage handle crashes the TinyUSB task on the host's first SCSI command. MSC is +therefore exposed only while the SD is actually handed to the host (mass-storage +mode) and hidden again on exit. Only compiled in when `CFG_TUD_MSC` is set; a +no-op otherwise. ### `usb_mux_init` ```c @@ -115,9 +159,14 @@ The component implements the required TinyUSB callbacks to serve descriptors to | Callback | Purpose | |----------|---------| -| `tud_descriptor_device_cb` | Returns the device descriptor | -| `tud_descriptor_configuration_cb` | Returns the configuration descriptor | -| `tud_descriptor_string_cb` | Returns string descriptors (manufacturer, product, serial) | +| `tud_descriptor_device_cb` | Returns the composite device descriptor | +| `tud_descriptor_configuration_cb` | Returns the configuration descriptor, selecting the variant by link speed (HS/FS) and whether MSC is currently exposed | +| `tud_descriptor_string_cb` | Returns string descriptors (manufacturer, product, serial, CDC, MSC) | | `tud_hid_descriptor_report_cb` | Returns the HID report descriptor | | `tud_hid_get_report_cb` | Handles GET_REPORT requests (stub) | | `tud_hid_set_report_cb` | Handles SET_REPORT requests (stub) | + +The CDC-ACM and MSC class callbacks are not implemented here: they live in the +components that own those interfaces (the companion host link for CDC, and the +SD/mass-storage feature for MSC). This component only defines the shared +descriptors and the descriptor-serving callbacks above. diff --git a/docs/ui/README.md b/docs/ui/README.md index eb3b0d408..663dcd686 100644 --- a/docs/ui/README.md +++ b/docs/ui/README.md @@ -1,13 +1,17 @@ # ui_manager -step-by-step process for adding a new screen (feature) to the HighBoy system using the ui_manager architecture. -**Example** used: We'll create a fictional **Bluetooth (BLE)** screen. +Step-by-step process for adding a new screen (feature) to TentacleOS using the +`ui_manager` architecture. Source of truth: `ui/ui_manager.c`, +`ui/include/ui_manager.h`, `ui/include/ui_metrics.h`, `ui/include/ui_theme.h`, +and the screens under `ui/screens/`. -### 1. Register the screen in the UI ui_manager -The `ui_manager` needs to know about the new screen to handle navigation. +**Example** used: a fictional **Bluetooth (BLE)** menu screen. -**File:** `ui/ui_manager.h` -1. Add a new identifier to the `enum`: +### 1. Add a screen id +The `ui_manager` identifies every screen by an enum value. + +**File:** `ui/include/ui_manager.h` +Add a new identifier to `screen_id_t`: ```c typedef enum { SCREEN_NONE, @@ -15,163 +19,202 @@ typedef enum { SCREEN_MENU, SCREEN_WIFI_MENU, // ... - SCREEN_BLE_MENU, // <--- NEW ID ADDED + SCREEN_BLE_MENU, // <--- NEW ID } screen_id_t; ``` -### 2. Configure routing and Power Management -Define how the ui_manager should open the screen and handle any required hardware power states. +### 2. Register the open function in the dispatch table +Routing is a dispatch table, not a `switch` inside `ui_switch_screen`. +`screen_open_fn(screen_id_t)` maps an id to the function that builds and loads +the screen; `ui_switch_screen` looks the id up there. If it returns `NULL` the +screen is treated as unavailable and the manager stays put. **File:** `ui/ui_manager.c` -1. Include de header for the new screen (created in Step 3): +1. Include the screen header (created in Step 4): ```c -#include "screens/bluetooth/ui_ble_menu.h" +#include "ui_ble_menu.h" ``` -2. (Optional) Power Management: If the screen uses a radio (Wi-Fi, BLE, RF), add logic to automatically enable/disable the hardware. +2. Add a `case` to `screen_open_fn()`: ```c -static bool is_ble_screen(screen_id_t screen) { - switch (screen) { - case SCREEN_BLE_MENU: - case SCREEN_BLE_SCAN: // Future sub-screens - return true; - default: - return false; - } +static ui_open_fn_t screen_open_fn(screen_id_t s) { + switch (s) { + // ... other cases ... + case SCREEN_BLE_MENU: + return ui_ble_menu_open; // <--- NEW ROUTE + default: + return NULL; + } } ``` -Update `ui_switch_screen` to call `ble_init()` / `ble_deinit()` based on this flag (similar to how Wi-Fi is handled). +### 3. If the screen owns hardware or a task, register a stop hook +Screens that start a radio, a worker task, or a media player must register a +stop function in `screen_close_fn()`. `ui_switch_screen` calls it on the +outgoing screen before tearing it down, so the resource is always released on +navigation. Current examples: `SCREEN_SUBGHZ_READ` -> `subghz_receiver_stop`, +`SCREEN_NFC_READ` / `SCREEN_NFC_EMULATE` -> `nfc_manager_stop`, +`SCREEN_WAV_PLAYER` -> `ui_wav_player_stop`, `SCREEN_MP3_PLAYER` -> +`ui_mp3_player_stop`, `SCREEN_IMAGE_VIEWER` -> `ui_image_viewer_stop`, +`SCREEN_USB_STORAGE` -> `ui_usb_storage_stop`. -3. Add the case to the main switch statement: ```c -void ui_switch_screen(screen_id_t new_screen) { - if (ui_acquire()) { - // ... init/deinit logic ... - clear_current_screen(); - - switch (new_screen) { - // ... other cases ... - - case SCREEN_BLE_MENU: // <--- NEW ROUTE - ui_ble_menu_open(); - break; - } - // ... - } +static ui_close_fn_t screen_close_fn(screen_id_t s) { + switch (s) { + // ... other cases ... + case SCREEN_BLE_READ: + return ble_scanner_stop; // <--- release the radio/task on leave + default: + return NULL; + } } ``` -### 3. Create the New Screen UI -Create the folder and files for the new feature: `ui/screens/bluetooth/` +This supersedes the old advice of calling `ble_init()` / `ble_deinit()` inside +`ui_switch_screen`. A plain menu that owns no hardware needs no close hook. + +Enabling a radio for a whole area is done at the menu, not here: `menu_ui.c`'s +`ensure_radio_on()` powers Wi-Fi / BLE on when the user opens that area's menu. + +### 4. Create the screen source +Create the files under `ui/screens/bluetooth/`. -**Header File:** `ui_ble_menu.h` +**Header:** `ui/screens/bluetooth/include/ui_ble_menu.h` ```c #ifndef UI_BLE_MENU_H #define UI_BLE_MENU_H -#include "lvgl.h" -void ui_ble_menu_open(void); // Public function +void ui_ble_menu_open(void); #endif ``` -**Source File:** `ui_ble_menu.c` -Standard template from any Highboy screen: +**Source:** `ui/screens/bluetooth/ui_ble_menu.c`. The current template: ```c #include "ui_ble_menu.h" -#include "ui_manager.h" -#include "lv_port_indev.h" // Access to main_group + #include "esp_log.h" -static const char *TAG = "UI_BLE"; -static lv_obj_t * screen_ble = NULL; - -// 1. Event Callback (Navigation) -static void ble_event_cb(lv_event_t * e) { - lv_event_code_t code = lv_event_get_code(e); - - if (code == LV_EVENT_KEY) { - uint32_t key = lv_event_get_key(e); - // BACK BUTTON (ESC/LEFT) - if (key == LV_KEY_ESC || key == LV_KEY_LEFT) { - ESP_LOGI(TAG, "Returning to Main Menu"); - // Destroy current screen and open Menu - ui_switch_screen(SCREEN_MENU); - } - } +#include "menu_component_ui.h" +#include "ui_manager.h" +#include "ui_metrics.h" +#include "ui_theme.h" + +static const char *TAG = "UI_BLE_MENU"; + +static lv_obj_t *s_screen = NULL; +static menu_component_t s_menu; + +// Event-driven input: one debounced event at a time. The central pump only +// calls this while input is unlocked and no modal overlay is up. UP/DOWN also +// act on REPEAT for held auto-scroll; actions use PRESS only. +static void ble_menu_input(const input_event_t *ev, void *ctx) { + (void)ctx; + const bool press = (ev->action == INPUT_ACTION_PRESS); + const bool nav = press || (ev->action == INPUT_ACTION_REPEAT); + switch (ev->button) { + case INPUT_BTN_DOWN: + if (nav) menu_component_next(&s_menu); + break; + case INPUT_BTN_UP: + if (nav) menu_component_prev(&s_menu); + break; + case INPUT_BTN_BACK: + case INPUT_BTN_LEFT: + if (press) ui_switch_screen(SCREEN_MENU); + break; + case INPUT_BTN_OK: + case INPUT_BTN_RIGHT: + if (press) { /* enter the selected item */ } + break; + default: + break; + } } -// 2. Screen Build Function void ui_ble_menu_open(void) { - // Safety cleanup - if (screen_ble) { - lv_obj_del(screen_ble); - screen_ble = NULL; - } - - // A. Create Base Screen - screen_ble = lv_obj_create(NULL); - lv_obj_set_style_bg_color(screen_ble, lv_color_black(), 0); - - // B. Add Content (e.g., Title) - lv_obj_t * label = lv_label_create(screen_ble); - lv_label_set_text(label, "Bluetooth Menu"); - lv_obj_set_style_text_color(label, lv_color_white(), 0); - lv_obj_align(label, LV_ALIGN_CENTER, 0, 0); - - // C. Setup Navigation - lv_obj_add_event_cb(screen_ble, ble_event_cb, LV_EVENT_KEY, NULL); - - // Add to Input Group (Essential!) - if (main_group) { - lv_group_add_obj(main_group, screen_ble); - lv_group_focus_obj(screen_ble); - } - - // D. Load Screen - lv_screen_load(screen_ble); + if (s_screen != NULL) { + lv_obj_del(s_screen); + s_screen = NULL; + } + + s_screen = lv_obj_create(NULL); + lv_obj_set_style_bg_color(s_screen, current_theme.screen_base, 0); + lv_obj_set_style_bg_opa(s_screen, LV_OPA_COVER, 0); + lv_obj_remove_flag(s_screen, LV_OBJ_FLAG_SCROLLABLE); + + s_menu = menu_component_create(s_screen, "BLUETOOTH", "/assets/icons/bluetooth.bin"); + // ... add items ... + + ui_input_set_screen_handler(ble_menu_input, NULL); + + ui_screen_load_owned(&s_screen, s_screen); } ``` -### 4. Link from the main Menu -Add a button/entru in the main menu to access the new screen - -**File:** `ui/screens/menu/ui_menu.c` -1. In the `menu_event_cb` callback, locate the corresponding item ID case and add/uncomment the call: +Points that differ from older screens - all required for new screens: + +- **Input is event-driven.** Register a handler with + `ui_input_set_screen_handler(handler, ctx)`; it receives an `input_event_t` + (`ev->button` in `INPUT_BTN_UP..BACK`, `ev->action` in + `INPUT_ACTION_PRESS` / `RELEASE` / `LONG_PRESS` / `REPEAT`, both from + `input_manager.h`). No `LV_EVENT_KEY` callback, no `lv_event_get_key`, no + manual `main_group` focus, no `s_*_last` edge bookkeeping. The handler is + cleared for you on the next screen switch. See + [input-migration.md](input-migration.md) for the full pattern and gotchas. +- **Load the screen owned.** Use `ui_screen_load_owned(&s_screen, scr)` instead + of raw `lv_screen_load`. When the object is freed on navigation, the slot is + set back to `NULL`, so the screen never double-frees or dereferences a stale + pointer. Do not manually `lv_obj_del` the previous screen in the open + function beyond the guarded self-cleanup shown above. +- **Be rotation-aware.** Never hardcode `LCD_H_RES` / `LCD_V_RES` for layout; + those are fixed panel constants. Use `ui_screen_w()` / `ui_screen_h()` from + `ui_metrics.h` (they follow the live rotation). If the screen must reflow + after a rotation change, call `ui_relayout_current_screen()`. When a component + polls button levels directly, use `ui_nav_pressed(logical)` so it navigates + correctly in landscape. +- **Schedule onto the UI thread from workers.** Any worker/radio task that + needs to touch LVGL must marshal through `ui_async_call()` (the required + wrapper around `lv_async_call`; it takes the UI lock first). Never call LVGL + directly from another task. +- **Styling uses the theme.** Pull colors from `current_theme` in + `ui_theme.h` (`screen_base`, `bg_primary`, `text_main`, `border_accent`, + ...). For a protocol screen, set the active protocol with + `ui_theme_set_protocol(PROTOCOL_BLE)` and read the accent with + `ui_theme_get_accent()`, or use the per-protocol fields directly + (`current_theme.protocol_ble`, `protocol_nfc`, `protocol_wifi`, + `protocol_subghz`, `protocol_rfid`, `protocol_ir`, `protocol_lora`). Do not + hardcode `lv_color_black()` / `lv_color_white()`. + +### 5. Link from the main menu +The main menu (`ui/screens/menu/menu_ui.c`) is a data table: each +`menu_ui_item_t` carries a `target` of type `screen_id_t`. Add (or point) an +entry at the new id: ```c -case MENU_ID_BLUETOOTH: - ui_switch_screen(SCREEN_BLE_MENU); // <--- Routes to the new screen - break; +{"BLUETOOTH", + { /* icon frames */ }, + BASE_FRAMES, {NULL}, {NULL}, + SCREEN_BLE_MENU}, // <--- target ``` -(Note: If the MENU_ID_BLUETOOTH entry doesn't exist yet in menu_item_id_t, create it.) +Selecting the item calls `ensure_radio_on(target)` and then +`ui_switch_screen(target)`; you do not write a per-item `case`. + +### 6. Build system (CMake) +**File:** `components/Applications/CMakeLists.txt` (this is the `Applications` +component; there is no separate CMakeLists under `ui/`). -### 5. Update Build System (CMake) -Commom error: forgettint to register the new source files. +Sources are picked up automatically: `file(GLOB_RECURSE UI_SRCS "ui/*.c")` +globs every `.c` under `ui/`, so a new screen source compiles without editing +the SRCS list. Because it is a glob, **adding a new `.c` needs a reconfigure** +(`idf.py reconfigure`, or a clean build) to re-run it. -**File:** `CMakeLists.txt` (UI component) -1. Add the new sources files and include directory: +The only hand-maintained part is `INCLUDE_DIRS`: when you introduce a brand-new +screen *area* (a new folder with its own `include/`), add its include dir: ```cmake -file(GLOB_RECURSE HOME_UI_SRCS "ui/screens/home/*.c") -file(GLOB_RECURSE MENU_UI_SRCS "ui/screens/menu/*.c") -file(GLOB_RECURSE WIFI_UI_SRCS "ui/screens/wifi/*.c") -file(GLOB_RECURSE BLE_UI_SRCS "ui/screens/ble/*.c") # <---- Add srcs here - -idf_component_register(SRCS - "ui/ui_manager.c" - ${HOME_UI_SRCS} - ${MENU_UI_SRCS} - ${WIFI_UI_SRCS} - ${BLE_UI_SRCS} # <----- and call it here - - INCLUDE_DIRS - "ui/include" - "ui/screens/home/include" - "ui/screens/menu/include" - "ui/screens/wifi/include" - "ui/screens/ble/include" # <----- dont forget include files -) + # --- ui screens --- + "ui/screens/bluetooth/include" # note: screens/bluetooth/, there is no screens/ble/ ``` -2. Recommended: Run `idf.py reconfigure` in the terminal after saving +An existing area (like `bluetooth`) already has its include dir listed, so a +new screen inside it needs nothing here. --- @@ -224,9 +267,10 @@ That is all: no timer to create or delete, no `ui_input_is_locked()` / you on the next screen switch. Use `REPEAT` for held auto-scroll and `input_is_down(button)` when you need a continuous held state (games). -See `nfc_menu_ui.c` for the reference migration, and -[input-migration.md](input-migration.md) for the step-by-step guide to convert -the remaining ~91 screens off their polling timers (with the gotchas). +See `nfc_menu_ui.c` for the reference handler, and +[input-migration.md](input-migration.md) for the full pattern and gotchas. Every +screen already uses this model; the lone exception is `games/octopet_ui.c`, +which keeps a poll timer on purpose for continuous held-direction movement. ## Screen power policy (auto-dim / sleep) @@ -249,16 +293,19 @@ lock. It reads `input_last_activity_ms()` (from `input_manager`) and the The display settings screen writes `auto_lock_seconds` / `auto_dim` / brightness through `tos_config`, which is the sole writer of the `screen` config file. -## Execution Flow Sumamary -1. User selects **Bluetooth** from the Main Menu. -2. Menu callback calls `ui_switch_screen(SCREEN_BLE_MENU)`. -3. `ui_manager`: - - Handles hardware power (enables BLE if needed). - - Clears previous screen. - - Calls `ui_ble_menu_open()`. +## Execution flow summary +1. User selects **Bluetooth** in the main menu. +2. The menu resolves the item's `target` (`SCREEN_BLE_MENU`), calls + `ensure_radio_on(target)` (powers BLE on for the area), then + `ui_switch_screen(SCREEN_BLE_MENU)`. +3. `ui_switch_screen`: + - Looks the id up in `screen_open_fn()`; stays put if it is `NULL`. + - Calls the outgoing screen's `screen_close_fn()` stop hook, if any. + - Clears the previous screen (and the old input handler). + - Calls the resolved open function, `ui_ble_menu_open()`. 4. `ui_ble_menu_open`: - - Creates visual objects. - - Adds objects to `main_group`. - - Loads the screen. + - Builds the screen objects (theme colors, rotation-aware layout). + - Registers its input handler with `ui_input_set_screen_handler()`. + - Loads the screen with `ui_screen_load_owned(&s_screen, scr)`. -**Done! The new screen is fully integrated, safe and navigable.** +**Done: the new screen is integrated, safe and navigable.** diff --git a/docs/ui/input-migration.md b/docs/ui/input-migration.md index cdd29a624..8e4d5ecb1 100644 --- a/docs/ui/input-migration.md +++ b/docs/ui/input-migration.md @@ -1,18 +1,25 @@ # Screen input migration guide -How to convert one screen from the old per-screen polling `lv_timer` to the -central event-driven input. Background: [input_manager](../input_manager/README.md), +How a screen wires up input: the central event-driven model, and how it was +converted from the old per-screen polling `lv_timer`. Background: +[input_manager](../input_manager/README.md), [ui: event-driven handlers](README.md#input-event-driven-handlers). -**Status:** 7 screens migrated (see [Reference examples](#reference-examples)); -~91 still use `nav_timer_cb`. Find the remaining ones with: +**Status: migration complete (historical reference).** Every screen now +registers an input handler with `ui_input_set_screen_handler` (~110 screen +files). The single remaining `nav_timer_cb` is in `games/octopet_ui.c`, a +deliberate continuous-held-state case (see the games gotcha below), not a +pending TODO. Confirm with: ```sh -grep -rln "nav_timer_cb\|nav_cb\b" firmware_p4/components/Applications/ui/screens --include='*.c' +grep -rln "ui_input_set_screen_handler" firmware_p4/components/Applications/ui/screens --include='*.c' | wc -l +grep -rln "nav_timer_cb" firmware_p4/components/Applications/ui/screens --include='*.c' ``` -Nothing is broken in the meantime: unmigrated screens keep working through the -`buttons_gpio` shim. Migrate opportunistically, build after each batch. +This document is kept as a reference: the recipe and gotchas below describe the +current handler pattern, so use it when adding a new screen or if you ever find +a stray polling timer. The reference-examples table still points at real, +representative screens. ## What you are replacing @@ -110,14 +117,12 @@ Copy the closest match: | `bluetooth/ui_ble_menu.c` | Forward decl + `MENU_ITEMS` target dispatch | | `games/games_menu_ui.c` | Carousel: LEFT/UP and RIGHT/DOWN cycle an index with REPEAT | -## Remaining work (~91 screens) +## The one intentional exception -By area (run the grep above for the live list): bluetooth 15, wifi 14, nfc 13, -settings 11, lora 8, infrared 7, subghz 5, audio 4, dev 3, games 2, badusb 2, -and one each in theme, rfid, haptic, gpio, files, connect_wifi, -connection_settings. The simple area menus (settings, submenus) are quickest; -the games and multi-view NFC/RFID screens need the continuous-state / multi-view -handling above. +`games/octopet_ui.c` keeps a `nav_timer_cb` on purpose: the pet moves while a +direction is held, which is a continuous-held-state case (see the games gotcha +above), not something a pure press/edge handler models well. It reads button +levels from its own tick timer. This is by design; do not "migrate" it. ## Verify diff --git a/docs/wifi/README.md b/docs/wifi/README.md index 26fa45a1e..7ef61361f 100644 --- a/docs/wifi/README.md +++ b/docs/wifi/README.md @@ -1,19 +1,41 @@ +# Wi-Fi service + +Split across **both firmwares**. The **C5 owns the real radio** (the full esp-idf +Wi-Fi stack, AP/STA, scanning, promiscuous capture, config persistence). The **P4 +is a pure proxy**: every public call forwards to the C5 over the SPI bridge. Keep +this split in mind - the two `wifi_service` APIs look almost identical, but the +P4 one has no local Wi-Fi stack behind it. + +Both headers share the same file-path defines: + +```c +#define WIFI_AP_CONFIG_FILE "config/wifi/wifi_ap.conf" +#define WIFI_KNOWN_NETWORKS_FILE "storage/wifi/know_networks.json" +``` + +Both are relative paths handed to the `storage_assets` API; the actual files +live on the C5 (it owns storage for Wi-Fi state). + +--- + # P4 -This component manages Wi-Fi functionalities including Access Point (AP) mode, Station (STA) mode, scanning, and configuration persistence using JSON files. +`firmware_p4/components/Service/wifi/` - a **thin SPI-bridge proxy** to the C5. +There is **no local esp-idf Wi-Fi stack on the P4**: no APSTA mode, no NVS init, +no default event loop, no netif / static IP / DHCP server, no `cJSON`, no local +channel-hop task, and no LED signalling in this module. Every operation is a +`spi_bridge_send_command` (or `spi_bridge_run_scan`) to the C5. -## Functionality Overview +## How state is read without blocking the UI -The service handles: -- **Initialization/Deinitialization:** Setup of NVS, Netif, Event Loops, and Wi-Fi drivers. -- **Access Point (AP):** Configurable SSID, password, max connections, and custom IP address. -- **Scanning:** Active scanning for nearby networks. -- **Station (STA):** Connecting to external Wi-Fi networks. -- **Hotspot Management:** Dynamic switching of AP configuration. -- **Promiscuous Mode:** Low-level packet sniffing and environment monitoring. -- **Channel Hopping:** Automated cycling through Wi-Fi channels for environment monitoring. -- **Configuration Persistence:** AP/client settings loaded via `tos_config_load_all()` from SD (`config/wifi.conf`) with flash fallback (`/assets/config/wifi/wifi_ap.conf`). -- **Known Networks:** Automatically saves connected network credentials to `wifi/` on SD card. +The header getters (`wifi_service_is_active` / `wifi_service_is_connected`) are +polled from an `lv_timer` on the LVGL thread. Doing a blocking SPI RPC there +froze the renderer whenever the bridge was busy (e.g. during a scan, which holds +the bridge mutex for seconds). So a background `status_poll_task` (pinned to +`SYS_CORE_RADIO`, priority `SYS_PRIO_BACKGROUND`) polls `SPI_ID_SYSTEM_STATUS` +once per second and caches `wifi_active` / `wifi_connected` into volatile flags; +the getters just read those flags. The same poll also feeds +`bluetooth_service_set_running_cached`. ## API Functions @@ -23,51 +45,51 @@ The service handles: ```c void wifi_service_init(void); ``` -Initializes the Wi-Fi stack in `APSTA` mode. -- Initializes NVS (performing erase if necessary). -- Sets up the default event loop and registers handlers. -- Loads AP configuration from storage (or uses defaults "Darth Maul"/"MyPassword123"). -- Configures the static IP (default: 192.168.4.1) and starts the DHCP server. +Spawns the background `status_poll_task` (once). It does **not** bring up any +radio - the C5 owns that. #### `wifi_service_deinit` ```c void wifi_service_deinit(void); ``` -Completely shuts down the Wi-Fi service. -- Stops the Wi-Fi driver. -- Unregisters event handlers. -- Deinitializes the driver. -- Frees synchronization primitives (mutexes) and clears static data. +Sends `SPI_ID_WIFI_STOP` to the C5 (functionally identical to +`wifi_service_stop`; there is no local stack to tear down). #### `wifi_service_start` / `wifi_service_stop` ```c void wifi_service_start(void); void wifi_service_stop(void); ``` -Simple wrappers to start or stop the Wi-Fi driver without full deinitialization. `wifi_service_stop` also clears stored scan results. +Forward `SPI_ID_WIFI_START` / `SPI_ID_WIFI_STOP` to the C5. ### Scanning #### `wifi_service_scan` ```c -void wifi_service_scan(void); +esp_err_t wifi_service_scan(void); ``` -Performs an active Wi-Fi scan. -- Uses a mutex to ensure thread safety. -- Stores up to `WIFI_SCAN_LIST_SIZE` results internally. -- Provides visual feedback via LEDs (Green for AP connection, Red for failures, Blue for scan success). +**Async, proxied.** The C5 runs the scan. This calls +`spi_bridge_run_scan(SPI_ID_WIFI_SCAN, SPI_ID_WIFI_SCAN_STATUS, NULL, 0)`, which +kicks the scan and polls the scan-status id **without holding the bridge mutex** +(so the UI and other peripherals stay free), returning once the C5 reports +completion. Returns `esp_err_t`. #### `wifi_service_get_ap_count` ```c uint16_t wifi_service_get_ap_count(void); ``` -Returns the number of networks found in the last scan. +Fetches the last-scan count from the C5 via `SPI_ID_SYSTEM_DATA` (with the +`SPI_DATA_INDEX_COUNT` magic index). Returns 0 on bridge failure. #### `wifi_service_get_ap_record` ```c -wifi_ap_record_t* wifi_service_get_ap_record(uint16_t index); +wifi_ap_record_t *wifi_service_get_ap_record(uint16_t index); ``` -Retrieves a pointer to a specific scan result record. Returns `NULL` if the index is invalid. +Fetches one scan record from the C5 (`SPI_ID_SYSTEM_DATA`, indexed) into a static +cache and returns a pointer to it, or `NULL` on failure. Sanitizes the SSID at +this single point for every consumer (console + UI): non-printable / non-ASCII +bytes become `?` (they hang LVGL's text renderer), and an empty SSID (hidden +network) is replaced with the placeholder `[rede oculta]`. ### Connection & Management @@ -75,132 +97,105 @@ Retrieves a pointer to a specific scan result record. Returns `NULL` if the inde ```c esp_err_t wifi_service_connect_to_ap(const char *ssid, const char *password); ``` -Connects the device (as a station) to an external Access Point. -- Configures authentication mode based on the presence of a password (WPA2_PSK or OPEN). -- Disconnects any existing connection before attempting a new one. -- **Persistence:** Automatically saves the SSID and password to `assets/storage/wifi/know_networks.json`. If the network already exists, the password is updated. +Packs the SSID/password into `spi_wifi_connect_t` and forwards +`SPI_ID_WIFI_CONNECT` to the C5. The C5 performs the actual join and known-network +persistence. #### `wifi_service_is_connected` ```c bool wifi_service_is_connected(void); ``` -Returns `true` if the device is currently connected to an external Wi-Fi network and has an IP address. +Returns the cached `wifi_connected` flag (refreshed by `status_poll_task`). Does +**not** hit the bridge. #### `wifi_service_is_active` ```c bool wifi_service_is_active(void); ``` -Returns `true` if the Wi-Fi service is started (driver initialized and interface up). +Returns the cached `wifi_active` flag (refreshed by `status_poll_task`). Does +**not** hit the bridge. #### `wifi_service_get_connected_ssid` ```c -const char* wifi_service_get_connected_ssid(void); +const char *wifi_service_get_connected_ssid(void); ``` -Returns the SSID of the currently connected network. Returns `NULL` if not connected. +Fetches the STA SSID from the C5 via `SPI_ID_WIFI_GET_STA_INFO` into a static +buffer; returns it (NUL-terminated) or `NULL` on failure. #### `wifi_service_change_to_hotspot` ```c void wifi_service_change_to_hotspot(const char *new_ssid); ``` -Dynamically reconfigures the device's Access Point to an **Open** network with the specified SSID. -- Stops the Wi-Fi driver briefly to apply changes. -- Sets `authmode` to `WIFI_AUTH_OPEN`. -- Restarts Wi-Fi with the new configuration. +On the P4 this is just a wrapper around `wifi_service_set_ap_ssid(new_ssid)`. ### Promiscuous Mode -#### `wifi_service_promiscuous_start` +#### `wifi_service_promiscuous_start` / `wifi_service_promiscuous_stop` ```c void wifi_service_promiscuous_start(wifi_promiscuous_cb_t cb, wifi_promiscuous_filter_t *filter); -``` -Enables promiscuous mode (sniffer) with a custom callback and filter. -- `cb`: Function to handle captured packets. -- `filter`: Filter mask (e.g., `WIFI_PROMIS_FILTER_MASK_MGMT`). - -#### `wifi_service_promiscuous_stop` -```c void wifi_service_promiscuous_stop(void); ``` -Disables promiscuous mode and clears the callback. +Forward `SPI_ID_WIFI_PROMISC_START` / `SPI_ID_WIFI_PROMISC_STOP`. The `cb` and +`filter` arguments are **ignored** on the P4 (the capture and its callback run on +the C5); the signature is kept for source compatibility. If the C5 replies +`ESP_ERR_NOT_SUPPORTED`, a warning is logged. ### Channel Hopping -#### `wifi_service_start_channel_hopping` +#### `wifi_service_start_channel_hopping` / `wifi_service_stop_channel_hopping` ```c void wifi_service_start_channel_hopping(void); -``` -Starts a background task that cycles the Wi-Fi interface through channels 1 to 13. -- Useful for promiscuous mode applications (e.g., deauth detection). -- Task memory is allocated in PSRAM if available. - -#### `wifi_service_stop_channel_hopping` -```c void wifi_service_stop_channel_hopping(void); ``` -Stops the channel hopping task and frees associated memory resources. +Forward `SPI_ID_WIFI_CH_HOP_START` / `SPI_ID_WIFI_CH_HOP_STOP`. The hopping task +itself lives on the C5. ### Configuration Storage +All setters forward to the C5, which persists to `WIFI_AP_CONFIG_FILE` and +applies any radio state change. + #### `wifi_service_save_ap_config` ```c -esp_err_t wifi_service_save_ap_config(const char *ssid, const char *password, uint8_t max_conn, const char *ip_addr, bool enabled); +esp_err_t wifi_service_save_ap_config( + const char *ssid, const char *password, uint8_t max_conn, const char *ip_addr, bool enabled); ``` -Saves the AP configuration to a JSON file (`/assets/config/wifi/wifi_ap.conf`). -- Uses `cJSON` to serialize settings. -- Persists data using the storage API. -- **State Management:** If `enabled` is `true` and Wi-Fi is inactive, it calls `wifi_service_start()`. If `enabled` is `false` and Wi-Fi is active, it calls `wifi_service_stop()`. +Packs the fields into `spi_wifi_ap_config_t` and forwards +`SPI_ID_WIFI_SAVE_AP_CONFIG`. Returns `ESP_ERR_INVALID_ARG` if `ssid` is NULL. #### Individual Setters -Helper functions to update a single configuration parameter while preserving others. They automatically save the config and trigger state changes if `enabled` is toggled. - ```c -esp_err_t wifi_service_set_enabled(bool enabled); -esp_err_t wifi_service_set_ap_ssid(const char *ssid); -esp_err_t wifi_service_set_ap_password(const char *password); -esp_err_t wifi_service_set_ap_max_conn(uint8_t max_conn); -esp_err_t wifi_service_set_ap_ip(const char *ip_addr); +esp_err_t wifi_service_set_enabled(bool enabled); // SPI_ID_WIFI_SET_ENABLED +esp_err_t wifi_service_set_ap_ssid(const char *ssid); // SPI_ID_WIFI_SET_AP +esp_err_t wifi_service_set_ap_password(const char *p); // SPI_ID_WIFI_SET_AP_PASSWORD +esp_err_t wifi_service_set_ap_max_conn(uint8_t n); // SPI_ID_WIFI_SET_AP_MAX_CONN +esp_err_t wifi_service_set_ap_ip(const char *ip_addr); // SPI_ID_WIFI_SET_AP_IP ``` - -**Internal Loader:** `wifi_service_load_ap_config` is called during initialization to read these settings. If `enabled` is found to be `false` in the config, `wifi_service_init` will initialize the driver but **not** start the radio. - -## Internal Implementation Details - -### Event Handling -A static `wifi_event_handler` manages Wi-Fi and IP events: -- **WIFI_EVENT_AP_STACONNECTED:** Logs the MAC of the connected station and blinks Green. -- **WIFI_EVENT_AP_STADISCONNECTED:** Blinks Red. -- **IP_EVENT_AP_STAIPASSIGNED:** Logs IP assignment and blinks Green. - -### Thread Safety -A `wifi_mutex` (Semaphore) is used to protect the scanning process (`wifi_service_scan`), preventing concurrent scan requests which could lead to resource conflicts. - -### Channel Hopping Task -The channel hopping feature runs as a static FreeRTOS task. It uses `esp_wifi_set_channel` to switch channels every 250ms. To optimize internal RAM usage, both the task stack and the Task Control Block (TCB) are allocated in **PSRAM** using the `SPIRAM` capability. - -### Castings & Memory Management -- **cJSON:** Used extensively for parsing and generating configuration files. -- **PSRAM Allocation:** Critical tasks and large buffers are allocated in PSRAM to preserve internal memory. -- **Type Casting:** `event_data` is cast to specific event structures (e.g., `wifi_event_ap_staconnected_t*`) within handlers. -- **String Handling:** `strncpy` is used safely with explicit null-termination to prevent buffer overflows when handling SSIDs and passwords. +Each forwards one SPI command. `wifi_service_set_ap_password` / +`wifi_service_set_ap_ip` return `ESP_ERR_INVALID_ARG` on a NULL argument. --- # C5 -This component manages Wi-Fi functionalities including Access Point (AP) mode, Station (STA) mode, scanning, and configuration persistence using JSON files. +`firmware_c5/components/Service/wifi/` - this is where the **real Wi-Fi stack +lives**. It manages AP mode, STA mode, scanning, promiscuous capture, channel +hopping, and JSON config persistence directly on the esp-idf Wi-Fi driver. ## Functionality Overview -The service handles: -- **Initialization/Deinitialization:** Setup of NVS, Netif, Event Loops, and Wi-Fi drivers. -- **Access Point (AP):** Configurable SSID, password, max connections, and custom IP address. -- **Scanning:** Active scanning for nearby networks. -- **Station (STA):** Connecting to external Wi-Fi networks. -- **Hotspot Management:** Dynamic switching of AP configuration. -- **Promiscuous Mode:** Low-level packet sniffing and environment monitoring. -- **Channel Hopping:** Automated cycling through Wi-Fi channels for environment monitoring. -- **Configuration Persistence:** Loading and saving AP settings to/from `assets/config/wifi/wifi_ap.conf`. -- **Known Networks:** Automatically saves connected network credentials to `assets/storage/wifi/know_networks.json`. +- **Initialization/Deinitialization:** NVS, Netif, default event loop + handlers, + and the Wi-Fi driver, brought up in `WIFI_MODE_APSTA`. +- **Access Point (AP):** Configurable SSID, password, max connections, custom + static IP (default `192.168.4.1`), DHCP server. +- **Scanning:** Active scan storing up to `WIFI_SCAN_LIST_SIZE` results. +- **Station (STA):** Joins external networks. +- **Promiscuous Mode / Channel Hopping:** Low-level capture plus a background task + cycling channels for environment monitoring. +- **Configuration Persistence:** AP settings to/from `WIFI_AP_CONFIG_FILE` + (`config/wifi/wifi_ap.conf`) via `storage_assets`. +- **Known Networks:** Connected credentials saved to `WIFI_KNOWN_NETWORKS_FILE` + (`storage/wifi/know_networks.json`). ## API Functions @@ -210,28 +205,38 @@ The service handles: ```c void wifi_service_init(void); ``` -Initializes the Wi-Fi stack in `APSTA` mode. -- Initializes NVS (performing erase if necessary). -- Sets up the default event loop and registers handlers. -- Loads AP configuration from storage (or uses defaults "Darth Maul"/"MyPassword123"). -- Configures the static IP (default: 192.168.4.1) and starts the DHCP server. +Initializes the Wi-Fi stack in `APSTA` mode: NVS (erase if needed), default event +loop + handlers, AP config load (defaults `Darth Maul` / `MyPassword123`), static +IP and DHCP server. If the loaded config has `enabled == false`, the driver is +initialized but the radio is **not** started. #### `wifi_service_deinit` ```c void wifi_service_deinit(void); ``` -Completely shuts down the Wi-Fi service. -- Stops the Wi-Fi driver. -- Unregisters event handlers. -- Deinitializes the driver. -- Frees synchronization primitives (mutexes) and clears static data. +Fully shuts down the service: stops the driver, unregisters handlers, deinits the +driver, frees mutexes, and clears static state. #### `wifi_service_start` / `wifi_service_stop` ```c void wifi_service_start(void); void wifi_service_stop(void); ``` -Simple wrappers to start or stop the Wi-Fi driver without full deinitialization. `wifi_service_stop` also clears stored scan results. +Start or stop the driver without full deinit. `wifi_service_stop` also clears +stored scan results. + +#### `wifi_service_is_busy` +```c +bool wifi_service_is_busy(void); +``` +Returns `true` while a capture (promiscuous sniffer) is running. + +#### `wifi_service_set_power_save` +```c +void wifi_service_set_power_save(bool deep); +``` +Sets the Wi-Fi modem-sleep depth via `esp_wifi_set_ps`: `deep == true` selects +`WIFI_PS_MAX_MODEM`, otherwise `WIFI_PS_MIN_MODEM`. ### Scanning @@ -239,10 +244,9 @@ Simple wrappers to start or stop the Wi-Fi driver without full deinitialization. ```c void wifi_service_scan(void); ``` -Performs an active Wi-Fi scan. -- Uses a mutex to ensure thread safety. -- Stores up to `WIFI_SCAN_LIST_SIZE` results internally. -- Provides visual feedback via LEDs (Green for AP connection, Red for failures, Blue for scan success). +Performs an active Wi-Fi scan (mutex-guarded, stops channel hopping first). Stores +up to `WIFI_SCAN_LIST_SIZE` results and gives LED feedback (red on failure, blue +on success). #### `wifi_service_get_ap_count` ```c @@ -252,9 +256,9 @@ Returns the number of networks found in the last scan. #### `wifi_service_get_ap_record` ```c -wifi_ap_record_t* wifi_service_get_ap_record(uint16_t index); +wifi_ap_record_t *wifi_service_get_ap_record(uint16_t index); ``` -Retrieves a pointer to a specific scan result record. Returns `NULL` if the index is invalid. +Returns a pointer to a specific scan result, or `NULL` if the index is invalid. ### Connection & Management @@ -262,37 +266,36 @@ Retrieves a pointer to a specific scan result record. Returns `NULL` if the inde ```c esp_err_t wifi_service_connect_to_ap(const char *ssid, const char *password); ``` -Connects the device (as a station) to an external Access Point. -- Configures authentication mode based on the presence of a password (WPA2_PSK or OPEN). -- Disconnects any existing connection before attempting a new one. -- **Persistence:** Automatically saves the SSID and password to `assets/storage/wifi/know_networks.json`. If the network already exists, the password is updated. +Connects (as STA) to an external AP. +- Auth mode chosen by password presence (WPA2_PSK or OPEN). +- Disconnects any existing connection first. +- **Persistence:** saves SSID/password to `WIFI_KNOWN_NETWORKS_FILE`, updating the + password if the network already exists. #### `wifi_service_is_connected` ```c bool wifi_service_is_connected(void); ``` -Returns `true` if the device is currently connected to an external Wi-Fi network and has an IP address. +Returns `true` if connected to an external network and holding an IP. #### `wifi_service_is_active` ```c bool wifi_service_is_active(void); ``` -Returns `true` if the Wi-Fi service is started (driver initialized and interface up). +Returns `true` if the service is started (driver up, interface up). #### `wifi_service_get_connected_ssid` ```c -const char* wifi_service_get_connected_ssid(void); +const char *wifi_service_get_connected_ssid(void); ``` -Returns the SSID of the currently connected network. Returns `NULL` if not connected. +Returns the connected SSID, or `NULL` if not connected. #### `wifi_service_change_to_hotspot` ```c void wifi_service_change_to_hotspot(const char *new_ssid); ``` -Dynamically reconfigures the device's Access Point to an **Open** network with the specified SSID. -- Stops the Wi-Fi driver briefly to apply changes. -- Sets `authmode` to `WIFI_AUTH_OPEN`. -- Restarts Wi-Fi with the new configuration. +Reconfigures the AP to an **Open** network with the given SSID: briefly stops the +driver, sets `authmode` to `WIFI_AUTH_OPEN`, restarts with the new config. ### Promiscuous Mode @@ -300,9 +303,8 @@ Dynamically reconfigures the device's Access Point to an **Open** network with t ```c void wifi_service_promiscuous_start(wifi_promiscuous_cb_t cb, wifi_promiscuous_filter_t *filter); ``` -Enables promiscuous mode (sniffer) with a custom callback and filter. -- `cb`: Function to handle captured packets. -- `filter`: Filter mask (e.g., `WIFI_PROMIS_FILTER_MASK_MGMT`). +Enables promiscuous (sniffer) mode with the given packet callback and filter mask +(e.g. `WIFI_PROMIS_FILTER_MASK_MGMT`; `NULL` for no filter). #### `wifi_service_promiscuous_stop` ```c @@ -316,30 +318,32 @@ Disables promiscuous mode and clears the callback. ```c void wifi_service_start_channel_hopping(void); ``` -Starts a background task that cycles the Wi-Fi interface through channels 1 to 13. -- Useful for promiscuous mode applications (e.g., deauth detection). -- Task memory is allocated in PSRAM if available. +Starts a background task cycling channels 1..13 (250 ms cadence via +`esp_wifi_set_channel`), useful for promiscuous applications (e.g. deauth +detection). Both the task stack and TCB are allocated in **PSRAM** (`SPIRAM`) to +spare internal RAM. #### `wifi_service_stop_channel_hopping` ```c void wifi_service_stop_channel_hopping(void); ``` -Stops the channel hopping task and frees associated memory resources. +Stops the channel-hopping task and frees its resources. ### Configuration Storage #### `wifi_service_save_ap_config` ```c -esp_err_t wifi_service_save_ap_config(const char *ssid, const char *password, uint8_t max_conn, const char *ip_addr, bool enabled); +esp_err_t wifi_service_save_ap_config( + const char *ssid, const char *password, uint8_t max_conn, const char *ip_addr, bool enabled); ``` -Saves the AP configuration to a JSON file (`/assets/config/wifi/wifi_ap.conf`). -- Uses `cJSON` to serialize settings. -- Persists data using the storage API. -- **State Management:** If `enabled` is `true` and Wi-Fi is inactive, it calls `wifi_service_start()`. If `enabled` is `false` and Wi-Fi is active, it calls `wifi_service_stop()`. +Serializes the settings with `cJSON` and writes them to `WIFI_AP_CONFIG_FILE`. +**State management:** if `enabled` is `true` and Wi-Fi is inactive it calls +`wifi_service_start()`; if `enabled` is `false` and Wi-Fi is active it calls +`wifi_service_stop()`. #### Individual Setters -Helper functions to update a single configuration parameter while preserving others. They automatically save the config and trigger state changes if `enabled` is toggled. - +Helpers that update a single parameter while preserving the rest, auto-saving and +triggering state changes when `enabled` toggles. ```c esp_err_t wifi_service_set_enabled(bool enabled); esp_err_t wifi_service_set_ap_ssid(const char *ssid); @@ -348,24 +352,27 @@ esp_err_t wifi_service_set_ap_max_conn(uint8_t max_conn); esp_err_t wifi_service_set_ap_ip(const char *ip_addr); ``` -**Internal Loader:** `wifi_service_load_ap_config` is called during initialization to read these settings. If `enabled` is found to be `false` in the config, `wifi_service_init` will initialize the driver but **not** start the radio. +**Internal loader:** an internal `load_ap_config` runs during init. If `enabled` +is `false` in the config, `wifi_service_init` brings the driver up but does **not** +start the radio. ## Internal Implementation Details ### Event Handling -A static `wifi_event_handler` manages Wi-Fi and IP events: -- **WIFI_EVENT_AP_STACONNECTED:** Logs the MAC of the connected station and blinks Green. -- **WIFI_EVENT_AP_STADISCONNECTED:** Blinks Red. -- **IP_EVENT_AP_STAIPASSIGNED:** Logs IP assignment and blinks Green. +A static `event_handler` (registered for `WIFI_EVENT` and `IP_EVENT`) manages +station connect/disconnect and IP-assignment events, logging and driving LED +feedback (green on connect / IP, red on disconnect). ### Thread Safety -A `wifi_mutex` (Semaphore) is used to protect the scanning process (`wifi_service_scan`), preventing concurrent scan requests which could lead to resource conflicts. +A mutex protects the scan path (`wifi_service_scan`) against concurrent scan +requests. ### Channel Hopping Task -The channel hopping feature runs as a static FreeRTOS task. It uses `esp_wifi_set_channel` to switch channels every 250ms. To optimize internal RAM usage, both the task stack and the Task Control Block (TCB) are allocated in **PSRAM** using the `SPIRAM` capability. - -### Castings & Memory Management -- **cJSON:** Used extensively for parsing and generating configuration files. -- **PSRAM Allocation:** Critical tasks and large buffers are allocated in PSRAM to preserve internal memory. -- **Type Casting:** `event_data` is cast to specific event structures (e.g., `wifi_event_ap_staconnected_t*`) within handlers. -- **String Handling:** `strncpy` is used safely with explicit null-termination to prevent buffer overflows when handling SSIDs and passwords. +Runs as a static FreeRTOS task; stack and TCB are placed in **PSRAM** (`SPIRAM`) +to preserve internal RAM. + +### Memory & String Handling +`cJSON` is used for config parse/serialize; SSIDs/passwords are copied with +`strncpy` plus explicit NUL-termination. + + diff --git a/docs/ys_rfid2/README.md b/docs/ys_rfid2/README.md new file mode 100644 index 000000000..f78a39017 --- /dev/null +++ b/docs/ys_rfid2/README.md @@ -0,0 +1,227 @@ +# YS-RFID2 UART RFID Reader Driver + +This component drives the YS-RFID2 serial RFID reader module. It reads the module's ASCII output over UART, parses the "card number" frames into card IDs, and delivers card-detected / card-removed events to the application through a callback. It is `firmware_p4` only. + +## Overview + +- **Location:** `firmware_p4/components/Drivers/ys_rfid2/` +- **Header:** `include/ys_rfid2.h` +- **Dependencies:** `pin_def`, `sys_prio`, `driver/uart`, `esp_timer`, `freertos` +- **Interface:** UART (via the ESP-IDF `driver/uart` layer, wrapped by the local HAL) +- **UART port:** `UART_NUM_2` (default) +- **Baud rate:** 9600 8N1, no flow control (default) +- **Pins:** TX = GPIO 24 (`GPIO_RFID_UART_TX_PIN`), RX = GPIO 25 (`GPIO_RFID_UART_RX_PIN`) + +## Module Structure + +The driver is split into three layers: + +| Layer | Files | Responsibility | +|--------|-------|----------------| +| Core | `ys_rfid2_core.c`, `include/ys_rfid2.h`, `include/ys_rfid2_types.h` | Public API, state machine, background reader task, debounce and removal logic, event dispatch. | +| Parser | `ys_rfid2_parser.c`, `include/ys_rfid2_parser.h` | Byte-by-byte frame accumulation, card ID extraction and validation, decimal-to-bytes conversion. | +| HAL | `hal/ys_rfid2_hal_uart.c`, `hal/include/ys_rfid2_hal_uart.h` | Thin UART wrapper: install/config/pins, read, write, flush. | + +Data flow: HAL UART reads raw bytes -> core reader task feeds each byte to the parser -> a completed valid frame becomes a `YS_RFID2_EVENT_CARD_DETECTED` event delivered to the user callback. + +## Frame Format and Parsing + +The module emits ASCII lines of the form: + +``` +card number: xxxxxxxxxx@ +``` + +The parser (`ys_rfid2_parser_feed`) accumulates bytes into a 64-byte line buffer until it sees the `@` delimiter. On the delimiter it searches the buffer for the `"card number: "` prefix (13 chars) followed by exactly 10 decimal digits (`YS_RFID2_CARD_ID_LEN`). If found and all 10 characters are digits `0-9`, it: + +- Copies the 10-digit ASCII string into `id_str` (NUL-terminated). +- Converts the decimal string to 5 big-endian raw bytes (40 bits) in `data`. +- Sets `bit_count` to 40. + +Any byte that overflows the line buffer resets the accumulator. The parser holds a single static line buffer, so it is not reentrant. + +## Data Types (`ys_rfid2_types.h`) + +Constants: + +| Constant | Value | Meaning | +|----------|-------|---------| +| `YS_RFID2_CARD_ID_LEN` | 10 | Card ID string length (decimal digits) | +| `YS_RFID2_RAW_DATA_LEN` | 5 | Raw byte count (40 bits) | + +### `ys_rfid2_state_t` + +`YS_RFID2_STATE_UNINITIALIZED`, `YS_RFID2_STATE_IDLE`, `YS_RFID2_STATE_SCANNING`, `YS_RFID2_STATE_ERROR`, `YS_RFID2_STATE_COUNT`. + +### `ys_rfid2_event_type_t` + +`YS_RFID2_EVENT_CARD_DETECTED`, `YS_RFID2_EVENT_CARD_REMOVED`, `YS_RFID2_EVENT_COUNT`. + +### `ys_rfid2_raw_data_t` +```c +typedef struct { + char id_str[YS_RFID2_CARD_ID_LEN + 1]; // 10-digit ID + NUL + uint8_t data[YS_RFID2_RAW_DATA_LEN]; // 5 raw bytes (40 bits, big-endian) + uint8_t bit_count; // 40 for a parsed card +} ys_rfid2_raw_data_t; +``` + +### `ys_rfid2_event_t` +```c +typedef struct { + ys_rfid2_event_type_t type; + ys_rfid2_raw_data_t raw; + int64_t timestamp_ms; // esp_timer time in ms at detection +} ys_rfid2_event_t; +``` + +### `ys_rfid2_event_cb_t` +```c +typedef void (*ys_rfid2_event_cb_t)(const ys_rfid2_event_t *event, void *ctx); +``` +Invoked from the reader task context. Must not block for extended periods. The `event` pointer is valid only during the callback. + +### `ys_rfid2_config_t` +```c +typedef struct { + int uart_port; + int baud_rate; + int tx_pin; + int rx_pin; + uint32_t debounce_ms; + uint32_t removal_timeout_ms; +} ys_rfid2_config_t; +``` + +## Configuration and Tunables + +`ys_rfid2_default_config()` returns: + +| Field | Default | Source | +|-------|---------|--------| +| `uart_port` | `UART_NUM_2` | fixed default | +| `baud_rate` | 9600 | `RFID_DEFAULT_BAUD` | +| `tx_pin` | GPIO 24 | `GPIO_RFID_UART_TX_PIN` (pin_def.h) | +| `rx_pin` | GPIO 25 | `GPIO_RFID_UART_RX_PIN` (pin_def.h) | +| `debounce_ms` | 1000 | `RFID_DEFAULT_DEBOUNCE_MS` | +| `removal_timeout_ms` | 2000 | `RFID_DEFAULT_REMOVAL_MS` | + +- **debounce_ms:** while the same card ID keeps being read, repeat detections within this window are suppressed (the timer is refreshed on each read). A different card ID fires immediately. +- **removal_timeout_ms:** if no byte is read for longer than this after the last detection, a `YS_RFID2_EVENT_CARD_REMOVED` event fires for the last card. + +Other internal constants (`ys_rfid2_core.c`): reader task stack 4096 bytes, priority `SYS_PRIO_SERVICE_HI`, UART read timeout 100 ms. The task is pinned to `SYS_CORE_RADIO` (core 0) via `xTaskCreatePinnedToCore`. + +## API Reference + +### Configuration + +#### `ys_rfid2_default_config` +```c +ys_rfid2_config_t ys_rfid2_default_config(void); +``` +Returns the default configuration described above. + +### Lifecycle + +#### `ys_rfid2_init` +```c +esp_err_t ys_rfid2_init(const ys_rfid2_config_t *config); +``` +Creates the driver mutex and initializes the UART via the HAL. Does NOT start scanning. Pass `NULL` to use the default config. Returns `ESP_OK`, `ESP_ERR_INVALID_STATE` if already initialized, `ESP_ERR_NO_MEM` if mutex creation fails, or a propagated HAL error. + +#### `ys_rfid2_deinit` +```c +esp_err_t ys_rfid2_deinit(void); +``` +Stops scanning if active, tears down the UART, and deletes the mutex. Returns `ESP_OK`, or `ESP_ERR_INVALID_STATE` if not initialized. + +### Scanning + +#### `ys_rfid2_start` +```c +esp_err_t ys_rfid2_start(ys_rfid2_event_cb_t cb, void *ctx); +``` +Resets the parser, flushes the UART input, and creates the background reader task that delivers events via `cb`. `cb` must not be NULL. Returns `ESP_OK`, `ESP_ERR_INVALID_ARG` if `cb` is NULL, `ESP_ERR_INVALID_STATE` if not initialized (state must be IDLE), or `ESP_ERR_NO_MEM` if task creation fails. + +#### `ys_rfid2_stop` +```c +void ys_rfid2_stop(void); +``` +Signals the reader task to stop and blocks until it has exited, then clears the callback. + +### State and Query + +#### `ys_rfid2_get_state` +```c +ys_rfid2_state_t ys_rfid2_get_state(void); +``` +Returns the current driver state. + +#### `ys_rfid2_get_last_card` +```c +esp_err_t ys_rfid2_get_last_card(ys_rfid2_event_t *out_event); +``` +Copies the last detected card event into `out_event` (mutex-guarded, 100 ms take timeout). Returns `ESP_OK` if a card was previously detected, `ESP_ERR_NOT_FOUND` if none yet, `ESP_ERR_INVALID_ARG` if `out_event` is NULL, or `ESP_ERR_TIMEOUT` if the mutex could not be taken. + +## HAL UART Layer (`ys_rfid2_hal_uart.h`) + +A thin wrapper over ESP-IDF `driver/uart`. It installs the driver with a 256-byte RX buffer, configures 8N1 with no flow control and `UART_SCLK_DEFAULT`, and sets TX/RX pins (no RTS/CTS). It holds a single static port, so only one instance is supported at a time. + +#### `ys_rfid2_hal_uart_config_t` +```c +typedef struct { + int port; + int baud_rate; + int tx_pin; + int rx_pin; +} ys_rfid2_hal_uart_config_t; +``` + +#### API +```c +esp_err_t ys_rfid2_hal_uart_init(const ys_rfid2_hal_uart_config_t *config); +void ys_rfid2_hal_uart_deinit(void); +int ys_rfid2_hal_uart_read(uint8_t *out_data, size_t len, uint32_t timeout_ms); +esp_err_t ys_rfid2_hal_uart_write(const uint8_t *data, size_t len); +void ys_rfid2_hal_uart_flush(void); +``` + +- `ys_rfid2_hal_uart_init`: returns `ESP_OK`, `ESP_ERR_INVALID_ARG` if `config` is NULL, `ESP_ERR_INVALID_STATE` if already initialized, or a propagated `uart_*` error. +- `ys_rfid2_hal_uart_read`: wraps `uart_read_bytes`. Returns the number of bytes read, or -1 on error / not initialized. +- `ys_rfid2_hal_uart_write`: wraps `uart_write_bytes`. Returns `ESP_OK`, `ESP_ERR_INVALID_STATE` if not initialized or `data` is NULL, or `ESP_FAIL` on write error. +- `ys_rfid2_hal_uart_flush`: flushes the UART input buffer. + +## Parser Layer (`ys_rfid2_parser.h`) + +#### `ys_rfid2_parser_reset` +```c +void ys_rfid2_parser_reset(void); +``` +Discards accumulated bytes and resets the line position. + +#### `ys_rfid2_parser_feed` +```c +bool ys_rfid2_parser_feed(uint8_t byte, ys_rfid2_raw_data_t *out_raw); +``` +Feeds one byte. Returns `true` and fills `out_raw` when a complete, valid `"card number: xxxxxxxxxx@"` frame is parsed; otherwise `false`. + +## Usage Example + +```c +static void on_card(const ys_rfid2_event_t *ev, void *ctx) { + if (ev->type == YS_RFID2_EVENT_CARD_DETECTED) { + ESP_LOGI("APP", "Card: %s", ev->raw.id_str); + } else { + ESP_LOGI("APP", "Card removed"); + } +} + +void app(void) { + ys_rfid2_config_t cfg = ys_rfid2_default_config(); + ESP_ERROR_CHECK(ys_rfid2_init(&cfg)); + ESP_ERROR_CHECK(ys_rfid2_start(on_card, NULL)); + // ... + ys_rfid2_stop(); + ys_rfid2_deinit(); +} +``` diff --git a/firmware_c5/components/Applications/wifi/wifi_deauther.c b/firmware_c5/components/Applications/wifi/wifi_deauther.c index dd1906df4..a1dcd463b 100644 --- a/firmware_c5/components/Applications/wifi/wifi_deauther.c +++ b/firmware_c5/components/Applications/wifi/wifi_deauther.c @@ -64,12 +64,8 @@ static void deauth_task(void *pvParameters); void wifi_deauther_send_raw_frame(const uint8_t *frame_buffer, int size) { esp_err_t ret = esp_wifi_80211_tx(WIFI_IF_AP, frame_buffer, size, false); - if (ret != ESP_OK) { + if (ret != ESP_OK) ESP_LOGD(TAG, "TX Fail: 0x%x", ret); - led_blink_red(); - } else { - led_blink_green(); - } } void wifi_deauther_send_deauth_frame(const wifi_ap_record_t *ap_record, @@ -159,7 +155,6 @@ bool wifi_deauther_start(const wifi_ap_record_t *ap_record, if (ap_record == NULL) return false; - // Clean up previous task memory if it wasn't freed if (s_deauth_task_stack != NULL) { free(s_deauth_task_stack); s_deauth_task_stack = NULL; @@ -262,7 +257,6 @@ bool wifi_deauther_start_targeted(const wifi_ap_record_t *ap_record, void wifi_deauther_stop(void) { if (s_is_running) { s_is_running = false; - // Give task time to finish loop and delete itself vTaskDelay(pdMS_TO_TICKS(DEAUTHER_DELAY_MS + DEAUTHER_STOP_MARGIN_MS)); } } @@ -290,6 +284,7 @@ static const uint8_t *get_deauth_frame_template(wifi_deauther_frame_type_t type) static void deauth_task(void *pvParameters) { ESP_LOGI(TAG, "Deauther Task Started"); + led_blink_green(); const uint8_t *frame_template = get_deauth_frame_template(s_type); uint8_t frame[DEAUTH_FRAME_LEN]; diff --git a/firmware_c5/components/Applications/wifi/wifi_sniffer.c b/firmware_c5/components/Applications/wifi/wifi_sniffer.c index ad3b4b9b4..3d2b6216b 100644 --- a/firmware_c5/components/Applications/wifi/wifi_sniffer.c +++ b/firmware_c5/components/Applications/wifi/wifi_sniffer.c @@ -793,19 +793,36 @@ static void sniffer_callback(void *buf, wifi_promiscuous_pkt_type_t type) { } if (is_save && spi_bridge_stream_is_enabled(SPI_ID_WIFI_APP_SNIFFER)) { + uint16_t total_len = ppkt->rx_ctrl.sig_len; + if (total_len > SPI_WIFI_SNIFFER_FRAME_MAX) + total_len = SPI_WIFI_SNIFFER_FRAME_MAX; // bounded by the P4 reassembly buffer + uint8_t stream_buf[SPI_MAX_PAYLOAD]; - spi_wifi_sniffer_frame_t *stream = (spi_wifi_sniffer_frame_t *)stream_buf; - uint16_t raw_len = ppkt->rx_ctrl.sig_len; - if (raw_len > SPI_WIFI_SNIFFER_MAX_DATA) - raw_len = SPI_WIFI_SNIFFER_MAX_DATA; - stream->rssi = ppkt->rx_ctrl.rssi; - stream->channel = ppkt->rx_ctrl.channel; - stream->len = (uint8_t)raw_len; - memcpy(stream->data, ppkt->payload, raw_len); - if (s_session_id != SPI_SESSION_INVALID_ID) { - session_manager_try_emit(s_session_id, stream_buf, (uint8_t)(raw_len + 3)); - } else { - spi_bridge_stream_push(SPI_ID_WIFI_APP_SNIFFER, stream_buf, (uint8_t)(raw_len + 3)); + spi_wifi_sniffer_frame_t *frag = (spi_wifi_sniffer_frame_t *)stream_buf; + + // Split the frame into ordered fragments; the P4 reassembles them. A frame + // that fits in one transfer is just a single fragment (offset 0, no MORE). + for (uint16_t off = 0; off < total_len;) { + uint16_t chunk = total_len - off; + if (chunk > SPI_WIFI_SNIFFER_FRAG_DATA_MAX) + chunk = SPI_WIFI_SNIFFER_FRAG_DATA_MAX; + + frag->rssi = ppkt->rx_ctrl.rssi; + frag->channel = ppkt->rx_ctrl.channel; + frag->total_len = total_len; + frag->frag_off = off; + frag->frag_len = (uint8_t)chunk; + frag->flags = ((off + chunk) < total_len) ? SPI_WIFI_SNIFFER_FRAG_MORE : 0; + memcpy(frag->data, ppkt->payload + off, chunk); + + uint8_t buf_len = (uint8_t)(sizeof(spi_wifi_sniffer_frame_t) + chunk); + bool ok = (s_session_id != SPI_SESSION_INVALID_ID) + ? (session_manager_try_emit(s_session_id, stream_buf, buf_len) == ESP_OK) + : spi_bridge_stream_push(SPI_ID_WIFI_APP_SNIFFER, stream_buf, buf_len); + if (!ok) + break; // backpressure: drop the rest; the P4 discards the partial frame + + off += chunk; } } diff --git a/firmware_c5/components/Drivers/i2c_init/README.md b/firmware_c5/components/Drivers/i2c_init/README.md new file mode 100644 index 000000000..f1b430fe9 --- /dev/null +++ b/firmware_c5/components/Drivers/i2c_init/README.md @@ -0,0 +1,7 @@ +# I2C Init - C5 + +Documentation for this component lives in the project docs hub (single source of truth): + +- [docs/i2c_init/README.md](../../../../docs/i2c_init/README.md) + +This file is only a pointer: edit the docs there to keep things from drifting. diff --git a/firmware_c5/components/Drivers/led/README.md b/firmware_c5/components/Drivers/led/README.md new file mode 100644 index 000000000..11dd3207b --- /dev/null +++ b/firmware_c5/components/Drivers/led/README.md @@ -0,0 +1,7 @@ +# LED - C5 (LP5816 status LED) + +Documentation for this component lives in the project docs hub (single source of truth): + +- [docs/led/README.md](../../../../docs/led/README.md) + +This file is only a pointer: edit the docs there to keep things from drifting. diff --git a/firmware_c5/components/Drivers/pins/README.md b/firmware_c5/components/Drivers/pins/README.md new file mode 100644 index 000000000..6a3242d82 --- /dev/null +++ b/firmware_c5/components/Drivers/pins/README.md @@ -0,0 +1,7 @@ +# Pins - C5 (GPIO map) + +Documentation for this component lives in the project docs hub (single source of truth): + +- [docs/pins/README.md](../../../../docs/pins/README.md) + +This file is only a pointer: edit the docs there to keep things from drifting. diff --git a/firmware_c5/components/Service/spi_bridge/include/spi_protocol.h b/firmware_c5/components/Service/spi_bridge/include/spi_protocol.h index 707abeab0..02c4c350e 100644 --- a/firmware_c5/components/Service/spi_bridge/include/spi_protocol.h +++ b/firmware_c5/components/Service/spi_bridge/include/spi_protocol.h @@ -575,15 +575,37 @@ typedef struct { } __attribute__((packed)) spi_wifi_scan_record_t; /** - * @brief WiFi sniffer stream frame. + * @brief WiFi sniffer stream fragment. + * + * A single 802.11 frame can exceed one SPI transfer (payload capped at + * SPI_MAX_PAYLOAD), so the C5 splits large frames into ordered fragments and + * the P4 reassembles them. rssi/channel/total_len are repeated on every + * fragment so the P4 can validate cheaply. frag_off is this fragment's byte + * offset within the full frame; SPI_WIFI_SNIFFER_FRAG_MORE is set on every + * fragment except the last. A frame that fits in one transfer is a single + * fragment with frag_off == 0 and the MORE bit clear. + * + * Max data bytes per fragment = SPI_MAX_PAYLOAD - sizeof(spi_stream_meta_t) + * - sizeof(spi_wifi_sniffer_frame_t). */ typedef struct { - int8_t rssi; - uint8_t channel; - uint8_t len; + int8_t rssi; // dBm signal of the frame + uint8_t channel; // primary channel + uint16_t total_len; // full 802.11 frame length across all fragments + uint16_t frag_off; // byte offset of this fragment within the frame + uint8_t frag_len; // data bytes carried by this fragment + uint8_t flags; // SPI_WIFI_SNIFFER_FRAG_* bits uint8_t data[0]; } __attribute__((packed)) spi_wifi_sniffer_frame_t; +#define SPI_WIFI_SNIFFER_FRAG_MORE 0x01u // more fragments follow this one +#define SPI_WIFI_SNIFFER_FRAME_MAX 2346 // max reassembled 802.11 frame (bytes) + +// Max frame bytes carried by a single fragment (transfer cap minus the stream +// meta prepended by the session layer minus this fragment header). +#define SPI_WIFI_SNIFFER_FRAG_DATA_MAX \ + ((int)(SPI_MAX_PAYLOAD - sizeof(spi_stream_meta_t) - sizeof(spi_wifi_sniffer_frame_t))) + /** * @brief BLE sniffer stream frame. */ diff --git a/firmware_c5/components/Service/wifi/wifi_service.c b/firmware_c5/components/Service/wifi/wifi_service.c index 1d865b5fd..2d709c898 100644 --- a/firmware_c5/components/Service/wifi/wifi_service.c +++ b/firmware_c5/components/Service/wifi/wifi_service.c @@ -76,8 +76,6 @@ static void get_config_defaults(char *out_ssid, char *out_ip_addr, bool *out_enabled); -// Public function implementations - void wifi_service_init(void) { esp_err_t err; @@ -114,8 +112,6 @@ void wifi_service_init(void) { ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA)); - // Dual-band (2.4 + 5 GHz) BEFORE configuring the AP: a stale 5G-only band mode - // persisted in NVS otherwise rejects the 2.4 GHz AP channel and aborts set_config. esp_err_t band_err = esp_wifi_set_band_mode(WIFI_BAND_MODE_AUTO); if (band_err != ESP_OK) { ESP_LOGW(TAG, "Could not enable dual-band: %s", esp_err_to_name(band_err)); @@ -166,10 +162,6 @@ void wifi_service_init(void) { if (is_enabled) { ESP_ERROR_CHECK(esp_wifi_start()); s_is_active = true; - // Enable modem sleep explicitly. It only takes effect for an idle, connected - // STA (radio wakes per DTIM); AP mode and promiscuous sniffing keep the radio - // in continuous RX regardless. The real idle savings need the P4 to signal - // low-power over the bridge so the C5 can drop the radio (see power state API). esp_wifi_set_ps(WIFI_PS_MIN_MODEM); ESP_LOGI(TAG, "Wi-Fi AP started with SSID: %s", target_ssid); } else { @@ -565,14 +557,10 @@ void wifi_service_promiscuous_stop(void) { } bool wifi_service_is_busy(void) { - // A capture is in flight (sniffer / deauth detector / handshake). Do not drop - // the radio for power saving while this is true. return s_promiscuous_active; } void wifi_service_set_power_save(bool deep) { - // deep = screen dimmed (MAX_MODEM, longer beacon skips); otherwise MIN_MODEM. - // Only meaningful for an idle connected STA; harmless otherwise. esp_wifi_set_ps(deep ? WIFI_PS_MAX_MODEM : WIFI_PS_MIN_MODEM); } @@ -637,31 +625,24 @@ void wifi_service_stop_channel_hopping(void) { ESP_LOGI(TAG, "Channel hopping stopped"); } -// Static function implementations - static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STACONNECTED) { wifi_event_ap_staconnected_t *event = (wifi_event_ap_staconnected_t *)event_data; ESP_LOGI(TAG, "Station connected to AP, MAC: " MACSTR, MAC2STR(event->mac)); - led_blink_green(); } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STADISCONNECTED) { - led_blink_red(); + ESP_LOGI(TAG, "Station disconnected from AP"); } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { ESP_LOGI(TAG, "Disconnected from AP"); s_is_connected = false; } else if (event_base == IP_EVENT && event_id == IP_EVENT_AP_STAIPASSIGNED) { ESP_LOGI(TAG, "IP assigned to station connected to AP"); - led_blink_green(); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { ESP_LOGI(TAG, "Got IP address, Wi-Fi connected"); s_is_connected = true; } } -// 2.4 GHz (1-13) plus the common non-DFS 5 GHz channels (UNII-1 + UNII-3). DFS -// channels (52-144) need radar detection and aren't usable for passive hopping, -// so they're left out. esp_wifi_set_channel picks the band from the number. static const uint8_t HOP_CHANNELS[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 36, 40, 44, 48, 149, 153, 157, 161, 165}; #define HOP_CHANNEL_COUNT (sizeof(HOP_CHANNELS) / sizeof(HOP_CHANNELS[0])) diff --git a/firmware_p4/CMakeLists.txt b/firmware_p4/CMakeLists.txt index 54191944d..68ad194c1 100644 --- a/firmware_p4/CMakeLists.txt +++ b/firmware_p4/CMakeLists.txt @@ -8,8 +8,26 @@ cmake_minimum_required(VERSION 3.16) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(IDF_TARGET "esp32p4") +set(EXTRA_COMPONENT_DIRS + "${CMAKE_CURRENT_LIST_DIR}/components/Applications/gameboy" + "${CMAKE_CURRENT_LIST_DIR}/components/Applications/doom") + include($ENV{IDF_PATH}/tools/cmake/project.cmake) +# The -O2 perf build (CONFIG_COMPILER_OPTIMIZATION_PERF) makes GCC run the flow +# analysis behind -Wstringop-truncation / -Wformat-truncation / -Wstringop-overflow +# / -Warray-bounds / -Wmaybe-uninitialized. With the IDF default -Werror=all these +# turn a cascade of pre-existing warnings across unrelated code into hard errors. +# Keep -O2 and -Werror everywhere else; demote just these noisy -O2 analysis +# classes to warnings (appended after IDF's flags so they win). NOTE: some of the +# stringop-overflow / array-bounds hits (LoRa mesh varint/resp buffers) are REAL +# undersized-buffer bugs, not false positives — track them for a dedicated fix. +idf_build_set_property(COMPILE_OPTIONS "-Wno-error=stringop-truncation" APPEND) +idf_build_set_property(COMPILE_OPTIONS "-Wno-error=format-truncation" APPEND) +idf_build_set_property(COMPILE_OPTIONS "-Wno-error=stringop-overflow" APPEND) +idf_build_set_property(COMPILE_OPTIONS "-Wno-error=array-bounds" APPEND) +idf_build_set_property(COMPILE_OPTIONS "-Wno-error=maybe-uninitialized" APPEND) + project(TentacleOS_P4) if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") diff --git a/firmware_p4/assets/icons/game_brk.png b/firmware_p4/assets/icons/game_brk.png new file mode 100644 index 000000000..5e4a14ddc Binary files /dev/null and b/firmware_p4/assets/icons/game_brk.png differ diff --git a/firmware_p4/assets/icons/game_flap.png b/firmware_p4/assets/icons/game_flap.png new file mode 100644 index 000000000..677683541 Binary files /dev/null and b/firmware_p4/assets/icons/game_flap.png differ diff --git a/firmware_p4/assets/icons/game_gb.png b/firmware_p4/assets/icons/game_gb.png new file mode 100644 index 000000000..106c3c75d Binary files /dev/null and b/firmware_p4/assets/icons/game_gb.png differ diff --git a/firmware_p4/assets/icons/game_pet.png b/firmware_p4/assets/icons/game_pet.png new file mode 100644 index 000000000..18f05611b Binary files /dev/null and b/firmware_p4/assets/icons/game_pet.png differ diff --git a/firmware_p4/assets/icons/game_snake.png b/firmware_p4/assets/icons/game_snake.png new file mode 100644 index 000000000..4f1fc510b Binary files /dev/null and b/firmware_p4/assets/icons/game_snake.png differ diff --git a/firmware_p4/assets/storage/bad_usb_scripts/amiga.txt b/firmware_p4/assets/storage/bad_usb_scripts/amiga.txt new file mode 100644 index 000000000..6f400b1c3 --- /dev/null +++ b/firmware_p4/assets/storage/bad_usb_scripts/amiga.txt @@ -0,0 +1,137 @@ +REM Amiga ASCII art (LGB) - types the art into the focused window +REM Focus a text editor first. END before ENTER stops the editor auto-indent +REM from accumulating; HOME returns to column 0 for the next line. +DELAY 1000 +HOME +STRING ___..-.---.---.--..___ +END +ENTER +HOME +STRING _..-- `.`. `. `. `. --.._ +END +ENTER +HOME +STRING / ___________\ \ \______ \ +END +ENTER +HOME +STRING | |.-----------`. `. `.---.| | +END +ENTER +HOME +STRING |`. |' \`. \ \ \ '| | +END +ENTER +HOME +STRING |`. |' \ `-._ `. `. `.'| | +END +ENTER +HOME +STRING /| |' `-._o)\ /(o\ \ \| |\ +END +ENTER +HOME +STRING .' | |' `. .' '. `. `. `. | `. +END +ENTER +HOME +STRING / .| |' `. (_.==._) \ \ \ |. \ _.--. +END +ENTER +HOME +STRING .' .' | |' _.-======-._ `. `. `. `. `. _.-_.-'\\ +END +ENTER +HOME +STRING / / | |' .' |_||_| `. \ \ \ \ \ .'_.' || +END +ENTER +HOME +STRING / .' |`. |' /_.-'========`-._\ `. `-._`._`. \(.__ :| +END +ENTER +HOME +STRING ( ' |`. |'.______________________.'\ _.) ` )`-._`-._/ / +END +ENTER +HOME +STRING \\ | '.------------------------.'`-._-' // `-._.' +END +ENTER +HOME +STRING _\\_ \ | AMIGA O O O O * * `.`.| ' // +END +ENTER +HOME +STRING (_ _) '-._|________________________|_.-'| _//_ +END +ENTER +HOME +STRING / / /`-._ |`-._ / / / | (_ _) +END +ENTER +HOME +STRING .' \ |`-._ `-._ `-._`-._/ / / | \ \ +END +ENTER +HOME +STRING / `. | `-._ `-._ `-._|/ / | / `. +END +ENTER +HOME +STRING / / / /. ) | `-._ `-._ `-._ / / .' \ +END +ENTER +HOME +STRING | | | \ \|/ | `-._`-._ `-._ `-._ / /. ( .\ \ \ \ +END +ENTER +HOME +STRING \ \ \ \/ | `-._`-._`-._ `-._ `-._/ / \ \|/ / | | | +END +ENTER +HOME +STRING `.\_\/ `-._ `-._`-._`-._ `-._/| /| \ \/ / / / +END +ENTER +HOME +STRING / `-._ `-._`-._`-._ || / | \ \/_/.' +END +ENTER +HOME +STRING .' `-._ `-._`-._ || / | \ +END +ENTER +HOME +STRING LGB / / . `-._ `-._ || / | \ +END +ENTER +HOME +STRING '\ / / `-._ ||/'._.' \ +END +ENTER +HOME +STRING \`. .' / `-._|/ \ +END +ENTER +HOME +STRING `.`-._.' .' \ .' +END +ENTER +HOME +STRING `-.__\/ `\ .' ' +END +ENTER +HOME +STRING \`. _.' .' +END +ENTER +HOME +STRING `.`-._.-' _.' +END +ENTER +HOME +STRING `-.__.-' +END +ENTER +HOME diff --git a/firmware_p4/components/Applications/CMakeLists.txt b/firmware_p4/components/Applications/CMakeLists.txt index c9f7e1884..e5a7d22c7 100644 --- a/firmware_p4/components/Applications/CMakeLists.txt +++ b/firmware_p4/components/Applications/CMakeLists.txt @@ -18,16 +18,14 @@ # ui_manager.c and assets_manager.c at the ui/ root) — new screens compile # automatically. INCLUDE_DIRS is the only hand-maintained part; add a screen's # include/ dir there when a new screen area is introduced. +# Globs every ui/**/*.c (e.g. screens/files/usb_storage_ui.c). NOTE: adding a new +# .c under ui/ needs a CMake reconfigure to re-run this glob. file(GLOB_RECURSE UI_SRCS "ui/*.c") list(REMOVE_ITEM UI_SRCS - "${CMAKE_CURRENT_SOURCE_DIR}/ui/screens/games/games_menu_ui.c" "${CMAKE_CURRENT_SOURCE_DIR}/ui/screens/games/flappy_ui.c" - "${CMAKE_CURRENT_SOURCE_DIR}/ui/screens/games/snake_ui.c" - "${CMAKE_CURRENT_SOURCE_DIR}/ui/screens/games/breakout_ui.c" "${CMAKE_CURRENT_SOURCE_DIR}/ui/screens/games/octopet_ui.c" "${CMAKE_CURRENT_SOURCE_DIR}/ui/screens/games/motion_ui.c" - "${CMAKE_CURRENT_SOURCE_DIR}/ui/screens/games/game_fx.c" ) # Application (non-UI) sources — the real drivers/services stay in the build. @@ -83,6 +81,8 @@ idf_component_register(SRCS "ui/include" # --- ui screens (mirrors firmware_p4_prototype) --- "ui/screens/audio/include" + "ui/screens/video/include" + "ui/screens/images/include" "ui/screens/badusb/include" "ui/screens/bluetooth/include" "ui/screens/boot/include" @@ -139,8 +139,11 @@ idf_component_register(SRCS REQUIRES driver esp_driver_tsens + esp_driver_jpeg Drivers Service + gameboy + doom esp_common esp_wifi esp_tinyusb @@ -151,6 +154,7 @@ idf_component_register(SRCS joltwallet__littlefs nvs_flash espressif__esp-dsp + chmorgan__esp-libhelix-mp3 ) target_link_libraries(${COMPONENT_LIB} -Wl,-zmuldefs) target_compile_definitions(${COMPONENT_LIB} PRIVATE MESH_HEADLESS=0) diff --git a/firmware_p4/components/Applications/LoRa/README.md b/firmware_p4/components/Applications/LoRa/README.md new file mode 100644 index 000000000..67f10556a --- /dev/null +++ b/firmware_p4/components/Applications/LoRa/README.md @@ -0,0 +1,7 @@ +# LoRa Application + +Documentation for this component lives in the project docs hub (single source of truth): + +- [docs/LoRa/README.md](../../../../docs/LoRa/README.md) + +This file is only a pointer: edit the docs there to keep things from drifting. diff --git a/firmware_p4/components/Applications/LoRa/meshcore/include/meshcore_phone_bridge.h b/firmware_p4/components/Applications/LoRa/meshcore/include/meshcore_phone_bridge.h index 592874304..6906a41bf 100644 --- a/firmware_p4/components/Applications/LoRa/meshcore/include/meshcore_phone_bridge.h +++ b/firmware_p4/components/Applications/LoRa/meshcore/include/meshcore_phone_bridge.h @@ -81,6 +81,16 @@ esp_err_t meshcore_phone_bridge_ble_stop(void); */ bool meshcore_phone_bridge_is_connected(void); +/** + * @brief Whether the connected phone has subscribed to notifications, i.e. the + * companion app is actually paired and ready (not just BLE-linked). + * + * Use this for the "linked" UI state: ble_connected goes true at the raw BLE + * link (before the pairing PIN even shows), while ble_subscribed only goes true + * after encryption + the app subscribing. + */ +bool meshcore_phone_bridge_is_subscribed(void); + #ifdef __cplusplus } #endif diff --git a/firmware_p4/components/Applications/LoRa/meshcore/meshcore_db.c b/firmware_p4/components/Applications/LoRa/meshcore/meshcore_db.c index 1818c6306..20264b754 100644 --- a/firmware_p4/components/Applications/LoRa/meshcore/meshcore_db.c +++ b/firmware_p4/components/Applications/LoRa/meshcore/meshcore_db.c @@ -15,6 +15,8 @@ #include "meshcore_internal.h" +#include "esp_attr.h" + #include #include @@ -60,7 +62,7 @@ typedef struct { } mc_pending_t; static meshcore_channel_t s_channels[MESHCORE_MAX_CHANNELS]; -static meshcore_contact_t s_contacts[MESHCORE_MAX_CONTACTS]; +EXT_RAM_BSS_ATTR static meshcore_contact_t s_contacts[MESHCORE_MAX_CONTACTS]; static mc_dedup_entry_t s_dedup[MC_DEDUP_SIZE]; static uint16_t s_dedup_idx = 0; diff --git a/firmware_p4/components/Applications/LoRa/meshcore/meshcore_phone_bridge.c b/firmware_p4/components/Applications/LoRa/meshcore/meshcore_phone_bridge.c index bcc906a74..02d74e1b1 100644 --- a/firmware_p4/components/Applications/LoRa/meshcore/meshcore_phone_bridge.c +++ b/firmware_p4/components/Applications/LoRa/meshcore/meshcore_phone_bridge.c @@ -163,6 +163,18 @@ bool meshcore_phone_bridge_is_connected(void) { return is_connected; } +bool meshcore_phone_bridge_is_subscribed(void) { + bool is_subscribed = false; + if (s_status_mutex == NULL) { + return false; + } + if (xSemaphoreTake(s_status_mutex, pdMS_TO_TICKS(BRIDGE_STATUS_TICK_MS)) == pdTRUE) { + is_subscribed = (s_cached_status.ble_subscribed != 0); + xSemaphoreGive(s_status_mutex); + } + return is_subscribed; +} + static void status_task(void *pvParameters) { (void)pvParameters; diff --git a/firmware_p4/components/Applications/LoRa/meshcore/meshcore_phoneapi.c b/firmware_p4/components/Applications/LoRa/meshcore/meshcore_phoneapi.c index 33bad58f8..ad6ab661a 100644 --- a/firmware_p4/components/Applications/LoRa/meshcore/meshcore_phoneapi.c +++ b/firmware_p4/components/Applications/LoRa/meshcore/meshcore_phoneapi.c @@ -15,6 +15,8 @@ #include "meshcore_phoneapi.h" +#include "esp_attr.h" + #include #include #include @@ -22,6 +24,8 @@ #include "esp_err.h" #include "esp_log.h" #include "esp_random.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" #include "meshcore.h" #include "meshcore_internal.h" @@ -103,8 +107,9 @@ static const char *TAG = "MC_PHONEAPI"; #define CONTACT_FRAME_SIZE 143 -#define MC_OFFLINE_QUEUE_SIZE 16 -#define MC_OFFLINE_FRAME_MAX 240 +#define MC_OFFLINE_QUEUE_SIZE 16 +#define MC_OFFLINE_FRAME_MAX 240 +#define MC_QUEUE_MUTEX_TIMEOUT_MS 100 #define MC_NVS_KEY_BLE_PIN "ble_pin" #define MC_NVS_KEY_AUTOADD "autoadd" @@ -136,8 +141,9 @@ static uint8_t s_autoadd_max_hops = MC_AUTOADD_DEFAULT_MAX_HOPS; static meshcore_phoneapi_outbound_cb_t s_outbound_cb = NULL; static void *s_outbound_ctx = NULL; -static mc_queued_frame_t s_offline_queue[MC_OFFLINE_QUEUE_SIZE]; +EXT_RAM_BSS_ATTR static mc_queued_frame_t s_offline_queue[MC_OFFLINE_QUEUE_SIZE]; static uint8_t s_offline_queue_len = 0; +static SemaphoreHandle_t s_queue_mutex = NULL; static void send_resp(const uint8_t *buf, uint16_t len); static void send_err(uint8_t sub); @@ -193,6 +199,12 @@ esp_err_t meshcore_phoneapi_init(void) { s_offline_queue_len = 0; s_outbound_cb = NULL; s_outbound_ctx = NULL; + if (s_queue_mutex == NULL) { + s_queue_mutex = xSemaphoreCreateMutex(); + if (s_queue_mutex == NULL) { + return ESP_ERR_NO_MEM; + } + } s_is_initialized = true; ESP_LOGI(TAG, "Initialized — PIN=%lu", (unsigned long)s_ble_pin); return ESP_OK; @@ -209,7 +221,11 @@ uint32_t meshcore_phoneapi_get_pin(void) { void meshcore_phoneapi_on_disconnect(void) { s_app_target_ver = 0; + if (s_queue_mutex != NULL) + xSemaphoreTake(s_queue_mutex, portMAX_DELAY); s_offline_queue_len = 0; + if (s_queue_mutex != NULL) + xSemaphoreGive(s_queue_mutex); } void meshcore_phoneapi_on_inbound(const uint8_t *buf, uint16_t len) { @@ -460,6 +476,10 @@ static uint32_t load_or_generate_ble_pin(void) { static void offline_queue_push(const uint8_t *frame, uint16_t len) { if (len == 0 || len > MC_OFFLINE_FRAME_MAX) return; + if (s_queue_mutex != NULL && + xSemaphoreTake(s_queue_mutex, pdMS_TO_TICKS(MC_QUEUE_MUTEX_TIMEOUT_MS)) != pdTRUE) { + return; + } if (s_offline_queue_len >= MC_OFFLINE_QUEUE_SIZE) { for (int i = 0; i < MC_OFFLINE_QUEUE_SIZE - 1; i++) { s_offline_queue[i] = s_offline_queue[i + 1]; @@ -469,17 +489,28 @@ static void offline_queue_push(const uint8_t *frame, uint16_t len) { s_offline_queue[s_offline_queue_len].len = len; memcpy(s_offline_queue[s_offline_queue_len].buf, frame, len); s_offline_queue_len++; + if (s_queue_mutex != NULL) + xSemaphoreGive(s_queue_mutex); } static bool offline_queue_pop(uint8_t *out, uint16_t *out_len) { - if (s_offline_queue_len == 0) + if (s_queue_mutex != NULL && + xSemaphoreTake(s_queue_mutex, pdMS_TO_TICKS(MC_QUEUE_MUTEX_TIMEOUT_MS)) != pdTRUE) { + return false; + } + if (s_offline_queue_len == 0) { + if (s_queue_mutex != NULL) + xSemaphoreGive(s_queue_mutex); return false; + } *out_len = s_offline_queue[0].len; memcpy(out, s_offline_queue[0].buf, *out_len); for (int i = 0; i < s_offline_queue_len - 1; i++) { s_offline_queue[i] = s_offline_queue[i + 1]; } s_offline_queue_len--; + if (s_queue_mutex != NULL) + xSemaphoreGive(s_queue_mutex); return true; } @@ -514,6 +545,8 @@ static void deserialize_contact(meshcore_contact_t *c, const uint8_t in[], size_ c->type = in[o++]; c->flags = in[o++]; c->out_path_len = in[o++]; + if (c->out_path_len != MESHCORE_OUT_PATH_UNKNOWN && c->out_path_len > MESHCORE_MAX_PATH) + c->out_path_len = MESHCORE_MAX_PATH; memcpy(c->out_path, &in[o], MESHCORE_MAX_PATH); o += MESHCORE_MAX_PATH; char tmp[MESHCORE_NAME_MAX + 1] = {0}; @@ -545,7 +578,7 @@ static void handle_device_query(const uint8_t *p, uint16_t len) { s_app_target_ver = p[1]; ESP_LOGI(TAG, "app_target_ver = %u", s_app_target_ver); } - uint8_t resp[80]; + uint8_t resp[82]; uint16_t o = 0; resp[o++] = RESP_CODE_DEVICE_INFO; resp[o++] = FIRMWARE_VER_CODE; @@ -925,9 +958,12 @@ static void handle_get_advert_path(const uint8_t *p, uint16_t len) { if (c == NULL || c->out_path_len == MESHCORE_OUT_PATH_UNKNOWN) { resp[o++] = 0; } else { - resp[o++] = c->out_path_len; - memcpy(&resp[o], c->out_path, c->out_path_len); - o += c->out_path_len; + uint8_t plen = c->out_path_len; + if (plen > MESHCORE_MAX_PATH) + plen = MESHCORE_MAX_PATH; + resp[o++] = plen; + memcpy(&resp[o], c->out_path, plen); + o += plen; } send_resp(resp, o); } diff --git a/firmware_p4/components/Applications/LoRa/meshcore/meshcore_router.c b/firmware_p4/components/Applications/LoRa/meshcore/meshcore_router.c index bbb874a40..07d15300f 100644 --- a/firmware_p4/components/Applications/LoRa/meshcore/meshcore_router.c +++ b/firmware_p4/components/Applications/LoRa/meshcore/meshcore_router.c @@ -658,14 +658,21 @@ static void on_rx_done(const sx1262_packet_t *pkt, void *ctx) { (void)ctx; if (pkt == NULL) return; + ESP_LOGD(TAG, + "RX diag: %u bytes rssi=%d snr=%d crc_err=%d hdr_err=%d", + pkt->len, + pkt->rssi_pkt_dbm, + pkt->snr_pkt_db, + (int)pkt->has_crc_error, + (int)pkt->has_header_error); if (pkt->has_crc_error || pkt->has_header_error) { - ESP_LOGW(TAG, "RX HW error -- skip"); + ESP_LOGD(TAG, "RX HW error -- skip"); return; } meshcore_packet_view_t view; if (!meshcore_packet_parse(pkt->buf, pkt->len, &view)) { - ESP_LOGW(TAG, "RX parse failed (%d bytes)", pkt->len); + ESP_LOGD(TAG, "RX parse failed (%d bytes)", pkt->len); return; } view.rssi_dbm = pkt->rssi_pkt_dbm; diff --git a/firmware_p4/components/Applications/LoRa/meshtastic/include/meshtastic_phone_bridge.h b/firmware_p4/components/Applications/LoRa/meshtastic/include/meshtastic_phone_bridge.h index 0dc955158..867c9789d 100644 --- a/firmware_p4/components/Applications/LoRa/meshtastic/include/meshtastic_phone_bridge.h +++ b/firmware_p4/components/Applications/LoRa/meshtastic/include/meshtastic_phone_bridge.h @@ -94,6 +94,13 @@ esp_err_t meshtastic_phone_bridge_wifi_stop(void); */ bool meshtastic_phone_bridge_is_connected(void); +/** + * @brief Whether the phone app is actually paired and ready: BLE subscribed + * (after encryption) or a TCP client is attached — not just BLE-linked. + * Use this for the "linked" UI state. + */ +bool meshtastic_phone_bridge_is_subscribed(void); + #ifdef __cplusplus } #endif diff --git a/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_mesh.c b/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_mesh.c index 18d386b20..75318b933 100644 --- a/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_mesh.c +++ b/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_mesh.c @@ -1181,7 +1181,7 @@ static void on_rx_done(const sx1262_packet_t *pkt, void *ctx) { if (pkt == NULL) return; if (pkt->has_crc_error || pkt->has_header_error) { - ESP_LOGW(TAG, "RX: erro HW, ignoring"); + ESP_LOGD(TAG, "RX: erro HW, ignoring"); if (s_is_running) sx1262_receive_continuous(); diff --git a/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_nodedb.c b/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_nodedb.c index c1cd02761..8adac8efc 100644 --- a/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_nodedb.c +++ b/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_nodedb.c @@ -28,7 +28,7 @@ static const char *TAG = "MT_NODEDB"; #define MT_NODEDB_NVS_KEY "nodedb" -static mt_node_entry_t s_nodes[MT_NODEDB_MAX_NODES]; +EXT_RAM_BSS_ATTR static mt_node_entry_t s_nodes[MT_NODEDB_MAX_NODES]; static uint16_t s_count = 0; static uint64_t dec_varint(const uint8_t *buf, uint16_t max_len, uint16_t *out_used) { diff --git a/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_phone_bridge.c b/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_phone_bridge.c index a1f1726d4..8776d7671 100644 --- a/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_phone_bridge.c +++ b/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_phone_bridge.c @@ -31,7 +31,7 @@ static const char *TAG = "MESH_BRIDGE"; -#define BRIDGE_NOTIFY_TASK_STACK 4096 +#define BRIDGE_NOTIFY_TASK_STACK 8192 #define BRIDGE_NOTIFY_TASK_PRIO SYS_PRIO_SERVICE_HI #define BRIDGE_NOTIFY_TICK_MS 100 #define BRIDGE_SPI_TIMEOUT_MS 1000 @@ -184,6 +184,18 @@ bool meshtastic_phone_bridge_is_connected(void) { return is_connected; } +bool meshtastic_phone_bridge_is_subscribed(void) { + bool is_subscribed = false; + if (s_status_mutex == NULL) { + return false; + } + if (xSemaphoreTake(s_status_mutex, pdMS_TO_TICKS(BRIDGE_NOTIFY_TICK_MS)) == pdTRUE) { + is_subscribed = (s_cached_status.ble_subscribed != 0) || (s_cached_status.tcp_clients != 0); + xSemaphoreGive(s_status_mutex); + } + return is_subscribed; +} + static void notify_task(void *pvParameters) { (void)pvParameters; uint8_t frame_buf[BRIDGE_FROMRADIO_BUF_SIZE]; diff --git a/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_phoneapi.c b/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_phoneapi.c index 38da59a6b..e6aefdf11 100644 --- a/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_phoneapi.c +++ b/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_phoneapi.c @@ -50,10 +50,11 @@ typedef struct { uint8_t buf[PA_MAX_FRAME_SIZE]; } pa_frame_t; -static pa_frame_t s_queue[PA_FROMRADIO_QUEUE_SZ]; +EXT_RAM_BSS_ATTR static pa_frame_t s_queue[PA_FROMRADIO_QUEUE_SZ]; static uint8_t s_queue_head = 0; static uint8_t s_queue_tail = 0; static SemaphoreHandle_t s_queue_mutex = NULL; +static SemaphoreHandle_t s_fsm_mutex = NULL; static uint32_t s_node_num = 0; static phoneapi_state_t s_state = PA_STATE_IDLE; @@ -74,25 +75,25 @@ static uint16_t enc_varint(uint8_t *buf, uint64_t value) { } static uint16_t enc_field_varint(uint8_t *buf, uint8_t field_num, uint64_t value) { - buf[0] = (field_num << 3) | 0; - return 1 + enc_varint(&buf[1], value); + uint16_t pos = enc_varint(buf, ((uint64_t)field_num << 3) | 0); + return pos + enc_varint(&buf[pos], value); } static uint16_t enc_field_bytes(uint8_t *buf, uint8_t field_num, const uint8_t *data, uint16_t len) { - buf[0] = (field_num << 3) | 2; - uint16_t pos = 1 + enc_varint(&buf[1], len); + uint16_t pos = enc_varint(buf, ((uint64_t)field_num << 3) | 2); + pos += enc_varint(&buf[pos], len); memcpy(&buf[pos], data, len); return pos + len; } static uint16_t enc_field_fixed32(uint8_t *buf, uint8_t field_num, uint32_t value) { - buf[0] = (field_num << 3) | 5; - buf[1] = (uint8_t)(value & 0xFF); - buf[2] = (uint8_t)((value >> 8) & 0xFF); - buf[3] = (uint8_t)((value >> 16) & 0xFF); - buf[4] = (uint8_t)((value >> 24) & 0xFF); - return 5; + uint16_t pos = enc_varint(buf, ((uint64_t)field_num << 3) | 5); + buf[pos++] = (uint8_t)(value & 0xFF); + buf[pos++] = (uint8_t)((value >> 8) & 0xFF); + buf[pos++] = (uint8_t)((value >> 16) & 0xFF); + buf[pos++] = (uint8_t)((value >> 24) & 0xFF); + return pos; } static uint64_t dec_varint(const uint8_t *buf, uint16_t max_len, uint16_t *out_used) { @@ -272,7 +273,7 @@ static uint16_t build_config_empty(uint8_t *out, uint32_t rid, uint8_t cfg_type) static uint16_t build_moduleconfig_empty(uint8_t *out, uint32_t rid, uint8_t mc_type) { uint8_t mc[8]; uint16_t mc_len = 0; - mc[mc_len++] = (mc_type << 3) | 2; + mc_len += enc_varint(&mc[mc_len], ((uint64_t)mc_type << 3) | 2); mc[mc_len++] = 0; uint16_t pos = 0; @@ -576,6 +577,11 @@ esp_err_t phoneapi_init(uint32_t node_num) { if (s_queue_mutex == NULL) return ESP_ERR_NO_MEM; } + if (s_fsm_mutex == NULL) { + s_fsm_mutex = xSemaphoreCreateMutex(); + if (s_fsm_mutex == NULL) + return ESP_ERR_NO_MEM; + } ESP_LOGI( TAG, "Initialized - node=0x%08lX, queue=%d", (unsigned long)node_num, PA_FROMRADIO_QUEUE_SZ); @@ -620,6 +626,8 @@ esp_err_t phoneapi_on_toradio(const uint8_t *pb_data, uint16_t pb_len) { uint64_t value = dec_varint(&pb_data[i], pb_len - i, &vused); i += vused; if (field == 3) { + if (s_fsm_mutex != NULL) + xSemaphoreTake(s_fsm_mutex, portMAX_DELAY); s_want_config_nonce = (uint32_t)value; ESP_LOGI(TAG, "ToRadio.want_config_id = %lu", (unsigned long)value); if (s_want_config_nonce == PA_NONCE_ONLY_NODES) { @@ -633,6 +641,8 @@ esp_err_t phoneapi_on_toradio(const uint8_t *pb_data, uint16_t pb_len) { k++) { advance_fsm(); } + if (s_fsm_mutex != NULL) + xSemaphoreGive(s_fsm_mutex); } } else if (wire_type == 5) { i += 4; @@ -644,9 +654,13 @@ esp_err_t phoneapi_on_toradio(const uint8_t *pb_data, uint16_t pb_len) { } uint16_t phoneapi_poll_fromradio(uint8_t *out_buf, uint16_t max_len) { + if (s_fsm_mutex != NULL) + xSemaphoreTake(s_fsm_mutex, portMAX_DELAY); while (!queue_has_data() && s_state != PA_STATE_SEND_PACKETS && s_state != PA_STATE_IDLE) { advance_fsm(); } + if (s_fsm_mutex != NULL) + xSemaphoreGive(s_fsm_mutex); return queue_pop(out_buf, max_len); } @@ -669,7 +683,15 @@ void phoneapi_push_packet(const uint8_t *mp_bytes, uint16_t mp_len) { void phoneapi_disconnect(void) { ESP_LOGI(TAG, "Transporte desconectado - resetando FSM"); + if (s_fsm_mutex != NULL) + xSemaphoreTake(s_fsm_mutex, portMAX_DELAY); s_state = PA_STATE_IDLE; - s_queue_head = s_queue_tail; s_config_iter = 0; + if (s_fsm_mutex != NULL) + xSemaphoreGive(s_fsm_mutex); + if (s_queue_mutex != NULL) + xSemaphoreTake(s_queue_mutex, portMAX_DELAY); + s_queue_head = s_queue_tail; + if (s_queue_mutex != NULL) + xSemaphoreGive(s_queue_mutex); } diff --git a/firmware_p4/components/Applications/LoRa/session/lora_session.c b/firmware_p4/components/Applications/LoRa/session/lora_session.c index a36488d95..ec31af3f1 100644 --- a/firmware_p4/components/Applications/LoRa/session/lora_session.c +++ b/firmware_p4/components/Applications/LoRa/session/lora_session.c @@ -39,7 +39,7 @@ static const char *TAG = "LORA_SESSION"; static lora_proto_t s_proto = LORA_PROTO_NONE; -static lora_msg_t s_msgs[MSG_RING]; +EXT_RAM_BSS_ATTR static lora_msg_t s_msgs[MSG_RING]; static uint16_t s_head = 0; static uint16_t s_count = 0; static uint32_t s_total = 0; @@ -219,9 +219,9 @@ void lora_session_on_rx_text(const char *who, const char *text) { bool lora_session_app_connected(void) { switch (s_proto) { case LORA_PROTO_MESHTASTIC: - return meshtastic_phone_bridge_is_connected(); + return meshtastic_phone_bridge_is_subscribed(); case LORA_PROTO_MESHCORE: - return meshcore_phone_bridge_is_connected(); + return meshcore_phone_bridge_is_subscribed(); default: return false; } diff --git a/firmware_p4/components/Applications/SubGhz/include/subghz_brute.h b/firmware_p4/components/Applications/SubGhz/include/subghz_brute.h new file mode 100644 index 000000000..6a0343de0 --- /dev/null +++ b/firmware_p4/components/Applications/SubGhz/include/subghz_brute.h @@ -0,0 +1,69 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#ifndef SUBGHZ_BRUTE_H +#define SUBGHZ_BRUTE_H + +#include +#include + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Progress of a running brute-force sweep. + */ +typedef struct { + uint32_t sent; /**< @brief Codes transmitted so far. */ + uint32_t total; /**< @brief Total codes in the sweep. */ + bool running; /**< @brief true while the sweep is active. */ + bool done; /**< @brief true once the sweep finished (or was stopped). */ +} subghz_brute_status_t; + +/** + * @brief Start a real brute-force sweep: transmit every code for a protocol. + * + * Stops the receiver, initializes the transmitter, tunes to @p freq, then + * transmits each candidate code. There is no "hit" detection on fixed-code + * remotes — the sweep simply completes. + * + * @param protocol Protocol name (must have an encoder: e.g. "CAME", "RCSwitch"). + * @param bit_count Code width in bits (sweep size is capped internally). + * @param freq Transmit frequency in Hz. + * @return ESP_OK if the sweep started, or an error code on failure. + */ +esp_err_t subghz_brute_start(const char *protocol, uint8_t bit_count, uint32_t freq); + +/** + * @brief Stop a running brute-force sweep. + */ +void subghz_brute_stop(void); + +/** + * @brief Copy the current sweep progress. + * + * @param out Destination. + * @return true if a sweep has been started (out is filled), false otherwise. + */ +bool subghz_brute_get_status(subghz_brute_status_t *out); + +#ifdef __cplusplus +} +#endif + +#endif // SUBGHZ_BRUTE_H diff --git a/firmware_p4/components/Applications/SubGhz/include/subghz_receiver.h b/firmware_p4/components/Applications/SubGhz/include/subghz_receiver.h index 8f0da732a..fbe45c6b3 100644 --- a/firmware_p4/components/Applications/SubGhz/include/subghz_receiver.h +++ b/firmware_p4/components/Applications/SubGhz/include/subghz_receiver.h @@ -23,6 +23,8 @@ #include "esp_err.h" #include "cc1101.h" +#include "subghz_analyzer.h" +#include "subghz_types.h" #ifdef __cplusplus extern "C" { @@ -37,6 +39,27 @@ typedef enum { SUBGHZ_MODE_COUNT /**< @brief Number of receiver modes (sentinel). */ } subghz_mode_t; +/** + * @brief Latest capture published by the receiver task for UI consumers. + */ +typedef struct { + uint32_t seq; /**< @brief Capture counter; 0 means nothing captured yet. */ + bool decoded; /**< @brief true if a known protocol was decoded. */ + subghz_data_t data; /**< @brief Decoded fields; protocol_name points to name_buf. */ + char name_buf[32]; /**< @brief Backing storage for data.protocol_name. */ + char save_name[40]; /**< @brief File name the capture was auto-saved under. */ + uint32_t freq; /**< @brief Frequency in Hz at the moment of capture. */ + subghz_analyzer_result_t analysis; /**< @brief Timing/modulation analysis of the capture. */ +} subghz_rx_result_t; + +/** + * @brief Copy the most recent capture published by the receiver task. + * + * @param out Destination buffer. + * @return true if a capture is available (out is filled), false otherwise. + */ +bool subghz_receiver_get_result(subghz_rx_result_t *out); + /** * @brief Start the Sub-GHz receiver. * diff --git a/firmware_p4/components/Service/storage_api/include/tos_log.h b/firmware_p4/components/Applications/SubGhz/include/subghz_replay.h similarity index 51% rename from firmware_p4/components/Service/storage_api/include/tos_log.h rename to firmware_p4/components/Applications/SubGhz/include/subghz_replay.h index c6c9acaf8..4b0a8da46 100644 --- a/firmware_p4/components/Service/storage_api/include/tos_log.h +++ b/firmware_p4/components/Applications/SubGhz/include/subghz_replay.h @@ -13,43 +13,31 @@ // You should have received a copy of the GNU General Public License // along with TentacleOS. If not, see . -#ifndef TOS_LOG_H -#define TOS_LOG_H +#ifndef SUBGHZ_REPLAY_H +#define SUBGHZ_REPLAY_H + +#include "esp_err.h" #ifdef __cplusplus extern "C" { #endif -#include "esp_err.h" - /** - * @brief Initialize the persistent log system. - * - * Redirects all ESP_LOGx output to a file on the SD card via - * esp_log_set_vprintf. Logs are written to both serial and file - * simultaneously. + * @brief Load a saved capture and transmit it over the CC1101. * - * Log files are rotated automatically: - * - sys.1.log (current) -> sys.2.log -> ... -> sys.5.log - * - Maximum 5 files, total limited to 10 MB - * - When sys.1.log exceeds 2 MB, rotation occurs - * - * @return - * - ESP_OK on success - * - ESP_FAIL if the log file cannot be opened - */ -esp_err_t tos_log_init(void); - -/** - * @brief Flush and close the log file. + * Stops the receiver if running, initializes the transmitter, tunes to the + * saved frequency, and replays the signal: RAW captures are sent pulse-for-pulse; + * decoded captures are re-encoded via the protocol registry (only protocols with + * an encoder are supported). * - * Restores the default ESP_LOG vprintf handler. Call this before - * unmounting the SD card. + * @param name Saved capture name (without .sub). + * @return ESP_OK if the transmit was queued, ESP_ERR_NOT_SUPPORTED if the + * protocol cannot be re-encoded, or another error code on failure. */ -void tos_log_deinit(void); +esp_err_t subghz_replay_file(const char *name); #ifdef __cplusplus } #endif -#endif // TOS_LOG_H +#endif // SUBGHZ_REPLAY_H diff --git a/firmware_p4/components/Applications/SubGhz/include/subghz_settings.h b/firmware_p4/components/Applications/SubGhz/include/subghz_settings.h new file mode 100644 index 000000000..1e79c5fb6 --- /dev/null +++ b/firmware_p4/components/Applications/SubGhz/include/subghz_settings.h @@ -0,0 +1,55 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#ifndef SUBGHZ_SETTINGS_H +#define SUBGHZ_SETTINGS_H + +#include + +#include "cc1101.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Shared Sub-GHz radio settings chosen in the Radio Config screen and + * consumed by the Read/scan screen. + */ +typedef struct { + cc1101_preset_t preset; /**< @brief CC1101 preset used when starting RX. */ + uint32_t freq; /**< @brief RX frequency in Hz; 0 means frequency hopping. */ +} subghz_settings_t; + +/** + * @brief Get the current shared Sub-GHz settings. + */ +subghz_settings_t subghz_settings_get(void); + +/** + * @brief Set the preset used when starting RX. + */ +void subghz_settings_set_preset(cc1101_preset_t preset); + +/** + * @brief Set the RX frequency in Hz (0 = frequency hopping). + */ +void subghz_settings_set_freq(uint32_t freq); + +#ifdef __cplusplus +} +#endif + +#endif // SUBGHZ_SETTINGS_H diff --git a/firmware_p4/components/Applications/SubGhz/include/subghz_storage.h b/firmware_p4/components/Applications/SubGhz/include/subghz_storage.h index 21460af30..a5b653890 100644 --- a/firmware_p4/components/Applications/SubGhz/include/subghz_storage.h +++ b/firmware_p4/components/Applications/SubGhz/include/subghz_storage.h @@ -27,6 +27,18 @@ extern "C" { #endif +#define SUBGHZ_STORAGE_NAME_MAX 40 +#define SUBGHZ_STORAGE_PROTO_MAX 24 + +/** + * @brief Metadata for one saved Sub-GHz capture (parsed from its .sub header). + */ +typedef struct { + char name[SUBGHZ_STORAGE_NAME_MAX]; /**< @brief File name without the .sub suffix. */ + char protocol[SUBGHZ_STORAGE_PROTO_MAX]; /**< @brief Protocol label ("RAW" for raw captures). */ + uint32_t frequency; /**< @brief Center frequency in Hz. */ +} subghz_storage_entry_t; + /** * @brief Initialize the Sub-GHz storage subsystem. * @@ -34,6 +46,33 @@ extern "C" { */ esp_err_t subghz_storage_init(void); +/** + * @brief List saved captures on the SD card. + * + * @param out Destination array. + * @param max_entries Capacity of the destination array. + * @return Number of entries written, or -1 on error. + */ +int subghz_storage_list(subghz_storage_entry_t *out, int max_entries); + +/** + * @brief Read a saved capture's raw .sub text. + * + * @param name Capture name (without .sub). + * @param out_buf Destination buffer (null-terminated on success). + * @param out_size Size of the destination buffer. + * @return Number of bytes read, or -1 on error. + */ +int subghz_storage_read(const char *name, char *out_buf, size_t out_size); + +/** + * @brief Delete a saved capture. + * + * @param name Capture name (without .sub). + * @return esp_err_t ESP_OK on success, or an error code on failure. + */ +esp_err_t subghz_storage_delete(const char *name); + /** * @brief Save a decoded signal to persistent storage. * diff --git a/firmware_p4/components/Applications/SubGhz/subghz_brute.c b/firmware_p4/components/Applications/SubGhz/subghz_brute.c new file mode 100644 index 000000000..d61cc19de --- /dev/null +++ b/firmware_p4/components/Applications/SubGhz/subghz_brute.c @@ -0,0 +1,164 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#include "subghz_brute.h" + +#include +#include + +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" +#include "sys_prio.h" + +#include "cc1101.h" +#include "subghz_protocol_decoder.h" +#include "subghz_protocol_registry.h" +#include "subghz_receiver.h" +#include "subghz_transmitter.h" +#include "subghz_types.h" + +static const char *TAG = "SUBGHZ_BRUTE"; + +#define BRUTE_MAX_CODES 65536 +#define BRUTE_MAX_PULSES 2048 +#define BRUTE_PACING_MS 100 +#define BRUTE_RX_DRAIN_MS 150 +#define BRUTE_TASK_STACK 4096 +#define BRUTE_TASK_PRIORITY SYS_PRIO_SERVICE_HI +#define BRUTE_TASK_CORE SYS_CORE_RADIO +#define BRUTE_LOCK_MS 20 + +static const subghz_protocol_t *s_proto = NULL; +static uint8_t s_bit_count = 0; +static volatile bool s_run = false; +static TaskHandle_t s_task = NULL; +static SemaphoreHandle_t s_mutex = NULL; +static subghz_brute_status_t s_status; + +static void set_status(uint32_t sent, uint32_t total, bool running, bool done) { + if (s_mutex == NULL) + return; + if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(BRUTE_LOCK_MS)) != pdTRUE) + return; + s_status.sent = sent; + s_status.total = total; + s_status.running = running; + s_status.done = done; + xSemaphoreGive(s_mutex); +} + +static void brute_task(void *arg) { + (void)arg; + uint32_t total = s_status.total; + int32_t *pulses = malloc(BRUTE_MAX_PULSES * sizeof(int32_t)); + + if (pulses == NULL || s_proto == NULL || s_proto->encode == NULL) { + if (pulses != NULL) + free(pulses); + set_status(0, total, false, true); + s_run = false; + s_task = NULL; + vTaskDelete(NULL); + return; + } + + for (uint32_t code = 0; code < total && s_run; code++) { + subghz_data_t data = {0}; + data.protocol_name = s_proto->name; + data.bit_count = s_bit_count; + data.raw_value = code; + data.serial = code; + + size_t n = s_proto->encode(&data, pulses, BRUTE_MAX_PULSES); + if (n > 0) + subghz_tx_send_raw(pulses, n); + + set_status(code + 1, total, true, false); + vTaskDelay(pdMS_TO_TICKS(BRUTE_PACING_MS)); + } + + free(pulses); + ESP_LOGI(TAG, "sweep complete (%lu codes)", (unsigned long)total); + set_status(s_status.sent, total, false, true); + s_run = false; + s_task = NULL; + vTaskDelete(NULL); +} + +esp_err_t subghz_brute_start(const char *protocol, uint8_t bit_count, uint32_t freq) { + if (protocol == NULL || bit_count == 0 || s_run) + return ESP_ERR_INVALID_STATE; + + const subghz_protocol_t *p = subghz_protocol_registry_get_by_name(protocol); + if (p == NULL || p->encode == NULL) { + ESP_LOGW(TAG, "protocol '%s' has no encoder", protocol); + return ESP_ERR_NOT_SUPPORTED; + } + + uint32_t total = (bit_count >= 32) ? 0xFFFFFFFFu : (1u << bit_count); + if (total == 0 || total > BRUTE_MAX_CODES) + total = BRUTE_MAX_CODES; + + if (s_mutex == NULL) + s_mutex = xSemaphoreCreateMutex(); + + s_proto = p; + s_bit_count = bit_count; + set_status(0, total, true, false); + + if (subghz_receiver_is_running()) { + subghz_receiver_stop(); + vTaskDelay(pdMS_TO_TICKS(BRUTE_RX_DRAIN_MS)); + } + + esp_err_t err = subghz_tx_init(); + if (err != ESP_OK) { + set_status(0, total, false, true); + return err; + } + if (freq != 0) + cc1101_set_frequency(freq); + + s_run = true; + BaseType_t ret = xTaskCreatePinnedToCore(brute_task, + "subghz_brute", + BRUTE_TASK_STACK, + NULL, + BRUTE_TASK_PRIORITY, + &s_task, + BRUTE_TASK_CORE); + if (ret != pdPASS) { + s_run = false; + set_status(0, total, false, true); + return ESP_ERR_NO_MEM; + } + return ESP_OK; +} + +void subghz_brute_stop(void) { + s_run = false; +} + +bool subghz_brute_get_status(subghz_brute_status_t *out) { + if (out == NULL || s_mutex == NULL) + return false; + if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(BRUTE_LOCK_MS)) != pdTRUE) + return false; + *out = s_status; + xSemaphoreGive(s_mutex); + return true; +} diff --git a/firmware_p4/components/Applications/SubGhz/subghz_receiver.c b/firmware_p4/components/Applications/SubGhz/subghz_receiver.c index d34bd4ab6..46f587fd1 100644 --- a/firmware_p4/components/Applications/SubGhz/subghz_receiver.c +++ b/firmware_p4/components/Applications/SubGhz/subghz_receiver.c @@ -15,6 +15,8 @@ #include "subghz_receiver.h" +#include + #include "driver/gpio.h" #include "driver/rmt_rx.h" #include "driver/rmt_encoder.h" @@ -23,6 +25,7 @@ #include "freertos/task.h" #include "sys_prio.h" #include "freertos/queue.h" +#include "freertos/semphr.h" #include "cc1101.h" #include "pin_def.h" @@ -30,6 +33,7 @@ #include "subghz_protocol_registry.h" #include "subghz_analyzer.h" #include "subghz_storage.h" +#include "subghz_transmitter.h" static const char *TAG = "SUBGHZ_RX"; @@ -79,6 +83,49 @@ static uint32_t s_capture_count = 0; static rmt_channel_handle_t s_rx_channel = NULL; static QueueHandle_t s_rx_queue = NULL; +#define RESULT_LOCK_TIMEOUT_MS 20 +static SemaphoreHandle_t s_result_mutex = NULL; +static subghz_rx_result_t s_latest; +static uint32_t s_result_seq = 0; + +static void publish_rx_result(bool decoded, + const subghz_data_t *data, + const subghz_analyzer_result_t *analysis, + const char *save_name) { + if (s_result_mutex == NULL) + return; + if (xSemaphoreTake(s_result_mutex, pdMS_TO_TICKS(RESULT_LOCK_TIMEOUT_MS)) != pdTRUE) + return; + + s_result_seq++; + s_latest.seq = s_result_seq; + s_latest.decoded = decoded; + s_latest.freq = s_rx_freq; + if (save_name != NULL) + strlcpy(s_latest.save_name, save_name, sizeof(s_latest.save_name)); + else + s_latest.save_name[0] = '\0'; + + if (data != NULL) { + s_latest.data = *data; + if (data->protocol_name != NULL) + strlcpy(s_latest.name_buf, data->protocol_name, sizeof(s_latest.name_buf)); + else + s_latest.name_buf[0] = '\0'; + } else { + memset(&s_latest.data, 0, sizeof(s_latest.data)); + s_latest.name_buf[0] = '\0'; + } + s_latest.data.protocol_name = s_latest.name_buf; + + if (analysis != NULL) + s_latest.analysis = *analysis; + else + memset(&s_latest.analysis, 0, sizeof(s_latest.analysis)); + + xSemaphoreGive(s_result_mutex); +} + static void get_dynamic_filename(char *out_name, size_t out_size, const char *prefix) { s_capture_count++; snprintf(out_name, out_size, "%s_%03lu", prefix, (unsigned long)s_capture_count); @@ -121,6 +168,7 @@ static void handle_raw_mode(const int32_t *decode_buffer, size_t decode_idx) { ESP_LOGD(TAG, "RAW: received %d pulses", (int)decode_idx); get_dynamic_filename(filename, sizeof(filename), "RAW"); subghz_storage_save_raw(filename, decode_buffer, decode_idx, s_rx_freq); + publish_rx_result(false, NULL, NULL, filename); } static void log_recovered_bitstream(const subghz_analyzer_result_t *analysis) { @@ -165,6 +213,7 @@ static void handle_scan_mode(const int32_t *decode_buffer, size_t decode_idx) { subghz_analyzer_process(decode_buffer, decode_idx, &analysis); get_dynamic_filename(filename, sizeof(filename), "DEC"); subghz_storage_save_decoded(filename, &decoded, s_rx_freq, analysis.estimated_te); + publish_rx_result(true, &decoded, &analysis, filename); led_signal_info(); // decoded a known protocol return; } @@ -180,6 +229,7 @@ static void handle_scan_mode(const int32_t *decode_buffer, size_t decode_idx) { get_dynamic_filename(filename, sizeof(filename), "UNK"); subghz_storage_save_raw(filename, decode_buffer, decode_idx, s_rx_freq); + publish_rx_result(false, NULL, &analysis, filename); log_recovered_bitstream(&analysis); led_signal_warning(); // captured RF but no known protocol matched } @@ -350,9 +400,21 @@ esp_err_t subghz_receiver_start(subghz_mode_t mode, cc1101_preset_t preset, uint if (s_is_running) { return ESP_ERR_INVALID_STATE; } + + subghz_tx_stop(); + s_rx_mode = mode; s_rx_preset = preset; + if (s_result_mutex == NULL) + s_result_mutex = xSemaphoreCreateMutex(); + if (s_result_mutex != NULL && + xSemaphoreTake(s_result_mutex, pdMS_TO_TICKS(RESULT_LOCK_TIMEOUT_MS)) == pdTRUE) { + s_result_seq = 0; + memset(&s_latest, 0, sizeof(s_latest)); + xSemaphoreGive(s_result_mutex); + } + if (freq == 0) { s_is_hopping_active = true; s_hop_idx = 0; @@ -384,3 +446,18 @@ void subghz_receiver_stop(void) { bool subghz_receiver_is_running(void) { return s_is_running; } + +bool subghz_receiver_get_result(subghz_rx_result_t *out) { + if (out == NULL || s_result_mutex == NULL) + return false; + if (xSemaphoreTake(s_result_mutex, pdMS_TO_TICKS(RESULT_LOCK_TIMEOUT_MS)) != pdTRUE) + return false; + + bool has = (s_latest.seq != 0); + if (has) { + *out = s_latest; + out->data.protocol_name = out->name_buf; + } + xSemaphoreGive(s_result_mutex); + return has; +} diff --git a/firmware_p4/components/Applications/SubGhz/subghz_replay.c b/firmware_p4/components/Applications/SubGhz/subghz_replay.c new file mode 100644 index 000000000..ae8d06d0a --- /dev/null +++ b/firmware_p4/components/Applications/SubGhz/subghz_replay.c @@ -0,0 +1,184 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#include "subghz_replay.h" + +#include +#include +#include + +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "cc1101.h" +#include "subghz_protocol_decoder.h" +#include "subghz_protocol_registry.h" +#include "subghz_protocol_serializer.h" +#include "subghz_receiver.h" +#include "subghz_storage.h" +#include "subghz_transmitter.h" +#include "subghz_types.h" + +static const char *TAG = "SUBGHZ_REPLAY"; + +#define REPLAY_FILE_BUF 8192 +#define REPLAY_MAX_PULSES 2048 +#define REPLAY_RX_DRAIN_MS 150 +#define REPLAY_PROTO_MAX 24 + +static uint32_t parse_u32_after(const char *content, const char *key) { + const char *p = strstr(content, key); + if (p == NULL) + return 0; + return (uint32_t)strtoul(p + strlen(key), NULL, 10); +} + +static void parse_protocol(const char *content, char *out, size_t out_size) { + out[0] = '\0'; + const char *p = strstr(content, "Protocol: "); + if (p == NULL) + return; + p += strlen("Protocol: "); + size_t i = 0; + while (p[i] != '\0' && p[i] != '\n' && p[i] != '\r' && i < out_size - 1) { + out[i] = p[i]; + i++; + } + out[i] = '\0'; +} + +static uint32_t parse_key_value(const char *content) { + const char *p = strstr(content, "Key: "); + if (p == NULL) + return 0; + unsigned b[8] = {0}; + int n = sscanf(p + strlen("Key: "), + "%x %x %x %x %x %x %x %x", + &b[0], + &b[1], + &b[2], + &b[3], + &b[4], + &b[5], + &b[6], + &b[7]); + if (n < 8) + return 0; + return ((uint32_t)b[4] << 24) | ((uint32_t)b[5] << 16) | ((uint32_t)b[6] << 8) | (uint32_t)b[7]; +} + +static const subghz_protocol_t *lookup_protocol(const char *proto) { + const subghz_protocol_t *p = subghz_protocol_registry_get_by_name(proto); + if (p != NULL) + return p; + + char first[REPLAY_PROTO_MAX]; + size_t i = 0; + while (proto[i] != '\0' && proto[i] != ' ' && i < sizeof(first) - 1) { + first[i] = proto[i]; + i++; + } + first[i] = '\0'; + if (i == 0) + return NULL; + return subghz_protocol_registry_get_by_name(first); +} + +static esp_err_t replay_raw(const char *content) { + int32_t *pulses = malloc(REPLAY_MAX_PULSES * sizeof(int32_t)); + if (pulses == NULL) + return ESP_ERR_NO_MEM; + + uint32_t freq = 0; + uint8_t preset = 0; + size_t n = subghz_protocol_parse_raw(content, pulses, REPLAY_MAX_PULSES, &freq, &preset); + if (n == 0) { + free(pulses); + return ESP_FAIL; + } + + if (freq != 0) + cc1101_set_frequency(freq); + esp_err_t err = subghz_tx_send_raw(pulses, n); + free(pulses); + return err; +} + +static esp_err_t replay_decoded(const char *content) { + uint32_t freq = parse_u32_after(content, "Frequency: "); + char proto[REPLAY_PROTO_MAX]; + parse_protocol(content, proto, sizeof(proto)); + + const subghz_protocol_t *p = lookup_protocol(proto); + if (p == NULL || p->encode == NULL) { + ESP_LOGW(TAG, "replay unsupported for protocol '%s'", proto); + return ESP_ERR_NOT_SUPPORTED; + } + + subghz_data_t data = {0}; + data.protocol_name = proto; + data.bit_count = (uint8_t)parse_u32_after(content, "Bit: "); + data.raw_value = parse_key_value(content); + data.serial = data.raw_value; + + int32_t *pulses = malloc(REPLAY_MAX_PULSES * sizeof(int32_t)); + if (pulses == NULL) + return ESP_ERR_NO_MEM; + + size_t n = p->encode(&data, pulses, REPLAY_MAX_PULSES); + if (n == 0) { + free(pulses); + return ESP_FAIL; + } + + if (freq != 0) + cc1101_set_frequency(freq); + esp_err_t err = subghz_tx_send_raw(pulses, n); + free(pulses); + return err; +} + +esp_err_t subghz_replay_file(const char *name) { + if (name == NULL) + return ESP_ERR_INVALID_ARG; + + char *content = malloc(REPLAY_FILE_BUF); + if (content == NULL) + return ESP_ERR_NO_MEM; + + int rd = subghz_storage_read(name, content, REPLAY_FILE_BUF); + if (rd <= 0) { + free(content); + return ESP_ERR_NOT_FOUND; + } + + if (subghz_receiver_is_running()) { + subghz_receiver_stop(); + vTaskDelay(pdMS_TO_TICKS(REPLAY_RX_DRAIN_MS)); + } + + esp_err_t err = subghz_tx_init(); + if (err != ESP_OK) { + free(content); + return err; + } + + bool is_raw = (strstr(content, "RAW_Data:") != NULL); + err = is_raw ? replay_raw(content) : replay_decoded(content); + + free(content); + return err; +} diff --git a/firmware_p4/components/Applications/SubGhz/subghz_settings.c b/firmware_p4/components/Applications/SubGhz/subghz_settings.c new file mode 100644 index 000000000..22d3ed905 --- /dev/null +++ b/firmware_p4/components/Applications/SubGhz/subghz_settings.c @@ -0,0 +1,33 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#include "subghz_settings.h" + +static subghz_settings_t s_settings = { + .preset = CC1101_PRESET_OOK_800KHZ, + .freq = 0, +}; + +subghz_settings_t subghz_settings_get(void) { + return s_settings; +} + +void subghz_settings_set_preset(cc1101_preset_t preset) { + s_settings.preset = preset; +} + +void subghz_settings_set_freq(uint32_t freq) { + s_settings.freq = freq; +} diff --git a/firmware_p4/components/Applications/SubGhz/subghz_spectrum.c b/firmware_p4/components/Applications/SubGhz/subghz_spectrum.c index 7e6f8cfd5..dd70fea10 100644 --- a/firmware_p4/components/Applications/SubGhz/subghz_spectrum.c +++ b/firmware_p4/components/Applications/SubGhz/subghz_spectrum.c @@ -33,8 +33,8 @@ static const char *TAG = "SUBGHZ_SPECTRUM"; #define RSSI_SAMPLE_DELAY_US 20 #define RSSI_SAMPLE_COUNT 3 #define RSSI_MIN_DBM (-130.0f) -#define YIELD_INTERVAL 16 -#define SWEEP_DELAY_MS 5 +#define YIELD_INTERVAL 1 +#define SWEEP_DELAY_MS 15 #define MUTEX_TIMEOUT_MS 10 #define GET_LINE_TIMEOUT_MS 5 #define SPECTRUM_TASK_STACK 4096 diff --git a/firmware_p4/components/Applications/SubGhz/subghz_storage.c b/firmware_p4/components/Applications/SubGhz/subghz_storage.c index ac6652d89..7ca83b08f 100644 --- a/firmware_p4/components/Applications/SubGhz/subghz_storage.c +++ b/firmware_p4/components/Applications/SubGhz/subghz_storage.c @@ -15,23 +15,63 @@ #include "subghz_storage.h" +#include #include #include +#include #include "esp_log.h" +#include "storage_mkdir.h" #include "subghz_protocol_serializer.h" +#include "tos_storage_paths.h" static const char *TAG = "SUBGHZ_STORAGE"; #define DECODED_BUF_SIZE 1024 -#define RAW_BUF_SIZE 4096 +#define RAW_BUF_SIZE 8192 +#define STORAGE_DIR TOS_PATH_SUBGHZ +#define PATH_BUF_SIZE 128 +#define HEADER_PEEK_SIZE 256 +#define SUB_EXT ".sub" + +static void build_path(char *out, size_t out_size, const char *name) { + snprintf(out, out_size, "%s/%s%s", STORAGE_DIR, name, SUB_EXT); +} -esp_err_t subghz_storage_init(void) { - ESP_LOGI(TAG, "Storage API Initialized (Placeholder Mode)"); +static esp_err_t write_file(const char *name, const char *content, size_t len) { + if (name == NULL || content == NULL || len == 0) + return ESP_ERR_INVALID_ARG; + + storage_mkdir_recursive(STORAGE_DIR); + + char path[PATH_BUF_SIZE]; + build_path(path, sizeof(path), name); + + FILE *f = fopen(path, "wb"); + if (f == NULL) { + ESP_LOGE(TAG, "open for write failed: %s", path); + return ESP_FAIL; + } + + size_t written = fwrite(content, 1, len, f); + fclose(f); + + if (written != len) { + ESP_LOGE(TAG, "short write: %s (%u/%u)", path, (unsigned)written, (unsigned)len); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "saved %s (%u bytes)", path, (unsigned)len); return ESP_OK; } +esp_err_t subghz_storage_init(void) { + esp_err_t err = storage_mkdir_recursive(STORAGE_DIR); + ESP_LOGI(TAG, "storage ready at %s", STORAGE_DIR); + return err; +} + esp_err_t subghz_storage_save_decoded(const char *name, const subghz_data_t *data, uint32_t frequency, @@ -42,18 +82,14 @@ esp_err_t subghz_storage_save_decoded(const char *name, } char *buf = malloc(DECODED_BUF_SIZE); - if (buf == NULL) { - ESP_LOGE(TAG, "Failed to allocate decoded buffer"); + if (buf == NULL) return ESP_ERR_NO_MEM; - } - - subghz_protocol_serialize_decoded(data, frequency, te, buf, DECODED_BUF_SIZE); - ESP_LOGI(TAG, "Saving Decoded Signal to %s...", name); - ESP_LOGI(TAG, "\n--- FILE CONTENT START (%s) ---\n%s--- FILE CONTENT END ---\n", name, buf); + size_t len = subghz_protocol_serialize_decoded(data, frequency, te, buf, DECODED_BUF_SIZE); + esp_err_t err = write_file(name, buf, len); free(buf); - return ESP_OK; + return err; } esp_err_t @@ -64,16 +100,107 @@ subghz_storage_save_raw(const char *name, const int32_t *pulses, size_t count, u } char *buf = malloc(RAW_BUF_SIZE); - if (buf == NULL) { - ESP_LOGE(TAG, "Failed to allocate raw buffer"); + if (buf == NULL) return ESP_ERR_NO_MEM; + + size_t len = subghz_protocol_serialize_raw(pulses, count, frequency, buf, RAW_BUF_SIZE); + esp_err_t err = write_file(name, buf, len); + + free(buf); + return err; +} + +int subghz_storage_read(const char *name, char *out_buf, size_t out_size) { + if (name == NULL || out_buf == NULL || out_size == 0) + return -1; + + char path[PATH_BUF_SIZE]; + build_path(path, sizeof(path), name); + + FILE *f = fopen(path, "rb"); + if (f == NULL) + return -1; + + size_t rd = fread(out_buf, 1, out_size - 1, f); + fclose(f); + out_buf[rd] = '\0'; + return (int)rd; +} + +static void +parse_header(const char *content, char *proto_out, size_t proto_size, uint32_t *freq_out) { + proto_out[0] = '\0'; + *freq_out = 0; + + const char *fp = strstr(content, "Frequency: "); + if (fp != NULL) + *freq_out = (uint32_t)strtoul(fp + strlen("Frequency: "), NULL, 10); + + const char *pp = strstr(content, "Protocol: "); + if (pp != NULL) { + pp += strlen("Protocol: "); + size_t i = 0; + while (pp[i] != '\0' && pp[i] != '\n' && pp[i] != '\r' && i < proto_size - 1) { + proto_out[i] = pp[i]; + i++; + } + proto_out[i] = '\0'; } +} - subghz_protocol_serialize_raw(pulses, count, frequency, buf, RAW_BUF_SIZE); +int subghz_storage_list(subghz_storage_entry_t *out, int max_entries) { + if (out == NULL || max_entries <= 0) + return -1; + + DIR *d = opendir(STORAGE_DIR); + if (d == NULL) + return 0; + + int count = 0; + struct dirent *ent; + char content[HEADER_PEEK_SIZE]; + + while ((ent = readdir(d)) != NULL && count < max_entries) { + if (ent->d_name[0] == '.') + continue; + const char *dot = strrchr(ent->d_name, '.'); + if (dot == NULL || strcmp(dot, SUB_EXT) != 0) + continue; + + size_t base_len = (size_t)(dot - ent->d_name); + if (base_len == 0 || base_len >= SUBGHZ_STORAGE_NAME_MAX) + continue; + + subghz_storage_entry_t *e = &out[count]; + memcpy(e->name, ent->d_name, base_len); + e->name[base_len] = '\0'; + e->protocol[0] = '\0'; + e->frequency = 0; + + int rd = subghz_storage_read(e->name, content, sizeof(content)); + if (rd > 0) + parse_header(content, e->protocol, sizeof(e->protocol), &e->frequency); + if (e->protocol[0] == '\0') + strlcpy(e->protocol, "RAW", sizeof(e->protocol)); + + count++; + } - ESP_LOGI(TAG, "Saving RAW Signal to %s...", name); - ESP_LOGI(TAG, "\n--- FILE CONTENT START (%s) ---\n%s--- FILE CONTENT END ---\n", name, buf); + closedir(d); + return count; +} - free(buf); +esp_err_t subghz_storage_delete(const char *name) { + if (name == NULL) + return ESP_ERR_INVALID_ARG; + + char path[PATH_BUF_SIZE]; + build_path(path, sizeof(path), name); + + if (remove(path) != 0) { + ESP_LOGE(TAG, "delete failed: %s", path); + return ESP_FAIL; + } + ESP_LOGI(TAG, "deleted %s", path); return ESP_OK; } diff --git a/firmware_p4/components/Applications/SubGhz/subghz_transmitter.c b/firmware_p4/components/Applications/SubGhz/subghz_transmitter.c index ce4765028..bb72836ce 100644 --- a/firmware_p4/components/Applications/SubGhz/subghz_transmitter.c +++ b/firmware_p4/components/Applications/SubGhz/subghz_transmitter.c @@ -156,7 +156,7 @@ esp_err_t subghz_tx_init(void) { .resolution_hz = RMT_RESOLUTION_HZ, .mem_block_symbols = RMT_MEM_BLOCK_SYMBOLS, .trans_queue_depth = RMT_TRANS_QUEUE_DEPTH, - .gpio_num = GPIO_CC1101_GDO2_PIN, + .gpio_num = GPIO_CC1101_GDO0_PIN, .flags.invert_out = false, }; esp_err_t err = rmt_new_tx_channel(&tx_channel_cfg, &s_tx_channel); diff --git a/firmware_p4/components/Applications/bad_usb/bad_usb.c b/firmware_p4/components/Applications/bad_usb/bad_usb.c index afea66022..b97386f73 100644 --- a/firmware_p4/components/Applications/bad_usb/bad_usb.c +++ b/firmware_p4/components/Applications/bad_usb/bad_usb.c @@ -38,6 +38,7 @@ static bool s_is_initialized = false; static void send_keyboard_report(uint8_t keycode, uint8_t modifier); static void send_mouse_report(int8_t x, int8_t y, uint8_t buttons, int8_t wheel); +static bool hid_report_ready(void); esp_err_t bad_usb_init(void) { if (s_is_initialized) { @@ -51,7 +52,8 @@ esp_err_t bad_usb_init(void) { return err; } - hid_hal_register_callback(send_keyboard_report, send_mouse_report, bad_usb_wait_for_connection); + hid_hal_register_callback( + send_keyboard_report, send_mouse_report, bad_usb_wait_for_connection, hid_report_ready); s_is_initialized = true; ESP_LOGI(TAG, "Initialized"); @@ -65,7 +67,7 @@ esp_err_t bad_usb_deinit(void) { } ESP_LOGI(TAG, "Deinitializing..."); - hid_hal_register_callback(NULL, NULL, NULL); + hid_hal_register_callback(NULL, NULL, NULL, NULL); esp_err_t err = tinyusb_driver_uninstall(); if (err != ESP_OK) { @@ -117,3 +119,7 @@ static void send_keyboard_report(uint8_t keycode, uint8_t modifier) { static void send_mouse_report(int8_t x, int8_t y, uint8_t buttons, int8_t wheel) { tud_hid_mouse_report(HID_REPORT_ID_MOUSE, buttons, x, y, wheel, 0); } + +static bool hid_report_ready(void) { + return tud_hid_ready(); +} diff --git a/firmware_p4/components/Applications/bad_usb/ducky_parser.c b/firmware_p4/components/Applications/bad_usb/ducky_parser.c index 8fbbc84d5..48baaf8dd 100644 --- a/firmware_p4/components/Applications/bad_usb/ducky_parser.c +++ b/firmware_p4/components/Applications/bad_usb/ducky_parser.c @@ -259,7 +259,7 @@ static bool is_modifier(const char *word, uint8_t *out_mod) { return true; } if (strcasecmp(word, "GUI") == 0 || strcasecmp(word, "WINDOWS") == 0 || - strcasecmp(word, "COMMAND") == 0) { + strcasecmp(word, "SUPER") == 0 || strcasecmp(word, "COMMAND") == 0) { *out_mod |= KEYBOARD_MODIFIER_LEFTGUI; return true; } diff --git a/firmware_p4/components/Applications/bad_usb/hid_hal.c b/firmware_p4/components/Applications/bad_usb/hid_hal.c index d7170c050..1e23ed169 100644 --- a/firmware_p4/components/Applications/bad_usb/hid_hal.c +++ b/firmware_p4/components/Applications/bad_usb/hid_hal.c @@ -24,21 +24,41 @@ static const char *TAG = "HID_HAL"; -#define KEY_PRESS_DELAY_US 5000 -#define KEY_RELEASE_DELAY_US 5000 -#define MOUSE_MOVE_DELAY_US 2000 -#define MOUSE_CLICK_DELAY_US 5000 +// Fallback pacing used only when no readiness source is registered. +#define FALLBACK_KEY_DELAY_US 5000 +#define FALLBACK_MOUSE_DELAY_US 2000 +// Must exceed how long a busy host/editor can stall HID polling, else a report +// is sent into a busy endpoint and dropped. Only a real disconnect should hit it. +#define REPORT_READY_TIMEOUT_MS 1000 static hid_send_cb_t s_send_cb = NULL; static hid_mouse_cb_t s_mouse_cb = NULL; static hid_wait_cb_t s_wait_cb = NULL; +static hid_ready_cb_t s_ready_cb = NULL; void hid_hal_register_callback(hid_send_cb_t send_cb, hid_mouse_cb_t mouse_cb, - hid_wait_cb_t wait_cb) { + hid_wait_cb_t wait_cb, + hid_ready_cb_t ready_cb) { s_send_cb = send_cb; s_mouse_cb = mouse_cb; s_wait_cb = wait_cb; + s_ready_cb = ready_cb; +} + +// Wait until the previous report was delivered before sending the next, so we +// never overwrite an unpolled report (dropped keys). Paces to the host's ~1ms poll. +static void wait_report_ready(uint32_t fallback_us) { + if (s_ready_cb == NULL) { + ets_delay_us(fallback_us); + return; + } + for (uint32_t waited_ms = 0; !s_ready_cb(); waited_ms++) { + if (waited_ms >= REPORT_READY_TIMEOUT_MS) { + return; + } + vTaskDelay(pdMS_TO_TICKS(1)); + } } void hid_hal_press_key(uint8_t keycode, uint8_t modifiers) { @@ -46,13 +66,11 @@ void hid_hal_press_key(uint8_t keycode, uint8_t modifiers) { return; } + wait_report_ready(FALLBACK_KEY_DELAY_US); s_send_cb(keycode, modifiers); - ets_delay_us(KEY_PRESS_DELAY_US); - + wait_report_ready(FALLBACK_KEY_DELAY_US); s_send_cb(0, 0); - ets_delay_us(KEY_RELEASE_DELAY_US); - - vTaskDelay(0); // Yield to prevent WDT starvation + wait_report_ready(FALLBACK_KEY_DELAY_US); } void hid_hal_mouse_move(int8_t x, int8_t y) { @@ -60,9 +78,9 @@ void hid_hal_mouse_move(int8_t x, int8_t y) { return; } + wait_report_ready(FALLBACK_MOUSE_DELAY_US); s_mouse_cb(x, y, 0, 0); - ets_delay_us(MOUSE_MOVE_DELAY_US); - vTaskDelay(0); + wait_report_ready(FALLBACK_MOUSE_DELAY_US); } void hid_hal_mouse_click(uint8_t buttons) { @@ -70,11 +88,11 @@ void hid_hal_mouse_click(uint8_t buttons) { return; } + wait_report_ready(FALLBACK_MOUSE_DELAY_US); s_mouse_cb(0, 0, buttons, 0); // Press - ets_delay_us(MOUSE_CLICK_DELAY_US); + wait_report_ready(FALLBACK_MOUSE_DELAY_US); s_mouse_cb(0, 0, 0, 0); // Release - ets_delay_us(MOUSE_CLICK_DELAY_US); - vTaskDelay(0); + wait_report_ready(FALLBACK_MOUSE_DELAY_US); } void hid_hal_mouse_scroll(int8_t wheel) { @@ -82,9 +100,9 @@ void hid_hal_mouse_scroll(int8_t wheel) { return; } + wait_report_ready(FALLBACK_MOUSE_DELAY_US); s_mouse_cb(0, 0, 0, wheel); - ets_delay_us(MOUSE_MOVE_DELAY_US); - vTaskDelay(0); + wait_report_ready(FALLBACK_MOUSE_DELAY_US); } void hid_hal_wait_for_connection(void) { diff --git a/firmware_p4/components/Applications/bad_usb/hid_layouts.c b/firmware_p4/components/Applications/bad_usb/hid_layouts.c index 01c5ff61d..37b2e3344 100644 --- a/firmware_p4/components/Applications/bad_usb/hid_layouts.c +++ b/firmware_p4/components/Applications/bad_usb/hid_layouts.c @@ -32,22 +32,6 @@ static const char *TAG = "HID_LAYOUTS"; #define HID_KEY_NON_US_BACKSLASH 0x64 #endif -// ABNT2 UTF-8 byte pairs for accented characters -#define UTF8_LOWER_C_CEDILLA_B2 0xA7 // c = 0xC3 0xA7 -#define UTF8_UPPER_C_CEDILLA_B2 0x87 // C = 0xC3 0x87 -#define UTF8_LOWER_A_ACUTE_B2 0xA1 // a = 0xC3 0xA1 -#define UTF8_LOWER_E_ACUTE_B2 0xA9 // e = 0xC3 0xA9 -#define UTF8_LOWER_I_ACUTE_B2 0xAD // i = 0xC3 0xAD -#define UTF8_LOWER_O_ACUTE_B2 0xB3 // o = 0xC3 0xB3 -#define UTF8_LOWER_U_ACUTE_B2 0xBA // u = 0xC3 0xBA -#define UTF8_LOWER_A_CIRCUM_B2 0xA2 // a = 0xC3 0xA2 -#define UTF8_LOWER_E_CIRCUM_B2 0xAA // e = 0xC3 0xAA -#define UTF8_LOWER_O_CIRCUM_B2 0xB4 // o = 0xC3 0xB4 -#define UTF8_LOWER_A_TILDE_B2 0xA3 // a = 0xC3 0xA3 -#define UTF8_LOWER_O_TILDE_B2 0xB5 // o = 0xC3 0xB5 -#define UTF8_LOWER_A_GRAVE_B2 0xA0 // a = 0xC3 0xA0 -#define UTF8_2BYTE_LEAD 0xC3 - static bool try_decode_abnt2_utf8(uint8_t c1, uint8_t c2); void hid_layouts_type_string_us(const char *str) { @@ -144,6 +128,19 @@ void hid_layouts_type_string_us(const char *str) { case ';': keycode = HID_KEY_SEMICOLON; break; + case '\'': + keycode = HID_KEY_APOSTROPHE; + break; + case '`': + keycode = HID_KEY_GRAVE; + break; + case '\\': + keycode = HID_KEY_BACKSLASH; + break; + case '|': + modifier = KEYBOARD_MODIFIER_LEFTSHIFT; + keycode = HID_KEY_BACKSLASH; + break; default: break; } @@ -160,16 +157,32 @@ void hid_layouts_type_string_abnt2(const char *str) { uint8_t c1 = (uint8_t)str[i]; uint8_t c2 = (uint8_t)str[i + 1]; - // Single quote -> dead key acute + space + // Apostrophe and double quote sit on the key left of '1' (US grave position). if (c1 == '\'') { - hid_hal_press_key(HID_KEY_BRACKET_LEFT, 0); - hid_hal_press_key(HID_KEY_SPACE, 0); + hid_hal_press_key(HID_KEY_GRAVE, 0); continue; } - - // Double quote -> dead key acute + shift if (c1 == '"') { + hid_hal_press_key(HID_KEY_GRAVE, KEYBOARD_MODIFIER_LEFTSHIFT); + continue; + } + + // Backtick is the grave dead key (Shift + acute key); space emits the literal. + if (c1 == '`') { hid_hal_press_key(HID_KEY_BRACKET_LEFT, KEYBOARD_MODIFIER_LEFTSHIFT); + hid_hal_press_key(HID_KEY_SPACE, 0); + continue; + } + + // Circumflex/tilde are dead keys; a trailing space emits the literal char. + if (c1 == '^') { + hid_hal_press_key(HID_KEY_APOSTROPHE, KEYBOARD_MODIFIER_LEFTSHIFT); + hid_hal_press_key(HID_KEY_SPACE, 0); + continue; + } + if (c1 == '~') { + hid_hal_press_key(HID_KEY_APOSTROPHE, 0); + hid_hal_press_key(HID_KEY_SPACE, 0); continue; } @@ -260,6 +273,14 @@ void hid_layouts_type_string_abnt2(const char *str) { case ',': keycode = HID_KEY_COMMA; break; + case '<': + modifier = KEYBOARD_MODIFIER_LEFTSHIFT; + keycode = HID_KEY_COMMA; + break; + case '>': + modifier = KEYBOARD_MODIFIER_LEFTSHIFT; + keycode = HID_KEY_PERIOD; + break; case ';': keycode = HID_KEY_SLASH; break; @@ -308,83 +329,96 @@ void hid_layouts_type_string_abnt2(const char *str) { } } -static bool try_decode_abnt2_utf8(uint8_t c1, uint8_t c2) { - if (c1 != UTF8_2BYTE_LEAD) { - return false; - } +typedef struct { + uint8_t c1; // UTF-8 lead byte (0xC2 or 0xC3) + uint8_t c2; // UTF-8 continuation byte + uint8_t k1; // dead key or direct key + uint8_t m1; // modifier for k1 + uint8_t k2; // base letter, 0 for a single keypress + uint8_t m2; // modifier for k2 (shift = uppercase) +} abnt2_utf8_entry_t; - // Cedilla - if (c2 == UTF8_LOWER_C_CEDILLA_B2) { - hid_hal_press_key(HID_KEY_SEMICOLON, 0); - return true; - } - if (c2 == UTF8_UPPER_C_CEDILLA_B2) { - hid_hal_press_key(HID_KEY_SEMICOLON, KEYBOARD_MODIFIER_LEFTSHIFT); - return true; - } +// ABNT2 dead-key prefix (key, modifier) pressed before the base letter. +#define DK_ACUTE HID_KEY_BRACKET_LEFT, 0 +#define DK_GRAVE HID_KEY_BRACKET_LEFT, KEYBOARD_MODIFIER_LEFTSHIFT +#define DK_CIRCUM HID_KEY_APOSTROPHE, KEYBOARD_MODIFIER_LEFTSHIFT +#define DK_TILDE HID_KEY_APOSTROPHE, 0 +#define DK_DIAER HID_KEY_6, KEYBOARD_MODIFIER_LEFTSHIFT - // Acute accent (dead key = BRACKET_LEFT) - if (c2 == UTF8_LOWER_A_ACUTE_B2) { - hid_hal_press_key(HID_KEY_BRACKET_LEFT, 0); - hid_hal_press_key(HID_KEY_A, 0); - return true; - } - if (c2 == UTF8_LOWER_E_ACUTE_B2) { - hid_hal_press_key(HID_KEY_BRACKET_LEFT, 0); - hid_hal_press_key(HID_KEY_E, 0); - return true; - } - if (c2 == UTF8_LOWER_I_ACUTE_B2) { - hid_hal_press_key(HID_KEY_BRACKET_LEFT, 0); - hid_hal_press_key(HID_KEY_I, 0); - return true; - } - if (c2 == UTF8_LOWER_O_ACUTE_B2) { - hid_hal_press_key(HID_KEY_BRACKET_LEFT, 0); - hid_hal_press_key(HID_KEY_O, 0); - return true; - } - if (c2 == UTF8_LOWER_U_ACUTE_B2) { - hid_hal_press_key(HID_KEY_BRACKET_LEFT, 0); - hid_hal_press_key(HID_KEY_U, 0); - return true; - } +static const abnt2_utf8_entry_t ABNT2_UTF8_MAP[] = { + {0xC3, 0xA7, HID_KEY_SEMICOLON, 0, 0, 0}, // ç + {0xC3, 0x87, HID_KEY_SEMICOLON, KEYBOARD_MODIFIER_LEFTSHIFT, 0, 0}, // Ç + {0xC3, 0xA0, DK_GRAVE, HID_KEY_A, 0}, // à + {0xC3, 0xA1, DK_ACUTE, HID_KEY_A, 0}, // á + {0xC3, 0xA2, DK_CIRCUM, HID_KEY_A, 0}, // â + {0xC3, 0xA3, DK_TILDE, HID_KEY_A, 0}, // ã + {0xC3, 0xA4, DK_DIAER, HID_KEY_A, 0}, // ä + {0xC3, 0xA8, DK_GRAVE, HID_KEY_E, 0}, // è + {0xC3, 0xA9, DK_ACUTE, HID_KEY_E, 0}, // é + {0xC3, 0xAA, DK_CIRCUM, HID_KEY_E, 0}, // ê + {0xC3, 0xAB, DK_DIAER, HID_KEY_E, 0}, // ë + {0xC3, 0xAC, DK_GRAVE, HID_KEY_I, 0}, // ì + {0xC3, 0xAD, DK_ACUTE, HID_KEY_I, 0}, // í + {0xC3, 0xAE, DK_CIRCUM, HID_KEY_I, 0}, // î + {0xC3, 0xAF, DK_DIAER, HID_KEY_I, 0}, // ï + {0xC3, 0xB1, DK_TILDE, HID_KEY_N, 0}, // ñ + {0xC3, 0xB2, DK_GRAVE, HID_KEY_O, 0}, // ò + {0xC3, 0xB3, DK_ACUTE, HID_KEY_O, 0}, // ó + {0xC3, 0xB4, DK_CIRCUM, HID_KEY_O, 0}, // ô + {0xC3, 0xB5, DK_TILDE, HID_KEY_O, 0}, // õ + {0xC3, 0xB6, DK_DIAER, HID_KEY_O, 0}, // ö + {0xC3, 0xB9, DK_GRAVE, HID_KEY_U, 0}, // ù + {0xC3, 0xBA, DK_ACUTE, HID_KEY_U, 0}, // ú + {0xC3, 0xBB, DK_CIRCUM, HID_KEY_U, 0}, // û + {0xC3, 0xBC, DK_DIAER, HID_KEY_U, 0}, // ü + {0xC3, 0x80, DK_GRAVE, HID_KEY_A, KEYBOARD_MODIFIER_LEFTSHIFT}, // À + {0xC3, 0x81, DK_ACUTE, HID_KEY_A, KEYBOARD_MODIFIER_LEFTSHIFT}, // Á + {0xC3, 0x82, DK_CIRCUM, HID_KEY_A, KEYBOARD_MODIFIER_LEFTSHIFT}, // Â + {0xC3, 0x83, DK_TILDE, HID_KEY_A, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ã + {0xC3, 0x84, DK_DIAER, HID_KEY_A, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ä + {0xC3, 0x88, DK_GRAVE, HID_KEY_E, KEYBOARD_MODIFIER_LEFTSHIFT}, // È + {0xC3, 0x89, DK_ACUTE, HID_KEY_E, KEYBOARD_MODIFIER_LEFTSHIFT}, // É + {0xC3, 0x8A, DK_CIRCUM, HID_KEY_E, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ê + {0xC3, 0x8B, DK_DIAER, HID_KEY_E, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ë + {0xC3, 0x8C, DK_GRAVE, HID_KEY_I, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ì + {0xC3, 0x8D, DK_ACUTE, HID_KEY_I, KEYBOARD_MODIFIER_LEFTSHIFT}, // Í + {0xC3, 0x8E, DK_CIRCUM, HID_KEY_I, KEYBOARD_MODIFIER_LEFTSHIFT}, // Î + {0xC3, 0x8F, DK_DIAER, HID_KEY_I, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ï + {0xC3, 0x91, DK_TILDE, HID_KEY_N, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ñ + {0xC3, 0x92, DK_GRAVE, HID_KEY_O, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ò + {0xC3, 0x93, DK_ACUTE, HID_KEY_O, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ó + {0xC3, 0x94, DK_CIRCUM, HID_KEY_O, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ô + {0xC3, 0x95, DK_TILDE, HID_KEY_O, KEYBOARD_MODIFIER_LEFTSHIFT}, // Õ + {0xC3, 0x96, DK_DIAER, HID_KEY_O, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ö + {0xC3, 0x99, DK_GRAVE, HID_KEY_U, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ù + {0xC3, 0x9A, DK_ACUTE, HID_KEY_U, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ú + {0xC3, 0x9B, DK_CIRCUM, HID_KEY_U, KEYBOARD_MODIFIER_LEFTSHIFT}, // Û + {0xC3, 0x9C, DK_DIAER, HID_KEY_U, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ü + {0xC2, 0xA2, HID_KEY_5, KEYBOARD_MODIFIER_RIGHTALT, 0, 0}, // ¢ + {0xC2, 0xA3, HID_KEY_4, KEYBOARD_MODIFIER_RIGHTALT, 0, 0}, // £ + {0xC2, 0xA7, HID_KEY_EQUAL, KEYBOARD_MODIFIER_RIGHTALT, 0, 0}, // § + {0xC2, 0xAC, HID_KEY_6, KEYBOARD_MODIFIER_RIGHTALT, 0, 0}, // ¬ + {0xC2, 0xB2, HID_KEY_2, KEYBOARD_MODIFIER_RIGHTALT, 0, 0}, // ² + {0xC2, 0xB3, HID_KEY_3, KEYBOARD_MODIFIER_RIGHTALT, 0, 0}, // ³ + {0xC2, 0xB9, HID_KEY_1, KEYBOARD_MODIFIER_RIGHTALT, 0, 0}, // ¹ +}; - // Circumflex accent (dead key = APOSTROPHE + SHIFT) - if (c2 == UTF8_LOWER_A_CIRCUM_B2) { - hid_hal_press_key(HID_KEY_APOSTROPHE, KEYBOARD_MODIFIER_LEFTSHIFT); - hid_hal_press_key(HID_KEY_A, 0); - return true; - } - if (c2 == UTF8_LOWER_E_CIRCUM_B2) { - hid_hal_press_key(HID_KEY_APOSTROPHE, KEYBOARD_MODIFIER_LEFTSHIFT); - hid_hal_press_key(HID_KEY_E, 0); - return true; - } - if (c2 == UTF8_LOWER_O_CIRCUM_B2) { - hid_hal_press_key(HID_KEY_APOSTROPHE, KEYBOARD_MODIFIER_LEFTSHIFT); - hid_hal_press_key(HID_KEY_O, 0); - return true; - } +#undef DK_ACUTE +#undef DK_GRAVE +#undef DK_CIRCUM +#undef DK_TILDE +#undef DK_DIAER - // Tilde (dead key = APOSTROPHE) - if (c2 == UTF8_LOWER_A_TILDE_B2) { - hid_hal_press_key(HID_KEY_APOSTROPHE, 0); - hid_hal_press_key(HID_KEY_A, 0); - return true; - } - if (c2 == UTF8_LOWER_O_TILDE_B2) { - hid_hal_press_key(HID_KEY_APOSTROPHE, 0); - hid_hal_press_key(HID_KEY_O, 0); - return true; - } - - // Grave accent (dead key = BRACKET_LEFT + SHIFT) - if (c2 == UTF8_LOWER_A_GRAVE_B2) { - hid_hal_press_key(HID_KEY_BRACKET_LEFT, KEYBOARD_MODIFIER_LEFTSHIFT); - hid_hal_press_key(HID_KEY_A, 0); - return true; +static bool try_decode_abnt2_utf8(uint8_t c1, uint8_t c2) { + for (size_t i = 0; i < sizeof(ABNT2_UTF8_MAP) / sizeof(ABNT2_UTF8_MAP[0]); ++i) { + const abnt2_utf8_entry_t *e = &ABNT2_UTF8_MAP[i]; + if (e->c1 == c1 && e->c2 == c2) { + hid_hal_press_key(e->k1, e->m1); + if (e->k2 != 0) { + hid_hal_press_key(e->k2, e->m2); + } + return true; + } } - return false; } diff --git a/firmware_p4/components/Applications/bad_usb/include/hid_hal.h b/firmware_p4/components/Applications/bad_usb/include/hid_hal.h index 2598af055..2ad15ef8f 100644 --- a/firmware_p4/components/Applications/bad_usb/include/hid_hal.h +++ b/firmware_p4/components/Applications/bad_usb/include/hid_hal.h @@ -20,6 +20,7 @@ extern "C" { #endif +#include #include /** @@ -45,6 +46,15 @@ typedef void (*hid_mouse_cb_t)(int8_t x, int8_t y, uint8_t buttons, int8_t wheel */ typedef void (*hid_wait_cb_t)(void); +/** + * @brief Callback type reporting whether the transport can accept a new report. + * + * Returns true once the previous report has been delivered to the host. The HAL + * gates on this to send at the host's poll rate without overwriting a report the + * host has not read yet (which would drop keys). + */ +typedef bool (*hid_ready_cb_t)(void); + /** * @brief Register the transport driver callbacks. * @@ -54,10 +64,12 @@ typedef void (*hid_wait_cb_t)(void); * @param send_cb Keyboard report callback. * @param mouse_cb Mouse report callback. * @param wait_cb Connection wait callback. + * @param ready_cb Report-readiness callback (NULL falls back to fixed delays). */ void hid_hal_register_callback(hid_send_cb_t send_cb, hid_mouse_cb_t mouse_cb, - hid_wait_cb_t wait_cb); + hid_wait_cb_t wait_cb, + hid_ready_cb_t ready_cb); /** * @brief Press and release a key. diff --git a/firmware_p4/components/Applications/doom/CMakeLists.txt b/firmware_p4/components/Applications/doom/CMakeLists.txt new file mode 100644 index 000000000..39b6b4e47 --- /dev/null +++ b/firmware_p4/components/Applications/doom/CMakeLists.txt @@ -0,0 +1,23 @@ +# Official DOOM (doomgeneric) as an ESP-IDF component for the HighBoy P4. +# All engine sources + doomgeneric.c + the HighBoy platform layer compile here. +file(GLOB DOOM_SRCS "*.c") + +idf_component_register( + SRCS ${DOOM_SRCS} + INCLUDE_DIRS "include" # public: only doom_highboy.h (launcher entry) + PRIV_INCLUDE_DIRS "." # private: doomgeneric internal headers (config.h, etc.) + REQUIRES Drivers Service esp_lcd esp_timer esp_system driver + LDFRAGMENTS "linker.lf" # park DOOM's ~300 KB static .bss in PSRAM +) + +# Render at DOOM's native internal resolution (no upscale). ALL sources must +# agree, so define it component-wide. +target_compile_definitions(${COMPONENT_LIB} PRIVATE + DOOMGENERIC_RESX=320 + DOOMGENERIC_RESY=200 + FEATURE_SOUND=1 # enable SFX (DG_sound_module in doom_sound_highboy.c) +) + +# Legacy id-Software C: silence its many warnings and optimize for framerate +# (the project default is -Og; DOOM needs -O2 to be playable). +target_compile_options(${COMPONENT_LIB} PRIVATE -O2 -w) diff --git a/firmware_p4/components/Applications/doom/am_map.c b/firmware_p4/components/Applications/doom/am_map.c new file mode 100644 index 000000000..d3d504e32 --- /dev/null +++ b/firmware_p4/components/Applications/doom/am_map.c @@ -0,0 +1,1355 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// +// DESCRIPTION: the automap code +// + + +#include + +#include "deh_main.h" + +#include "z_zone.h" +#include "doomkeys.h" +#include "doomdef.h" +#include "st_stuff.h" +#include "p_local.h" +#include "w_wad.h" + +#include "m_cheat.h" +#include "m_controls.h" +#include "m_misc.h" +#include "i_system.h" + +// Needs access to LFB. +#include "v_video.h" + +// State. +#include "doomstat.h" +#include "r_state.h" + +// Data. +#include "dstrings.h" + +#include "am_map.h" + + +// For use if I do walls with outsides/insides +#define REDS (256-5*16) +#define REDRANGE 16 +#define BLUES (256-4*16+8) +#define BLUERANGE 8 +#define GREENS (7*16) +#define GREENRANGE 16 +#define GRAYS (6*16) +#define GRAYSRANGE 16 +#define BROWNS (4*16) +#define BROWNRANGE 16 +#define YELLOWS (256-32+7) +#define YELLOWRANGE 1 +#define BLACK 0 +#define WHITE (256-47) + +// Automap colors +#define BACKGROUND BLACK +#define YOURCOLORS WHITE +#define YOURRANGE 0 +#define WALLCOLORS REDS +#define WALLRANGE REDRANGE +#define TSWALLCOLORS GRAYS +#define TSWALLRANGE GRAYSRANGE +#define FDWALLCOLORS BROWNS +#define FDWALLRANGE BROWNRANGE +#define CDWALLCOLORS YELLOWS +#define CDWALLRANGE YELLOWRANGE +#define THINGCOLORS GREENS +#define THINGRANGE GREENRANGE +#define SECRETWALLCOLORS WALLCOLORS +#define SECRETWALLRANGE WALLRANGE +#define GRIDCOLORS (GRAYS + GRAYSRANGE/2) +#define GRIDRANGE 0 +#define XHAIRCOLORS GRAYS + +// drawing stuff + +#define AM_NUMMARKPOINTS 10 + +// scale on entry +#define INITSCALEMTOF (.2*FRACUNIT) +// how much the automap moves window per tic in frame-buffer coordinates +// moves 140 pixels in 1 second +#define F_PANINC 4 +// how much zoom-in per tic +// goes to 2x in 1 second +#define M_ZOOMIN ((int) (1.02*FRACUNIT)) +// how much zoom-out per tic +// pulls out to 0.5x in 1 second +#define M_ZOOMOUT ((int) (FRACUNIT/1.02)) + +// translates between frame-buffer and map distances +#define FTOM(x) FixedMul(((x)<<16),scale_ftom) +#define MTOF(x) (FixedMul((x),scale_mtof)>>16) +// translates between frame-buffer and map coordinates +#define CXMTOF(x) (f_x + MTOF((x)-m_x)) +#define CYMTOF(y) (f_y + (f_h - MTOF((y)-m_y))) + +// the following is crap +#define LINE_NEVERSEE ML_DONTDRAW + +typedef struct +{ + int x, y; +} fpoint_t; + +typedef struct +{ + fpoint_t a, b; +} fline_t; + +typedef struct +{ + fixed_t x,y; +} mpoint_t; + +typedef struct +{ + mpoint_t a, b; +} mline_t; + +typedef struct +{ + fixed_t slp, islp; +} islope_t; + + + +// +// The vector graphics for the automap. +// A line drawing of the player pointing right, +// starting from the middle. +// +#define R ((8*PLAYERRADIUS)/7) +mline_t player_arrow[] = { + { { -R+R/8, 0 }, { R, 0 } }, // ----- + { { R, 0 }, { R-R/2, R/4 } }, // -----> + { { R, 0 }, { R-R/2, -R/4 } }, + { { -R+R/8, 0 }, { -R-R/8, R/4 } }, // >----> + { { -R+R/8, 0 }, { -R-R/8, -R/4 } }, + { { -R+3*R/8, 0 }, { -R+R/8, R/4 } }, // >>---> + { { -R+3*R/8, 0 }, { -R+R/8, -R/4 } } +}; +#undef R + +#define R ((8*PLAYERRADIUS)/7) +mline_t cheat_player_arrow[] = { + { { -R+R/8, 0 }, { R, 0 } }, // ----- + { { R, 0 }, { R-R/2, R/6 } }, // -----> + { { R, 0 }, { R-R/2, -R/6 } }, + { { -R+R/8, 0 }, { -R-R/8, R/6 } }, // >-----> + { { -R+R/8, 0 }, { -R-R/8, -R/6 } }, + { { -R+3*R/8, 0 }, { -R+R/8, R/6 } }, // >>-----> + { { -R+3*R/8, 0 }, { -R+R/8, -R/6 } }, + { { -R/2, 0 }, { -R/2, -R/6 } }, // >>-d---> + { { -R/2, -R/6 }, { -R/2+R/6, -R/6 } }, + { { -R/2+R/6, -R/6 }, { -R/2+R/6, R/4 } }, + { { -R/6, 0 }, { -R/6, -R/6 } }, // >>-dd--> + { { -R/6, -R/6 }, { 0, -R/6 } }, + { { 0, -R/6 }, { 0, R/4 } }, + { { R/6, R/4 }, { R/6, -R/7 } }, // >>-ddt-> + { { R/6, -R/7 }, { R/6+R/32, -R/7-R/32 } }, + { { R/6+R/32, -R/7-R/32 }, { R/6+R/10, -R/7 } } +}; +#undef R + +#define R (FRACUNIT) +mline_t triangle_guy[] = { + { { (fixed_t)(-.867*R), (fixed_t)(-.5*R) }, { (fixed_t)(.867*R ), (fixed_t)(-.5*R) } }, + { { (fixed_t)(.867*R ), (fixed_t)(-.5*R) }, { (fixed_t)(0 ), (fixed_t)(R ) } }, + { { (fixed_t)(0 ), (fixed_t)(R ) }, { (fixed_t)(-.867*R), (fixed_t)(-.5*R) } } +}; +#undef R + +#define R (FRACUNIT) +mline_t thintriangle_guy[] = { + { { (fixed_t)(-.5*R), (fixed_t)(-.7*R) }, { (fixed_t)(R ), (fixed_t)(0 ) } }, + { { (fixed_t)(R ), (fixed_t)(0 ) }, { (fixed_t)(-.5*R), (fixed_t)(.7*R ) } }, + { { (fixed_t)(-.5*R), (fixed_t)(.7*R ) }, { (fixed_t)(-.5*R), (fixed_t)(-.7*R) } } +}; +#undef R + + + + +static int cheating = 0; +static int grid = 0; + +static int leveljuststarted = 1; // kluge until AM_LevelInit() is called + +boolean automapactive = false; +static int finit_width = SCREENWIDTH; +static int finit_height = SCREENHEIGHT - 32; + +// location of window on screen +static int f_x; +static int f_y; + +// size of window on screen +static int f_w; +static int f_h; + +static int lightlev; // used for funky strobing effect +static byte* fb; // pseudo-frame buffer +static int amclock; + +static mpoint_t m_paninc; // how far the window pans each tic (map coords) +static fixed_t mtof_zoommul; // how far the window zooms in each tic (map coords) +static fixed_t ftom_zoommul; // how far the window zooms in each tic (fb coords) + +static fixed_t m_x, m_y; // LL x,y where the window is on the map (map coords) +static fixed_t m_x2, m_y2; // UR x,y where the window is on the map (map coords) + +// +// width/height of window on map (map coords) +// +static fixed_t m_w; +static fixed_t m_h; + +// based on level size +static fixed_t min_x; +static fixed_t min_y; +static fixed_t max_x; +static fixed_t max_y; + +static fixed_t max_w; // max_x-min_x, +static fixed_t max_h; // max_y-min_y + +// based on player size +static fixed_t min_w; +static fixed_t min_h; + + +static fixed_t min_scale_mtof; // used to tell when to stop zooming out +static fixed_t max_scale_mtof; // used to tell when to stop zooming in + +// old stuff for recovery later +static fixed_t old_m_w, old_m_h; +static fixed_t old_m_x, old_m_y; + +// old location used by the Follower routine +static mpoint_t f_oldloc; + +// used by MTOF to scale from map-to-frame-buffer coords +static fixed_t scale_mtof = (fixed_t)INITSCALEMTOF; +// used by FTOM to scale from frame-buffer-to-map coords (=1/scale_mtof) +static fixed_t scale_ftom; + +static player_t *plr; // the player represented by an arrow + +static patch_t *marknums[10]; // numbers used for marking by the automap +static mpoint_t markpoints[AM_NUMMARKPOINTS]; // where the points are +static int markpointnum = 0; // next point to be assigned + +static int followplayer = 1; // specifies whether to follow the player around + +cheatseq_t cheat_amap = CHEAT("iddt", 0); + +static boolean stopped = true; + +// Calculates the slope and slope according to the x-axis of a line +// segment in map coordinates (with the upright y-axis n' all) so +// that it can be used with the brain-dead drawing stuff. + +void +AM_getIslope +( mline_t* ml, + islope_t* is ) +{ + int dx, dy; + + dy = ml->a.y - ml->b.y; + dx = ml->b.x - ml->a.x; + if (!dy) is->islp = (dx<0?-INT_MAX:INT_MAX); + else is->islp = FixedDiv(dx, dy); + if (!dx) is->slp = (dy<0?-INT_MAX:INT_MAX); + else is->slp = FixedDiv(dy, dx); + +} + +// +// +// +void AM_activateNewScale(void) +{ + m_x += m_w/2; + m_y += m_h/2; + m_w = FTOM(f_w); + m_h = FTOM(f_h); + m_x -= m_w/2; + m_y -= m_h/2; + m_x2 = m_x + m_w; + m_y2 = m_y + m_h; +} + +// +// +// +void AM_saveScaleAndLoc(void) +{ + old_m_x = m_x; + old_m_y = m_y; + old_m_w = m_w; + old_m_h = m_h; +} + +// +// +// +void AM_restoreScaleAndLoc(void) +{ + + m_w = old_m_w; + m_h = old_m_h; + if (!followplayer) + { + m_x = old_m_x; + m_y = old_m_y; + } else { + m_x = plr->mo->x - m_w/2; + m_y = plr->mo->y - m_h/2; + } + m_x2 = m_x + m_w; + m_y2 = m_y + m_h; + + // Change the scaling multipliers + scale_mtof = FixedDiv(f_w< max_x) + max_x = vertexes[i].x; + + if (vertexes[i].y < min_y) + min_y = vertexes[i].y; + else if (vertexes[i].y > max_y) + max_y = vertexes[i].y; + } + + max_w = max_x - min_x; + max_h = max_y - min_y; + + min_w = 2*PLAYERRADIUS; // const? never changed? + min_h = 2*PLAYERRADIUS; + + a = FixedDiv(f_w< max_x) + m_x = max_x - m_w/2; + else if (m_x + m_w/2 < min_x) + m_x = min_x - m_w/2; + + if (m_y + m_h/2 > max_y) + m_y = max_y - m_h/2; + else if (m_y + m_h/2 < min_y) + m_y = min_y - m_h/2; + + m_x2 = m_x + m_w; + m_y2 = m_y + m_h; +} + + +// +// +// +void AM_initVariables(void) +{ + int pnum; + static event_t st_notify = { ev_keyup, AM_MSGENTERED, 0, 0 }; + + automapactive = true; + fb = I_VideoBuffer; + + f_oldloc.x = INT_MAX; + amclock = 0; + lightlev = 0; + + m_paninc.x = m_paninc.y = 0; + ftom_zoommul = FRACUNIT; + mtof_zoommul = FRACUNIT; + + m_w = FTOM(f_w); + m_h = FTOM(f_h); + + // find player to center on initially + if (playeringame[consoleplayer]) + { + plr = &players[consoleplayer]; + } + else + { + plr = &players[0]; + + for (pnum=0;pnummo->x - m_w/2; + m_y = plr->mo->y - m_h/2; + AM_changeWindowLoc(); + + // for saving & restoring + old_m_x = m_x; + old_m_y = m_y; + old_m_w = m_w; + old_m_h = m_h; + + // inform the status bar of the change + ST_Responder(&st_notify); + +} + +// +// +// +void AM_loadPics(void) +{ + int i; + char namebuf[9]; + + for (i=0;i<10;i++) + { + DEH_snprintf(namebuf, 9, "AMMNUM%d", i); + marknums[i] = W_CacheLumpName(namebuf, PU_STATIC); + } + +} + +void AM_unloadPics(void) +{ + int i; + char namebuf[9]; + + for (i=0;i<10;i++) + { + DEH_snprintf(namebuf, 9, "AMMNUM%d", i); + W_ReleaseLumpName(namebuf); + } +} + +void AM_clearMarks(void) +{ + int i; + + for (i=0;i max_scale_mtof) + scale_mtof = min_scale_mtof; + scale_ftom = FixedDiv(FRACUNIT, scale_mtof); +} + + + + +// +// +// +void AM_Stop (void) +{ + static event_t st_notify = { 0, ev_keyup, AM_MSGEXITED, 0 }; + + AM_unloadPics(); + automapactive = false; + ST_Responder(&st_notify); + stopped = true; +} + +// +// +// +void AM_Start (void) +{ + static int lastlevel = -1, lastepisode = -1; + + if (!stopped) AM_Stop(); + stopped = false; + if (lastlevel != gamemap || lastepisode != gameepisode) + { + AM_LevelInit(); + lastlevel = gamemap; + lastepisode = gameepisode; + } + AM_initVariables(); + AM_loadPics(); +} + +// +// set the window scale to the maximum size +// +void AM_minOutWindowScale(void) +{ + scale_mtof = min_scale_mtof; + scale_ftom = FixedDiv(FRACUNIT, scale_mtof); + AM_activateNewScale(); +} + +// +// set the window scale to the minimum size +// +void AM_maxOutWindowScale(void) +{ + scale_mtof = max_scale_mtof; + scale_ftom = FixedDiv(FRACUNIT, scale_mtof); + AM_activateNewScale(); +} + + +// +// Handle events (user inputs) in automap mode +// +boolean +AM_Responder +( event_t* ev ) +{ + + int rc; + static int bigstate=0; + static char buffer[20]; + int key; + + rc = false; + + if (!automapactive) + { + if (ev->type == ev_keydown && ev->data1 == key_map_toggle) + { + AM_Start (); + viewactive = false; + rc = true; + } + } + else if (ev->type == ev_keydown) + { + rc = true; + key = ev->data1; + + if (key == key_map_east) // pan right + { + if (!followplayer) m_paninc.x = FTOM(F_PANINC); + else rc = false; + } + else if (key == key_map_west) // pan left + { + if (!followplayer) m_paninc.x = -FTOM(F_PANINC); + else rc = false; + } + else if (key == key_map_north) // pan up + { + if (!followplayer) m_paninc.y = FTOM(F_PANINC); + else rc = false; + } + else if (key == key_map_south) // pan down + { + if (!followplayer) m_paninc.y = -FTOM(F_PANINC); + else rc = false; + } + else if (key == key_map_zoomout) // zoom out + { + mtof_zoommul = M_ZOOMOUT; + ftom_zoommul = M_ZOOMIN; + } + else if (key == key_map_zoomin) // zoom in + { + mtof_zoommul = M_ZOOMIN; + ftom_zoommul = M_ZOOMOUT; + } + else if (key == key_map_toggle) + { + bigstate = 0; + viewactive = true; + AM_Stop (); + } + else if (key == key_map_maxzoom) + { + bigstate = !bigstate; + if (bigstate) + { + AM_saveScaleAndLoc(); + AM_minOutWindowScale(); + } + else AM_restoreScaleAndLoc(); + } + else if (key == key_map_follow) + { + followplayer = !followplayer; + f_oldloc.x = INT_MAX; + if (followplayer) + plr->message = DEH_String(AMSTR_FOLLOWON); + else + plr->message = DEH_String(AMSTR_FOLLOWOFF); + } + else if (key == key_map_grid) + { + grid = !grid; + if (grid) + plr->message = DEH_String(AMSTR_GRIDON); + else + plr->message = DEH_String(AMSTR_GRIDOFF); + } + else if (key == key_map_mark) + { + M_snprintf(buffer, sizeof(buffer), "%s %d", + DEH_String(AMSTR_MARKEDSPOT), markpointnum); + plr->message = buffer; + AM_addMark(); + } + else if (key == key_map_clearmark) + { + AM_clearMarks(); + plr->message = DEH_String(AMSTR_MARKSCLEARED); + } + else + { + rc = false; + } + + if (!deathmatch && cht_CheckCheat(&cheat_amap, ev->data2)) + { + rc = false; + cheating = (cheating+1) % 3; + } + } + else if (ev->type == ev_keyup) + { + rc = false; + key = ev->data1; + + if (key == key_map_east) + { + if (!followplayer) m_paninc.x = 0; + } + else if (key == key_map_west) + { + if (!followplayer) m_paninc.x = 0; + } + else if (key == key_map_north) + { + if (!followplayer) m_paninc.y = 0; + } + else if (key == key_map_south) + { + if (!followplayer) m_paninc.y = 0; + } + else if (key == key_map_zoomout || key == key_map_zoomin) + { + mtof_zoommul = FRACUNIT; + ftom_zoommul = FRACUNIT; + } + } + + return rc; + +} + + +// +// Zooming +// +void AM_changeWindowScale(void) +{ + + // Change the scaling multipliers + scale_mtof = FixedMul(scale_mtof, mtof_zoommul); + scale_ftom = FixedDiv(FRACUNIT, scale_mtof); + + if (scale_mtof < min_scale_mtof) + AM_minOutWindowScale(); + else if (scale_mtof > max_scale_mtof) + AM_maxOutWindowScale(); + else + AM_activateNewScale(); +} + + +// +// +// +void AM_doFollowPlayer(void) +{ + + if (f_oldloc.x != plr->mo->x || f_oldloc.y != plr->mo->y) + { + m_x = FTOM(MTOF(plr->mo->x)) - m_w/2; + m_y = FTOM(MTOF(plr->mo->y)) - m_h/2; + m_x2 = m_x + m_w; + m_y2 = m_y + m_h; + f_oldloc.x = plr->mo->x; + f_oldloc.y = plr->mo->y; + + // m_x = FTOM(MTOF(plr->mo->x - m_w/2)); + // m_y = FTOM(MTOF(plr->mo->y - m_h/2)); + // m_x = plr->mo->x - m_w/2; + // m_y = plr->mo->y - m_h/2; + + } + +} + +// +// +// +void AM_updateLightLev(void) +{ + static int nexttic = 0; + //static int litelevels[] = { 0, 3, 5, 6, 6, 7, 7, 7 }; + static int litelevels[] = { 0, 4, 7, 10, 12, 14, 15, 15 }; + static int litelevelscnt = 0; + + // Change light level + if (amclock>nexttic) + { + lightlev = litelevels[litelevelscnt++]; + if (litelevelscnt == arrlen(litelevels)) litelevelscnt = 0; + nexttic = amclock + 6 - (amclock % 6); + } + +} + + +// +// Updates on Game Tick +// +void AM_Ticker (void) +{ + + if (!automapactive) + return; + + amclock++; + + if (followplayer) + AM_doFollowPlayer(); + + // Change the zoom if necessary + if (ftom_zoommul != FRACUNIT) + AM_changeWindowScale(); + + // Change x,y location + if (m_paninc.x || m_paninc.y) + AM_changeWindowLoc(); + + // Update light level + // AM_updateLightLev(); + +} + + +// +// Clear automap frame buffer. +// +void AM_clearFB(int color) +{ + memset(fb, color, f_w*f_h); +} + + +// +// Automap clipping of lines. +// +// Based on Cohen-Sutherland clipping algorithm but with a slightly +// faster reject and precalculated slopes. If the speed is needed, +// use a hash algorithm to handle the common cases. +// +boolean +AM_clipMline +( mline_t* ml, + fline_t* fl ) +{ + enum + { + LEFT =1, + RIGHT =2, + BOTTOM =4, + TOP =8 + }; + + register int outcode1 = 0; + register int outcode2 = 0; + register int outside; + + fpoint_t tmp; + int dx; + int dy; + + +#define DOOUTCODE(oc, mx, my) \ + (oc) = 0; \ + if ((my) < 0) (oc) |= TOP; \ + else if ((my) >= f_h) (oc) |= BOTTOM; \ + if ((mx) < 0) (oc) |= LEFT; \ + else if ((mx) >= f_w) (oc) |= RIGHT; + + + // do trivial rejects and outcodes + if (ml->a.y > m_y2) + outcode1 = TOP; + else if (ml->a.y < m_y) + outcode1 = BOTTOM; + + if (ml->b.y > m_y2) + outcode2 = TOP; + else if (ml->b.y < m_y) + outcode2 = BOTTOM; + + if (outcode1 & outcode2) + return false; // trivially outside + + if (ml->a.x < m_x) + outcode1 |= LEFT; + else if (ml->a.x > m_x2) + outcode1 |= RIGHT; + + if (ml->b.x < m_x) + outcode2 |= LEFT; + else if (ml->b.x > m_x2) + outcode2 |= RIGHT; + + if (outcode1 & outcode2) + return false; // trivially outside + + // transform to frame-buffer coordinates. + fl->a.x = CXMTOF(ml->a.x); + fl->a.y = CYMTOF(ml->a.y); + fl->b.x = CXMTOF(ml->b.x); + fl->b.y = CYMTOF(ml->b.y); + + DOOUTCODE(outcode1, fl->a.x, fl->a.y); + DOOUTCODE(outcode2, fl->b.x, fl->b.y); + + if (outcode1 & outcode2) + return false; + + while (outcode1 | outcode2) + { + // may be partially inside box + // find an outside point + if (outcode1) + outside = outcode1; + else + outside = outcode2; + + // clip to each side + if (outside & TOP) + { + dy = fl->a.y - fl->b.y; + dx = fl->b.x - fl->a.x; + tmp.x = fl->a.x + (dx*(fl->a.y))/dy; + tmp.y = 0; + } + else if (outside & BOTTOM) + { + dy = fl->a.y - fl->b.y; + dx = fl->b.x - fl->a.x; + tmp.x = fl->a.x + (dx*(fl->a.y-f_h))/dy; + tmp.y = f_h-1; + } + else if (outside & RIGHT) + { + dy = fl->b.y - fl->a.y; + dx = fl->b.x - fl->a.x; + tmp.y = fl->a.y + (dy*(f_w-1 - fl->a.x))/dx; + tmp.x = f_w-1; + } + else if (outside & LEFT) + { + dy = fl->b.y - fl->a.y; + dx = fl->b.x - fl->a.x; + tmp.y = fl->a.y + (dy*(-fl->a.x))/dx; + tmp.x = 0; + } + else + { + tmp.x = 0; + tmp.y = 0; + } + + if (outside == outcode1) + { + fl->a = tmp; + DOOUTCODE(outcode1, fl->a.x, fl->a.y); + } + else + { + fl->b = tmp; + DOOUTCODE(outcode2, fl->b.x, fl->b.y); + } + + if (outcode1 & outcode2) + return false; // trivially outside + } + + return true; +} +#undef DOOUTCODE + + +// +// Classic Bresenham w/ whatever optimizations needed for speed +// +void +AM_drawFline +( fline_t* fl, + int color ) +{ + register int x; + register int y; + register int dx; + register int dy; + register int sx; + register int sy; + register int ax; + register int ay; + register int d; + + static int fuck = 0; + + // For debugging only + if ( fl->a.x < 0 || fl->a.x >= f_w + || fl->a.y < 0 || fl->a.y >= f_h + || fl->b.x < 0 || fl->b.x >= f_w + || fl->b.y < 0 || fl->b.y >= f_h) + { + DEH_fprintf(stderr, "fuck %d \r", fuck++); + return; + } + +#define PUTDOT(xx,yy,cc) fb[(yy)*f_w+(xx)]=(cc) + + dx = fl->b.x - fl->a.x; + ax = 2 * (dx<0 ? -dx : dx); + sx = dx<0 ? -1 : 1; + + dy = fl->b.y - fl->a.y; + ay = 2 * (dy<0 ? -dy : dy); + sy = dy<0 ? -1 : 1; + + x = fl->a.x; + y = fl->a.y; + + if (ax > ay) + { + d = ay - ax/2; + while (1) + { + PUTDOT(x,y,color); + if (x == fl->b.x) return; + if (d>=0) + { + y += sy; + d -= ax; + } + x += sx; + d += ay; + } + } + else + { + d = ax - ay/2; + while (1) + { + PUTDOT(x, y, color); + if (y == fl->b.y) return; + if (d >= 0) + { + x += sx; + d -= ay; + } + y += sy; + d += ax; + } + } +} + + +// +// Clip lines, draw visible part sof lines. +// +void +AM_drawMline +( mline_t* ml, + int color ) +{ + static fline_t fl; + + if (AM_clipMline(ml, &fl)) + AM_drawFline(&fl, color); // draws it on frame buffer using fb coords +} + + + +// +// Draws flat (floor/ceiling tile) aligned grid lines. +// +void AM_drawGrid(int color) +{ + fixed_t x, y; + fixed_t start, end; + mline_t ml; + + // Figure out start of vertical gridlines + start = m_x; + if ((start-bmaporgx)%(MAPBLOCKUNITS<x; + l.a.y = lines[i].v1->y; + l.b.x = lines[i].v2->x; + l.b.y = lines[i].v2->y; + if (cheating || (lines[i].flags & ML_MAPPED)) + { + if ((lines[i].flags & LINE_NEVERSEE) && !cheating) + continue; + if (!lines[i].backsector) + { + AM_drawMline(&l, WALLCOLORS+lightlev); + } + else + { + if (lines[i].special == 39) + { // teleporters + AM_drawMline(&l, WALLCOLORS+WALLRANGE/2); + } + else if (lines[i].flags & ML_SECRET) // secret door + { + if (cheating) AM_drawMline(&l, SECRETWALLCOLORS + lightlev); + else AM_drawMline(&l, WALLCOLORS+lightlev); + } + else if (lines[i].backsector->floorheight + != lines[i].frontsector->floorheight) { + AM_drawMline(&l, FDWALLCOLORS + lightlev); // floor level change + } + else if (lines[i].backsector->ceilingheight + != lines[i].frontsector->ceilingheight) { + AM_drawMline(&l, CDWALLCOLORS+lightlev); // ceiling level change + } + else if (cheating) { + AM_drawMline(&l, TSWALLCOLORS+lightlev); + } + } + } + else if (plr->powers[pw_allmap]) + { + if (!(lines[i].flags & LINE_NEVERSEE)) AM_drawMline(&l, GRAYS+3); + } + } +} + + +// +// Rotation in 2D. +// Used to rotate player arrow line character. +// +void +AM_rotate +( fixed_t* x, + fixed_t* y, + angle_t a ) +{ + fixed_t tmpx; + + tmpx = + FixedMul(*x,finecosine[a>>ANGLETOFINESHIFT]) + - FixedMul(*y,finesine[a>>ANGLETOFINESHIFT]); + + *y = + FixedMul(*x,finesine[a>>ANGLETOFINESHIFT]) + + FixedMul(*y,finecosine[a>>ANGLETOFINESHIFT]); + + *x = tmpx; +} + +void +AM_drawLineCharacter +( mline_t* lineguy, + int lineguylines, + fixed_t scale, + angle_t angle, + int color, + fixed_t x, + fixed_t y ) +{ + int i; + mline_t l; + + for (i=0;imo->angle, WHITE, plr->mo->x, plr->mo->y); + else + AM_drawLineCharacter + (player_arrow, arrlen(player_arrow), 0, plr->mo->angle, + WHITE, plr->mo->x, plr->mo->y); + return; + } + + for (i=0;ipowers[pw_invisibility]) + color = 246; // *close* to black + else + color = their_colors[their_color]; + + AM_drawLineCharacter + (player_arrow, arrlen(player_arrow), 0, p->mo->angle, + color, p->mo->x, p->mo->y); + } + +} + +void +AM_drawThings +( int colors, + int colorrange) +{ + int i; + mobj_t* t; + + for (i=0;iangle, colors+lightlev, t->x, t->y); + t = t->snext; + } + } +} + +void AM_drawMarks(void) +{ + int i, fx, fy, w, h; + + for (i=0;iwidth); + // h = SHORT(marknums[i]->height); + w = 5; // because something's wrong with the wad, i guess + h = 6; // because something's wrong with the wad, i guess + fx = CXMTOF(markpoints[i].x); + fy = CYMTOF(markpoints[i].y); + if (fx >= f_x && fx <= f_w - w && fy >= f_y && fy <= f_h - h) + V_DrawPatch(fx, fy, marknums[i]); + } + } + +} + +void AM_drawCrosshair(int color) +{ + fb[(f_w*(f_h+1))/2] = color; // single point for now + +} + +void AM_Drawer (void) +{ + if (!automapactive) return; + + AM_clearFB(BACKGROUND); + if (grid) + AM_drawGrid(GRIDCOLORS); + AM_drawWalls(); + AM_drawPlayers(); + if (cheating==2) + AM_drawThings(THINGCOLORS, THINGRANGE); + AM_drawCrosshair(XHAIRCOLORS); + + AM_drawMarks(); + + V_MarkRect(f_x, f_y, f_w, f_h); + +} diff --git a/firmware_p4/components/Applications/doom/am_map.h b/firmware_p4/components/Applications/doom/am_map.h new file mode 100644 index 000000000..572d2389b --- /dev/null +++ b/firmware_p4/components/Applications/doom/am_map.h @@ -0,0 +1,49 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// AutoMap module. +// + +#ifndef __AMMAP_H__ +#define __AMMAP_H__ + +#include "d_event.h" +#include "m_cheat.h" + +// Used by ST StatusBar stuff. +#define AM_MSGHEADER (('a'<<24)+('m'<<16)) +#define AM_MSGENTERED (AM_MSGHEADER | ('e'<<8)) +#define AM_MSGEXITED (AM_MSGHEADER | ('x'<<8)) + + +// Called by main loop. +boolean AM_Responder (event_t* ev); + +// Called by main loop. +void AM_Ticker (void); + +// Called by main loop, +// called instead of view drawer if automap active. +void AM_Drawer (void); + +// Called to force the automap to quit +// if the level is completed while it is up. +void AM_Stop (void); + + +extern cheatseq_t cheat_amap; + + +#endif diff --git a/firmware_p4/components/Applications/doom/config.h b/firmware_p4/components/Applications/doom/config.h new file mode 100644 index 000000000..7e05102e0 --- /dev/null +++ b/firmware_p4/components/Applications/doom/config.h @@ -0,0 +1,100 @@ +/* config.hin. Generated from configure.ac by autoheader. */ + +/* Define to 1 if you have the header file. */ +#undef HAVE_DEV_ISA_SPKRIO_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_DEV_SPEAKER_SPEAKER_H + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `ioperm' function. */ +#undef HAVE_IOPERM + +/* Define to 1 if you have the `amd64' library (-lamd64). */ +#undef HAVE_LIBAMD64 + +/* Define to 1 if you have the `i386' library (-li386). */ +#undef HAVE_LIBI386 + +/* Define to 1 if you have the `m' library (-lm). */ +#undef HAVE_LIBM + +/* Define to 1 if you have the `png' library (-lpng). */ +#undef HAVE_LIBPNG + +/* Define to 1 if you have the `samplerate' library (-lsamplerate). */ +#undef HAVE_LIBSAMPLERATE + +/* Define to 1 if you have the `z' library (-lz). */ +#undef HAVE_LIBZ + +/* Define to 1 if you have the header file. */ +#undef HAVE_LINUX_KD_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the `mmap' function. */ +#undef HAVE_MMAP + +/* Define to 1 if you have the `sched_setaffinity' function. */ +#undef HAVE_SCHED_SETAFFINITY + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Name of package */ +#define PACKAGE "Doom" + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "Doom Generic" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "Doom Generic 0.1" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "doomgeneric.tar" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION 0.1 + +/* Change this when you create your awesome forked version */ +#define PROGRAM_PREFIX "doomgeneric" + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Version number of package */ +#define VERSION 0.1 + +/* Define to 1 if you want to compile the unmodified code */ +#undef ORIGCODE + +/* Define to the directory where all game files are located */ +#define FILES_DIR "." diff --git a/firmware_p4/components/Applications/doom/d_englsh.h b/firmware_p4/components/Applications/doom/d_englsh.h new file mode 100644 index 000000000..cfb05722c --- /dev/null +++ b/firmware_p4/components/Applications/doom/d_englsh.h @@ -0,0 +1,693 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Printed strings for translation. +// English language support (default). +// + +#ifndef __D_ENGLSH__ +#define __D_ENGLSH__ + +// +// Printed strings for translation +// + +// +// D_Main.C +// +#define D_DEVSTR "Development mode ON.\n" +#define D_CDROM "CD-ROM Version: default.cfg from c:\\doomdata\n" + +// +// M_Menu.C +// +#define PRESSKEY "press a key." +#define PRESSYN "press y or n." +#define QUITMSG "are you sure you want to\nquit this great game?" +#define LOADNET "you can't do load while in a net game!\n\n"PRESSKEY +#define QLOADNET "you can't quickload during a netgame!\n\n"PRESSKEY +#define QSAVESPOT "you haven't picked a quicksave slot yet!\n\n"PRESSKEY +#define SAVEDEAD "you can't save if you aren't playing!\n\n"PRESSKEY +#define QSPROMPT "quicksave over your game named\n\n'%s'?\n\n"PRESSYN +#define QLPROMPT "do you want to quickload the game named\n\n'%s'?\n\n"PRESSYN + +#define NEWGAME \ +"you can't start a new game\n"\ +"while in a network game.\n\n"PRESSKEY + +#define NIGHTMARE \ +"are you sure? this skill level\n"\ +"isn't even remotely fair.\n\n"PRESSYN + +#define SWSTRING \ +"this is the shareware version of doom.\n\n"\ +"you need to order the entire trilogy.\n\n"PRESSKEY + +#define MSGOFF "Messages OFF" +#define MSGON "Messages ON" +#define NETEND "you can't end a netgame!\n\n"PRESSKEY +#define ENDGAME "are you sure you want to end the game?\n\n"PRESSYN + +#define DOSY "(press y to quit to dos.)" + +#define DETAILHI "High detail" +#define DETAILLO "Low detail" +#define GAMMALVL0 "Gamma correction OFF" +#define GAMMALVL1 "Gamma correction level 1" +#define GAMMALVL2 "Gamma correction level 2" +#define GAMMALVL3 "Gamma correction level 3" +#define GAMMALVL4 "Gamma correction level 4" +#define EMPTYSTRING "empty slot" + +// +// P_inter.C +// +#define GOTARMOR "Picked up the armor." +#define GOTMEGA "Picked up the MegaArmor!" +#define GOTHTHBONUS "Picked up a health bonus." +#define GOTARMBONUS "Picked up an armor bonus." +#define GOTSTIM "Picked up a stimpack." +#define GOTMEDINEED "Picked up a medikit that you REALLY need!" +#define GOTMEDIKIT "Picked up a medikit." +#define GOTSUPER "Supercharge!" + +#define GOTBLUECARD "Picked up a blue keycard." +#define GOTYELWCARD "Picked up a yellow keycard." +#define GOTREDCARD "Picked up a red keycard." +#define GOTBLUESKUL "Picked up a blue skull key." +#define GOTYELWSKUL "Picked up a yellow skull key." +#define GOTREDSKULL "Picked up a red skull key." + +#define GOTINVUL "Invulnerability!" +#define GOTBERSERK "Berserk!" +#define GOTINVIS "Partial Invisibility" +#define GOTSUIT "Radiation Shielding Suit" +#define GOTMAP "Computer Area Map" +#define GOTVISOR "Light Amplification Visor" +#define GOTMSPHERE "MegaSphere!" + +#define GOTCLIP "Picked up a clip." +#define GOTCLIPBOX "Picked up a box of bullets." +#define GOTROCKET "Picked up a rocket." +#define GOTROCKBOX "Picked up a box of rockets." +#define GOTCELL "Picked up an energy cell." +#define GOTCELLBOX "Picked up an energy cell pack." +#define GOTSHELLS "Picked up 4 shotgun shells." +#define GOTSHELLBOX "Picked up a box of shotgun shells." +#define GOTBACKPACK "Picked up a backpack full of ammo!" + +#define GOTBFG9000 "You got the BFG9000! Oh, yes." +#define GOTCHAINGUN "You got the chaingun!" +#define GOTCHAINSAW "A chainsaw! Find some meat!" +#define GOTLAUNCHER "You got the rocket launcher!" +#define GOTPLASMA "You got the plasma gun!" +#define GOTSHOTGUN "You got the shotgun!" +#define GOTSHOTGUN2 "You got the super shotgun!" + +// +// P_Doors.C +// +#define PD_BLUEO "You need a blue key to activate this object" +#define PD_REDO "You need a red key to activate this object" +#define PD_YELLOWO "You need a yellow key to activate this object" +#define PD_BLUEK "You need a blue key to open this door" +#define PD_REDK "You need a red key to open this door" +#define PD_YELLOWK "You need a yellow key to open this door" + +// +// G_game.C +// +#define GGSAVED "game saved." + +// +// HU_stuff.C +// +#define HUSTR_MSGU "[Message unsent]" + +#define HUSTR_E1M1 "E1M1: Hangar" +#define HUSTR_E1M2 "E1M2: Nuclear Plant" +#define HUSTR_E1M3 "E1M3: Toxin Refinery" +#define HUSTR_E1M4 "E1M4: Command Control" +#define HUSTR_E1M5 "E1M5: Phobos Lab" +#define HUSTR_E1M6 "E1M6: Central Processing" +#define HUSTR_E1M7 "E1M7: Computer Station" +#define HUSTR_E1M8 "E1M8: Phobos Anomaly" +#define HUSTR_E1M9 "E1M9: Military Base" + +#define HUSTR_E2M1 "E2M1: Deimos Anomaly" +#define HUSTR_E2M2 "E2M2: Containment Area" +#define HUSTR_E2M3 "E2M3: Refinery" +#define HUSTR_E2M4 "E2M4: Deimos Lab" +#define HUSTR_E2M5 "E2M5: Command Center" +#define HUSTR_E2M6 "E2M6: Halls of the Damned" +#define HUSTR_E2M7 "E2M7: Spawning Vats" +#define HUSTR_E2M8 "E2M8: Tower of Babel" +#define HUSTR_E2M9 "E2M9: Fortress of Mystery" + +#define HUSTR_E3M1 "E3M1: Hell Keep" +#define HUSTR_E3M2 "E3M2: Slough of Despair" +#define HUSTR_E3M3 "E3M3: Pandemonium" +#define HUSTR_E3M4 "E3M4: House of Pain" +#define HUSTR_E3M5 "E3M5: Unholy Cathedral" +#define HUSTR_E3M6 "E3M6: Mt. Erebus" +#define HUSTR_E3M7 "E3M7: Limbo" +#define HUSTR_E3M8 "E3M8: Dis" +#define HUSTR_E3M9 "E3M9: Warrens" + +#define HUSTR_E4M1 "E4M1: Hell Beneath" +#define HUSTR_E4M2 "E4M2: Perfect Hatred" +#define HUSTR_E4M3 "E4M3: Sever The Wicked" +#define HUSTR_E4M4 "E4M4: Unruly Evil" +#define HUSTR_E4M5 "E4M5: They Will Repent" +#define HUSTR_E4M6 "E4M6: Against Thee Wickedly" +#define HUSTR_E4M7 "E4M7: And Hell Followed" +#define HUSTR_E4M8 "E4M8: Unto The Cruel" +#define HUSTR_E4M9 "E4M9: Fear" + +#define HUSTR_1 "level 1: entryway" +#define HUSTR_2 "level 2: underhalls" +#define HUSTR_3 "level 3: the gantlet" +#define HUSTR_4 "level 4: the focus" +#define HUSTR_5 "level 5: the waste tunnels" +#define HUSTR_6 "level 6: the crusher" +#define HUSTR_7 "level 7: dead simple" +#define HUSTR_8 "level 8: tricks and traps" +#define HUSTR_9 "level 9: the pit" +#define HUSTR_10 "level 10: refueling base" +#define HUSTR_11 "level 11: 'o' of destruction!" + +#define HUSTR_12 "level 12: the factory" +#define HUSTR_13 "level 13: downtown" +#define HUSTR_14 "level 14: the inmost dens" +#define HUSTR_15 "level 15: industrial zone" +#define HUSTR_16 "level 16: suburbs" +#define HUSTR_17 "level 17: tenements" +#define HUSTR_18 "level 18: the courtyard" +#define HUSTR_19 "level 19: the citadel" +#define HUSTR_20 "level 20: gotcha!" + +#define HUSTR_21 "level 21: nirvana" +#define HUSTR_22 "level 22: the catacombs" +#define HUSTR_23 "level 23: barrels o' fun" +#define HUSTR_24 "level 24: the chasm" +#define HUSTR_25 "level 25: bloodfalls" +#define HUSTR_26 "level 26: the abandoned mines" +#define HUSTR_27 "level 27: monster condo" +#define HUSTR_28 "level 28: the spirit world" +#define HUSTR_29 "level 29: the living end" +#define HUSTR_30 "level 30: icon of sin" + +#define HUSTR_31 "level 31: wolfenstein" +#define HUSTR_32 "level 32: grosse" + +#define PHUSTR_1 "level 1: congo" +#define PHUSTR_2 "level 2: well of souls" +#define PHUSTR_3 "level 3: aztec" +#define PHUSTR_4 "level 4: caged" +#define PHUSTR_5 "level 5: ghost town" +#define PHUSTR_6 "level 6: baron's lair" +#define PHUSTR_7 "level 7: caughtyard" +#define PHUSTR_8 "level 8: realm" +#define PHUSTR_9 "level 9: abattoire" +#define PHUSTR_10 "level 10: onslaught" +#define PHUSTR_11 "level 11: hunted" + +#define PHUSTR_12 "level 12: speed" +#define PHUSTR_13 "level 13: the crypt" +#define PHUSTR_14 "level 14: genesis" +#define PHUSTR_15 "level 15: the twilight" +#define PHUSTR_16 "level 16: the omen" +#define PHUSTR_17 "level 17: compound" +#define PHUSTR_18 "level 18: neurosphere" +#define PHUSTR_19 "level 19: nme" +#define PHUSTR_20 "level 20: the death domain" + +#define PHUSTR_21 "level 21: slayer" +#define PHUSTR_22 "level 22: impossible mission" +#define PHUSTR_23 "level 23: tombstone" +#define PHUSTR_24 "level 24: the final frontier" +#define PHUSTR_25 "level 25: the temple of darkness" +#define PHUSTR_26 "level 26: bunker" +#define PHUSTR_27 "level 27: anti-christ" +#define PHUSTR_28 "level 28: the sewers" +#define PHUSTR_29 "level 29: odyssey of noises" +#define PHUSTR_30 "level 30: the gateway of hell" + +#define PHUSTR_31 "level 31: cyberden" +#define PHUSTR_32 "level 32: go 2 it" + +#define THUSTR_1 "level 1: system control" +#define THUSTR_2 "level 2: human bbq" +#define THUSTR_3 "level 3: power control" +#define THUSTR_4 "level 4: wormhole" +#define THUSTR_5 "level 5: hanger" +#define THUSTR_6 "level 6: open season" +#define THUSTR_7 "level 7: prison" +#define THUSTR_8 "level 8: metal" +#define THUSTR_9 "level 9: stronghold" +#define THUSTR_10 "level 10: redemption" +#define THUSTR_11 "level 11: storage facility" + +#define THUSTR_12 "level 12: crater" +#define THUSTR_13 "level 13: nukage processing" +#define THUSTR_14 "level 14: steel works" +#define THUSTR_15 "level 15: dead zone" +#define THUSTR_16 "level 16: deepest reaches" +#define THUSTR_17 "level 17: processing area" +#define THUSTR_18 "level 18: mill" +#define THUSTR_19 "level 19: shipping/respawning" +#define THUSTR_20 "level 20: central processing" + +#define THUSTR_21 "level 21: administration center" +#define THUSTR_22 "level 22: habitat" +#define THUSTR_23 "level 23: lunar mining project" +#define THUSTR_24 "level 24: quarry" +#define THUSTR_25 "level 25: baron's den" +#define THUSTR_26 "level 26: ballistyx" +#define THUSTR_27 "level 27: mount pain" +#define THUSTR_28 "level 28: heck" +#define THUSTR_29 "level 29: river styx" +#define THUSTR_30 "level 30: last call" + +#define THUSTR_31 "level 31: pharaoh" +#define THUSTR_32 "level 32: caribbean" + +#define HUSTR_CHATMACRO1 "I'm ready to kick butt!" +#define HUSTR_CHATMACRO2 "I'm OK." +#define HUSTR_CHATMACRO3 "I'm not looking too good!" +#define HUSTR_CHATMACRO4 "Help!" +#define HUSTR_CHATMACRO5 "You suck!" +#define HUSTR_CHATMACRO6 "Next time, scumbag..." +#define HUSTR_CHATMACRO7 "Come here!" +#define HUSTR_CHATMACRO8 "I'll take care of it." +#define HUSTR_CHATMACRO9 "Yes" +#define HUSTR_CHATMACRO0 "No" + +#define HUSTR_TALKTOSELF1 "You mumble to yourself" +#define HUSTR_TALKTOSELF2 "Who's there?" +#define HUSTR_TALKTOSELF3 "You scare yourself" +#define HUSTR_TALKTOSELF4 "You start to rave" +#define HUSTR_TALKTOSELF5 "You've lost it..." + +#define HUSTR_MESSAGESENT "[Message Sent]" + +// The following should NOT be changed unless it seems +// just AWFULLY necessary + +#define HUSTR_PLRGREEN "Green: " +#define HUSTR_PLRINDIGO "Indigo: " +#define HUSTR_PLRBROWN "Brown: " +#define HUSTR_PLRRED "Red: " + +#define HUSTR_KEYGREEN 'g' +#define HUSTR_KEYINDIGO 'i' +#define HUSTR_KEYBROWN 'b' +#define HUSTR_KEYRED 'r' + +// +// AM_map.C +// + +#define AMSTR_FOLLOWON "Follow Mode ON" +#define AMSTR_FOLLOWOFF "Follow Mode OFF" + +#define AMSTR_GRIDON "Grid ON" +#define AMSTR_GRIDOFF "Grid OFF" + +#define AMSTR_MARKEDSPOT "Marked Spot" +#define AMSTR_MARKSCLEARED "All Marks Cleared" + +// +// ST_stuff.C +// + +#define STSTR_MUS "Music Change" +#define STSTR_NOMUS "IMPOSSIBLE SELECTION" +#define STSTR_DQDON "Degreelessness Mode On" +#define STSTR_DQDOFF "Degreelessness Mode Off" + +#define STSTR_KFAADDED "Very Happy Ammo Added" +#define STSTR_FAADDED "Ammo (no keys) Added" + +#define STSTR_NCON "No Clipping Mode ON" +#define STSTR_NCOFF "No Clipping Mode OFF" + +#define STSTR_BEHOLD "inVuln, Str, Inviso, Rad, Allmap, or Lite-amp" +#define STSTR_BEHOLDX "Power-up Toggled" + +#define STSTR_CHOPPERS "... doesn't suck - GM" +#define STSTR_CLEV "Changing Level..." + +// +// F_Finale.C +// +#define E1TEXT \ +"Once you beat the big badasses and\n"\ +"clean out the moon base you're supposed\n"\ +"to win, aren't you? Aren't you? Where's\n"\ +"your fat reward and ticket home? What\n"\ +"the hell is this? It's not supposed to\n"\ +"end this way!\n"\ +"\n" \ +"It stinks like rotten meat, but looks\n"\ +"like the lost Deimos base. Looks like\n"\ +"you're stuck on The Shores of Hell.\n"\ +"The only way out is through.\n"\ +"\n"\ +"To continue the DOOM experience, play\n"\ +"The Shores of Hell and its amazing\n"\ +"sequel, Inferno!\n" + + +#define E2TEXT \ +"You've done it! The hideous cyber-\n"\ +"demon lord that ruled the lost Deimos\n"\ +"moon base has been slain and you\n"\ +"are triumphant! But ... where are\n"\ +"you? You clamber to the edge of the\n"\ +"moon and look down to see the awful\n"\ +"truth.\n" \ +"\n"\ +"Deimos floats above Hell itself!\n"\ +"You've never heard of anyone escaping\n"\ +"from Hell, but you'll make the bastards\n"\ +"sorry they ever heard of you! Quickly,\n"\ +"you rappel down to the surface of\n"\ +"Hell.\n"\ +"\n" \ +"Now, it's on to the final chapter of\n"\ +"DOOM! -- Inferno." + + +#define E3TEXT \ +"The loathsome spiderdemon that\n"\ +"masterminded the invasion of the moon\n"\ +"bases and caused so much death has had\n"\ +"its ass kicked for all time.\n"\ +"\n"\ +"A hidden doorway opens and you enter.\n"\ +"You've proven too tough for Hell to\n"\ +"contain, and now Hell at last plays\n"\ +"fair -- for you emerge from the door\n"\ +"to see the green fields of Earth!\n"\ +"Home at last.\n" \ +"\n"\ +"You wonder what's been happening on\n"\ +"Earth while you were battling evil\n"\ +"unleashed. It's good that no Hell-\n"\ +"spawn could have come through that\n"\ +"door with you ..." + + +#define E4TEXT \ +"the spider mastermind must have sent forth\n"\ +"its legions of hellspawn before your\n"\ +"final confrontation with that terrible\n"\ +"beast from hell. but you stepped forward\n"\ +"and brought forth eternal damnation and\n"\ +"suffering upon the horde as a true hero\n"\ +"would in the face of something so evil.\n"\ +"\n"\ +"besides, someone was gonna pay for what\n"\ +"happened to daisy, your pet rabbit.\n"\ +"\n"\ +"but now, you see spread before you more\n"\ +"potential pain and gibbitude as a nation\n"\ +"of demons run amok among our cities.\n"\ +"\n"\ +"next stop, hell on earth!" + + +// after level 6, put this: + +#define C1TEXT \ +"YOU HAVE ENTERED DEEPLY INTO THE INFESTED\n" \ +"STARPORT. BUT SOMETHING IS WRONG. THE\n" \ +"MONSTERS HAVE BROUGHT THEIR OWN REALITY\n" \ +"WITH THEM, AND THE STARPORT'S TECHNOLOGY\n" \ +"IS BEING SUBVERTED BY THEIR PRESENCE.\n" \ +"\n"\ +"AHEAD, YOU SEE AN OUTPOST OF HELL, A\n" \ +"FORTIFIED ZONE. IF YOU CAN GET PAST IT,\n" \ +"YOU CAN PENETRATE INTO THE HAUNTED HEART\n" \ +"OF THE STARBASE AND FIND THE CONTROLLING\n" \ +"SWITCH WHICH HOLDS EARTH'S POPULATION\n" \ +"HOSTAGE." + +// After level 11, put this: + +#define C2TEXT \ +"YOU HAVE WON! YOUR VICTORY HAS ENABLED\n" \ +"HUMANKIND TO EVACUATE EARTH AND ESCAPE\n"\ +"THE NIGHTMARE. NOW YOU ARE THE ONLY\n"\ +"HUMAN LEFT ON THE FACE OF THE PLANET.\n"\ +"CANNIBAL MUTATIONS, CARNIVOROUS ALIENS,\n"\ +"AND EVIL SPIRITS ARE YOUR ONLY NEIGHBORS.\n"\ +"YOU SIT BACK AND WAIT FOR DEATH, CONTENT\n"\ +"THAT YOU HAVE SAVED YOUR SPECIES.\n"\ +"\n"\ +"BUT THEN, EARTH CONTROL BEAMS DOWN A\n"\ +"MESSAGE FROM SPACE: \"SENSORS HAVE LOCATED\n"\ +"THE SOURCE OF THE ALIEN INVASION. IF YOU\n"\ +"GO THERE, YOU MAY BE ABLE TO BLOCK THEIR\n"\ +"ENTRY. THE ALIEN BASE IS IN THE HEART OF\n"\ +"YOUR OWN HOME CITY, NOT FAR FROM THE\n"\ +"STARPORT.\" SLOWLY AND PAINFULLY YOU GET\n"\ +"UP AND RETURN TO THE FRAY." + + +// After level 20, put this: + +#define C3TEXT \ +"YOU ARE AT THE CORRUPT HEART OF THE CITY,\n"\ +"SURROUNDED BY THE CORPSES OF YOUR ENEMIES.\n"\ +"YOU SEE NO WAY TO DESTROY THE CREATURES'\n"\ +"ENTRYWAY ON THIS SIDE, SO YOU CLENCH YOUR\n"\ +"TEETH AND PLUNGE THROUGH IT.\n"\ +"\n"\ +"THERE MUST BE A WAY TO CLOSE IT ON THE\n"\ +"OTHER SIDE. WHAT DO YOU CARE IF YOU'VE\n"\ +"GOT TO GO THROUGH HELL TO GET TO IT?" + + +// After level 29, put this: + +#define C4TEXT \ +"THE HORRENDOUS VISAGE OF THE BIGGEST\n"\ +"DEMON YOU'VE EVER SEEN CRUMBLES BEFORE\n"\ +"YOU, AFTER YOU PUMP YOUR ROCKETS INTO\n"\ +"HIS EXPOSED BRAIN. THE MONSTER SHRIVELS\n"\ +"UP AND DIES, ITS THRASHING LIMBS\n"\ +"DEVASTATING UNTOLD MILES OF HELL'S\n"\ +"SURFACE.\n"\ +"\n"\ +"YOU'VE DONE IT. THE INVASION IS OVER.\n"\ +"EARTH IS SAVED. HELL IS A WRECK. YOU\n"\ +"WONDER WHERE BAD FOLKS WILL GO WHEN THEY\n"\ +"DIE, NOW. WIPING THE SWEAT FROM YOUR\n"\ +"FOREHEAD YOU BEGIN THE LONG TREK BACK\n"\ +"HOME. REBUILDING EARTH OUGHT TO BE A\n"\ +"LOT MORE FUN THAN RUINING IT WAS.\n" + + + +// Before level 31, put this: + +#define C5TEXT \ +"CONGRATULATIONS, YOU'VE FOUND THE SECRET\n"\ +"LEVEL! LOOKS LIKE IT'S BEEN BUILT BY\n"\ +"HUMANS, RATHER THAN DEMONS. YOU WONDER\n"\ +"WHO THE INMATES OF THIS CORNER OF HELL\n"\ +"WILL BE." + + +// Before level 32, put this: + +#define C6TEXT \ +"CONGRATULATIONS, YOU'VE FOUND THE\n"\ +"SUPER SECRET LEVEL! YOU'D BETTER\n"\ +"BLAZE THROUGH THIS ONE!\n" + + +// after map 06 + +#define P1TEXT \ +"You gloat over the steaming carcass of the\n"\ +"Guardian. With its death, you've wrested\n"\ +"the Accelerator from the stinking claws\n"\ +"of Hell. You relax and glance around the\n"\ +"room. Damn! There was supposed to be at\n"\ +"least one working prototype, but you can't\n"\ +"see it. The demons must have taken it.\n"\ +"\n"\ +"You must find the prototype, or all your\n"\ +"struggles will have been wasted. Keep\n"\ +"moving, keep fighting, keep killing.\n"\ +"Oh yes, keep living, too." + + +// after map 11 + +#define P2TEXT \ +"Even the deadly Arch-Vile labyrinth could\n"\ +"not stop you, and you've gotten to the\n"\ +"prototype Accelerator which is soon\n"\ +"efficiently and permanently deactivated.\n"\ +"\n"\ +"You're good at that kind of thing." + + +// after map 20 + +#define P3TEXT \ +"You've bashed and battered your way into\n"\ +"the heart of the devil-hive. Time for a\n"\ +"Search-and-Destroy mission, aimed at the\n"\ +"Gatekeeper, whose foul offspring is\n"\ +"cascading to Earth. Yeah, he's bad. But\n"\ +"you know who's worse!\n"\ +"\n"\ +"Grinning evilly, you check your gear, and\n"\ +"get ready to give the bastard a little Hell\n"\ +"of your own making!" + +// after map 30 + +#define P4TEXT \ +"The Gatekeeper's evil face is splattered\n"\ +"all over the place. As its tattered corpse\n"\ +"collapses, an inverted Gate forms and\n"\ +"sucks down the shards of the last\n"\ +"prototype Accelerator, not to mention the\n"\ +"few remaining demons. You're done. Hell\n"\ +"has gone back to pounding bad dead folks \n"\ +"instead of good live ones. Remember to\n"\ +"tell your grandkids to put a rocket\n"\ +"launcher in your coffin. If you go to Hell\n"\ +"when you die, you'll need it for some\n"\ +"final cleaning-up ..." + +// before map 31 + +#define P5TEXT \ +"You've found the second-hardest level we\n"\ +"got. Hope you have a saved game a level or\n"\ +"two previous. If not, be prepared to die\n"\ +"aplenty. For master marines only." + +// before map 32 + +#define P6TEXT \ +"Betcha wondered just what WAS the hardest\n"\ +"level we had ready for ya? Now you know.\n"\ +"No one gets out alive." + + +#define T1TEXT \ +"You've fought your way out of the infested\n"\ +"experimental labs. It seems that UAC has\n"\ +"once again gulped it down. With their\n"\ +"high turnover, it must be hard for poor\n"\ +"old UAC to buy corporate health insurance\n"\ +"nowadays..\n"\ +"\n"\ +"Ahead lies the military complex, now\n"\ +"swarming with diseased horrors hot to get\n"\ +"their teeth into you. With luck, the\n"\ +"complex still has some warlike ordnance\n"\ +"laying around." + + +#define T2TEXT \ +"You hear the grinding of heavy machinery\n"\ +"ahead. You sure hope they're not stamping\n"\ +"out new hellspawn, but you're ready to\n"\ +"ream out a whole herd if you have to.\n"\ +"They might be planning a blood feast, but\n"\ +"you feel about as mean as two thousand\n"\ +"maniacs packed into one mad killer.\n"\ +"\n"\ +"You don't plan to go down easy." + + +#define T3TEXT \ +"The vista opening ahead looks real damn\n"\ +"familiar. Smells familiar, too -- like\n"\ +"fried excrement. You didn't like this\n"\ +"place before, and you sure as hell ain't\n"\ +"planning to like it now. The more you\n"\ +"brood on it, the madder you get.\n"\ +"Hefting your gun, an evil grin trickles\n"\ +"onto your face. Time to take some names." + +#define T4TEXT \ +"Suddenly, all is silent, from one horizon\n"\ +"to the other. The agonizing echo of Hell\n"\ +"fades away, the nightmare sky turns to\n"\ +"blue, the heaps of monster corpses start \n"\ +"to evaporate along with the evil stench \n"\ +"that filled the air. Jeeze, maybe you've\n"\ +"done it. Have you really won?\n"\ +"\n"\ +"Something rumbles in the distance.\n"\ +"A blue light begins to glow inside the\n"\ +"ruined skull of the demon-spitter." + + +#define T5TEXT \ +"What now? Looks totally different. Kind\n"\ +"of like King Tut's condo. Well,\n"\ +"whatever's here can't be any worse\n"\ +"than usual. Can it? Or maybe it's best\n"\ +"to let sleeping gods lie.." + + +#define T6TEXT \ +"Time for a vacation. You've burst the\n"\ +"bowels of hell and by golly you're ready\n"\ +"for a break. You mutter to yourself,\n"\ +"Maybe someone else can kick Hell's ass\n"\ +"next time around. Ahead lies a quiet town,\n"\ +"with peaceful flowing water, quaint\n"\ +"buildings, and presumably no Hellspawn.\n"\ +"\n"\ +"As you step off the transport, you hear\n"\ +"the stomp of a cyberdemon's iron shoe." + + + +// +// Character cast strings F_FINALE.C +// +#define CC_ZOMBIE "ZOMBIEMAN" +#define CC_SHOTGUN "SHOTGUN GUY" +#define CC_HEAVY "HEAVY WEAPON DUDE" +#define CC_IMP "IMP" +#define CC_DEMON "DEMON" +#define CC_LOST "LOST SOUL" +#define CC_CACO "CACODEMON" +#define CC_HELL "HELL KNIGHT" +#define CC_BARON "BARON OF HELL" +#define CC_ARACH "ARACHNOTRON" +#define CC_PAIN "PAIN ELEMENTAL" +#define CC_REVEN "REVENANT" +#define CC_MANCU "MANCUBUS" +#define CC_ARCH "ARCH-VILE" +#define CC_SPIDER "THE SPIDER MASTERMIND" +#define CC_CYBER "THE CYBERDEMON" +#define CC_HERO "OUR HERO" + + +#endif diff --git a/firmware_p4/components/Applications/doom/d_event.c b/firmware_p4/components/Applications/doom/d_event.c new file mode 100644 index 000000000..3eef57282 --- /dev/null +++ b/firmware_p4/components/Applications/doom/d_event.c @@ -0,0 +1,63 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// +// DESCRIPTION: Event handling. +// +// Events are asynchronous inputs generally generated by the game user. +// Events can be discarded if no responder claims them +// + +#include +#include "d_event.h" + +#define MAXEVENTS 64 + +static event_t events[MAXEVENTS]; +static int eventhead; +static int eventtail; + +// +// D_PostEvent +// Called by the I/O functions when input is detected +// +void D_PostEvent (event_t* ev) +{ + events[eventhead] = *ev; + eventhead = (eventhead + 1) % MAXEVENTS; +} + +// Read an event from the queue. + +event_t *D_PopEvent(void) +{ + event_t *result; + + // No more events waiting. + + if (eventtail == eventhead) + { + return NULL; + } + + result = &events[eventtail]; + + // Advance to the next event in the queue. + + eventtail = (eventtail + 1) % MAXEVENTS; + + return result; +} + + diff --git a/firmware_p4/components/Applications/doom/d_event.h b/firmware_p4/components/Applications/doom/d_event.h new file mode 100644 index 000000000..c9374b296 --- /dev/null +++ b/firmware_p4/components/Applications/doom/d_event.h @@ -0,0 +1,137 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// +// + + +#ifndef __D_EVENT__ +#define __D_EVENT__ + + +#include "doomtype.h" + + +// +// Event handling. +// + +// Input event types. +typedef enum +{ + ev_keydown, + ev_keyup, + ev_mouse, + ev_joystick, + ev_quit +} evtype_t; + +// Event structure. +typedef struct +{ + evtype_t type; + + // Event-related data that depends on the type of event: + // + // ev_keydown/ev_keyup: + // data1: Key code (from doomkeys.h) of the key that was + // pressed or released. + // data2: Ascii text of the character that was pressed, + // shifted appropriately (eg. '$' if 4 was pressed + // while shift was held). + // + // ev_mouse: + // data1: Bitfield of buttons currently held down. + // (bit 0 = left; bit 1 = right; bit 2 = middle). + // data2: X axis mouse movement (turn). + // data3: Y axis mouse movement (forward/backward). + // + // ev_joystick: + // data1: Bitfield of buttons currently pressed. + // data2: X axis mouse movement (turn). + // data3: Y axis mouse movement (forward/backward). + // data4: Third axis mouse movement (strafe). + + int data1, data2, data3, data4; +} event_t; + + +// +// Button/action code definitions. +// +typedef enum +{ + // Press "Fire". + BT_ATTACK = 1, + // Use button, to open doors, activate switches. + BT_USE = 2, + + // Flag: game events, not really buttons. + BT_SPECIAL = 128, + BT_SPECIALMASK = 3, + + // Flag, weapon change pending. + // If true, the next 3 bits hold weapon num. + BT_CHANGE = 4, + // The 3bit weapon mask and shift, convenience. + BT_WEAPONMASK = (8+16+32), + BT_WEAPONSHIFT = 3, + + // Pause the game. + BTS_PAUSE = 1, + // Save the game at each console. + BTS_SAVEGAME = 2, + + // Savegame slot numbers + // occupy the second byte of buttons. + BTS_SAVEMASK = (4+8+16), + BTS_SAVESHIFT = 2, + +} buttoncode_t; + +// villsa [STRIFE] Strife specific buttons +// TODO - not finished +typedef enum +{ + // Player view look up + BT2_LOOKUP = 1, + // Player view look down + BT2_LOOKDOWN = 2, + // Center player's view + BT2_CENTERVIEW = 4, + // Use inventory item + BT2_INVUSE = 8, + // Drop inventory item + BT2_INVDROP = 16, + // Jump up and down + BT2_JUMP = 32, + // Use medkit + BT2_HEALTH = 128, + +} buttoncode2_t; + + + + +// Called by IO functions when input is detected. +void D_PostEvent (event_t *ev); + +// Read an event from the event queue + +event_t *D_PopEvent(void); + + +#endif + diff --git a/firmware_p4/components/Applications/doom/d_items.c b/firmware_p4/components/Applications/doom/d_items.c new file mode 100644 index 000000000..33f310c63 --- /dev/null +++ b/firmware_p4/components/Applications/doom/d_items.c @@ -0,0 +1,128 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// + + +// We are referring to sprite numbers. +#include "info.h" + +#include "d_items.h" + + +// +// PSPRITE ACTIONS for waepons. +// This struct controls the weapon animations. +// +// Each entry is: +// ammo/amunition type +// upstate +// downstate +// readystate +// atkstate, i.e. attack/fire/hit frame +// flashstate, muzzle flash +// +weaponinfo_t weaponinfo[NUMWEAPONS] = +{ + { + // fist + am_noammo, + S_PUNCHUP, + S_PUNCHDOWN, + S_PUNCH, + S_PUNCH1, + S_NULL + }, + { + // pistol + am_clip, + S_PISTOLUP, + S_PISTOLDOWN, + S_PISTOL, + S_PISTOL1, + S_PISTOLFLASH + }, + { + // shotgun + am_shell, + S_SGUNUP, + S_SGUNDOWN, + S_SGUN, + S_SGUN1, + S_SGUNFLASH1 + }, + { + // chaingun + am_clip, + S_CHAINUP, + S_CHAINDOWN, + S_CHAIN, + S_CHAIN1, + S_CHAINFLASH1 + }, + { + // missile launcher + am_misl, + S_MISSILEUP, + S_MISSILEDOWN, + S_MISSILE, + S_MISSILE1, + S_MISSILEFLASH1 + }, + { + // plasma rifle + am_cell, + S_PLASMAUP, + S_PLASMADOWN, + S_PLASMA, + S_PLASMA1, + S_PLASMAFLASH1 + }, + { + // bfg 9000 + am_cell, + S_BFGUP, + S_BFGDOWN, + S_BFG, + S_BFG1, + S_BFGFLASH1 + }, + { + // chainsaw + am_noammo, + S_SAWUP, + S_SAWDOWN, + S_SAW, + S_SAW1, + S_NULL + }, + { + // super shotgun + am_shell, + S_DSGUNUP, + S_DSGUNDOWN, + S_DSGUN, + S_DSGUN1, + S_DSGUNFLASH1 + }, +}; + + + + + + + + diff --git a/firmware_p4/components/Applications/doom/d_items.h b/firmware_p4/components/Applications/doom/d_items.h new file mode 100644 index 000000000..3d22a0630 --- /dev/null +++ b/firmware_p4/components/Applications/doom/d_items.h @@ -0,0 +1,41 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Items: key cards, artifacts, weapon, ammunition. +// + + +#ifndef __D_ITEMS__ +#define __D_ITEMS__ + +#include "doomdef.h" + + + +// Weapon info: sprite frames, ammunition use. +typedef struct +{ + ammotype_t ammo; + int upstate; + int downstate; + int readystate; + int atkstate; + int flashstate; + +} weaponinfo_t; + +extern weaponinfo_t weaponinfo[NUMWEAPONS]; + +#endif diff --git a/firmware_p4/components/Applications/doom/d_iwad.c b/firmware_p4/components/Applications/doom/d_iwad.c new file mode 100644 index 000000000..508cc6188 --- /dev/null +++ b/firmware_p4/components/Applications/doom/d_iwad.c @@ -0,0 +1,848 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Search for and locate an IWAD file, and initialize according +// to the IWAD type. +// + +#include +#include +#include +#include + +#include "config.h" +#include "deh_str.h" +#include "doomkeys.h" +#include "d_iwad.h" +#include "i_system.h" +#include "m_argv.h" +#include "m_config.h" +#include "m_misc.h" +#include "w_wad.h" +#include "z_zone.h" + +static const iwad_t iwads[] = +{ + { "doom2.wad", doom2, commercial, "Doom II" }, + { "plutonia.wad", pack_plut, commercial, "Final Doom: Plutonia Experiment" }, + { "tnt.wad", pack_tnt, commercial, "Final Doom: TNT: Evilution" }, + { "doom.wad", doom, retail, "Doom" }, + { "doom1.wad", doom, shareware, "Doom Shareware" }, + { "chex.wad", pack_chex, shareware, "Chex Quest" }, + { "hacx.wad", pack_hacx, commercial, "Hacx" }, + { "freedm.wad", doom2, commercial, "FreeDM" }, + { "freedoom2.wad", doom2, commercial, "Freedoom: Phase 2" }, + { "freedoom1.wad", doom, retail, "Freedoom: Phase 1" }, + { "heretic.wad", heretic, retail, "Heretic" }, + { "heretic1.wad", heretic, shareware, "Heretic Shareware" }, + { "hexen.wad", hexen, commercial, "Hexen" }, + //{ "strife0.wad", strife, commercial, "Strife" }, // haleyjd: STRIFE-FIXME + { "strife1.wad", strife, commercial, "Strife" }, +}; + +// Array of locations to search for IWAD files +// +// "128 IWAD search directories should be enough for anybody". + +#define MAX_IWAD_DIRS 128 + +static boolean iwad_dirs_built = false; +static char *iwad_dirs[MAX_IWAD_DIRS]; +static int num_iwad_dirs = 0; + +static void AddIWADDir(char *dir) +{ + if (num_iwad_dirs < MAX_IWAD_DIRS) + { + iwad_dirs[num_iwad_dirs] = dir; + ++num_iwad_dirs; + } +} + +// This is Windows-specific code that automatically finds the location +// of installed IWAD files. The registry is inspected to find special +// keys installed by the Windows installers for various CD versions +// of Doom. From these keys we can deduce where to find an IWAD. + +#if defined(_WIN32) && !defined(_WIN32_WCE) + +#define WIN32_LEAN_AND_MEAN +#include + +typedef struct +{ + HKEY root; + char *path; + char *value; +} registry_value_t; + +#define UNINSTALLER_STRING "\\uninstl.exe /S " + +// Keys installed by the various CD editions. These are actually the +// commands to invoke the uninstaller and look like this: +// +// C:\Program Files\Path\uninstl.exe /S C:\Program Files\Path +// +// With some munging we can find where Doom was installed. + +// [AlexMax] From the persepctive of a 64-bit executable, 32-bit registry +// keys are located in a different spot. +#if _WIN64 +#define SOFTWARE_KEY "Software\\Wow6432Node" +#else +#define SOFTWARE_KEY "Software" +#endif + +static registry_value_t uninstall_values[] = +{ + // Ultimate Doom, CD version (Depths of Doom trilogy) + + { + HKEY_LOCAL_MACHINE, + SOFTWARE_KEY "\\Microsoft\\Windows\\CurrentVersion\\" + "Uninstall\\Ultimate Doom for Windows 95", + "UninstallString", + }, + + // Doom II, CD version (Depths of Doom trilogy) + + { + HKEY_LOCAL_MACHINE, + SOFTWARE_KEY "\\Microsoft\\Windows\\CurrentVersion\\" + "Uninstall\\Doom II for Windows 95", + "UninstallString", + }, + + // Final Doom + + { + HKEY_LOCAL_MACHINE, + SOFTWARE_KEY "\\Microsoft\\Windows\\CurrentVersion\\" + "Uninstall\\Final Doom for Windows 95", + "UninstallString", + }, + + // Shareware version + + { + HKEY_LOCAL_MACHINE, + SOFTWARE_KEY "\\Microsoft\\Windows\\CurrentVersion\\" + "Uninstall\\Doom Shareware for Windows 95", + "UninstallString", + }, +}; + +// Value installed by the Collector's Edition when it is installed + +static registry_value_t collectors_edition_value = +{ + HKEY_LOCAL_MACHINE, + SOFTWARE_KEY "\\Activision\\DOOM Collector's Edition\\v1.0", + "INSTALLPATH", +}; + +// Subdirectories of the above install path, where IWADs are installed. + +static char *collectors_edition_subdirs[] = +{ + "Doom2", + "Final Doom", + "Ultimate Doom", +}; + +// Location where Steam is installed + +static registry_value_t steam_install_location = +{ + HKEY_LOCAL_MACHINE, + SOFTWARE_KEY "\\Valve\\Steam", + "InstallPath", +}; + +// Subdirs of the steam install directory where IWADs are found + +static char *steam_install_subdirs[] = +{ + "steamapps\\common\\doom 2\\base", + "steamapps\\common\\final doom\\base", + "steamapps\\common\\ultimate doom\\base", + "steamapps\\common\\heretic shadow of the serpent riders\\base", + "steamapps\\common\\hexen\\base", + "steamapps\\common\\hexen deathkings of the dark citadel\\base", + + // From Doom 3: BFG Edition: + + "steamapps\\common\\DOOM 3 BFG Edition\\base\\wads", +}; + +#define STEAM_BFG_GUS_PATCHES \ + "steamapps\\common\\DOOM 3 BFG Edition\\base\\classicmusic\\instruments" + +static char *GetRegistryString(registry_value_t *reg_val) +{ + HKEY key; + DWORD len; + DWORD valtype; + char *result; + + // Open the key (directory where the value is stored) + + if (RegOpenKeyEx(reg_val->root, reg_val->path, + 0, KEY_READ, &key) != ERROR_SUCCESS) + { + return NULL; + } + + result = NULL; + + // Find the type and length of the string, and only accept strings. + + if (RegQueryValueEx(key, reg_val->value, + NULL, &valtype, NULL, &len) == ERROR_SUCCESS + && valtype == REG_SZ) + { + // Allocate a buffer for the value and read the value + + result = malloc(len); + + if (RegQueryValueEx(key, reg_val->value, NULL, &valtype, + (unsigned char *) result, &len) != ERROR_SUCCESS) + { + free(result); + result = NULL; + } + } + + // Close the key + + RegCloseKey(key); + + return result; +} + +// Check for the uninstall strings from the CD versions + +static void CheckUninstallStrings(void) +{ + unsigned int i; + + for (i=0; i 0) + { + return; + } + + install_path = GetRegistryString(&steam_install_location); + + if (install_path == NULL) + { + return; + } + + len = strlen(install_path) + strlen(STEAM_BFG_GUS_PATCHES) + 20; + patch_path = malloc(len); + M_snprintf(patch_path, len, "%s\\%s\\ACBASS.PAT", + install_path, STEAM_BFG_GUS_PATCHES); + + // Does acbass.pat exist? If so, then set gus_patch_path. + if (M_FileExists(patch_path)) + { + M_snprintf(patch_path, len, "%s\\%s", + install_path, STEAM_BFG_GUS_PATCHES); + M_SetVariable("gus_patch_path", patch_path); + } + + free(patch_path); + free(install_path); +} + +// Default install directories for DOS Doom + +static void CheckDOSDefaults(void) +{ + // These are the default install directories used by the deice + // installer program: + + AddIWADDir("\\doom2"); // Doom II + AddIWADDir("\\plutonia"); // Final Doom + AddIWADDir("\\tnt"); + AddIWADDir("\\doom_se"); // Ultimate Doom + AddIWADDir("\\doom"); // Shareware / Registered Doom + AddIWADDir("\\dooms"); // Shareware versions + AddIWADDir("\\doomsw"); + + AddIWADDir("\\heretic"); // Heretic + AddIWADDir("\\hrtic_se"); // Heretic Shareware from Quake disc + + AddIWADDir("\\hexen"); // Hexen + AddIWADDir("\\hexendk"); // Hexen Deathkings of the Dark Citadel + + AddIWADDir("\\strife"); // Strife +} + +#endif + +// Returns true if the specified path is a path to a file +// of the specified name. + +static boolean DirIsFile(char *path, char *filename) +{ + size_t path_len; + size_t filename_len; + + path_len = strlen(path); + filename_len = strlen(filename); + + return path_len >= filename_len + 1 + && path[path_len - filename_len - 1] == DIR_SEPARATOR + && !strcasecmp(&path[path_len - filename_len], filename); +} + +// Check if the specified directory contains the specified IWAD +// file, returning the full path to the IWAD if found, or NULL +// if not found. + +static char *CheckDirectoryHasIWAD(char *dir, char *iwadname) +{ + char *filename; + + // As a special case, the "directory" may refer directly to an + // IWAD file if the path comes from DOOMWADDIR or DOOMWADPATH. + + if (DirIsFile(dir, iwadname) && M_FileExists(dir)) + { + return strdup(dir); + } + + // Construct the full path to the IWAD if it is located in + // this directory, and check if it exists. + + if (!strcmp(dir, ".")) + { + filename = strdup(iwadname); + } + else + { + filename = M_StringJoin(dir, DIR_SEPARATOR_S, iwadname, NULL); + } + + printf("Trying IWAD file:%s\n", filename); + + if (M_FileExists(filename)) + { + return filename; + } + + free(filename); + + return NULL; +} + +// Search a directory to try to find an IWAD +// Returns the location of the IWAD if found, otherwise NULL. + +static char *SearchDirectoryForIWAD(char *dir, int mask, GameMission_t *mission) +{ + char *filename; + size_t i; + + for (i=0; i + // + + iwadparm = M_CheckParmWithArgs("-iwad", 1); + + if (iwadparm) + { + // Search through IWAD dirs for an IWAD with the given name. + + iwadfile = myargv[iwadparm + 1]; + + result = D_FindWADByName(iwadfile); + + if (result == NULL) + { + I_Error("IWAD file '%s' not found!", iwadfile); + } + + *mission = IdentifyIWADByName(result, mask); + } + else + { + // Search through the list and look for an IWAD + + printf("-iwad not specified, trying a few iwad names\n"); + + result = NULL; + + BuildIWADDirList(); + + for (i=0; result == NULL && i +#include + +#include "doomfeatures.h" + +#include "d_event.h" +#include "d_loop.h" +#include "d_ticcmd.h" + +#include "i_system.h" +#include "i_timer.h" +#include "i_video.h" + +#include "m_argv.h" +#include "m_fixed.h" + +#include "net_client.h" +#include "net_gui.h" +#include "net_io.h" +#include "net_query.h" +#include "net_server.h" +#include "net_sdl.h" +#include "net_loop.h" + +// The complete set of data for a particular tic. + +typedef struct +{ + ticcmd_t cmds[NET_MAXPLAYERS]; + boolean ingame[NET_MAXPLAYERS]; +} ticcmd_set_t; + +// +// gametic is the tic about to (or currently being) run +// maketic is the tic that hasn't had control made for it yet +// recvtic is the latest tic received from the server. +// +// a gametic cannot be run until ticcmds are received for it +// from all players. +// + +static ticcmd_set_t ticdata[BACKUPTICS]; + +// The index of the next tic to be made (with a call to BuildTiccmd). + +static int maketic; + +// The number of complete tics received from the server so far. + +static int recvtic; + +// The number of tics that have been run (using RunTic) so far. + +int gametic; + +// When set to true, a single tic is run each time TryRunTics() is called. +// This is used for -timedemo mode. + +boolean singletics = false; + +// Index of the local player. + +static int localplayer; + +// Used for original sync code. + +static int skiptics = 0; + +// Reduce the bandwidth needed by sampling game input less and transmitting +// less. If ticdup is 2, sample half normal, 3 = one third normal, etc. + +int ticdup; + +// Amount to offset the timer for game sync. + +fixed_t offsetms; + +// Use new client syncronisation code + +static boolean new_sync = true; + +// Callback functions for loop code. + +static loop_interface_t *loop_interface = NULL; + +// Current players in the multiplayer game. +// This is distinct from playeringame[] used by the game code, which may +// modify playeringame[] when playing back multiplayer demos. + +static boolean local_playeringame[NET_MAXPLAYERS]; + +// Requested player class "sent" to the server on connect. +// If we are only doing a single player game then this needs to be remembered +// and saved in the game settings. + +static int player_class; + + +// 35 fps clock adjusted by offsetms milliseconds + +static int GetAdjustedTime(void) +{ + int time_ms; + + time_ms = I_GetTimeMS(); + + if (new_sync) + { + // Use the adjustments from net_client.c only if we are + // using the new sync mode. + + time_ms += (offsetms / FRACUNIT); + } + + return (time_ms * TICRATE) / 1000; +} + +static boolean BuildNewTic(void) +{ + int gameticdiv; + ticcmd_t cmd; + + gameticdiv = gametic/ticdup; + + I_StartTic (); + loop_interface->ProcessEvents(); + + // Always run the menu + + loop_interface->RunMenu(); + + if (drone) + { + // In drone mode, do not generate any ticcmds. + + return false; + } + + if (new_sync) + { + // If playing single player, do not allow tics to buffer + // up very far + + if (!net_client_connected && maketic - gameticdiv > 2) + return false; + + // Never go more than ~200ms ahead + + if (maketic - gameticdiv > 8) + return false; + } + else + { + if (maketic - gameticdiv >= 5) + return false; + } + + //printf ("mk:%i ",maketic); + memset(&cmd, 0, sizeof(ticcmd_t)); + loop_interface->BuildTiccmd(&cmd, maketic); + +#ifdef FEATURE_MULTIPLAYER + + if (net_client_connected) + { + NET_CL_SendTiccmd(&cmd, maketic); + } + +#endif + ticdata[maketic % BACKUPTICS].cmds[localplayer] = cmd; + ticdata[maketic % BACKUPTICS].ingame[localplayer] = true; + + ++maketic; + + return true; +} + +// +// NetUpdate +// Builds ticcmds for console player, +// sends out a packet +// +int lasttime; + +void NetUpdate (void) +{ + int nowtime; + int newtics; + int i; + + // If we are running with singletics (timing a demo), this + // is all done separately. + + if (singletics) + return; + +#ifdef FEATURE_MULTIPLAYER + + // Run network subsystems + + NET_CL_Run(); + NET_SV_Run(); + +#endif + + // check time + nowtime = GetAdjustedTime() / ticdup; + newtics = nowtime - lasttime; + + lasttime = nowtime; + + if (skiptics <= newtics) + { + newtics -= skiptics; + skiptics = 0; + } + else + { + skiptics -= newtics; + newtics = 0; + } + + // build new ticcmds for console player + + for (i=0 ; iconsoleplayer = 0; + settings->num_players = 1; + settings->player_classes[0] = player_class; + + //! + // @category net + // + // Use new network client sync code rather than the classic + // sync code. This is currently disabled by default because it + // has some bugs. + // + if (M_CheckParm("-newsync") > 0) + settings->new_sync = 1; + else + settings->new_sync = 0; + + // TODO: New sync code is not enabled by default because it's + // currently broken. + //if (M_CheckParm("-oldsync") > 0) + // settings->new_sync = 0; + //else + // settings->new_sync = 1; + + //! + // @category net + // @arg + // + // Send n extra tics in every packet as insurance against dropped + // packets. + // + + i = M_CheckParmWithArgs("-extratics", 1); + + if (i > 0) + settings->extratics = atoi(myargv[i+1]); + else + settings->extratics = 1; + + //! + // @category net + // @arg + // + // Reduce the resolution of the game by a factor of n, reducing + // the amount of network bandwidth needed. + // + + i = M_CheckParmWithArgs("-dup", 1); + + if (i > 0) + settings->ticdup = atoi(myargv[i+1]); + else + settings->ticdup = 1; + + if (net_client_connected) + { + // Send our game settings and block until game start is received + // from the server. + + NET_CL_StartGame(settings); + BlockUntilStart(settings, callback); + + // Read the game settings that were received. + + NET_CL_GetSettings(settings); + } + + if (drone) + { + settings->consoleplayer = 0; + } + + // Set the local player and playeringame[] values. + + localplayer = settings->consoleplayer; + + for (i = 0; i < NET_MAXPLAYERS; ++i) + { + local_playeringame[i] = i < settings->num_players; + } + + // Copy settings to global variables. + + ticdup = settings->ticdup; + new_sync = settings->new_sync; + + // TODO: Message disabled until we fix new_sync. + //if (!new_sync) + //{ + // printf("Syncing netgames like Vanilla Doom.\n"); + //} +#else + settings->consoleplayer = 0; + settings->num_players = 1; + settings->player_classes[0] = player_class; + settings->new_sync = 0; + settings->extratics = 1; + settings->ticdup = 1; + + ticdup = settings->ticdup; + new_sync = settings->new_sync; +#endif +} + +boolean D_InitNetGame(net_connect_data_t *connect_data) +{ + boolean result = false; +#ifdef FEATURE_MULTIPLAYER + net_addr_t *addr = NULL; + int i; +#endif + + // Call D_QuitNetGame on exit: + + I_AtExit(D_QuitNetGame, true); + + player_class = connect_data->player_class; + +#ifdef FEATURE_MULTIPLAYER + + //! + // @category net + // + // Start a multiplayer server, listening for connections. + // + + if (M_CheckParm("-server") > 0 + || M_CheckParm("-privateserver") > 0) + { + NET_SV_Init(); + NET_SV_AddModule(&net_loop_server_module); + NET_SV_AddModule(&net_sdl_module); + NET_SV_RegisterWithMaster(); + + net_loop_client_module.InitClient(); + addr = net_loop_client_module.ResolveAddress(NULL); + } + else + { + //! + // @category net + // + // Automatically search the local LAN for a multiplayer + // server and join it. + // + + i = M_CheckParm("-autojoin"); + + if (i > 0) + { + addr = NET_FindLANServer(); + + if (addr == NULL) + { + I_Error("No server found on local LAN"); + } + } + + //! + // @arg
+ // @category net + // + // Connect to a multiplayer server running on the given + // address. + // + + i = M_CheckParmWithArgs("-connect", 1); + + if (i > 0) + { + net_sdl_module.InitClient(); + addr = net_sdl_module.ResolveAddress(myargv[i+1]); + + if (addr == NULL) + { + I_Error("Unable to resolve '%s'\n", myargv[i+1]); + } + } + } + + if (addr != NULL) + { + if (M_CheckParm("-drone") > 0) + { + connect_data->drone = true; + } + + if (!NET_CL_Connect(addr, connect_data)) + { + I_Error("D_InitNetGame: Failed to connect to %s\n", + NET_AddrToString(addr)); + } + + printf("D_InitNetGame: Connected to %s\n", NET_AddrToString(addr)); + + // Wait for launch message received from server. + + NET_WaitForLaunch(); + + result = true; + } +#endif + + return result; +} + + +// +// D_QuitNetGame +// Called before quitting to leave a net game +// without hanging the other players +// +void D_QuitNetGame (void) +{ +#ifdef FEATURE_MULTIPLAYER + NET_SV_Shutdown(); + NET_CL_Disconnect(); +#endif +} + +static int GetLowTic(void) +{ + int lowtic; + + lowtic = maketic; + +#ifdef FEATURE_MULTIPLAYER + if (net_client_connected) + { + if (drone || recvtic < lowtic) + { + lowtic = recvtic; + } + } +#endif + + return lowtic; +} + +static int frameon; +static int frameskip[4]; +static int oldnettics; + +static void OldNetSync(void) +{ + unsigned int i; + int keyplayer = -1; + + frameon++; + + // ideally maketic should be 1 - 3 tics above lowtic + // if we are consistantly slower, speed up time + + for (i=0 ; i recvtic; + oldnettics = maketic; + + if (frameskip[0] && frameskip[1] && frameskip[2] && frameskip[3]) + { + skiptics = 1; + // printf ("+"); + } + } +} + +// Returns true if there are players in the game: + +static boolean PlayersInGame(void) +{ + boolean result = false; + unsigned int i; + + // If we are connected to a server, check if there are any players + // in the game. + + if (net_client_connected) + { + for (i = 0; i < NET_MAXPLAYERS; ++i) + { + result = result || local_playeringame[i]; + } + } + + // Whether single or multi-player, unless we are running as a drone, + // we are in the game. + + if (!drone) + { + result = true; + } + + return result; +} + +// When using ticdup, certain values must be cleared out when running +// the duplicate ticcmds. + +static void TicdupSquash(ticcmd_set_t *set) +{ + ticcmd_t *cmd; + unsigned int i; + + for (i = 0; i < NET_MAXPLAYERS ; ++i) + { + cmd = &set->cmds[i]; + cmd->chatchar = 0; + if (cmd->buttons & BT_SPECIAL) + cmd->buttons = 0; + } +} + +// When running in single player mode, clear all the ingame[] array +// except the local player. + +static void SinglePlayerClear(ticcmd_set_t *set) +{ + unsigned int i; + + for (i = 0; i < NET_MAXPLAYERS; ++i) + { + if (i != localplayer) + { + set->ingame[i] = false; + } + } +} + +// +// TryRunTics +// + +void TryRunTics (void) +{ + int i; + int lowtic; + int entertic; + static int oldentertics; + int realtics; + int availabletics; + int counts; + + // get real tics + entertic = I_GetTime() / ticdup; + realtics = entertic - oldentertics; + oldentertics = entertic; + + // in singletics mode, run a single tic every time this function + // is called. + + if (singletics) + { + BuildNewTic(); + } + else + { + NetUpdate (); + } + + lowtic = GetLowTic(); + + availabletics = lowtic - gametic/ticdup; + + // decide how many tics to run + + if (new_sync) + { + counts = availabletics; + } + else + { + // decide how many tics to run + if (realtics < availabletics-1) + counts = realtics+1; + else if (realtics < availabletics) + counts = realtics; + else + counts = availabletics; + + if (counts < 1) + counts = 1; + + if (net_client_connected) + { + OldNetSync(); + } + } + + if (counts < 1) + counts = 1; + + // wait for new tics if needed + + while (!PlayersInGame() || lowtic < gametic/ticdup + counts) + { + NetUpdate (); + + lowtic = GetLowTic(); + + if (lowtic < gametic/ticdup) + I_Error ("TryRunTics: lowtic < gametic"); + + // Don't stay in this loop forever. The menu is still running, + // so return to update the screen + + if (I_GetTime() / ticdup - entertic > 0) + { + return; + } + + I_Sleep(1); + } + + // run the count * ticdup dics + while (counts--) + { + ticcmd_set_t *set; + + if (!PlayersInGame()) + { + return; + } + + set = &ticdata[(gametic / ticdup) % BACKUPTICS]; + + if (!net_client_connected) + { + SinglePlayerClear(set); + } + + for (i=0 ; i lowtic) + I_Error ("gametic>lowtic"); + + memcpy(local_playeringame, set->ingame, sizeof(local_playeringame)); + + loop_interface->RunTic(set->cmds, set->ingame); + gametic++; + + // modify command for duplicated tics + + TicdupSquash(set); + } + + NetUpdate (); // check for new console commands + } +} + +void D_RegisterLoopCallbacks(loop_interface_t *i) +{ + loop_interface = i; +} diff --git a/firmware_p4/components/Applications/doom/d_loop.h b/firmware_p4/components/Applications/doom/d_loop.h new file mode 100644 index 000000000..eb87d8488 --- /dev/null +++ b/firmware_p4/components/Applications/doom/d_loop.h @@ -0,0 +1,81 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Main loop stuff. +// + +#ifndef __D_LOOP__ +#define __D_LOOP__ + +#include "net_defs.h" + +// Callback function invoked while waiting for the netgame to start. +// The callback is invoked when new players are ready. The callback +// should return true, or return false to abort startup. + +typedef boolean (*netgame_startup_callback_t)(int ready_players, + int num_players); + +typedef struct +{ + // Read events from the event queue, and process them. + + void (*ProcessEvents)(); + + // Given the current input state, fill in the fields of the specified + // ticcmd_t structure with data for a new tic. + + void (*BuildTiccmd)(ticcmd_t *cmd, int maketic); + + // Advance the game forward one tic, using the specified player input. + + void (*RunTic)(ticcmd_t *cmds, boolean *ingame); + + // Run the menu (runs independently of the game). + + void (*RunMenu)(); +} loop_interface_t; + +// Register callback functions for the main loop code to use. +void D_RegisterLoopCallbacks(loop_interface_t *i); + +// Create any new ticcmds and broadcast to other players. +void NetUpdate (void); + +// Broadcasts special packets to other players +// to notify of game exit +void D_QuitNetGame (void); + +//? how many ticks to run? +void TryRunTics (void); + +// Called at start of game loop to initialize timers +void D_StartGameLoop(void); + +// Initialize networking code and connect to server. + +boolean D_InitNetGame(net_connect_data_t *connect_data); + +// Start game with specified settings. The structure will be updated +// with the actual settings for the game. + +void D_StartNetGame(net_gamesettings_t *settings, + netgame_startup_callback_t callback); + +extern boolean singletics; +extern int gametic, ticdup; + +#endif + diff --git a/firmware_p4/components/Applications/doom/d_main.c b/firmware_p4/components/Applications/doom/d_main.c new file mode 100644 index 000000000..9012e5f54 --- /dev/null +++ b/firmware_p4/components/Applications/doom/d_main.c @@ -0,0 +1,1845 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// DOOM main program (D_DoomMain) and game loop (D_DoomLoop), +// plus functions to determine game mode (shareware, registered), +// parse command line parameters, configure game parameters (turbo), +// and call the startup functions. +// + + +#include +#include +#include +#include + +#include "config.h" +#include "deh_main.h" +#include "doomdef.h" +#include "doomstat.h" + +#include "dstrings.h" +#include "doomfeatures.h" +#include "sounds.h" + +#include "d_iwad.h" + +#include "z_zone.h" +#include "w_main.h" +#include "w_wad.h" +#include "s_sound.h" +#include "v_video.h" + +#include "f_finale.h" +#include "f_wipe.h" + +#include "m_argv.h" +#include "m_config.h" +#include "m_controls.h" +#include "m_misc.h" +#include "m_menu.h" +#include "p_saveg.h" + +#include "i_endoom.h" +#include "i_joystick.h" +#include "i_system.h" +#include "i_timer.h" +#include "i_video.h" + +#include "g_game.h" + +#include "hu_stuff.h" +#include "wi_stuff.h" +#include "st_stuff.h" +#include "am_map.h" +#include "net_client.h" +#include "net_dedicated.h" +#include "net_query.h" + +#include "p_setup.h" +#include "r_local.h" +#include "statdump.h" + +#include "d_main.h" + +// +// D-DoomLoop() +// Not a globally visible function, +// just included for source reference, +// called by D_DoomMain, never exits. +// Manages timing and IO, +// calls all ?_Responder, ?_Ticker, and ?_Drawer, +// calls I_GetTime, I_StartFrame, and I_StartTic +// +void D_DoomLoop (void); + +// Location where savegames are stored + +char * savegamedir; + +// location of IWAD and WAD files + +char * iwadfile; + + +boolean devparm; // started game with -devparm +boolean nomonsters; // checkparm of -nomonsters +boolean respawnparm; // checkparm of -respawn +boolean fastparm; // checkparm of -fast + +//extern int soundVolume; +//extern int sfxVolume; +//extern int musicVolume; + +extern boolean inhelpscreens; + +skill_t startskill; +int startepisode; +int startmap; +boolean autostart; +int startloadgame; + +boolean advancedemo; + +// Store demo, do not accept any inputs +boolean storedemo; + +// "BFG Edition" version of doom2.wad does not include TITLEPIC. +boolean bfgedition; + +// If true, the main game loop has started. +boolean main_loop_started = false; + +char wadfile[1024]; // primary wad file +char mapdir[1024]; // directory of development maps + +int show_endoom = 1; + + +void D_ConnectNetGame(void); +void D_CheckNetGame(void); + + +// +// D_ProcessEvents +// Send all the events of the given timestamp down the responder chain +// +void D_ProcessEvents (void) +{ + event_t* ev; + + // IF STORE DEMO, DO NOT ACCEPT INPUT + if (storedemo) + return; + + while ((ev = D_PopEvent()) != NULL) + { + if (M_Responder (ev)) + continue; // menu ate the event + G_Responder (ev); + } +} + + + + +// +// D_Display +// draw current display, possibly wiping it from the previous +// + +// wipegamestate can be set to -1 to force a wipe on the next draw +gamestate_t wipegamestate = GS_DEMOSCREEN; +extern boolean setsizeneeded; +extern int showMessages; +void R_ExecuteSetViewSize (void); + +void D_Display (void) +{ + static boolean viewactivestate = false; + static boolean menuactivestate = false; + static boolean inhelpscreensstate = false; + static boolean fullscreen = false; + static gamestate_t oldgamestate = -1; + static int borderdrawcount; + int nowtime; + int tics; + int wipestart; + int y; + boolean done; + boolean wipe; + boolean redrawsbar; + + if (nodrawers) + return; // for comparative timing / profiling + + redrawsbar = false; + + // change the view size if needed + if (setsizeneeded) + { + R_ExecuteSetViewSize (); + oldgamestate = -1; // force background redraw + borderdrawcount = 3; + } + + // save the current screen if about to wipe + if (gamestate != wipegamestate) + { + wipe = true; + wipe_StartScreen(0, 0, SCREENWIDTH, SCREENHEIGHT); + } + else + wipe = false; + + if (gamestate == GS_LEVEL && gametic) + HU_Erase(); + + // do buffered drawing + switch (gamestate) + { + case GS_LEVEL: + if (!gametic) + break; + if (automapactive) + AM_Drawer (); + if (wipe || (viewheight != 200 && fullscreen) ) + redrawsbar = true; + if (inhelpscreensstate && !inhelpscreens) + redrawsbar = true; // just put away the help screen + ST_Drawer (viewheight == 200, redrawsbar ); + fullscreen = viewheight == 200; + break; + + case GS_INTERMISSION: + WI_Drawer (); + break; + + case GS_FINALE: + F_Drawer (); + break; + + case GS_DEMOSCREEN: + D_PageDrawer (); + break; + } + + // draw buffered stuff to screen + I_UpdateNoBlit (); + + // draw the view directly + if (gamestate == GS_LEVEL && !automapactive && gametic) + R_RenderPlayerView (&players[displayplayer]); + + if (gamestate == GS_LEVEL && gametic) + HU_Drawer (); + + // clean up border stuff + if (gamestate != oldgamestate && gamestate != GS_LEVEL) + I_SetPalette (W_CacheLumpName (DEH_String("PLAYPAL"),PU_CACHE)); + + // see if the border needs to be initially drawn + if (gamestate == GS_LEVEL && oldgamestate != GS_LEVEL) + { + viewactivestate = false; // view was not active + R_FillBackScreen (); // draw the pattern into the back screen + } + + // see if the border needs to be updated to the screen + if (gamestate == GS_LEVEL && !automapactive && scaledviewwidth != 320) + { + if (menuactive || menuactivestate || !viewactivestate) + borderdrawcount = 3; + if (borderdrawcount) + { + R_DrawViewBorder (); // erase old menu stuff + borderdrawcount--; + } + } + + if (testcontrols) + { + // Box showing current mouse speed + + V_DrawMouseSpeedBox(testcontrols_mousespeed); + } + + menuactivestate = menuactive; + viewactivestate = viewactive; + inhelpscreensstate = inhelpscreens; + oldgamestate = wipegamestate = gamestate; + + // draw pause pic + if (paused) + { + if (automapactive) + y = 4; + else + y = viewwindowy+4; + V_DrawPatchDirect(viewwindowx + (scaledviewwidth - 68) / 2, y, + W_CacheLumpName (DEH_String("M_PAUSE"), PU_CACHE)); + } + + + // menus go directly to the screen + M_Drawer (); // menu is drawn even on top of everything + NetUpdate (); // send out any new accumulation + + + // normal update + if (!wipe) + { + I_FinishUpdate (); // page flip or blit buffer + return; + } + + // wipe update + wipe_EndScreen(0, 0, SCREENWIDTH, SCREENHEIGHT); + + wipestart = I_GetTime () - 1; + + do + { + do + { + nowtime = I_GetTime (); + tics = nowtime - wipestart; + I_Sleep(1); + } while (tics <= 0); + + wipestart = nowtime; + done = wipe_ScreenWipe(wipe_Melt + , 0, 0, SCREENWIDTH, SCREENHEIGHT, tics); + I_UpdateNoBlit (); + M_Drawer (); // menu is drawn even on top of wipes + I_FinishUpdate (); // page flip or blit buffer + } while (!done); +} + +// +// Add configuration file variable bindings. +// + +void D_BindVariables(void) +{ + int i; + + M_ApplyPlatformDefaults(); + + I_BindVideoVariables(); + I_BindJoystickVariables(); + I_BindSoundVariables(); + + M_BindBaseControls(); + M_BindWeaponControls(); + M_BindMapControls(); + M_BindMenuControls(); + M_BindChatControls(MAXPLAYERS); + + key_multi_msgplayer[0] = HUSTR_KEYGREEN; + key_multi_msgplayer[1] = HUSTR_KEYINDIGO; + key_multi_msgplayer[2] = HUSTR_KEYBROWN; + key_multi_msgplayer[3] = HUSTR_KEYRED; + +#ifdef FEATURE_MULTIPLAYER + NET_BindVariables(); +#endif + + M_BindVariable("mouse_sensitivity", &mouseSensitivity); + M_BindVariable("sfx_volume", &sfxVolume); + M_BindVariable("music_volume", &musicVolume); + M_BindVariable("show_messages", &showMessages); + M_BindVariable("screenblocks", &screenblocks); + M_BindVariable("detaillevel", &detailLevel); + M_BindVariable("snd_channels", &snd_channels); + M_BindVariable("vanilla_savegame_limit", &vanilla_savegame_limit); + M_BindVariable("vanilla_demo_limit", &vanilla_demo_limit); + M_BindVariable("show_endoom", &show_endoom); + + // Multiplayer chat macros + + for (i=0; i<10; ++i) + { + char buf[12]; + + M_snprintf(buf, sizeof(buf), "chatmacro%i", i); + M_BindVariable(buf, &chat_macros[i]); + } +} + +// +// D_GrabMouseCallback +// +// Called to determine whether to grab the mouse pointer +// + +boolean D_GrabMouseCallback(void) +{ + // Drone players don't need mouse focus + + if (drone) + return false; + + // when menu is active or game is paused, release the mouse + + if (menuactive || paused) + return false; + + // only grab mouse when playing levels (but not demos) + + return (gamestate == GS_LEVEL) && !demoplayback && !advancedemo; +} + +void doomgeneric_Tick() +{ + // frame syncronous IO operations + I_StartFrame (); + + TryRunTics (); // will run at least one tic + + S_UpdateSounds (players[consoleplayer].mo);// move positional sounds + + // Update display, next frame, with current state. + if (screenvisible) + { + D_Display (); + } +} + +// +// D_DoomLoop +// +void D_DoomLoop (void) +{ + if (bfgedition && + (demorecording || (gameaction == ga_playdemo) || netgame)) + { + printf(" WARNING: You are playing using one of the Doom Classic\n" + " IWAD files shipped with the Doom 3: BFG Edition. These are\n" + " known to be incompatible with the regular IWAD files and\n" + " may cause demos and network games to get out of sync.\n"); + } + + if (demorecording) + G_BeginRecording (); + + main_loop_started = true; + + TryRunTics(); + + I_SetWindowTitle(gamedescription); + I_GraphicsCheckCommandLine(); + I_SetGrabMouseCallback(D_GrabMouseCallback); + I_InitGraphics(); + I_EnableLoadingDisk(); + + V_RestoreBuffer(); + R_ExecuteSetViewSize(); + + D_StartGameLoop(); + + if (testcontrols) + { + wipegamestate = gamestate; + } + + doomgeneric_Tick(); +} + + + +// +// DEMO LOOP +// +int demosequence; +int pagetic; +char *pagename; + + +// +// D_PageTicker +// Handles timing for warped projection +// +void D_PageTicker (void) +{ + if (--pagetic < 0) + D_AdvanceDemo (); +} + + + +// +// D_PageDrawer +// +void D_PageDrawer (void) +{ + V_DrawPatch (0, 0, W_CacheLumpName(pagename, PU_CACHE)); +} + + +// +// D_AdvanceDemo +// Called after each demo or intro demosequence finishes +// +void D_AdvanceDemo (void) +{ + advancedemo = true; +} + + +// +// This cycles through the demo sequences. +// FIXME - version dependend demo numbers? +// +void D_DoAdvanceDemo (void) +{ + players[consoleplayer].playerstate = PST_LIVE; // not reborn + advancedemo = false; + usergame = false; // no save / end game here + paused = false; + gameaction = ga_nothing; + + // The Ultimate Doom executable changed the demo sequence to add + // a DEMO4 demo. Final Doom was based on Ultimate, so also + // includes this change; however, the Final Doom IWADs do not + // include a DEMO4 lump, so the game bombs out with an error + // when it reaches this point in the demo sequence. + + // However! There is an alternate version of Final Doom that + // includes a fixed executable. + + if (gameversion == exe_ultimate || gameversion == exe_final) + demosequence = (demosequence+1)%7; + else + demosequence = (demosequence+1)%6; + + switch (demosequence) + { + case 0: + if ( gamemode == commercial ) + pagetic = TICRATE * 11; + else + pagetic = 170; + gamestate = GS_DEMOSCREEN; + pagename = DEH_String("TITLEPIC"); + if ( gamemode == commercial ) + S_StartMusic(mus_dm2ttl); + else + S_StartMusic (mus_intro); + break; + case 1: + G_DeferedPlayDemo(DEH_String("demo1")); + break; + case 2: + pagetic = 200; + gamestate = GS_DEMOSCREEN; + pagename = DEH_String("CREDIT"); + break; + case 3: + G_DeferedPlayDemo(DEH_String("demo2")); + break; + case 4: + gamestate = GS_DEMOSCREEN; + if ( gamemode == commercial) + { + pagetic = TICRATE * 11; + pagename = DEH_String("TITLEPIC"); + S_StartMusic(mus_dm2ttl); + } + else + { + pagetic = 200; + + if ( gamemode == retail ) + pagename = DEH_String("CREDIT"); + else + pagename = DEH_String("HELP2"); + } + break; + case 5: + G_DeferedPlayDemo(DEH_String("demo3")); + break; + // THE DEFINITIVE DOOM Special Edition demo + case 6: + G_DeferedPlayDemo(DEH_String("demo4")); + break; + } + + // The Doom 3: BFG Edition version of doom2.wad does not have a + // TITLETPIC lump. Use INTERPIC instead as a workaround. + if (bfgedition && !strcasecmp(pagename, "TITLEPIC") + && W_CheckNumForName("titlepic") < 0) + { + pagename = DEH_String("INTERPIC"); + } +} + + + +// +// D_StartTitle +// +void D_StartTitle (void) +{ + gameaction = ga_nothing; + demosequence = -1; + D_AdvanceDemo (); +} + +// Strings for dehacked replacements of the startup banner +// +// These are from the original source: some of them are perhaps +// not used in any dehacked patches + +static char *banners[] = +{ + // doom2.wad + " " + "DOOM 2: Hell on Earth v%i.%i" + " ", + // doom1.wad + " " + "DOOM Shareware Startup v%i.%i" + " ", + // doom.wad + " " + "DOOM Registered Startup v%i.%i" + " ", + // Registered DOOM uses this + " " + "DOOM System Startup v%i.%i" + " ", + // doom.wad (Ultimate DOOM) + " " + "The Ultimate DOOM Startup v%i.%i" + " ", + // tnt.wad + " " + "DOOM 2: TNT - Evilution v%i.%i" + " ", + // plutonia.wad + " " + "DOOM 2: Plutonia Experiment v%i.%i" + " ", +}; + +// +// Get game name: if the startup banner has been replaced, use that. +// Otherwise, use the name given +// + +static char *GetGameName(char *gamename) +{ + size_t i; + char *deh_sub; + + for (i=0; i 0) + { + // Ultimate Doom + + gamemode = retail; + } + else if (W_CheckNumForName("E3M1") > 0) + { + gamemode = registered; + } + else + { + gamemode = shareware; + } + } + else + { + int p; + + // Doom 2 of some kind. + gamemode = commercial; + + // We can manually override the gamemission that we got from the + // IWAD detection code. This allows us to eg. play Plutonia 2 + // with Freedoom and get the right level names. + + //! + // @arg + // + // Explicitly specify a Doom II "mission pack" to run as, instead of + // detecting it based on the filename. Valid values are: "doom2", + // "tnt" and "plutonia". + // + p = M_CheckParmWithArgs("-pack", 1); + if (p > 0) + { + SetMissionForPackName(myargv[p + 1]); + } + } +} + +// Set the gamedescription string + +void D_SetGameDescription(void) +{ + boolean is_freedoom = W_CheckNumForName("FREEDOOM") >= 0, + is_freedm = W_CheckNumForName("FREEDM") >= 0; + + gamedescription = "Unknown"; + + if (logical_gamemission == doom) + { + // Doom 1. But which version? + + if (is_freedoom) + { + gamedescription = GetGameName("Freedoom: Phase 1"); + } + else if (gamemode == retail) + { + // Ultimate Doom + + gamedescription = GetGameName("The Ultimate DOOM"); + } + else if (gamemode == registered) + { + gamedescription = GetGameName("DOOM Registered"); + } + else if (gamemode == shareware) + { + gamedescription = GetGameName("DOOM Shareware"); + } + } + else + { + // Doom 2 of some kind. But which mission? + + if (is_freedoom) + { + if (is_freedm) + { + gamedescription = GetGameName("FreeDM"); + } + else + { + gamedescription = GetGameName("Freedoom: Phase 2"); + } + } + else if (logical_gamemission == doom2) + { + gamedescription = GetGameName("DOOM 2: Hell on Earth"); + } + else if (logical_gamemission == pack_plut) + { + gamedescription = GetGameName("DOOM 2: Plutonia Experiment"); + } + else if (logical_gamemission == pack_tnt) + { + gamedescription = GetGameName("DOOM 2: TNT - Evilution"); + } + } +} + +// print title for every printed line +char title[128]; + +static boolean D_AddFile(char *filename) +{ + wad_file_t *handle; + + printf(" adding %s\n", filename); + handle = W_AddFile(filename); + + return handle != NULL; +} + +// Copyright message banners +// Some dehacked mods replace these. These are only displayed if they are +// replaced by dehacked. + +static char *copyright_banners[] = +{ + "===========================================================================\n" + "ATTENTION: This version of DOOM has been modified. If you would like to\n" + "get a copy of the original game, call 1-800-IDGAMES or see the readme file.\n" + " You will not receive technical support for modified games.\n" + " press enter to continue\n" + "===========================================================================\n", + + "===========================================================================\n" + " Commercial product - do not distribute!\n" + " Please report software piracy to the SPA: 1-800-388-PIR8\n" + "===========================================================================\n", + + "===========================================================================\n" + " Shareware!\n" + "===========================================================================\n" +}; + +// Prints a message only if it has been modified by dehacked. + +void PrintDehackedBanners(void) +{ + size_t i; + + for (i=0; i + // @category compat + // + // Emulate a specific version of Doom. Valid values are "1.9", + // "ultimate", "final", "final2", "hacx" and "chex". + // + + p = M_CheckParmWithArgs("-gameversion", 1); + + if (p) + { + for (i=0; gameversions[i].description != NULL; ++i) + { + if (!strcmp(myargv[p+1], gameversions[i].cmdline)) + { + gameversion = gameversions[i].version; + break; + } + } + + if (gameversions[i].description == NULL) + { + printf("Supported game versions:\n"); + + for (i=0; gameversions[i].description != NULL; ++i) + { + printf("\t%s (%s)\n", gameversions[i].cmdline, + gameversions[i].description); + } + + I_Error("Unknown game version '%s'", myargv[p+1]); + } + } + else + { + // Determine automatically + + if (gamemission == pack_chex) + { + // chex.exe - identified by iwad filename + + gameversion = exe_chex; + } + else if (gamemission == pack_hacx) + { + // hacx.exe: identified by iwad filename + + gameversion = exe_hacx; + } + else if (gamemode == shareware || gamemode == registered) + { + // original + + gameversion = exe_doom_1_9; + + // TODO: Detect IWADs earlier than Doom v1.9. + } + else if (gamemode == retail) + { + gameversion = exe_ultimate; + } + else if (gamemode == commercial) + { + if (gamemission == doom2) + { + gameversion = exe_doom_1_9; + } + else + { + // Final Doom: tnt or plutonia + // Defaults to emulating the first Final Doom executable, + // which has the crash in the demo loop; however, having + // this as the default should mean that it plays back + // most demos correctly. + + gameversion = exe_final; + } + } + } + + // The original exe does not support retail - 4th episode not supported + + if (gameversion < exe_ultimate && gamemode == retail) + { + gamemode = registered; + } + + // EXEs prior to the Final Doom exes do not support Final Doom. + + if (gameversion < exe_final && gamemode == commercial + && (gamemission == pack_tnt || gamemission == pack_plut)) + { + gamemission = doom2; + } +} + +void PrintGameVersion(void) +{ + int i; + + for (i=0; gameversions[i].description != NULL; ++i) + { + if (gameversions[i].version == gameversion) + { + printf("Emulating the behavior of the " + "'%s' executable.\n", gameversions[i].description); + break; + } + } +} + +// Function called at exit to display the ENDOOM screen + +static void D_Endoom(void) +{ + byte *endoom; + + // Don't show ENDOOM if we have it disabled, or we're running + // in screensaver or control test mode. Only show it once the + // game has actually started. + + if (!show_endoom || !main_loop_started + || screensaver_mode || M_CheckParm("-testcontrols") > 0) + { + return; + } + + endoom = W_CacheLumpName(DEH_String("ENDOOM"), PU_STATIC); + + I_Endoom(endoom); + + exit(0); +} + +#if ORIGCODE +// Load dehacked patches needed for certain IWADs. +static void LoadIwadDeh(void) +{ + // The Freedoom IWADs have DEHACKED lumps that must be loaded. + if (W_CheckNumForName("FREEDOOM") >= 0) + { + // Old versions of Freedoom (before 2014-09) did not have technically + // valid DEHACKED lumps, so ignore errors and just continue if this + // is an old IWAD. + DEH_LoadLumpByName("DEHACKED", false, true); + } + + // If this is the HACX IWAD, we need to load the DEHACKED lump. + if (gameversion == exe_hacx) + { + if (!DEH_LoadLumpByName("DEHACKED", true, false)) + { + I_Error("DEHACKED lump not found. Please check that this is the " + "Hacx v1.2 IWAD."); + } + } + + // Chex Quest needs a separate Dehacked patch which must be downloaded + // and installed next to the IWAD. + if (gameversion == exe_chex) + { + char *chex_deh = NULL; + char *sep; + + // Look for chex.deh in the same directory as the IWAD file. + sep = strrchr(iwadfile, DIR_SEPARATOR); + + if (sep != NULL) + { + size_t chex_deh_len = strlen(iwadfile) + 9; + chex_deh = malloc(chex_deh_len); + M_StringCopy(chex_deh, iwadfile, chex_deh_len); + chex_deh[sep - iwadfile + 1] = '\0'; + M_StringConcat(chex_deh, "chex.deh", chex_deh_len); + } + else + { + chex_deh = strdup("chex.deh"); + } + + // If the dehacked patch isn't found, try searching the WAD + // search path instead. We might find it... + if (!M_FileExists(chex_deh)) + { + free(chex_deh); + chex_deh = D_FindWADByName("chex.deh"); + } + + // Still not found? + if (chex_deh == NULL) + { + I_Error("Unable to find Chex Quest dehacked file (chex.deh).\n" + "The dehacked file is required in order to emulate\n" + "chex.exe correctly. It can be found in your nearest\n" + "/idgames repository mirror at:\n\n" + " utils/exe_edit/patches/chexdeh.zip"); + } + + if (!DEH_LoadFile(chex_deh)) + { + I_Error("Failed to load chex.deh needed for emulating chex.exe."); + } + } +} +#endif + +// +// D_DoomMain +// +void D_DoomMain (void) +{ + int p; + char file[256]; + char demolumpname[9]; +#if ORIGCODE + int numiwadlumps; +#endif + + I_AtExit(D_Endoom, false); + + // print banner + + I_PrintBanner(PACKAGE_STRING); + + DEH_printf("Z_Init: Init zone memory allocation daemon. \n"); + Z_Init (); + +#ifdef FEATURE_MULTIPLAYER + //! + // @category net + // + // Start a dedicated server, routing packets but not participating + // in the game itself. + // + + if (M_CheckParm("-dedicated") > 0) + { + printf("Dedicated server mode.\n"); + NET_DedicatedServer(); + + // Never returns + } + + //! + // @category net + // + // Query the Internet master server for a global list of active + // servers. + // + + if (M_CheckParm("-search")) + { + NET_MasterQuery(); + exit(0); + } + + //! + // @arg
+ // @category net + // + // Query the status of the server running on the given IP + // address. + // + + p = M_CheckParmWithArgs("-query", 1); + + if (p) + { + NET_QueryAddress(myargv[p+1]); + exit(0); + } + + //! + // @category net + // + // Search the local LAN for running servers. + // + + if (M_CheckParm("-localsearch")) + { + NET_LANQuery(); + exit(0); + } + +#endif + + //! + // @vanilla + // + // Disable monsters. + // + + nomonsters = M_CheckParm ("-nomonsters"); + + //! + // @vanilla + // + // Monsters respawn after being killed. + // + + respawnparm = M_CheckParm ("-respawn"); + + //! + // @vanilla + // + // Monsters move faster. + // + + fastparm = M_CheckParm ("-fast"); + + //! + // @vanilla + // + // Developer mode. F1 saves a screenshot in the current working + // directory. + // + + devparm = M_CheckParm ("-devparm"); + + I_DisplayFPSDots(devparm); + + //! + // @category net + // @vanilla + // + // Start a deathmatch game. + // + + if (M_CheckParm ("-deathmatch")) + deathmatch = 1; + + //! + // @category net + // @vanilla + // + // Start a deathmatch 2.0 game. Weapons do not stay in place and + // all items respawn after 30 seconds. + // + + if (M_CheckParm ("-altdeath")) + deathmatch = 2; + + if (devparm) + DEH_printf(D_DEVSTR); + + // find which dir to use for config files + +#ifdef _WIN32 + + //! + // @platform windows + // @vanilla + // + // Save configuration data and savegames in c:\doomdata, + // allowing play from CD. + // + + if (M_ParmExists("-cdrom")) + { + printf(D_CDROM); + + M_SetConfigDir("c:\\doomdata\\"); + } + else +#endif + { + // Auto-detect the configuration dir. + + M_SetConfigDir(NULL); + } + + //! + // @arg + // @vanilla + // + // Turbo mode. The player's speed is multiplied by x%. If unspecified, + // x defaults to 200. Values are rounded up to 10 and down to 400. + // + + if ( (p=M_CheckParm ("-turbo")) ) + { + int scale = 200; + extern int forwardmove[2]; + extern int sidemove[2]; + + if (p 400) + scale = 400; + DEH_printf("turbo scale: %i%%\n", scale); + forwardmove[0] = forwardmove[0]*scale/100; + forwardmove[1] = forwardmove[1]*scale/100; + sidemove[0] = sidemove[0]*scale/100; + sidemove[1] = sidemove[1]*scale/100; + } + + // init subsystems + DEH_printf("V_Init: allocate screens.\n"); + V_Init (); + + // Load configuration files before initialising other subsystems. + DEH_printf("M_LoadDefaults: Load system defaults.\n"); + M_SetConfigFilenames("default.cfg", PROGRAM_PREFIX "doom.cfg"); + D_BindVariables(); + M_LoadDefaults(); + + // Save configuration at exit. + I_AtExit(M_SaveDefaults, false); + + // Find main IWAD file and load it. + iwadfile = D_FindIWAD(IWAD_MASK_DOOM, &gamemission); + + // None found? + + if (iwadfile == NULL) + { + I_Error("Game mode indeterminate. No IWAD file was found. Try\n" + "specifying one with the '-iwad' command line parameter.\n"); + } + + modifiedgame = false; + + DEH_printf("W_Init: Init WADfiles.\n"); + D_AddFile(iwadfile); +#if ORIGCODE + numiwadlumps = numlumps; +#endif + + W_CheckCorrectIWAD(doom); + + // Now that we've loaded the IWAD, we can figure out what gamemission + // we're playing and which version of Vanilla Doom we need to emulate. + D_IdentifyVersion(); + InitGameVersion(); + +#if ORIGCODE + //! + // @category mod + // + // Disable automatic loading of Dehacked patches for certain + // IWAD files. + // + if (!M_ParmExists("-nodeh")) + { + // Some IWADs have dehacked patches that need to be loaded for + // them to be played properly. + LoadIwadDeh(); + } +#endif + + // Doom 3: BFG Edition includes modified versions of the classic + // IWADs which can be identified by an additional DMENUPIC lump. + // Furthermore, the M_GDHIGH lumps have been modified in a way that + // makes them incompatible to Vanilla Doom and the modified version + // of doom2.wad is missing the TITLEPIC lump. + // We specifically check for DMENUPIC here, before PWADs have been + // loaded which could probably include a lump of that name. + + if (W_CheckNumForName("dmenupic") >= 0) + { + printf("BFG Edition: Using workarounds as needed.\n"); + bfgedition = true; + + // BFG Edition changes the names of the secret levels to + // censor the Wolfenstein references. It also has an extra + // secret level (MAP33). In Vanilla Doom (meaning the DOS + // version), MAP33 overflows into the Plutonia level names + // array, so HUSTR_33 is actually PHUSTR_1. + + DEH_AddStringReplacement(HUSTR_31, "level 31: idkfa"); + DEH_AddStringReplacement(HUSTR_32, "level 32: keen"); + DEH_AddStringReplacement(PHUSTR_1, "level 33: betray"); + + // The BFG edition doesn't have the "low detail" menu option (fair + // enough). But bizarrely, it reuses the M_GDHIGH patch as a label + // for the options menu (says "Fullscreen:"). Why the perpetrators + // couldn't just add a new graphic lump and had to reuse this one, + // I don't know. + // + // The end result is that M_GDHIGH is too wide and causes the game + // to crash. As a workaround to get a minimum level of support for + // the BFG edition IWADs, use the "ON"/"OFF" graphics instead. + + DEH_AddStringReplacement("M_GDHIGH", "M_MSGON"); + DEH_AddStringReplacement("M_GDLOW", "M_MSGOFF"); + } + +#ifdef FEATURE_DEHACKED + // Load Dehacked patches specified on the command line with -deh. + // Note that there's a very careful and deliberate ordering to how + // Dehacked patches are loaded. The order we use is: + // 1. IWAD dehacked patches. + // 2. Command line dehacked patches specified with -deh. + // 3. PWAD dehacked patches in DEHACKED lumps. + DEH_ParseCommandLine(); +#endif + + // Load PWAD files. + modifiedgame = W_ParseCommandLine(); + + // Debug: +// W_PrintDirectory(); + + //! + // @arg + // @category demo + // @vanilla + // + // Play back the demo named demo.lmp. + // + + p = M_CheckParmWithArgs ("-playdemo", 1); + + if (!p) + { + //! + // @arg + // @category demo + // @vanilla + // + // Play back the demo named demo.lmp, determining the framerate + // of the screen. + // + p = M_CheckParmWithArgs("-timedemo", 1); + + } + + if (p) + { + // With Vanilla you have to specify the file without extension, + // but make that optional. + if (M_StringEndsWith(myargv[p + 1], ".lmp")) + { + M_StringCopy(file, myargv[p + 1], sizeof(file)); + } + else + { + DEH_snprintf(file, sizeof(file), "%s.lmp", myargv[p+1]); + } + + if (D_AddFile(file)) + { + M_StringCopy(demolumpname, lumpinfo[numlumps - 1].name, + sizeof(demolumpname)); + } + else + { + // If file failed to load, still continue trying to play + // the demo in the same way as Vanilla Doom. This makes + // tricks like "-playdemo demo1" possible. + + M_StringCopy(demolumpname, myargv[p + 1], sizeof(demolumpname)); + } + + printf("Playing demo %s.\n", file); + } + + I_AtExit((atexit_func_t) G_CheckDemoStatus, true); + + // Generate the WAD hash table. Speed things up a bit. + W_GenerateHashTable(); + + // Load DEHACKED lumps from WAD files - but only if we give the right + // command line parameter. + +#if ORIGCODE + //! + // @category mod + // + // Load Dehacked patches from DEHACKED lumps contained in one of the + // loaded PWAD files. + // + if (M_ParmExists("-dehlump")) + { + int i, loaded = 0; + + for (i = numiwadlumps; i < numlumps; ++i) + { + if (!strncmp(lumpinfo[i].name, "DEHACKED", 8)) + { + DEH_LoadLump(i, false, false); + loaded++; + } + } + + printf(" loaded %i DEHACKED lumps from PWAD files.\n", loaded); + } +#endif + + // Set the gamedescription string. This is only possible now that + // we've finished loading Dehacked patches. + D_SetGameDescription(); + +#ifdef _WIN32 + // In -cdrom mode, we write savegames to c:\doomdata as well as configs. + if (M_ParmExists("-cdrom")) + { + savegamedir = configdir; + } + else +#endif + { + savegamedir = M_GetSaveGameDir(D_SaveGameIWADName(gamemission)); + } + + // Check for -file in shareware + if (modifiedgame) + { + // These are the lumps that will be checked in IWAD, + // if any one is not present, execution will be aborted. + char name[23][8]= + { + "e2m1","e2m2","e2m3","e2m4","e2m5","e2m6","e2m7","e2m8","e2m9", + "e3m1","e3m3","e3m3","e3m4","e3m5","e3m6","e3m7","e3m8","e3m9", + "dphoof","bfgga0","heada1","cybra1","spida1d1" + }; + int i; + + if ( gamemode == shareware) + I_Error(DEH_String("\nYou cannot -file with the shareware " + "version. Register!")); + + // Check for fake IWAD with right name, + // but w/o all the lumps of the registered version. + if (gamemode == registered) + for (i = 0;i < 23; i++) + if (W_CheckNumForName(name[i])<0) + I_Error(DEH_String("\nThis is not the registered version.")); + } + + if (W_CheckNumForName("SS_START") >= 0 + || W_CheckNumForName("FF_END") >= 0) + { + I_PrintDivider(); + printf(" WARNING: The loaded WAD file contains modified sprites or\n" + " floor textures. You may want to use the '-merge' command\n" + " line option instead of '-file'.\n"); + } + + I_PrintStartupBanner(gamedescription); + PrintDehackedBanners(); + + // Freedoom's IWADs are Boom-compatible, which means they usually + // don't work in Vanilla (though FreeDM is okay). Show a warning + // message and give a link to the website. + if (W_CheckNumForName("FREEDOOM") >= 0 && W_CheckNumForName("FREEDM") < 0) + { + printf(" WARNING: You are playing using one of the Freedoom IWAD\n" + " files, which might not work in this port. See this page\n" + " for more information on how to play using Freedoom:\n" + " http://www.chocolate-doom.org/wiki/index.php/Freedoom\n"); + I_PrintDivider(); + } + + DEH_printf("I_Init: Setting up machine state.\n"); + I_CheckIsScreensaver(); + I_InitTimer(); + I_InitJoystick(); + I_InitSound(true); + I_InitMusic(); + +#ifdef FEATURE_MULTIPLAYER + printf ("NET_Init: Init network subsystem.\n"); + NET_Init (); +#endif + + // Initial netgame startup. Connect to server etc. + D_ConnectNetGame(); + + // get skill / episode / map from parms + startskill = sk_medium; + startepisode = 1; + startmap = 1; + autostart = false; + + //! + // @arg + // @vanilla + // + // Set the game skill, 1-5 (1: easiest, 5: hardest). A skill of + // 0 disables all monsters. + // + + p = M_CheckParmWithArgs("-skill", 1); + + if (p) + { + startskill = myargv[p+1][0]-'1'; + autostart = true; + } + + //! + // @arg + // @vanilla + // + // Start playing on episode n (1-4) + // + + p = M_CheckParmWithArgs("-episode", 1); + + if (p) + { + startepisode = myargv[p+1][0]-'0'; + startmap = 1; + autostart = true; + } + + timelimit = 0; + + //! + // @arg + // @category net + // @vanilla + // + // For multiplayer games: exit each level after n minutes. + // + + p = M_CheckParmWithArgs("-timer", 1); + + if (p) + { + timelimit = atoi(myargv[p+1]); + } + + //! + // @category net + // @vanilla + // + // Austin Virtual Gaming: end levels after 20 minutes. + // + + p = M_CheckParm ("-avg"); + + if (p) + { + timelimit = 20; + } + + //! + // @arg [ | ] + // @vanilla + // + // Start a game immediately, warping to ExMy (Doom 1) or MAPxy + // (Doom 2) + // + + p = M_CheckParmWithArgs("-warp", 1); + + if (p) + { + if (gamemode == commercial) + startmap = atoi (myargv[p+1]); + else + { + startepisode = myargv[p+1][0]-'0'; + + if (p + 2 < myargc) + { + startmap = myargv[p+2][0]-'0'; + } + else + { + startmap = 1; + } + } + autostart = true; + } + + // Undocumented: + // Invoked by setup to test the controls. + + p = M_CheckParm("-testcontrols"); + + if (p > 0) + { + startepisode = 1; + startmap = 1; + autostart = true; + testcontrols = true; + } + + // Check for load game parameter + // We do this here and save the slot number, so that the network code + // can override it or send the load slot to other players. + + //! + // @arg + // @vanilla + // + // Load the game in slot s. + // + + p = M_CheckParmWithArgs("-loadgame", 1); + + if (p) + { + startloadgame = atoi(myargv[p+1]); + } + else + { + // Not loading a game + startloadgame = -1; + } + + DEH_printf("M_Init: Init miscellaneous info.\n"); + M_Init (); + + DEH_printf("R_Init: Init DOOM refresh daemon - "); + R_Init (); + + DEH_printf("\nP_Init: Init Playloop state.\n"); + P_Init (); + + DEH_printf("S_Init: Setting up sound.\n"); + S_Init (sfxVolume * 8, musicVolume * 8); + + DEH_printf("D_CheckNetGame: Checking network game status.\n"); + D_CheckNetGame (); + + PrintGameVersion(); + + DEH_printf("HU_Init: Setting up heads up display.\n"); + HU_Init (); + + DEH_printf("ST_Init: Init status bar.\n"); + ST_Init (); + + // If Doom II without a MAP01 lump, this is a store demo. + // Moved this here so that MAP01 isn't constantly looked up + // in the main loop. + + if (gamemode == commercial && W_CheckNumForName("map01") < 0) + storedemo = true; + + if (M_CheckParmWithArgs("-statdump", 1)) + { + I_AtExit(StatDump, true); + DEH_printf("External statistics registered.\n"); + } + + //! + // @arg + // @category demo + // @vanilla + // + // Record a demo named x.lmp. + // + + p = M_CheckParmWithArgs("-record", 1); + + if (p) + { + G_RecordDemo (myargv[p+1]); + autostart = true; + } + + p = M_CheckParmWithArgs("-playdemo", 1); + if (p) + { + singledemo = true; // quit after one demo + G_DeferedPlayDemo (demolumpname); + D_DoomLoop (); + return; + } + + p = M_CheckParmWithArgs("-timedemo", 1); + if (p) + { + G_TimeDemo (demolumpname); + D_DoomLoop (); + return; + } + + if (startloadgame >= 0) + { + M_StringCopy(file, P_SaveGameFile(startloadgame), sizeof(file)); + G_LoadGame(file); + } + + if (gameaction != ga_loadgame ) + { + if (autostart || netgame) + G_InitNew (startskill, startepisode, startmap); + else + D_StartTitle (); // start up intro loop + } + + D_DoomLoop (); +} + diff --git a/firmware_p4/components/Applications/doom/d_main.h b/firmware_p4/components/Applications/doom/d_main.h new file mode 100644 index 000000000..0fe9547b3 --- /dev/null +++ b/firmware_p4/components/Applications/doom/d_main.h @@ -0,0 +1,50 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// System specific interface stuff. +// + + +#ifndef __D_MAIN__ +#define __D_MAIN__ + +#include "doomdef.h" + + + + +// Read events from all input devices + +void D_ProcessEvents (void); + + +// +// BASE LEVEL +// +void D_PageTicker (void); +void D_PageDrawer (void); +void D_AdvanceDemo (void); +void D_DoAdvanceDemo (void); +void D_StartTitle (void); + +// +// GLOBAL VARIABLES +// + +extern gameaction_t gameaction; + + +#endif + diff --git a/firmware_p4/components/Applications/doom/d_mode.c b/firmware_p4/components/Applications/doom/d_mode.c new file mode 100644 index 000000000..afd84acb3 --- /dev/null +++ b/firmware_p4/components/Applications/doom/d_mode.c @@ -0,0 +1,209 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// +// DESCRIPTION: +// Functions and definitions relating to the game type and operational +// mode. +// + +#include "doomtype.h" +#include "d_mode.h" + +// Valid game mode/mission combinations, with the number of +// episodes/maps for each. + +static struct +{ + GameMission_t mission; + GameMode_t mode; + int episode; + int map; +} valid_modes[] = { + { pack_chex, shareware, 1, 5 }, + { doom, shareware, 1, 9 }, + { doom, registered, 3, 9 }, + { doom, retail, 4, 9 }, + { doom2, commercial, 1, 32 }, + { pack_tnt, commercial, 1, 32 }, + { pack_plut, commercial, 1, 32 }, + { pack_hacx, commercial, 1, 32 }, + { heretic, shareware, 1, 9 }, + { heretic, registered, 3, 9 }, + { heretic, retail, 5, 9 }, + { hexen, commercial, 1, 60 }, + { strife, commercial, 1, 34 }, +}; + +// Check that a gamemode+gamemission received over the network is valid. + +boolean D_ValidGameMode(GameMission_t mission, GameMode_t mode) +{ + int i; + + for (i=0; i= 1 && map <= 3; + } + else if (mode == registered && episode == 4) + { + return map == 1; + } + } + + // Find the table entry for this mission/mode combination. + + for (i=0; i= 1 && episode <= valid_modes[i].episode + && map >= 1 && map <= valid_modes[i].map; + } + } + + // Unknown mode/mission combination + + return false; +} + +// Get the number of valid episodes for the specified mission/mode. + +int D_GetNumEpisodes(GameMission_t mission, GameMode_t mode) +{ + int episode; + + episode = 1; + + while (D_ValidEpisodeMap(mission, mode, episode, 1)) + { + ++episode; + } + + return episode - 1; +} + +// Table of valid versions + +static struct { + GameMission_t mission; + GameVersion_t version; +} valid_versions[] = { + { doom, exe_doom_1_9 }, + { doom, exe_hacx }, + { doom, exe_ultimate }, + { doom, exe_final }, + { doom, exe_final2 }, + { doom, exe_chex }, + { heretic, exe_heretic_1_3 }, + { hexen, exe_hexen_1_1 }, + { strife, exe_strife_1_2 }, + { strife, exe_strife_1_31 }, +}; + +boolean D_ValidGameVersion(GameMission_t mission, GameVersion_t version) +{ + int i; + + // All Doom variants can use the Doom versions. + + if (mission == doom2 || mission == pack_plut || mission == pack_tnt + || mission == pack_hacx || mission == pack_chex) + { + mission = doom; + } + + for (i=0; i + +#include "doomfeatures.h" + +#include "d_main.h" +#include "m_argv.h" +#include "m_menu.h" +#include "m_misc.h" +#include "i_system.h" +#include "i_timer.h" +#include "i_video.h" +#include "g_game.h" +#include "doomdef.h" +#include "doomstat.h" +#include "w_checksum.h" +#include "w_wad.h" + +#include "deh_main.h" + +#include "d_loop.h" + +ticcmd_t *netcmds; + +// Called when a player leaves the game + +static void PlayerQuitGame(player_t *player) +{ + static char exitmsg[80]; + unsigned int player_num; + + player_num = player - players; + + // Do this the same way as Vanilla Doom does, to allow dehacked + // replacements of this message + + M_StringCopy(exitmsg, DEH_String("Player 1 left the game"), + sizeof(exitmsg)); + + exitmsg[7] += player_num; + + playeringame[player_num] = false; + players[consoleplayer].message = exitmsg; + + // TODO: check if it is sensible to do this: + + if (demorecording) + { + G_CheckDemoStatus (); + } +} + +static void RunTic(ticcmd_t *cmds, boolean *ingame) +{ + extern boolean advancedemo; + unsigned int i; + + // Check for player quits. + + for (i = 0; i < MAXPLAYERS; ++i) + { + if (!demoplayback && playeringame[i] && !ingame[i]) + { + PlayerQuitGame(&players[i]); + } + } + + netcmds = cmds; + + // check that there are players in the game. if not, we cannot + // run a tic. + + if (advancedemo) + D_DoAdvanceDemo (); + + G_Ticker (); +} + +static loop_interface_t doom_loop_interface = { + D_ProcessEvents, + G_BuildTiccmd, + RunTic, + M_Ticker +}; + + +// Load game settings from the specified structure and +// set global variables. + +static void LoadGameSettings(net_gamesettings_t *settings) +{ + unsigned int i; + + deathmatch = settings->deathmatch; + startepisode = settings->episode; + startmap = settings->map; + startskill = settings->skill; + startloadgame = settings->loadgame; + lowres_turn = settings->lowres_turn; + nomonsters = settings->nomonsters; + fastparm = settings->fast_monsters; + respawnparm = settings->respawn_monsters; + timelimit = settings->timelimit; + consoleplayer = settings->consoleplayer; + + if (lowres_turn) + { + printf("NOTE: Turning resolution is reduced; this is probably " + "because there is a client recording a Vanilla demo.\n"); + } + + for (i = 0; i < MAXPLAYERS; ++i) + { + playeringame[i] = i < settings->num_players; + } +} + +// Save the game settings from global variables to the specified +// game settings structure. + +static void SaveGameSettings(net_gamesettings_t *settings) +{ + // Fill in game settings structure with appropriate parameters + // for the new game + + settings->deathmatch = deathmatch; + settings->episode = startepisode; + settings->map = startmap; + settings->skill = startskill; + settings->loadgame = startloadgame; + settings->gameversion = gameversion; + settings->nomonsters = nomonsters; + settings->fast_monsters = fastparm; + settings->respawn_monsters = respawnparm; + settings->timelimit = timelimit; + + settings->lowres_turn = M_CheckParm("-record") > 0 + && M_CheckParm("-longtics") == 0; +} + +static void InitConnectData(net_connect_data_t *connect_data) +{ + connect_data->max_players = MAXPLAYERS; + connect_data->drone = false; + + //! + // @category net + // + // Run as the left screen in three screen mode. + // + + if (M_CheckParm("-left") > 0) + { + viewangleoffset = ANG90; + connect_data->drone = true; + } + + //! + // @category net + // + // Run as the right screen in three screen mode. + // + + if (M_CheckParm("-right") > 0) + { + viewangleoffset = ANG270; + connect_data->drone = true; + } + + // + // Connect data + // + + // Game type fields: + + connect_data->gamemode = gamemode; + connect_data->gamemission = gamemission; + + // Are we recording a demo? Possibly set lowres turn mode + + connect_data->lowres_turn = M_CheckParm("-record") > 0 + && M_CheckParm("-longtics") == 0; + + // Read checksums of our WAD directory and dehacked information + + W_Checksum(connect_data->wad_sha1sum); + +#if ORIGCODE + DEH_Checksum(connect_data->deh_sha1sum); +#endif + + // Are we playing with the Freedoom IWAD? + + connect_data->is_freedoom = W_CheckNumForName("FREEDOOM") >= 0; +} + +void D_ConnectNetGame(void) +{ + net_connect_data_t connect_data; + + InitConnectData(&connect_data); + netgame = D_InitNetGame(&connect_data); + + //! + // @category net + // + // Start the game playing as though in a netgame with a single + // player. This can also be used to play back single player netgame + // demos. + // + + if (M_CheckParm("-solo-net") > 0) + { + netgame = true; + } +} + +// +// D_CheckNetGame +// Works out player numbers among the net participants +// +void D_CheckNetGame (void) +{ + net_gamesettings_t settings; + + if (netgame) + { + autostart = true; + } + + D_RegisterLoopCallbacks(&doom_loop_interface); + + SaveGameSettings(&settings); + D_StartNetGame(&settings, NULL); + LoadGameSettings(&settings); + + DEH_printf("startskill %i deathmatch: %i startmap: %i startepisode: %i\n", + startskill, deathmatch, startmap, startepisode); + + DEH_printf("player %i of %i (%i nodes)\n", + consoleplayer+1, settings.num_players, settings.num_players); + + // Show players here; the server might have specified a time limit + + if (timelimit > 0 && deathmatch) + { + // Gross hack to work like Vanilla: + + if (timelimit == 20 && M_CheckParm("-avg")) + { + DEH_printf("Austin Virtual Gaming: Levels will end " + "after 20 minutes\n"); + } + else + { + DEH_printf("Levels will end after %d minute", timelimit); + if (timelimit > 1) + printf("s"); + printf(".\n"); + } + } +} + diff --git a/firmware_p4/components/Applications/doom/d_player.h b/firmware_p4/components/Applications/doom/d_player.h new file mode 100644 index 000000000..a72c2f5f1 --- /dev/null +++ b/firmware_p4/components/Applications/doom/d_player.h @@ -0,0 +1,209 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// +// + + +#ifndef __D_PLAYER__ +#define __D_PLAYER__ + + +// The player data structure depends on a number +// of other structs: items (internal inventory), +// animation states (closely tied to the sprites +// used to represent them, unfortunately). +#include "d_items.h" +#include "p_pspr.h" + +// In addition, the player is just a special +// case of the generic moving object/actor. +#include "p_mobj.h" + +// Finally, for odd reasons, the player input +// is buffered within the player data struct, +// as commands per game tick. +#include "d_ticcmd.h" + +#include "net_defs.h" + + + + +// +// Player states. +// +typedef enum +{ + // Playing or camping. + PST_LIVE, + // Dead on the ground, view follows killer. + PST_DEAD, + // Ready to restart/respawn??? + PST_REBORN + +} playerstate_t; + + +// +// Player internal flags, for cheats and debug. +// +typedef enum +{ + // No clipping, walk through barriers. + CF_NOCLIP = 1, + // No damage, no health loss. + CF_GODMODE = 2, + // Not really a cheat, just a debug aid. + CF_NOMOMENTUM = 4 + +} cheat_t; + + +// +// Extended player object info: player_t +// +typedef struct player_s +{ + mobj_t* mo; + playerstate_t playerstate; + ticcmd_t cmd; + + // Determine POV, + // including viewpoint bobbing during movement. + // Focal origin above r.z + fixed_t viewz; + // Base height above floor for viewz. + fixed_t viewheight; + // Bob/squat speed. + fixed_t deltaviewheight; + // bounded/scaled total momentum. + fixed_t bob; + + // This is only used between levels, + // mo->health is used during levels. + int health; + int armorpoints; + // Armor type is 0-2. + int armortype; + + // Power ups. invinc and invis are tic counters. + int powers[NUMPOWERS]; + boolean cards[NUMCARDS]; + boolean backpack; + + // Frags, kills of other players. + int frags[MAXPLAYERS]; + weapontype_t readyweapon; + + // Is wp_nochange if not changing. + weapontype_t pendingweapon; + + boolean weaponowned[NUMWEAPONS]; + int ammo[NUMAMMO]; + int maxammo[NUMAMMO]; + + // True if button down last tic. + int attackdown; + int usedown; + + // Bit flags, for cheats and debug. + // See cheat_t, above. + int cheats; + + // Refired shots are less accurate. + int refire; + + // For intermission stats. + int killcount; + int itemcount; + int secretcount; + + // Hint messages. + char* message; + + // For screen flashing (red or bright). + int damagecount; + int bonuscount; + + // Who did damage (NULL for floors/ceilings). + mobj_t* attacker; + + // So gun flashes light up areas. + int extralight; + + // Current PLAYPAL, ??? + // can be set to REDCOLORMAP for pain, etc. + int fixedcolormap; + + // Player skin colorshift, + // 0-3 for which color to draw player. + int colormap; + + // Overlay view sprites (gun, etc). + pspdef_t psprites[NUMPSPRITES]; + + // True if secret level has been done. + boolean didsecret; + +} player_t; + + +// +// INTERMISSION +// Structure passed e.g. to WI_Start(wb) +// +typedef struct +{ + boolean in; // whether the player is in game + + // Player stats, kills, collected items etc. + int skills; + int sitems; + int ssecret; + int stime; + int frags[4]; + int score; // current score on entry, modified on return + +} wbplayerstruct_t; + +typedef struct +{ + int epsd; // episode # (0-2) + + // if true, splash the secret level + boolean didsecret; + + // previous and next levels, origin 0 + int last; + int next; + + int maxkills; + int maxitems; + int maxsecret; + int maxfrags; + + // the par time + int partime; + + // index of this player in game + int pnum; + + wbplayerstruct_t plyr[MAXPLAYERS]; + +} wbstartstruct_t; + + +#endif diff --git a/firmware_p4/components/Applications/doom/d_textur.h b/firmware_p4/components/Applications/doom/d_textur.h new file mode 100644 index 000000000..1afe040eb --- /dev/null +++ b/firmware_p4/components/Applications/doom/d_textur.h @@ -0,0 +1,43 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Typedefs related to to textures etc., +// isolated here to make it easier separating modules. +// + + +#ifndef __D_TEXTUR__ +#define __D_TEXTUR__ + +#include "doomtype.h" + + + + +// +// Flats? +// +// a pic is an unmasked block of pixels +typedef struct +{ + byte width; + byte height; + byte data; +} pic_t; + + + + +#endif diff --git a/firmware_p4/components/Applications/doom/d_think.h b/firmware_p4/components/Applications/doom/d_think.h new file mode 100644 index 000000000..0966ad960 --- /dev/null +++ b/firmware_p4/components/Applications/doom/d_think.h @@ -0,0 +1,68 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// MapObj data. Map Objects or mobjs are actors, entities, +// thinker, take-your-pick... anything that moves, acts, or +// suffers state changes of more or less violent nature. +// + + +#ifndef __D_THINK__ +#define __D_THINK__ + + + + + +// +// Experimental stuff. +// To compile this as "ANSI C with classes" +// we will need to handle the various +// action functions cleanly. +// +typedef void (*actionf_v)(); +typedef void (*actionf_p1)( void* ); +typedef void (*actionf_p2)( void*, void* ); + +typedef union +{ + actionf_v acv; + actionf_p1 acp1; + actionf_p2 acp2; + +} actionf_t; + + + + + +// Historically, "think_t" is yet another +// function pointer to a routine to handle +// an actor. +typedef actionf_t think_t; + + +// Doubly linked list of actors. +typedef struct thinker_s +{ + struct thinker_s* prev; + struct thinker_s* next; + think_t function; + +} thinker_t; + + + +#endif diff --git a/firmware_p4/components/Applications/doom/d_ticcmd.h b/firmware_p4/components/Applications/doom/d_ticcmd.h new file mode 100644 index 000000000..daf0da318 --- /dev/null +++ b/firmware_p4/components/Applications/doom/d_ticcmd.h @@ -0,0 +1,56 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// System specific interface stuff. +// + + +#ifndef __D_TICCMD__ +#define __D_TICCMD__ + +#include "doomtype.h" + + +// The data sampled per tick (single player) +// and transmitted to other peers (multiplayer). +// Mainly movements/button commands per game tick, +// plus a checksum for internal state consistency. + +typedef struct +{ + signed char forwardmove; // *2048 for move + signed char sidemove; // *2048 for move + short angleturn; // <<16 for angle delta + byte chatchar; + byte buttons; + // villsa [STRIFE] according to the asm, + // consistancy is a short, not a byte + byte consistancy; // checks for net game + + // villsa - Strife specific: + + byte buttons2; + int inventory; + + // Heretic/Hexen specific: + + byte lookfly; // look/fly up/down/centering + byte arti; // artitype_t to use +} ticcmd_t; + + + +#endif diff --git a/firmware_p4/components/Applications/doom/deh_main.h b/firmware_p4/components/Applications/doom/deh_main.h new file mode 100644 index 000000000..10ac2360e --- /dev/null +++ b/firmware_p4/components/Applications/doom/deh_main.h @@ -0,0 +1,48 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// +// Dehacked entrypoint and common code +// + +#ifndef DEH_MAIN_H +#define DEH_MAIN_H + +#include "doomtype.h" +#include "doomfeatures.h" +#include "deh_str.h" +#include "sha1.h" + +// These are the limits that dehacked uses (from dheinit.h in the dehacked +// source). If these limits are exceeded, it does not generate an error, but +// a warning is displayed. + +#define DEH_VANILLA_NUMSTATES 966 +#define DEH_VANILLA_NUMSFX 107 + +void DEH_ParseCommandLine(void); +int DEH_LoadFile(char *filename); +int DEH_LoadLump(int lumpnum, boolean allow_long, boolean allow_error); +int DEH_LoadLumpByName(char *name, boolean allow_long, boolean allow_error); + +boolean DEH_ParseAssignment(char *line, char **variable_name, char **value); + +void DEH_Checksum(sha1_digest_t digest); + +extern boolean deh_allow_extended_strings; +extern boolean deh_allow_long_strings; +extern boolean deh_allow_long_cheats; +extern boolean deh_apply_cheats; + +#endif /* #ifndef DEH_MAIN_H */ + diff --git a/firmware_p4/components/Applications/doom/deh_misc.h b/firmware_p4/components/Applications/doom/deh_misc.h new file mode 100644 index 000000000..319a1451a --- /dev/null +++ b/firmware_p4/components/Applications/doom/deh_misc.h @@ -0,0 +1,83 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// +// Parses "Misc" sections in dehacked files +// + +#ifndef DEH_MISC_H +#define DEH_MISC_H + +#include "doomfeatures.h" + +#define DEH_DEFAULT_INITIAL_HEALTH 100 +#define DEH_DEFAULT_INITIAL_BULLETS 50 +#define DEH_DEFAULT_MAX_HEALTH 200 +#define DEH_DEFAULT_MAX_ARMOR 200 +#define DEH_DEFAULT_GREEN_ARMOR_CLASS 1 +#define DEH_DEFAULT_BLUE_ARMOR_CLASS 2 +#define DEH_DEFAULT_MAX_SOULSPHERE 200 +#define DEH_DEFAULT_SOULSPHERE_HEALTH 100 +#define DEH_DEFAULT_MEGASPHERE_HEALTH 200 +#define DEH_DEFAULT_GOD_MODE_HEALTH 100 +#define DEH_DEFAULT_IDFA_ARMOR 200 +#define DEH_DEFAULT_IDFA_ARMOR_CLASS 2 +#define DEH_DEFAULT_IDKFA_ARMOR 200 +#define DEH_DEFAULT_IDKFA_ARMOR_CLASS 2 +#define DEH_DEFAULT_BFG_CELLS_PER_SHOT 40 +#define DEH_DEFAULT_SPECIES_INFIGHTING 0 + +#ifdef FEATURE_DEHACKED + +extern int deh_initial_health; +extern int deh_initial_bullets; +extern int deh_max_health; +extern int deh_max_armor; +extern int deh_green_armor_class; +extern int deh_blue_armor_class; +extern int deh_max_soulsphere; +extern int deh_soulsphere_health; +extern int deh_megasphere_health; +extern int deh_god_mode_health; +extern int deh_idfa_armor; +extern int deh_idfa_armor_class; +extern int deh_idkfa_armor; +extern int deh_idkfa_armor_class; +extern int deh_bfg_cells_per_shot; +extern int deh_species_infighting; + +#else + +// If dehacked is disabled, hard coded values + +#define deh_initial_health DEH_DEFAULT_INITIAL_HEALTH +#define deh_initial_bullets DEH_DEFAULT_INITIAL_BULLETS +#define deh_max_health DEH_DEFAULT_MAX_HEALTH +#define deh_max_armor DEH_DEFAULT_MAX_ARMOR +#define deh_green_armor_class DEH_DEFAULT_GREEN_ARMOR_CLASS +#define deh_blue_armor_class DEH_DEFAULT_BLUE_ARMOR_CLASS +#define deh_max_soulsphere DEH_DEFAULT_MAX_SOULSPHERE +#define deh_soulsphere_health DEH_DEFAULT_SOULSPHERE_HEALTH +#define deh_megasphere_health DEH_DEFAULT_MEGASPHERE_HEALTH +#define deh_god_mode_health DEH_DEFAULT_GOD_MODE_HEALTH +#define deh_idfa_armor DEH_DEFAULT_IDFA_ARMOR +#define deh_idfa_armor_class DEH_DEFAULT_IDFA_ARMOR_CLASS +#define deh_idkfa_armor DEH_DEFAULT_IDKFA_ARMOR +#define deh_idkfa_armor_class DEH_DEFAULT_IDKFA_ARMOR_CLASS +#define deh_bfg_cells_per_shot DEH_DEFAULT_BFG_CELLS_PER_SHOT +#define deh_species_infighting DEH_DEFAULT_SPECIES_INFIGHTING + +#endif + +#endif /* #ifndef DEH_MISC_H */ + diff --git a/firmware_p4/components/Applications/doom/deh_str.h b/firmware_p4/components/Applications/doom/deh_str.h new file mode 100644 index 000000000..cdecaf0e5 --- /dev/null +++ b/firmware_p4/components/Applications/doom/deh_str.h @@ -0,0 +1,47 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// +// Dehacked string replacements +// + +#ifndef DEH_STR_H +#define DEH_STR_H + +#include + +#include "doomfeatures.h" + +// Used to do dehacked text substitutions throughout the program + +#ifdef FEATURE_DEHACKED + +char *DEH_String(char *s); +void DEH_printf(char *fmt, ...); +void DEH_fprintf(FILE *fstream, char *fmt, ...); +void DEH_snprintf(char *buffer, size_t len, char *fmt, ...); +void DEH_AddStringReplacement(char *from_text, char *to_text); + + +#else + +#define DEH_String(x) (x) +#define DEH_printf printf +#define DEH_fprintf fprintf +#define DEH_snprintf snprintf +#define DEH_AddStringReplacement(x, y) + +#endif + +#endif /* #ifndef DEH_STR_H */ + diff --git a/firmware_p4/components/Applications/doom/doom.h b/firmware_p4/components/Applications/doom/doom.h new file mode 100644 index 000000000..3c0d0be74 --- /dev/null +++ b/firmware_p4/components/Applications/doom/doom.h @@ -0,0 +1,42 @@ +/* + * doom.h + * + * Created on: 18.02.2015 + * Author: Florian + */ + + +#ifndef SRC_CHOCDOOM_DOOM_H_ +#define SRC_CHOCDOOM_DOOM_H_ + +/*---------------------------------------------------------------------* + * additional includes * + *---------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------* + * global definitions * + *---------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------* + * type declarations * + *---------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------* + * function prototypes * + *---------------------------------------------------------------------*/ + +void D_DoomMain (void); + +/*---------------------------------------------------------------------* + * global data * + *---------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------* + * inline functions and function-like macros * + *---------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------* + * eof * + *---------------------------------------------------------------------*/ + +#endif /* SRC_CHOCDOOM_DOOM_H_ */ diff --git a/firmware_p4/components/Applications/doom/doom_sound_highboy.c b/firmware_p4/components/Applications/doom/doom_sound_highboy.c new file mode 100644 index 000000000..fd1c52572 --- /dev/null +++ b/firmware_p4/components/Applications/doom/doom_sound_highboy.c @@ -0,0 +1,158 @@ +// HighBoy DOOM sound backend (replaces the SDL_mixer module). Provides the +// DG_sound_module (SFX) + DG_music_module (no-op) that i_sound.c references when +// FEATURE_SOUND is defined. SFX are DMX lumps (8-bit unsigned PCM); a mixer task +// sums the active channels and streams mono 16-bit PCM to the NS4168 via I2S. +// Music (MIDI/MUS) is not synthesised — the music module is a silent stub. + +#include +#include +#include + +#include "doomtype.h" +#include "i_sound.h" +#include "w_wad.h" +#include "z_zone.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/idf_additions.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "audio_i2s.h" + +#define ARRLEN(a) ((int)(sizeof(a) / sizeof((a)[0]))) +#define NUM_CH 16 // matches DOOM's max sfx channels +#define OUT_RATE 11025 // DOOM sfx native rate; per-channel resample handles others + +typedef struct { + const uint8_t *pcm; // 8-bit unsigned PCM (into the cached WAD lump) + uint32_t len; // sample count + volatile uint32_t pos; // 16.16 fixed-point read position + uint32_t step; // 16.16 increment = src_rate / OUT_RATE + volatile int vol; // 0..127 + volatile bool active; +} sfxchan_t; + +static sfxchan_t s_ch[NUM_CH]; +static bool s_use_prefix; +static bool s_started; + +// Config vars that i_sound.c's I_BindSoundVariables() references (were defined +// in the excluded i_sdlsound.c). We don't use libsamplerate, but must provide them. +int use_libsamplerate = 0; +float libsamplerate_scale = 0.65f; + +static snddevice_t sfx_devices[] = { + SNDDEVICE_SB, SNDDEVICE_PAS, SNDDEVICE_GUS, + SNDDEVICE_WAVEBLASTER, SNDDEVICE_SOUNDCANVAS, SNDDEVICE_AWE32, +}; + +static void mixer_task(void *arg) { + (void)arg; + if (audio_i2s_stream_start(OUT_RATE) != ESP_OK) { + ESP_LOGE("DOOM_SND", "stream start failed — no sound"); + for (;;) vTaskDelay(pdMS_TO_TICKS(1000)); + } + const int N = 512; + int16_t *out = heap_caps_malloc(N * sizeof(int16_t), MALLOC_CAP_SPIRAM); + if (!out) { for (;;) vTaskDelay(pdMS_TO_TICKS(1000)); } + for (;;) { + for (int i = 0; i < N; i++) { + int acc = 0; + for (int c = 0; c < NUM_CH; c++) { + sfxchan_t *ch = &s_ch[c]; + if (!ch->active) continue; + uint32_t idx = ch->pos >> 16; + if (idx >= ch->len) { ch->active = false; continue; } + int s = (int)ch->pcm[idx] - 128; // 8-bit unsigned -> signed + acc += s * ch->vol; // vol 0..127 + ch->pos += ch->step; + } + if (acc > 32767) acc = 32767; + else if (acc < -32768) acc = -32768; + out[i] = (int16_t)acc; + } + audio_i2s_stream_write(out, N); + } +} + +static boolean I_HB_InitSound(boolean use_sfx_prefix) { + s_use_prefix = use_sfx_prefix; + memset(s_ch, 0, sizeof(s_ch)); + if (!s_started) { + xTaskCreatePinnedToCoreWithCaps(mixer_task, "doom_snd", 4096, NULL, 6, NULL, 0, + MALLOC_CAP_SPIRAM); + s_started = true; + } + return true; +} +static void I_HB_ShutdownSound(void) {} + +static int I_HB_GetSfxLumpNum(sfxinfo_t *sfx) { + char nm[16]; + if (s_use_prefix) snprintf(nm, sizeof(nm), "ds%s", sfx->name); + else snprintf(nm, sizeof(nm), "%s", sfx->name); + return W_CheckNumForName(nm); +} + +static int I_HB_StartSound(sfxinfo_t *sfx, int channel, int vol, int sep) { + (void)sep; + if (channel < 0 || channel >= NUM_CH) return channel; + int lump = sfx->lumpnum; + if (lump < 0) lump = I_HB_GetSfxLumpNum(sfx); + if (lump < 0) return channel; + const uint8_t *data = W_CacheLumpNum(lump, PU_STATIC); + int lumplen = W_LumpLength(lump); + if (!data || lumplen < 8) return channel; + uint16_t rate = (uint16_t)(data[2] | (data[3] << 8)); + uint32_t plen = (uint32_t)data[4] | ((uint32_t)data[5] << 8) | + ((uint32_t)data[6] << 16) | ((uint32_t)data[7] << 24); + if (rate == 0) rate = OUT_RATE; + if (plen + 8 > (uint32_t)lumplen) plen = (uint32_t)lumplen - 8; + + s_ch[channel].active = false; // retire any old sound first + s_ch[channel].pcm = data + 8; // skip 8-byte DMX header + s_ch[channel].len = plen; + s_ch[channel].pos = 0; + s_ch[channel].step = (uint32_t)(((uint64_t)rate << 16) / OUT_RATE); + s_ch[channel].vol = vol; + s_ch[channel].active = true; + return channel; +} +static void I_HB_StopSound(int ch) { if (ch >= 0 && ch < NUM_CH) s_ch[ch].active = false; } +static boolean I_HB_SoundIsPlaying(int ch) { return (ch >= 0 && ch < NUM_CH) && s_ch[ch].active; } +static void I_HB_UpdateSoundParams(int ch, int vol, int sep) { + (void)sep; + if (ch >= 0 && ch < NUM_CH) s_ch[ch].vol = vol; +} +static void I_HB_Update(void) {} +static void I_HB_CacheSounds(sfxinfo_t *sounds, int num) { (void)sounds; (void)num; } + +sound_module_t DG_sound_module = { + sfx_devices, ARRLEN(sfx_devices), + I_HB_InitSound, I_HB_ShutdownSound, I_HB_GetSfxLumpNum, I_HB_Update, + I_HB_UpdateSoundParams, I_HB_StartSound, I_HB_StopSound, I_HB_SoundIsPlaying, + I_HB_CacheSounds, +}; + +// --- music: silent stub ----------------------------------------------------- +static snddevice_t mus_devices[] = { + SNDDEVICE_GENMIDI, SNDDEVICE_ADLIB, SNDDEVICE_SB, SNDDEVICE_PAS, SNDDEVICE_GUS, +}; +static boolean M_Init(void) { return true; } +static void M_Shutdown(void) {} +static void M_SetVol(int v) { (void)v; } +static void M_Pause(void) {} +static void M_Resume(void) {} +static void *M_Register(void *d, int l) { (void)d; (void)l; return NULL; } +static void M_Unregister(void *h) { (void)h; } +static void M_Play(void *h, boolean loop) { (void)h; (void)loop; } +static void M_Stop(void) {} +static boolean M_IsPlaying(void) { return false; } +static void M_Poll(void) {} + +music_module_t DG_music_module = { + mus_devices, ARRLEN(mus_devices), + M_Init, M_Shutdown, M_SetVol, M_Pause, M_Resume, + M_Register, M_Unregister, M_Play, M_Stop, M_IsPlaying, M_Poll, +}; diff --git a/firmware_p4/components/Applications/doom/doomdata.h b/firmware_p4/components/Applications/doom/doomdata.h new file mode 100644 index 000000000..46b3e11b6 --- /dev/null +++ b/firmware_p4/components/Applications/doom/doomdata.h @@ -0,0 +1,213 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// all external data is defined here +// most of the data is loaded into different structures at run time +// some internal structures shared by many modules are here +// + +#ifndef __DOOMDATA__ +#define __DOOMDATA__ + +// The most basic types we use, portability. +#include "doomtype.h" + +// Some global defines, that configure the game. +#include "doomdef.h" + + + +// +// Map level types. +// The following data structures define the persistent format +// used in the lumps of the WAD files. +// + +// Lump order in a map WAD: each map needs a couple of lumps +// to provide a complete scene geometry description. +enum +{ + ML_LABEL, // A separator, name, ExMx or MAPxx + ML_THINGS, // Monsters, items.. + ML_LINEDEFS, // LineDefs, from editing + ML_SIDEDEFS, // SideDefs, from editing + ML_VERTEXES, // Vertices, edited and BSP splits generated + ML_SEGS, // LineSegs, from LineDefs split by BSP + ML_SSECTORS, // SubSectors, list of LineSegs + ML_NODES, // BSP nodes + ML_SECTORS, // Sectors, from editing + ML_REJECT, // LUT, sector-sector visibility + ML_BLOCKMAP // LUT, motion clipping, walls/grid element +}; + + +// A single Vertex. +typedef struct +{ + short x; + short y; +} PACKEDATTR mapvertex_t; + + +// A SideDef, defining the visual appearance of a wall, +// by setting textures and offsets. +typedef struct +{ + short textureoffset; + short rowoffset; + char toptexture[8]; + char bottomtexture[8]; + char midtexture[8]; + // Front sector, towards viewer. + short sector; +} PACKEDATTR mapsidedef_t; + + + +// A LineDef, as used for editing, and as input +// to the BSP builder. +typedef struct +{ + short v1; + short v2; + short flags; + short special; + short tag; + // sidenum[1] will be -1 if one sided + short sidenum[2]; +} PACKEDATTR maplinedef_t; + + +// +// LineDef attributes. +// + +// Solid, is an obstacle. +#define ML_BLOCKING 1 + +// Blocks monsters only. +#define ML_BLOCKMONSTERS 2 + +// Backside will not be present at all +// if not two sided. +#define ML_TWOSIDED 4 + +// If a texture is pegged, the texture will have +// the end exposed to air held constant at the +// top or bottom of the texture (stairs or pulled +// down things) and will move with a height change +// of one of the neighbor sectors. +// Unpegged textures allways have the first row of +// the texture at the top pixel of the line for both +// top and bottom textures (use next to windows). + +// upper texture unpegged +#define ML_DONTPEGTOP 8 + +// lower texture unpegged +#define ML_DONTPEGBOTTOM 16 + +// In AutoMap: don't map as two sided: IT'S A SECRET! +#define ML_SECRET 32 + +// Sound rendering: don't let sound cross two of these. +#define ML_SOUNDBLOCK 64 + +// Don't draw on the automap at all. +#define ML_DONTDRAW 128 + +// Set if already seen, thus drawn in automap. +#define ML_MAPPED 256 + + + + +// Sector definition, from editing. +typedef struct +{ + short floorheight; + short ceilingheight; + char floorpic[8]; + char ceilingpic[8]; + short lightlevel; + short special; + short tag; +} PACKEDATTR mapsector_t; + +// SubSector, as generated by BSP. +typedef struct +{ + short numsegs; + // Index of first one, segs are stored sequentially. + short firstseg; +} PACKEDATTR mapsubsector_t; + + +// LineSeg, generated by splitting LineDefs +// using partition lines selected by BSP builder. +typedef struct +{ + short v1; + short v2; + short angle; + short linedef; + short side; + short offset; +} PACKEDATTR mapseg_t; + + + +// BSP node structure. + +// Indicate a leaf. +#define NF_SUBSECTOR 0x8000 + +typedef struct +{ + // Partition line from (x,y) to x+dx,y+dy) + short x; + short y; + short dx; + short dy; + + // Bounding box for each child, + // clip against view frustum. + short bbox[2][4]; + + // If NF_SUBSECTOR its a subsector, + // else it's a node of another subtree. + unsigned short children[2]; + +} PACKEDATTR mapnode_t; + + + + +// Thing definition, position, orientation and type, +// plus skill/visibility flags and attributes. +typedef struct +{ + short x; + short y; + short angle; + short type; + short options; +} PACKEDATTR mapthing_t; + + + + + +#endif // __DOOMDATA__ diff --git a/firmware_p4/components/Applications/doom/doomdef.c b/firmware_p4/components/Applications/doom/doomdef.c new file mode 100644 index 000000000..d31f3efa2 --- /dev/null +++ b/firmware_p4/components/Applications/doom/doomdef.c @@ -0,0 +1,28 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// DoomDef - basic defines for DOOM, e.g. Version, game mode +// and skill level, and display parameters. +// + + + +#include "doomdef.h" + +// Location for any defines turned variables. + +// None. + + diff --git a/firmware_p4/components/Applications/doom/doomdef.h b/firmware_p4/components/Applications/doom/doomdef.h new file mode 100644 index 000000000..62d729ddb --- /dev/null +++ b/firmware_p4/components/Applications/doom/doomdef.h @@ -0,0 +1,168 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Internally used data structures for virtually everything, +// lots of other stuff. +// + +#ifndef __DOOMDEF__ +#define __DOOMDEF__ + +#include +#include + +#include "doomtype.h" +#include "i_timer.h" +#include "d_mode.h" + +// +// Global parameters/defines. +// +// DOOM version +#define DOOM_VERSION 109 + +// Version code for cph's longtics hack ("v1.91") +#define DOOM_191_VERSION 111 + + +// If rangecheck is undefined, +// most parameter validation debugging code will not be compiled +#define RANGECHECK + +// The maximum number of players, multiplayer/networking. +#define MAXPLAYERS 4 + +// The current state of the game: whether we are +// playing, gazing at the intermission screen, +// the game final animation, or a demo. +typedef enum +{ + GS_LEVEL, + GS_INTERMISSION, + GS_FINALE, + GS_DEMOSCREEN, +} gamestate_t; + +typedef enum +{ + ga_nothing, + ga_loadlevel, + ga_newgame, + ga_loadgame, + ga_savegame, + ga_playdemo, + ga_completed, + ga_victory, + ga_worlddone, + ga_screenshot +} gameaction_t; + +// +// Difficulty/skill settings/filters. +// + +// Skill flags. +#define MTF_EASY 1 +#define MTF_NORMAL 2 +#define MTF_HARD 4 + +// Deaf monsters/do not react to sound. +#define MTF_AMBUSH 8 + + +// +// Key cards. +// +typedef enum +{ + it_bluecard, + it_yellowcard, + it_redcard, + it_blueskull, + it_yellowskull, + it_redskull, + + NUMCARDS + +} card_t; + + + +// The defined weapons, +// including a marker indicating +// user has not changed weapon. +typedef enum +{ + wp_fist, + wp_pistol, + wp_shotgun, + wp_chaingun, + wp_missile, + wp_plasma, + wp_bfg, + wp_chainsaw, + wp_supershotgun, + + NUMWEAPONS, + + // No pending weapon change. + wp_nochange + +} weapontype_t; + + +// Ammunition types defined. +typedef enum +{ + am_clip, // Pistol / chaingun ammo. + am_shell, // Shotgun / double barreled shotgun. + am_cell, // Plasma rifle, BFG. + am_misl, // Missile launcher. + NUMAMMO, + am_noammo // Unlimited for chainsaw / fist. + +} ammotype_t; + + +// Power up artifacts. +typedef enum +{ + pw_invulnerability, + pw_strength, + pw_invisibility, + pw_ironfeet, + pw_allmap, + pw_infrared, + NUMPOWERS + +} powertype_t; + + + +// +// Power up durations, +// how many seconds till expiration, +// assuming TICRATE is 35 ticks/second. +// +typedef enum +{ + INVULNTICS = (30*TICRATE), + INVISTICS = (60*TICRATE), + INFRATICS = (120*TICRATE), + IRONTICS = (60*TICRATE) + +} powerduration_t; + +#endif // __DOOMDEF__ diff --git a/firmware_p4/components/Applications/doom/doomfeatures.h b/firmware_p4/components/Applications/doom/doomfeatures.h new file mode 100644 index 000000000..dff693611 --- /dev/null +++ b/firmware_p4/components/Applications/doom/doomfeatures.h @@ -0,0 +1,40 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// List of features which can be enabled/disabled to slim down the +// program. +// + +#ifndef DOOM_FEATURES_H +#define DOOM_FEATURES_H + +// Enables wad merging (the '-merge' command line parameter) + +#undef FEATURE_WAD_MERGE + +// Enables dehacked support ('-deh') + +#undef FEATURE_DEHACKED + +// Enables multiplayer support (network games) + +#undef FEATURE_MULTIPLAYER + +// Enables sound output + +//#undef FEATURE_SOUND + +#endif /* #ifndef DOOM_FEATURES_H */ + + diff --git a/firmware_p4/components/Applications/doom/doomgeneric.c b/firmware_p4/components/Applications/doom/doomgeneric.c new file mode 100644 index 000000000..881d70077 --- /dev/null +++ b/firmware_p4/components/Applications/doom/doomgeneric.c @@ -0,0 +1,27 @@ +#include + +#include "m_argv.h" + +#include "doomgeneric.h" + +pixel_t* DG_ScreenBuffer = NULL; + +void M_FindResponseFile(void); +void D_DoomMain (void); + + +void doomgeneric_Create(int argc, char **argv) +{ + // save arguments + myargc = argc; + myargv = argv; + + M_FindResponseFile(); + + DG_ScreenBuffer = malloc(DOOMGENERIC_RESX * DOOMGENERIC_RESY * 4); + + DG_Init(); + + D_DoomMain (); +} + diff --git a/firmware_p4/components/Applications/doom/doomgeneric.h b/firmware_p4/components/Applications/doom/doomgeneric.h new file mode 100644 index 000000000..fd8708f42 --- /dev/null +++ b/firmware_p4/components/Applications/doom/doomgeneric.h @@ -0,0 +1,49 @@ +#ifndef DOOM_GENERIC +#define DOOM_GENERIC + +#include +#include + +#ifndef DOOMGENERIC_RESX +#define DOOMGENERIC_RESX 640 +#endif // DOOMGENERIC_RESX + +#ifndef DOOMGENERIC_RESY +#define DOOMGENERIC_RESY 400 +#endif // DOOMGENERIC_RESY + + +#ifdef CMAP256 + +typedef uint8_t pixel_t; + +#else // CMAP256 + +typedef uint32_t pixel_t; + +#endif // CMAP256 + + +extern pixel_t* DG_ScreenBuffer; + +#ifdef __cplusplus +extern "C" { +#endif + +void doomgeneric_Create(int argc, char **argv); +void doomgeneric_Tick(); + + +//Implement below functions for your platform +void DG_Init(); +void DG_DrawFrame(); +void DG_SleepMs(uint32_t ms); +uint32_t DG_GetTicksMs(); +int DG_GetKey(int* pressed, unsigned char* key); +void DG_SetWindowTitle(const char * title); + +#ifdef __cplusplus +} +#endif + +#endif //DOOM_GENERIC diff --git a/firmware_p4/components/Applications/doom/doomgeneric_highboy.c b/firmware_p4/components/Applications/doom/doomgeneric_highboy.c new file mode 100644 index 000000000..79286d75b --- /dev/null +++ b/firmware_p4/components/Applications/doom/doomgeneric_highboy.c @@ -0,0 +1,312 @@ +// HighBoy (ESP32-P4) platform layer for doomgeneric. +// +// Implements the six DG_* hooks + a launcher that runs DOOM in its own task: +// - DG_DrawFrame : DG_ScreenBuffer (ARGB8888, DOOMGENERIC_RESX x RESY) -> RGB565 +// big-endian, blitted directly to the ST7789 (bypassing LVGL) +// in landscape (320x240), letterboxed to DOOM's 320x200. +// - DG_GetKey : drained from a ring buffer filled by a polling input task that +// maps the 6 physical buttons (D-pad + OK + BACK) to DOOM keys, +// with an OK+BACK chord = ESC (menu) and hold-both ~2s = quit. +// - timing : esp_timer / FreeRTOS ticks. +// The WAD is streamed from /sdcard/doom1.wad. Zone memory comes from PSRAM +// automatically (CONFIG_SPIRAM_USE_MALLOC: large mallocs land in PSRAM). +// +// Exit model: doomgeneric_Create() runs DOOM's own infinite loop and never +// returns, so "quit to launcher" = esp_restart() (clean reboot into the UI). + +#include +#include +#include +#include +#include +#include +#include + +#include "doomgeneric.h" +#include "doomkeys.h" +#include "doom_highboy.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/idf_additions.h" // xTaskCreatePinnedToCoreWithCaps +#include "esp_timer.h" +#include "esp_log.h" +#include "esp_system.h" +#include "esp_heap_caps.h" +#include "esp_lcd_panel_ops.h" + +#include "st7789.h" // extern panel_handle +#include "buttons_gpio.h" // *_button_is_down() +#include "lvgl_glue.h" // lvgl_glue_direct_begin() +#include "storage_init.h" // storage_is_mounted(), storage_init() + +static const char *TAG = "DOOM"; + +// ---- geometry ------------------------------------------------------------- +#define DG_W DOOMGENERIC_RESX // 320 +#define DG_H DOOMGENERIC_RESY // 200 +#define PANEL_W 320 // ST7789 in landscape (swap_xy) +#define PANEL_H 240 +#define Y_OFF ((PANEL_H - DG_H) / 2) // 20px letterbox top/bottom +#define STRIP 20 // blit height per SPI burst (avoid corruption) + +static uint16_t *s_fb[2]; // double-buffered RGB565 (big-endian) frames +static int s_fb_sel; + +// Pulsed once per frame to keep the sys_monitor render-liveness beat alive while +// DOOM owns the panel and the LVGL task is parked (see doom_main_task). +static void (*s_beat_kick)(void); + +// ---- timing --------------------------------------------------------------- +uint32_t DG_GetTicksMs(void) { return (uint32_t)(esp_timer_get_time() / 1000); } +void DG_SleepMs(uint32_t ms) { vTaskDelay(pdMS_TO_TICKS(ms ? ms : 1)); } +void DG_SetWindowTitle(const char *title) { (void)title; } + +// ---- input ring buffer (filled by doom_input_task, drained by DG_GetKey) -- +#define KQ 64 +static volatile struct { int pressed; unsigned char key; } s_kq[KQ]; +static volatile int s_kq_head, s_kq_tail; + +static void kq_push(int pressed, unsigned char key) { + int n = (s_kq_head + 1) % KQ; + if (n == s_kq_tail) return; // full: drop + s_kq[s_kq_head].pressed = pressed; + s_kq[s_kq_head].key = key; + s_kq_head = n; +} + +int DG_GetKey(int *pressed, unsigned char *key) { + if (s_kq_tail == s_kq_head) return 0; + *pressed = s_kq[s_kq_tail].pressed; + *key = s_kq[s_kq_tail].key; + s_kq_tail = (s_kq_tail + 1) % KQ; + return 1; +} + +// Simple edge helper for the D-pad (one-shot press/release into the ring). +static void edge(bool now, bool *prev, unsigned char key) { + if (now && !*prev) { kq_push(1, key); *prev = true; } + else if (!now && *prev) { kq_push(0, key); *prev = false; } +} + +// Poll the 6 buttons and translate to DOOM keys. Mapping: +// D-pad -> arrows, ROTATED 90° for landscape (UP->LEFT DOWN->RIGHT RIGHT->UP LEFT->DOWN) +// OK -> FIRE (+ENTER so it also selects in menus) +// BACK -> USE (open doors/switches) +// OK+BACK pressed together (within ~250ms) -> ESCAPE (open/close menu) +// OK+BACK held together ~2s -> quit to launcher (esp_restart) +static void doom_input_task(void *arg) { + (void)arg; + bool p_up = 0, p_dn = 0, p_l = 0, p_r = 0; + bool ok_c = 0, bk_c = 0; // OK / BACK committed as their own key + bool ok_sw = 0, bk_sw = 0; // swallow until physical release (post-chord) + bool chord = 0; // ESC chord active + int64_t t_ok = 0, t_bk = 0, t_chord = 0; + + for (;;) { + bool up = up_button_is_down(), dn = down_button_is_down(); + bool l = left_button_is_down(), r = right_button_is_down(); + bool ok = ok_button_is_down(), bk = back_button_is_down(); + int64_t now = esp_timer_get_time() / 1000; + + // D-pad rotated 90° for landscape play (device held sideways): + edge(up, &p_up, KEY_LEFTARROW); // physical UP -> game LEFT + edge(dn, &p_dn, KEY_RIGHTARROW); // physical DOWN -> game RIGHT + edge(r, &p_r, KEY_UPARROW); // physical RIGHT -> game UP + edge(l, &p_l, KEY_DOWNARROW); // physical LEFT -> game DOWN + + // Track when OK / BACK physically went down (for the chord window). + static bool pok = 0, pbk = 0; + if (ok && !pok) t_ok = now; + if (bk && !pbk) t_bk = now; + + if (chord) { + if (ok && bk) { + if (now - t_chord >= 2000) { // hold both ~2s -> quit to launcher + ESP_LOGW(TAG, "quit gesture -> esp_restart()"); + vTaskDelay(pdMS_TO_TICKS(50)); + esp_restart(); + } + } else { // one released -> close chord + kq_push(0, KEY_ESCAPE); + chord = 0; + ok_sw = ok; // swallow whatever is still held + bk_sw = bk; + } + } else if (ok && bk && !ok_sw && !bk_sw && + (t_ok > t_bk ? t_ok - t_bk : t_bk - t_ok) <= 250) { + // enter chord: retract any single-key commits, send ESC down + if (ok_c) { kq_push(0, KEY_FIRE); kq_push(0, KEY_ENTER); ok_c = 0; } + if (bk_c) { kq_push(0, KEY_USE); bk_c = 0; } + kq_push(1, KEY_ESCAPE); + chord = 1; + t_chord = now; + } else { + // normal handling; honor swallow-until-release + if (ok_sw && !ok) ok_sw = 0; + if (bk_sw && !bk) bk_sw = 0; + bool ok_eff = ok && !ok_sw; + bool bk_eff = bk && !bk_sw; + if (ok_eff && !ok_c) { kq_push(1, KEY_FIRE); kq_push(1, KEY_ENTER); ok_c = 1; } + else if (!ok_eff && ok_c) { kq_push(0, KEY_FIRE); kq_push(0, KEY_ENTER); ok_c = 0; } + if (bk_eff && !bk_c) { kq_push(1, KEY_USE); bk_c = 1; } + else if (!bk_eff && bk_c) { kq_push(0, KEY_USE); bk_c = 0; } + } + + pok = ok; pbk = bk; + vTaskDelay(pdMS_TO_TICKS(15)); + } +} + +// ---- framebuffer ---------------------------------------------------------- +static inline uint16_t argb_to_565be(uint32_t p) { + uint8_t r = (p >> 16) & 0xFF, g = (p >> 8) & 0xFF, b = p & 0xFF; + uint16_t c = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); + return (uint16_t)((c >> 8) | (c << 8)); // ST7789 wants big-endian RGB565 +} + +static void panel_clear_black(void) { + // Fill the whole 320x240 landscape panel black once (letterbox stays black). + uint16_t *row = heap_caps_malloc(PANEL_W * STRIP * sizeof(uint16_t), + MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); + if (!row) return; + memset(row, 0, PANEL_W * STRIP * sizeof(uint16_t)); + for (int y = 0; y < PANEL_H; y += STRIP) { + int rows = (y + STRIP <= PANEL_H) ? STRIP : (PANEL_H - y); + esp_lcd_panel_draw_bitmap(panel_handle, 0, y, PANEL_W, y + rows, row); + } + vTaskDelay(pdMS_TO_TICKS(30)); // let the blits drain before freeing + heap_caps_free(row); +} + +void DG_Init(void) { + for (int i = 0; i < 2; i++) + s_fb[i] = heap_caps_malloc(DG_W * DG_H * sizeof(uint16_t), MALLOC_CAP_SPIRAM); + ESP_LOGW(TAG, "DG_Init: fb0=%p fb1=%p (%d bytes each)", s_fb[0], s_fb[1], + DG_W * DG_H * (int)sizeof(uint16_t)); + // Input runs in its own task (DOOM's loop blocks this task via Create()). + // Stack in PSRAM — internal RAM is scarce (~30 KB free after full boot). + xTaskCreatePinnedToCoreWithCaps(doom_input_task, "doom_in", 4096, NULL, 6, NULL, 0, + MALLOC_CAP_SPIRAM); +} + +void DG_DrawFrame(void) { + uint16_t *fb = s_fb[s_fb_sel]; + if (!fb) return; + s_fb_sel ^= 1; // ping-pong so the previous frame's DMA can still be in flight + + const uint32_t *src = (const uint32_t *)DG_ScreenBuffer; + const int n = DG_W * DG_H; + for (int i = 0; i < n; i++) fb[i] = argb_to_565be(src[i]); + + for (int y = 0; y < DG_H; y += STRIP) { + int rows = (y + STRIP <= DG_H) ? STRIP : (DG_H - y); + esp_lcd_panel_draw_bitmap(panel_handle, 0, Y_OFF + y, DG_W, Y_OFF + y + rows, + &fb[y * DG_W]); + } +} + +// ---- WAD discovery -------------------------------------------------------- +static bool ends_with_wad(const char *name) { + size_t n = strlen(name); + return n >= 4 && strcasecmp(name + n - 4, ".wad") == 0; +} + +// List a directory to the log and return the first *.wad found (full path). +static bool scan_dir_for_wad(const char *dir, char *out, size_t outsz) { + DIR *d = opendir(dir); + if (!d) return false; + ESP_LOGW(TAG, "listing %s:", dir); + bool found = false; + struct dirent *e; + while ((e = readdir(d)) != NULL) { + ESP_LOGI(TAG, " %s", e->d_name); + if (!found && ends_with_wad(e->d_name)) { + snprintf(out, outsz, "%s/%s", dir, e->d_name); + found = true; + } + } + closedir(d); + return found; +} + +// Find a WAD: try common exact paths, then scan the SD root and /sdcard/doom +// for any *.wad. Robust to subfolders and name/case differences. +static bool find_wad(char *out, size_t outsz) { + static const char *cands[] = { + "/sdcard/doom1.wad", "/sdcard/doom.wad", + "/sdcard/DOOM1.WAD", "/sdcard/DOOM.WAD", + "/sdcard/doom/doom1.wad", "/sdcard/doom/doom.wad", NULL}; + for (int i = 0; cands[i]; i++) { + FILE *f = fopen(cands[i], "rb"); + if (f) { fclose(f); snprintf(out, outsz, "%s", cands[i]); return true; } + } + if (scan_dir_for_wad("/sdcard", out, outsz)) return true; + if (scan_dir_for_wad("/sdcard/doom", out, outsz)) return true; + return false; +} + +// ---- launcher ------------------------------------------------------------- +static void doom_main_task(void *arg) { + (void)arg; + ESP_LOGW(TAG, "doom_main_task: enter (free int=%u psram=%u)", + (unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL), + (unsigned)heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); + vTaskDelay(pdMS_TO_TICKS(150)); // let the UI screen switch settle + + // Own the display exclusively: direct-draw routes the panel's DMA-done callback + // to DOOM (not LVGL), and holding the LVGL lock forever parks the LVGL task so + // it never flushes or fights DOOM for the SPI3 bus. The sys_monitor render beat + // is fed by s_beat_kick() in the loop instead of by LVGL. Quitting reboots, so + // the lock is never released. + lvgl_glue_direct_begin(); + lvgl_glue_lock(-1); + ESP_LOGW(TAG, "doom_main_task: panel owned (LVGL parked)"); + + // Landscape 320x240 so DOOM's 320-wide frame fits (portrait is only 240 wide). + esp_lcd_panel_swap_xy(panel_handle, true); + esp_lcd_panel_mirror(panel_handle, true, false); + panel_clear_black(); + ESP_LOGW(TAG, "doom_main_task: panel landscape + cleared"); + + if (!storage_is_mounted()) { + ESP_LOGW(TAG, "SD not mounted, mounting..."); + storage_init(); + } + // Locate a WAD (lists the SD so you can see what's there). Fail loudly rather + // than hang inside DOOM's I_Error if none is present. + static char wadpath[128]; + if (!find_wad(wadpath, sizeof(wadpath))) { + ESP_LOGE(TAG, "No .wad found on SD (checked /sdcard and /sdcard/doom)."); + ESP_LOGE(TAG, "Copy doom1.wad to the SD root, then reboot."); + for (;;) vTaskDelay(pdMS_TO_TICKS(2000)); + } + chdir("/sdcard"); // DOOM writes config/saves relative to CWD (SD mount point) + + ESP_LOGW(TAG, "starting DOOM (wad=%s). Quit = hold OK+BACK ~2s.", wadpath); + static char *argv[] = {"doom", "-iwad", wadpath, NULL}; + doomgeneric_Create(3, argv); // init only (D_DoomMain/D_DoomLoop return after 1 tick) + + // doomgeneric's design: the platform drives the game loop by calling + // doomgeneric_Tick() repeatedly (D_DoomLoop runs ONE tick then returns). + for (;;) { + doomgeneric_Tick(); + // LVGL is parked, so feed the render-liveness beat ourselves. + if (s_beat_kick != NULL) + s_beat_kick(); + // Yield 1 tick (1ms @1kHz) so core 1's IDLE task runs and resets the task + // watchdog; doomgeneric's tic loop never blocks on its own. + vTaskDelay(1); + } +} + +void highboy_doom_start(void (*render_beat_kick)(void)) { + s_beat_kick = render_beat_kick; + // 48 KB stack in PSRAM: DOOM recurses deeply (R_RenderBSPNode) and internal + // RAM is nearly exhausted after boot (~30 KB), so an internal stack can't be + // allocated. WithCaps puts the stack in the ample 32 MB PSRAM. + BaseType_t ok = xTaskCreatePinnedToCoreWithCaps( + doom_main_task, "doom", 49152, NULL, 5, NULL, 1, MALLOC_CAP_SPIRAM); + ESP_LOGW(TAG, "highboy_doom_start: task create -> %s", + ok == pdPASS ? "OK" : "FAILED"); +} diff --git a/firmware_p4/components/Applications/doom/doomkeys.h b/firmware_p4/components/Applications/doom/doomkeys.h new file mode 100644 index 000000000..2dfc431b9 --- /dev/null +++ b/firmware_p4/components/Applications/doom/doomkeys.h @@ -0,0 +1,97 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Key definitions +// + +#ifndef __DOOMKEYS__ +#define __DOOMKEYS__ + +// +// DOOM keyboard definition. +// This is the stuff configured by Setup.Exe. +// Most key data are simple ascii (uppercased). +// +#define KEY_RIGHTARROW 0xae +#define KEY_LEFTARROW 0xac +#define KEY_UPARROW 0xad +#define KEY_DOWNARROW 0xaf +#define KEY_STRAFE_L 0xa0 +#define KEY_STRAFE_R 0xa1 +#define KEY_USE 0xa2 +#define KEY_FIRE 0xa3 +#define KEY_ESCAPE 27 +#define KEY_ENTER 13 +#define KEY_TAB 9 +#define KEY_F1 (0x80+0x3b) +#define KEY_F2 (0x80+0x3c) +#define KEY_F3 (0x80+0x3d) +#define KEY_F4 (0x80+0x3e) +#define KEY_F5 (0x80+0x3f) +#define KEY_F6 (0x80+0x40) +#define KEY_F7 (0x80+0x41) +#define KEY_F8 (0x80+0x42) +#define KEY_F9 (0x80+0x43) +#define KEY_F10 (0x80+0x44) +#define KEY_F11 (0x80+0x57) +#define KEY_F12 (0x80+0x58) + +#define KEY_BACKSPACE 0x7f +#define KEY_PAUSE 0xff + +#define KEY_EQUALS 0x3d +#define KEY_MINUS 0x2d + +#define KEY_RSHIFT (0x80+0x36) +#define KEY_RCTRL (0x80+0x1d) +#define KEY_RALT (0x80+0x38) + +#define KEY_LALT KEY_RALT + +// new keys: + +#define KEY_CAPSLOCK (0x80+0x3a) +#define KEY_NUMLOCK (0x80+0x45) +#define KEY_SCRLCK (0x80+0x46) +#define KEY_PRTSCR (0x80+0x59) + +#define KEY_HOME (0x80+0x47) +#define KEY_END (0x80+0x4f) +#define KEY_PGUP (0x80+0x49) +#define KEY_PGDN (0x80+0x51) +#define KEY_INS (0x80+0x52) +#define KEY_DEL (0x80+0x53) + +#define KEYP_0 0 +#define KEYP_1 KEY_END +#define KEYP_2 KEY_DOWNARROW +#define KEYP_3 KEY_PGDN +#define KEYP_4 KEY_LEFTARROW +#define KEYP_5 '5' +#define KEYP_6 KEY_RIGHTARROW +#define KEYP_7 KEY_HOME +#define KEYP_8 KEY_UPARROW +#define KEYP_9 KEY_PGUP + +#define KEYP_DIVIDE '/' +#define KEYP_PLUS '+' +#define KEYP_MINUS '-' +#define KEYP_MULTIPLY '*' +#define KEYP_PERIOD 0 +#define KEYP_EQUALS KEY_EQUALS +#define KEYP_ENTER KEY_ENTER + +#endif // __DOOMKEYS__ + diff --git a/firmware_p4/components/Applications/doom/doomstat.c b/firmware_p4/components/Applications/doom/doomstat.c new file mode 100644 index 000000000..ed4c6ccc3 --- /dev/null +++ b/firmware_p4/components/Applications/doom/doomstat.c @@ -0,0 +1,35 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Put all global tate variables here. +// + +#include + +#include "doomstat.h" + + +// Game Mode - identify IWAD as shareware, retail etc. +GameMode_t gamemode = indetermined; +GameMission_t gamemission = doom; +GameVersion_t gameversion = exe_final2; +char *gamedescription; + +// Set if homebrew PWAD stuff has been added. +boolean modifiedgame; + + + + diff --git a/firmware_p4/components/Applications/doom/doomstat.h b/firmware_p4/components/Applications/doom/doomstat.h new file mode 100644 index 000000000..acd65dcca --- /dev/null +++ b/firmware_p4/components/Applications/doom/doomstat.h @@ -0,0 +1,281 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// All the global variables that store the internal state. +// Theoretically speaking, the internal state of the engine +// should be found by looking at the variables collected +// here, and every relevant module will have to include +// this header file. +// In practice, things are a bit messy. +// + + +#ifndef __D_STATE__ +#define __D_STATE__ + +// We need globally shared data structures, +// for defining the global state variables. +#include "doomdata.h" +#include "d_loop.h" + +// We need the playr data structure as well. +#include "d_player.h" + +// Game mode/mission +#include "d_mode.h" + +#include "net_defs.h" + + + +// ------------------------ +// Command line parameters. +// +extern boolean nomonsters; // checkparm of -nomonsters +extern boolean respawnparm; // checkparm of -respawn +extern boolean fastparm; // checkparm of -fast + +extern boolean devparm; // DEBUG: launched with -devparm + + +// ----------------------------------------------------- +// Game Mode - identify IWAD as shareware, retail etc. +// +extern GameMode_t gamemode; +extern GameMission_t gamemission; +extern GameVersion_t gameversion; +extern char *gamedescription; + +// If true, we're using one of the mangled BFG edition IWADs. +extern boolean bfgedition; + +// Convenience macro. +// 'gamemission' can be equal to pack_chex or pack_hacx, but these are +// just modified versions of doom and doom2, and should be interpreted +// as the same most of the time. + +#define logical_gamemission \ + (gamemission == pack_chex ? doom : \ + gamemission == pack_hacx ? doom2 : gamemission) + +// Set if homebrew PWAD stuff has been added. +extern boolean modifiedgame; + + +// ------------------------------------------- +// Selected skill type, map etc. +// + +// Defaults for menu, methinks. +extern skill_t startskill; +extern int startepisode; +extern int startmap; + +// Savegame slot to load on startup. This is the value provided to +// the -loadgame option. If this has not been provided, this is -1. + +extern int startloadgame; + +extern boolean autostart; + +// Selected by user. +extern skill_t gameskill; +extern int gameepisode; +extern int gamemap; + +// If non-zero, exit the level after this number of minutes +extern int timelimit; + +// Nightmare mode flag, single player. +extern boolean respawnmonsters; + +// Netgame? Only true if >1 player. +extern boolean netgame; + +// 0=Cooperative; 1=Deathmatch; 2=Altdeath +extern int deathmatch; + +// ------------------------- +// Internal parameters for sound rendering. +// These have been taken from the DOS version, +// but are not (yet) supported with Linux +// (e.g. no sound volume adjustment with menu. + +// From m_menu.c: +// Sound FX volume has default, 0 - 15 +// Music volume has default, 0 - 15 +// These are multiplied by 8. +extern int sfxVolume; +extern int musicVolume; + +// Current music/sfx card - index useless +// w/o a reference LUT in a sound module. +// Ideally, this would use indices found +// in: /usr/include/linux/soundcard.h +extern int snd_MusicDevice; +extern int snd_SfxDevice; +// Config file? Same disclaimer as above. +extern int snd_DesiredMusicDevice; +extern int snd_DesiredSfxDevice; + + +// ------------------------- +// Status flags for refresh. +// + +// Depending on view size - no status bar? +// Note that there is no way to disable the +// status bar explicitely. +extern boolean statusbaractive; + +extern boolean automapactive; // In AutoMap mode? +extern boolean menuactive; // Menu overlayed? +extern boolean paused; // Game Pause? + + +extern boolean viewactive; + +extern boolean nodrawers; + + +extern boolean testcontrols; +extern int testcontrols_mousespeed; + + + + +// This one is related to the 3-screen display mode. +// ANG90 = left side, ANG270 = right +extern int viewangleoffset; + +// Player taking events, and displaying. +extern int consoleplayer; +extern int displayplayer; + + +// ------------------------------------- +// Scores, rating. +// Statistics on a given map, for intermission. +// +extern int totalkills; +extern int totalitems; +extern int totalsecret; + +// Timer, for scores. +extern int levelstarttic; // gametic at level start +extern int leveltime; // tics in game play for par + + + +// -------------------------------------- +// DEMO playback/recording related stuff. +// No demo, there is a human player in charge? +// Disable save/end game? +extern boolean usergame; + +//? +extern boolean demoplayback; +extern boolean demorecording; + +// Round angleturn in ticcmds to the nearest 256. This is used when +// recording Vanilla demos in netgames. + +extern boolean lowres_turn; + +// Quit after playing a demo from cmdline. +extern boolean singledemo; + + + + +//? +extern gamestate_t gamestate; + + + + + + +//----------------------------- +// Internal parameters, fixed. +// These are set by the engine, and not changed +// according to user inputs. Partly load from +// WAD, partly set at startup time. + + + +// Bookkeeping on players - state. +extern player_t players[MAXPLAYERS]; + +// Alive? Disconnected? +extern boolean playeringame[MAXPLAYERS]; + + +// Player spawn spots for deathmatch. +#define MAX_DM_STARTS 10 +extern mapthing_t deathmatchstarts[MAX_DM_STARTS]; +extern mapthing_t* deathmatch_p; + +// Player spawn spots. +extern mapthing_t playerstarts[MAXPLAYERS]; + +// Intermission stats. +// Parameters for world map / intermission. +extern wbstartstruct_t wminfo; + + + + + + + +//----------------------------------------- +// Internal parameters, used for engine. +// + +// File handling stuff. +extern char * savegamedir; +extern char basedefault[1024]; + +// if true, load all graphics at level load +extern boolean precache; + + +// wipegamestate can be set to -1 +// to force a wipe on the next draw +extern gamestate_t wipegamestate; + +extern int mouseSensitivity; + +extern int bodyqueslot; + + + +// Needed to store the number of the dummy sky flat. +// Used for rendering, +// as well as tracking projectiles etc. +extern int skyflatnum; + + + +// Netgame stuff (buffers and pointers, i.e. indices). + + +extern int rndindex; + +extern ticcmd_t *netcmds; + + +#endif diff --git a/firmware_p4/components/Applications/doom/doomtype.h b/firmware_p4/components/Applications/doom/doomtype.h new file mode 100644 index 000000000..dc14bc5da --- /dev/null +++ b/firmware_p4/components/Applications/doom/doomtype.h @@ -0,0 +1,104 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Simple basic typedefs, isolated here to make it easier +// separating modules. +// + + +#ifndef __DOOMTYPE__ +#define __DOOMTYPE__ + +// #define macros to provide functions missing in Windows. +// Outside Windows, we use strings.h for str[n]casecmp. + + +#ifdef _WIN32 + +#define strcasecmp _stricmp +#define strncasecmp _strnicmp + +#else + +#include +#include +#endif + + +// +// The packed attribute forces structures to be packed into the minimum +// space necessary. If this is not done, the compiler may align structure +// fields differently to optimize memory access, inflating the overall +// structure size. It is important to use the packed attribute on certain +// structures where alignment is important, particularly data read/written +// to disk. +// + +#ifdef __GNUC__ +#define PACKEDATTR __attribute__((packed)) +#else +#define PACKEDATTR +#endif + +// C99 integer types; with gcc we just use this. Other compilers +// should add conditional statements that define the C99 types. + +// What is really wanted here is stdint.h; however, some old versions +// of Solaris don't have stdint.h and only have inttypes.h (the +// pre-standardisation version). inttypes.h is also in the C99 +// standard and defined to include stdint.h, so include this. + +#include + +#if defined(__cplusplus) || defined(__bool_true_false_are_defined) + +//boolean is cast to int* in doom. so to keep size the same, make boolean an int. +typedef unsigned int boolean; + +#else + +#ifndef __bool_true_false_are_defined +typedef enum +{ + false = 0, + true = 1, + undef = 0xFFFFFFFF +} boolean; +#endif + +#endif + +typedef uint8_t byte; + +#include + +#if defined(_WIN32) || defined(__DJGPP__) + +#define DIR_SEPARATOR '\\' +#define DIR_SEPARATOR_S "\\" +#define PATH_SEPARATOR ';' + +#else + +#define DIR_SEPARATOR '/' +#define DIR_SEPARATOR_S "/" +#define PATH_SEPARATOR ':' + +#endif + +#define arrlen(array) (sizeof(array) / sizeof(*array)) + +#endif + diff --git a/firmware_p4/components/Applications/doom/dstrings.c b/firmware_p4/components/Applications/doom/dstrings.c new file mode 100644 index 000000000..b87f198b3 --- /dev/null +++ b/firmware_p4/components/Applications/doom/dstrings.c @@ -0,0 +1,73 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Globally defined strings. +// + + + +#include "dstrings.h" + +char *doom1_endmsg[] = +{ + "are you sure you want to\nquit this great game?", + "please don't leave, there's more\ndemons to toast!", + "let's beat it -- this is turning\ninto a bloodbath!", + "i wouldn't leave if i were you.\ndos is much worse.", + "you're trying to say you like dos\nbetter than me, right?", + "don't leave yet -- there's a\ndemon around that corner!", + "ya know, next time you come in here\ni'm gonna toast ya.", + "go ahead and leave. see if i care.", +}; + +char *doom2_endmsg[] = +{ + // QuitDOOM II messages + "are you sure you want to\nquit this great game?", + "you want to quit?\nthen, thou hast lost an eighth!", + "don't go now, there's a \ndimensional shambler waiting\nat the dos prompt!", + "get outta here and go back\nto your boring programs.", + "if i were your boss, i'd \n deathmatch ya in a minute!", + "look, bud. you leave now\nand you forfeit your body count!", + "just leave. when you come\nback, i'll be waiting with a bat.", + "you're lucky i don't smack\nyou for thinking about leaving.", +}; + +#if 0 + +// UNUSED messages included in the source release + +char* endmsg[] = +{ + // DOOM1 + QUITMSG, + // FinalDOOM? + "fuck you, pussy!\nget the fuck out!", + "you quit and i'll jizz\nin your cystholes!", + "if you leave, i'll make\nthe lord drink my jizz.", + "hey, ron! can we say\n'fuck' in the game?", + "i'd leave: this is just\nmore monsters and levels.\nwhat a load.", + "suck it down, asshole!\nyou're a fucking wimp!", + "don't quit now! we're \nstill spending your money!", + + // Internal debug. Different style, too. + "THIS IS NO MESSAGE!\nPage intentionally left blank." +}; + +#endif + + + + diff --git a/firmware_p4/components/Applications/doom/dstrings.h b/firmware_p4/components/Applications/doom/dstrings.h new file mode 100644 index 000000000..d3240b499 --- /dev/null +++ b/firmware_p4/components/Applications/doom/dstrings.h @@ -0,0 +1,41 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// +// DESCRIPTION: +// DOOM strings, by language. +// + + +#ifndef __DSTRINGS__ +#define __DSTRINGS__ + + +// All important printed strings. + +#include "d_englsh.h" + +// Misc. other strings. +#define SAVEGAMENAME "doomsav" + + +// QuitDOOM messages +// 8 per each game type +#define NUM_QUITMESSAGES 8 + +extern char *doom1_endmsg[]; +extern char *doom2_endmsg[]; + + +#endif diff --git a/firmware_p4/components/Applications/doom/dummy.c b/firmware_p4/components/Applications/doom/dummy.c new file mode 100644 index 000000000..d78816b8c --- /dev/null +++ b/firmware_p4/components/Applications/doom/dummy.c @@ -0,0 +1,53 @@ +/* + * dummy.c + * + * Created on: 16.02.2015 + * Author: Florian + */ + + +/*---------------------------------------------------------------------* + * include files * + *---------------------------------------------------------------------*/ + +#include "doomtype.h" + +/*---------------------------------------------------------------------* + * local definitions * + *---------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------* + * external declarations * + *---------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------* + * public data * + *---------------------------------------------------------------------*/ + +boolean net_client_connected = false; + +boolean drone = false; + +/*---------------------------------------------------------------------* + * private data * + *---------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------* + * private functions * + *---------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------* + * public functions * + *---------------------------------------------------------------------*/ + +#ifndef FEATURE_SOUND + +void I_InitTimidityConfig(void) +{ +} + +#endif + +/*---------------------------------------------------------------------* + * eof * + *---------------------------------------------------------------------*/ diff --git a/firmware_p4/components/Applications/doom/f_finale.c b/firmware_p4/components/Applications/doom/f_finale.c new file mode 100644 index 000000000..ca6775ef8 --- /dev/null +++ b/firmware_p4/components/Applications/doom/f_finale.c @@ -0,0 +1,718 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Game completion, final screen animation. +// + + +#include +#include + +// Functions. +#include "deh_main.h" +#include "i_system.h" +#include "i_swap.h" +#include "z_zone.h" +#include "v_video.h" +#include "w_wad.h" +#include "s_sound.h" + +// Data. +#include "d_main.h" +#include "dstrings.h" +#include "sounds.h" + +#include "doomstat.h" +#include "r_state.h" + +typedef enum +{ + F_STAGE_TEXT, + F_STAGE_ARTSCREEN, + F_STAGE_CAST, +} finalestage_t; + +// ? +//#include "doomstat.h" +//#include "r_local.h" +//#include "f_finale.h" + +// Stage of animation: +finalestage_t finalestage; + +unsigned int finalecount; + +#define TEXTSPEED 3 +#define TEXTWAIT 250 + +typedef struct +{ + GameMission_t mission; + int episode, level; + char *background; + char *text; +} textscreen_t; + +static textscreen_t textscreens[] = +{ + { doom, 1, 8, "FLOOR4_8", E1TEXT}, + { doom, 2, 8, "SFLR6_1", E2TEXT}, + { doom, 3, 8, "MFLR8_4", E3TEXT}, + { doom, 4, 8, "MFLR8_3", E4TEXT}, + + { doom2, 1, 6, "SLIME16", C1TEXT}, + { doom2, 1, 11, "RROCK14", C2TEXT}, + { doom2, 1, 20, "RROCK07", C3TEXT}, + { doom2, 1, 30, "RROCK17", C4TEXT}, + { doom2, 1, 15, "RROCK13", C5TEXT}, + { doom2, 1, 31, "RROCK19", C6TEXT}, + + { pack_tnt, 1, 6, "SLIME16", T1TEXT}, + { pack_tnt, 1, 11, "RROCK14", T2TEXT}, + { pack_tnt, 1, 20, "RROCK07", T3TEXT}, + { pack_tnt, 1, 30, "RROCK17", T4TEXT}, + { pack_tnt, 1, 15, "RROCK13", T5TEXT}, + { pack_tnt, 1, 31, "RROCK19", T6TEXT}, + + { pack_plut, 1, 6, "SLIME16", P1TEXT}, + { pack_plut, 1, 11, "RROCK14", P2TEXT}, + { pack_plut, 1, 20, "RROCK07", P3TEXT}, + { pack_plut, 1, 30, "RROCK17", P4TEXT}, + { pack_plut, 1, 15, "RROCK13", P5TEXT}, + { pack_plut, 1, 31, "RROCK19", P6TEXT}, +}; + +char* finaletext; +char* finaleflat; + +void F_StartCast (void); +void F_CastTicker (void); +boolean F_CastResponder (event_t *ev); +void F_CastDrawer (void); + +// +// F_StartFinale +// +void F_StartFinale (void) +{ + size_t i; + + gameaction = ga_nothing; + gamestate = GS_FINALE; + viewactive = false; + automapactive = false; + + if (logical_gamemission == doom) + { + S_ChangeMusic(mus_victor, true); + } + else + { + S_ChangeMusic(mus_read_m, true); + } + + // Find the right screen and set the text and background + + for (i=0; imission == doom) + { + screen->level = 5; + } + + if (logical_gamemission == screen->mission + && (logical_gamemission != doom || gameepisode == screen->episode) + && gamemap == screen->level) + { + finaletext = screen->text; + finaleflat = screen->background; + } + } + + // Do dehacked substitutions of strings + + finaletext = DEH_String(finaletext); + finaleflat = DEH_String(finaleflat); + + finalestage = F_STAGE_TEXT; + finalecount = 0; + +} + + + +boolean F_Responder (event_t *event) +{ + if (finalestage == F_STAGE_CAST) + return F_CastResponder (event); + + return false; +} + + +// +// F_Ticker +// +void F_Ticker (void) +{ + size_t i; + + // check for skipping + if ( (gamemode == commercial) + && ( finalecount > 50) ) + { + // go on to the next level + for (i=0 ; istrlen (finaletext)*TEXTSPEED + TEXTWAIT) + { + finalecount = 0; + finalestage = F_STAGE_ARTSCREEN; + wipegamestate = -1; // force a wipe + if (gameepisode == 3) + S_StartMusic (mus_bunny); + } +} + + + +// +// F_TextWrite +// + +#include "hu_stuff.h" +extern patch_t *hu_font[HU_FONTSIZE]; + + +void F_TextWrite (void) +{ + byte* src; + byte* dest; + + int x,y,w; + signed int count; + char* ch; + int c; + int cx; + int cy; + + // erase the entire screen to a tiled background + src = W_CacheLumpName ( finaleflat , PU_CACHE); + dest = I_VideoBuffer; + + for (y=0 ; y HU_FONTSIZE) + { + cx += 4; + continue; + } + + w = SHORT (hu_font[c]->width); + if (cx+w > SCREENWIDTH) + break; + V_DrawPatch(cx, cy, hu_font[c]); + cx+=w; + } + +} + +// +// Final DOOM 2 animation +// Casting by id Software. +// in order of appearance +// +typedef struct +{ + char *name; + mobjtype_t type; +} castinfo_t; + +castinfo_t castorder[] = { + {CC_ZOMBIE, MT_POSSESSED}, + {CC_SHOTGUN, MT_SHOTGUY}, + {CC_HEAVY, MT_CHAINGUY}, + {CC_IMP, MT_TROOP}, + {CC_DEMON, MT_SERGEANT}, + {CC_LOST, MT_SKULL}, + {CC_CACO, MT_HEAD}, + {CC_HELL, MT_KNIGHT}, + {CC_BARON, MT_BRUISER}, + {CC_ARACH, MT_BABY}, + {CC_PAIN, MT_PAIN}, + {CC_REVEN, MT_UNDEAD}, + {CC_MANCU, MT_FATSO}, + {CC_ARCH, MT_VILE}, + {CC_SPIDER, MT_SPIDER}, + {CC_CYBER, MT_CYBORG}, + {CC_HERO, MT_PLAYER}, + + {NULL,0} +}; + +int castnum; +int casttics; +state_t* caststate; +boolean castdeath; +int castframes; +int castonmelee; +boolean castattacking; + + +// +// F_StartCast +// +void F_StartCast (void) +{ + wipegamestate = -1; // force a screen wipe + castnum = 0; + caststate = &states[mobjinfo[castorder[castnum].type].seestate]; + casttics = caststate->tics; + castdeath = false; + finalestage = F_STAGE_CAST; + castframes = 0; + castonmelee = 0; + castattacking = false; + S_ChangeMusic(mus_evil, true); +} + + +// +// F_CastTicker +// +void F_CastTicker (void) +{ + int st; + int sfx; + + if (--casttics > 0) + return; // not time to change state yet + + if (caststate->tics == -1 || caststate->nextstate == S_NULL) + { + // switch from deathstate to next monster + castnum++; + castdeath = false; + if (castorder[castnum].name == NULL) + castnum = 0; + if (mobjinfo[castorder[castnum].type].seesound) + S_StartSound (NULL, mobjinfo[castorder[castnum].type].seesound); + caststate = &states[mobjinfo[castorder[castnum].type].seestate]; + castframes = 0; + } + else + { + // just advance to next state in animation + if (caststate == &states[S_PLAY_ATK1]) + goto stopattack; // Oh, gross hack! + st = caststate->nextstate; + caststate = &states[st]; + castframes++; + + // sound hacks.... + switch (st) + { + case S_PLAY_ATK1: sfx = sfx_dshtgn; break; + case S_POSS_ATK2: sfx = sfx_pistol; break; + case S_SPOS_ATK2: sfx = sfx_shotgn; break; + case S_VILE_ATK2: sfx = sfx_vilatk; break; + case S_SKEL_FIST2: sfx = sfx_skeswg; break; + case S_SKEL_FIST4: sfx = sfx_skepch; break; + case S_SKEL_MISS2: sfx = sfx_skeatk; break; + case S_FATT_ATK8: + case S_FATT_ATK5: + case S_FATT_ATK2: sfx = sfx_firsht; break; + case S_CPOS_ATK2: + case S_CPOS_ATK3: + case S_CPOS_ATK4: sfx = sfx_shotgn; break; + case S_TROO_ATK3: sfx = sfx_claw; break; + case S_SARG_ATK2: sfx = sfx_sgtatk; break; + case S_BOSS_ATK2: + case S_BOS2_ATK2: + case S_HEAD_ATK2: sfx = sfx_firsht; break; + case S_SKULL_ATK2: sfx = sfx_sklatk; break; + case S_SPID_ATK2: + case S_SPID_ATK3: sfx = sfx_shotgn; break; + case S_BSPI_ATK2: sfx = sfx_plasma; break; + case S_CYBER_ATK2: + case S_CYBER_ATK4: + case S_CYBER_ATK6: sfx = sfx_rlaunc; break; + case S_PAIN_ATK3: sfx = sfx_sklatk; break; + default: sfx = 0; break; + } + + if (sfx) + S_StartSound (NULL, sfx); + } + + if (castframes == 12) + { + // go into attack frame + castattacking = true; + if (castonmelee) + caststate=&states[mobjinfo[castorder[castnum].type].meleestate]; + else + caststate=&states[mobjinfo[castorder[castnum].type].missilestate]; + castonmelee ^= 1; + if (caststate == &states[S_NULL]) + { + if (castonmelee) + caststate= + &states[mobjinfo[castorder[castnum].type].meleestate]; + else + caststate= + &states[mobjinfo[castorder[castnum].type].missilestate]; + } + } + + if (castattacking) + { + if (castframes == 24 + || caststate == &states[mobjinfo[castorder[castnum].type].seestate] ) + { + stopattack: + castattacking = false; + castframes = 0; + caststate = &states[mobjinfo[castorder[castnum].type].seestate]; + } + } + + casttics = caststate->tics; + if (casttics == -1) + casttics = 15; +} + + +// +// F_CastResponder +// + +boolean F_CastResponder (event_t* ev) +{ + if (ev->type != ev_keydown) + return false; + + if (castdeath) + return true; // already in dying frames + + // go into death frame + castdeath = true; + caststate = &states[mobjinfo[castorder[castnum].type].deathstate]; + casttics = caststate->tics; + castframes = 0; + castattacking = false; + if (mobjinfo[castorder[castnum].type].deathsound) + S_StartSound (NULL, mobjinfo[castorder[castnum].type].deathsound); + + return true; +} + + +void F_CastPrint (char* text) +{ + char* ch; + int c; + int cx; + int w; + int width; + + // find width + ch = text; + width = 0; + + while (ch) + { + c = *ch++; + if (!c) + break; + c = toupper(c) - HU_FONTSTART; + if (c < 0 || c> HU_FONTSIZE) + { + width += 4; + continue; + } + + w = SHORT (hu_font[c]->width); + width += w; + } + + // draw it + cx = 160-width/2; + ch = text; + while (ch) + { + c = *ch++; + if (!c) + break; + c = toupper(c) - HU_FONTSTART; + if (c < 0 || c> HU_FONTSIZE) + { + cx += 4; + continue; + } + + w = SHORT (hu_font[c]->width); + V_DrawPatch(cx, 180, hu_font[c]); + cx+=w; + } + +} + + +// +// F_CastDrawer +// + +void F_CastDrawer (void) +{ + spritedef_t* sprdef; + spriteframe_t* sprframe; + int lump; + boolean flip; + patch_t* patch; + + // erase the entire screen to a background + V_DrawPatch (0, 0, W_CacheLumpName (DEH_String("BOSSBACK"), PU_CACHE)); + + F_CastPrint (DEH_String(castorder[castnum].name)); + + // draw the current frame in the middle of the screen + sprdef = &sprites[caststate->sprite]; + sprframe = &sprdef->spriteframes[ caststate->frame & FF_FRAMEMASK]; + lump = sprframe->lump[0]; + flip = (boolean)sprframe->flip[0]; + + patch = W_CacheLumpNum (lump+firstspritelump, PU_CACHE); + if (flip) + V_DrawPatchFlipped(160, 170, patch); + else + V_DrawPatch(160, 170, patch); +} + + +// +// F_DrawPatchCol +// +void +F_DrawPatchCol +( int x, + patch_t* patch, + int col ) +{ + column_t* column; + byte* source; + byte* dest; + byte* desttop; + int count; + + column = (column_t *)((byte *)patch + LONG(patch->columnofs[col])); + desttop = I_VideoBuffer + x; + + // step through the posts in a column + while (column->topdelta != 0xff ) + { + source = (byte *)column + 3; + dest = desttop + column->topdelta*SCREENWIDTH; + count = column->length; + + while (count--) + { + *dest = *source++; + dest += SCREENWIDTH; + } + column = (column_t *)( (byte *)column + column->length + 4 ); + } +} + + +// +// F_BunnyScroll +// +void F_BunnyScroll (void) +{ + signed int scrolled; + int x; + patch_t* p1; + patch_t* p2; + char name[10]; + int stage; + static int laststage; + + p1 = W_CacheLumpName (DEH_String("PFUB2"), PU_LEVEL); + p2 = W_CacheLumpName (DEH_String("PFUB1"), PU_LEVEL); + + V_MarkRect (0, 0, SCREENWIDTH, SCREENHEIGHT); + + scrolled = (320 - ((signed int) finalecount-230)/2); + if (scrolled > 320) + scrolled = 320; + if (scrolled < 0) + scrolled = 0; + + for ( x=0 ; x 6) + stage = 6; + if (stage > laststage) + { + S_StartSound (NULL, sfx_pistol); + laststage = stage; + } + + DEH_snprintf(name, 10, "END%i", stage); + V_DrawPatch((SCREENWIDTH - 13 * 8) / 2, + (SCREENHEIGHT - 8 * 8) / 2, + W_CacheLumpName (name,PU_CACHE)); +} + +static void F_ArtScreenDrawer(void) +{ + char *lumpname; + + if (gameepisode == 3) + { + F_BunnyScroll(); + } + else + { + switch (gameepisode) + { + case 1: + if (gamemode == retail) + { + lumpname = "CREDIT"; + } + else + { + lumpname = "HELP2"; + } + break; + case 2: + lumpname = "VICTORY2"; + break; + case 4: + lumpname = "ENDPIC"; + break; + default: + return; + } + + lumpname = DEH_String(lumpname); + + V_DrawPatch (0, 0, W_CacheLumpName(lumpname, PU_CACHE)); + } +} + +// +// F_Drawer +// +void F_Drawer (void) +{ + switch (finalestage) + { + case F_STAGE_CAST: + F_CastDrawer(); + break; + case F_STAGE_TEXT: + F_TextWrite(); + break; + case F_STAGE_ARTSCREEN: + F_ArtScreenDrawer(); + break; + } +} + + diff --git a/firmware_p4/components/Applications/doom/f_finale.h b/firmware_p4/components/Applications/doom/f_finale.h new file mode 100644 index 000000000..daa71c32a --- /dev/null +++ b/firmware_p4/components/Applications/doom/f_finale.h @@ -0,0 +1,45 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// +// + + +#ifndef __F_FINALE__ +#define __F_FINALE__ + + +#include "doomtype.h" +#include "d_event.h" +// +// FINALE +// + +// Called by main loop. +boolean F_Responder (event_t* ev); + +// Called by main loop. +void F_Ticker (void); + +// Called by main loop. +void F_Drawer (void); + + +void F_StartFinale (void); + + + + +#endif diff --git a/firmware_p4/components/Applications/doom/f_wipe.c b/firmware_p4/components/Applications/doom/f_wipe.c new file mode 100644 index 000000000..05852fd20 --- /dev/null +++ b/firmware_p4/components/Applications/doom/f_wipe.c @@ -0,0 +1,294 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Mission begin melt/wipe screen special effect. +// + +#include + +#include "z_zone.h" +#include "i_video.h" +#include "v_video.h" +#include "m_random.h" + +#include "doomtype.h" + +#include "f_wipe.h" + +// +// SCREEN WIPE PACKAGE +// + +// when zero, stop the wipe +static boolean go = 0; + +static byte* wipe_scr_start; +static byte* wipe_scr_end; +static byte* wipe_scr; + + +void +wipe_shittyColMajorXform +( short* array, + int width, + int height ) +{ + int x; + int y; + short* dest; + + dest = (short*) Z_Malloc(width*height*2, PU_STATIC, 0); + + for(y=0;y *e) + { + newval = *w - ticks; + if (newval < *e) + *w = *e; + else + *w = newval; + changed = true; + } + else if (*w < *e) + { + newval = *w + ticks; + if (newval > *e) + *w = *e; + else + *w = newval; + changed = true; + } + } + w++; + e++; + } + + return !changed; + +} + +int +wipe_exitColorXForm +( int width, + int height, + int ticks ) +{ + return 0; +} + + +static int* y; + +int +wipe_initMelt +( int width, + int height, + int ticks ) +{ + int i, r; + + // copy start screen to main screen + memcpy(wipe_scr, wipe_scr_start, width*height); + + // makes this wipe faster (in theory) + // to have stuff in column-major format + wipe_shittyColMajorXform((short*)wipe_scr_start, width/2, height); + wipe_shittyColMajorXform((short*)wipe_scr_end, width/2, height); + + // setup initial column positions + // (y<0 => not ready to scroll yet) + y = (int *) Z_Malloc(width*sizeof(int), PU_STATIC, 0); + y[0] = -(M_Random()%16); + for (i=1;i 0) y[i] = 0; + else if (y[i] == -16) y[i] = -15; + } + + return 0; +} + +int +wipe_doMelt +( int width, + int height, + int ticks ) +{ + int i; + int j; + int dy; + int idx; + + short* s; + short* d; + boolean done = true; + + width/=2; + + while (ticks--) + { + for (i=0;i= height) dy = height - y[i]; + s = &((short *)wipe_scr_end)[i*height+y[i]]; + d = &((short *)wipe_scr)[y[i]*width+i]; + idx = 0; + for (j=dy;j;j--) + { + d[idx] = *(s++); + idx += width; + } + y[i] += dy; + s = &((short *)wipe_scr_start)[i*height]; + d = &((short *)wipe_scr)[y[i]*width+i]; + idx = 0; + for (j=height-y[i];j;j--) + { + d[idx] = *(s++); + idx += width; + } + done = false; + } + } + } + + return done; + +} + +int +wipe_exitMelt +( int width, + int height, + int ticks ) +{ + Z_Free(y); + Z_Free(wipe_scr_start); + Z_Free(wipe_scr_end); + return 0; +} + +int +wipe_StartScreen +( int x, + int y, + int width, + int height ) +{ + wipe_scr_start = Z_Malloc(SCREENWIDTH * SCREENHEIGHT, PU_STATIC, NULL); + I_ReadScreen(wipe_scr_start); + return 0; +} + +int +wipe_EndScreen +( int x, + int y, + int width, + int height ) +{ + wipe_scr_end = Z_Malloc(SCREENWIDTH * SCREENHEIGHT, PU_STATIC, NULL); + I_ReadScreen(wipe_scr_end); + V_DrawBlock(x, y, width, height, wipe_scr_start); // restore start scr. + return 0; +} + +int +wipe_ScreenWipe +( int wipeno, + int x, + int y, + int width, + int height, + int ticks ) +{ + int rc; + static int (*wipes[])(int, int, int) = + { + wipe_initColorXForm, wipe_doColorXForm, wipe_exitColorXForm, + wipe_initMelt, wipe_doMelt, wipe_exitMelt + }; + + // initial stuff + if (!go) + { + go = 1; + // wipe_scr = (byte *) Z_Malloc(width*height, PU_STATIC, 0); // DEBUG + wipe_scr = I_VideoBuffer; + (*wipes[wipeno*3])(width, height, ticks); + } + + // do a piece of wipe-in + V_MarkRect(0, 0, width, height); + rc = (*wipes[wipeno*3+1])(width, height, ticks); + // V_DrawBlock(x, y, 0, width, height, wipe_scr); // DEBUG + + // final stuff + if (rc) + { + go = 0; + (*wipes[wipeno*3+2])(width, height, ticks); + } + + return !go; +} + diff --git a/firmware_p4/components/Applications/doom/f_wipe.h b/firmware_p4/components/Applications/doom/f_wipe.h new file mode 100644 index 000000000..f48a9ca65 --- /dev/null +++ b/firmware_p4/components/Applications/doom/f_wipe.h @@ -0,0 +1,63 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Mission start screen wipe/melt, special effects. +// + + +#ifndef __F_WIPE_H__ +#define __F_WIPE_H__ + +// +// SCREEN WIPE PACKAGE +// + +enum +{ + // simple gradual pixel change for 8-bit only + wipe_ColorXForm, + + // weird screen melt + wipe_Melt, + + wipe_NUMWIPES +}; + +int +wipe_StartScreen +( int x, + int y, + int width, + int height ); + + +int +wipe_EndScreen +( int x, + int y, + int width, + int height ); + + +int +wipe_ScreenWipe +( int wipeno, + int x, + int y, + int width, + int height, + int ticks ); + +#endif diff --git a/firmware_p4/components/Applications/doom/g_game.c b/firmware_p4/components/Applications/doom/g_game.c new file mode 100644 index 000000000..9954d7890 --- /dev/null +++ b/firmware_p4/components/Applications/doom/g_game.c @@ -0,0 +1,2303 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: none +// + + + +#include +#include +#include + +#include "doomdef.h" +#include "doomkeys.h" +#include "doomstat.h" + +#include "deh_main.h" +#include "deh_misc.h" + +#include "z_zone.h" +#include "f_finale.h" +#include "m_argv.h" +#include "m_controls.h" +#include "m_misc.h" +#include "m_menu.h" +#include "m_random.h" +#include "i_system.h" +#include "i_timer.h" +#include "i_video.h" + +#include "p_setup.h" +#include "p_saveg.h" +#include "p_tick.h" + +#include "d_main.h" + +#include "wi_stuff.h" +#include "hu_stuff.h" +#include "st_stuff.h" +#include "am_map.h" +#include "statdump.h" + +// Needs access to LFB. +#include "v_video.h" + +#include "w_wad.h" + +#include "p_local.h" + +#include "s_sound.h" + +// Data. +#include "dstrings.h" +#include "sounds.h" + +// SKY handling - still the wrong place. +#include "r_data.h" +#include "r_sky.h" + + + +#include "g_game.h" + + +#define SAVEGAMESIZE 0x2c000 + +void G_ReadDemoTiccmd (ticcmd_t* cmd); +void G_WriteDemoTiccmd (ticcmd_t* cmd); +void G_PlayerReborn (int player); + +void G_DoReborn (int playernum); + +void G_DoLoadLevel (void); +void G_DoNewGame (void); +void G_DoPlayDemo (void); +void G_DoCompleted (void); +void G_DoVictory (void); +void G_DoWorldDone (void); +void G_DoSaveGame (void); + +// Gamestate the last time G_Ticker was called. + +gamestate_t oldgamestate; + +gameaction_t gameaction; +gamestate_t gamestate; +skill_t gameskill; +boolean respawnmonsters; +int gameepisode; +int gamemap; + +// If non-zero, exit the level after this number of minutes. + +int timelimit; + +boolean paused; +boolean sendpause; // send a pause event next tic +boolean sendsave; // send a save event next tic +boolean usergame; // ok to save / end game + +boolean timingdemo; // if true, exit with report on completion +boolean nodrawers; // for comparative timing purposes +int starttime; // for comparative timing purposes + +boolean viewactive; + +int deathmatch; // only if started as net death +boolean netgame; // only true if packets are broadcast +boolean playeringame[MAXPLAYERS]; +player_t players[MAXPLAYERS]; + +boolean turbodetected[MAXPLAYERS]; + +int consoleplayer; // player taking events and displaying +int displayplayer; // view being displayed +int levelstarttic; // gametic at level start +int totalkills, totalitems, totalsecret; // for intermission + +char *demoname; +boolean demorecording; +boolean longtics; // cph's doom 1.91 longtics hack +boolean lowres_turn; // low resolution turning for longtics +boolean demoplayback; +boolean netdemo; +byte* demobuffer; +byte* demo_p; +byte* demoend; +boolean singledemo; // quit after playing a demo from cmdline + +boolean precache = true; // if true, load all graphics at start + +boolean testcontrols = false; // Invoked by setup to test controls +int testcontrols_mousespeed; + + + +wbstartstruct_t wminfo; // parms for world map / intermission + +byte consistancy[MAXPLAYERS][BACKUPTICS]; + +#define MAXPLMOVE (forwardmove[1]) + +#define TURBOTHRESHOLD 0x32 + +fixed_t forwardmove[2] = {0x19, 0x32}; +fixed_t sidemove[2] = {0x18, 0x28}; +fixed_t angleturn[3] = {640, 1280, 320}; // + slow turn + +static int *weapon_keys[] = { + &key_weapon1, + &key_weapon2, + &key_weapon3, + &key_weapon4, + &key_weapon5, + &key_weapon6, + &key_weapon7, + &key_weapon8 +}; + +// Set to -1 or +1 to switch to the previous or next weapon. + +static int next_weapon = 0; + +// Used for prev/next weapon keys. + +static const struct +{ + weapontype_t weapon; + weapontype_t weapon_num; +} weapon_order_table[] = { + { wp_fist, wp_fist }, + { wp_chainsaw, wp_fist }, + { wp_pistol, wp_pistol }, + { wp_shotgun, wp_shotgun }, + { wp_supershotgun, wp_shotgun }, + { wp_chaingun, wp_chaingun }, + { wp_missile, wp_missile }, + { wp_plasma, wp_plasma }, + { wp_bfg, wp_bfg } +}; + +#define SLOWTURNTICS 6 + +#define NUMKEYS 256 +#define MAX_JOY_BUTTONS 20 + +static boolean gamekeydown[NUMKEYS]; +static int turnheld; // for accelerative turning + +static boolean mousearray[MAX_MOUSE_BUTTONS + 1]; +static boolean *mousebuttons = &mousearray[1]; // allow [-1] + +// mouse values are used once +int mousex; +int mousey; + +static int dclicktime; +static boolean dclickstate; +static int dclicks; +static int dclicktime2; +static boolean dclickstate2; +static int dclicks2; + +// joystick values are repeated +static int joyxmove; +static int joyymove; +static int joystrafemove; +static boolean joyarray[MAX_JOY_BUTTONS + 1]; +static boolean *joybuttons = &joyarray[1]; // allow [-1] + +static int savegameslot; +static char savedescription[32]; + +#define BODYQUESIZE 32 + +mobj_t* bodyque[BODYQUESIZE]; +int bodyqueslot; + +int vanilla_savegame_limit = 1; +int vanilla_demo_limit = 1; + +int G_CmdChecksum (ticcmd_t* cmd) +{ + size_t i; + int sum = 0; + + for (i=0 ; i< sizeof(*cmd)/4 - 1 ; i++) + sum += ((int *)cmd)[i]; + + return sum; +} + +static boolean WeaponSelectable(weapontype_t weapon) +{ + // Can't select the super shotgun in Doom 1. + + if (weapon == wp_supershotgun && logical_gamemission == doom) + { + return false; + } + + // These weapons aren't available in shareware. + + if ((weapon == wp_plasma || weapon == wp_bfg) + && gamemission == doom && gamemode == shareware) + { + return false; + } + + // Can't select a weapon if we don't own it. + + if (!players[consoleplayer].weaponowned[weapon]) + { + return false; + } + + // Can't select the fist if we have the chainsaw, unless + // we also have the berserk pack. + + if (weapon == wp_fist + && players[consoleplayer].weaponowned[wp_chainsaw] + && !players[consoleplayer].powers[pw_strength]) + { + return false; + } + + return true; +} + +static int G_NextWeapon(int direction) +{ + weapontype_t weapon; + int start_i, i; + + // Find index in the table. + + if (players[consoleplayer].pendingweapon == wp_nochange) + { + weapon = players[consoleplayer].readyweapon; + } + else + { + weapon = players[consoleplayer].pendingweapon; + } + + for (i=0; iconsistancy = + consistancy[consoleplayer][maketic%BACKUPTICS]; + + strafe = gamekeydown[key_strafe] || mousebuttons[mousebstrafe] + || joybuttons[joybstrafe]; + + // fraggle: support the old "joyb_speed = 31" hack which + // allowed an autorun effect + + speed = key_speed >= NUMKEYS + || joybspeed >= MAX_JOY_BUTTONS + || gamekeydown[key_speed] + || joybuttons[joybspeed]; + + forward = side = 0; + + // use two stage accelerative turning + // on the keyboard and joystick + if (joyxmove < 0 + || joyxmove > 0 + || gamekeydown[key_right] + || gamekeydown[key_left]) + turnheld += ticdup; + else + turnheld = 0; + + if (turnheld < SLOWTURNTICS) + tspeed = 2; // slow turn + else + tspeed = speed; + + // let movement keys cancel each other out + if (strafe) + { + if (gamekeydown[key_right]) + { + // fprintf(stderr, "strafe right\n"); + side += sidemove[speed]; + } + if (gamekeydown[key_left]) + { + // fprintf(stderr, "strafe left\n"); + side -= sidemove[speed]; + } + if (joyxmove > 0) + side += sidemove[speed]; + if (joyxmove < 0) + side -= sidemove[speed]; + + } + else + { + if (gamekeydown[key_right]) + cmd->angleturn -= angleturn[tspeed]; + if (gamekeydown[key_left]) + cmd->angleturn += angleturn[tspeed]; + if (joyxmove > 0) + cmd->angleturn -= angleturn[tspeed]; + if (joyxmove < 0) + cmd->angleturn += angleturn[tspeed]; + } + + if (gamekeydown[key_up]) + { + // fprintf(stderr, "up\n"); + forward += forwardmove[speed]; + } + if (gamekeydown[key_down]) + { + // fprintf(stderr, "down\n"); + forward -= forwardmove[speed]; + } + + if (joyymove < 0) + forward += forwardmove[speed]; + if (joyymove > 0) + forward -= forwardmove[speed]; + + if (gamekeydown[key_strafeleft] + || joybuttons[joybstrafeleft] + || mousebuttons[mousebstrafeleft] + || joystrafemove < 0) + { + side -= sidemove[speed]; + } + + if (gamekeydown[key_straferight] + || joybuttons[joybstraferight] + || mousebuttons[mousebstraferight] + || joystrafemove > 0) + { + side += sidemove[speed]; + } + + // buttons + cmd->chatchar = HU_dequeueChatChar(); + + if (gamekeydown[key_fire] || mousebuttons[mousebfire] + || joybuttons[joybfire]) + cmd->buttons |= BT_ATTACK; + + if (gamekeydown[key_use] + || joybuttons[joybuse] + || mousebuttons[mousebuse]) + { + cmd->buttons |= BT_USE; + // clear double clicks if hit use button + dclicks = 0; + } + + // If the previous or next weapon button is pressed, the + // next_weapon variable is set to change weapons when + // we generate a ticcmd. Choose a new weapon. + + if (gamestate == GS_LEVEL && next_weapon != 0) + { + i = G_NextWeapon(next_weapon); + cmd->buttons |= BT_CHANGE; + cmd->buttons |= i << BT_WEAPONSHIFT; + } + else + { + // Check weapon keys. + + for (i=0; ibuttons |= BT_CHANGE; + cmd->buttons |= i< 1 ) + { + dclickstate = mousebuttons[mousebforward]; + if (dclickstate) + dclicks++; + if (dclicks == 2) + { + cmd->buttons |= BT_USE; + dclicks = 0; + } + else + dclicktime = 0; + } + else + { + dclicktime += ticdup; + if (dclicktime > 20) + { + dclicks = 0; + dclickstate = 0; + } + } + + // strafe double click + bstrafe = + mousebuttons[mousebstrafe] + || joybuttons[joybstrafe]; + if (bstrafe != dclickstate2 && dclicktime2 > 1 ) + { + dclickstate2 = bstrafe; + if (dclickstate2) + dclicks2++; + if (dclicks2 == 2) + { + cmd->buttons |= BT_USE; + dclicks2 = 0; + } + else + dclicktime2 = 0; + } + else + { + dclicktime2 += ticdup; + if (dclicktime2 > 20) + { + dclicks2 = 0; + dclickstate2 = 0; + } + } + } + + forward += mousey; + + if (strafe) + side += mousex*2; + else + cmd->angleturn -= mousex*0x8; + + if (mousex == 0) + { + // No movement in the previous frame + + testcontrols_mousespeed = 0; + } + + mousex = mousey = 0; + + if (forward > MAXPLMOVE) + forward = MAXPLMOVE; + else if (forward < -MAXPLMOVE) + forward = -MAXPLMOVE; + if (side > MAXPLMOVE) + side = MAXPLMOVE; + else if (side < -MAXPLMOVE) + side = -MAXPLMOVE; + + cmd->forwardmove += forward; + cmd->sidemove += side; + + // special buttons + if (sendpause) + { + sendpause = false; + cmd->buttons = BT_SPECIAL | BTS_PAUSE; + } + + if (sendsave) + { + sendsave = false; + cmd->buttons = BT_SPECIAL | BTS_SAVEGAME | (savegameslot<angleturn + carry; + + // round angleturn to the nearest 256 unit boundary + // for recording demos with single byte values for turn + + cmd->angleturn = (desired_angleturn + 128) & 0xff00; + + // Carry forward the error from the reduced resolution to the + // next tic, so that successive small movements can accumulate. + + carry = desired_angleturn - cmd->angleturn; + } +} + + +// +// G_DoLoadLevel +// +void G_DoLoadLevel (void) +{ + int i; + + // Set the sky map. + // First thing, we have a dummy sky texture name, + // a flat. The data is in the WAD only because + // we look for an actual index, instead of simply + // setting one. + + skyflatnum = R_FlatNumForName(DEH_String(SKYFLATNAME)); + + // The "Sky never changes in Doom II" bug was fixed in + // the id Anthology version of doom2.exe for Final Doom. + if ((gamemode == commercial) + && (gameversion == exe_final2 || gameversion == exe_chex)) + { + char *skytexturename; + + if (gamemap < 12) + { + skytexturename = "SKY1"; + } + else if (gamemap < 21) + { + skytexturename = "SKY2"; + } + else + { + skytexturename = "SKY3"; + } + + skytexturename = DEH_String(skytexturename); + + skytexture = R_TextureNumForName(skytexturename); + } + + levelstarttic = gametic; // for time calculation + + if (wipegamestate == GS_LEVEL) + wipegamestate = -1; // force a wipe + + gamestate = GS_LEVEL; + + for (i=0 ; itype == ev_keydown + && ev->data1 == key_spy && (singledemo || !deathmatch) ) + { + // spy mode + do + { + displayplayer++; + if (displayplayer == MAXPLAYERS) + displayplayer = 0; + } while (!playeringame[displayplayer] && displayplayer != consoleplayer); + return true; + } + + // any other key pops up menu if in demos + if (gameaction == ga_nothing && !singledemo && + (demoplayback || gamestate == GS_DEMOSCREEN) + ) + { + if (ev->type == ev_keydown || + (ev->type == ev_mouse && ev->data1) || + (ev->type == ev_joystick && ev->data1) ) + { + M_StartControlPanel (); + return true; + } + return false; + } + + if (gamestate == GS_LEVEL) + { +#if 0 + if (devparm && ev->type == ev_keydown && ev->data1 == ';') + { + G_DeathMatchSpawnPlayer (0); + return true; + } +#endif + if (HU_Responder (ev)) + return true; // chat ate the event + if (ST_Responder (ev)) + return true; // status window ate it + if (AM_Responder (ev)) + return true; // automap ate it + } + + if (gamestate == GS_FINALE) + { + if (F_Responder (ev)) + return true; // finale ate the event + } + + if (testcontrols && ev->type == ev_mouse) + { + // If we are invoked by setup to test the controls, save the + // mouse speed so that we can display it on-screen. + // Perform a low pass filter on this so that the thermometer + // appears to move smoothly. + + testcontrols_mousespeed = abs(ev->data2); + } + + // If the next/previous weapon keys are pressed, set the next_weapon + // variable to change weapons when the next ticcmd is generated. + + if (ev->type == ev_keydown && ev->data1 == key_prevweapon) + { + next_weapon = -1; + } + else if (ev->type == ev_keydown && ev->data1 == key_nextweapon) + { + next_weapon = 1; + } + + switch (ev->type) + { + case ev_keydown: + if (ev->data1 == key_pause) + { + sendpause = true; + } + else if (ev->data1 data1] = true; + } + + return true; // eat key down events + + case ev_keyup: + if (ev->data1 data1] = false; + return false; // always let key up events filter down + + case ev_mouse: + SetMouseButtons(ev->data1); + mousex = ev->data2*(mouseSensitivity+5)/10; + mousey = ev->data3*(mouseSensitivity+5)/10; + return true; // eat events + + case ev_joystick: + SetJoyButtons(ev->data1); + joyxmove = ev->data2; + joyymove = ev->data3; + joystrafemove = ev->data4; + return true; // eat events + + default: + break; + } + + return false; +} + + + +// +// G_Ticker +// Make ticcmd_ts for the players. +// +void G_Ticker (void) +{ + int i; + int buf; + ticcmd_t* cmd; + + // do player reborns if needed + for (i=0 ; iforwardmove > TURBOTHRESHOLD) + { + turbodetected[i] = true; + } + + if ((gametic & 31) == 0 + && ((gametic >> 5) % MAXPLAYERS) == i + && turbodetected[i]) + { + static char turbomessage[80]; + extern char *player_names[4]; + M_snprintf(turbomessage, sizeof(turbomessage), + "%s is turbo!", player_names[i]); + players[consoleplayer].message = turbomessage; + turbodetected[i] = false; + } + + if (netgame && !netdemo && !(gametic%ticdup) ) + { + if (gametic > BACKUPTICS + && consistancy[i][buf] != cmd->consistancy) + { + I_Error ("consistency failure (%i should be %i)", + cmd->consistancy, consistancy[i][buf]); + } + if (players[i].mo) + consistancy[i][buf] = players[i].mo->x; + else + consistancy[i][buf] = rndindex; + } + } + } + + // check for special buttons + for (i=0 ; i>BTS_SAVESHIFT; + gameaction = ga_savegame; + break; + } + } + } + } + + // Have we just finished displaying an intermission screen? + + if (oldgamestate == GS_INTERMISSION && gamestate != GS_INTERMISSION) + { + WI_End(); + } + + oldgamestate = gamestate; + + // do main actions + switch (gamestate) + { + case GS_LEVEL: + P_Ticker (); + ST_Ticker (); + AM_Ticker (); + HU_Ticker (); + break; + + case GS_INTERMISSION: + WI_Ticker (); + break; + + case GS_FINALE: + F_Ticker (); + break; + + case GS_DEMOSCREEN: + D_PageTicker (); + break; + } +} + + +// +// PLAYER STRUCTURE FUNCTIONS +// also see P_SpawnPlayer in P_Things +// + +// +// G_InitPlayer +// Called at the start. +// Called by the game initialization functions. +// +void G_InitPlayer (int player) +{ + // clear everything else to defaults + G_PlayerReborn (player); +} + + + +// +// G_PlayerFinishLevel +// Can when a player completes a level. +// +void G_PlayerFinishLevel (int player) +{ + player_t* p; + + p = &players[player]; + + memset (p->powers, 0, sizeof (p->powers)); + memset (p->cards, 0, sizeof (p->cards)); + p->mo->flags &= ~MF_SHADOW; // cancel invisibility + p->extralight = 0; // cancel gun flashes + p->fixedcolormap = 0; // cancel ir gogles + p->damagecount = 0; // no palette changes + p->bonuscount = 0; +} + + +// +// G_PlayerReborn +// Called after a player dies +// almost everything is cleared and initialized +// +void G_PlayerReborn (int player) +{ + player_t* p; + int i; + int frags[MAXPLAYERS]; + int killcount; + int itemcount; + int secretcount; + + memcpy (frags,players[player].frags,sizeof(frags)); + killcount = players[player].killcount; + itemcount = players[player].itemcount; + secretcount = players[player].secretcount; + + p = &players[player]; + memset (p, 0, sizeof(*p)); + + memcpy (players[player].frags, frags, sizeof(players[player].frags)); + players[player].killcount = killcount; + players[player].itemcount = itemcount; + players[player].secretcount = secretcount; + + p->usedown = p->attackdown = true; // don't do anything immediately + p->playerstate = PST_LIVE; + p->health = deh_initial_health; // Use dehacked value + p->readyweapon = p->pendingweapon = wp_pistol; + p->weaponowned[wp_fist] = true; + p->weaponowned[wp_pistol] = true; + p->ammo[am_clip] = deh_initial_bullets; + + for (i=0 ; imaxammo[i] = maxammo[i]; + +} + +// +// G_CheckSpot +// Returns false if the player cannot be respawned +// at the given mapthing_t spot +// because something is occupying it +// +void P_SpawnPlayer (mapthing_t* mthing); + +boolean +G_CheckSpot +( int playernum, + mapthing_t* mthing ) +{ + fixed_t x; + fixed_t y; + subsector_t* ss; + mobj_t* mo; + int i; + + if (!players[playernum].mo) + { + // first spawn of level, before corpses + for (i=0 ; ix == mthing->x << FRACBITS + && players[i].mo->y == mthing->y << FRACBITS) + return false; + return true; + } + + x = mthing->x << FRACBITS; + y = mthing->y << FRACBITS; + + if (!P_CheckPosition (players[playernum].mo, x, y) ) + return false; + + // flush an old corpse if needed + if (bodyqueslot >= BODYQUESIZE) + P_RemoveMobj (bodyque[bodyqueslot%BODYQUESIZE]); + bodyque[bodyqueslot%BODYQUESIZE] = players[playernum].mo; + bodyqueslot++; + + // spawn a teleport fog + ss = R_PointInSubsector (x,y); + + + // The code in the released source looks like this: + // + // an = ( ANG45 * (((unsigned int) mthing->angle)/45) ) + // >> ANGLETOFINESHIFT; + // mo = P_SpawnMobj (x+20*finecosine[an], y+20*finesine[an] + // , ss->sector->floorheight + // , MT_TFOG); + // + // But 'an' can be a signed value in the DOS version. This means that + // we get a negative index and the lookups into finecosine/finesine + // end up dereferencing values in finetangent[]. + // A player spawning on a deathmatch start facing directly west spawns + // "silently" with no spawn fog. Emulate this. + // + // This code is imported from PrBoom+. + + { + fixed_t xa, ya; + signed int an; + + // This calculation overflows in Vanilla Doom, but here we deliberately + // avoid integer overflow as it is undefined behavior, so the value of + // 'an' will always be positive. + an = (ANG45 >> ANGLETOFINESHIFT) * ((signed int) mthing->angle / 45); + + switch (an) + { + case 4096: // -4096: + xa = finetangent[2048]; // finecosine[-4096] + ya = finetangent[0]; // finesine[-4096] + break; + case 5120: // -3072: + xa = finetangent[3072]; // finecosine[-3072] + ya = finetangent[1024]; // finesine[-3072] + break; + case 6144: // -2048: + xa = finesine[0]; // finecosine[-2048] + ya = finetangent[2048]; // finesine[-2048] + break; + case 7168: // -1024: + xa = finesine[1024]; // finecosine[-1024] + ya = finetangent[3072]; // finesine[-1024] + break; + case 0: + case 1024: + case 2048: + case 3072: + xa = finecosine[an]; + ya = finesine[an]; + break; + default: + I_Error("G_CheckSpot: unexpected angle %d\n", an); + xa = ya = 0; + break; + } + mo = P_SpawnMobj(x + 20 * xa, y + 20 * ya, + ss->sector->floorheight, MT_TFOG); + } + + if (players[consoleplayer].viewz != 1) + S_StartSound (mo, sfx_telept); // don't start sound on first frame + + return true; +} + + +// +// G_DeathMatchSpawnPlayer +// Spawns a player at one of the random death match spots +// called at level load and each death +// +void G_DeathMatchSpawnPlayer (int playernum) +{ + int i,j; + int selections; + + selections = deathmatch_p - deathmatchstarts; + if (selections < 4) + I_Error ("Only %i deathmatch spots, 4 required", selections); + + for (j=0 ; j<20 ; j++) + { + i = P_Random() % selections; + if (G_CheckSpot (playernum, &deathmatchstarts[i]) ) + { + deathmatchstarts[i].type = playernum+1; + P_SpawnPlayer (&deathmatchstarts[i]); + return; + } + } + + // no good spot, so the player will probably get stuck + P_SpawnPlayer (&playerstarts[playernum]); +} + +// +// G_DoReborn +// +void G_DoReborn (int playernum) +{ + int i; + + if (!netgame) + { + // reload the level from scratch + gameaction = ga_loadlevel; + } + else + { + // respawn at the start + + // first dissasociate the corpse + players[playernum].mo->player = NULL; + + // spawn at random spot if in death match + if (deathmatch) + { + G_DeathMatchSpawnPlayer (playernum); + return; + } + + if (G_CheckSpot (playernum, &playerstarts[playernum]) ) + { + P_SpawnPlayer (&playerstarts[playernum]); + return; + } + + // try to spawn at one of the other players spots + for (i=0 ; i SAVEGAMESIZE) + { + I_Error ("Savegame buffer overrun"); + } + + // Finish up, close the savegame file. + + fclose(save_stream); + + if (recovery_savegame_file != NULL) + { + // We failed to save to the normal location, but we wrote a + // recovery file to the temp directory. Now we can bomb out + // with an error. + I_Error("Failed to open savegame file '%s' for writing.\n" + "But your game has been saved to '%s' for recovery.", + temp_savegame_file, recovery_savegame_file); + } + + // Now rename the temporary savegame file to the actual savegame + // file, overwriting the old savegame if there was one there. + + remove(savegame_file); + rename(temp_savegame_file, savegame_file); + + gameaction = ga_nothing; + M_StringCopy(savedescription, "", sizeof(savedescription)); + + players[consoleplayer].message = DEH_String(GGSAVED); + + // draw the pattern into the back screen + R_FillBackScreen (); +} + + +// +// G_InitNew +// Can be called by the startup code or the menu task, +// consoleplayer, displayplayer, playeringame[] should be set. +// +skill_t d_skill; +int d_episode; +int d_map; + +void +G_DeferedInitNew +( skill_t skill, + int episode, + int map) +{ + d_skill = skill; + d_episode = episode; + d_map = map; + gameaction = ga_newgame; +} + + +void G_DoNewGame (void) +{ + demoplayback = false; + netdemo = false; + netgame = false; + deathmatch = false; + playeringame[1] = playeringame[2] = playeringame[3] = 0; + respawnparm = false; + fastparm = false; + nomonsters = false; + consoleplayer = 0; + G_InitNew (d_skill, d_episode, d_map); + gameaction = ga_nothing; +} + + +void +G_InitNew +( skill_t skill, + int episode, + int map ) +{ + char *skytexturename; + int i; + + if (paused) + { + paused = false; + S_ResumeSound (); + } + + /* + // Note: This commented-out block of code was added at some point + // between the DOS version(s) and the Doom source release. It isn't + // found in disassemblies of the DOS version and causes IDCLEV and + // the -warp command line parameter to behave differently. + // This is left here for posterity. + + // This was quite messy with SPECIAL and commented parts. + // Supposedly hacks to make the latest edition work. + // It might not work properly. + if (episode < 1) + episode = 1; + + if ( gamemode == retail ) + { + if (episode > 4) + episode = 4; + } + else if ( gamemode == shareware ) + { + if (episode > 1) + episode = 1; // only start episode 1 on shareware + } + else + { + if (episode > 3) + episode = 3; + } + */ + + if (skill > sk_nightmare) + skill = sk_nightmare; + + if (gameversion >= exe_ultimate) + { + if (episode == 0) + { + episode = 4; + } + } + else + { + if (episode < 1) + { + episode = 1; + } + if (episode > 3) + { + episode = 3; + } + } + + if (episode > 1 && gamemode == shareware) + { + episode = 1; + } + + if (map < 1) + map = 1; + + if ( (map > 9) + && ( gamemode != commercial) ) + map = 9; + + M_ClearRandom (); + + if (skill == sk_nightmare || respawnparm ) + respawnmonsters = true; + else + respawnmonsters = false; + + if (fastparm || (skill == sk_nightmare && gameskill != sk_nightmare) ) + { + for (i=S_SARG_RUN1 ; i<=S_SARG_PAIN2 ; i++) + states[i].tics >>= 1; + mobjinfo[MT_BRUISERSHOT].speed = 20*FRACUNIT; + mobjinfo[MT_HEADSHOT].speed = 20*FRACUNIT; + mobjinfo[MT_TROOPSHOT].speed = 20*FRACUNIT; + } + else if (skill != sk_nightmare && gameskill == sk_nightmare) + { + for (i=S_SARG_RUN1 ; i<=S_SARG_PAIN2 ; i++) + states[i].tics <<= 1; + mobjinfo[MT_BRUISERSHOT].speed = 15*FRACUNIT; + mobjinfo[MT_HEADSHOT].speed = 10*FRACUNIT; + mobjinfo[MT_TROOPSHOT].speed = 10*FRACUNIT; + } + + // force players to be initialized upon first level load + for (i=0 ; iforwardmove = ((signed char)*demo_p++); + cmd->sidemove = ((signed char)*demo_p++); + + // If this is a longtics demo, read back in higher resolution + + if (longtics) + { + cmd->angleturn = *demo_p++; + cmd->angleturn |= (*demo_p++) << 8; + } + else + { + cmd->angleturn = ((unsigned char) *demo_p++)<<8; + } + + cmd->buttons = (unsigned char)*demo_p++; +} + +// Increase the size of the demo buffer to allow unlimited demos + +static void IncreaseDemoBuffer(void) +{ + int current_length; + byte *new_demobuffer; + byte *new_demop; + int new_length; + + // Find the current size + + current_length = demoend - demobuffer; + + // Generate a new buffer twice the size + new_length = current_length * 2; + + new_demobuffer = Z_Malloc(new_length, PU_STATIC, 0); + new_demop = new_demobuffer + (demo_p - demobuffer); + + // Copy over the old data + + memcpy(new_demobuffer, demobuffer, current_length); + + // Free the old buffer and point the demo pointers at the new buffer. + + Z_Free(demobuffer); + + demobuffer = new_demobuffer; + demo_p = new_demop; + demoend = demobuffer + new_length; +} + +void G_WriteDemoTiccmd (ticcmd_t* cmd) +{ + byte *demo_start; + + if (gamekeydown[key_demo_quit]) // press q to end demo recording + G_CheckDemoStatus (); + + demo_start = demo_p; + + *demo_p++ = cmd->forwardmove; + *demo_p++ = cmd->sidemove; + + // If this is a longtics demo, record in higher resolution + + if (longtics) + { + *demo_p++ = (cmd->angleturn & 0xff); + *demo_p++ = (cmd->angleturn >> 8) & 0xff; + } + else + { + *demo_p++ = cmd->angleturn >> 8; + } + + *demo_p++ = cmd->buttons; + + // reset demo pointer back + demo_p = demo_start; + + if (demo_p > demoend - 16) + { + if (vanilla_demo_limit) + { + // no more space + G_CheckDemoStatus (); + return; + } + else + { + // Vanilla demo limit disabled: unlimited + // demo lengths! + + IncreaseDemoBuffer(); + } + } + + G_ReadDemoTiccmd (cmd); // make SURE it is exactly the same +} + + + +// +// G_RecordDemo +// +void G_RecordDemo (char *name) +{ + size_t demoname_size; + int i; + int maxsize; + + usergame = false; + demoname_size = strlen(name) + 5; + demoname = Z_Malloc(demoname_size, PU_STATIC, NULL); + M_snprintf(demoname, demoname_size, "%s.lmp", name); + maxsize = 0x20000; + + //! + // @arg + // @category demo + // @vanilla + // + // Specify the demo buffer size (KiB) + // + + i = M_CheckParmWithArgs("-maxdemo", 1); + if (i) + maxsize = atoi(myargv[i+1])*1024; + demobuffer = Z_Malloc (maxsize,PU_STATIC,NULL); + demoend = demobuffer + maxsize; + + demorecording = true; +} + +// Get the demo version code appropriate for the version set in gameversion. +int G_VanillaVersionCode(void) +{ + switch (gameversion) + { + case exe_doom_1_2: + I_Error("Doom 1.2 does not have a version code!"); + case exe_doom_1_666: + return 106; + case exe_doom_1_7: + return 107; + case exe_doom_1_8: + return 108; + case exe_doom_1_9: + default: // All other versions are variants on v1.9: + return 109; + } +} + +void G_BeginRecording (void) +{ + int i; + + //! + // @category demo + // + // Record a high resolution "Doom 1.91" demo. + // + + longtics = M_CheckParm("-longtics") != 0; + + // If not recording a longtics demo, record in low res + + lowres_turn = !longtics; + + demo_p = demobuffer; + + // Save the right version code for this demo + + if (longtics) + { + *demo_p++ = DOOM_191_VERSION; + } + else + { + *demo_p++ = G_VanillaVersionCode(); + } + + *demo_p++ = gameskill; + *demo_p++ = gameepisode; + *demo_p++ = gamemap; + *demo_p++ = deathmatch; + *demo_p++ = respawnparm; + *demo_p++ = fastparm; + *demo_p++ = nomonsters; + *demo_p++ = consoleplayer; + + for (i=0 ; i= 0 && version <= 4) + { + return "v1.0/v1.1/v1.2"; + } + else + { + M_snprintf(resultbuf, sizeof(resultbuf), + "%i.%i (unknown)", version / 100, version % 100); + return resultbuf; + } +} + +void G_DoPlayDemo (void) +{ + skill_t skill; + int i, episode, map; + int demoversion; + + gameaction = ga_nothing; + demobuffer = demo_p = W_CacheLumpName (defdemoname, PU_STATIC); + + demoversion = *demo_p++; + + if (demoversion == G_VanillaVersionCode()) + { + longtics = false; + } + else if (demoversion == DOOM_191_VERSION) + { + // demo recorded with cph's modified "v1.91" doom exe + longtics = true; + } + else + { + char *message = "Demo is from a different game version!\n" + "(read %i, should be %i)\n" + "\n" + "*** You may need to upgrade your version " + "of Doom to v1.9. ***\n" + " See: https://www.doomworld.com/classicdoom" + "/info/patches.php\n" + " This appears to be %s."; + + //I_Error(message, demoversion, G_VanillaVersionCode(), + printf(message, demoversion, G_VanillaVersionCode(), + DemoVersionDescription(demoversion)); + } + + skill = *demo_p++; + episode = *demo_p++; + map = *demo_p++; + deathmatch = *demo_p++; + respawnparm = *demo_p++; + fastparm = *demo_p++; + nomonsters = *demo_p++; + consoleplayer = *demo_p++; + + for (i=0 ; i 0 + || M_CheckParm("-netdemo") > 0) + { + netgame = true; + netdemo = true; + } + + // don't spend a lot of time in loadlevel + precache = false; + G_InitNew (skill, episode, map); + precache = true; + starttime = I_GetTime (); + + usergame = false; + demoplayback = true; +} + +// +// G_TimeDemo +// +void G_TimeDemo (char* name) +{ + //! + // @vanilla + // + // Disable rendering the screen entirely. + // + + nodrawers = M_CheckParm ("-nodraw"); + + timingdemo = true; + singletics = true; + + defdemoname = name; + gameaction = ga_playdemo; +} + + +/* +=================== += += G_CheckDemoStatus += += Called after a death or level completion to allow demos to be cleaned up += Returns true if a new demo loop action will take place +=================== +*/ + +boolean G_CheckDemoStatus (void) +{ + int endtime; + + if (timingdemo) + { + float fps; + int realtics; + + endtime = I_GetTime (); + realtics = endtime - starttime; + fps = ((float) gametic * TICRATE) / realtics; + + // Prevent recursive calls + timingdemo = false; + demoplayback = false; + + I_Error ("timed %i gametics in %i realtics (%f fps)", + gametic, realtics, fps); + } + + if (demoplayback) + { + W_ReleaseLumpName(defdemoname); + demoplayback = false; + netdemo = false; + netgame = false; + deathmatch = false; + playeringame[1] = playeringame[2] = playeringame[3] = 0; + respawnparm = false; + fastparm = false; + nomonsters = false; + consoleplayer = 0; + + if (singledemo) + I_Quit (); + else + D_AdvanceDemo (); + + return true; + } + + if (demorecording) + { + *demo_p++ = DEMOMARKER; + M_WriteFile (demoname, demobuffer, demo_p - demobuffer); + Z_Free (demobuffer); + demorecording = false; + I_Error ("Demo %s recorded",demoname); + } + + return false; +} + + + diff --git a/firmware_p4/components/Applications/doom/g_game.h b/firmware_p4/components/Applications/doom/g_game.h new file mode 100644 index 000000000..da0df39b1 --- /dev/null +++ b/firmware_p4/components/Applications/doom/g_game.h @@ -0,0 +1,80 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Duh. +// + + +#ifndef __G_GAME__ +#define __G_GAME__ + +#include "doomdef.h" +#include "d_event.h" +#include "d_ticcmd.h" + + +// +// GAME +// +void G_DeathMatchSpawnPlayer (int playernum); + +void G_InitNew (skill_t skill, int episode, int map); + +// Can be called by the startup code or M_Responder. +// A normal game starts at map 1, +// but a warp test can start elsewhere +void G_DeferedInitNew (skill_t skill, int episode, int map); + +void G_DeferedPlayDemo (char* demo); + +// Can be called by the startup code or M_Responder, +// calls P_SetupLevel or W_EnterWorld. +void G_LoadGame (char* name); + +void G_DoLoadGame (void); + +// Called by M_Responder. +void G_SaveGame (int slot, char* description); + +// Only called by startup code. +void G_RecordDemo (char* name); + +void G_BeginRecording (void); + +void G_PlayDemo (char* name); +void G_TimeDemo (char* name); +boolean G_CheckDemoStatus (void); + +void G_ExitLevel (void); +void G_SecretExitLevel (void); + +void G_WorldDone (void); + +// Read current data from inputs and build a player movement command. + +void G_BuildTiccmd (ticcmd_t *cmd, int maketic); + +void G_Ticker (void); +boolean G_Responder (event_t* ev); + +void G_ScreenShot (void); + +void G_DrawMouseSpeedBox(void); +int G_VanillaVersionCode(void); + +extern int vanilla_savegame_limit; +extern int vanilla_demo_limit; +#endif + diff --git a/firmware_p4/components/Applications/doom/gusconf.c b/firmware_p4/components/Applications/doom/gusconf.c new file mode 100644 index 000000000..70cdd87c7 --- /dev/null +++ b/firmware_p4/components/Applications/doom/gusconf.c @@ -0,0 +1,271 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// GUS emulation code. +// +// Actually emulating a GUS is far too much work; fortunately +// GUS "emulation" already exists in the form of Timidity, which +// supports GUS patch files. This code therefore converts Doom's +// DMXGUS lump into an equivalent Timidity configuration file. +// + + +#include +#include +#include +#include + +#include "w_wad.h" +#include "z_zone.h" + +#define MAX_INSTRUMENTS 256 + +typedef struct +{ + char *patch_names[MAX_INSTRUMENTS]; + int mapping[MAX_INSTRUMENTS]; +} gus_config_t; + +char *gus_patch_path = ""; +unsigned int gus_ram_kb = 1024; + +static unsigned int MappingIndex(void) +{ + unsigned int result = gus_ram_kb / 256; + + if (result < 1) + { + return 1; + } + else if (result > 4) + { + return 4; + } + else + { + return result; + } +} + +static int SplitLine(char *line, char **fields, unsigned int max_fields) +{ + unsigned int num_fields; + char *p; + + fields[0] = line; + num_fields = 1; + + for (p = line; *p != '\0'; ++p) + { + if (*p == ',') + { + *p = '\0'; + + // Skip spaces following the comma. + do + { + ++p; + } while (*p != '\0' && isspace(*p)); + + fields[num_fields] = p; + ++num_fields; + --p; + + if (num_fields >= max_fields) + { + break; + } + } + else if (*p == '#') + { + *p = '\0'; + break; + } + } + + // Strip off trailing whitespace from the end of the line. + p = fields[num_fields - 1] + strlen(fields[num_fields - 1]); + while (p > fields[num_fields - 1] && isspace(*(p - 1))) + { + --p; + *p = '\0'; + } + + return num_fields; +} + +static void ParseLine(gus_config_t *config, char *line) +{ + char *fields[6]; + unsigned int num_fields; + unsigned int instr_id, mapped_id; + + num_fields = SplitLine(line, fields, 6); + + if (num_fields < 6) + { + return; + } + + instr_id = atoi(fields[0]); + mapped_id = atoi(fields[MappingIndex()]); + + free(config->patch_names[instr_id]); + config->patch_names[instr_id] = strdup(fields[5]); + config->mapping[instr_id] = mapped_id; +} + +static void ParseDMXConfig(char *dmxconf, gus_config_t *config) +{ + char *p, *newline; + unsigned int i; + + memset(config, 0, sizeof(gus_config_t)); + + for (i = 0; i < MAX_INSTRUMENTS; ++i) + { + config->mapping[i] = -1; + } + + p = dmxconf; + + for (;;) + { + newline = strchr(p, '\n'); + + if (newline != NULL) + { + *newline = '\0'; + } + + ParseLine(config, p); + + if (newline == NULL) + { + break; + } + else + { + p = newline + 1; + } + } +} + +static void FreeDMXConfig(gus_config_t *config) +{ + unsigned int i; + + for (i = 0; i < MAX_INSTRUMENTS; ++i) + { + free(config->patch_names[i]); + } +} + +static char *ReadDMXConfig(void) +{ + int lumpnum; + unsigned int len; + char *data; + + // TODO: This should be chosen based on gamemode == commercial: + + lumpnum = W_CheckNumForName("DMXGUS"); + + if (lumpnum < 0) + { + lumpnum = W_GetNumForName("DMXGUSC"); + } + + len = W_LumpLength(lumpnum); + data = Z_Malloc(len + 1, PU_STATIC, NULL); + W_ReadLump(lumpnum, data); + + return data; +} + +static boolean WriteTimidityConfig(char *path, gus_config_t *config) +{ + FILE *fstream; + unsigned int i; + + fstream = fopen(path, "w"); + + if (fstream == NULL) + { + return false; + } + + fprintf(fstream, "# Autogenerated Timidity config.\n\n"); + + fprintf(fstream, "dir %s\n", gus_patch_path); + + fprintf(fstream, "\nbank 0\n\n"); + + for (i = 0; i < 128; ++i) + { + if (config->mapping[i] >= 0 && config->mapping[i] < MAX_INSTRUMENTS + && config->patch_names[config->mapping[i]] != NULL) + { + fprintf(fstream, "%i %s\n", + i, config->patch_names[config->mapping[i]]); + } + } + + fprintf(fstream, "\ndrumset 0\n\n"); + + for (i = 128 + 25; i < MAX_INSTRUMENTS; ++i) + { + if (config->mapping[i] >= 0 && config->mapping[i] < MAX_INSTRUMENTS + && config->patch_names[config->mapping[i]] != NULL) + { + fprintf(fstream, "%i %s\n", + i - 128, config->patch_names[config->mapping[i]]); + } + } + + fprintf(fstream, "\n"); + + fclose(fstream); + + return true; +} + +boolean GUS_WriteConfig(char *path) +{ + boolean result; + char *dmxconf; + gus_config_t config; + + if (!strcmp(gus_patch_path, "")) + { + printf("You haven't configured gus_patch_path.\n"); + printf("gus_patch_path needs to point to the location of " + "your GUS patch set.\n" + "To get a copy of the \"standard\" GUS patches, " + "download a copy of dgguspat.zip.\n"); + + return false; + } + + dmxconf = ReadDMXConfig(); + ParseDMXConfig(dmxconf, &config); + + result = WriteTimidityConfig(path, &config); + + FreeDMXConfig(&config); + Z_Free(dmxconf); + + return result; +} + diff --git a/firmware_p4/components/Applications/doom/gusconf.h b/firmware_p4/components/Applications/doom/gusconf.h new file mode 100644 index 000000000..e0124266e --- /dev/null +++ b/firmware_p4/components/Applications/doom/gusconf.h @@ -0,0 +1,29 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// GUS emulation code. +// + +#ifndef __GUSCONF_H__ +#define __GUSCONF_H__ + +#include "doomtype.h" + +extern char *gus_patch_path; +extern unsigned int gus_ram_kb; + +boolean GUS_WriteConfig(char *path); + +#endif /* #ifndef __GUSCONF_H__ */ + diff --git a/firmware_p4/components/Applications/doom/hu_lib.c b/firmware_p4/components/Applications/doom/hu_lib.c new file mode 100644 index 000000000..47038a043 --- /dev/null +++ b/firmware_p4/components/Applications/doom/hu_lib.c @@ -0,0 +1,347 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: heads-up text and input code +// + + +#include + +#include "doomdef.h" +#include "doomkeys.h" + +#include "v_video.h" +#include "i_swap.h" + +#include "hu_lib.h" +#include "r_local.h" +#include "r_draw.h" + +// boolean : whether the screen is always erased +#define noterased viewwindowx + +extern boolean automapactive; // in AM_map.c + +void HUlib_init(void) +{ +} + +void HUlib_clearTextLine(hu_textline_t* t) +{ + t->len = 0; + t->l[0] = 0; + t->needsupdate = true; +} + +void +HUlib_initTextLine +( hu_textline_t* t, + int x, + int y, + patch_t** f, + int sc ) +{ + t->x = x; + t->y = y; + t->f = f; + t->sc = sc; + HUlib_clearTextLine(t); +} + +boolean +HUlib_addCharToTextLine +( hu_textline_t* t, + char ch ) +{ + + if (t->len == HU_MAXLINELENGTH) + return false; + else + { + t->l[t->len++] = ch; + t->l[t->len] = 0; + t->needsupdate = 4; + return true; + } + +} + +boolean HUlib_delCharFromTextLine(hu_textline_t* t) +{ + + if (!t->len) return false; + else + { + t->l[--t->len] = 0; + t->needsupdate = 4; + return true; + } + +} + +void +HUlib_drawTextLine +( hu_textline_t* l, + boolean drawcursor ) +{ + + int i; + int w; + int x; + unsigned char c; + + // draw the new stuff + x = l->x; + for (i=0;ilen;i++) + { + c = toupper((int)l->l[i]); + if (c != ' ' + && c >= l->sc + && c <= '_') + { + w = SHORT(l->f[c - l->sc]->width); + if (x+w > SCREENWIDTH) + break; + V_DrawPatchDirect(x, l->y, l->f[c - l->sc]); + x += w; + } + else + { + x += 4; + if (x >= SCREENWIDTH) + break; + } + } + + // draw the cursor if requested + if (drawcursor + && x + SHORT(l->f['_' - l->sc]->width) <= SCREENWIDTH) + { + V_DrawPatchDirect(x, l->y, l->f['_' - l->sc]); + } +} + + +// sorta called by HU_Erase and just better darn get things straight +void HUlib_eraseTextLine(hu_textline_t* l) +{ + int lh; + int y; + int yoffset; + + // Only erases when NOT in automap and the screen is reduced, + // and the text must either need updating or refreshing + // (because of a recent change back from the automap) + + if (!automapactive && + viewwindowx && l->needsupdate) + { + lh = SHORT(l->f[0]->height) + 1; + for (y=l->y,yoffset=y*SCREENWIDTH ; yy+lh ; y++,yoffset+=SCREENWIDTH) + { + if (y < viewwindowy || y >= viewwindowy + viewheight) + R_VideoErase(yoffset, SCREENWIDTH); // erase entire line + else + { + R_VideoErase(yoffset, viewwindowx); // erase left border + R_VideoErase(yoffset + viewwindowx + viewwidth, viewwindowx); + // erase right border + } + } + } + + if (l->needsupdate) l->needsupdate--; + +} + +void +HUlib_initSText +( hu_stext_t* s, + int x, + int y, + int h, + patch_t** font, + int startchar, + boolean* on ) +{ + + int i; + + s->h = h; + s->on = on; + s->laston = true; + s->cl = 0; + for (i=0;il[i], + x, y - i*(SHORT(font[0]->height)+1), + font, startchar); + +} + +void HUlib_addLineToSText(hu_stext_t* s) +{ + + int i; + + // add a clear line + if (++s->cl == s->h) + s->cl = 0; + HUlib_clearTextLine(&s->l[s->cl]); + + // everything needs updating + for (i=0 ; ih ; i++) + s->l[i].needsupdate = 4; + +} + +void +HUlib_addMessageToSText +( hu_stext_t* s, + char* prefix, + char* msg ) +{ + HUlib_addLineToSText(s); + if (prefix) + while (*prefix) + HUlib_addCharToTextLine(&s->l[s->cl], *(prefix++)); + + while (*msg) + HUlib_addCharToTextLine(&s->l[s->cl], *(msg++)); +} + +void HUlib_drawSText(hu_stext_t* s) +{ + int i, idx; + hu_textline_t *l; + + if (!*s->on) + return; // if not on, don't draw + + // draw everything + for (i=0 ; ih ; i++) + { + idx = s->cl - i; + if (idx < 0) + idx += s->h; // handle queue of lines + + l = &s->l[idx]; + + // need a decision made here on whether to skip the draw + HUlib_drawTextLine(l, false); // no cursor, please + } + +} + +void HUlib_eraseSText(hu_stext_t* s) +{ + + int i; + + for (i=0 ; ih ; i++) + { + if (s->laston && !*s->on) + s->l[i].needsupdate = 4; + HUlib_eraseTextLine(&s->l[i]); + } + s->laston = *s->on; + +} + +void +HUlib_initIText +( hu_itext_t* it, + int x, + int y, + patch_t** font, + int startchar, + boolean* on ) +{ + it->lm = 0; // default left margin is start of text + it->on = on; + it->laston = true; + HUlib_initTextLine(&it->l, x, y, font, startchar); +} + + +// The following deletion routines adhere to the left margin restriction +void HUlib_delCharFromIText(hu_itext_t* it) +{ + if (it->l.len != it->lm) + HUlib_delCharFromTextLine(&it->l); +} + +void HUlib_eraseLineFromIText(hu_itext_t* it) +{ + while (it->lm != it->l.len) + HUlib_delCharFromTextLine(&it->l); +} + +// Resets left margin as well +void HUlib_resetIText(hu_itext_t* it) +{ + it->lm = 0; + HUlib_clearTextLine(&it->l); +} + +void +HUlib_addPrefixToIText +( hu_itext_t* it, + char* str ) +{ + while (*str) + HUlib_addCharToTextLine(&it->l, *(str++)); + it->lm = it->l.len; +} + +// wrapper function for handling general keyed input. +// returns true if it ate the key +boolean +HUlib_keyInIText +( hu_itext_t* it, + unsigned char ch ) +{ + ch = toupper(ch); + + if (ch >= ' ' && ch <= '_') + HUlib_addCharToTextLine(&it->l, (char) ch); + else + if (ch == KEY_BACKSPACE) + HUlib_delCharFromIText(it); + else + if (ch != KEY_ENTER) + return false; // did not eat key + + return true; // ate the key + +} + +void HUlib_drawIText(hu_itext_t* it) +{ + + hu_textline_t *l = &it->l; + + if (!*it->on) + return; + HUlib_drawTextLine(l, true); // draw the line w/ cursor + +} + +void HUlib_eraseIText(hu_itext_t* it) +{ + if (it->laston && !*it->on) + it->l.needsupdate = 4; + HUlib_eraseTextLine(&it->l); + it->laston = *it->on; +} + diff --git a/firmware_p4/components/Applications/doom/hu_lib.h b/firmware_p4/components/Applications/doom/hu_lib.h new file mode 100644 index 000000000..8f0994e60 --- /dev/null +++ b/firmware_p4/components/Applications/doom/hu_lib.h @@ -0,0 +1,182 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: none +// + +#ifndef __HULIB__ +#define __HULIB__ + +// We are referring to patches. +#include "r_defs.h" + +// font stuff +#define HU_CHARERASE KEY_BACKSPACE + +#define HU_MAXLINES 4 +#define HU_MAXLINELENGTH 80 + +// +// Typedefs of widgets +// + +// Text Line widget +// (parent of Scrolling Text and Input Text widgets) +typedef struct +{ + // left-justified position of scrolling text window + int x; + int y; + + patch_t** f; // font + int sc; // start character + char l[HU_MAXLINELENGTH+1]; // line of text + int len; // current line length + + // whether this line needs to be udpated + int needsupdate; + +} hu_textline_t; + + + +// Scrolling Text window widget +// (child of Text Line widget) +typedef struct +{ + hu_textline_t l[HU_MAXLINES]; // text lines to draw + int h; // height in lines + int cl; // current line number + + // pointer to boolean stating whether to update window + boolean* on; + boolean laston; // last value of *->on. + +} hu_stext_t; + + + +// Input Text Line widget +// (child of Text Line widget) +typedef struct +{ + hu_textline_t l; // text line to input on + + // left margin past which I am not to delete characters + int lm; + + // pointer to boolean stating whether to update window + boolean* on; + boolean laston; // last value of *->on; + +} hu_itext_t; + + +// +// Widget creation, access, and update routines +// + +// initializes heads-up widget library +void HUlib_init(void); + +// +// textline code +// + +// clear a line of text +void HUlib_clearTextLine(hu_textline_t *t); + +void HUlib_initTextLine(hu_textline_t *t, int x, int y, patch_t **f, int sc); + +// returns success +boolean HUlib_addCharToTextLine(hu_textline_t *t, char ch); + +// returns success +boolean HUlib_delCharFromTextLine(hu_textline_t *t); + +// draws tline +void HUlib_drawTextLine(hu_textline_t *l, boolean drawcursor); + +// erases text line +void HUlib_eraseTextLine(hu_textline_t *l); + + +// +// Scrolling Text window widget routines +// + +// ? +void +HUlib_initSText +( hu_stext_t* s, + int x, + int y, + int h, + patch_t** font, + int startchar, + boolean* on ); + +// add a new line +void HUlib_addLineToSText(hu_stext_t* s); + +// ? +void +HUlib_addMessageToSText +( hu_stext_t* s, + char* prefix, + char* msg ); + +// draws stext +void HUlib_drawSText(hu_stext_t* s); + +// erases all stext lines +void HUlib_eraseSText(hu_stext_t* s); + +// Input Text Line widget routines +void +HUlib_initIText +( hu_itext_t* it, + int x, + int y, + patch_t** font, + int startchar, + boolean* on ); + +// enforces left margin +void HUlib_delCharFromIText(hu_itext_t* it); + +// enforces left margin +void HUlib_eraseLineFromIText(hu_itext_t* it); + +// resets line and left margin +void HUlib_resetIText(hu_itext_t* it); + +// left of left-margin +void +HUlib_addPrefixToIText +( hu_itext_t* it, + char* str ); + +// whether eaten +boolean +HUlib_keyInIText +( hu_itext_t* it, + unsigned char ch ); + +void HUlib_drawIText(hu_itext_t* it); + +// erases all itext lines +void HUlib_eraseIText(hu_itext_t* it); + +#endif diff --git a/firmware_p4/components/Applications/doom/hu_stuff.c b/firmware_p4/components/Applications/doom/hu_stuff.c new file mode 100644 index 000000000..b63cac765 --- /dev/null +++ b/firmware_p4/components/Applications/doom/hu_stuff.c @@ -0,0 +1,641 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: Heads-up displays +// + + +#include + +#include "doomdef.h" +#include "doomkeys.h" + +#include "z_zone.h" + +#include "deh_main.h" +#include "i_swap.h" +#include "i_video.h" + +#include "hu_stuff.h" +#include "hu_lib.h" +#include "m_controls.h" +#include "m_misc.h" +#include "w_wad.h" + +#include "s_sound.h" + +#include "doomstat.h" + +// Data. +#include "dstrings.h" +#include "sounds.h" + +// +// Locally used constants, shortcuts. +// +#define HU_TITLE (mapnames[(gameepisode-1)*9+gamemap-1]) +#define HU_TITLE2 (mapnames_commercial[gamemap-1]) +#define HU_TITLEP (mapnames_commercial[gamemap-1 + 32]) +#define HU_TITLET (mapnames_commercial[gamemap-1 + 64]) +#define HU_TITLE_CHEX (mapnames[gamemap - 1]) +#define HU_TITLEHEIGHT 1 +#define HU_TITLEX 0 +#define HU_TITLEY (167 - SHORT(hu_font[0]->height)) + +#define HU_INPUTTOGGLE 't' +#define HU_INPUTX HU_MSGX +#define HU_INPUTY (HU_MSGY + HU_MSGHEIGHT*(SHORT(hu_font[0]->height) +1)) +#define HU_INPUTWIDTH 64 +#define HU_INPUTHEIGHT 1 + + + +char *chat_macros[10] = +{ + HUSTR_CHATMACRO0, + HUSTR_CHATMACRO1, + HUSTR_CHATMACRO2, + HUSTR_CHATMACRO3, + HUSTR_CHATMACRO4, + HUSTR_CHATMACRO5, + HUSTR_CHATMACRO6, + HUSTR_CHATMACRO7, + HUSTR_CHATMACRO8, + HUSTR_CHATMACRO9 +}; + +char* player_names[] = +{ + HUSTR_PLRGREEN, + HUSTR_PLRINDIGO, + HUSTR_PLRBROWN, + HUSTR_PLRRED +}; + +char chat_char; // remove later. +static player_t* plr; +patch_t* hu_font[HU_FONTSIZE]; +static hu_textline_t w_title; +boolean chat_on; +static hu_itext_t w_chat; +static boolean always_off = false; +static char chat_dest[MAXPLAYERS]; +static hu_itext_t w_inputbuffer[MAXPLAYERS]; + +static boolean message_on; +boolean message_dontfuckwithme; +static boolean message_nottobefuckedwith; + +static hu_stext_t w_message; +static int message_counter; + +extern int showMessages; + +static boolean headsupactive = false; + +// +// Builtin map names. +// The actual names can be found in DStrings.h. +// + +char* mapnames[] = // DOOM shareware/registered/retail (Ultimate) names. +{ + + HUSTR_E1M1, + HUSTR_E1M2, + HUSTR_E1M3, + HUSTR_E1M4, + HUSTR_E1M5, + HUSTR_E1M6, + HUSTR_E1M7, + HUSTR_E1M8, + HUSTR_E1M9, + + HUSTR_E2M1, + HUSTR_E2M2, + HUSTR_E2M3, + HUSTR_E2M4, + HUSTR_E2M5, + HUSTR_E2M6, + HUSTR_E2M7, + HUSTR_E2M8, + HUSTR_E2M9, + + HUSTR_E3M1, + HUSTR_E3M2, + HUSTR_E3M3, + HUSTR_E3M4, + HUSTR_E3M5, + HUSTR_E3M6, + HUSTR_E3M7, + HUSTR_E3M8, + HUSTR_E3M9, + + HUSTR_E4M1, + HUSTR_E4M2, + HUSTR_E4M3, + HUSTR_E4M4, + HUSTR_E4M5, + HUSTR_E4M6, + HUSTR_E4M7, + HUSTR_E4M8, + HUSTR_E4M9, + + "NEWLEVEL", + "NEWLEVEL", + "NEWLEVEL", + "NEWLEVEL", + "NEWLEVEL", + "NEWLEVEL", + "NEWLEVEL", + "NEWLEVEL", + "NEWLEVEL" +}; + +// List of names for levels in commercial IWADs +// (doom2.wad, plutonia.wad, tnt.wad). These are stored in a +// single large array; WADs like pl2.wad have a MAP33, and rely on +// the layout in the Vanilla executable, where it is possible to +// overflow the end of one array into the next. + +char *mapnames_commercial[] = +{ + // DOOM 2 map names. + + HUSTR_1, + HUSTR_2, + HUSTR_3, + HUSTR_4, + HUSTR_5, + HUSTR_6, + HUSTR_7, + HUSTR_8, + HUSTR_9, + HUSTR_10, + HUSTR_11, + + HUSTR_12, + HUSTR_13, + HUSTR_14, + HUSTR_15, + HUSTR_16, + HUSTR_17, + HUSTR_18, + HUSTR_19, + HUSTR_20, + + HUSTR_21, + HUSTR_22, + HUSTR_23, + HUSTR_24, + HUSTR_25, + HUSTR_26, + HUSTR_27, + HUSTR_28, + HUSTR_29, + HUSTR_30, + HUSTR_31, + HUSTR_32, + + // Plutonia WAD map names. + + PHUSTR_1, + PHUSTR_2, + PHUSTR_3, + PHUSTR_4, + PHUSTR_5, + PHUSTR_6, + PHUSTR_7, + PHUSTR_8, + PHUSTR_9, + PHUSTR_10, + PHUSTR_11, + + PHUSTR_12, + PHUSTR_13, + PHUSTR_14, + PHUSTR_15, + PHUSTR_16, + PHUSTR_17, + PHUSTR_18, + PHUSTR_19, + PHUSTR_20, + + PHUSTR_21, + PHUSTR_22, + PHUSTR_23, + PHUSTR_24, + PHUSTR_25, + PHUSTR_26, + PHUSTR_27, + PHUSTR_28, + PHUSTR_29, + PHUSTR_30, + PHUSTR_31, + PHUSTR_32, + + // TNT WAD map names. + + THUSTR_1, + THUSTR_2, + THUSTR_3, + THUSTR_4, + THUSTR_5, + THUSTR_6, + THUSTR_7, + THUSTR_8, + THUSTR_9, + THUSTR_10, + THUSTR_11, + + THUSTR_12, + THUSTR_13, + THUSTR_14, + THUSTR_15, + THUSTR_16, + THUSTR_17, + THUSTR_18, + THUSTR_19, + THUSTR_20, + + THUSTR_21, + THUSTR_22, + THUSTR_23, + THUSTR_24, + THUSTR_25, + THUSTR_26, + THUSTR_27, + THUSTR_28, + THUSTR_29, + THUSTR_30, + THUSTR_31, + THUSTR_32 +}; + +void HU_Init(void) +{ + + int i; + int j; + char buffer[9]; + + // load the heads-up font + j = HU_FONTSTART; + for (i=0;imessage && !message_nottobefuckedwith) + || (plr->message && message_dontfuckwithme)) + { + HUlib_addMessageToSText(&w_message, 0, plr->message); + plr->message = 0; + message_on = true; + message_counter = HU_MSGTIMEOUT; + message_nottobefuckedwith = message_dontfuckwithme; + message_dontfuckwithme = 0; + } + + } // else message_on = false; + + // check for incoming chat characters + if (netgame) + { + for (i=0 ; imessage = DEH_String(HUSTR_MSGU); + } + else + { + chatchars[head] = c; + head = (head + 1) & (QUEUESIZE-1); + } +} + +char HU_dequeueChatChar(void) +{ + char c; + + if (head != tail) + { + c = chatchars[tail]; + tail = (tail + 1) & (QUEUESIZE-1); + } + else + { + c = 0; + } + + return c; +} + +boolean HU_Responder(event_t *ev) +{ + + static char lastmessage[HU_MAXLINELENGTH+1]; + char* macromessage; + boolean eatkey = false; + static boolean altdown = false; + unsigned char c; + int i; + int numplayers; + + static int num_nobrainers = 0; + + numplayers = 0; + for (i=0 ; idata1 == KEY_RSHIFT) + { + return false; + } + else if (ev->data1 == KEY_RALT || ev->data1 == KEY_LALT) + { + altdown = ev->type == ev_keydown; + return false; + } + + if (ev->type != ev_keydown) + return false; + + if (!chat_on) + { + if (ev->data1 == key_message_refresh) + { + message_on = true; + message_counter = HU_MSGTIMEOUT; + eatkey = true; + } + else if (netgame && ev->data2 == key_multi_msg) + { + eatkey = chat_on = true; + HUlib_resetIText(&w_chat); + HU_queueChatChar(HU_BROADCAST); + } + else if (netgame && numplayers > 2) + { + for (i=0; idata2 == key_multi_msgplayer[i]) + { + if (playeringame[i] && i!=consoleplayer) + { + eatkey = chat_on = true; + HUlib_resetIText(&w_chat); + HU_queueChatChar(i+1); + break; + } + else if (i == consoleplayer) + { + num_nobrainers++; + if (num_nobrainers < 3) + plr->message = DEH_String(HUSTR_TALKTOSELF1); + else if (num_nobrainers < 6) + plr->message = DEH_String(HUSTR_TALKTOSELF2); + else if (num_nobrainers < 9) + plr->message = DEH_String(HUSTR_TALKTOSELF3); + else if (num_nobrainers < 32) + plr->message = DEH_String(HUSTR_TALKTOSELF4); + else + plr->message = DEH_String(HUSTR_TALKTOSELF5); + } + } + } + } + } + else + { + // send a macro + if (altdown) + { + c = ev->data1 - '0'; + if (c > 9) + return false; + // fprintf(stderr, "got here\n"); + macromessage = chat_macros[c]; + + // kill last message with a '\n' + HU_queueChatChar(KEY_ENTER); // DEBUG!!! + + // send the macro message + while (*macromessage) + HU_queueChatChar(*macromessage++); + HU_queueChatChar(KEY_ENTER); + + // leave chat mode and notify that it was sent + chat_on = false; + M_StringCopy(lastmessage, chat_macros[c], sizeof(lastmessage)); + plr->message = lastmessage; + eatkey = true; + } + else + { + c = ev->data2; + + eatkey = HUlib_keyInIText(&w_chat, c); + if (eatkey) + { + // static unsigned char buf[20]; // DEBUG + HU_queueChatChar(c); + + // M_snprintf(buf, sizeof(buf), "KEY: %d => %d", ev->data1, c); + // plr->message = buf; + } + if (c == KEY_ENTER) + { + chat_on = false; + if (w_chat.l.len) + { + M_StringCopy(lastmessage, w_chat.l.l, sizeof(lastmessage)); + plr->message = lastmessage; + } + } + else if (c == KEY_ESCAPE) + chat_on = false; + } + } + + return eatkey; + +} diff --git a/firmware_p4/components/Applications/doom/hu_stuff.h b/firmware_p4/components/Applications/doom/hu_stuff.h new file mode 100644 index 000000000..a3affc5b9 --- /dev/null +++ b/firmware_p4/components/Applications/doom/hu_stuff.h @@ -0,0 +1,59 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: Head up display +// + +#ifndef __HU_STUFF_H__ +#define __HU_STUFF_H__ + +#include "d_event.h" + + +// +// Globally visible constants. +// +#define HU_FONTSTART '!' // the first font characters +#define HU_FONTEND '_' // the last font characters + +// Calculate # of glyphs in font. +#define HU_FONTSIZE (HU_FONTEND - HU_FONTSTART + 1) + +#define HU_BROADCAST 5 + +#define HU_MSGX 0 +#define HU_MSGY 0 +#define HU_MSGWIDTH 64 // in characters +#define HU_MSGHEIGHT 1 // in lines + +#define HU_MSGTIMEOUT (4*TICRATE) + +// +// HEADS UP TEXT +// + +void HU_Init(void); +void HU_Start(void); + +boolean HU_Responder(event_t* ev); + +void HU_Ticker(void); +void HU_Drawer(void); +char HU_dequeueChatChar(void); +void HU_Erase(void); + +extern char *chat_macros[10]; + +#endif + diff --git a/firmware_p4/components/Applications/doom/i_cdmus.c b/firmware_p4/components/Applications/doom/i_cdmus.c new file mode 100644 index 000000000..12a815cfa --- /dev/null +++ b/firmware_p4/components/Applications/doom/i_cdmus.c @@ -0,0 +1,243 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// +// 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 +// of the License, 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. +// +// +// SDL implementation of the Hexen CD interface. +// + +#include + +#ifdef ORIGCODE +#include "SDL2/SDL.h" +#include "SDL2/SDL_cdrom.h" +#endif + +#include "doomtype.h" + +#include "i_cdmus.h" + +#ifdef ORIGCODE +static SDL_CD *cd_handle = NULL; +static char *startup_error = NULL; +static const char *cd_name = NULL; +#endif + +int cd_Error; + +int I_CDMusInit(void) +{ +#ifdef ORIGCODE + int drive_num = 0; + + // The initialize function is re-invoked when the CD track play cheat + // is used, so use the opportunity to call SDL_CDStatus() to update + // the status of the drive. + + if (cd_handle == NULL) + { + if (SDL_Init(SDL_INIT_CDROM) < 0) + { + startup_error = "Failed to init CD subsystem."; + cd_Error = 1; + return -1; + } + + // TODO: config variable to control CDROM to use. + + cd_handle = SDL_CDOpen(drive_num); + + if (cd_handle == NULL) + { + startup_error = "Failed to open CD-ROM drive."; + cd_Error = 1; + return -1; + } + + cd_name = SDL_CDName(drive_num); + } + + if (SDL_CDStatus(cd_handle) == CD_ERROR) + { + startup_error = "Failed to read CD status."; + cd_Error = 1; + return -1; + } + + if (!CD_INDRIVE(cd_handle->status)) + { + startup_error = "No CD in drive."; + cd_Error = 1; + return -1; + } + + cd_Error = 0; +#endif + return 0; +} + +// We cannot print status messages inline during startup, they must +// be deferred until after I_CDMusInit has returned. + +void I_CDMusPrintStartup(void) +{ +#ifdef ORIGCODE + if (cd_name != NULL) + { + printf("I_CDMusInit: Using CD-ROM drive: %s\n", cd_name); + } + + if (startup_error != NULL) + { + fprintf(stderr, "I_CDMusInit: %s\n", startup_error); + } +#endif +} + +int I_CDMusPlay(int track) +{ +#ifdef ORIGCODE + int result; + + if (cd_handle == NULL) + { + cd_Error = 1; + return -1; + } + + // Play one track + // Track is indexed from 1. + + result = SDL_CDPlayTracks(cd_handle, track - 1, 0, 1, 0); + + cd_Error = 0; + return result; +#else + return 0; +#endif +} + +int I_CDMusStop(void) +{ +#ifdef ORIGCODE + int result; + + result = SDL_CDStop(cd_handle); + + cd_Error = 0; + + return result; +#else + return 0; +#endif +} + +int I_CDMusResume(void) +{ +#ifdef ORIGCODE + int result; + + result = SDL_CDResume(cd_handle); + + cd_Error = 0; + + return result; +#else + return 0; +#endif +} + +int I_CDMusSetVolume(int volume) +{ + /* Not supported yet */ + + cd_Error = 0; + + return 0; +} + +int I_CDMusFirstTrack(void) +{ +#ifdef ORIGCODE + int i; + + if (cd_handle == NULL) + { + cd_Error = 1; + return -1; + } + + // Find the first audio track. + + for (i=0; inumtracks; ++i) + { + if (cd_handle->track[i].type == SDL_AUDIO_TRACK) + { + cd_Error = 0; + + // Tracks are indexed from 1. + return i + 1; + } + } + + // Don't know? + cd_Error = 1; + + return -1; +#else + return 0; +#endif +} + +int I_CDMusLastTrack(void) +{ +#ifdef ORIGCODE + if (cd_handle == NULL) + { + cd_Error = 1; + return -1; + } + + cd_Error = 0; + + return cd_handle->numtracks; +#else + return 0; +#endif +} + +int I_CDMusTrackLength(int track_num) +{ +#ifdef ORIGCODE + SDL_CDtrack *track; + + if (cd_handle == NULL || track_num < 1 || track_num > cd_handle->numtracks) + { + cd_Error = 1; + return -1; + } + + // Track number is indexed from 1. + + track = &cd_handle->track[track_num - 1]; + + // Round up to the next second + + cd_Error = 0; + + return (track->length + CD_FPS - 1) / CD_FPS; +#else + return 0; +#endif +} + diff --git a/firmware_p4/components/Applications/doom/i_cdmus.h b/firmware_p4/components/Applications/doom/i_cdmus.h new file mode 100644 index 000000000..31db2a659 --- /dev/null +++ b/firmware_p4/components/Applications/doom/i_cdmus.h @@ -0,0 +1,41 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// +// 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 +// of the License, 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. +// + +// i_cdmus.h + +#ifndef __ICDMUS__ +#define __ICDMUS__ + +#define CDERR_NOTINSTALLED 10 // MSCDEX not installed +#define CDERR_NOAUDIOSUPPORT 11 // CD-ROM Doesn't support audio +#define CDERR_NOAUDIOTRACKS 12 // Current CD has no audio tracks +#define CDERR_BADDRIVE 20 // Bad drive number +#define CDERR_BADTRACK 21 // Bad track number +#define CDERR_IOCTLBUFFMEM 22 // Not enough low memory for IOCTL +#define CDERR_DEVREQBASE 100 // DevReq errors + +extern int cd_Error; + +int I_CDMusInit(void); +void I_CDMusPrintStartup(void); +int I_CDMusPlay(int track); +int I_CDMusStop(void); +int I_CDMusResume(void); +int I_CDMusSetVolume(int volume); +int I_CDMusFirstTrack(void); +int I_CDMusLastTrack(void); +int I_CDMusTrackLength(int track); + +#endif diff --git a/firmware_p4/components/Applications/doom/i_endoom.c b/firmware_p4/components/Applications/doom/i_endoom.c new file mode 100644 index 000000000..6de261bb1 --- /dev/null +++ b/firmware_p4/components/Applications/doom/i_endoom.c @@ -0,0 +1,101 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Exit text-mode ENDOOM screen. +// + +#include +#include + +#include "config.h" +#include "doomtype.h" +#include "i_video.h" + +#ifdef ORIGCODE +#include "txt_main.h" +#endif + + +#ifdef __DJGPP__ +#include +#endif // __DJGPP__ + + +#define ENDOOM_W 80 +#define ENDOOM_H 25 + +// +// Displays the text mode ending screen after the game quits +// + +void I_Endoom(byte *endoom_data) +{ +#ifdef ORIGCODE + unsigned char *screendata; + int y; + int indent; + + // Set up text mode screen + + TXT_Init(); + I_InitWindowTitle(); + I_InitWindowIcon(); + + // Write the data to the screen memory + + screendata = TXT_GetScreenData(); + + indent = (ENDOOM_W - TXT_SCREEN_W) / 2; + + for (y=0; y 0) + { + break; + } + + TXT_Sleep(0); + } + + // Shut down text mode screen + + TXT_Shutdown(); + +#elif defined(__DJGPP__) + + int y; + + // move cursor to bottom + // there's a direct call for moving cursor somewhere but this is simpler to write + for (y = 0; y < ENDOOM_H; y++) { + puts("\n"); + } + + // allegro exit should have been run already and so we should be in text mode again + movedata(_my_ds(), (unsigned) endoom_data, _dos_ds, 0xB8000UL, ENDOOM_W * ENDOOM_H * 2); + +#endif +} + diff --git a/firmware_p4/components/Applications/doom/i_endoom.h b/firmware_p4/components/Applications/doom/i_endoom.h new file mode 100644 index 000000000..8c8ff457f --- /dev/null +++ b/firmware_p4/components/Applications/doom/i_endoom.h @@ -0,0 +1,29 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Exit text-mode ENDOOM screen. +// + + +#ifndef __I_ENDOOM__ +#define __I_ENDOOM__ + +// Display the Endoom screen on shutdown. Pass a pointer to the +// ENDOOM lump. + +void I_Endoom(byte *data); + +#endif + diff --git a/firmware_p4/components/Applications/doom/i_input.c b/firmware_p4/components/Applications/doom/i_input.c new file mode 100644 index 000000000..7ec4b63dd --- /dev/null +++ b/firmware_p4/components/Applications/doom/i_input.c @@ -0,0 +1,341 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// + + +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "deh_str.h" +#include "doomtype.h" +#include "doomkeys.h" +#include "i_joystick.h" +#include "i_system.h" +#include "i_swap.h" +#include "i_timer.h" +#include "i_video.h" +#include "i_scale.h" +#include "m_argv.h" +#include "m_config.h" +#include "m_misc.h" +#include "tables.h" +#include "v_video.h" +#include "w_wad.h" +#include "z_zone.h" + +#include "doomgeneric.h" + +int vanilla_keyboard_mapping = 1; + +// Is the shift key currently down? + +static int shiftdown = 0; + +// Lookup table for mapping AT keycodes to their doom keycode +static const char at_to_doom[] = +{ + /* 0x00 */ 0x00, + /* 0x01 */ KEY_ESCAPE, + /* 0x02 */ '1', + /* 0x03 */ '2', + /* 0x04 */ '3', + /* 0x05 */ '4', + /* 0x06 */ '5', + /* 0x07 */ '6', + /* 0x08 */ '7', + /* 0x09 */ '8', + /* 0x0a */ '9', + /* 0x0b */ '0', + /* 0x0c */ '-', + /* 0x0d */ '=', + /* 0x0e */ KEY_BACKSPACE, + /* 0x0f */ KEY_TAB, + /* 0x10 */ 'q', + /* 0x11 */ 'w', + /* 0x12 */ 'e', + /* 0x13 */ 'r', + /* 0x14 */ 't', + /* 0x15 */ 'y', + /* 0x16 */ 'u', + /* 0x17 */ 'i', + /* 0x18 */ 'o', + /* 0x19 */ 'p', + /* 0x1a */ '[', + /* 0x1b */ ']', + /* 0x1c */ KEY_ENTER, + /* 0x1d */ KEY_FIRE, /* KEY_RCTRL, */ + /* 0x1e */ 'a', + /* 0x1f */ 's', + /* 0x20 */ 'd', + /* 0x21 */ 'f', + /* 0x22 */ 'g', + /* 0x23 */ 'h', + /* 0x24 */ 'j', + /* 0x25 */ 'k', + /* 0x26 */ 'l', + /* 0x27 */ ';', + /* 0x28 */ '\'', + /* 0x29 */ '`', + /* 0x2a */ KEY_RSHIFT, + /* 0x2b */ '\\', + /* 0x2c */ 'z', + /* 0x2d */ 'x', + /* 0x2e */ 'c', + /* 0x2f */ 'v', + /* 0x30 */ 'b', + /* 0x31 */ 'n', + /* 0x32 */ 'm', + /* 0x33 */ ',', + /* 0x34 */ '.', + /* 0x35 */ '/', + /* 0x36 */ KEY_RSHIFT, + /* 0x37 */ KEYP_MULTIPLY, + /* 0x38 */ KEY_LALT, + /* 0x39 */ KEY_USE, + /* 0x3a */ KEY_CAPSLOCK, + /* 0x3b */ KEY_F1, + /* 0x3c */ KEY_F2, + /* 0x3d */ KEY_F3, + /* 0x3e */ KEY_F4, + /* 0x3f */ KEY_F5, + /* 0x40 */ KEY_F6, + /* 0x41 */ KEY_F7, + /* 0x42 */ KEY_F8, + /* 0x43 */ KEY_F9, + /* 0x44 */ KEY_F10, + /* 0x45 */ KEY_NUMLOCK, + /* 0x46 */ 0x0, + /* 0x47 */ 0x0, /* 47 (Keypad-7/Home) */ + /* 0x48 */ 0x0, /* 48 (Keypad-8/Up) */ + /* 0x49 */ 0x0, /* 49 (Keypad-9/PgUp) */ + /* 0x4a */ 0x0, /* 4a (Keypad--) */ + /* 0x4b */ 0x0, /* 4b (Keypad-4/Left) */ + /* 0x4c */ 0x0, /* 4c (Keypad-5) */ + /* 0x4d */ 0x0, /* 4d (Keypad-6/Right) */ + /* 0x4e */ 0x0, /* 4e (Keypad-+) */ + /* 0x4f */ 0x0, /* 4f (Keypad-1/End) */ + /* 0x50 */ 0x0, /* 50 (Keypad-2/Down) */ + /* 0x51 */ 0x0, /* 51 (Keypad-3/PgDn) */ + /* 0x52 */ 0x0, /* 52 (Keypad-0/Ins) */ + /* 0x53 */ 0x0, /* 53 (Keypad-./Del) */ + /* 0x54 */ 0x0, /* 54 (Alt-SysRq) on a 84+ key keyboard */ + /* 0x55 */ 0x0, + /* 0x56 */ 0x0, + /* 0x57 */ 0x0, + /* 0x58 */ 0x0, + /* 0x59 */ 0x0, + /* 0x5a */ 0x0, + /* 0x5b */ 0x0, + /* 0x5c */ 0x0, + /* 0x5d */ 0x0, + /* 0x5e */ 0x0, + /* 0x5f */ 0x0, + /* 0x60 */ 0x0, + /* 0x61 */ 0x0, + /* 0x62 */ 0x0, + /* 0x63 */ 0x0, + /* 0x64 */ 0x0, + /* 0x65 */ 0x0, + /* 0x66 */ 0x0, + /* 0x67 */ KEY_UPARROW, + /* 0x68 */ 0x0, + /* 0x69 */ KEY_LEFTARROW, + /* 0x6a */ KEY_RIGHTARROW, + /* 0x6b */ 0x0, + /* 0x6c */ KEY_DOWNARROW, + /* 0x6d */ 0x0, + /* 0x6e */ 0x0, + /* 0x6f */ 0x0, + /* 0x70 */ 0x0, + /* 0x71 */ 0x0, + /* 0x72 */ 0x0, + /* 0x73 */ 0x0, + /* 0x74 */ 0x0, + /* 0x75 */ 0x0, + /* 0x76 */ 0x0, + /* 0x77 */ 0x0, + /* 0x78 */ 0x0, + /* 0x79 */ 0x0, + /* 0x7a */ 0x0, + /* 0x7b */ 0x0, + /* 0x7c */ 0x0, + /* 0x7d */ 0x0, + /* 0x7e */ 0x0, + /* 0x7f */ KEY_FIRE, //KEY_RCTRL, +}; + +// Lookup table for mapping ASCII characters to their equivalent when +// shift is pressed on an American layout keyboard: +static const char shiftxform[] = +{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, ' ', '!', '"', '#', '$', '%', '&', + '"', // shift-' + '(', ')', '*', '+', + '<', // shift-, + '_', // shift-- + '>', // shift-. + '?', // shift-/ + ')', // shift-0 + '!', // shift-1 + '@', // shift-2 + '#', // shift-3 + '$', // shift-4 + '%', // shift-5 + '^', // shift-6 + '&', // shift-7 + '*', // shift-8 + '(', // shift-9 + ':', + ':', // shift-; + '<', + '+', // shift-= + '>', '?', '@', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '[', // shift-[ + '!', // shift-backslash - OH MY GOD DOES WATCOM SUCK + ']', // shift-] + '"', '_', + '\'', // shift-` + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '{', '|', '}', '~', 127 +}; + + +static unsigned char TranslateKey(unsigned char key) +{ + return key; + + /* + if (key < sizeof(at_to_doom)) + return at_to_doom[key]; + else + return 0x0; + */ + + //default: + // return tolower(key); +} + +// Get the equivalent ASCII (Unicode?) character for a keypress. + +static unsigned char GetTypedChar(unsigned char key) +{ + key = TranslateKey(key); + + // Is shift held down? If so, perform a translation. + + if (shiftdown > 0) + { + if (key >= 0 && key < arrlen(shiftxform)) + { + key = shiftxform[key]; + } + else + { + key = 0; + } + } + + return key; +} + +static void UpdateShiftStatus(int pressed, unsigned char key) +{ + int change; + + if (pressed) { + change = 1; + } else { + change = -1; + } + + if (key == KEY_RSHIFT) { + shiftdown += change; + } +} + + +void I_GetEvent(void) +{ + event_t event; + int pressed; + unsigned char key; + + + while (DG_GetKey(&pressed, &key)) + { + UpdateShiftStatus(pressed, key); + + // process event + + if (pressed) + { + // data1 has the key pressed, data2 has the character + // (shift-translated, etc) + event.type = ev_keydown; + event.data1 = TranslateKey(key); + event.data2 = GetTypedChar(key); + + if (event.data1 != 0) + { + D_PostEvent(&event); + } + } + else + { + event.type = ev_keyup; + event.data1 = TranslateKey(key); + + // data2 is just initialized to zero for ev_keyup. + // For ev_keydown it's the shifted Unicode character + // that was typed, but if something wants to detect + // key releases it should do so based on data1 + // (key ID), not the printable char. + + event.data2 = 0; + + if (event.data1 != 0) + { + D_PostEvent(&event); + } + break; + } + } + + + /* + case SDL_MOUSEMOTION: + event.type = ev_mouse; + event.data1 = mouse_button_state; + event.data2 = AccelerateMouse(sdlevent.motion.xrel); + event.data3 = -AccelerateMouse(sdlevent.motion.yrel); + D_PostEvent(&event); + break; + */ +} + +void I_InitInput(void) +{ +} + diff --git a/firmware_p4/components/Applications/doom/i_joystick.c b/firmware_p4/components/Applications/doom/i_joystick.c new file mode 100644 index 000000000..755aec3d3 --- /dev/null +++ b/firmware_p4/components/Applications/doom/i_joystick.c @@ -0,0 +1,359 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// SDL Joystick code. +// + +#ifdef ORIGCODE +#include "SDL.h" +#include "SDL_joystick.h" +#endif + +#include +#include +#include + +#include "doomtype.h" +#include "d_event.h" +#include "i_joystick.h" +#include "i_system.h" + +#include "m_config.h" +#include "m_misc.h" + +// When an axis is within the dead zone, it is set to zero. +// This is 5% of the full range: + +#define DEAD_ZONE (32768 / 3) + +#ifdef ORIGCODE +static SDL_Joystick *joystick = NULL; +#endif + +// Configuration variables: + +// Standard default.cfg Joystick enable/disable + +static int usejoystick = 0; + +// Joystick to use, as an SDL joystick index: + +static int joystick_index = -1; + +// Which joystick axis to use for horizontal movement, and whether to +// invert the direction: + +static int joystick_x_axis = 0; +static int joystick_x_invert = 0; + +// Which joystick axis to use for vertical movement, and whether to +// invert the direction: + +static int joystick_y_axis = 1; +static int joystick_y_invert = 0; + +// Which joystick axis to use for strafing? + +static int joystick_strafe_axis = -1; +static int joystick_strafe_invert = 0; + +// Virtual to physical button joystick button mapping. By default this +// is a straight mapping. +static int joystick_physical_buttons[NUM_VIRTUAL_BUTTONS] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 +}; + +void I_ShutdownJoystick(void) +{ +#ifdef ORIGCODE + if (joystick != NULL) + { + SDL_JoystickClose(joystick); + joystick = NULL; + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + } +#endif +} + +#ifdef ORIGCODE +static boolean IsValidAxis(int axis) +{ + int num_axes; + + if (axis < 0) + { + return true; + } + + if (IS_BUTTON_AXIS(axis)) + { + return true; + } + + if (IS_HAT_AXIS(axis)) + { + return HAT_AXIS_HAT(axis) < SDL_JoystickNumHats(joystick); + } + + num_axes = SDL_JoystickNumAxes(joystick); + + return axis < num_axes; +} +#endif + +void I_InitJoystick(void) +{ +#ifdef ORIGCODE + if (!usejoystick) + { + return; + } + + if (SDL_Init(SDL_INIT_JOYSTICK) < 0) + { + return; + } + + if (joystick_index < 0 || joystick_index >= SDL_NumJoysticks()) + { + printf("I_InitJoystick: Invalid joystick ID: %i\n", joystick_index); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + return; + } + + // Open the joystick + + joystick = SDL_JoystickOpen(joystick_index); + + if (joystick == NULL) + { + printf("I_InitJoystick: Failed to open joystick #%i\n", + joystick_index); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + return; + } + + if (!IsValidAxis(joystick_x_axis) + || !IsValidAxis(joystick_y_axis) + || !IsValidAxis(joystick_strafe_axis)) + { + printf("I_InitJoystick: Invalid joystick axis for joystick #%i " + "(run joystick setup again)\n", + joystick_index); + + SDL_JoystickClose(joystick); + joystick = NULL; + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + } + + SDL_JoystickEventState(SDL_ENABLE); + + // Initialized okay! + + printf("I_InitJoystick: %s\n", SDL_JoystickName(joystick_index)); + + I_AtExit(I_ShutdownJoystick, true); +#endif +} + +#ifdef ORIGCODE +static boolean IsAxisButton(int physbutton) +{ + if (IS_BUTTON_AXIS(joystick_x_axis)) + { + if (physbutton == BUTTON_AXIS_NEG(joystick_x_axis) + || physbutton == BUTTON_AXIS_POS(joystick_x_axis)) + { + return true; + } + } + if (IS_BUTTON_AXIS(joystick_y_axis)) + { + if (physbutton == BUTTON_AXIS_NEG(joystick_y_axis) + || physbutton == BUTTON_AXIS_POS(joystick_y_axis)) + { + return true; + } + } + if (IS_BUTTON_AXIS(joystick_strafe_axis)) + { + if (physbutton == BUTTON_AXIS_NEG(joystick_strafe_axis) + || physbutton == BUTTON_AXIS_POS(joystick_strafe_axis)) + { + return true; + } + } + + return false; +} + +// Get the state of the given virtual button. + +static int ReadButtonState(int vbutton) +{ + int physbutton; + + // Map from virtual button to physical (SDL) button. + if (vbutton < NUM_VIRTUAL_BUTTONS) + { + physbutton = joystick_physical_buttons[vbutton]; + } + else + { + physbutton = vbutton; + } + + // Never read axis buttons as buttons. + if (IsAxisButton(physbutton)) + { + return 0; + } + + return SDL_JoystickGetButton(joystick, physbutton); +} + +// Get a bitmask of all currently-pressed buttons + +static int GetButtonsState(void) +{ + int i; + int result; + + result = 0; + + for (i = 0; i < 20; ++i) + { + if (ReadButtonState(i)) + { + result |= 1 << i; + } + } + + return result; +} + +// Read the state of an axis, inverting if necessary. + +static int GetAxisState(int axis, int invert) +{ + int result; + + // Axis -1 means disabled. + + if (axis < 0) + { + return 0; + } + + // Is this a button axis, or a hat axis? + // If so, we need to handle it specially. + + result = 0; + + if (IS_BUTTON_AXIS(axis)) + { + if (SDL_JoystickGetButton(joystick, BUTTON_AXIS_NEG(axis))) + { + result -= 32767; + } + if (SDL_JoystickGetButton(joystick, BUTTON_AXIS_POS(axis))) + { + result += 32767; + } + } + else if (IS_HAT_AXIS(axis)) + { + int direction = HAT_AXIS_DIRECTION(axis); + int hatval = SDL_JoystickGetHat(joystick, HAT_AXIS_HAT(axis)); + + if (direction == HAT_AXIS_HORIZONTAL) + { + if ((hatval & SDL_HAT_LEFT) != 0) + { + result -= 32767; + } + else if ((hatval & SDL_HAT_RIGHT) != 0) + { + result += 32767; + } + } + else if (direction == HAT_AXIS_VERTICAL) + { + if ((hatval & SDL_HAT_UP) != 0) + { + result -= 32767; + } + else if ((hatval & SDL_HAT_DOWN) != 0) + { + result += 32767; + } + } + } + else + { + result = SDL_JoystickGetAxis(joystick, axis); + + if (result < DEAD_ZONE && result > -DEAD_ZONE) + { + result = 0; + } + } + + if (invert) + { + result = -result; + } + + return result; +} +#endif +void I_UpdateJoystick(void) +{ +#ifdef ORIGCODE + if (joystick != NULL) + { + event_t ev; + + ev.type = ev_joystick; + ev.data1 = GetButtonsState(); + ev.data2 = GetAxisState(joystick_x_axis, joystick_x_invert); + ev.data3 = GetAxisState(joystick_y_axis, joystick_y_invert); + ev.data4 = GetAxisState(joystick_strafe_axis, joystick_strafe_invert); + + D_PostEvent(&ev); + } +#endif +} + +void I_BindJoystickVariables(void) +{ + int i; + + M_BindVariable("use_joystick", &usejoystick); + M_BindVariable("joystick_index", &joystick_index); + M_BindVariable("joystick_x_axis", &joystick_x_axis); + M_BindVariable("joystick_y_axis", &joystick_y_axis); + M_BindVariable("joystick_strafe_axis", &joystick_strafe_axis); + M_BindVariable("joystick_x_invert", &joystick_x_invert); + M_BindVariable("joystick_y_invert", &joystick_y_invert); + M_BindVariable("joystick_strafe_invert",&joystick_strafe_invert); + + for (i = 0; i < NUM_VIRTUAL_BUTTONS; ++i) + { + char name[32]; + M_snprintf(name, sizeof(name), "joystick_physical_button%i", i); + M_BindVariable(name, &joystick_physical_buttons[i]); + } +} + diff --git a/firmware_p4/components/Applications/doom/i_joystick.h b/firmware_p4/components/Applications/doom/i_joystick.h new file mode 100644 index 000000000..b8815e215 --- /dev/null +++ b/firmware_p4/components/Applications/doom/i_joystick.h @@ -0,0 +1,70 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// System-specific joystick interface. +// + + +#ifndef __I_JOYSTICK__ +#define __I_JOYSTICK__ + +// Number of "virtual" joystick buttons defined in configuration files. +// This needs to be at least as large as the number of different key +// bindings supported by the higher-level game code (joyb* variables). +#define NUM_VIRTUAL_BUTTONS 10 + +// If this bit is set in a configuration file axis value, the axis is +// not actually a joystick axis, but instead is a "button axis". This +// means that instead of reading an SDL joystick axis, we read the +// state of two buttons to get the axis value. This is needed for eg. +// the PS3 SIXAXIS controller, where the D-pad buttons register as +// buttons, not as two axes. +#define BUTTON_AXIS 0x10000 + +// Query whether a given axis value describes a button axis. +#define IS_BUTTON_AXIS(axis) ((axis) >= 0 && ((axis) & BUTTON_AXIS) != 0) + +// Get the individual buttons from a button axis value. +#define BUTTON_AXIS_NEG(axis) ((axis) & 0xff) +#define BUTTON_AXIS_POS(axis) (((axis) >> 8) & 0xff) + +// Create a button axis value from two button values. +#define CREATE_BUTTON_AXIS(neg, pos) (BUTTON_AXIS | (neg) | ((pos) << 8)) + +// If this bit is set in an axis value, the axis is not actually a +// joystick axis, but is a "hat" axis. This means that we read (one of) +// the hats on the joystick. +#define HAT_AXIS 0x20000 + +#define IS_HAT_AXIS(axis) ((axis) >= 0 && ((axis) & HAT_AXIS) != 0) + +// Get the hat number from a hat axis value. +#define HAT_AXIS_HAT(axis) ((axis) & 0xff) +// Which axis of the hat? (horizonal or vertical) +#define HAT_AXIS_DIRECTION(axis) (((axis) >> 8) & 0xff) + +#define CREATE_HAT_AXIS(hat, direction) \ + (HAT_AXIS | (hat) | ((direction) << 8)) + +#define HAT_AXIS_HORIZONTAL 1 +#define HAT_AXIS_VERTICAL 2 + +void I_InitJoystick(void); +void I_ShutdownJoystick(void); +void I_UpdateJoystick(void); + +void I_BindJoystickVariables(void); + +#endif /* #ifndef __I_JOYSTICK__ */ + diff --git a/firmware_p4/components/Applications/doom/i_scale.c b/firmware_p4/components/Applications/doom/i_scale.c new file mode 100644 index 000000000..f88c69449 --- /dev/null +++ b/firmware_p4/components/Applications/doom/i_scale.c @@ -0,0 +1,1452 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Screen scale-up code: +// 1x,2x,3x,4x pixel doubling +// Aspect ratio-correcting stretch functions +// + +#include +#include +#include + +#include "doomtype.h" + +#include "i_video.h" +#include "m_argv.h" +#include "z_zone.h" + +#if defined(_MSC_VER) && !defined(__cplusplus) +#define inline __inline +#endif + +// Should be I_VideoBuffer + +static byte *src_buffer; + +// Destination buffer, ie. screen->pixels. + +static byte *dest_buffer; + +// Pitch of destination buffer, ie. screen->pitch. + +static int dest_pitch; + +// Lookup tables used for aspect ratio correction stretching code. +// stretch_tables[0] : 20% / 80% +// stretch_tables[1] : 40% / 60% +// All other combinations can be reached from these two tables. + +static byte *stretch_tables[2] = { NULL, NULL }; + +// 50%/50% stretch table, for 800x600 squash mode + +static byte *half_stretch_table = NULL; + +// Called to set the source and destination buffers before doing the +// scale. + +void I_InitScale(byte *_src_buffer, byte *_dest_buffer, int _dest_pitch) +{ + src_buffer = _src_buffer; + dest_buffer = _dest_buffer; + dest_pitch = _dest_pitch; +} + +// +// Pixel doubling scale-up functions. +// + +// 1x scale doesn't really do any scaling: it just copies the buffer +// a line at a time for when pitch != SCREENWIDTH (!native_surface) + +static boolean I_Scale1x(int x1, int y1, int x2, int y2) +{ + byte *bufp, *screenp; + int y; + int w = x2 - x1; + + // Need to byte-copy from buffer into the screen buffer + + bufp = src_buffer + y1 * SCREENWIDTH + x1; + screenp = (byte *) dest_buffer + y1 * dest_pitch + x1; + + for (y=y1; y 240) + + for (y=0; y 480) + + for (y=0; y 720) + + for (y=0; y 960) + + for (y=0; y 1200) + + for (y=0; y 0) + { + screenp = (byte *) dest_buffer + 2 * dest_pitch; + + for (y=0; y<1198; y += 3) + { + memset(screenp, 0, 1600); + + screenp += dest_pitch * 3; + } + } + + return true; +} + +screen_mode_t mode_stretch_5x = { + SCREENWIDTH * 5, SCREENHEIGHT_4_3 * 5, + I_InitStretchTables, + I_Stretch5x, + false, +}; + +// +// Aspect ratio correcting "squash" functions. +// +// These do the opposite of the "stretch" functions above: while the +// stretch functions increase the vertical dimensions, the squash +// functions decrease the horizontal dimensions for the same result. +// +// The same blend tables from the stretch functions are reused; as +// a result, the dimensions are *slightly* wrong (eg. 320x200 should +// squash to 266x200, but actually squashes to 256x200). +// + +// +// 1x squashed scale (256x200) +// + +static inline void WriteSquashedLine1x(byte *dest, byte *src) +{ + int x; + + for (x=0; x multiples of 320x240) + +extern screen_mode_t mode_stretch_1x; +extern screen_mode_t mode_stretch_2x; +extern screen_mode_t mode_stretch_3x; +extern screen_mode_t mode_stretch_4x; +extern screen_mode_t mode_stretch_5x; + +// Horizontally squashed modes (320x200 -> multiples of 256x200) + +extern screen_mode_t mode_squash_1x; +extern screen_mode_t mode_squash_2x; +extern screen_mode_t mode_squash_3x; +extern screen_mode_t mode_squash_4x; +extern screen_mode_t mode_squash_5x; + +#endif /* #ifndef __I_SCALE__ */ + diff --git a/firmware_p4/components/Applications/doom/i_sound.c b/firmware_p4/components/Applications/doom/i_sound.c new file mode 100644 index 000000000..c93e9ace4 --- /dev/null +++ b/firmware_p4/components/Applications/doom/i_sound.c @@ -0,0 +1,419 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: none +// + +#include +#include + +// HighBoy: SDL_mixer is not used — the DG_sound_module/DG_music_module backend +// lives in doom_sound_highboy.c. (i_sound.c itself uses no SDL symbols.) + +#include "config.h" +#include "doomfeatures.h" +#include "doomtype.h" + +#ifdef ORIGCODE +#include "gusconf.h" +#endif +#include "i_sound.h" +#include "i_video.h" +#include "m_argv.h" +#include "m_config.h" + +// Sound sample rate to use for digital output (Hz) + +int snd_samplerate = 44100; + +// Maximum number of bytes to dedicate to allocated sound effects. +// (Default: 64MB) + +int snd_cachesize = 64 * 1024 * 1024; + +// Config variable that controls the sound buffer size. +// We default to 28ms (1000 / 35fps = 1 buffer per tic). + +int snd_maxslicetime_ms = 28; + +// External command to invoke to play back music. + +char *snd_musiccmd = ""; + +// Low-level sound and music modules we are using + +static sound_module_t *sound_module = NULL; +static music_module_t *music_module = NULL; + +int snd_musicdevice = SNDDEVICE_SB; +int snd_sfxdevice = SNDDEVICE_SB; + +// DOS-specific options: These are unused but should be maintained +// so that the config file can be shared between chocolate +// doom and doom.exe + +static int snd_sbport = 0; +static int snd_sbirq = 0; +static int snd_sbdma = 0; +static int snd_mport = 0; + +// Compiled-in sound modules: + +static sound_module_t *sound_modules[] = +{ + #ifdef FEATURE_SOUND + &DG_sound_module, + #endif + NULL, +}; + +// Check if a sound device is in the given list of devices + +static boolean SndDeviceInList(snddevice_t device, snddevice_t *list, + int len) +{ + int i; + + for (i=0; isound_devices, + sound_modules[i]->num_sound_devices)) + { + // Initialize the module + + if (sound_modules[i]->Init(use_sfx_prefix)) + { + sound_module = sound_modules[i]; + return; + } + } + } +} + +// Initialize music according to snd_musicdevice. + +static void InitMusicModule(void) +{ +#ifdef FEATURE_SOUND + music_module = &DG_music_module; +#endif /* FEATURE_SOUND */ +} + +// +// Initializes sound stuff, including volume +// Sets channels, SFX and music volume, +// allocates channel buffer, sets S_sfx lookup. +// + +void I_InitSound(boolean use_sfx_prefix) +{ + boolean nosound, nosfx, nomusic; + + //! + // @vanilla + // + // Disable all sound output. + // + + nosound = M_CheckParm("-nosound") > 0; + + //! + // @vanilla + // + // Disable sound effects. + // + + nosfx = M_CheckParm("-nosfx") > 0; + + //! + // @vanilla + // + // Disable music. + // + + nomusic = M_CheckParm("-nomusic") > 0; + + // Initialize the sound and music subsystems. + + if (!nosound && !screensaver_mode) + { + // This is kind of a hack. If native MIDI is enabled, set up + // the TIMIDITY_CFG environment variable here before SDL_mixer + // is opened. + + if (!nomusic + && (snd_musicdevice == SNDDEVICE_GENMIDI + || snd_musicdevice == SNDDEVICE_GUS)) + { + //I_InitTimidityConfig(); + } + + if (!nosfx) + { + InitSfxModule(use_sfx_prefix); + } + + if (!nomusic) + { + InitMusicModule(); + } + } + +} + +void I_ShutdownSound(void) +{ + if (sound_module != NULL) + { + sound_module->Shutdown(); + } + + if (music_module != NULL) + { + music_module->Shutdown(); + } +} + +int I_GetSfxLumpNum(sfxinfo_t *sfxinfo) +{ + if (sound_module != NULL) + { + return sound_module->GetSfxLumpNum(sfxinfo); + } + else + { + return 0; + } +} + +void I_UpdateSound(void) +{ + if (sound_module != NULL) + { + sound_module->Update(); + } + + if (music_module != NULL && music_module->Poll != NULL) + { + music_module->Poll(); + } +} + +static void CheckVolumeSeparation(int *vol, int *sep) +{ + if (*sep < 0) + { + *sep = 0; + } + else if (*sep > 254) + { + *sep = 254; + } + + if (*vol < 0) + { + *vol = 0; + } + else if (*vol > 127) + { + *vol = 127; + } +} + +void I_UpdateSoundParams(int channel, int vol, int sep) +{ + if (sound_module != NULL) + { + CheckVolumeSeparation(&vol, &sep); + sound_module->UpdateSoundParams(channel, vol, sep); + } +} + +int I_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep) +{ + if (sound_module != NULL) + { + CheckVolumeSeparation(&vol, &sep); + return sound_module->StartSound(sfxinfo, channel, vol, sep); + } + else + { + return 0; + } +} + +void I_StopSound(int channel) +{ + if (sound_module != NULL) + { + sound_module->StopSound(channel); + } +} + +boolean I_SoundIsPlaying(int channel) +{ + if (sound_module != NULL) + { + return sound_module->SoundIsPlaying(channel); + } + else + { + return false; + } +} + +void I_PrecacheSounds(sfxinfo_t *sounds, int num_sounds) +{ + if (sound_module != NULL && sound_module->CacheSounds != NULL) + { + sound_module->CacheSounds(sounds, num_sounds); + } +} + +void I_InitMusic(void) +{ + if(music_module != NULL) + { + music_module->Init(); + } +} + +void I_ShutdownMusic(void) +{ + +} + +void I_SetMusicVolume(int volume) +{ + if (music_module != NULL) + { + music_module->SetMusicVolume(volume); + } +} + +void I_PauseSong(void) +{ + if (music_module != NULL) + { + music_module->PauseMusic(); + } +} + +void I_ResumeSong(void) +{ + if (music_module != NULL) + { + music_module->ResumeMusic(); + } +} + +void *I_RegisterSong(void *data, int len) +{ + if (music_module != NULL) + { + return music_module->RegisterSong(data, len); + } + else + { + return NULL; + } +} + +void I_UnRegisterSong(void *handle) +{ + if (music_module != NULL) + { + music_module->UnRegisterSong(handle); + } +} + +void I_PlaySong(void *handle, boolean looping) +{ + if (music_module != NULL) + { + music_module->PlaySong(handle, looping); + } +} + +void I_StopSong(void) +{ + if (music_module != NULL) + { + music_module->StopSong(); + } +} + +boolean I_MusicIsPlaying(void) +{ + if (music_module != NULL) + { + return music_module->MusicIsPlaying(); + } + else + { + return false; + } + +} + +void I_BindSoundVariables(void) +{ + extern int use_libsamplerate; + extern float libsamplerate_scale; + + M_BindVariable("snd_musicdevice", &snd_musicdevice); + M_BindVariable("snd_sfxdevice", &snd_sfxdevice); + M_BindVariable("snd_sbport", &snd_sbport); + M_BindVariable("snd_sbirq", &snd_sbirq); + M_BindVariable("snd_sbdma", &snd_sbdma); + M_BindVariable("snd_mport", &snd_mport); + M_BindVariable("snd_maxslicetime_ms", &snd_maxslicetime_ms); + M_BindVariable("snd_musiccmd", &snd_musiccmd); + M_BindVariable("snd_samplerate", &snd_samplerate); + M_BindVariable("snd_cachesize", &snd_cachesize); + +#ifdef FEATURE_SOUND + M_BindVariable("use_libsamplerate", &use_libsamplerate); + M_BindVariable("libsamplerate_scale", &libsamplerate_scale); +#endif + + // Before SDL_mixer version 1.2.11, MIDI music caused the game + // to crash when it looped. If this is an old SDL_mixer version, + // disable MIDI. +} + diff --git a/firmware_p4/components/Applications/doom/i_sound.h b/firmware_p4/components/Applications/doom/i_sound.h new file mode 100644 index 000000000..e31b5e58e --- /dev/null +++ b/firmware_p4/components/Applications/doom/i_sound.h @@ -0,0 +1,256 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// The not so system specific sound interface. +// + + +#ifndef __I_SOUND__ +#define __I_SOUND__ + +#include "doomtype.h" + + +// +// SoundFX struct. +// +typedef struct sfxinfo_struct sfxinfo_t; + +struct sfxinfo_struct +{ + // tag name, used for hexen. + char *tagname; + + // lump name. If we are running with use_sfx_prefix=true, a + // 'DS' (or 'DP' for PC speaker sounds) is prepended to this. + + char name[9]; + + // Sfx priority + int priority; + + // referenced sound if a link + sfxinfo_t *link; + + // pitch if a link + int pitch; + + // volume if a link + int volume; + + // this is checked every second to see if sound + // can be thrown out (if 0, then decrement, if -1, + // then throw out, if > 0, then it is in use) + int usefulness; + + // lump number of sfx + int lumpnum; + + // Maximum number of channels that the sound can be played on + // (Heretic) + int numchannels; + + // data used by the low level code + void *driver_data; +}; + +// +// MusicInfo struct. +// +typedef struct +{ + // up to 6-character name + char *name; + + // lump number of music + int lumpnum; + + // music data + void *data; + + // music handle once registered + void *handle; + +} musicinfo_t; + +typedef enum +{ + SNDDEVICE_NONE = 0, + SNDDEVICE_PCSPEAKER = 1, + SNDDEVICE_ADLIB = 2, + SNDDEVICE_SB = 3, + SNDDEVICE_PAS = 4, + SNDDEVICE_GUS = 5, + SNDDEVICE_WAVEBLASTER = 6, + SNDDEVICE_SOUNDCANVAS = 7, + SNDDEVICE_GENMIDI = 8, + SNDDEVICE_AWE32 = 9, + SNDDEVICE_CD = 10, +} snddevice_t; + +// Interface for sound modules + +typedef struct +{ + // List of sound devices that this sound module is used for. + + snddevice_t *sound_devices; + int num_sound_devices; + + // Initialise sound module + // Returns true if successfully initialised + + boolean (*Init)(boolean use_sfx_prefix); + + // Shutdown sound module + + void (*Shutdown)(void); + + // Returns the lump index of the given sound. + + int (*GetSfxLumpNum)(sfxinfo_t *sfxinfo); + + // Called periodically to update the subsystem. + + void (*Update)(void); + + // Update the sound settings on the given channel. + + void (*UpdateSoundParams)(int channel, int vol, int sep); + + // Start a sound on a given channel. Returns the channel id + // or -1 on failure. + + int (*StartSound)(sfxinfo_t *sfxinfo, int channel, int vol, int sep); + + // Stop the sound playing on the given channel. + + void (*StopSound)(int channel); + + // Query if a sound is playing on the given channel + + boolean (*SoundIsPlaying)(int channel); + + // Called on startup to precache sound effects (if necessary) + + void (*CacheSounds)(sfxinfo_t *sounds, int num_sounds); + +} sound_module_t; + +void I_InitSound(boolean use_sfx_prefix); +void I_ShutdownSound(void); +int I_GetSfxLumpNum(sfxinfo_t *sfxinfo); +void I_UpdateSound(void); +void I_UpdateSoundParams(int channel, int vol, int sep); +int I_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep); +void I_StopSound(int channel); +boolean I_SoundIsPlaying(int channel); +void I_PrecacheSounds(sfxinfo_t *sounds, int num_sounds); + +// Interface for music modules + +typedef struct +{ + // List of sound devices that this music module is used for. + + snddevice_t *sound_devices; + int num_sound_devices; + + // Initialise the music subsystem + + boolean (*Init)(void); + + // Shutdown the music subsystem + + void (*Shutdown)(void); + + // Set music volume - range 0-127 + + void (*SetMusicVolume)(int volume); + + // Pause music + + void (*PauseMusic)(void); + + // Un-pause music + + void (*ResumeMusic)(void); + + // Register a song handle from data + // Returns a handle that can be used to play the song + + void *(*RegisterSong)(void *data, int len); + + // Un-register (free) song data + + void (*UnRegisterSong)(void *handle); + + // Play the song + + void (*PlaySong)(void *handle, boolean looping); + + // Stop playing the current song. + + void (*StopSong)(void); + + // Query if music is playing. + + boolean (*MusicIsPlaying)(void); + + // Invoked periodically to poll. + + void (*Poll)(void); +} music_module_t; + +void I_InitMusic(void); +void I_ShutdownMusic(void); +void I_SetMusicVolume(int volume); +void I_PauseSong(void); +void I_ResumeSong(void); +void *I_RegisterSong(void *data, int len); +void I_UnRegisterSong(void *handle); +void I_PlaySong(void *handle, boolean looping); +void I_StopSong(void); +boolean I_MusicIsPlaying(void); + +extern int snd_sfxdevice; +extern int snd_musicdevice; +extern int snd_samplerate; +extern int snd_cachesize; +extern int snd_maxslicetime_ms; +extern char *snd_musiccmd; + +void I_BindSoundVariables(void); + +// Sound modules + +void I_InitTimidityConfig(void); +#ifdef FEATURE_SOUND +extern sound_module_t DG_sound_module; +extern music_module_t DG_music_module; +#endif +extern sound_module_t sound_pcsound_module; +extern music_module_t music_opl_module; + +// For OPL module: + +extern int opl_io_port; + +// For native music module: + +extern char *timidity_cfg_path; + +#endif + diff --git a/firmware_p4/components/Applications/doom/i_swap.h b/firmware_p4/components/Applications/doom/i_swap.h new file mode 100644 index 000000000..fa45d4c91 --- /dev/null +++ b/firmware_p4/components/Applications/doom/i_swap.h @@ -0,0 +1,73 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Endianess handling, swapping 16bit and 32bit. +// + + +#ifndef __I_SWAP__ +#define __I_SWAP__ + +#ifdef __DJGPP__ + + +#define SHORT(x) ((signed short) (x)) +#define LONG(x) ((signed int) (x)) + +#define SYS_LITTLE_ENDIAN + + +#else // __DJGPP__ + + +#if ( __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ ) +#define SYS_LITTLE_ENDIAN +#define SHORT(x) ((signed short) (x)) +#define LONG(x) ((signed int) (x)) +#elif ( __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ ) +#define SYS_BIG_ENDIAN + +static inline unsigned short swapLE16(unsigned short val) { + return ((val << 8) | (val >> 8)); +} + +static inline unsigned long swapLE32(unsigned long val) { + return ((val << 24) | ((val << 8) & 0x00FF0000) | ((val >> 8) & 0x0000FF00) | (val >> 24)); +} + +#define SHORT(x) ((signed short) swapLE16(x)) +#define LONG(x) ((signed int) swapLE32(x)) +#else +#error "Unknown byte order" +#endif + + +// cosmito from lsdldoom +#define doom_swap_s(x) \ + ((short int)((((unsigned short int)(x) & 0x00ff) << 8) | \ + (((unsigned short int)(x) & 0xff00) >> 8))) + +#if ( __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ ) +#define doom_wtohs(x) doom_swap_s(x) +#else +#define doom_wtohs(x) (short int)(x) +#endif + + +#endif // __DJGPP__ + + +#endif + diff --git a/firmware_p4/components/Applications/doom/i_system.c b/firmware_p4/components/Applications/doom/i_system.c new file mode 100644 index 000000000..53ab2c945 --- /dev/null +++ b/firmware_p4/components/Applications/doom/i_system.c @@ -0,0 +1,578 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// + + + +#include +#include +#include + +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else +#include +#endif + +#ifdef ORIGCODE +#include "SDL.h" +#endif + +#include "config.h" + +#include "deh_str.h" +#include "doomtype.h" +#include "m_argv.h" +#include "m_config.h" +#include "m_misc.h" +#include "i_joystick.h" +#include "i_sound.h" +#include "i_timer.h" +#include "i_video.h" + +#include "i_system.h" + +#include "w_wad.h" +#include "z_zone.h" + +#ifdef __MACOSX__ +#include +#endif + +#define DEFAULT_RAM 6 /* MiB */ +#define MIN_RAM 6 /* MiB */ + + +typedef struct atexit_listentry_s atexit_listentry_t; + +struct atexit_listentry_s +{ + atexit_func_t func; + boolean run_on_error; + atexit_listentry_t *next; +}; + +static atexit_listentry_t *exit_funcs = NULL; + +void I_AtExit(atexit_func_t func, boolean run_on_error) +{ + atexit_listentry_t *entry; + + entry = malloc(sizeof(*entry)); + + entry->func = func; + entry->run_on_error = run_on_error; + entry->next = exit_funcs; + exit_funcs = entry; +} + +// Tactile feedback function, probably used for the Logitech Cyberman + +void I_Tactile(int on, int off, int total) +{ +} + +// Zone memory auto-allocation function that allocates the zone size +// by trying progressively smaller zone sizes until one is found that +// works. + +static byte *AutoAllocMemory(int *size, int default_ram, int min_ram) +{ + byte *zonemem; + + // Allocate the zone memory. This loop tries progressively smaller + // zone sizes until a size is found that can be allocated. + // If we used the -mb command line parameter, only the parameter + // provided is accepted. + + zonemem = NULL; + + while (zonemem == NULL) + { + // We need a reasonable minimum amount of RAM to start. + + if (default_ram < min_ram) + { + I_Error("Unable to allocate %i MiB of RAM for zone", default_ram); + } + + // Try to allocate the zone memory. + + *size = default_ram * 1024 * 1024; + + zonemem = malloc(*size); + + // Failed to allocate? Reduce zone size until we reach a size + // that is acceptable. + + if (zonemem == NULL) + { + default_ram -= 1; + } + } + + return zonemem; +} + +byte *I_ZoneBase (int *size) +{ + byte *zonemem; + int min_ram, default_ram; + int p; + + //! + // @arg + // + // Specify the heap size, in MiB (default 16). + // + + p = M_CheckParmWithArgs("-mb", 1); + + if (p > 0) + { + default_ram = atoi(myargv[p+1]); + min_ram = default_ram; + } + else + { + default_ram = DEFAULT_RAM; + min_ram = MIN_RAM; + } + + zonemem = AutoAllocMemory(size, default_ram, min_ram); + + printf("zone memory: %p, %x allocated for zone\n", + zonemem, *size); + + return zonemem; +} + +void I_PrintBanner(char *msg) +{ + int i; + int spaces = 35 - (strlen(msg) / 2); + + for (i=0; ifunc(); + entry = entry->next; + } + +#if ORIGCODE + SDL_Quit(); + + exit(0); +#endif +} + +#if !defined(_WIN32) && !defined(__MACOSX__) && !defined(__DJGPP__) +#define ZENITY_BINARY "/usr/bin/zenity" + +// returns non-zero if zenity is available + +static int ZenityAvailable(void) +{ + return system(ZENITY_BINARY " --help >/dev/null 2>&1") == 0; +} + +// Escape special characters in the given string so that they can be +// safely enclosed in shell quotes. + +static char *EscapeShellString(char *string) +{ + char *result; + char *r, *s; + + // In the worst case, every character might be escaped. + result = malloc(strlen(string) * 2 + 3); + r = result; + + // Enclosing quotes. + *r = '"'; + ++r; + + for (s = string; *s != '\0'; ++s) + { + // From the bash manual: + // + // "Enclosing characters in double quotes preserves the literal + // value of all characters within the quotes, with the exception + // of $, `, \, and, when history expansion is enabled, !." + // + // Therefore, escape these characters by prefixing with a backslash. + + if (strchr("$`\\!", *s) != NULL) + { + *r = '\\'; + ++r; + } + + *r = *s; + ++r; + } + + // Enclosing quotes. + *r = '"'; + ++r; + *r = '\0'; + + return result; +} + +// Open a native error box with a message using zenity + +static int ZenityErrorBox(char *message) +{ + int result; + char *escaped_message; + char *errorboxpath; + static size_t errorboxpath_size; + + if (!ZenityAvailable()) + { + return 0; + } + + escaped_message = EscapeShellString(message); + + errorboxpath_size = strlen(ZENITY_BINARY) + strlen(escaped_message) + 19; + errorboxpath = malloc(errorboxpath_size); + M_snprintf(errorboxpath, errorboxpath_size, "%s --error --text=%s", + ZENITY_BINARY, escaped_message); + + result = system(errorboxpath); + + free(errorboxpath); + free(escaped_message); + + return result; +} + +#endif /* !defined(_WIN32) && !defined(__MACOSX__) && !defined(__DJGPP__) */ + + +// +// I_Error +// + +static boolean already_quitting = false; + +void I_Error (char *error, ...) +{ + char msgbuf[512]; + va_list argptr; + atexit_listentry_t *entry; + boolean exit_gui_popup; + + if (already_quitting) + { + fprintf(stderr, "Warning: recursive call to I_Error detected.\n"); +#if ORIGCODE + exit(-1); +#endif + } + else + { + already_quitting = true; + } + + // Message first. + va_start(argptr, error); + //fprintf(stderr, "\nError: "); + vfprintf(stderr, error, argptr); + fprintf(stderr, "\n\n"); + va_end(argptr); + fflush(stderr); + + // Write a copy of the message into buffer. + va_start(argptr, error); + memset(msgbuf, 0, sizeof(msgbuf)); + M_vsnprintf(msgbuf, sizeof(msgbuf), error, argptr); + va_end(argptr); + + // Shutdown. Here might be other errors. + + entry = exit_funcs; + + while (entry != NULL) + { + if (entry->run_on_error) + { + entry->func(); + } + + entry = entry->next; + } + + exit_gui_popup = !M_ParmExists("-nogui"); + + // Pop up a GUI dialog box to show the error message, if the + // game was not run from the console (and the user will + // therefore be unable to otherwise see the message). + if (exit_gui_popup && !I_ConsoleStdout()) +#ifdef _WIN32 + { + wchar_t wmsgbuf[512]; + + MultiByteToWideChar(CP_ACP, 0, + msgbuf, strlen(msgbuf) + 1, + wmsgbuf, sizeof(wmsgbuf)); + + MessageBoxW(NULL, wmsgbuf, L"", MB_OK); + } +#elif defined(__MACOSX__) + { + CFStringRef message; + int i; + + // The CoreFoundation message box wraps text lines, so replace + // newline characters with spaces so that multiline messages + // are continuous. + + for (i = 0; msgbuf[i] != '\0'; ++i) + { + if (msgbuf[i] == '\n') + { + msgbuf[i] = ' '; + } + } + + message = CFStringCreateWithCString(NULL, msgbuf, + kCFStringEncodingUTF8); + + CFUserNotificationDisplayNotice(0, + kCFUserNotificationCautionAlertLevel, + NULL, + NULL, + NULL, + CFSTR(PACKAGE_STRING), + message, + NULL); + } +#elif defined(__DJGPP__) + { + printf("%s\n", msgbuf); + exit(-1); + } + +#else + { + ZenityErrorBox(msgbuf); + } +#endif + + // abort(); +#if ORIGCODE + SDL_Quit(); + + exit(-1); +#else + exit(-1); +#endif +} + +// +// Read Access Violation emulation. +// +// From PrBoom+, by entryway. +// + +// C:\>debug +// -d 0:0 +// +// DOS 6.22: +// 0000:0000 (57 92 19 00) F4 06 70 00-(16 00) +// DOS 7.1: +// 0000:0000 (9E 0F C9 00) 65 04 70 00-(16 00) +// Win98: +// 0000:0000 (9E 0F C9 00) 65 04 70 00-(16 00) +// DOSBox under XP: +// 0000:0000 (00 00 00 F1) ?? ?? ?? 00-(07 00) + +#define DOS_MEM_DUMP_SIZE 10 + +static const unsigned char mem_dump_dos622[DOS_MEM_DUMP_SIZE] = { + 0x57, 0x92, 0x19, 0x00, 0xF4, 0x06, 0x70, 0x00, 0x16, 0x00}; +static const unsigned char mem_dump_win98[DOS_MEM_DUMP_SIZE] = { + 0x9E, 0x0F, 0xC9, 0x00, 0x65, 0x04, 0x70, 0x00, 0x16, 0x00}; +static const unsigned char mem_dump_dosbox[DOS_MEM_DUMP_SIZE] = { + 0x00, 0x00, 0x00, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00}; +static unsigned char mem_dump_custom[DOS_MEM_DUMP_SIZE]; + +static const unsigned char *dos_mem_dump = mem_dump_dos622; + +boolean I_GetMemoryValue(unsigned int offset, void *value, int size) +{ + static boolean firsttime = true; + + if (firsttime) + { + int p, i, val; + + firsttime = false; + i = 0; + + //! + // @category compat + // @arg + // + // Specify DOS version to emulate for NULL pointer dereference + // emulation. Supported versions are: dos622, dos71, dosbox. + // The default is to emulate DOS 7.1 (Windows 98). + // + + p = M_CheckParmWithArgs("-setmem", 1); + + if (p > 0) + { + if (!strcasecmp(myargv[p + 1], "dos622")) + { + dos_mem_dump = mem_dump_dos622; + } + if (!strcasecmp(myargv[p + 1], "dos71")) + { + dos_mem_dump = mem_dump_win98; + } + else if (!strcasecmp(myargv[p + 1], "dosbox")) + { + dos_mem_dump = mem_dump_dosbox; + } + else + { + for (i = 0; i < DOS_MEM_DUMP_SIZE; ++i) + { + ++p; + + if (p >= myargc || myargv[p][0] == '-') + { + break; + } + + M_StrToInt(myargv[p], &val); + mem_dump_custom[i++] = (unsigned char) val; + } + + dos_mem_dump = mem_dump_custom; + } + } + } + + switch (size) + { + case 1: + *((unsigned char *) value) = dos_mem_dump[offset]; + return true; + case 2: + *((unsigned short *) value) = dos_mem_dump[offset] + | (dos_mem_dump[offset + 1] << 8); + return true; + case 4: + *((unsigned int *) value) = dos_mem_dump[offset] + | (dos_mem_dump[offset + 1] << 8) + | (dos_mem_dump[offset + 2] << 16) + | (dos_mem_dump[offset + 3] << 24); + return true; + } + + return false; +} + diff --git a/firmware_p4/components/Applications/doom/i_system.h b/firmware_p4/components/Applications/doom/i_system.h new file mode 100644 index 000000000..b65daff00 --- /dev/null +++ b/firmware_p4/components/Applications/doom/i_system.h @@ -0,0 +1,84 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// System specific interface stuff. +// + + +#ifndef __I_SYSTEM__ +#define __I_SYSTEM__ + +#include "d_ticcmd.h" +#include "d_event.h" + + +typedef void (*atexit_func_t)(void); + +// Called by DoomMain. +void I_Init (void); + +// Called by startup code +// to get the ammount of memory to malloc +// for the zone management. +byte* I_ZoneBase (int *size); + +boolean I_ConsoleStdout(void); + + +// Asynchronous interrupt functions should maintain private queues +// that are read by the synchronous functions +// to be converted into events. + +// Either returns a null ticcmd, +// or calls a loadable driver to build it. +// This ticcmd will then be modified by the gameloop +// for normal input. +ticcmd_t* I_BaseTiccmd (void); + + +// Called by M_Responder when quit is selected. +// Clean exit, displays sell blurb. +void I_Quit (void); + +void I_Error (char *error, ...); + +void I_Tactile (int on, int off, int total); + +boolean I_GetMemoryValue(unsigned int offset, void *value, int size); + +// Schedule a function to be called when the program exits. +// If run_if_error is true, the function is called if the exit +// is due to an error (I_Error) + +void I_AtExit(atexit_func_t func, boolean run_if_error); + +// Add all system-specific config file variable bindings. + +void I_BindVariables(void); + +// Print startup banner copyright message. + +void I_PrintStartupBanner(char *gamedescription); + +// Print a centered text banner displaying the given string. + +void I_PrintBanner(char *text); + +// Print a dividing line for startup banners. + +void I_PrintDivider(void); + +#endif + diff --git a/firmware_p4/components/Applications/doom/i_timer.c b/firmware_p4/components/Applications/doom/i_timer.c new file mode 100644 index 000000000..0a85c722b --- /dev/null +++ b/firmware_p4/components/Applications/doom/i_timer.c @@ -0,0 +1,96 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Timer functions. +// + +#include "i_timer.h" +#include "doomtype.h" + +#include "doomgeneric.h" + +#include + +//#include +//#include + + +// +// I_GetTime +// returns time in 1/35th second tics +// + +static uint32_t basetime = 0; + + +int I_GetTicks(void) +{ + return DG_GetTicksMs(); +} + +int I_GetTime (void) +{ + uint32_t ticks; + + ticks = I_GetTicks(); + + if (basetime == 0) + basetime = ticks; + + ticks -= basetime; + + return (ticks * TICRATE) / 1000; +} + + +// +// Same as I_GetTime, but returns time in milliseconds +// + +int I_GetTimeMS(void) +{ + uint32_t ticks; + + ticks = I_GetTicks(); + + if (basetime == 0) + basetime = ticks; + + return ticks - basetime; +} + +// Sleep for a specified number of ms + +void I_Sleep(int ms) +{ + //SDL_Delay(ms); + //usleep (ms * 1000); + + DG_SleepMs(ms); +} + +void I_WaitVBL(int count) +{ + //I_Sleep((count * 1000) / 70); +} + + +void I_InitTimer(void) +{ + // initialize timer + + //SDL_Init(SDL_INIT_TIMER); +} + diff --git a/firmware_p4/components/Applications/doom/i_timer.h b/firmware_p4/components/Applications/doom/i_timer.h new file mode 100644 index 000000000..9b3dbb8d1 --- /dev/null +++ b/firmware_p4/components/Applications/doom/i_timer.h @@ -0,0 +1,42 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// System-specific timer interface +// + + +#ifndef __I_TIMER__ +#define __I_TIMER__ + +#define TICRATE 35 + +// Called by D_DoomLoop, +// returns current time in tics. +int I_GetTime (void); + +// returns current time in ms +int I_GetTimeMS (void); + +// Pause for a specified number of ms +void I_Sleep(int ms); + +// Initialize timer +void I_InitTimer(void); + +// Wait for vertical retrace or pause a bit. +void I_WaitVBL(int count); + +#endif + diff --git a/firmware_p4/components/Applications/doom/i_video.c b/firmware_p4/components/Applications/doom/i_video.c new file mode 100644 index 000000000..84bebdeb6 --- /dev/null +++ b/firmware_p4/components/Applications/doom/i_video.c @@ -0,0 +1,495 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// $Id:$ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// 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 +// of the License, 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. +// +// $Log:$ +// +// DESCRIPTION: +// DOOM graphics stuff for X11, UNIX. +// +//----------------------------------------------------------------------------- + +static const char +rcsid[] = "$Id: i_x.c,v 1.6 1997/02/03 22:45:10 b1 Exp $"; + +#include "config.h" +#include "v_video.h" +#include "m_argv.h" +#include "d_event.h" +#include "d_main.h" +#include "i_video.h" +#include "i_system.h" +#include "z_zone.h" + +#include "tables.h" +#include "doomkeys.h" + +#include "doomgeneric.h" + +#include +#include + +#include + +#include + +#include + +//#define CMAP256 + +struct FB_BitField +{ + uint32_t offset; /* beginning of bitfield */ + uint32_t length; /* length of bitfield */ +}; + +struct FB_ScreenInfo +{ + uint32_t xres; /* visible resolution */ + uint32_t yres; + uint32_t xres_virtual; /* virtual resolution */ + uint32_t yres_virtual; + + uint32_t bits_per_pixel; /* guess what */ + + /* >1 = FOURCC */ + struct FB_BitField red; /* bitfield in s_Fb mem if true color, */ + struct FB_BitField green; /* else only length is significant */ + struct FB_BitField blue; + struct FB_BitField transp; /* transparency */ +}; + +static struct FB_ScreenInfo s_Fb; +int fb_scaling = 1; +int usemouse = 0; + + +#ifdef CMAP256 + +boolean palette_changed; +struct color colors[256]; + +#else // CMAP256 + +static struct color colors[256]; + + +#endif // CMAP256 + + +void I_GetEvent(void); + +// The screen buffer; this is modified to draw things to the screen + +byte *I_VideoBuffer = NULL; + +// If true, game is running as a screensaver + +boolean screensaver_mode = false; + +// Flag indicating whether the screen is currently visible: +// when the screen isnt visible, don't render the screen + +boolean screenvisible; + +// Mouse acceleration +// +// This emulates some of the behavior of DOS mouse drivers by increasing +// the speed when the mouse is moved fast. +// +// The mouse input values are input directly to the game, but when +// the values exceed the value of mouse_threshold, they are multiplied +// by mouse_acceleration to increase the speed. + +float mouse_acceleration = 2.0; +int mouse_threshold = 10; + +// Gamma correction level to use + +int usegamma = 0; + +typedef struct +{ + byte r; + byte g; + byte b; +} col_t; + +// Palette converted to RGB565 + +static uint16_t rgb565_palette[256]; + +void cmap_to_rgb565(uint16_t * out, uint8_t * in, int in_pixels) +{ + int i, j; + struct color c; + uint16_t r, g, b; + + for (i = 0; i < in_pixels; i++) + { + c = colors[*in]; + r = ((uint16_t)(c.r >> 3)) << 11; + g = ((uint16_t)(c.g >> 2)) << 5; + b = ((uint16_t)(c.b >> 3)) << 0; + *out = (r | g | b); + + in++; + for (j = 0; j < fb_scaling; j++) { + out++; + } + } +} + +void cmap_to_fb(uint8_t *out, uint8_t *in, int in_pixels) +{ + int i, k; + struct color c; + uint32_t pix; + + for (i = 0; i < in_pixels; i++) + { + c = colors[*in]; // R:8 G:8 B:8 + + if (s_Fb.bits_per_pixel == 16) + { + // RGB565 packing + uint16_t p = ((c.r & 0xF8) << 8) | + ((c.g & 0xFC) << 3) | + (c.b >> 3); + +#ifdef SYS_BIG_ENDIAN + p = swapeLE16(p); // can't use SHORT() because this needs to stay unsigned +#endif + for (k = 0; k < fb_scaling; k++) { + *(uint16_t *)out = p; + out += 2; + } + } + else if (s_Fb.bits_per_pixel == 32) + { + // Assuming RGBA8888 + pix = (c.r << s_Fb.red.offset) | + (c.g << s_Fb.green.offset) | + (c.b << s_Fb.blue.offset); + +#ifdef SYS_BIG_ENDIAN + pix = swapLE32(pix); +#endif + for (k = 0; k < fb_scaling; k++) { + *(uint32_t *)out = pix; + out += 4; + } + } + else { + // no clue how to convert this + I_Error("No idea how to convert %d bpp pixels", s_Fb.bits_per_pixel); + } + + in++; + } +} + +void I_InitGraphics (void) +{ + int i, gfxmodeparm; + char *mode; + + memset(&s_Fb, 0, sizeof(struct FB_ScreenInfo)); + s_Fb.xres = DOOMGENERIC_RESX; + s_Fb.yres = DOOMGENERIC_RESY; + s_Fb.xres_virtual = s_Fb.xres; + s_Fb.yres_virtual = s_Fb.yres; + +#ifdef CMAP256 + + s_Fb.bits_per_pixel = 8; + +#else // CMAP256 + + gfxmodeparm = M_CheckParmWithArgs("-gfxmode", 1); + + if (gfxmodeparm) { + mode = myargv[gfxmodeparm + 1]; + } + else { + // default to rgba8888 like the old behavior, for compatibility + // maybe could warn here? + mode = "rgba8888"; + } + + if (strcmp(mode, "rgba8888") == 0) { + // default mode + s_Fb.bits_per_pixel = 32; + + s_Fb.blue.length = 8; + s_Fb.green.length = 8; + s_Fb.red.length = 8; + s_Fb.transp.length = 8; + + s_Fb.blue.offset = 0; + s_Fb.green.offset = 8; + s_Fb.red.offset = 16; + s_Fb.transp.offset = 24; + } + + else if (strcmp(mode, "rgb565") == 0) { + s_Fb.bits_per_pixel = 16; + + s_Fb.blue.length = 5; + s_Fb.green.length = 6; + s_Fb.red.length = 5; + s_Fb.transp.length = 0; + + s_Fb.blue.offset = 11; + s_Fb.green.offset = 5; + s_Fb.red.offset = 0; + s_Fb.transp.offset = 16; + } + else + I_Error("Unknown gfxmode value: %s\n", mode); + + +#endif // CMAP256 + + printf("I_InitGraphics: framebuffer: x_res: %d, y_res: %d, x_virtual: %d, y_virtual: %d, bpp: %d\n", + s_Fb.xres, s_Fb.yres, s_Fb.xres_virtual, s_Fb.yres_virtual, s_Fb.bits_per_pixel); + + printf("I_InitGraphics: framebuffer: RGBA: %d%d%d%d, red_off: %d, green_off: %d, blue_off: %d, transp_off: %d\n", + s_Fb.red.length, s_Fb.green.length, s_Fb.blue.length, s_Fb.transp.length, s_Fb.red.offset, s_Fb.green.offset, s_Fb.blue.offset, s_Fb.transp.offset); + + printf("I_InitGraphics: DOOM screen size: w x h: %d x %d\n", SCREENWIDTH, SCREENHEIGHT); + + + i = M_CheckParmWithArgs("-scaling", 1); + if (i > 0) { + i = atoi(myargv[i + 1]); + fb_scaling = i; + printf("I_InitGraphics: Scaling factor: %d\n", fb_scaling); + } else { + fb_scaling = s_Fb.xres / SCREENWIDTH; + if (s_Fb.yres / SCREENHEIGHT < fb_scaling) + fb_scaling = s_Fb.yres / SCREENHEIGHT; + printf("I_InitGraphics: Auto-scaling factor: %d\n", fb_scaling); + } + + + /* Allocate screen to draw to */ + I_VideoBuffer = (byte*)Z_Malloc (SCREENWIDTH * SCREENHEIGHT, PU_STATIC, NULL); // For DOOM to draw on + + screenvisible = true; + + extern void I_InitInput(void); + I_InitInput(); +} + +void I_ShutdownGraphics (void) +{ + Z_Free (I_VideoBuffer); +} + +void I_StartFrame (void) +{ + +} + +void I_StartTic (void) +{ + I_GetEvent(); +} + +void I_UpdateNoBlit (void) +{ +} + +// +// I_FinishUpdate +// + +void I_FinishUpdate (void) +{ + int y; + int x_offset, y_offset, x_offset_end; + unsigned char *line_in, *line_out; + + /* Offsets in case FB is bigger than DOOM */ + /* 600 = s_Fb heigt, 200 screenheight */ + /* 600 = s_Fb heigt, 200 screenheight */ + /* 2048 =s_Fb width, 320 screenwidth */ + y_offset = (((s_Fb.yres - (SCREENHEIGHT * fb_scaling)) * s_Fb.bits_per_pixel/8)) / 2; + x_offset = (((s_Fb.xres - (SCREENWIDTH * fb_scaling)) * s_Fb.bits_per_pixel/8)) / 2; // XXX: siglent FB hack: /4 instead of /2, since it seems to handle the resolution in a funny way + //x_offset = 0; + x_offset_end = ((s_Fb.xres - (SCREENWIDTH * fb_scaling)) * s_Fb.bits_per_pixel/8) - x_offset; + + /* DRAW SCREEN */ + line_in = (unsigned char *) I_VideoBuffer; + line_out = (unsigned char *) DG_ScreenBuffer; + + y = SCREENHEIGHT; + + while (y--) + { + int i; + for (i = 0; i < fb_scaling; i++) { + line_out += x_offset; +#ifdef CMAP256 + if (fb_scaling == 1) { + memcpy(line_out, line_in, SCREENWIDTH); /* fb_width is bigger than Doom SCREENWIDTH... */ + } else { + int j; + + for (j = 0; j < SCREENWIDTH; j++) { + int k; + for (k = 0; k < fb_scaling; k++) { + line_out[j * fb_scaling + k] = line_in[j]; + } + } + } +#else + //cmap_to_rgb565((void*)line_out, (void*)line_in, SCREENWIDTH); + cmap_to_fb((void*)line_out, (void*)line_in, SCREENWIDTH); +#endif + line_out += (SCREENWIDTH * fb_scaling * (s_Fb.bits_per_pixel/8)) + x_offset_end; + } + line_in += SCREENWIDTH; + } + + DG_DrawFrame(); +} + +// +// I_ReadScreen +// +void I_ReadScreen (byte* scr) +{ + memcpy (scr, I_VideoBuffer, SCREENWIDTH * SCREENHEIGHT); +} + +// +// I_SetPalette +// +#define GFX_RGB565(r, g, b) ((((r & 0xF8) >> 3) << 11) | (((g & 0xFC) >> 2) << 5) | ((b & 0xF8) >> 3)) +#define GFX_RGB565_R(color) ((0xF800 & color) >> 11) +#define GFX_RGB565_G(color) ((0x07E0 & color) >> 5) +#define GFX_RGB565_B(color) (0x001F & color) + +void I_SetPalette (byte* palette) +{ + int i; + //col_t* c; + + //for (i = 0; i < 256; i++) + //{ + // c = (col_t*)palette; + + // rgb565_palette[i] = GFX_RGB565(gammatable[usegamma][c->r], + // gammatable[usegamma][c->g], + // gammatable[usegamma][c->b]); + + // palette += 3; + //} + + + /* performance boost: + * map to the right pixel format over here! */ + + for (i=0; i<256; ++i ) { + colors[i].a = 0; + colors[i].r = gammatable[usegamma][*palette++]; + colors[i].g = gammatable[usegamma][*palette++]; + colors[i].b = gammatable[usegamma][*palette++]; + } + +#ifdef CMAP256 + + palette_changed = true; + +#endif // CMAP256 +} + +// Given an RGB value, find the closest matching palette index. + +int I_GetPaletteIndex (int r, int g, int b) +{ + int best, best_diff, diff; + int i; + col_t color; + + printf("I_GetPaletteIndex\n"); + + best = 0; + best_diff = INT_MAX; + + for (i = 0; i < 256; ++i) + { + color.r = GFX_RGB565_R(rgb565_palette[i]); + color.g = GFX_RGB565_G(rgb565_palette[i]); + color.b = GFX_RGB565_B(rgb565_palette[i]); + + diff = (r - color.r) * (r - color.r) + + (g - color.g) * (g - color.g) + + (b - color.b) * (b - color.b); + + if (diff < best_diff) + { + best = i; + best_diff = diff; + } + + if (diff == 0) + { + break; + } + } + + return best; +} + +void I_BeginRead (void) +{ +} + +void I_EndRead (void) +{ +} + +void I_SetWindowTitle (char *title) +{ + DG_SetWindowTitle(title); +} + +void I_GraphicsCheckCommandLine (void) +{ +} + +void I_SetGrabMouseCallback (grabmouse_callback_t func) +{ +} + +void I_EnableLoadingDisk(void) +{ +} + +void I_BindVideoVariables (void) +{ +} + +void I_DisplayFPSDots (boolean dots_on) +{ +} + +void I_CheckIsScreensaver (void) +{ +} diff --git a/firmware_p4/components/Applications/doom/i_video.h b/firmware_p4/components/Applications/doom/i_video.h new file mode 100644 index 000000000..c8565f419 --- /dev/null +++ b/firmware_p4/components/Applications/doom/i_video.h @@ -0,0 +1,175 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// System specific interface stuff. +// + + +#ifndef __I_VIDEO__ +#define __I_VIDEO__ + +#include "doomtype.h" + +// Screen width and height. + +#define SCREENWIDTH 320 +#define SCREENHEIGHT 200 + +// Screen width used for "squash" scale functions + +#define SCREENWIDTH_4_3 256 + +// Screen height used for "stretch" scale functions. + +#define SCREENHEIGHT_4_3 240 + +#define MAX_MOUSE_BUTTONS 8 + +typedef struct +{ + // Screen width and height + + int width; + int height; + + // Initialisation function to call when using this mode. + // Called with a pointer to the Doom palette. + // + // If NULL, no init function is called. + + void (*InitMode)(byte *palette); + + // Function to call to draw the screen from the source buffer. + // Return true if draw was successful. + + boolean (*DrawScreen)(int x1, int y1, int x2, int y2); + + // If true, this is a "poor quality" mode. The autoadjust + // code should always attempt to use a different mode to this + // mode in fullscreen. + // + // Some notes about what "poor quality" means in this context: + // + // The aspect ratio correction works by scaling up to the larger + // screen size and then drawing pixels on the edges between the + // "virtual" pixels so that an authentic blocky look-and-feel is + // achieved. + // + // For a mode like 640x480, you can imagine the grid of the + // "original" pixels spaced out, with extra "blurry" pixels added + // in the space between them to fill it out. However, when you're + // running at a resolution like 320x240, this is not the case. In + // the small screen case, every single pixel has to be a blurry + // interpolation of two pixels from the original image. + // + // If you run in 320x240 and put your face up close to the screen + // you can see this: it's particularly visible in the small yellow + // status bar numbers for example. Overall it still looks "okay" + // but there's an obvious - albeit small - deterioration in + // quality. + // + // Once you get to 640x480, all the original pixels are there at + // least once and it's okay (the higher the resolution, the more + // accurate it is). When I first wrote the code I was expecting + // that even higher resolutions would be needed before it would + // look acceptable, but it turned out to be okay even at 640x480. + + boolean poor_quality; +} screen_mode_t; + +typedef boolean (*grabmouse_callback_t)(void); + +// Called by D_DoomMain, +// determines the hardware configuration +// and sets up the video mode +void I_InitGraphics (void); + +void I_GraphicsCheckCommandLine(void); + +void I_ShutdownGraphics(void); + +// Takes full 8 bit values. +void I_SetPalette (byte* palette); +int I_GetPaletteIndex(int r, int g, int b); + +void I_UpdateNoBlit (void); +void I_FinishUpdate (void); + +void I_ReadScreen (byte* scr); + +void I_BeginRead (void); + +void I_SetWindowTitle(char *title); + +void I_CheckIsScreensaver(void); +void I_SetGrabMouseCallback(grabmouse_callback_t func); + +void I_DisplayFPSDots(boolean dots_on); +void I_BindVideoVariables(void); + +void I_InitWindowTitle(void); +void I_InitWindowIcon(void); + +// Called before processing any tics in a frame (just after displaying a frame). +// Time consuming syncronous operations are performed here (joystick reading). + +void I_StartFrame (void); + +// Called before processing each tic in a frame. +// Quick syncronous operations are performed here. + +void I_StartTic (void); + +// Enable the loading disk image displayed when reading from disk. + +void I_EnableLoadingDisk(void); + +void I_EndRead (void); + +struct color { + uint32_t b:8; + uint32_t g:8; + uint32_t r:8; + uint32_t a:8; +}; + + +extern char *video_driver; +extern boolean screenvisible; + +extern float mouse_acceleration; +extern int mouse_threshold; +extern int vanilla_keyboard_mapping; +extern boolean screensaver_mode; +extern int usegamma; +extern byte *I_VideoBuffer; + +extern int screen_width; +extern int screen_height; +extern int screen_bpp; +extern int fullscreen; +extern int aspect_ratio_correct; + +extern int show_diskicon; +extern int diskicon_readbytes; + +#ifdef CMAP256 + +extern boolean palette_changed; +extern struct color colors[256]; + +#endif // CMAP256 + +#endif diff --git a/firmware_p4/components/Applications/doom/icon.c b/firmware_p4/components/Applications/doom/icon.c new file mode 100644 index 000000000..14c607c23 --- /dev/null +++ b/firmware_p4/components/Applications/doom/icon.c @@ -0,0 +1,262 @@ +static int icon_w = 32; +static int icon_h = 32; + +static unsigned char icon_data[] = { + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xa2,0x86,0x73, + 0xa9,0x8d,0x7a, 0xbd,0xa0,0x8c, 0xda,0xba,0xa0, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0xbd,0x8d,0x67, 0xd7,0xb9,0xa5, 0xeb,0xd8,0xcd, 0xd3,0xbf,0xae, + 0xbd,0xa0,0x8c, 0xeb,0xd8,0xcd, 0xc2,0x9d,0x86, 0x95,0x5d,0x38, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x9b,0x7e,0x66, + 0xc5,0x9e,0x81, 0xd3,0xb3,0x99, 0xd4,0xac,0x8e, 0xee,0xdc,0xd1, + 0xb9,0x93,0x76, 0xad,0x71,0x45, 0xd4,0xac,0x8e, 0xb9,0x93,0x76, + 0xa3,0x77,0x58, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x39,0x1d,0x2d, 0x55,0x20,0x22, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0xda,0xb4,0x9c, 0xd3,0xa3,0x83, + 0xaf,0x91,0x78, 0xa7,0x83,0x6d, 0xc4,0xa7,0x93, 0xee,0xe2,0xd5, + 0xeb,0xd8,0xcd, 0x8c,0x60,0x3d, 0x9b,0x7e,0x66, 0xce,0x9f,0x7e, + 0x84,0x54,0x33, 0xba,0x83,0x5b, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x24,0x1c,0x35, 0x00,0x0f,0x32, 0x29,0x18,0x2e, 0x55,0x20,0x22, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0xd3,0xb3,0x99, 0xca,0x93,0x6f, 0xc4,0x94,0x6e, + 0x98,0x66,0x45, 0x78,0x50,0x2d, 0xd7,0xb9,0xa5, 0xee,0xdc,0xd1, + 0xc4,0x9b,0x79, 0xa1,0x6d,0x45, 0x66,0x40,0x24, 0xb8,0x7a,0x4f, + 0xcf,0xa6,0x83, 0x98,0x6d,0x4e, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x30,0x1c,0x2f, 0x08,0x13,0x30, 0x00,0x0f,0x32, 0x00,0x0f,0x32, + 0x39,0x1d,0x2d, 0x52,0x1c,0x1a, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x9e,0x7b,0x65, 0xb9,0x89,0x64, 0xaa,0x7d,0x5e, 0x9e,0x72,0x53, + 0x88,0x5e,0x40, 0xc4,0xa7,0x93, 0xb9,0x89,0x64, 0x90,0x6c,0x51, + 0x7f,0x50,0x2f, 0x90,0x5e,0x37, 0x75,0x4d,0x30, 0x7f,0x50,0x2f, + 0xd3,0xa3,0x83, 0xd4,0xac,0x8e, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x29,0x18,0x2e, 0x08,0x13,0x30, 0x08,0x13,0x30, 0x08,0x13,0x30, + 0x00,0x0f,0x32, 0x08,0x13,0x30, 0x49,0x1e,0x2b, 0x49,0x1a,0x16, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xda,0xba,0xa0, + 0xd4,0xac,0x8e, 0xc4,0x9b,0x79, 0xaa,0x7d,0x5e, 0xaa,0x7d,0x5e, + 0xbd,0xa0,0x8c, 0x8c,0x60,0x3d, 0x70,0x49,0x2c, 0x89,0x60,0x42, + 0x57,0x38,0x20, 0x6c,0x45,0x29, 0x66,0x40,0x24, 0x51,0x35,0x21, + 0x7e,0x55,0x38, 0xce,0x9f,0x7e, 0xc2,0x8a,0x61, 0x00,0x00,0x00, + 0x30,0x1c,0x2f, 0x00,0x0f,0x32, 0x00,0x0f,0x32, 0x08,0x13,0x30, + 0x00,0x0f,0x32, 0x00,0x0f,0x32, 0x08,0x13,0x30, 0x08,0x13,0x30, + 0x59,0x25,0x2b, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xcb,0x9a,0x74, + 0xb7,0x81,0x58, 0x8c,0x60,0x3d, 0x79,0x4b,0x2b, 0x89,0x58,0x31, + 0x89,0x58,0x31, 0x7f,0x50,0x2f, 0x9e,0x64,0x39, 0x75,0x4c,0x2a, + 0x51,0x35,0x21, 0x84,0x54,0x33, 0x54,0x36,0x1d, 0x98,0x6d,0x4e, + 0xb4,0x7f,0x5c, 0xba,0x83,0x5b, 0xb8,0x7a,0x4f, 0x00,0x00,0x00, + 0x3e,0x28,0x36, 0x08,0x13,0x30, 0x00,0x0f,0x32, 0x08,0x13,0x30, + 0x00,0x0f,0x32, 0x00,0x0f,0x32, 0x00,0x0f,0x32, 0x08,0x13,0x30, + 0x20,0x1f,0x36, 0x35,0x19,0x12, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xc2,0x8a,0x61, + 0x89,0x60,0x42, 0x84,0x54,0x33, 0x7f,0x50,0x2f, 0x86,0x56,0x35, + 0x8d,0x5b,0x35, 0x75,0x4c,0x2a, 0x8d,0x5b,0x35, 0x5c,0x38,0x22, + 0x5e,0x3f,0x27, 0x75,0x4d,0x30, 0x9d,0x64,0x3f, 0x75,0x4c,0x2a, + 0x78,0x50,0x2d, 0x7f,0x50,0x2f, 0xb7,0x81,0x58, 0x00,0x00,0x00, + 0x46,0x35,0x42, 0x04,0x18,0x3a, 0x08,0x13,0x30, 0x5d,0x30,0x28, + 0x20,0x1f,0x36, 0x08,0x13,0x30, 0x08,0x13,0x30, 0x04,0x18,0x3a, + 0x19,0x1c,0x37, 0x3a,0x1d,0x16, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x91,0x5f,0x3e, + 0x84,0x54,0x33, 0x89,0x58,0x31, 0x7e,0x6e,0x64, 0xc4,0x94,0x6e, + 0x78,0x50,0x2d, 0x92,0x6f,0x59, 0xa1,0x7c,0x60, 0x9c,0x6f,0x4b, + 0x8d,0x5b,0x35, 0xbc,0x7f,0x53, 0xad,0x71,0x45, 0x75,0x4d,0x30, + 0x51,0x35,0x21, 0x4b,0x2f,0x1c, 0x70,0x49,0x2c, 0x00,0x00,0x00, + 0x59,0x44,0x4d, 0x1e,0x28,0x42, 0x1e,0x28,0x42, 0x48,0x19,0x10, + 0x42,0x19,0x12, 0x53,0x2b,0x30, 0x0c,0x26,0x48, 0x1e,0x28,0x42, + 0x24,0x2d,0x48, 0x5f,0x2c,0x1d, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x89,0x58,0x31, + 0xa1,0x84,0x6c, 0xc4,0x94,0x6e, 0x88,0x64,0x44, 0xb5,0x8f,0x73, + 0x9e,0x72,0x53, 0xa1,0x6d,0x45, 0x93,0x60,0x3a, 0xad,0x71,0x45, + 0xb4,0x7f,0x5c, 0xbd,0x8d,0x67, 0xc2,0x8a,0x61, 0xb3,0x76,0x4b, + 0xb8,0x7a,0x4f, 0x88,0x64,0x44, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x61,0x50,0x52, 0x1c,0x34,0x52, 0x1c,0x34,0x52, 0x54,0x27,0x16, + 0x29,0x17,0x09, 0x5d,0x30,0x28, 0x1c,0x34,0x52, 0x1c,0x34,0x52, + 0x24,0x35,0x4f, 0x69,0x34,0x24, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xa7,0x83,0x6d, + 0xac,0x86,0x6a, 0x74,0x47,0x2d, 0x84,0x54,0x33, 0x5c,0x38,0x22, + 0x54,0x36,0x1d, 0x6c,0x45,0x29, 0x96,0x63,0x3c, 0xa3,0x6e,0x41, + 0xb3,0x76,0x4b, 0xb3,0x76,0x4b, 0xa2,0x68,0x3d, 0x7c,0x4e,0x2d, + 0x63,0x3e,0x27, 0x96,0x63,0x3c, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x6b,0x5d,0x59, 0x22,0x42,0x5f, 0x22,0x42,0x5f, 0x5d,0x34,0x1a, + 0x38,0x23,0x0f, 0x5c,0x38,0x22, 0x22,0x42,0x5f, 0x22,0x42,0x5f, + 0x2c,0x45,0x5e, 0x6f,0x3e,0x2b, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xb7,0x81,0x58, + 0x98,0x74,0x59, 0x6c,0x45,0x29, 0x4b,0x35,0x25, 0x78,0x50,0x2d, + 0x78,0x50,0x2d, 0x78,0x50,0x2d, 0x7f,0x50,0x2f, 0x84,0x54,0x33, + 0x8d,0x5b,0x35, 0x96,0x63,0x3c, 0x74,0x47,0x2d, 0x65,0x45,0x26, + 0x65,0x45,0x26, 0x7c,0x4e,0x2d, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x77,0x69,0x64, 0x30,0x4e,0x6d, 0x32,0x52,0x6b, 0x69,0x42,0x26, + 0x49,0x31,0x11, 0x6c,0x47,0x2f, 0x27,0x4f,0x6d, 0x27,0x4f,0x6d, + 0x32,0x52,0x6b, 0x70,0x49,0x2c, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xb7,0x81,0x58, + 0x8a,0x5a,0x39, 0x8a,0x5a,0x39, 0x91,0x5f,0x3e, 0x5e,0x3f,0x27, + 0x5c,0x38,0x22, 0x89,0x58,0x31, 0x89,0x58,0x31, 0x95,0x5d,0x38, + 0x9d,0x64,0x3f, 0x65,0x45,0x26, 0x4b,0x2f,0x1c, 0x7f,0x50,0x2f, + 0x78,0x50,0x2d, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x77,0x69,0x64, 0x36,0x5c,0x7a, 0x3e,0x5e,0x78, 0x76,0x52,0x2e, + 0x5d,0x42,0x22, 0x75,0x4d,0x30, 0x36,0x5c,0x7a, 0x36,0x5c,0x7a, + 0x3e,0x5e,0x78, 0x74,0x47,0x2d, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x89,0x58,0x31, 0x63,0x3e,0x27, 0xa8,0x6d,0x42, 0x4b,0x2f,0x1c, + 0x65,0x45,0x26, 0x70,0x49,0x2c, 0x51,0x35,0x21, 0x78,0x50,0x2d, + 0x42,0x30,0x14, 0x49,0x31,0x11, 0x59,0x44,0x22, 0x7c,0x5c,0x2a, + 0x8a,0x71,0x27, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x71,0x67,0x5c, 0x37,0x52,0x66, 0x3f,0x55,0x64, 0x80,0x55,0x27, + 0x64,0x4c,0x1f, 0x7e,0x59,0x2e, 0x37,0x52,0x66, 0x37,0x52,0x66, + 0x3f,0x55,0x64, 0x6c,0x47,0x2f, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x70,0x49,0x2c, 0x65,0x45,0x26, 0x65,0x45,0x26, 0x63,0x3e,0x27, + 0x76,0x4d,0x25, 0x5d,0x42,0x22, 0x5e,0x3f,0x27, 0x4e,0x43,0x18, + 0x4e,0x43,0x18, 0x6a,0x5b,0x1c, 0x4e,0x43,0x18, 0x5f,0x51,0x19, + 0x8a,0x76,0x2a, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x63,0x38,0x19, 0x34,0x11,0x04, 0x32,0x0f,0x00, 0x86,0x58,0x1e, + 0x74,0x59,0x25, 0x86,0x58,0x1e, 0x34,0x15,0x00, 0x32,0x0f,0x00, + 0x34,0x15,0x00, 0x4e,0x31,0x18, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x7e,0x6c,0x27, 0x5a,0x4d,0x1c, + 0x4d,0x3e,0x15, 0x67,0x58,0x21, 0x5a,0x4d,0x1c, 0x57,0x4b,0x1a, + 0x5f,0x51,0x19, 0x64,0x55,0x1e, 0x5a,0x4d,0x1c, 0x8a,0x71,0x27, + 0x8e,0x79,0x26, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x63,0x38,0x19, 0x34,0x11,0x04, 0x32,0x0f,0x00, 0x8d,0x63,0x1f, + 0x83,0x66,0x2c, 0x8d,0x63,0x1f, 0x32,0x0f,0x00, 0x35,0x19,0x12, + 0x34,0x11,0x04, 0x53,0x3a,0x20, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0xac,0x93,0x39, 0x76,0x65,0x20, + 0x6a,0x5b,0x1c, 0x6a,0x5b,0x1c, 0x67,0x58,0x21, 0x4e,0x43,0x18, + 0x4e,0x43,0x18, 0x9b,0x85,0x32, 0xb8,0x9e,0x3c, 0xb1,0x8d,0x36, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x63,0x38,0x19, 0x34,0x15,0x00, 0x32,0x0f,0x00, 0x8d,0x63,0x1f, + 0x83,0x66,0x2c, 0x8d,0x63,0x1f, 0x32,0x0f,0x00, 0x32,0x0f,0x00, + 0x34,0x15,0x00, 0x53,0x3a,0x20, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0xb0,0x96,0x34, 0x76,0x65,0x20, + 0x7e,0x66,0x23, 0x8e,0x79,0x26, 0x8a,0x71,0x27, 0x7e,0x6c,0x27, + 0x8a,0x71,0x27, 0x8a,0x71,0x27, 0xb0,0x96,0x34, 0x98,0x82,0x2f, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x75,0x4c,0x2a, 0x38,0x19,0x05, 0x38,0x19,0x05, 0x99,0x6d,0x22, + 0x96,0x70,0x2a, 0x99,0x6d,0x22, 0x38,0x19,0x05, 0x38,0x19,0x05, + 0x38,0x19,0x05, 0x59,0x3f,0x25, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0xac,0x93,0x39, 0x8a,0x76,0x2a, + 0x7e,0x66,0x23, 0x76,0x65,0x20, 0x93,0x7d,0x2a, 0x82,0x6f,0x23, + 0x9f,0x88,0x35, 0xb8,0xa0,0x4c, 0xb8,0xa0,0x4c, 0xc4,0xa8,0x3f, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x7c,0x5c,0x2a, 0x52,0x2e,0x0d, 0x52,0x2e,0x0d, 0xa4,0x7b,0x27, + 0xa1,0x80,0x37, 0x9f,0x77,0x1a, 0x52,0x2e,0x0d, 0x52,0x2e,0x0d, + 0x52,0x2e,0x0d, 0x5f,0x4e,0x2a, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x86,0x72,0x26, + 0xac,0x93,0x39, 0x97,0x82,0x36, 0xb1,0x8d,0x36, 0xac,0x93,0x39, + 0x97,0x82,0x36, 0xa4,0x8c,0x32, 0xbd,0xa2,0x41, 0x8a,0x71,0x27, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x8b,0x6d,0x32, 0x66,0x44,0x14, 0x6d,0x4a,0x20, 0xab,0x86,0x29, + 0xb1,0x8d,0x36, 0xa4,0x7b,0x27, 0x66,0x44,0x14, 0x66,0x44,0x14, + 0x66,0x44,0x14, 0x69,0x56,0x2c, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xac,0x93,0x31, + 0x7e,0x6c,0x27, 0x9f,0x88,0x35, 0x97,0x82,0x36, 0x7e,0x66,0x23, + 0x7e,0x66,0x23, 0xb2,0x99,0x3f, 0xbd,0xa2,0x41, 0x8a,0x76,0x2a, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x99,0x7a,0x38, 0x86,0x58,0x1e, 0x7f,0x59,0x22, 0xb2,0x8b,0x1c, + 0x94,0x6e,0x21, 0x7f,0x59,0x22, 0x7f,0x59,0x22, 0x7f,0x59,0x22, + 0x7f,0x59,0x22, 0x69,0x56,0x2c, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xac,0x93,0x39, + 0x8a,0x71,0x27, 0xb4,0x9c,0x48, 0x7e,0x66,0x23, 0xac,0x93,0x39, + 0x9c,0x87,0x3a, 0x9c,0x87,0x3a, 0xbd,0xa2,0x41, 0x8e,0x79,0x26, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0xa8,0x86,0x3d, 0x96,0x70,0x2a, 0x96,0x70,0x2a, 0x96,0x70,0x2a, + 0x96,0x70,0x2a, 0x96,0x70,0x2a, 0x96,0x70,0x2a, 0x96,0x70,0x2a, + 0xa1,0x80,0x37, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xb4,0x9c,0x48, + 0xac,0x93,0x31, 0x93,0x7d,0x2a, 0xbd,0xa3,0x48, 0x93,0x7d,0x2a, + 0xb8,0xa0,0x4c, 0xb4,0x9c,0x48, 0xcc,0xa5,0x4e, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0xa8,0x86,0x3d, 0xaf,0x85,0x31, 0xaf,0x85,0x31, 0xaf,0x85,0x31, + 0xaf,0x85,0x31, 0xaf,0x85,0x31, 0xaf,0x85,0x31, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x97,0x82,0x36, 0xb4,0x9c,0x48, 0xb2,0x99,0x3f, 0xb4,0x9c,0x48, + 0xb0,0x96,0x34, 0xc1,0xa7,0x4c, 0x9b,0x84,0x2a, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0xbd,0x9e,0x4c, 0xc7,0x9a,0x3f, 0xc7,0x9a,0x3f, 0xc7,0x9a,0x3f, + 0xc7,0x9a,0x3f, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x9b,0x85,0x32, 0xa7,0x8e,0x2c, 0xac,0x93,0x39, 0xb5,0x91,0x41, + 0x76,0x65,0x20, 0xa7,0x8e,0x2c, 0xb4,0x9a,0x38, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0xcc,0xa5,0x4e, 0xe0,0xaf,0x45, 0xe0,0xaf,0x45, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0xa4,0x8c,0x32, 0xb8,0x9e,0x44, 0x86,0x72,0x26, 0x9f,0x88,0x35, + 0xbd,0xa3,0x48, 0x9b,0x85,0x32, 0xa3,0x81,0x32, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0xd1,0xae,0x4e, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0xb0,0x97,0x3c, 0xb4,0x9a,0x38, 0xac,0x94,0x41, 0xb2,0x99,0x3f, + 0xb4,0x9a,0x38, 0xb8,0x9e,0x3c, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0xcc,0xa5,0x4e, 0xa6,0x8f,0x3c, 0xb2,0x99,0x3f, 0xb4,0x9c,0x48, + 0xa8,0x90,0x36, 0x9f,0x88,0x35, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, + +}; diff --git a/firmware_p4/components/Applications/doom/include/doom_highboy.h b/firmware_p4/components/Applications/doom/include/doom_highboy.h new file mode 100644 index 000000000..34ab48c2a --- /dev/null +++ b/firmware_p4/components/Applications/doom/include/doom_highboy.h @@ -0,0 +1,22 @@ +#ifndef DOOM_HIGHBOY_H +#define DOOM_HIGHBOY_H + +#ifdef __cplusplus +extern "C" { +#endif + +// Launch official DOOM (doomgeneric) in its own FreeRTOS task. Takes over the +// ST7789 (landscape, bypassing LVGL) and streams /sdcard/doom1.wad. Call from +// the games-menu screen open handler. Quitting DOOM reboots the device +// (hold OK+BACK ~2s), so this does not return control to the caller's UI. +// +// render_beat_kick: optional callback pulsed once per frame while DOOM owns the +// panel (LVGL is parked). Pass ui_render_beat_kick so the render-liveness +// watchdog (sys_monitor) sees progress; NULL disables it. +void highboy_doom_start(void (*render_beat_kick)(void)); + +#ifdef __cplusplus +} +#endif + +#endif // DOOM_HIGHBOY_H diff --git a/firmware_p4/components/Applications/doom/info.c b/firmware_p4/components/Applications/doom/info.c new file mode 100644 index 000000000..9389e84f0 --- /dev/null +++ b/firmware_p4/components/Applications/doom/info.c @@ -0,0 +1,4662 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Thing frame/state LUT, +// generated by multigen utilitiy. +// This one is the original DOOM version, preserved. +// + +#include +#include + +// Data. +#include "sounds.h" +#include "m_fixed.h" + +#include "info.h" + +#include "p_mobj.h" + +char *sprnames[] = { + "TROO","SHTG","PUNG","PISG","PISF","SHTF","SHT2","CHGG","CHGF","MISG", + "MISF","SAWG","PLSG","PLSF","BFGG","BFGF","BLUD","PUFF","BAL1","BAL2", + "PLSS","PLSE","MISL","BFS1","BFE1","BFE2","TFOG","IFOG","PLAY","POSS", + "SPOS","VILE","FIRE","FATB","FBXP","SKEL","MANF","FATT","CPOS","SARG", + "HEAD","BAL7","BOSS","BOS2","SKUL","SPID","BSPI","APLS","APBX","CYBR", + "PAIN","SSWV","KEEN","BBRN","BOSF","ARM1","ARM2","BAR1","BEXP","FCAN", + "BON1","BON2","BKEY","RKEY","YKEY","BSKU","RSKU","YSKU","STIM","MEDI", + "SOUL","PINV","PSTR","PINS","MEGA","SUIT","PMAP","PVIS","CLIP","AMMO", + "ROCK","BROK","CELL","CELP","SHEL","SBOX","BPAK","BFUG","MGUN","CSAW", + "LAUN","PLAS","SHOT","SGN2","COLU","SMT2","GOR1","POL2","POL5","POL4", + "POL3","POL1","POL6","GOR2","GOR3","GOR4","GOR5","SMIT","COL1","COL2", + "COL3","COL4","CAND","CBRA","COL6","TRE1","TRE2","ELEC","CEYE","FSKU", + "COL5","TBLU","TGRN","TRED","SMBT","SMGT","SMRT","HDB1","HDB2","HDB3", + "HDB4","HDB5","HDB6","POB1","POB2","BRS1","TLMP","TLP2", NULL +}; + + +// Doesn't work with g++, needs actionf_p1 +void A_Light0(); +void A_WeaponReady(); +void A_Lower(); +void A_Raise(); +void A_Punch(); +void A_ReFire(); +void A_FirePistol(); +void A_Light1(); +void A_FireShotgun(); +void A_Light2(); +void A_FireShotgun2(); +void A_CheckReload(); +void A_OpenShotgun2(); +void A_LoadShotgun2(); +void A_CloseShotgun2(); +void A_FireCGun(); +void A_GunFlash(); +void A_FireMissile(); +void A_Saw(); +void A_FirePlasma(); +void A_BFGsound(); +void A_FireBFG(); +void A_BFGSpray(); +void A_Explode(); +void A_Pain(); +void A_PlayerScream(); +void A_Fall(); +void A_XScream(); +void A_Look(); +void A_Chase(); +void A_FaceTarget(); +void A_PosAttack(); +void A_Scream(); +void A_SPosAttack(); +void A_VileChase(); +void A_VileStart(); +void A_VileTarget(); +void A_VileAttack(); +void A_StartFire(); +void A_Fire(); +void A_FireCrackle(); +void A_Tracer(); +void A_SkelWhoosh(); +void A_SkelFist(); +void A_SkelMissile(); +void A_FatRaise(); +void A_FatAttack1(); +void A_FatAttack2(); +void A_FatAttack3(); +void A_BossDeath(); +void A_CPosAttack(); +void A_CPosRefire(); +void A_TroopAttack(); +void A_SargAttack(); +void A_HeadAttack(); +void A_BruisAttack(); +void A_SkullAttack(); +void A_Metal(); +void A_SpidRefire(); +void A_BabyMetal(); +void A_BspiAttack(); +void A_Hoof(); +void A_CyberAttack(); +void A_PainAttack(); +void A_PainDie(); +void A_KeenDie(); +void A_BrainPain(); +void A_BrainScream(); +void A_BrainDie(); +void A_BrainAwake(); +void A_BrainSpit(); +void A_SpawnSound(); +void A_SpawnFly(); +void A_BrainExplode(); + + +state_t states[NUMSTATES] = { + {SPR_TROO,0,-1,{NULL},S_NULL,0,0}, // S_NULL + {SPR_SHTG,4,0,{A_Light0},S_NULL,0,0}, // S_LIGHTDONE + {SPR_PUNG,0,1,{A_WeaponReady},S_PUNCH,0,0}, // S_PUNCH + {SPR_PUNG,0,1,{A_Lower},S_PUNCHDOWN,0,0}, // S_PUNCHDOWN + {SPR_PUNG,0,1,{A_Raise},S_PUNCHUP,0,0}, // S_PUNCHUP + {SPR_PUNG,1,4,{NULL},S_PUNCH2,0,0}, // S_PUNCH1 + {SPR_PUNG,2,4,{A_Punch},S_PUNCH3,0,0}, // S_PUNCH2 + {SPR_PUNG,3,5,{NULL},S_PUNCH4,0,0}, // S_PUNCH3 + {SPR_PUNG,2,4,{NULL},S_PUNCH5,0,0}, // S_PUNCH4 + {SPR_PUNG,1,5,{A_ReFire},S_PUNCH,0,0}, // S_PUNCH5 + {SPR_PISG,0,1,{A_WeaponReady},S_PISTOL,0,0},// S_PISTOL + {SPR_PISG,0,1,{A_Lower},S_PISTOLDOWN,0,0}, // S_PISTOLDOWN + {SPR_PISG,0,1,{A_Raise},S_PISTOLUP,0,0}, // S_PISTOLUP + {SPR_PISG,0,4,{NULL},S_PISTOL2,0,0}, // S_PISTOL1 + {SPR_PISG,1,6,{A_FirePistol},S_PISTOL3,0,0},// S_PISTOL2 + {SPR_PISG,2,4,{NULL},S_PISTOL4,0,0}, // S_PISTOL3 + {SPR_PISG,1,5,{A_ReFire},S_PISTOL,0,0}, // S_PISTOL4 + {SPR_PISF,32768,7,{A_Light1},S_LIGHTDONE,0,0}, // S_PISTOLFLASH + {SPR_SHTG,0,1,{A_WeaponReady},S_SGUN,0,0}, // S_SGUN + {SPR_SHTG,0,1,{A_Lower},S_SGUNDOWN,0,0}, // S_SGUNDOWN + {SPR_SHTG,0,1,{A_Raise},S_SGUNUP,0,0}, // S_SGUNUP + {SPR_SHTG,0,3,{NULL},S_SGUN2,0,0}, // S_SGUN1 + {SPR_SHTG,0,7,{A_FireShotgun},S_SGUN3,0,0}, // S_SGUN2 + {SPR_SHTG,1,5,{NULL},S_SGUN4,0,0}, // S_SGUN3 + {SPR_SHTG,2,5,{NULL},S_SGUN5,0,0}, // S_SGUN4 + {SPR_SHTG,3,4,{NULL},S_SGUN6,0,0}, // S_SGUN5 + {SPR_SHTG,2,5,{NULL},S_SGUN7,0,0}, // S_SGUN6 + {SPR_SHTG,1,5,{NULL},S_SGUN8,0,0}, // S_SGUN7 + {SPR_SHTG,0,3,{NULL},S_SGUN9,0,0}, // S_SGUN8 + {SPR_SHTG,0,7,{A_ReFire},S_SGUN,0,0}, // S_SGUN9 + {SPR_SHTF,32768,4,{A_Light1},S_SGUNFLASH2,0,0}, // S_SGUNFLASH1 + {SPR_SHTF,32769,3,{A_Light2},S_LIGHTDONE,0,0}, // S_SGUNFLASH2 + {SPR_SHT2,0,1,{A_WeaponReady},S_DSGUN,0,0}, // S_DSGUN + {SPR_SHT2,0,1,{A_Lower},S_DSGUNDOWN,0,0}, // S_DSGUNDOWN + {SPR_SHT2,0,1,{A_Raise},S_DSGUNUP,0,0}, // S_DSGUNUP + {SPR_SHT2,0,3,{NULL},S_DSGUN2,0,0}, // S_DSGUN1 + {SPR_SHT2,0,7,{A_FireShotgun2},S_DSGUN3,0,0}, // S_DSGUN2 + {SPR_SHT2,1,7,{NULL},S_DSGUN4,0,0}, // S_DSGUN3 + {SPR_SHT2,2,7,{A_CheckReload},S_DSGUN5,0,0}, // S_DSGUN4 + {SPR_SHT2,3,7,{A_OpenShotgun2},S_DSGUN6,0,0}, // S_DSGUN5 + {SPR_SHT2,4,7,{NULL},S_DSGUN7,0,0}, // S_DSGUN6 + {SPR_SHT2,5,7,{A_LoadShotgun2},S_DSGUN8,0,0}, // S_DSGUN7 + {SPR_SHT2,6,6,{NULL},S_DSGUN9,0,0}, // S_DSGUN8 + {SPR_SHT2,7,6,{A_CloseShotgun2},S_DSGUN10,0,0}, // S_DSGUN9 + {SPR_SHT2,0,5,{A_ReFire},S_DSGUN,0,0}, // S_DSGUN10 + {SPR_SHT2,1,7,{NULL},S_DSNR2,0,0}, // S_DSNR1 + {SPR_SHT2,0,3,{NULL},S_DSGUNDOWN,0,0}, // S_DSNR2 + {SPR_SHT2,32776,5,{A_Light1},S_DSGUNFLASH2,0,0}, // S_DSGUNFLASH1 + {SPR_SHT2,32777,4,{A_Light2},S_LIGHTDONE,0,0}, // S_DSGUNFLASH2 + {SPR_CHGG,0,1,{A_WeaponReady},S_CHAIN,0,0}, // S_CHAIN + {SPR_CHGG,0,1,{A_Lower},S_CHAINDOWN,0,0}, // S_CHAINDOWN + {SPR_CHGG,0,1,{A_Raise},S_CHAINUP,0,0}, // S_CHAINUP + {SPR_CHGG,0,4,{A_FireCGun},S_CHAIN2,0,0}, // S_CHAIN1 + {SPR_CHGG,1,4,{A_FireCGun},S_CHAIN3,0,0}, // S_CHAIN2 + {SPR_CHGG,1,0,{A_ReFire},S_CHAIN,0,0}, // S_CHAIN3 + {SPR_CHGF,32768,5,{A_Light1},S_LIGHTDONE,0,0}, // S_CHAINFLASH1 + {SPR_CHGF,32769,5,{A_Light2},S_LIGHTDONE,0,0}, // S_CHAINFLASH2 + {SPR_MISG,0,1,{A_WeaponReady},S_MISSILE,0,0}, // S_MISSILE + {SPR_MISG,0,1,{A_Lower},S_MISSILEDOWN,0,0}, // S_MISSILEDOWN + {SPR_MISG,0,1,{A_Raise},S_MISSILEUP,0,0}, // S_MISSILEUP + {SPR_MISG,1,8,{A_GunFlash},S_MISSILE2,0,0}, // S_MISSILE1 + {SPR_MISG,1,12,{A_FireMissile},S_MISSILE3,0,0}, // S_MISSILE2 + {SPR_MISG,1,0,{A_ReFire},S_MISSILE,0,0}, // S_MISSILE3 + {SPR_MISF,32768,3,{A_Light1},S_MISSILEFLASH2,0,0}, // S_MISSILEFLASH1 + {SPR_MISF,32769,4,{NULL},S_MISSILEFLASH3,0,0}, // S_MISSILEFLASH2 + {SPR_MISF,32770,4,{A_Light2},S_MISSILEFLASH4,0,0}, // S_MISSILEFLASH3 + {SPR_MISF,32771,4,{A_Light2},S_LIGHTDONE,0,0}, // S_MISSILEFLASH4 + {SPR_SAWG,2,4,{A_WeaponReady},S_SAWB,0,0}, // S_SAW + {SPR_SAWG,3,4,{A_WeaponReady},S_SAW,0,0}, // S_SAWB + {SPR_SAWG,2,1,{A_Lower},S_SAWDOWN,0,0}, // S_SAWDOWN + {SPR_SAWG,2,1,{A_Raise},S_SAWUP,0,0}, // S_SAWUP + {SPR_SAWG,0,4,{A_Saw},S_SAW2,0,0}, // S_SAW1 + {SPR_SAWG,1,4,{A_Saw},S_SAW3,0,0}, // S_SAW2 + {SPR_SAWG,1,0,{A_ReFire},S_SAW,0,0}, // S_SAW3 + {SPR_PLSG,0,1,{A_WeaponReady},S_PLASMA,0,0}, // S_PLASMA + {SPR_PLSG,0,1,{A_Lower},S_PLASMADOWN,0,0}, // S_PLASMADOWN + {SPR_PLSG,0,1,{A_Raise},S_PLASMAUP,0,0}, // S_PLASMAUP + {SPR_PLSG,0,3,{A_FirePlasma},S_PLASMA2,0,0}, // S_PLASMA1 + {SPR_PLSG,1,20,{A_ReFire},S_PLASMA,0,0}, // S_PLASMA2 + {SPR_PLSF,32768,4,{A_Light1},S_LIGHTDONE,0,0}, // S_PLASMAFLASH1 + {SPR_PLSF,32769,4,{A_Light1},S_LIGHTDONE,0,0}, // S_PLASMAFLASH2 + {SPR_BFGG,0,1,{A_WeaponReady},S_BFG,0,0}, // S_BFG + {SPR_BFGG,0,1,{A_Lower},S_BFGDOWN,0,0}, // S_BFGDOWN + {SPR_BFGG,0,1,{A_Raise},S_BFGUP,0,0}, // S_BFGUP + {SPR_BFGG,0,20,{A_BFGsound},S_BFG2,0,0}, // S_BFG1 + {SPR_BFGG,1,10,{A_GunFlash},S_BFG3,0,0}, // S_BFG2 + {SPR_BFGG,1,10,{A_FireBFG},S_BFG4,0,0}, // S_BFG3 + {SPR_BFGG,1,20,{A_ReFire},S_BFG,0,0}, // S_BFG4 + {SPR_BFGF,32768,11,{A_Light1},S_BFGFLASH2,0,0}, // S_BFGFLASH1 + {SPR_BFGF,32769,6,{A_Light2},S_LIGHTDONE,0,0}, // S_BFGFLASH2 + {SPR_BLUD,2,8,{NULL},S_BLOOD2,0,0}, // S_BLOOD1 + {SPR_BLUD,1,8,{NULL},S_BLOOD3,0,0}, // S_BLOOD2 + {SPR_BLUD,0,8,{NULL},S_NULL,0,0}, // S_BLOOD3 + {SPR_PUFF,32768,4,{NULL},S_PUFF2,0,0}, // S_PUFF1 + {SPR_PUFF,1,4,{NULL},S_PUFF3,0,0}, // S_PUFF2 + {SPR_PUFF,2,4,{NULL},S_PUFF4,0,0}, // S_PUFF3 + {SPR_PUFF,3,4,{NULL},S_NULL,0,0}, // S_PUFF4 + {SPR_BAL1,32768,4,{NULL},S_TBALL2,0,0}, // S_TBALL1 + {SPR_BAL1,32769,4,{NULL},S_TBALL1,0,0}, // S_TBALL2 + {SPR_BAL1,32770,6,{NULL},S_TBALLX2,0,0}, // S_TBALLX1 + {SPR_BAL1,32771,6,{NULL},S_TBALLX3,0,0}, // S_TBALLX2 + {SPR_BAL1,32772,6,{NULL},S_NULL,0,0}, // S_TBALLX3 + {SPR_BAL2,32768,4,{NULL},S_RBALL2,0,0}, // S_RBALL1 + {SPR_BAL2,32769,4,{NULL},S_RBALL1,0,0}, // S_RBALL2 + {SPR_BAL2,32770,6,{NULL},S_RBALLX2,0,0}, // S_RBALLX1 + {SPR_BAL2,32771,6,{NULL},S_RBALLX3,0,0}, // S_RBALLX2 + {SPR_BAL2,32772,6,{NULL},S_NULL,0,0}, // S_RBALLX3 + {SPR_PLSS,32768,6,{NULL},S_PLASBALL2,0,0}, // S_PLASBALL + {SPR_PLSS,32769,6,{NULL},S_PLASBALL,0,0}, // S_PLASBALL2 + {SPR_PLSE,32768,4,{NULL},S_PLASEXP2,0,0}, // S_PLASEXP + {SPR_PLSE,32769,4,{NULL},S_PLASEXP3,0,0}, // S_PLASEXP2 + {SPR_PLSE,32770,4,{NULL},S_PLASEXP4,0,0}, // S_PLASEXP3 + {SPR_PLSE,32771,4,{NULL},S_PLASEXP5,0,0}, // S_PLASEXP4 + {SPR_PLSE,32772,4,{NULL},S_NULL,0,0}, // S_PLASEXP5 + {SPR_MISL,32768,1,{NULL},S_ROCKET,0,0}, // S_ROCKET + {SPR_BFS1,32768,4,{NULL},S_BFGSHOT2,0,0}, // S_BFGSHOT + {SPR_BFS1,32769,4,{NULL},S_BFGSHOT,0,0}, // S_BFGSHOT2 + {SPR_BFE1,32768,8,{NULL},S_BFGLAND2,0,0}, // S_BFGLAND + {SPR_BFE1,32769,8,{NULL},S_BFGLAND3,0,0}, // S_BFGLAND2 + {SPR_BFE1,32770,8,{A_BFGSpray},S_BFGLAND4,0,0}, // S_BFGLAND3 + {SPR_BFE1,32771,8,{NULL},S_BFGLAND5,0,0}, // S_BFGLAND4 + {SPR_BFE1,32772,8,{NULL},S_BFGLAND6,0,0}, // S_BFGLAND5 + {SPR_BFE1,32773,8,{NULL},S_NULL,0,0}, // S_BFGLAND6 + {SPR_BFE2,32768,8,{NULL},S_BFGEXP2,0,0}, // S_BFGEXP + {SPR_BFE2,32769,8,{NULL},S_BFGEXP3,0,0}, // S_BFGEXP2 + {SPR_BFE2,32770,8,{NULL},S_BFGEXP4,0,0}, // S_BFGEXP3 + {SPR_BFE2,32771,8,{NULL},S_NULL,0,0}, // S_BFGEXP4 + {SPR_MISL,32769,8,{A_Explode},S_EXPLODE2,0,0}, // S_EXPLODE1 + {SPR_MISL,32770,6,{NULL},S_EXPLODE3,0,0}, // S_EXPLODE2 + {SPR_MISL,32771,4,{NULL},S_NULL,0,0}, // S_EXPLODE3 + {SPR_TFOG,32768,6,{NULL},S_TFOG01,0,0}, // S_TFOG + {SPR_TFOG,32769,6,{NULL},S_TFOG02,0,0}, // S_TFOG01 + {SPR_TFOG,32768,6,{NULL},S_TFOG2,0,0}, // S_TFOG02 + {SPR_TFOG,32769,6,{NULL},S_TFOG3,0,0}, // S_TFOG2 + {SPR_TFOG,32770,6,{NULL},S_TFOG4,0,0}, // S_TFOG3 + {SPR_TFOG,32771,6,{NULL},S_TFOG5,0,0}, // S_TFOG4 + {SPR_TFOG,32772,6,{NULL},S_TFOG6,0,0}, // S_TFOG5 + {SPR_TFOG,32773,6,{NULL},S_TFOG7,0,0}, // S_TFOG6 + {SPR_TFOG,32774,6,{NULL},S_TFOG8,0,0}, // S_TFOG7 + {SPR_TFOG,32775,6,{NULL},S_TFOG9,0,0}, // S_TFOG8 + {SPR_TFOG,32776,6,{NULL},S_TFOG10,0,0}, // S_TFOG9 + {SPR_TFOG,32777,6,{NULL},S_NULL,0,0}, // S_TFOG10 + {SPR_IFOG,32768,6,{NULL},S_IFOG01,0,0}, // S_IFOG + {SPR_IFOG,32769,6,{NULL},S_IFOG02,0,0}, // S_IFOG01 + {SPR_IFOG,32768,6,{NULL},S_IFOG2,0,0}, // S_IFOG02 + {SPR_IFOG,32769,6,{NULL},S_IFOG3,0,0}, // S_IFOG2 + {SPR_IFOG,32770,6,{NULL},S_IFOG4,0,0}, // S_IFOG3 + {SPR_IFOG,32771,6,{NULL},S_IFOG5,0,0}, // S_IFOG4 + {SPR_IFOG,32772,6,{NULL},S_NULL,0,0}, // S_IFOG5 + {SPR_PLAY,0,-1,{NULL},S_NULL,0,0}, // S_PLAY + {SPR_PLAY,0,4,{NULL},S_PLAY_RUN2,0,0}, // S_PLAY_RUN1 + {SPR_PLAY,1,4,{NULL},S_PLAY_RUN3,0,0}, // S_PLAY_RUN2 + {SPR_PLAY,2,4,{NULL},S_PLAY_RUN4,0,0}, // S_PLAY_RUN3 + {SPR_PLAY,3,4,{NULL},S_PLAY_RUN1,0,0}, // S_PLAY_RUN4 + {SPR_PLAY,4,12,{NULL},S_PLAY,0,0}, // S_PLAY_ATK1 + {SPR_PLAY,32773,6,{NULL},S_PLAY_ATK1,0,0}, // S_PLAY_ATK2 + {SPR_PLAY,6,4,{NULL},S_PLAY_PAIN2,0,0}, // S_PLAY_PAIN + {SPR_PLAY,6,4,{A_Pain},S_PLAY,0,0}, // S_PLAY_PAIN2 + {SPR_PLAY,7,10,{NULL},S_PLAY_DIE2,0,0}, // S_PLAY_DIE1 + {SPR_PLAY,8,10,{A_PlayerScream},S_PLAY_DIE3,0,0}, // S_PLAY_DIE2 + {SPR_PLAY,9,10,{A_Fall},S_PLAY_DIE4,0,0}, // S_PLAY_DIE3 + {SPR_PLAY,10,10,{NULL},S_PLAY_DIE5,0,0}, // S_PLAY_DIE4 + {SPR_PLAY,11,10,{NULL},S_PLAY_DIE6,0,0}, // S_PLAY_DIE5 + {SPR_PLAY,12,10,{NULL},S_PLAY_DIE7,0,0}, // S_PLAY_DIE6 + {SPR_PLAY,13,-1,{NULL},S_NULL,0,0}, // S_PLAY_DIE7 + {SPR_PLAY,14,5,{NULL},S_PLAY_XDIE2,0,0}, // S_PLAY_XDIE1 + {SPR_PLAY,15,5,{A_XScream},S_PLAY_XDIE3,0,0}, // S_PLAY_XDIE2 + {SPR_PLAY,16,5,{A_Fall},S_PLAY_XDIE4,0,0}, // S_PLAY_XDIE3 + {SPR_PLAY,17,5,{NULL},S_PLAY_XDIE5,0,0}, // S_PLAY_XDIE4 + {SPR_PLAY,18,5,{NULL},S_PLAY_XDIE6,0,0}, // S_PLAY_XDIE5 + {SPR_PLAY,19,5,{NULL},S_PLAY_XDIE7,0,0}, // S_PLAY_XDIE6 + {SPR_PLAY,20,5,{NULL},S_PLAY_XDIE8,0,0}, // S_PLAY_XDIE7 + {SPR_PLAY,21,5,{NULL},S_PLAY_XDIE9,0,0}, // S_PLAY_XDIE8 + {SPR_PLAY,22,-1,{NULL},S_NULL,0,0}, // S_PLAY_XDIE9 + {SPR_POSS,0,10,{A_Look},S_POSS_STND2,0,0}, // S_POSS_STND + {SPR_POSS,1,10,{A_Look},S_POSS_STND,0,0}, // S_POSS_STND2 + {SPR_POSS,0,4,{A_Chase},S_POSS_RUN2,0,0}, // S_POSS_RUN1 + {SPR_POSS,0,4,{A_Chase},S_POSS_RUN3,0,0}, // S_POSS_RUN2 + {SPR_POSS,1,4,{A_Chase},S_POSS_RUN4,0,0}, // S_POSS_RUN3 + {SPR_POSS,1,4,{A_Chase},S_POSS_RUN5,0,0}, // S_POSS_RUN4 + {SPR_POSS,2,4,{A_Chase},S_POSS_RUN6,0,0}, // S_POSS_RUN5 + {SPR_POSS,2,4,{A_Chase},S_POSS_RUN7,0,0}, // S_POSS_RUN6 + {SPR_POSS,3,4,{A_Chase},S_POSS_RUN8,0,0}, // S_POSS_RUN7 + {SPR_POSS,3,4,{A_Chase},S_POSS_RUN1,0,0}, // S_POSS_RUN8 + {SPR_POSS,4,10,{A_FaceTarget},S_POSS_ATK2,0,0}, // S_POSS_ATK1 + {SPR_POSS,5,8,{A_PosAttack},S_POSS_ATK3,0,0}, // S_POSS_ATK2 + {SPR_POSS,4,8,{NULL},S_POSS_RUN1,0,0}, // S_POSS_ATK3 + {SPR_POSS,6,3,{NULL},S_POSS_PAIN2,0,0}, // S_POSS_PAIN + {SPR_POSS,6,3,{A_Pain},S_POSS_RUN1,0,0}, // S_POSS_PAIN2 + {SPR_POSS,7,5,{NULL},S_POSS_DIE2,0,0}, // S_POSS_DIE1 + {SPR_POSS,8,5,{A_Scream},S_POSS_DIE3,0,0}, // S_POSS_DIE2 + {SPR_POSS,9,5,{A_Fall},S_POSS_DIE4,0,0}, // S_POSS_DIE3 + {SPR_POSS,10,5,{NULL},S_POSS_DIE5,0,0}, // S_POSS_DIE4 + {SPR_POSS,11,-1,{NULL},S_NULL,0,0}, // S_POSS_DIE5 + {SPR_POSS,12,5,{NULL},S_POSS_XDIE2,0,0}, // S_POSS_XDIE1 + {SPR_POSS,13,5,{A_XScream},S_POSS_XDIE3,0,0}, // S_POSS_XDIE2 + {SPR_POSS,14,5,{A_Fall},S_POSS_XDIE4,0,0}, // S_POSS_XDIE3 + {SPR_POSS,15,5,{NULL},S_POSS_XDIE5,0,0}, // S_POSS_XDIE4 + {SPR_POSS,16,5,{NULL},S_POSS_XDIE6,0,0}, // S_POSS_XDIE5 + {SPR_POSS,17,5,{NULL},S_POSS_XDIE7,0,0}, // S_POSS_XDIE6 + {SPR_POSS,18,5,{NULL},S_POSS_XDIE8,0,0}, // S_POSS_XDIE7 + {SPR_POSS,19,5,{NULL},S_POSS_XDIE9,0,0}, // S_POSS_XDIE8 + {SPR_POSS,20,-1,{NULL},S_NULL,0,0}, // S_POSS_XDIE9 + {SPR_POSS,10,5,{NULL},S_POSS_RAISE2,0,0}, // S_POSS_RAISE1 + {SPR_POSS,9,5,{NULL},S_POSS_RAISE3,0,0}, // S_POSS_RAISE2 + {SPR_POSS,8,5,{NULL},S_POSS_RAISE4,0,0}, // S_POSS_RAISE3 + {SPR_POSS,7,5,{NULL},S_POSS_RUN1,0,0}, // S_POSS_RAISE4 + {SPR_SPOS,0,10,{A_Look},S_SPOS_STND2,0,0}, // S_SPOS_STND + {SPR_SPOS,1,10,{A_Look},S_SPOS_STND,0,0}, // S_SPOS_STND2 + {SPR_SPOS,0,3,{A_Chase},S_SPOS_RUN2,0,0}, // S_SPOS_RUN1 + {SPR_SPOS,0,3,{A_Chase},S_SPOS_RUN3,0,0}, // S_SPOS_RUN2 + {SPR_SPOS,1,3,{A_Chase},S_SPOS_RUN4,0,0}, // S_SPOS_RUN3 + {SPR_SPOS,1,3,{A_Chase},S_SPOS_RUN5,0,0}, // S_SPOS_RUN4 + {SPR_SPOS,2,3,{A_Chase},S_SPOS_RUN6,0,0}, // S_SPOS_RUN5 + {SPR_SPOS,2,3,{A_Chase},S_SPOS_RUN7,0,0}, // S_SPOS_RUN6 + {SPR_SPOS,3,3,{A_Chase},S_SPOS_RUN8,0,0}, // S_SPOS_RUN7 + {SPR_SPOS,3,3,{A_Chase},S_SPOS_RUN1,0,0}, // S_SPOS_RUN8 + {SPR_SPOS,4,10,{A_FaceTarget},S_SPOS_ATK2,0,0}, // S_SPOS_ATK1 + {SPR_SPOS,32773,10,{A_SPosAttack},S_SPOS_ATK3,0,0}, // S_SPOS_ATK2 + {SPR_SPOS,4,10,{NULL},S_SPOS_RUN1,0,0}, // S_SPOS_ATK3 + {SPR_SPOS,6,3,{NULL},S_SPOS_PAIN2,0,0}, // S_SPOS_PAIN + {SPR_SPOS,6,3,{A_Pain},S_SPOS_RUN1,0,0}, // S_SPOS_PAIN2 + {SPR_SPOS,7,5,{NULL},S_SPOS_DIE2,0,0}, // S_SPOS_DIE1 + {SPR_SPOS,8,5,{A_Scream},S_SPOS_DIE3,0,0}, // S_SPOS_DIE2 + {SPR_SPOS,9,5,{A_Fall},S_SPOS_DIE4,0,0}, // S_SPOS_DIE3 + {SPR_SPOS,10,5,{NULL},S_SPOS_DIE5,0,0}, // S_SPOS_DIE4 + {SPR_SPOS,11,-1,{NULL},S_NULL,0,0}, // S_SPOS_DIE5 + {SPR_SPOS,12,5,{NULL},S_SPOS_XDIE2,0,0}, // S_SPOS_XDIE1 + {SPR_SPOS,13,5,{A_XScream},S_SPOS_XDIE3,0,0}, // S_SPOS_XDIE2 + {SPR_SPOS,14,5,{A_Fall},S_SPOS_XDIE4,0,0}, // S_SPOS_XDIE3 + {SPR_SPOS,15,5,{NULL},S_SPOS_XDIE5,0,0}, // S_SPOS_XDIE4 + {SPR_SPOS,16,5,{NULL},S_SPOS_XDIE6,0,0}, // S_SPOS_XDIE5 + {SPR_SPOS,17,5,{NULL},S_SPOS_XDIE7,0,0}, // S_SPOS_XDIE6 + {SPR_SPOS,18,5,{NULL},S_SPOS_XDIE8,0,0}, // S_SPOS_XDIE7 + {SPR_SPOS,19,5,{NULL},S_SPOS_XDIE9,0,0}, // S_SPOS_XDIE8 + {SPR_SPOS,20,-1,{NULL},S_NULL,0,0}, // S_SPOS_XDIE9 + {SPR_SPOS,11,5,{NULL},S_SPOS_RAISE2,0,0}, // S_SPOS_RAISE1 + {SPR_SPOS,10,5,{NULL},S_SPOS_RAISE3,0,0}, // S_SPOS_RAISE2 + {SPR_SPOS,9,5,{NULL},S_SPOS_RAISE4,0,0}, // S_SPOS_RAISE3 + {SPR_SPOS,8,5,{NULL},S_SPOS_RAISE5,0,0}, // S_SPOS_RAISE4 + {SPR_SPOS,7,5,{NULL},S_SPOS_RUN1,0,0}, // S_SPOS_RAISE5 + {SPR_VILE,0,10,{A_Look},S_VILE_STND2,0,0}, // S_VILE_STND + {SPR_VILE,1,10,{A_Look},S_VILE_STND,0,0}, // S_VILE_STND2 + {SPR_VILE,0,2,{A_VileChase},S_VILE_RUN2,0,0}, // S_VILE_RUN1 + {SPR_VILE,0,2,{A_VileChase},S_VILE_RUN3,0,0}, // S_VILE_RUN2 + {SPR_VILE,1,2,{A_VileChase},S_VILE_RUN4,0,0}, // S_VILE_RUN3 + {SPR_VILE,1,2,{A_VileChase},S_VILE_RUN5,0,0}, // S_VILE_RUN4 + {SPR_VILE,2,2,{A_VileChase},S_VILE_RUN6,0,0}, // S_VILE_RUN5 + {SPR_VILE,2,2,{A_VileChase},S_VILE_RUN7,0,0}, // S_VILE_RUN6 + {SPR_VILE,3,2,{A_VileChase},S_VILE_RUN8,0,0}, // S_VILE_RUN7 + {SPR_VILE,3,2,{A_VileChase},S_VILE_RUN9,0,0}, // S_VILE_RUN8 + {SPR_VILE,4,2,{A_VileChase},S_VILE_RUN10,0,0}, // S_VILE_RUN9 + {SPR_VILE,4,2,{A_VileChase},S_VILE_RUN11,0,0}, // S_VILE_RUN10 + {SPR_VILE,5,2,{A_VileChase},S_VILE_RUN12,0,0}, // S_VILE_RUN11 + {SPR_VILE,5,2,{A_VileChase},S_VILE_RUN1,0,0}, // S_VILE_RUN12 + {SPR_VILE,32774,0,{A_VileStart},S_VILE_ATK2,0,0}, // S_VILE_ATK1 + {SPR_VILE,32774,10,{A_FaceTarget},S_VILE_ATK3,0,0}, // S_VILE_ATK2 + {SPR_VILE,32775,8,{A_VileTarget},S_VILE_ATK4,0,0}, // S_VILE_ATK3 + {SPR_VILE,32776,8,{A_FaceTarget},S_VILE_ATK5,0,0}, // S_VILE_ATK4 + {SPR_VILE,32777,8,{A_FaceTarget},S_VILE_ATK6,0,0}, // S_VILE_ATK5 + {SPR_VILE,32778,8,{A_FaceTarget},S_VILE_ATK7,0,0}, // S_VILE_ATK6 + {SPR_VILE,32779,8,{A_FaceTarget},S_VILE_ATK8,0,0}, // S_VILE_ATK7 + {SPR_VILE,32780,8,{A_FaceTarget},S_VILE_ATK9,0,0}, // S_VILE_ATK8 + {SPR_VILE,32781,8,{A_FaceTarget},S_VILE_ATK10,0,0}, // S_VILE_ATK9 + {SPR_VILE,32782,8,{A_VileAttack},S_VILE_ATK11,0,0}, // S_VILE_ATK10 + {SPR_VILE,32783,20,{NULL},S_VILE_RUN1,0,0}, // S_VILE_ATK11 + {SPR_VILE,32794,10,{NULL},S_VILE_HEAL2,0,0}, // S_VILE_HEAL1 + {SPR_VILE,32795,10,{NULL},S_VILE_HEAL3,0,0}, // S_VILE_HEAL2 + {SPR_VILE,32796,10,{NULL},S_VILE_RUN1,0,0}, // S_VILE_HEAL3 + {SPR_VILE,16,5,{NULL},S_VILE_PAIN2,0,0}, // S_VILE_PAIN + {SPR_VILE,16,5,{A_Pain},S_VILE_RUN1,0,0}, // S_VILE_PAIN2 + {SPR_VILE,16,7,{NULL},S_VILE_DIE2,0,0}, // S_VILE_DIE1 + {SPR_VILE,17,7,{A_Scream},S_VILE_DIE3,0,0}, // S_VILE_DIE2 + {SPR_VILE,18,7,{A_Fall},S_VILE_DIE4,0,0}, // S_VILE_DIE3 + {SPR_VILE,19,7,{NULL},S_VILE_DIE5,0,0}, // S_VILE_DIE4 + {SPR_VILE,20,7,{NULL},S_VILE_DIE6,0,0}, // S_VILE_DIE5 + {SPR_VILE,21,7,{NULL},S_VILE_DIE7,0,0}, // S_VILE_DIE6 + {SPR_VILE,22,7,{NULL},S_VILE_DIE8,0,0}, // S_VILE_DIE7 + {SPR_VILE,23,5,{NULL},S_VILE_DIE9,0,0}, // S_VILE_DIE8 + {SPR_VILE,24,5,{NULL},S_VILE_DIE10,0,0}, // S_VILE_DIE9 + {SPR_VILE,25,-1,{NULL},S_NULL,0,0}, // S_VILE_DIE10 + {SPR_FIRE,32768,2,{A_StartFire},S_FIRE2,0,0}, // S_FIRE1 + {SPR_FIRE,32769,2,{A_Fire},S_FIRE3,0,0}, // S_FIRE2 + {SPR_FIRE,32768,2,{A_Fire},S_FIRE4,0,0}, // S_FIRE3 + {SPR_FIRE,32769,2,{A_Fire},S_FIRE5,0,0}, // S_FIRE4 + {SPR_FIRE,32770,2,{A_FireCrackle},S_FIRE6,0,0}, // S_FIRE5 + {SPR_FIRE,32769,2,{A_Fire},S_FIRE7,0,0}, // S_FIRE6 + {SPR_FIRE,32770,2,{A_Fire},S_FIRE8,0,0}, // S_FIRE7 + {SPR_FIRE,32769,2,{A_Fire},S_FIRE9,0,0}, // S_FIRE8 + {SPR_FIRE,32770,2,{A_Fire},S_FIRE10,0,0}, // S_FIRE9 + {SPR_FIRE,32771,2,{A_Fire},S_FIRE11,0,0}, // S_FIRE10 + {SPR_FIRE,32770,2,{A_Fire},S_FIRE12,0,0}, // S_FIRE11 + {SPR_FIRE,32771,2,{A_Fire},S_FIRE13,0,0}, // S_FIRE12 + {SPR_FIRE,32770,2,{A_Fire},S_FIRE14,0,0}, // S_FIRE13 + {SPR_FIRE,32771,2,{A_Fire},S_FIRE15,0,0}, // S_FIRE14 + {SPR_FIRE,32772,2,{A_Fire},S_FIRE16,0,0}, // S_FIRE15 + {SPR_FIRE,32771,2,{A_Fire},S_FIRE17,0,0}, // S_FIRE16 + {SPR_FIRE,32772,2,{A_Fire},S_FIRE18,0,0}, // S_FIRE17 + {SPR_FIRE,32771,2,{A_Fire},S_FIRE19,0,0}, // S_FIRE18 + {SPR_FIRE,32772,2,{A_FireCrackle},S_FIRE20,0,0}, // S_FIRE19 + {SPR_FIRE,32773,2,{A_Fire},S_FIRE21,0,0}, // S_FIRE20 + {SPR_FIRE,32772,2,{A_Fire},S_FIRE22,0,0}, // S_FIRE21 + {SPR_FIRE,32773,2,{A_Fire},S_FIRE23,0,0}, // S_FIRE22 + {SPR_FIRE,32772,2,{A_Fire},S_FIRE24,0,0}, // S_FIRE23 + {SPR_FIRE,32773,2,{A_Fire},S_FIRE25,0,0}, // S_FIRE24 + {SPR_FIRE,32774,2,{A_Fire},S_FIRE26,0,0}, // S_FIRE25 + {SPR_FIRE,32775,2,{A_Fire},S_FIRE27,0,0}, // S_FIRE26 + {SPR_FIRE,32774,2,{A_Fire},S_FIRE28,0,0}, // S_FIRE27 + {SPR_FIRE,32775,2,{A_Fire},S_FIRE29,0,0}, // S_FIRE28 + {SPR_FIRE,32774,2,{A_Fire},S_FIRE30,0,0}, // S_FIRE29 + {SPR_FIRE,32775,2,{A_Fire},S_NULL,0,0}, // S_FIRE30 + {SPR_PUFF,1,4,{NULL},S_SMOKE2,0,0}, // S_SMOKE1 + {SPR_PUFF,2,4,{NULL},S_SMOKE3,0,0}, // S_SMOKE2 + {SPR_PUFF,1,4,{NULL},S_SMOKE4,0,0}, // S_SMOKE3 + {SPR_PUFF,2,4,{NULL},S_SMOKE5,0,0}, // S_SMOKE4 + {SPR_PUFF,3,4,{NULL},S_NULL,0,0}, // S_SMOKE5 + {SPR_FATB,32768,2,{A_Tracer},S_TRACER2,0,0}, // S_TRACER + {SPR_FATB,32769,2,{A_Tracer},S_TRACER,0,0}, // S_TRACER2 + {SPR_FBXP,32768,8,{NULL},S_TRACEEXP2,0,0}, // S_TRACEEXP1 + {SPR_FBXP,32769,6,{NULL},S_TRACEEXP3,0,0}, // S_TRACEEXP2 + {SPR_FBXP,32770,4,{NULL},S_NULL,0,0}, // S_TRACEEXP3 + {SPR_SKEL,0,10,{A_Look},S_SKEL_STND2,0,0}, // S_SKEL_STND + {SPR_SKEL,1,10,{A_Look},S_SKEL_STND,0,0}, // S_SKEL_STND2 + {SPR_SKEL,0,2,{A_Chase},S_SKEL_RUN2,0,0}, // S_SKEL_RUN1 + {SPR_SKEL,0,2,{A_Chase},S_SKEL_RUN3,0,0}, // S_SKEL_RUN2 + {SPR_SKEL,1,2,{A_Chase},S_SKEL_RUN4,0,0}, // S_SKEL_RUN3 + {SPR_SKEL,1,2,{A_Chase},S_SKEL_RUN5,0,0}, // S_SKEL_RUN4 + {SPR_SKEL,2,2,{A_Chase},S_SKEL_RUN6,0,0}, // S_SKEL_RUN5 + {SPR_SKEL,2,2,{A_Chase},S_SKEL_RUN7,0,0}, // S_SKEL_RUN6 + {SPR_SKEL,3,2,{A_Chase},S_SKEL_RUN8,0,0}, // S_SKEL_RUN7 + {SPR_SKEL,3,2,{A_Chase},S_SKEL_RUN9,0,0}, // S_SKEL_RUN8 + {SPR_SKEL,4,2,{A_Chase},S_SKEL_RUN10,0,0}, // S_SKEL_RUN9 + {SPR_SKEL,4,2,{A_Chase},S_SKEL_RUN11,0,0}, // S_SKEL_RUN10 + {SPR_SKEL,5,2,{A_Chase},S_SKEL_RUN12,0,0}, // S_SKEL_RUN11 + {SPR_SKEL,5,2,{A_Chase},S_SKEL_RUN1,0,0}, // S_SKEL_RUN12 + {SPR_SKEL,6,0,{A_FaceTarget},S_SKEL_FIST2,0,0}, // S_SKEL_FIST1 + {SPR_SKEL,6,6,{A_SkelWhoosh},S_SKEL_FIST3,0,0}, // S_SKEL_FIST2 + {SPR_SKEL,7,6,{A_FaceTarget},S_SKEL_FIST4,0,0}, // S_SKEL_FIST3 + {SPR_SKEL,8,6,{A_SkelFist},S_SKEL_RUN1,0,0}, // S_SKEL_FIST4 + {SPR_SKEL,32777,0,{A_FaceTarget},S_SKEL_MISS2,0,0}, // S_SKEL_MISS1 + {SPR_SKEL,32777,10,{A_FaceTarget},S_SKEL_MISS3,0,0}, // S_SKEL_MISS2 + {SPR_SKEL,10,10,{A_SkelMissile},S_SKEL_MISS4,0,0}, // S_SKEL_MISS3 + {SPR_SKEL,10,10,{A_FaceTarget},S_SKEL_RUN1,0,0}, // S_SKEL_MISS4 + {SPR_SKEL,11,5,{NULL},S_SKEL_PAIN2,0,0}, // S_SKEL_PAIN + {SPR_SKEL,11,5,{A_Pain},S_SKEL_RUN1,0,0}, // S_SKEL_PAIN2 + {SPR_SKEL,11,7,{NULL},S_SKEL_DIE2,0,0}, // S_SKEL_DIE1 + {SPR_SKEL,12,7,{NULL},S_SKEL_DIE3,0,0}, // S_SKEL_DIE2 + {SPR_SKEL,13,7,{A_Scream},S_SKEL_DIE4,0,0}, // S_SKEL_DIE3 + {SPR_SKEL,14,7,{A_Fall},S_SKEL_DIE5,0,0}, // S_SKEL_DIE4 + {SPR_SKEL,15,7,{NULL},S_SKEL_DIE6,0,0}, // S_SKEL_DIE5 + {SPR_SKEL,16,-1,{NULL},S_NULL,0,0}, // S_SKEL_DIE6 + {SPR_SKEL,16,5,{NULL},S_SKEL_RAISE2,0,0}, // S_SKEL_RAISE1 + {SPR_SKEL,15,5,{NULL},S_SKEL_RAISE3,0,0}, // S_SKEL_RAISE2 + {SPR_SKEL,14,5,{NULL},S_SKEL_RAISE4,0,0}, // S_SKEL_RAISE3 + {SPR_SKEL,13,5,{NULL},S_SKEL_RAISE5,0,0}, // S_SKEL_RAISE4 + {SPR_SKEL,12,5,{NULL},S_SKEL_RAISE6,0,0}, // S_SKEL_RAISE5 + {SPR_SKEL,11,5,{NULL},S_SKEL_RUN1,0,0}, // S_SKEL_RAISE6 + {SPR_MANF,32768,4,{NULL},S_FATSHOT2,0,0}, // S_FATSHOT1 + {SPR_MANF,32769,4,{NULL},S_FATSHOT1,0,0}, // S_FATSHOT2 + {SPR_MISL,32769,8,{NULL},S_FATSHOTX2,0,0}, // S_FATSHOTX1 + {SPR_MISL,32770,6,{NULL},S_FATSHOTX3,0,0}, // S_FATSHOTX2 + {SPR_MISL,32771,4,{NULL},S_NULL,0,0}, // S_FATSHOTX3 + {SPR_FATT,0,15,{A_Look},S_FATT_STND2,0,0}, // S_FATT_STND + {SPR_FATT,1,15,{A_Look},S_FATT_STND,0,0}, // S_FATT_STND2 + {SPR_FATT,0,4,{A_Chase},S_FATT_RUN2,0,0}, // S_FATT_RUN1 + {SPR_FATT,0,4,{A_Chase},S_FATT_RUN3,0,0}, // S_FATT_RUN2 + {SPR_FATT,1,4,{A_Chase},S_FATT_RUN4,0,0}, // S_FATT_RUN3 + {SPR_FATT,1,4,{A_Chase},S_FATT_RUN5,0,0}, // S_FATT_RUN4 + {SPR_FATT,2,4,{A_Chase},S_FATT_RUN6,0,0}, // S_FATT_RUN5 + {SPR_FATT,2,4,{A_Chase},S_FATT_RUN7,0,0}, // S_FATT_RUN6 + {SPR_FATT,3,4,{A_Chase},S_FATT_RUN8,0,0}, // S_FATT_RUN7 + {SPR_FATT,3,4,{A_Chase},S_FATT_RUN9,0,0}, // S_FATT_RUN8 + {SPR_FATT,4,4,{A_Chase},S_FATT_RUN10,0,0}, // S_FATT_RUN9 + {SPR_FATT,4,4,{A_Chase},S_FATT_RUN11,0,0}, // S_FATT_RUN10 + {SPR_FATT,5,4,{A_Chase},S_FATT_RUN12,0,0}, // S_FATT_RUN11 + {SPR_FATT,5,4,{A_Chase},S_FATT_RUN1,0,0}, // S_FATT_RUN12 + {SPR_FATT,6,20,{A_FatRaise},S_FATT_ATK2,0,0}, // S_FATT_ATK1 + {SPR_FATT,32775,10,{A_FatAttack1},S_FATT_ATK3,0,0}, // S_FATT_ATK2 + {SPR_FATT,8,5,{A_FaceTarget},S_FATT_ATK4,0,0}, // S_FATT_ATK3 + {SPR_FATT,6,5,{A_FaceTarget},S_FATT_ATK5,0,0}, // S_FATT_ATK4 + {SPR_FATT,32775,10,{A_FatAttack2},S_FATT_ATK6,0,0}, // S_FATT_ATK5 + {SPR_FATT,8,5,{A_FaceTarget},S_FATT_ATK7,0,0}, // S_FATT_ATK6 + {SPR_FATT,6,5,{A_FaceTarget},S_FATT_ATK8,0,0}, // S_FATT_ATK7 + {SPR_FATT,32775,10,{A_FatAttack3},S_FATT_ATK9,0,0}, // S_FATT_ATK8 + {SPR_FATT,8,5,{A_FaceTarget},S_FATT_ATK10,0,0}, // S_FATT_ATK9 + {SPR_FATT,6,5,{A_FaceTarget},S_FATT_RUN1,0,0}, // S_FATT_ATK10 + {SPR_FATT,9,3,{NULL},S_FATT_PAIN2,0,0}, // S_FATT_PAIN + {SPR_FATT,9,3,{A_Pain},S_FATT_RUN1,0,0}, // S_FATT_PAIN2 + {SPR_FATT,10,6,{NULL},S_FATT_DIE2,0,0}, // S_FATT_DIE1 + {SPR_FATT,11,6,{A_Scream},S_FATT_DIE3,0,0}, // S_FATT_DIE2 + {SPR_FATT,12,6,{A_Fall},S_FATT_DIE4,0,0}, // S_FATT_DIE3 + {SPR_FATT,13,6,{NULL},S_FATT_DIE5,0,0}, // S_FATT_DIE4 + {SPR_FATT,14,6,{NULL},S_FATT_DIE6,0,0}, // S_FATT_DIE5 + {SPR_FATT,15,6,{NULL},S_FATT_DIE7,0,0}, // S_FATT_DIE6 + {SPR_FATT,16,6,{NULL},S_FATT_DIE8,0,0}, // S_FATT_DIE7 + {SPR_FATT,17,6,{NULL},S_FATT_DIE9,0,0}, // S_FATT_DIE8 + {SPR_FATT,18,6,{NULL},S_FATT_DIE10,0,0}, // S_FATT_DIE9 + {SPR_FATT,19,-1,{A_BossDeath},S_NULL,0,0}, // S_FATT_DIE10 + {SPR_FATT,17,5,{NULL},S_FATT_RAISE2,0,0}, // S_FATT_RAISE1 + {SPR_FATT,16,5,{NULL},S_FATT_RAISE3,0,0}, // S_FATT_RAISE2 + {SPR_FATT,15,5,{NULL},S_FATT_RAISE4,0,0}, // S_FATT_RAISE3 + {SPR_FATT,14,5,{NULL},S_FATT_RAISE5,0,0}, // S_FATT_RAISE4 + {SPR_FATT,13,5,{NULL},S_FATT_RAISE6,0,0}, // S_FATT_RAISE5 + {SPR_FATT,12,5,{NULL},S_FATT_RAISE7,0,0}, // S_FATT_RAISE6 + {SPR_FATT,11,5,{NULL},S_FATT_RAISE8,0,0}, // S_FATT_RAISE7 + {SPR_FATT,10,5,{NULL},S_FATT_RUN1,0,0}, // S_FATT_RAISE8 + {SPR_CPOS,0,10,{A_Look},S_CPOS_STND2,0,0}, // S_CPOS_STND + {SPR_CPOS,1,10,{A_Look},S_CPOS_STND,0,0}, // S_CPOS_STND2 + {SPR_CPOS,0,3,{A_Chase},S_CPOS_RUN2,0,0}, // S_CPOS_RUN1 + {SPR_CPOS,0,3,{A_Chase},S_CPOS_RUN3,0,0}, // S_CPOS_RUN2 + {SPR_CPOS,1,3,{A_Chase},S_CPOS_RUN4,0,0}, // S_CPOS_RUN3 + {SPR_CPOS,1,3,{A_Chase},S_CPOS_RUN5,0,0}, // S_CPOS_RUN4 + {SPR_CPOS,2,3,{A_Chase},S_CPOS_RUN6,0,0}, // S_CPOS_RUN5 + {SPR_CPOS,2,3,{A_Chase},S_CPOS_RUN7,0,0}, // S_CPOS_RUN6 + {SPR_CPOS,3,3,{A_Chase},S_CPOS_RUN8,0,0}, // S_CPOS_RUN7 + {SPR_CPOS,3,3,{A_Chase},S_CPOS_RUN1,0,0}, // S_CPOS_RUN8 + {SPR_CPOS,4,10,{A_FaceTarget},S_CPOS_ATK2,0,0}, // S_CPOS_ATK1 + {SPR_CPOS,32773,4,{A_CPosAttack},S_CPOS_ATK3,0,0}, // S_CPOS_ATK2 + {SPR_CPOS,32772,4,{A_CPosAttack},S_CPOS_ATK4,0,0}, // S_CPOS_ATK3 + {SPR_CPOS,5,1,{A_CPosRefire},S_CPOS_ATK2,0,0}, // S_CPOS_ATK4 + {SPR_CPOS,6,3,{NULL},S_CPOS_PAIN2,0,0}, // S_CPOS_PAIN + {SPR_CPOS,6,3,{A_Pain},S_CPOS_RUN1,0,0}, // S_CPOS_PAIN2 + {SPR_CPOS,7,5,{NULL},S_CPOS_DIE2,0,0}, // S_CPOS_DIE1 + {SPR_CPOS,8,5,{A_Scream},S_CPOS_DIE3,0,0}, // S_CPOS_DIE2 + {SPR_CPOS,9,5,{A_Fall},S_CPOS_DIE4,0,0}, // S_CPOS_DIE3 + {SPR_CPOS,10,5,{NULL},S_CPOS_DIE5,0,0}, // S_CPOS_DIE4 + {SPR_CPOS,11,5,{NULL},S_CPOS_DIE6,0,0}, // S_CPOS_DIE5 + {SPR_CPOS,12,5,{NULL},S_CPOS_DIE7,0,0}, // S_CPOS_DIE6 + {SPR_CPOS,13,-1,{NULL},S_NULL,0,0}, // S_CPOS_DIE7 + {SPR_CPOS,14,5,{NULL},S_CPOS_XDIE2,0,0}, // S_CPOS_XDIE1 + {SPR_CPOS,15,5,{A_XScream},S_CPOS_XDIE3,0,0}, // S_CPOS_XDIE2 + {SPR_CPOS,16,5,{A_Fall},S_CPOS_XDIE4,0,0}, // S_CPOS_XDIE3 + {SPR_CPOS,17,5,{NULL},S_CPOS_XDIE5,0,0}, // S_CPOS_XDIE4 + {SPR_CPOS,18,5,{NULL},S_CPOS_XDIE6,0,0}, // S_CPOS_XDIE5 + {SPR_CPOS,19,-1,{NULL},S_NULL,0,0}, // S_CPOS_XDIE6 + {SPR_CPOS,13,5,{NULL},S_CPOS_RAISE2,0,0}, // S_CPOS_RAISE1 + {SPR_CPOS,12,5,{NULL},S_CPOS_RAISE3,0,0}, // S_CPOS_RAISE2 + {SPR_CPOS,11,5,{NULL},S_CPOS_RAISE4,0,0}, // S_CPOS_RAISE3 + {SPR_CPOS,10,5,{NULL},S_CPOS_RAISE5,0,0}, // S_CPOS_RAISE4 + {SPR_CPOS,9,5,{NULL},S_CPOS_RAISE6,0,0}, // S_CPOS_RAISE5 + {SPR_CPOS,8,5,{NULL},S_CPOS_RAISE7,0,0}, // S_CPOS_RAISE6 + {SPR_CPOS,7,5,{NULL},S_CPOS_RUN1,0,0}, // S_CPOS_RAISE7 + {SPR_TROO,0,10,{A_Look},S_TROO_STND2,0,0}, // S_TROO_STND + {SPR_TROO,1,10,{A_Look},S_TROO_STND,0,0}, // S_TROO_STND2 + {SPR_TROO,0,3,{A_Chase},S_TROO_RUN2,0,0}, // S_TROO_RUN1 + {SPR_TROO,0,3,{A_Chase},S_TROO_RUN3,0,0}, // S_TROO_RUN2 + {SPR_TROO,1,3,{A_Chase},S_TROO_RUN4,0,0}, // S_TROO_RUN3 + {SPR_TROO,1,3,{A_Chase},S_TROO_RUN5,0,0}, // S_TROO_RUN4 + {SPR_TROO,2,3,{A_Chase},S_TROO_RUN6,0,0}, // S_TROO_RUN5 + {SPR_TROO,2,3,{A_Chase},S_TROO_RUN7,0,0}, // S_TROO_RUN6 + {SPR_TROO,3,3,{A_Chase},S_TROO_RUN8,0,0}, // S_TROO_RUN7 + {SPR_TROO,3,3,{A_Chase},S_TROO_RUN1,0,0}, // S_TROO_RUN8 + {SPR_TROO,4,8,{A_FaceTarget},S_TROO_ATK2,0,0}, // S_TROO_ATK1 + {SPR_TROO,5,8,{A_FaceTarget},S_TROO_ATK3,0,0}, // S_TROO_ATK2 + {SPR_TROO,6,6,{A_TroopAttack},S_TROO_RUN1,0,0}, // S_TROO_ATK3 + {SPR_TROO,7,2,{NULL},S_TROO_PAIN2,0,0}, // S_TROO_PAIN + {SPR_TROO,7,2,{A_Pain},S_TROO_RUN1,0,0}, // S_TROO_PAIN2 + {SPR_TROO,8,8,{NULL},S_TROO_DIE2,0,0}, // S_TROO_DIE1 + {SPR_TROO,9,8,{A_Scream},S_TROO_DIE3,0,0}, // S_TROO_DIE2 + {SPR_TROO,10,6,{NULL},S_TROO_DIE4,0,0}, // S_TROO_DIE3 + {SPR_TROO,11,6,{A_Fall},S_TROO_DIE5,0,0}, // S_TROO_DIE4 + {SPR_TROO,12,-1,{NULL},S_NULL,0,0}, // S_TROO_DIE5 + {SPR_TROO,13,5,{NULL},S_TROO_XDIE2,0,0}, // S_TROO_XDIE1 + {SPR_TROO,14,5,{A_XScream},S_TROO_XDIE3,0,0}, // S_TROO_XDIE2 + {SPR_TROO,15,5,{NULL},S_TROO_XDIE4,0,0}, // S_TROO_XDIE3 + {SPR_TROO,16,5,{A_Fall},S_TROO_XDIE5,0,0}, // S_TROO_XDIE4 + {SPR_TROO,17,5,{NULL},S_TROO_XDIE6,0,0}, // S_TROO_XDIE5 + {SPR_TROO,18,5,{NULL},S_TROO_XDIE7,0,0}, // S_TROO_XDIE6 + {SPR_TROO,19,5,{NULL},S_TROO_XDIE8,0,0}, // S_TROO_XDIE7 + {SPR_TROO,20,-1,{NULL},S_NULL,0,0}, // S_TROO_XDIE8 + {SPR_TROO,12,8,{NULL},S_TROO_RAISE2,0,0}, // S_TROO_RAISE1 + {SPR_TROO,11,8,{NULL},S_TROO_RAISE3,0,0}, // S_TROO_RAISE2 + {SPR_TROO,10,6,{NULL},S_TROO_RAISE4,0,0}, // S_TROO_RAISE3 + {SPR_TROO,9,6,{NULL},S_TROO_RAISE5,0,0}, // S_TROO_RAISE4 + {SPR_TROO,8,6,{NULL},S_TROO_RUN1,0,0}, // S_TROO_RAISE5 + {SPR_SARG,0,10,{A_Look},S_SARG_STND2,0,0}, // S_SARG_STND + {SPR_SARG,1,10,{A_Look},S_SARG_STND,0,0}, // S_SARG_STND2 + {SPR_SARG,0,2,{A_Chase},S_SARG_RUN2,0,0}, // S_SARG_RUN1 + {SPR_SARG,0,2,{A_Chase},S_SARG_RUN3,0,0}, // S_SARG_RUN2 + {SPR_SARG,1,2,{A_Chase},S_SARG_RUN4,0,0}, // S_SARG_RUN3 + {SPR_SARG,1,2,{A_Chase},S_SARG_RUN5,0,0}, // S_SARG_RUN4 + {SPR_SARG,2,2,{A_Chase},S_SARG_RUN6,0,0}, // S_SARG_RUN5 + {SPR_SARG,2,2,{A_Chase},S_SARG_RUN7,0,0}, // S_SARG_RUN6 + {SPR_SARG,3,2,{A_Chase},S_SARG_RUN8,0,0}, // S_SARG_RUN7 + {SPR_SARG,3,2,{A_Chase},S_SARG_RUN1,0,0}, // S_SARG_RUN8 + {SPR_SARG,4,8,{A_FaceTarget},S_SARG_ATK2,0,0}, // S_SARG_ATK1 + {SPR_SARG,5,8,{A_FaceTarget},S_SARG_ATK3,0,0}, // S_SARG_ATK2 + {SPR_SARG,6,8,{A_SargAttack},S_SARG_RUN1,0,0}, // S_SARG_ATK3 + {SPR_SARG,7,2,{NULL},S_SARG_PAIN2,0,0}, // S_SARG_PAIN + {SPR_SARG,7,2,{A_Pain},S_SARG_RUN1,0,0}, // S_SARG_PAIN2 + {SPR_SARG,8,8,{NULL},S_SARG_DIE2,0,0}, // S_SARG_DIE1 + {SPR_SARG,9,8,{A_Scream},S_SARG_DIE3,0,0}, // S_SARG_DIE2 + {SPR_SARG,10,4,{NULL},S_SARG_DIE4,0,0}, // S_SARG_DIE3 + {SPR_SARG,11,4,{A_Fall},S_SARG_DIE5,0,0}, // S_SARG_DIE4 + {SPR_SARG,12,4,{NULL},S_SARG_DIE6,0,0}, // S_SARG_DIE5 + {SPR_SARG,13,-1,{NULL},S_NULL,0,0}, // S_SARG_DIE6 + {SPR_SARG,13,5,{NULL},S_SARG_RAISE2,0,0}, // S_SARG_RAISE1 + {SPR_SARG,12,5,{NULL},S_SARG_RAISE3,0,0}, // S_SARG_RAISE2 + {SPR_SARG,11,5,{NULL},S_SARG_RAISE4,0,0}, // S_SARG_RAISE3 + {SPR_SARG,10,5,{NULL},S_SARG_RAISE5,0,0}, // S_SARG_RAISE4 + {SPR_SARG,9,5,{NULL},S_SARG_RAISE6,0,0}, // S_SARG_RAISE5 + {SPR_SARG,8,5,{NULL},S_SARG_RUN1,0,0}, // S_SARG_RAISE6 + {SPR_HEAD,0,10,{A_Look},S_HEAD_STND,0,0}, // S_HEAD_STND + {SPR_HEAD,0,3,{A_Chase},S_HEAD_RUN1,0,0}, // S_HEAD_RUN1 + {SPR_HEAD,1,5,{A_FaceTarget},S_HEAD_ATK2,0,0}, // S_HEAD_ATK1 + {SPR_HEAD,2,5,{A_FaceTarget},S_HEAD_ATK3,0,0}, // S_HEAD_ATK2 + {SPR_HEAD,32771,5,{A_HeadAttack},S_HEAD_RUN1,0,0}, // S_HEAD_ATK3 + {SPR_HEAD,4,3,{NULL},S_HEAD_PAIN2,0,0}, // S_HEAD_PAIN + {SPR_HEAD,4,3,{A_Pain},S_HEAD_PAIN3,0,0}, // S_HEAD_PAIN2 + {SPR_HEAD,5,6,{NULL},S_HEAD_RUN1,0,0}, // S_HEAD_PAIN3 + {SPR_HEAD,6,8,{NULL},S_HEAD_DIE2,0,0}, // S_HEAD_DIE1 + {SPR_HEAD,7,8,{A_Scream},S_HEAD_DIE3,0,0}, // S_HEAD_DIE2 + {SPR_HEAD,8,8,{NULL},S_HEAD_DIE4,0,0}, // S_HEAD_DIE3 + {SPR_HEAD,9,8,{NULL},S_HEAD_DIE5,0,0}, // S_HEAD_DIE4 + {SPR_HEAD,10,8,{A_Fall},S_HEAD_DIE6,0,0}, // S_HEAD_DIE5 + {SPR_HEAD,11,-1,{NULL},S_NULL,0,0}, // S_HEAD_DIE6 + {SPR_HEAD,11,8,{NULL},S_HEAD_RAISE2,0,0}, // S_HEAD_RAISE1 + {SPR_HEAD,10,8,{NULL},S_HEAD_RAISE3,0,0}, // S_HEAD_RAISE2 + {SPR_HEAD,9,8,{NULL},S_HEAD_RAISE4,0,0}, // S_HEAD_RAISE3 + {SPR_HEAD,8,8,{NULL},S_HEAD_RAISE5,0,0}, // S_HEAD_RAISE4 + {SPR_HEAD,7,8,{NULL},S_HEAD_RAISE6,0,0}, // S_HEAD_RAISE5 + {SPR_HEAD,6,8,{NULL},S_HEAD_RUN1,0,0}, // S_HEAD_RAISE6 + {SPR_BAL7,32768,4,{NULL},S_BRBALL2,0,0}, // S_BRBALL1 + {SPR_BAL7,32769,4,{NULL},S_BRBALL1,0,0}, // S_BRBALL2 + {SPR_BAL7,32770,6,{NULL},S_BRBALLX2,0,0}, // S_BRBALLX1 + {SPR_BAL7,32771,6,{NULL},S_BRBALLX3,0,0}, // S_BRBALLX2 + {SPR_BAL7,32772,6,{NULL},S_NULL,0,0}, // S_BRBALLX3 + {SPR_BOSS,0,10,{A_Look},S_BOSS_STND2,0,0}, // S_BOSS_STND + {SPR_BOSS,1,10,{A_Look},S_BOSS_STND,0,0}, // S_BOSS_STND2 + {SPR_BOSS,0,3,{A_Chase},S_BOSS_RUN2,0,0}, // S_BOSS_RUN1 + {SPR_BOSS,0,3,{A_Chase},S_BOSS_RUN3,0,0}, // S_BOSS_RUN2 + {SPR_BOSS,1,3,{A_Chase},S_BOSS_RUN4,0,0}, // S_BOSS_RUN3 + {SPR_BOSS,1,3,{A_Chase},S_BOSS_RUN5,0,0}, // S_BOSS_RUN4 + {SPR_BOSS,2,3,{A_Chase},S_BOSS_RUN6,0,0}, // S_BOSS_RUN5 + {SPR_BOSS,2,3,{A_Chase},S_BOSS_RUN7,0,0}, // S_BOSS_RUN6 + {SPR_BOSS,3,3,{A_Chase},S_BOSS_RUN8,0,0}, // S_BOSS_RUN7 + {SPR_BOSS,3,3,{A_Chase},S_BOSS_RUN1,0,0}, // S_BOSS_RUN8 + {SPR_BOSS,4,8,{A_FaceTarget},S_BOSS_ATK2,0,0}, // S_BOSS_ATK1 + {SPR_BOSS,5,8,{A_FaceTarget},S_BOSS_ATK3,0,0}, // S_BOSS_ATK2 + {SPR_BOSS,6,8,{A_BruisAttack},S_BOSS_RUN1,0,0}, // S_BOSS_ATK3 + {SPR_BOSS,7,2,{NULL},S_BOSS_PAIN2,0,0}, // S_BOSS_PAIN + {SPR_BOSS,7,2,{A_Pain},S_BOSS_RUN1,0,0}, // S_BOSS_PAIN2 + {SPR_BOSS,8,8,{NULL},S_BOSS_DIE2,0,0}, // S_BOSS_DIE1 + {SPR_BOSS,9,8,{A_Scream},S_BOSS_DIE3,0,0}, // S_BOSS_DIE2 + {SPR_BOSS,10,8,{NULL},S_BOSS_DIE4,0,0}, // S_BOSS_DIE3 + {SPR_BOSS,11,8,{A_Fall},S_BOSS_DIE5,0,0}, // S_BOSS_DIE4 + {SPR_BOSS,12,8,{NULL},S_BOSS_DIE6,0,0}, // S_BOSS_DIE5 + {SPR_BOSS,13,8,{NULL},S_BOSS_DIE7,0,0}, // S_BOSS_DIE6 + {SPR_BOSS,14,-1,{A_BossDeath},S_NULL,0,0}, // S_BOSS_DIE7 + {SPR_BOSS,14,8,{NULL},S_BOSS_RAISE2,0,0}, // S_BOSS_RAISE1 + {SPR_BOSS,13,8,{NULL},S_BOSS_RAISE3,0,0}, // S_BOSS_RAISE2 + {SPR_BOSS,12,8,{NULL},S_BOSS_RAISE4,0,0}, // S_BOSS_RAISE3 + {SPR_BOSS,11,8,{NULL},S_BOSS_RAISE5,0,0}, // S_BOSS_RAISE4 + {SPR_BOSS,10,8,{NULL},S_BOSS_RAISE6,0,0}, // S_BOSS_RAISE5 + {SPR_BOSS,9,8,{NULL},S_BOSS_RAISE7,0,0}, // S_BOSS_RAISE6 + {SPR_BOSS,8,8,{NULL},S_BOSS_RUN1,0,0}, // S_BOSS_RAISE7 + {SPR_BOS2,0,10,{A_Look},S_BOS2_STND2,0,0}, // S_BOS2_STND + {SPR_BOS2,1,10,{A_Look},S_BOS2_STND,0,0}, // S_BOS2_STND2 + {SPR_BOS2,0,3,{A_Chase},S_BOS2_RUN2,0,0}, // S_BOS2_RUN1 + {SPR_BOS2,0,3,{A_Chase},S_BOS2_RUN3,0,0}, // S_BOS2_RUN2 + {SPR_BOS2,1,3,{A_Chase},S_BOS2_RUN4,0,0}, // S_BOS2_RUN3 + {SPR_BOS2,1,3,{A_Chase},S_BOS2_RUN5,0,0}, // S_BOS2_RUN4 + {SPR_BOS2,2,3,{A_Chase},S_BOS2_RUN6,0,0}, // S_BOS2_RUN5 + {SPR_BOS2,2,3,{A_Chase},S_BOS2_RUN7,0,0}, // S_BOS2_RUN6 + {SPR_BOS2,3,3,{A_Chase},S_BOS2_RUN8,0,0}, // S_BOS2_RUN7 + {SPR_BOS2,3,3,{A_Chase},S_BOS2_RUN1,0,0}, // S_BOS2_RUN8 + {SPR_BOS2,4,8,{A_FaceTarget},S_BOS2_ATK2,0,0}, // S_BOS2_ATK1 + {SPR_BOS2,5,8,{A_FaceTarget},S_BOS2_ATK3,0,0}, // S_BOS2_ATK2 + {SPR_BOS2,6,8,{A_BruisAttack},S_BOS2_RUN1,0,0}, // S_BOS2_ATK3 + {SPR_BOS2,7,2,{NULL},S_BOS2_PAIN2,0,0}, // S_BOS2_PAIN + {SPR_BOS2,7,2,{A_Pain},S_BOS2_RUN1,0,0}, // S_BOS2_PAIN2 + {SPR_BOS2,8,8,{NULL},S_BOS2_DIE2,0,0}, // S_BOS2_DIE1 + {SPR_BOS2,9,8,{A_Scream},S_BOS2_DIE3,0,0}, // S_BOS2_DIE2 + {SPR_BOS2,10,8,{NULL},S_BOS2_DIE4,0,0}, // S_BOS2_DIE3 + {SPR_BOS2,11,8,{A_Fall},S_BOS2_DIE5,0,0}, // S_BOS2_DIE4 + {SPR_BOS2,12,8,{NULL},S_BOS2_DIE6,0,0}, // S_BOS2_DIE5 + {SPR_BOS2,13,8,{NULL},S_BOS2_DIE7,0,0}, // S_BOS2_DIE6 + {SPR_BOS2,14,-1,{NULL},S_NULL,0,0}, // S_BOS2_DIE7 + {SPR_BOS2,14,8,{NULL},S_BOS2_RAISE2,0,0}, // S_BOS2_RAISE1 + {SPR_BOS2,13,8,{NULL},S_BOS2_RAISE3,0,0}, // S_BOS2_RAISE2 + {SPR_BOS2,12,8,{NULL},S_BOS2_RAISE4,0,0}, // S_BOS2_RAISE3 + {SPR_BOS2,11,8,{NULL},S_BOS2_RAISE5,0,0}, // S_BOS2_RAISE4 + {SPR_BOS2,10,8,{NULL},S_BOS2_RAISE6,0,0}, // S_BOS2_RAISE5 + {SPR_BOS2,9,8,{NULL},S_BOS2_RAISE7,0,0}, // S_BOS2_RAISE6 + {SPR_BOS2,8,8,{NULL},S_BOS2_RUN1,0,0}, // S_BOS2_RAISE7 + {SPR_SKUL,32768,10,{A_Look},S_SKULL_STND2,0,0}, // S_SKULL_STND + {SPR_SKUL,32769,10,{A_Look},S_SKULL_STND,0,0}, // S_SKULL_STND2 + {SPR_SKUL,32768,6,{A_Chase},S_SKULL_RUN2,0,0}, // S_SKULL_RUN1 + {SPR_SKUL,32769,6,{A_Chase},S_SKULL_RUN1,0,0}, // S_SKULL_RUN2 + {SPR_SKUL,32770,10,{A_FaceTarget},S_SKULL_ATK2,0,0}, // S_SKULL_ATK1 + {SPR_SKUL,32771,4,{A_SkullAttack},S_SKULL_ATK3,0,0}, // S_SKULL_ATK2 + {SPR_SKUL,32770,4,{NULL},S_SKULL_ATK4,0,0}, // S_SKULL_ATK3 + {SPR_SKUL,32771,4,{NULL},S_SKULL_ATK3,0,0}, // S_SKULL_ATK4 + {SPR_SKUL,32772,3,{NULL},S_SKULL_PAIN2,0,0}, // S_SKULL_PAIN + {SPR_SKUL,32772,3,{A_Pain},S_SKULL_RUN1,0,0}, // S_SKULL_PAIN2 + {SPR_SKUL,32773,6,{NULL},S_SKULL_DIE2,0,0}, // S_SKULL_DIE1 + {SPR_SKUL,32774,6,{A_Scream},S_SKULL_DIE3,0,0}, // S_SKULL_DIE2 + {SPR_SKUL,32775,6,{NULL},S_SKULL_DIE4,0,0}, // S_SKULL_DIE3 + {SPR_SKUL,32776,6,{A_Fall},S_SKULL_DIE5,0,0}, // S_SKULL_DIE4 + {SPR_SKUL,9,6,{NULL},S_SKULL_DIE6,0,0}, // S_SKULL_DIE5 + {SPR_SKUL,10,6,{NULL},S_NULL,0,0}, // S_SKULL_DIE6 + {SPR_SPID,0,10,{A_Look},S_SPID_STND2,0,0}, // S_SPID_STND + {SPR_SPID,1,10,{A_Look},S_SPID_STND,0,0}, // S_SPID_STND2 + {SPR_SPID,0,3,{A_Metal},S_SPID_RUN2,0,0}, // S_SPID_RUN1 + {SPR_SPID,0,3,{A_Chase},S_SPID_RUN3,0,0}, // S_SPID_RUN2 + {SPR_SPID,1,3,{A_Chase},S_SPID_RUN4,0,0}, // S_SPID_RUN3 + {SPR_SPID,1,3,{A_Chase},S_SPID_RUN5,0,0}, // S_SPID_RUN4 + {SPR_SPID,2,3,{A_Metal},S_SPID_RUN6,0,0}, // S_SPID_RUN5 + {SPR_SPID,2,3,{A_Chase},S_SPID_RUN7,0,0}, // S_SPID_RUN6 + {SPR_SPID,3,3,{A_Chase},S_SPID_RUN8,0,0}, // S_SPID_RUN7 + {SPR_SPID,3,3,{A_Chase},S_SPID_RUN9,0,0}, // S_SPID_RUN8 + {SPR_SPID,4,3,{A_Metal},S_SPID_RUN10,0,0}, // S_SPID_RUN9 + {SPR_SPID,4,3,{A_Chase},S_SPID_RUN11,0,0}, // S_SPID_RUN10 + {SPR_SPID,5,3,{A_Chase},S_SPID_RUN12,0,0}, // S_SPID_RUN11 + {SPR_SPID,5,3,{A_Chase},S_SPID_RUN1,0,0}, // S_SPID_RUN12 + {SPR_SPID,32768,20,{A_FaceTarget},S_SPID_ATK2,0,0}, // S_SPID_ATK1 + {SPR_SPID,32774,4,{A_SPosAttack},S_SPID_ATK3,0,0}, // S_SPID_ATK2 + {SPR_SPID,32775,4,{A_SPosAttack},S_SPID_ATK4,0,0}, // S_SPID_ATK3 + {SPR_SPID,32775,1,{A_SpidRefire},S_SPID_ATK2,0,0}, // S_SPID_ATK4 + {SPR_SPID,8,3,{NULL},S_SPID_PAIN2,0,0}, // S_SPID_PAIN + {SPR_SPID,8,3,{A_Pain},S_SPID_RUN1,0,0}, // S_SPID_PAIN2 + {SPR_SPID,9,20,{A_Scream},S_SPID_DIE2,0,0}, // S_SPID_DIE1 + {SPR_SPID,10,10,{A_Fall},S_SPID_DIE3,0,0}, // S_SPID_DIE2 + {SPR_SPID,11,10,{NULL},S_SPID_DIE4,0,0}, // S_SPID_DIE3 + {SPR_SPID,12,10,{NULL},S_SPID_DIE5,0,0}, // S_SPID_DIE4 + {SPR_SPID,13,10,{NULL},S_SPID_DIE6,0,0}, // S_SPID_DIE5 + {SPR_SPID,14,10,{NULL},S_SPID_DIE7,0,0}, // S_SPID_DIE6 + {SPR_SPID,15,10,{NULL},S_SPID_DIE8,0,0}, // S_SPID_DIE7 + {SPR_SPID,16,10,{NULL},S_SPID_DIE9,0,0}, // S_SPID_DIE8 + {SPR_SPID,17,10,{NULL},S_SPID_DIE10,0,0}, // S_SPID_DIE9 + {SPR_SPID,18,30,{NULL},S_SPID_DIE11,0,0}, // S_SPID_DIE10 + {SPR_SPID,18,-1,{A_BossDeath},S_NULL,0,0}, // S_SPID_DIE11 + {SPR_BSPI,0,10,{A_Look},S_BSPI_STND2,0,0}, // S_BSPI_STND + {SPR_BSPI,1,10,{A_Look},S_BSPI_STND,0,0}, // S_BSPI_STND2 + {SPR_BSPI,0,20,{NULL},S_BSPI_RUN1,0,0}, // S_BSPI_SIGHT + {SPR_BSPI,0,3,{A_BabyMetal},S_BSPI_RUN2,0,0}, // S_BSPI_RUN1 + {SPR_BSPI,0,3,{A_Chase},S_BSPI_RUN3,0,0}, // S_BSPI_RUN2 + {SPR_BSPI,1,3,{A_Chase},S_BSPI_RUN4,0,0}, // S_BSPI_RUN3 + {SPR_BSPI,1,3,{A_Chase},S_BSPI_RUN5,0,0}, // S_BSPI_RUN4 + {SPR_BSPI,2,3,{A_Chase},S_BSPI_RUN6,0,0}, // S_BSPI_RUN5 + {SPR_BSPI,2,3,{A_Chase},S_BSPI_RUN7,0,0}, // S_BSPI_RUN6 + {SPR_BSPI,3,3,{A_BabyMetal},S_BSPI_RUN8,0,0}, // S_BSPI_RUN7 + {SPR_BSPI,3,3,{A_Chase},S_BSPI_RUN9,0,0}, // S_BSPI_RUN8 + {SPR_BSPI,4,3,{A_Chase},S_BSPI_RUN10,0,0}, // S_BSPI_RUN9 + {SPR_BSPI,4,3,{A_Chase},S_BSPI_RUN11,0,0}, // S_BSPI_RUN10 + {SPR_BSPI,5,3,{A_Chase},S_BSPI_RUN12,0,0}, // S_BSPI_RUN11 + {SPR_BSPI,5,3,{A_Chase},S_BSPI_RUN1,0,0}, // S_BSPI_RUN12 + {SPR_BSPI,32768,20,{A_FaceTarget},S_BSPI_ATK2,0,0}, // S_BSPI_ATK1 + {SPR_BSPI,32774,4,{A_BspiAttack},S_BSPI_ATK3,0,0}, // S_BSPI_ATK2 + {SPR_BSPI,32775,4,{NULL},S_BSPI_ATK4,0,0}, // S_BSPI_ATK3 + {SPR_BSPI,32775,1,{A_SpidRefire},S_BSPI_ATK2,0,0}, // S_BSPI_ATK4 + {SPR_BSPI,8,3,{NULL},S_BSPI_PAIN2,0,0}, // S_BSPI_PAIN + {SPR_BSPI,8,3,{A_Pain},S_BSPI_RUN1,0,0}, // S_BSPI_PAIN2 + {SPR_BSPI,9,20,{A_Scream},S_BSPI_DIE2,0,0}, // S_BSPI_DIE1 + {SPR_BSPI,10,7,{A_Fall},S_BSPI_DIE3,0,0}, // S_BSPI_DIE2 + {SPR_BSPI,11,7,{NULL},S_BSPI_DIE4,0,0}, // S_BSPI_DIE3 + {SPR_BSPI,12,7,{NULL},S_BSPI_DIE5,0,0}, // S_BSPI_DIE4 + {SPR_BSPI,13,7,{NULL},S_BSPI_DIE6,0,0}, // S_BSPI_DIE5 + {SPR_BSPI,14,7,{NULL},S_BSPI_DIE7,0,0}, // S_BSPI_DIE6 + {SPR_BSPI,15,-1,{A_BossDeath},S_NULL,0,0}, // S_BSPI_DIE7 + {SPR_BSPI,15,5,{NULL},S_BSPI_RAISE2,0,0}, // S_BSPI_RAISE1 + {SPR_BSPI,14,5,{NULL},S_BSPI_RAISE3,0,0}, // S_BSPI_RAISE2 + {SPR_BSPI,13,5,{NULL},S_BSPI_RAISE4,0,0}, // S_BSPI_RAISE3 + {SPR_BSPI,12,5,{NULL},S_BSPI_RAISE5,0,0}, // S_BSPI_RAISE4 + {SPR_BSPI,11,5,{NULL},S_BSPI_RAISE6,0,0}, // S_BSPI_RAISE5 + {SPR_BSPI,10,5,{NULL},S_BSPI_RAISE7,0,0}, // S_BSPI_RAISE6 + {SPR_BSPI,9,5,{NULL},S_BSPI_RUN1,0,0}, // S_BSPI_RAISE7 + {SPR_APLS,32768,5,{NULL},S_ARACH_PLAZ2,0,0}, // S_ARACH_PLAZ + {SPR_APLS,32769,5,{NULL},S_ARACH_PLAZ,0,0}, // S_ARACH_PLAZ2 + {SPR_APBX,32768,5,{NULL},S_ARACH_PLEX2,0,0}, // S_ARACH_PLEX + {SPR_APBX,32769,5,{NULL},S_ARACH_PLEX3,0,0}, // S_ARACH_PLEX2 + {SPR_APBX,32770,5,{NULL},S_ARACH_PLEX4,0,0}, // S_ARACH_PLEX3 + {SPR_APBX,32771,5,{NULL},S_ARACH_PLEX5,0,0}, // S_ARACH_PLEX4 + {SPR_APBX,32772,5,{NULL},S_NULL,0,0}, // S_ARACH_PLEX5 + {SPR_CYBR,0,10,{A_Look},S_CYBER_STND2,0,0}, // S_CYBER_STND + {SPR_CYBR,1,10,{A_Look},S_CYBER_STND,0,0}, // S_CYBER_STND2 + {SPR_CYBR,0,3,{A_Hoof},S_CYBER_RUN2,0,0}, // S_CYBER_RUN1 + {SPR_CYBR,0,3,{A_Chase},S_CYBER_RUN3,0,0}, // S_CYBER_RUN2 + {SPR_CYBR,1,3,{A_Chase},S_CYBER_RUN4,0,0}, // S_CYBER_RUN3 + {SPR_CYBR,1,3,{A_Chase},S_CYBER_RUN5,0,0}, // S_CYBER_RUN4 + {SPR_CYBR,2,3,{A_Chase},S_CYBER_RUN6,0,0}, // S_CYBER_RUN5 + {SPR_CYBR,2,3,{A_Chase},S_CYBER_RUN7,0,0}, // S_CYBER_RUN6 + {SPR_CYBR,3,3,{A_Metal},S_CYBER_RUN8,0,0}, // S_CYBER_RUN7 + {SPR_CYBR,3,3,{A_Chase},S_CYBER_RUN1,0,0}, // S_CYBER_RUN8 + {SPR_CYBR,4,6,{A_FaceTarget},S_CYBER_ATK2,0,0}, // S_CYBER_ATK1 + {SPR_CYBR,5,12,{A_CyberAttack},S_CYBER_ATK3,0,0}, // S_CYBER_ATK2 + {SPR_CYBR,4,12,{A_FaceTarget},S_CYBER_ATK4,0,0}, // S_CYBER_ATK3 + {SPR_CYBR,5,12,{A_CyberAttack},S_CYBER_ATK5,0,0}, // S_CYBER_ATK4 + {SPR_CYBR,4,12,{A_FaceTarget},S_CYBER_ATK6,0,0}, // S_CYBER_ATK5 + {SPR_CYBR,5,12,{A_CyberAttack},S_CYBER_RUN1,0,0}, // S_CYBER_ATK6 + {SPR_CYBR,6,10,{A_Pain},S_CYBER_RUN1,0,0}, // S_CYBER_PAIN + {SPR_CYBR,7,10,{NULL},S_CYBER_DIE2,0,0}, // S_CYBER_DIE1 + {SPR_CYBR,8,10,{A_Scream},S_CYBER_DIE3,0,0}, // S_CYBER_DIE2 + {SPR_CYBR,9,10,{NULL},S_CYBER_DIE4,0,0}, // S_CYBER_DIE3 + {SPR_CYBR,10,10,{NULL},S_CYBER_DIE5,0,0}, // S_CYBER_DIE4 + {SPR_CYBR,11,10,{NULL},S_CYBER_DIE6,0,0}, // S_CYBER_DIE5 + {SPR_CYBR,12,10,{A_Fall},S_CYBER_DIE7,0,0}, // S_CYBER_DIE6 + {SPR_CYBR,13,10,{NULL},S_CYBER_DIE8,0,0}, // S_CYBER_DIE7 + {SPR_CYBR,14,10,{NULL},S_CYBER_DIE9,0,0}, // S_CYBER_DIE8 + {SPR_CYBR,15,30,{NULL},S_CYBER_DIE10,0,0}, // S_CYBER_DIE9 + {SPR_CYBR,15,-1,{A_BossDeath},S_NULL,0,0}, // S_CYBER_DIE10 + {SPR_PAIN,0,10,{A_Look},S_PAIN_STND,0,0}, // S_PAIN_STND + {SPR_PAIN,0,3,{A_Chase},S_PAIN_RUN2,0,0}, // S_PAIN_RUN1 + {SPR_PAIN,0,3,{A_Chase},S_PAIN_RUN3,0,0}, // S_PAIN_RUN2 + {SPR_PAIN,1,3,{A_Chase},S_PAIN_RUN4,0,0}, // S_PAIN_RUN3 + {SPR_PAIN,1,3,{A_Chase},S_PAIN_RUN5,0,0}, // S_PAIN_RUN4 + {SPR_PAIN,2,3,{A_Chase},S_PAIN_RUN6,0,0}, // S_PAIN_RUN5 + {SPR_PAIN,2,3,{A_Chase},S_PAIN_RUN1,0,0}, // S_PAIN_RUN6 + {SPR_PAIN,3,5,{A_FaceTarget},S_PAIN_ATK2,0,0}, // S_PAIN_ATK1 + {SPR_PAIN,4,5,{A_FaceTarget},S_PAIN_ATK3,0,0}, // S_PAIN_ATK2 + {SPR_PAIN,32773,5,{A_FaceTarget},S_PAIN_ATK4,0,0}, // S_PAIN_ATK3 + {SPR_PAIN,32773,0,{A_PainAttack},S_PAIN_RUN1,0,0}, // S_PAIN_ATK4 + {SPR_PAIN,6,6,{NULL},S_PAIN_PAIN2,0,0}, // S_PAIN_PAIN + {SPR_PAIN,6,6,{A_Pain},S_PAIN_RUN1,0,0}, // S_PAIN_PAIN2 + {SPR_PAIN,32775,8,{NULL},S_PAIN_DIE2,0,0}, // S_PAIN_DIE1 + {SPR_PAIN,32776,8,{A_Scream},S_PAIN_DIE3,0,0}, // S_PAIN_DIE2 + {SPR_PAIN,32777,8,{NULL},S_PAIN_DIE4,0,0}, // S_PAIN_DIE3 + {SPR_PAIN,32778,8,{NULL},S_PAIN_DIE5,0,0}, // S_PAIN_DIE4 + {SPR_PAIN,32779,8,{A_PainDie},S_PAIN_DIE6,0,0}, // S_PAIN_DIE5 + {SPR_PAIN,32780,8,{NULL},S_NULL,0,0}, // S_PAIN_DIE6 + {SPR_PAIN,12,8,{NULL},S_PAIN_RAISE2,0,0}, // S_PAIN_RAISE1 + {SPR_PAIN,11,8,{NULL},S_PAIN_RAISE3,0,0}, // S_PAIN_RAISE2 + {SPR_PAIN,10,8,{NULL},S_PAIN_RAISE4,0,0}, // S_PAIN_RAISE3 + {SPR_PAIN,9,8,{NULL},S_PAIN_RAISE5,0,0}, // S_PAIN_RAISE4 + {SPR_PAIN,8,8,{NULL},S_PAIN_RAISE6,0,0}, // S_PAIN_RAISE5 + {SPR_PAIN,7,8,{NULL},S_PAIN_RUN1,0,0}, // S_PAIN_RAISE6 + {SPR_SSWV,0,10,{A_Look},S_SSWV_STND2,0,0}, // S_SSWV_STND + {SPR_SSWV,1,10,{A_Look},S_SSWV_STND,0,0}, // S_SSWV_STND2 + {SPR_SSWV,0,3,{A_Chase},S_SSWV_RUN2,0,0}, // S_SSWV_RUN1 + {SPR_SSWV,0,3,{A_Chase},S_SSWV_RUN3,0,0}, // S_SSWV_RUN2 + {SPR_SSWV,1,3,{A_Chase},S_SSWV_RUN4,0,0}, // S_SSWV_RUN3 + {SPR_SSWV,1,3,{A_Chase},S_SSWV_RUN5,0,0}, // S_SSWV_RUN4 + {SPR_SSWV,2,3,{A_Chase},S_SSWV_RUN6,0,0}, // S_SSWV_RUN5 + {SPR_SSWV,2,3,{A_Chase},S_SSWV_RUN7,0,0}, // S_SSWV_RUN6 + {SPR_SSWV,3,3,{A_Chase},S_SSWV_RUN8,0,0}, // S_SSWV_RUN7 + {SPR_SSWV,3,3,{A_Chase},S_SSWV_RUN1,0,0}, // S_SSWV_RUN8 + {SPR_SSWV,4,10,{A_FaceTarget},S_SSWV_ATK2,0,0}, // S_SSWV_ATK1 + {SPR_SSWV,5,10,{A_FaceTarget},S_SSWV_ATK3,0,0}, // S_SSWV_ATK2 + {SPR_SSWV,32774,4,{A_CPosAttack},S_SSWV_ATK4,0,0}, // S_SSWV_ATK3 + {SPR_SSWV,5,6,{A_FaceTarget},S_SSWV_ATK5,0,0}, // S_SSWV_ATK4 + {SPR_SSWV,32774,4,{A_CPosAttack},S_SSWV_ATK6,0,0}, // S_SSWV_ATK5 + {SPR_SSWV,5,1,{A_CPosRefire},S_SSWV_ATK2,0,0}, // S_SSWV_ATK6 + {SPR_SSWV,7,3,{NULL},S_SSWV_PAIN2,0,0}, // S_SSWV_PAIN + {SPR_SSWV,7,3,{A_Pain},S_SSWV_RUN1,0,0}, // S_SSWV_PAIN2 + {SPR_SSWV,8,5,{NULL},S_SSWV_DIE2,0,0}, // S_SSWV_DIE1 + {SPR_SSWV,9,5,{A_Scream},S_SSWV_DIE3,0,0}, // S_SSWV_DIE2 + {SPR_SSWV,10,5,{A_Fall},S_SSWV_DIE4,0,0}, // S_SSWV_DIE3 + {SPR_SSWV,11,5,{NULL},S_SSWV_DIE5,0,0}, // S_SSWV_DIE4 + {SPR_SSWV,12,-1,{NULL},S_NULL,0,0}, // S_SSWV_DIE5 + {SPR_SSWV,13,5,{NULL},S_SSWV_XDIE2,0,0}, // S_SSWV_XDIE1 + {SPR_SSWV,14,5,{A_XScream},S_SSWV_XDIE3,0,0}, // S_SSWV_XDIE2 + {SPR_SSWV,15,5,{A_Fall},S_SSWV_XDIE4,0,0}, // S_SSWV_XDIE3 + {SPR_SSWV,16,5,{NULL},S_SSWV_XDIE5,0,0}, // S_SSWV_XDIE4 + {SPR_SSWV,17,5,{NULL},S_SSWV_XDIE6,0,0}, // S_SSWV_XDIE5 + {SPR_SSWV,18,5,{NULL},S_SSWV_XDIE7,0,0}, // S_SSWV_XDIE6 + {SPR_SSWV,19,5,{NULL},S_SSWV_XDIE8,0,0}, // S_SSWV_XDIE7 + {SPR_SSWV,20,5,{NULL},S_SSWV_XDIE9,0,0}, // S_SSWV_XDIE8 + {SPR_SSWV,21,-1,{NULL},S_NULL,0,0}, // S_SSWV_XDIE9 + {SPR_SSWV,12,5,{NULL},S_SSWV_RAISE2,0,0}, // S_SSWV_RAISE1 + {SPR_SSWV,11,5,{NULL},S_SSWV_RAISE3,0,0}, // S_SSWV_RAISE2 + {SPR_SSWV,10,5,{NULL},S_SSWV_RAISE4,0,0}, // S_SSWV_RAISE3 + {SPR_SSWV,9,5,{NULL},S_SSWV_RAISE5,0,0}, // S_SSWV_RAISE4 + {SPR_SSWV,8,5,{NULL},S_SSWV_RUN1,0,0}, // S_SSWV_RAISE5 + {SPR_KEEN,0,-1,{NULL},S_KEENSTND,0,0}, // S_KEENSTND + {SPR_KEEN,0,6,{NULL},S_COMMKEEN2,0,0}, // S_COMMKEEN + {SPR_KEEN,1,6,{NULL},S_COMMKEEN3,0,0}, // S_COMMKEEN2 + {SPR_KEEN,2,6,{A_Scream},S_COMMKEEN4,0,0}, // S_COMMKEEN3 + {SPR_KEEN,3,6,{NULL},S_COMMKEEN5,0,0}, // S_COMMKEEN4 + {SPR_KEEN,4,6,{NULL},S_COMMKEEN6,0,0}, // S_COMMKEEN5 + {SPR_KEEN,5,6,{NULL},S_COMMKEEN7,0,0}, // S_COMMKEEN6 + {SPR_KEEN,6,6,{NULL},S_COMMKEEN8,0,0}, // S_COMMKEEN7 + {SPR_KEEN,7,6,{NULL},S_COMMKEEN9,0,0}, // S_COMMKEEN8 + {SPR_KEEN,8,6,{NULL},S_COMMKEEN10,0,0}, // S_COMMKEEN9 + {SPR_KEEN,9,6,{NULL},S_COMMKEEN11,0,0}, // S_COMMKEEN10 + {SPR_KEEN,10,6,{A_KeenDie},S_COMMKEEN12,0,0},// S_COMMKEEN11 + {SPR_KEEN,11,-1,{NULL},S_NULL,0,0}, // S_COMMKEEN12 + {SPR_KEEN,12,4,{NULL},S_KEENPAIN2,0,0}, // S_KEENPAIN + {SPR_KEEN,12,8,{A_Pain},S_KEENSTND,0,0}, // S_KEENPAIN2 + {SPR_BBRN,0,-1,{NULL},S_NULL,0,0}, // S_BRAIN + {SPR_BBRN,1,36,{A_BrainPain},S_BRAIN,0,0}, // S_BRAIN_PAIN + {SPR_BBRN,0,100,{A_BrainScream},S_BRAIN_DIE2,0,0}, // S_BRAIN_DIE1 + {SPR_BBRN,0,10,{NULL},S_BRAIN_DIE3,0,0}, // S_BRAIN_DIE2 + {SPR_BBRN,0,10,{NULL},S_BRAIN_DIE4,0,0}, // S_BRAIN_DIE3 + {SPR_BBRN,0,-1,{A_BrainDie},S_NULL,0,0}, // S_BRAIN_DIE4 + {SPR_SSWV,0,10,{A_Look},S_BRAINEYE,0,0}, // S_BRAINEYE + {SPR_SSWV,0,181,{A_BrainAwake},S_BRAINEYE1,0,0}, // S_BRAINEYESEE + {SPR_SSWV,0,150,{A_BrainSpit},S_BRAINEYE1,0,0}, // S_BRAINEYE1 + {SPR_BOSF,32768,3,{A_SpawnSound},S_SPAWN2,0,0}, // S_SPAWN1 + {SPR_BOSF,32769,3,{A_SpawnFly},S_SPAWN3,0,0}, // S_SPAWN2 + {SPR_BOSF,32770,3,{A_SpawnFly},S_SPAWN4,0,0}, // S_SPAWN3 + {SPR_BOSF,32771,3,{A_SpawnFly},S_SPAWN1,0,0}, // S_SPAWN4 + {SPR_FIRE,32768,4,{A_Fire},S_SPAWNFIRE2,0,0}, // S_SPAWNFIRE1 + {SPR_FIRE,32769,4,{A_Fire},S_SPAWNFIRE3,0,0}, // S_SPAWNFIRE2 + {SPR_FIRE,32770,4,{A_Fire},S_SPAWNFIRE4,0,0}, // S_SPAWNFIRE3 + {SPR_FIRE,32771,4,{A_Fire},S_SPAWNFIRE5,0,0}, // S_SPAWNFIRE4 + {SPR_FIRE,32772,4,{A_Fire},S_SPAWNFIRE6,0,0}, // S_SPAWNFIRE5 + {SPR_FIRE,32773,4,{A_Fire},S_SPAWNFIRE7,0,0}, // S_SPAWNFIRE6 + {SPR_FIRE,32774,4,{A_Fire},S_SPAWNFIRE8,0,0}, // S_SPAWNFIRE7 + {SPR_FIRE,32775,4,{A_Fire},S_NULL,0,0}, // S_SPAWNFIRE8 + {SPR_MISL,32769,10,{NULL},S_BRAINEXPLODE2,0,0}, // S_BRAINEXPLODE1 + {SPR_MISL,32770,10,{NULL},S_BRAINEXPLODE3,0,0}, // S_BRAINEXPLODE2 + {SPR_MISL,32771,10,{A_BrainExplode},S_NULL,0,0}, // S_BRAINEXPLODE3 + {SPR_ARM1,0,6,{NULL},S_ARM1A,0,0}, // S_ARM1 + {SPR_ARM1,32769,7,{NULL},S_ARM1,0,0}, // S_ARM1A + {SPR_ARM2,0,6,{NULL},S_ARM2A,0,0}, // S_ARM2 + {SPR_ARM2,32769,6,{NULL},S_ARM2,0,0}, // S_ARM2A + {SPR_BAR1,0,6,{NULL},S_BAR2,0,0}, // S_BAR1 + {SPR_BAR1,1,6,{NULL},S_BAR1,0,0}, // S_BAR2 + {SPR_BEXP,32768,5,{NULL},S_BEXP2,0,0}, // S_BEXP + {SPR_BEXP,32769,5,{A_Scream},S_BEXP3,0,0}, // S_BEXP2 + {SPR_BEXP,32770,5,{NULL},S_BEXP4,0,0}, // S_BEXP3 + {SPR_BEXP,32771,10,{A_Explode},S_BEXP5,0,0}, // S_BEXP4 + {SPR_BEXP,32772,10,{NULL},S_NULL,0,0}, // S_BEXP5 + {SPR_FCAN,32768,4,{NULL},S_BBAR2,0,0}, // S_BBAR1 + {SPR_FCAN,32769,4,{NULL},S_BBAR3,0,0}, // S_BBAR2 + {SPR_FCAN,32770,4,{NULL},S_BBAR1,0,0}, // S_BBAR3 + {SPR_BON1,0,6,{NULL},S_BON1A,0,0}, // S_BON1 + {SPR_BON1,1,6,{NULL},S_BON1B,0,0}, // S_BON1A + {SPR_BON1,2,6,{NULL},S_BON1C,0,0}, // S_BON1B + {SPR_BON1,3,6,{NULL},S_BON1D,0,0}, // S_BON1C + {SPR_BON1,2,6,{NULL},S_BON1E,0,0}, // S_BON1D + {SPR_BON1,1,6,{NULL},S_BON1,0,0}, // S_BON1E + {SPR_BON2,0,6,{NULL},S_BON2A,0,0}, // S_BON2 + {SPR_BON2,1,6,{NULL},S_BON2B,0,0}, // S_BON2A + {SPR_BON2,2,6,{NULL},S_BON2C,0,0}, // S_BON2B + {SPR_BON2,3,6,{NULL},S_BON2D,0,0}, // S_BON2C + {SPR_BON2,2,6,{NULL},S_BON2E,0,0}, // S_BON2D + {SPR_BON2,1,6,{NULL},S_BON2,0,0}, // S_BON2E + {SPR_BKEY,0,10,{NULL},S_BKEY2,0,0}, // S_BKEY + {SPR_BKEY,32769,10,{NULL},S_BKEY,0,0}, // S_BKEY2 + {SPR_RKEY,0,10,{NULL},S_RKEY2,0,0}, // S_RKEY + {SPR_RKEY,32769,10,{NULL},S_RKEY,0,0}, // S_RKEY2 + {SPR_YKEY,0,10,{NULL},S_YKEY2,0,0}, // S_YKEY + {SPR_YKEY,32769,10,{NULL},S_YKEY,0,0}, // S_YKEY2 + {SPR_BSKU,0,10,{NULL},S_BSKULL2,0,0}, // S_BSKULL + {SPR_BSKU,32769,10,{NULL},S_BSKULL,0,0}, // S_BSKULL2 + {SPR_RSKU,0,10,{NULL},S_RSKULL2,0,0}, // S_RSKULL + {SPR_RSKU,32769,10,{NULL},S_RSKULL,0,0}, // S_RSKULL2 + {SPR_YSKU,0,10,{NULL},S_YSKULL2,0,0}, // S_YSKULL + {SPR_YSKU,32769,10,{NULL},S_YSKULL,0,0}, // S_YSKULL2 + {SPR_STIM,0,-1,{NULL},S_NULL,0,0}, // S_STIM + {SPR_MEDI,0,-1,{NULL},S_NULL,0,0}, // S_MEDI + {SPR_SOUL,32768,6,{NULL},S_SOUL2,0,0}, // S_SOUL + {SPR_SOUL,32769,6,{NULL},S_SOUL3,0,0}, // S_SOUL2 + {SPR_SOUL,32770,6,{NULL},S_SOUL4,0,0}, // S_SOUL3 + {SPR_SOUL,32771,6,{NULL},S_SOUL5,0,0}, // S_SOUL4 + {SPR_SOUL,32770,6,{NULL},S_SOUL6,0,0}, // S_SOUL5 + {SPR_SOUL,32769,6,{NULL},S_SOUL,0,0}, // S_SOUL6 + {SPR_PINV,32768,6,{NULL},S_PINV2,0,0}, // S_PINV + {SPR_PINV,32769,6,{NULL},S_PINV3,0,0}, // S_PINV2 + {SPR_PINV,32770,6,{NULL},S_PINV4,0,0}, // S_PINV3 + {SPR_PINV,32771,6,{NULL},S_PINV,0,0}, // S_PINV4 + {SPR_PSTR,32768,-1,{NULL},S_NULL,0,0}, // S_PSTR + {SPR_PINS,32768,6,{NULL},S_PINS2,0,0}, // S_PINS + {SPR_PINS,32769,6,{NULL},S_PINS3,0,0}, // S_PINS2 + {SPR_PINS,32770,6,{NULL},S_PINS4,0,0}, // S_PINS3 + {SPR_PINS,32771,6,{NULL},S_PINS,0,0}, // S_PINS4 + {SPR_MEGA,32768,6,{NULL},S_MEGA2,0,0}, // S_MEGA + {SPR_MEGA,32769,6,{NULL},S_MEGA3,0,0}, // S_MEGA2 + {SPR_MEGA,32770,6,{NULL},S_MEGA4,0,0}, // S_MEGA3 + {SPR_MEGA,32771,6,{NULL},S_MEGA,0,0}, // S_MEGA4 + {SPR_SUIT,32768,-1,{NULL},S_NULL,0,0}, // S_SUIT + {SPR_PMAP,32768,6,{NULL},S_PMAP2,0,0}, // S_PMAP + {SPR_PMAP,32769,6,{NULL},S_PMAP3,0,0}, // S_PMAP2 + {SPR_PMAP,32770,6,{NULL},S_PMAP4,0,0}, // S_PMAP3 + {SPR_PMAP,32771,6,{NULL},S_PMAP5,0,0}, // S_PMAP4 + {SPR_PMAP,32770,6,{NULL},S_PMAP6,0,0}, // S_PMAP5 + {SPR_PMAP,32769,6,{NULL},S_PMAP,0,0}, // S_PMAP6 + {SPR_PVIS,32768,6,{NULL},S_PVIS2,0,0}, // S_PVIS + {SPR_PVIS,1,6,{NULL},S_PVIS,0,0}, // S_PVIS2 + {SPR_CLIP,0,-1,{NULL},S_NULL,0,0}, // S_CLIP + {SPR_AMMO,0,-1,{NULL},S_NULL,0,0}, // S_AMMO + {SPR_ROCK,0,-1,{NULL},S_NULL,0,0}, // S_ROCK + {SPR_BROK,0,-1,{NULL},S_NULL,0,0}, // S_BROK + {SPR_CELL,0,-1,{NULL},S_NULL,0,0}, // S_CELL + {SPR_CELP,0,-1,{NULL},S_NULL,0,0}, // S_CELP + {SPR_SHEL,0,-1,{NULL},S_NULL,0,0}, // S_SHEL + {SPR_SBOX,0,-1,{NULL},S_NULL,0,0}, // S_SBOX + {SPR_BPAK,0,-1,{NULL},S_NULL,0,0}, // S_BPAK + {SPR_BFUG,0,-1,{NULL},S_NULL,0,0}, // S_BFUG + {SPR_MGUN,0,-1,{NULL},S_NULL,0,0}, // S_MGUN + {SPR_CSAW,0,-1,{NULL},S_NULL,0,0}, // S_CSAW + {SPR_LAUN,0,-1,{NULL},S_NULL,0,0}, // S_LAUN + {SPR_PLAS,0,-1,{NULL},S_NULL,0,0}, // S_PLAS + {SPR_SHOT,0,-1,{NULL},S_NULL,0,0}, // S_SHOT + {SPR_SGN2,0,-1,{NULL},S_NULL,0,0}, // S_SHOT2 + {SPR_COLU,32768,-1,{NULL},S_NULL,0,0}, // S_COLU + {SPR_SMT2,0,-1,{NULL},S_NULL,0,0}, // S_STALAG + {SPR_GOR1,0,10,{NULL},S_BLOODYTWITCH2,0,0}, // S_BLOODYTWITCH + {SPR_GOR1,1,15,{NULL},S_BLOODYTWITCH3,0,0}, // S_BLOODYTWITCH2 + {SPR_GOR1,2,8,{NULL},S_BLOODYTWITCH4,0,0}, // S_BLOODYTWITCH3 + {SPR_GOR1,1,6,{NULL},S_BLOODYTWITCH,0,0}, // S_BLOODYTWITCH4 + {SPR_PLAY,13,-1,{NULL},S_NULL,0,0}, // S_DEADTORSO + {SPR_PLAY,18,-1,{NULL},S_NULL,0,0}, // S_DEADBOTTOM + {SPR_POL2,0,-1,{NULL},S_NULL,0,0}, // S_HEADSONSTICK + {SPR_POL5,0,-1,{NULL},S_NULL,0,0}, // S_GIBS + {SPR_POL4,0,-1,{NULL},S_NULL,0,0}, // S_HEADONASTICK + {SPR_POL3,32768,6,{NULL},S_HEADCANDLES2,0,0}, // S_HEADCANDLES + {SPR_POL3,32769,6,{NULL},S_HEADCANDLES,0,0}, // S_HEADCANDLES2 + {SPR_POL1,0,-1,{NULL},S_NULL,0,0}, // S_DEADSTICK + {SPR_POL6,0,6,{NULL},S_LIVESTICK2,0,0}, // S_LIVESTICK + {SPR_POL6,1,8,{NULL},S_LIVESTICK,0,0}, // S_LIVESTICK2 + {SPR_GOR2,0,-1,{NULL},S_NULL,0,0}, // S_MEAT2 + {SPR_GOR3,0,-1,{NULL},S_NULL,0,0}, // S_MEAT3 + {SPR_GOR4,0,-1,{NULL},S_NULL,0,0}, // S_MEAT4 + {SPR_GOR5,0,-1,{NULL},S_NULL,0,0}, // S_MEAT5 + {SPR_SMIT,0,-1,{NULL},S_NULL,0,0}, // S_STALAGTITE + {SPR_COL1,0,-1,{NULL},S_NULL,0,0}, // S_TALLGRNCOL + {SPR_COL2,0,-1,{NULL},S_NULL,0,0}, // S_SHRTGRNCOL + {SPR_COL3,0,-1,{NULL},S_NULL,0,0}, // S_TALLREDCOL + {SPR_COL4,0,-1,{NULL},S_NULL,0,0}, // S_SHRTREDCOL + {SPR_CAND,32768,-1,{NULL},S_NULL,0,0}, // S_CANDLESTIK + {SPR_CBRA,32768,-1,{NULL},S_NULL,0,0}, // S_CANDELABRA + {SPR_COL6,0,-1,{NULL},S_NULL,0,0}, // S_SKULLCOL + {SPR_TRE1,0,-1,{NULL},S_NULL,0,0}, // S_TORCHTREE + {SPR_TRE2,0,-1,{NULL},S_NULL,0,0}, // S_BIGTREE + {SPR_ELEC,0,-1,{NULL},S_NULL,0,0}, // S_TECHPILLAR + {SPR_CEYE,32768,6,{NULL},S_EVILEYE2,0,0}, // S_EVILEYE + {SPR_CEYE,32769,6,{NULL},S_EVILEYE3,0,0}, // S_EVILEYE2 + {SPR_CEYE,32770,6,{NULL},S_EVILEYE4,0,0}, // S_EVILEYE3 + {SPR_CEYE,32769,6,{NULL},S_EVILEYE,0,0}, // S_EVILEYE4 + {SPR_FSKU,32768,6,{NULL},S_FLOATSKULL2,0,0}, // S_FLOATSKULL + {SPR_FSKU,32769,6,{NULL},S_FLOATSKULL3,0,0}, // S_FLOATSKULL2 + {SPR_FSKU,32770,6,{NULL},S_FLOATSKULL,0,0}, // S_FLOATSKULL3 + {SPR_COL5,0,14,{NULL},S_HEARTCOL2,0,0}, // S_HEARTCOL + {SPR_COL5,1,14,{NULL},S_HEARTCOL,0,0}, // S_HEARTCOL2 + {SPR_TBLU,32768,4,{NULL},S_BLUETORCH2,0,0}, // S_BLUETORCH + {SPR_TBLU,32769,4,{NULL},S_BLUETORCH3,0,0}, // S_BLUETORCH2 + {SPR_TBLU,32770,4,{NULL},S_BLUETORCH4,0,0}, // S_BLUETORCH3 + {SPR_TBLU,32771,4,{NULL},S_BLUETORCH,0,0}, // S_BLUETORCH4 + {SPR_TGRN,32768,4,{NULL},S_GREENTORCH2,0,0}, // S_GREENTORCH + {SPR_TGRN,32769,4,{NULL},S_GREENTORCH3,0,0}, // S_GREENTORCH2 + {SPR_TGRN,32770,4,{NULL},S_GREENTORCH4,0,0}, // S_GREENTORCH3 + {SPR_TGRN,32771,4,{NULL},S_GREENTORCH,0,0}, // S_GREENTORCH4 + {SPR_TRED,32768,4,{NULL},S_REDTORCH2,0,0}, // S_REDTORCH + {SPR_TRED,32769,4,{NULL},S_REDTORCH3,0,0}, // S_REDTORCH2 + {SPR_TRED,32770,4,{NULL},S_REDTORCH4,0,0}, // S_REDTORCH3 + {SPR_TRED,32771,4,{NULL},S_REDTORCH,0,0}, // S_REDTORCH4 + {SPR_SMBT,32768,4,{NULL},S_BTORCHSHRT2,0,0}, // S_BTORCHSHRT + {SPR_SMBT,32769,4,{NULL},S_BTORCHSHRT3,0,0}, // S_BTORCHSHRT2 + {SPR_SMBT,32770,4,{NULL},S_BTORCHSHRT4,0,0}, // S_BTORCHSHRT3 + {SPR_SMBT,32771,4,{NULL},S_BTORCHSHRT,0,0}, // S_BTORCHSHRT4 + {SPR_SMGT,32768,4,{NULL},S_GTORCHSHRT2,0,0}, // S_GTORCHSHRT + {SPR_SMGT,32769,4,{NULL},S_GTORCHSHRT3,0,0}, // S_GTORCHSHRT2 + {SPR_SMGT,32770,4,{NULL},S_GTORCHSHRT4,0,0}, // S_GTORCHSHRT3 + {SPR_SMGT,32771,4,{NULL},S_GTORCHSHRT,0,0}, // S_GTORCHSHRT4 + {SPR_SMRT,32768,4,{NULL},S_RTORCHSHRT2,0,0}, // S_RTORCHSHRT + {SPR_SMRT,32769,4,{NULL},S_RTORCHSHRT3,0,0}, // S_RTORCHSHRT2 + {SPR_SMRT,32770,4,{NULL},S_RTORCHSHRT4,0,0}, // S_RTORCHSHRT3 + {SPR_SMRT,32771,4,{NULL},S_RTORCHSHRT,0,0}, // S_RTORCHSHRT4 + {SPR_HDB1,0,-1,{NULL},S_NULL,0,0}, // S_HANGNOGUTS + {SPR_HDB2,0,-1,{NULL},S_NULL,0,0}, // S_HANGBNOBRAIN + {SPR_HDB3,0,-1,{NULL},S_NULL,0,0}, // S_HANGTLOOKDN + {SPR_HDB4,0,-1,{NULL},S_NULL,0,0}, // S_HANGTSKULL + {SPR_HDB5,0,-1,{NULL},S_NULL,0,0}, // S_HANGTLOOKUP + {SPR_HDB6,0,-1,{NULL},S_NULL,0,0}, // S_HANGTNOBRAIN + {SPR_POB1,0,-1,{NULL},S_NULL,0,0}, // S_COLONGIBS + {SPR_POB2,0,-1,{NULL},S_NULL,0,0}, // S_SMALLPOOL + {SPR_BRS1,0,-1,{NULL},S_NULL,0,0}, // S_BRAINSTEM + {SPR_TLMP,32768,4,{NULL},S_TECHLAMP2,0,0}, // S_TECHLAMP + {SPR_TLMP,32769,4,{NULL},S_TECHLAMP3,0,0}, // S_TECHLAMP2 + {SPR_TLMP,32770,4,{NULL},S_TECHLAMP4,0,0}, // S_TECHLAMP3 + {SPR_TLMP,32771,4,{NULL},S_TECHLAMP,0,0}, // S_TECHLAMP4 + {SPR_TLP2,32768,4,{NULL},S_TECH2LAMP2,0,0}, // S_TECH2LAMP + {SPR_TLP2,32769,4,{NULL},S_TECH2LAMP3,0,0}, // S_TECH2LAMP2 + {SPR_TLP2,32770,4,{NULL},S_TECH2LAMP4,0,0}, // S_TECH2LAMP3 + {SPR_TLP2,32771,4,{NULL},S_TECH2LAMP,0,0} // S_TECH2LAMP4 +}; + + +mobjinfo_t mobjinfo[NUMMOBJTYPES] = { + + { // MT_PLAYER + -1, // doomednum + S_PLAY, // spawnstate + 100, // spawnhealth + S_PLAY_RUN1, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_PLAY_PAIN, // painstate + 255, // painchance + sfx_plpain, // painsound + S_NULL, // meleestate + S_PLAY_ATK1, // missilestate + S_PLAY_DIE1, // deathstate + S_PLAY_XDIE1, // xdeathstate + sfx_pldeth, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 56*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SHOOTABLE|MF_DROPOFF|MF_PICKUP|MF_NOTDMATCH, // flags + S_NULL // raisestate + }, + + { // MT_POSSESSED + 3004, // doomednum + S_POSS_STND, // spawnstate + 20, // spawnhealth + S_POSS_RUN1, // seestate + sfx_posit1, // seesound + 8, // reactiontime + sfx_pistol, // attacksound + S_POSS_PAIN, // painstate + 200, // painchance + sfx_popain, // painsound + 0, // meleestate + S_POSS_ATK1, // missilestate + S_POSS_DIE1, // deathstate + S_POSS_XDIE1, // xdeathstate + sfx_podth1, // deathsound + 8, // speed + 20*FRACUNIT, // radius + 56*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_posact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_POSS_RAISE1 // raisestate + }, + + { // MT_SHOTGUY + 9, // doomednum + S_SPOS_STND, // spawnstate + 30, // spawnhealth + S_SPOS_RUN1, // seestate + sfx_posit2, // seesound + 8, // reactiontime + 0, // attacksound + S_SPOS_PAIN, // painstate + 170, // painchance + sfx_popain, // painsound + 0, // meleestate + S_SPOS_ATK1, // missilestate + S_SPOS_DIE1, // deathstate + S_SPOS_XDIE1, // xdeathstate + sfx_podth2, // deathsound + 8, // speed + 20*FRACUNIT, // radius + 56*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_posact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_SPOS_RAISE1 // raisestate + }, + + { // MT_VILE + 64, // doomednum + S_VILE_STND, // spawnstate + 700, // spawnhealth + S_VILE_RUN1, // seestate + sfx_vilsit, // seesound + 8, // reactiontime + 0, // attacksound + S_VILE_PAIN, // painstate + 10, // painchance + sfx_vipain, // painsound + 0, // meleestate + S_VILE_ATK1, // missilestate + S_VILE_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_vildth, // deathsound + 15, // speed + 20*FRACUNIT, // radius + 56*FRACUNIT, // height + 500, // mass + 0, // damage + sfx_vilact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_NULL // raisestate + }, + + { // MT_FIRE + -1, // doomednum + S_FIRE1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_UNDEAD + 66, // doomednum + S_SKEL_STND, // spawnstate + 300, // spawnhealth + S_SKEL_RUN1, // seestate + sfx_skesit, // seesound + 8, // reactiontime + 0, // attacksound + S_SKEL_PAIN, // painstate + 100, // painchance + sfx_popain, // painsound + S_SKEL_FIST1, // meleestate + S_SKEL_MISS1, // missilestate + S_SKEL_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_skedth, // deathsound + 10, // speed + 20*FRACUNIT, // radius + 56*FRACUNIT, // height + 500, // mass + 0, // damage + sfx_skeact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_SKEL_RAISE1 // raisestate + }, + + { // MT_TRACER + -1, // doomednum + S_TRACER, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_skeatk, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_TRACEEXP1, // deathstate + S_NULL, // xdeathstate + sfx_barexp, // deathsound + 10*FRACUNIT, // speed + 11*FRACUNIT, // radius + 8*FRACUNIT, // height + 100, // mass + 10, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_SMOKE + -1, // doomednum + S_SMOKE1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_FATSO + 67, // doomednum + S_FATT_STND, // spawnstate + 600, // spawnhealth + S_FATT_RUN1, // seestate + sfx_mansit, // seesound + 8, // reactiontime + 0, // attacksound + S_FATT_PAIN, // painstate + 80, // painchance + sfx_mnpain, // painsound + 0, // meleestate + S_FATT_ATK1, // missilestate + S_FATT_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_mandth, // deathsound + 8, // speed + 48*FRACUNIT, // radius + 64*FRACUNIT, // height + 1000, // mass + 0, // damage + sfx_posact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_FATT_RAISE1 // raisestate + }, + + { // MT_FATSHOT + -1, // doomednum + S_FATSHOT1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_firsht, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_FATSHOTX1, // deathstate + S_NULL, // xdeathstate + sfx_firxpl, // deathsound + 20*FRACUNIT, // speed + 6*FRACUNIT, // radius + 8*FRACUNIT, // height + 100, // mass + 8, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_CHAINGUY + 65, // doomednum + S_CPOS_STND, // spawnstate + 70, // spawnhealth + S_CPOS_RUN1, // seestate + sfx_posit2, // seesound + 8, // reactiontime + 0, // attacksound + S_CPOS_PAIN, // painstate + 170, // painchance + sfx_popain, // painsound + 0, // meleestate + S_CPOS_ATK1, // missilestate + S_CPOS_DIE1, // deathstate + S_CPOS_XDIE1, // xdeathstate + sfx_podth2, // deathsound + 8, // speed + 20*FRACUNIT, // radius + 56*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_posact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_CPOS_RAISE1 // raisestate + }, + + { // MT_TROOP + 3001, // doomednum + S_TROO_STND, // spawnstate + 60, // spawnhealth + S_TROO_RUN1, // seestate + sfx_bgsit1, // seesound + 8, // reactiontime + 0, // attacksound + S_TROO_PAIN, // painstate + 200, // painchance + sfx_popain, // painsound + S_TROO_ATK1, // meleestate + S_TROO_ATK1, // missilestate + S_TROO_DIE1, // deathstate + S_TROO_XDIE1, // xdeathstate + sfx_bgdth1, // deathsound + 8, // speed + 20*FRACUNIT, // radius + 56*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_bgact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_TROO_RAISE1 // raisestate + }, + + { // MT_SERGEANT + 3002, // doomednum + S_SARG_STND, // spawnstate + 150, // spawnhealth + S_SARG_RUN1, // seestate + sfx_sgtsit, // seesound + 8, // reactiontime + sfx_sgtatk, // attacksound + S_SARG_PAIN, // painstate + 180, // painchance + sfx_dmpain, // painsound + S_SARG_ATK1, // meleestate + 0, // missilestate + S_SARG_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_sgtdth, // deathsound + 10, // speed + 30*FRACUNIT, // radius + 56*FRACUNIT, // height + 400, // mass + 0, // damage + sfx_dmact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_SARG_RAISE1 // raisestate + }, + + { // MT_SHADOWS + 58, // doomednum + S_SARG_STND, // spawnstate + 150, // spawnhealth + S_SARG_RUN1, // seestate + sfx_sgtsit, // seesound + 8, // reactiontime + sfx_sgtatk, // attacksound + S_SARG_PAIN, // painstate + 180, // painchance + sfx_dmpain, // painsound + S_SARG_ATK1, // meleestate + 0, // missilestate + S_SARG_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_sgtdth, // deathsound + 10, // speed + 30*FRACUNIT, // radius + 56*FRACUNIT, // height + 400, // mass + 0, // damage + sfx_dmact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_SHADOW|MF_COUNTKILL, // flags + S_SARG_RAISE1 // raisestate + }, + + { // MT_HEAD + 3005, // doomednum + S_HEAD_STND, // spawnstate + 400, // spawnhealth + S_HEAD_RUN1, // seestate + sfx_cacsit, // seesound + 8, // reactiontime + 0, // attacksound + S_HEAD_PAIN, // painstate + 128, // painchance + sfx_dmpain, // painsound + 0, // meleestate + S_HEAD_ATK1, // missilestate + S_HEAD_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_cacdth, // deathsound + 8, // speed + 31*FRACUNIT, // radius + 56*FRACUNIT, // height + 400, // mass + 0, // damage + sfx_dmact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_FLOAT|MF_NOGRAVITY|MF_COUNTKILL, // flags + S_HEAD_RAISE1 // raisestate + }, + + { // MT_BRUISER + 3003, // doomednum + S_BOSS_STND, // spawnstate + 1000, // spawnhealth + S_BOSS_RUN1, // seestate + sfx_brssit, // seesound + 8, // reactiontime + 0, // attacksound + S_BOSS_PAIN, // painstate + 50, // painchance + sfx_dmpain, // painsound + S_BOSS_ATK1, // meleestate + S_BOSS_ATK1, // missilestate + S_BOSS_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_brsdth, // deathsound + 8, // speed + 24*FRACUNIT, // radius + 64*FRACUNIT, // height + 1000, // mass + 0, // damage + sfx_dmact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_BOSS_RAISE1 // raisestate + }, + + { // MT_BRUISERSHOT + -1, // doomednum + S_BRBALL1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_firsht, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_BRBALLX1, // deathstate + S_NULL, // xdeathstate + sfx_firxpl, // deathsound + 15*FRACUNIT, // speed + 6*FRACUNIT, // radius + 8*FRACUNIT, // height + 100, // mass + 8, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_KNIGHT + 69, // doomednum + S_BOS2_STND, // spawnstate + 500, // spawnhealth + S_BOS2_RUN1, // seestate + sfx_kntsit, // seesound + 8, // reactiontime + 0, // attacksound + S_BOS2_PAIN, // painstate + 50, // painchance + sfx_dmpain, // painsound + S_BOS2_ATK1, // meleestate + S_BOS2_ATK1, // missilestate + S_BOS2_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_kntdth, // deathsound + 8, // speed + 24*FRACUNIT, // radius + 64*FRACUNIT, // height + 1000, // mass + 0, // damage + sfx_dmact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_BOS2_RAISE1 // raisestate + }, + + { // MT_SKULL + 3006, // doomednum + S_SKULL_STND, // spawnstate + 100, // spawnhealth + S_SKULL_RUN1, // seestate + 0, // seesound + 8, // reactiontime + sfx_sklatk, // attacksound + S_SKULL_PAIN, // painstate + 256, // painchance + sfx_dmpain, // painsound + 0, // meleestate + S_SKULL_ATK1, // missilestate + S_SKULL_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_firxpl, // deathsound + 8, // speed + 16*FRACUNIT, // radius + 56*FRACUNIT, // height + 50, // mass + 3, // damage + sfx_dmact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_FLOAT|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_SPIDER + 7, // doomednum + S_SPID_STND, // spawnstate + 3000, // spawnhealth + S_SPID_RUN1, // seestate + sfx_spisit, // seesound + 8, // reactiontime + sfx_shotgn, // attacksound + S_SPID_PAIN, // painstate + 40, // painchance + sfx_dmpain, // painsound + 0, // meleestate + S_SPID_ATK1, // missilestate + S_SPID_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_spidth, // deathsound + 12, // speed + 128*FRACUNIT, // radius + 100*FRACUNIT, // height + 1000, // mass + 0, // damage + sfx_dmact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_NULL // raisestate + }, + + { // MT_BABY + 68, // doomednum + S_BSPI_STND, // spawnstate + 500, // spawnhealth + S_BSPI_SIGHT, // seestate + sfx_bspsit, // seesound + 8, // reactiontime + 0, // attacksound + S_BSPI_PAIN, // painstate + 128, // painchance + sfx_dmpain, // painsound + 0, // meleestate + S_BSPI_ATK1, // missilestate + S_BSPI_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_bspdth, // deathsound + 12, // speed + 64*FRACUNIT, // radius + 64*FRACUNIT, // height + 600, // mass + 0, // damage + sfx_bspact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_BSPI_RAISE1 // raisestate + }, + + { // MT_CYBORG + 16, // doomednum + S_CYBER_STND, // spawnstate + 4000, // spawnhealth + S_CYBER_RUN1, // seestate + sfx_cybsit, // seesound + 8, // reactiontime + 0, // attacksound + S_CYBER_PAIN, // painstate + 20, // painchance + sfx_dmpain, // painsound + 0, // meleestate + S_CYBER_ATK1, // missilestate + S_CYBER_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_cybdth, // deathsound + 16, // speed + 40*FRACUNIT, // radius + 110*FRACUNIT, // height + 1000, // mass + 0, // damage + sfx_dmact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_NULL // raisestate + }, + + { // MT_PAIN + 71, // doomednum + S_PAIN_STND, // spawnstate + 400, // spawnhealth + S_PAIN_RUN1, // seestate + sfx_pesit, // seesound + 8, // reactiontime + 0, // attacksound + S_PAIN_PAIN, // painstate + 128, // painchance + sfx_pepain, // painsound + 0, // meleestate + S_PAIN_ATK1, // missilestate + S_PAIN_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_pedth, // deathsound + 8, // speed + 31*FRACUNIT, // radius + 56*FRACUNIT, // height + 400, // mass + 0, // damage + sfx_dmact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_FLOAT|MF_NOGRAVITY|MF_COUNTKILL, // flags + S_PAIN_RAISE1 // raisestate + }, + + { // MT_WOLFSS + 84, // doomednum + S_SSWV_STND, // spawnstate + 50, // spawnhealth + S_SSWV_RUN1, // seestate + sfx_sssit, // seesound + 8, // reactiontime + 0, // attacksound + S_SSWV_PAIN, // painstate + 170, // painchance + sfx_popain, // painsound + 0, // meleestate + S_SSWV_ATK1, // missilestate + S_SSWV_DIE1, // deathstate + S_SSWV_XDIE1, // xdeathstate + sfx_ssdth, // deathsound + 8, // speed + 20*FRACUNIT, // radius + 56*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_posact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_SSWV_RAISE1 // raisestate + }, + + { // MT_KEEN + 72, // doomednum + S_KEENSTND, // spawnstate + 100, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_KEENPAIN, // painstate + 256, // painchance + sfx_keenpn, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_COMMKEEN, // deathstate + S_NULL, // xdeathstate + sfx_keendt, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 72*FRACUNIT, // height + 10000000, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_NULL // raisestate + }, + + { // MT_BOSSBRAIN + 88, // doomednum + S_BRAIN, // spawnstate + 250, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_BRAIN_PAIN, // painstate + 255, // painchance + sfx_bospn, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_BRAIN_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_bosdth, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 10000000, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SHOOTABLE, // flags + S_NULL // raisestate + }, + + { // MT_BOSSSPIT + 89, // doomednum + S_BRAINEYE, // spawnstate + 1000, // spawnhealth + S_BRAINEYESEE, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 32*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOSECTOR, // flags + S_NULL // raisestate + }, + + { // MT_BOSSTARGET + 87, // doomednum + S_NULL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 32*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOSECTOR, // flags + S_NULL // raisestate + }, + + { // MT_SPAWNSHOT + -1, // doomednum + S_SPAWN1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_bospit, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_firxpl, // deathsound + 10*FRACUNIT, // speed + 6*FRACUNIT, // radius + 32*FRACUNIT, // height + 100, // mass + 3, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY|MF_NOCLIP, // flags + S_NULL // raisestate + }, + + { // MT_SPAWNFIRE + -1, // doomednum + S_SPAWNFIRE1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_BARREL + 2035, // doomednum + S_BAR1, // spawnstate + 20, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_BEXP, // deathstate + S_NULL, // xdeathstate + sfx_barexp, // deathsound + 0, // speed + 10*FRACUNIT, // radius + 42*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SHOOTABLE|MF_NOBLOOD, // flags + S_NULL // raisestate + }, + + { // MT_TROOPSHOT + -1, // doomednum + S_TBALL1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_firsht, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_TBALLX1, // deathstate + S_NULL, // xdeathstate + sfx_firxpl, // deathsound + 10*FRACUNIT, // speed + 6*FRACUNIT, // radius + 8*FRACUNIT, // height + 100, // mass + 3, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_HEADSHOT + -1, // doomednum + S_RBALL1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_firsht, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_RBALLX1, // deathstate + S_NULL, // xdeathstate + sfx_firxpl, // deathsound + 10*FRACUNIT, // speed + 6*FRACUNIT, // radius + 8*FRACUNIT, // height + 100, // mass + 5, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_ROCKET + -1, // doomednum + S_ROCKET, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_rlaunc, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_EXPLODE1, // deathstate + S_NULL, // xdeathstate + sfx_barexp, // deathsound + 20*FRACUNIT, // speed + 11*FRACUNIT, // radius + 8*FRACUNIT, // height + 100, // mass + 20, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_PLASMA + -1, // doomednum + S_PLASBALL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_plasma, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_PLASEXP, // deathstate + S_NULL, // xdeathstate + sfx_firxpl, // deathsound + 25*FRACUNIT, // speed + 13*FRACUNIT, // radius + 8*FRACUNIT, // height + 100, // mass + 5, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_BFG + -1, // doomednum + S_BFGSHOT, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + 0, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_BFGLAND, // deathstate + S_NULL, // xdeathstate + sfx_rxplod, // deathsound + 25*FRACUNIT, // speed + 13*FRACUNIT, // radius + 8*FRACUNIT, // height + 100, // mass + 100, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_ARACHPLAZ + -1, // doomednum + S_ARACH_PLAZ, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_plasma, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_ARACH_PLEX, // deathstate + S_NULL, // xdeathstate + sfx_firxpl, // deathsound + 25*FRACUNIT, // speed + 13*FRACUNIT, // radius + 8*FRACUNIT, // height + 100, // mass + 5, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_PUFF + -1, // doomednum + S_PUFF1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_BLOOD + -1, // doomednum + S_BLOOD1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP, // flags + S_NULL // raisestate + }, + + { // MT_TFOG + -1, // doomednum + S_TFOG, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_IFOG + -1, // doomednum + S_IFOG, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_TELEPORTMAN + 14, // doomednum + S_NULL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOSECTOR, // flags + S_NULL // raisestate + }, + + { // MT_EXTRABFG + -1, // doomednum + S_BFGEXP, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC0 + 2018, // doomednum + S_ARM1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC1 + 2019, // doomednum + S_ARM2, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC2 + 2014, // doomednum + S_BON1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { // MT_MISC3 + 2015, // doomednum + S_BON2, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { // MT_MISC4 + 5, // doomednum + S_BKEY, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_NOTDMATCH, // flags + S_NULL // raisestate + }, + + { // MT_MISC5 + 13, // doomednum + S_RKEY, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_NOTDMATCH, // flags + S_NULL // raisestate + }, + + { // MT_MISC6 + 6, // doomednum + S_YKEY, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_NOTDMATCH, // flags + S_NULL // raisestate + }, + + { // MT_MISC7 + 39, // doomednum + S_YSKULL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_NOTDMATCH, // flags + S_NULL // raisestate + }, + + { // MT_MISC8 + 38, // doomednum + S_RSKULL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_NOTDMATCH, // flags + S_NULL // raisestate + }, + + { // MT_MISC9 + 40, // doomednum + S_BSKULL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_NOTDMATCH, // flags + S_NULL // raisestate + }, + + { // MT_MISC10 + 2011, // doomednum + S_STIM, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC11 + 2012, // doomednum + S_MEDI, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC12 + 2013, // doomednum + S_SOUL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { // MT_INV + 2022, // doomednum + S_PINV, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { // MT_MISC13 + 2023, // doomednum + S_PSTR, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { // MT_INS + 2024, // doomednum + S_PINS, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { // MT_MISC14 + 2025, // doomednum + S_SUIT, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC15 + 2026, // doomednum + S_PMAP, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { // MT_MISC16 + 2045, // doomednum + S_PVIS, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { // MT_MEGA + 83, // doomednum + S_MEGA, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { // MT_CLIP + 2007, // doomednum + S_CLIP, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC17 + 2048, // doomednum + S_AMMO, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC18 + 2010, // doomednum + S_ROCK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC19 + 2046, // doomednum + S_BROK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC20 + 2047, // doomednum + S_CELL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC21 + 17, // doomednum + S_CELP, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC22 + 2008, // doomednum + S_SHEL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC23 + 2049, // doomednum + S_SBOX, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC24 + 8, // doomednum + S_BPAK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC25 + 2006, // doomednum + S_BFUG, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_CHAINGUN + 2002, // doomednum + S_MGUN, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC26 + 2005, // doomednum + S_CSAW, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC27 + 2003, // doomednum + S_LAUN, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC28 + 2004, // doomednum + S_PLAS, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_SHOTGUN + 2001, // doomednum + S_SHOT, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_SUPERSHOTGUN + 82, // doomednum + S_SHOT2, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC29 + 85, // doomednum + S_TECHLAMP, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC30 + 86, // doomednum + S_TECH2LAMP, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC31 + 2028, // doomednum + S_COLU, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC32 + 30, // doomednum + S_TALLGRNCOL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC33 + 31, // doomednum + S_SHRTGRNCOL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC34 + 32, // doomednum + S_TALLREDCOL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC35 + 33, // doomednum + S_SHRTREDCOL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC36 + 37, // doomednum + S_SKULLCOL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC37 + 36, // doomednum + S_HEARTCOL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC38 + 41, // doomednum + S_EVILEYE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC39 + 42, // doomednum + S_FLOATSKULL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC40 + 43, // doomednum + S_TORCHTREE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC41 + 44, // doomednum + S_BLUETORCH, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC42 + 45, // doomednum + S_GREENTORCH, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC43 + 46, // doomednum + S_REDTORCH, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC44 + 55, // doomednum + S_BTORCHSHRT, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC45 + 56, // doomednum + S_GTORCHSHRT, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC46 + 57, // doomednum + S_RTORCHSHRT, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC47 + 47, // doomednum + S_STALAGTITE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC48 + 48, // doomednum + S_TECHPILLAR, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC49 + 34, // doomednum + S_CANDLESTIK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { // MT_MISC50 + 35, // doomednum + S_CANDELABRA, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC51 + 49, // doomednum + S_BLOODYTWITCH, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 68*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC52 + 50, // doomednum + S_MEAT2, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 84*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC53 + 51, // doomednum + S_MEAT3, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 84*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC54 + 52, // doomednum + S_MEAT4, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 68*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC55 + 53, // doomednum + S_MEAT5, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 52*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC56 + 59, // doomednum + S_MEAT2, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 84*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC57 + 60, // doomednum + S_MEAT4, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 68*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC58 + 61, // doomednum + S_MEAT3, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 52*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC59 + 62, // doomednum + S_MEAT5, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 52*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC60 + 63, // doomednum + S_BLOODYTWITCH, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 68*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC61 + 22, // doomednum + S_HEAD_DIE6, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { // MT_MISC62 + 15, // doomednum + S_PLAY_DIE7, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { // MT_MISC63 + 18, // doomednum + S_POSS_DIE5, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { // MT_MISC64 + 21, // doomednum + S_SARG_DIE6, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { // MT_MISC65 + 23, // doomednum + S_SKULL_DIE6, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { // MT_MISC66 + 20, // doomednum + S_TROO_DIE5, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { // MT_MISC67 + 19, // doomednum + S_SPOS_DIE5, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { // MT_MISC68 + 10, // doomednum + S_PLAY_XDIE9, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { // MT_MISC69 + 12, // doomednum + S_PLAY_XDIE9, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { // MT_MISC70 + 28, // doomednum + S_HEADSONSTICK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC71 + 24, // doomednum + S_GIBS, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { // MT_MISC72 + 27, // doomednum + S_HEADONASTICK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC73 + 29, // doomednum + S_HEADCANDLES, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC74 + 25, // doomednum + S_DEADSTICK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC75 + 26, // doomednum + S_LIVESTICK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC76 + 54, // doomednum + S_BIGTREE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 32*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC77 + 70, // doomednum + S_BBAR1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC78 + 73, // doomednum + S_HANGNOGUTS, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 88*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC79 + 74, // doomednum + S_HANGBNOBRAIN, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 88*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC80 + 75, // doomednum + S_HANGTLOOKDN, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 64*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC81 + 76, // doomednum + S_HANGTSKULL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 64*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC82 + 77, // doomednum + S_HANGTLOOKUP, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 64*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC83 + 78, // doomednum + S_HANGTNOBRAIN, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 64*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC84 + 79, // doomednum + S_COLONGIBS, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP, // flags + S_NULL // raisestate + }, + + { // MT_MISC85 + 80, // doomednum + S_SMALLPOOL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP, // flags + S_NULL // raisestate + }, + + { // MT_MISC86 + 81, // doomednum + S_BRAINSTEM, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP, // flags + S_NULL // raisestate + } +}; + diff --git a/firmware_p4/components/Applications/doom/info.h b/firmware_p4/components/Applications/doom/info.h new file mode 100644 index 000000000..648b51888 --- /dev/null +++ b/firmware_p4/components/Applications/doom/info.h @@ -0,0 +1,1331 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Thing frame/state LUT, +// generated by multigen utilitiy. +// This one is the original DOOM version, preserved. +// + +#ifndef __INFO__ +#define __INFO__ + +// Needed for action function pointer handling. +#include "d_think.h" + +typedef enum +{ + SPR_TROO, + SPR_SHTG, + SPR_PUNG, + SPR_PISG, + SPR_PISF, + SPR_SHTF, + SPR_SHT2, + SPR_CHGG, + SPR_CHGF, + SPR_MISG, + SPR_MISF, + SPR_SAWG, + SPR_PLSG, + SPR_PLSF, + SPR_BFGG, + SPR_BFGF, + SPR_BLUD, + SPR_PUFF, + SPR_BAL1, + SPR_BAL2, + SPR_PLSS, + SPR_PLSE, + SPR_MISL, + SPR_BFS1, + SPR_BFE1, + SPR_BFE2, + SPR_TFOG, + SPR_IFOG, + SPR_PLAY, + SPR_POSS, + SPR_SPOS, + SPR_VILE, + SPR_FIRE, + SPR_FATB, + SPR_FBXP, + SPR_SKEL, + SPR_MANF, + SPR_FATT, + SPR_CPOS, + SPR_SARG, + SPR_HEAD, + SPR_BAL7, + SPR_BOSS, + SPR_BOS2, + SPR_SKUL, + SPR_SPID, + SPR_BSPI, + SPR_APLS, + SPR_APBX, + SPR_CYBR, + SPR_PAIN, + SPR_SSWV, + SPR_KEEN, + SPR_BBRN, + SPR_BOSF, + SPR_ARM1, + SPR_ARM2, + SPR_BAR1, + SPR_BEXP, + SPR_FCAN, + SPR_BON1, + SPR_BON2, + SPR_BKEY, + SPR_RKEY, + SPR_YKEY, + SPR_BSKU, + SPR_RSKU, + SPR_YSKU, + SPR_STIM, + SPR_MEDI, + SPR_SOUL, + SPR_PINV, + SPR_PSTR, + SPR_PINS, + SPR_MEGA, + SPR_SUIT, + SPR_PMAP, + SPR_PVIS, + SPR_CLIP, + SPR_AMMO, + SPR_ROCK, + SPR_BROK, + SPR_CELL, + SPR_CELP, + SPR_SHEL, + SPR_SBOX, + SPR_BPAK, + SPR_BFUG, + SPR_MGUN, + SPR_CSAW, + SPR_LAUN, + SPR_PLAS, + SPR_SHOT, + SPR_SGN2, + SPR_COLU, + SPR_SMT2, + SPR_GOR1, + SPR_POL2, + SPR_POL5, + SPR_POL4, + SPR_POL3, + SPR_POL1, + SPR_POL6, + SPR_GOR2, + SPR_GOR3, + SPR_GOR4, + SPR_GOR5, + SPR_SMIT, + SPR_COL1, + SPR_COL2, + SPR_COL3, + SPR_COL4, + SPR_CAND, + SPR_CBRA, + SPR_COL6, + SPR_TRE1, + SPR_TRE2, + SPR_ELEC, + SPR_CEYE, + SPR_FSKU, + SPR_COL5, + SPR_TBLU, + SPR_TGRN, + SPR_TRED, + SPR_SMBT, + SPR_SMGT, + SPR_SMRT, + SPR_HDB1, + SPR_HDB2, + SPR_HDB3, + SPR_HDB4, + SPR_HDB5, + SPR_HDB6, + SPR_POB1, + SPR_POB2, + SPR_BRS1, + SPR_TLMP, + SPR_TLP2, + NUMSPRITES + +} spritenum_t; + +typedef enum +{ + S_NULL, + S_LIGHTDONE, + S_PUNCH, + S_PUNCHDOWN, + S_PUNCHUP, + S_PUNCH1, + S_PUNCH2, + S_PUNCH3, + S_PUNCH4, + S_PUNCH5, + S_PISTOL, + S_PISTOLDOWN, + S_PISTOLUP, + S_PISTOL1, + S_PISTOL2, + S_PISTOL3, + S_PISTOL4, + S_PISTOLFLASH, + S_SGUN, + S_SGUNDOWN, + S_SGUNUP, + S_SGUN1, + S_SGUN2, + S_SGUN3, + S_SGUN4, + S_SGUN5, + S_SGUN6, + S_SGUN7, + S_SGUN8, + S_SGUN9, + S_SGUNFLASH1, + S_SGUNFLASH2, + S_DSGUN, + S_DSGUNDOWN, + S_DSGUNUP, + S_DSGUN1, + S_DSGUN2, + S_DSGUN3, + S_DSGUN4, + S_DSGUN5, + S_DSGUN6, + S_DSGUN7, + S_DSGUN8, + S_DSGUN9, + S_DSGUN10, + S_DSNR1, + S_DSNR2, + S_DSGUNFLASH1, + S_DSGUNFLASH2, + S_CHAIN, + S_CHAINDOWN, + S_CHAINUP, + S_CHAIN1, + S_CHAIN2, + S_CHAIN3, + S_CHAINFLASH1, + S_CHAINFLASH2, + S_MISSILE, + S_MISSILEDOWN, + S_MISSILEUP, + S_MISSILE1, + S_MISSILE2, + S_MISSILE3, + S_MISSILEFLASH1, + S_MISSILEFLASH2, + S_MISSILEFLASH3, + S_MISSILEFLASH4, + S_SAW, + S_SAWB, + S_SAWDOWN, + S_SAWUP, + S_SAW1, + S_SAW2, + S_SAW3, + S_PLASMA, + S_PLASMADOWN, + S_PLASMAUP, + S_PLASMA1, + S_PLASMA2, + S_PLASMAFLASH1, + S_PLASMAFLASH2, + S_BFG, + S_BFGDOWN, + S_BFGUP, + S_BFG1, + S_BFG2, + S_BFG3, + S_BFG4, + S_BFGFLASH1, + S_BFGFLASH2, + S_BLOOD1, + S_BLOOD2, + S_BLOOD3, + S_PUFF1, + S_PUFF2, + S_PUFF3, + S_PUFF4, + S_TBALL1, + S_TBALL2, + S_TBALLX1, + S_TBALLX2, + S_TBALLX3, + S_RBALL1, + S_RBALL2, + S_RBALLX1, + S_RBALLX2, + S_RBALLX3, + S_PLASBALL, + S_PLASBALL2, + S_PLASEXP, + S_PLASEXP2, + S_PLASEXP3, + S_PLASEXP4, + S_PLASEXP5, + S_ROCKET, + S_BFGSHOT, + S_BFGSHOT2, + S_BFGLAND, + S_BFGLAND2, + S_BFGLAND3, + S_BFGLAND4, + S_BFGLAND5, + S_BFGLAND6, + S_BFGEXP, + S_BFGEXP2, + S_BFGEXP3, + S_BFGEXP4, + S_EXPLODE1, + S_EXPLODE2, + S_EXPLODE3, + S_TFOG, + S_TFOG01, + S_TFOG02, + S_TFOG2, + S_TFOG3, + S_TFOG4, + S_TFOG5, + S_TFOG6, + S_TFOG7, + S_TFOG8, + S_TFOG9, + S_TFOG10, + S_IFOG, + S_IFOG01, + S_IFOG02, + S_IFOG2, + S_IFOG3, + S_IFOG4, + S_IFOG5, + S_PLAY, + S_PLAY_RUN1, + S_PLAY_RUN2, + S_PLAY_RUN3, + S_PLAY_RUN4, + S_PLAY_ATK1, + S_PLAY_ATK2, + S_PLAY_PAIN, + S_PLAY_PAIN2, + S_PLAY_DIE1, + S_PLAY_DIE2, + S_PLAY_DIE3, + S_PLAY_DIE4, + S_PLAY_DIE5, + S_PLAY_DIE6, + S_PLAY_DIE7, + S_PLAY_XDIE1, + S_PLAY_XDIE2, + S_PLAY_XDIE3, + S_PLAY_XDIE4, + S_PLAY_XDIE5, + S_PLAY_XDIE6, + S_PLAY_XDIE7, + S_PLAY_XDIE8, + S_PLAY_XDIE9, + S_POSS_STND, + S_POSS_STND2, + S_POSS_RUN1, + S_POSS_RUN2, + S_POSS_RUN3, + S_POSS_RUN4, + S_POSS_RUN5, + S_POSS_RUN6, + S_POSS_RUN7, + S_POSS_RUN8, + S_POSS_ATK1, + S_POSS_ATK2, + S_POSS_ATK3, + S_POSS_PAIN, + S_POSS_PAIN2, + S_POSS_DIE1, + S_POSS_DIE2, + S_POSS_DIE3, + S_POSS_DIE4, + S_POSS_DIE5, + S_POSS_XDIE1, + S_POSS_XDIE2, + S_POSS_XDIE3, + S_POSS_XDIE4, + S_POSS_XDIE5, + S_POSS_XDIE6, + S_POSS_XDIE7, + S_POSS_XDIE8, + S_POSS_XDIE9, + S_POSS_RAISE1, + S_POSS_RAISE2, + S_POSS_RAISE3, + S_POSS_RAISE4, + S_SPOS_STND, + S_SPOS_STND2, + S_SPOS_RUN1, + S_SPOS_RUN2, + S_SPOS_RUN3, + S_SPOS_RUN4, + S_SPOS_RUN5, + S_SPOS_RUN6, + S_SPOS_RUN7, + S_SPOS_RUN8, + S_SPOS_ATK1, + S_SPOS_ATK2, + S_SPOS_ATK3, + S_SPOS_PAIN, + S_SPOS_PAIN2, + S_SPOS_DIE1, + S_SPOS_DIE2, + S_SPOS_DIE3, + S_SPOS_DIE4, + S_SPOS_DIE5, + S_SPOS_XDIE1, + S_SPOS_XDIE2, + S_SPOS_XDIE3, + S_SPOS_XDIE4, + S_SPOS_XDIE5, + S_SPOS_XDIE6, + S_SPOS_XDIE7, + S_SPOS_XDIE8, + S_SPOS_XDIE9, + S_SPOS_RAISE1, + S_SPOS_RAISE2, + S_SPOS_RAISE3, + S_SPOS_RAISE4, + S_SPOS_RAISE5, + S_VILE_STND, + S_VILE_STND2, + S_VILE_RUN1, + S_VILE_RUN2, + S_VILE_RUN3, + S_VILE_RUN4, + S_VILE_RUN5, + S_VILE_RUN6, + S_VILE_RUN7, + S_VILE_RUN8, + S_VILE_RUN9, + S_VILE_RUN10, + S_VILE_RUN11, + S_VILE_RUN12, + S_VILE_ATK1, + S_VILE_ATK2, + S_VILE_ATK3, + S_VILE_ATK4, + S_VILE_ATK5, + S_VILE_ATK6, + S_VILE_ATK7, + S_VILE_ATK8, + S_VILE_ATK9, + S_VILE_ATK10, + S_VILE_ATK11, + S_VILE_HEAL1, + S_VILE_HEAL2, + S_VILE_HEAL3, + S_VILE_PAIN, + S_VILE_PAIN2, + S_VILE_DIE1, + S_VILE_DIE2, + S_VILE_DIE3, + S_VILE_DIE4, + S_VILE_DIE5, + S_VILE_DIE6, + S_VILE_DIE7, + S_VILE_DIE8, + S_VILE_DIE9, + S_VILE_DIE10, + S_FIRE1, + S_FIRE2, + S_FIRE3, + S_FIRE4, + S_FIRE5, + S_FIRE6, + S_FIRE7, + S_FIRE8, + S_FIRE9, + S_FIRE10, + S_FIRE11, + S_FIRE12, + S_FIRE13, + S_FIRE14, + S_FIRE15, + S_FIRE16, + S_FIRE17, + S_FIRE18, + S_FIRE19, + S_FIRE20, + S_FIRE21, + S_FIRE22, + S_FIRE23, + S_FIRE24, + S_FIRE25, + S_FIRE26, + S_FIRE27, + S_FIRE28, + S_FIRE29, + S_FIRE30, + S_SMOKE1, + S_SMOKE2, + S_SMOKE3, + S_SMOKE4, + S_SMOKE5, + S_TRACER, + S_TRACER2, + S_TRACEEXP1, + S_TRACEEXP2, + S_TRACEEXP3, + S_SKEL_STND, + S_SKEL_STND2, + S_SKEL_RUN1, + S_SKEL_RUN2, + S_SKEL_RUN3, + S_SKEL_RUN4, + S_SKEL_RUN5, + S_SKEL_RUN6, + S_SKEL_RUN7, + S_SKEL_RUN8, + S_SKEL_RUN9, + S_SKEL_RUN10, + S_SKEL_RUN11, + S_SKEL_RUN12, + S_SKEL_FIST1, + S_SKEL_FIST2, + S_SKEL_FIST3, + S_SKEL_FIST4, + S_SKEL_MISS1, + S_SKEL_MISS2, + S_SKEL_MISS3, + S_SKEL_MISS4, + S_SKEL_PAIN, + S_SKEL_PAIN2, + S_SKEL_DIE1, + S_SKEL_DIE2, + S_SKEL_DIE3, + S_SKEL_DIE4, + S_SKEL_DIE5, + S_SKEL_DIE6, + S_SKEL_RAISE1, + S_SKEL_RAISE2, + S_SKEL_RAISE3, + S_SKEL_RAISE4, + S_SKEL_RAISE5, + S_SKEL_RAISE6, + S_FATSHOT1, + S_FATSHOT2, + S_FATSHOTX1, + S_FATSHOTX2, + S_FATSHOTX3, + S_FATT_STND, + S_FATT_STND2, + S_FATT_RUN1, + S_FATT_RUN2, + S_FATT_RUN3, + S_FATT_RUN4, + S_FATT_RUN5, + S_FATT_RUN6, + S_FATT_RUN7, + S_FATT_RUN8, + S_FATT_RUN9, + S_FATT_RUN10, + S_FATT_RUN11, + S_FATT_RUN12, + S_FATT_ATK1, + S_FATT_ATK2, + S_FATT_ATK3, + S_FATT_ATK4, + S_FATT_ATK5, + S_FATT_ATK6, + S_FATT_ATK7, + S_FATT_ATK8, + S_FATT_ATK9, + S_FATT_ATK10, + S_FATT_PAIN, + S_FATT_PAIN2, + S_FATT_DIE1, + S_FATT_DIE2, + S_FATT_DIE3, + S_FATT_DIE4, + S_FATT_DIE5, + S_FATT_DIE6, + S_FATT_DIE7, + S_FATT_DIE8, + S_FATT_DIE9, + S_FATT_DIE10, + S_FATT_RAISE1, + S_FATT_RAISE2, + S_FATT_RAISE3, + S_FATT_RAISE4, + S_FATT_RAISE5, + S_FATT_RAISE6, + S_FATT_RAISE7, + S_FATT_RAISE8, + S_CPOS_STND, + S_CPOS_STND2, + S_CPOS_RUN1, + S_CPOS_RUN2, + S_CPOS_RUN3, + S_CPOS_RUN4, + S_CPOS_RUN5, + S_CPOS_RUN6, + S_CPOS_RUN7, + S_CPOS_RUN8, + S_CPOS_ATK1, + S_CPOS_ATK2, + S_CPOS_ATK3, + S_CPOS_ATK4, + S_CPOS_PAIN, + S_CPOS_PAIN2, + S_CPOS_DIE1, + S_CPOS_DIE2, + S_CPOS_DIE3, + S_CPOS_DIE4, + S_CPOS_DIE5, + S_CPOS_DIE6, + S_CPOS_DIE7, + S_CPOS_XDIE1, + S_CPOS_XDIE2, + S_CPOS_XDIE3, + S_CPOS_XDIE4, + S_CPOS_XDIE5, + S_CPOS_XDIE6, + S_CPOS_RAISE1, + S_CPOS_RAISE2, + S_CPOS_RAISE3, + S_CPOS_RAISE4, + S_CPOS_RAISE5, + S_CPOS_RAISE6, + S_CPOS_RAISE7, + S_TROO_STND, + S_TROO_STND2, + S_TROO_RUN1, + S_TROO_RUN2, + S_TROO_RUN3, + S_TROO_RUN4, + S_TROO_RUN5, + S_TROO_RUN6, + S_TROO_RUN7, + S_TROO_RUN8, + S_TROO_ATK1, + S_TROO_ATK2, + S_TROO_ATK3, + S_TROO_PAIN, + S_TROO_PAIN2, + S_TROO_DIE1, + S_TROO_DIE2, + S_TROO_DIE3, + S_TROO_DIE4, + S_TROO_DIE5, + S_TROO_XDIE1, + S_TROO_XDIE2, + S_TROO_XDIE3, + S_TROO_XDIE4, + S_TROO_XDIE5, + S_TROO_XDIE6, + S_TROO_XDIE7, + S_TROO_XDIE8, + S_TROO_RAISE1, + S_TROO_RAISE2, + S_TROO_RAISE3, + S_TROO_RAISE4, + S_TROO_RAISE5, + S_SARG_STND, + S_SARG_STND2, + S_SARG_RUN1, + S_SARG_RUN2, + S_SARG_RUN3, + S_SARG_RUN4, + S_SARG_RUN5, + S_SARG_RUN6, + S_SARG_RUN7, + S_SARG_RUN8, + S_SARG_ATK1, + S_SARG_ATK2, + S_SARG_ATK3, + S_SARG_PAIN, + S_SARG_PAIN2, + S_SARG_DIE1, + S_SARG_DIE2, + S_SARG_DIE3, + S_SARG_DIE4, + S_SARG_DIE5, + S_SARG_DIE6, + S_SARG_RAISE1, + S_SARG_RAISE2, + S_SARG_RAISE3, + S_SARG_RAISE4, + S_SARG_RAISE5, + S_SARG_RAISE6, + S_HEAD_STND, + S_HEAD_RUN1, + S_HEAD_ATK1, + S_HEAD_ATK2, + S_HEAD_ATK3, + S_HEAD_PAIN, + S_HEAD_PAIN2, + S_HEAD_PAIN3, + S_HEAD_DIE1, + S_HEAD_DIE2, + S_HEAD_DIE3, + S_HEAD_DIE4, + S_HEAD_DIE5, + S_HEAD_DIE6, + S_HEAD_RAISE1, + S_HEAD_RAISE2, + S_HEAD_RAISE3, + S_HEAD_RAISE4, + S_HEAD_RAISE5, + S_HEAD_RAISE6, + S_BRBALL1, + S_BRBALL2, + S_BRBALLX1, + S_BRBALLX2, + S_BRBALLX3, + S_BOSS_STND, + S_BOSS_STND2, + S_BOSS_RUN1, + S_BOSS_RUN2, + S_BOSS_RUN3, + S_BOSS_RUN4, + S_BOSS_RUN5, + S_BOSS_RUN6, + S_BOSS_RUN7, + S_BOSS_RUN8, + S_BOSS_ATK1, + S_BOSS_ATK2, + S_BOSS_ATK3, + S_BOSS_PAIN, + S_BOSS_PAIN2, + S_BOSS_DIE1, + S_BOSS_DIE2, + S_BOSS_DIE3, + S_BOSS_DIE4, + S_BOSS_DIE5, + S_BOSS_DIE6, + S_BOSS_DIE7, + S_BOSS_RAISE1, + S_BOSS_RAISE2, + S_BOSS_RAISE3, + S_BOSS_RAISE4, + S_BOSS_RAISE5, + S_BOSS_RAISE6, + S_BOSS_RAISE7, + S_BOS2_STND, + S_BOS2_STND2, + S_BOS2_RUN1, + S_BOS2_RUN2, + S_BOS2_RUN3, + S_BOS2_RUN4, + S_BOS2_RUN5, + S_BOS2_RUN6, + S_BOS2_RUN7, + S_BOS2_RUN8, + S_BOS2_ATK1, + S_BOS2_ATK2, + S_BOS2_ATK3, + S_BOS2_PAIN, + S_BOS2_PAIN2, + S_BOS2_DIE1, + S_BOS2_DIE2, + S_BOS2_DIE3, + S_BOS2_DIE4, + S_BOS2_DIE5, + S_BOS2_DIE6, + S_BOS2_DIE7, + S_BOS2_RAISE1, + S_BOS2_RAISE2, + S_BOS2_RAISE3, + S_BOS2_RAISE4, + S_BOS2_RAISE5, + S_BOS2_RAISE6, + S_BOS2_RAISE7, + S_SKULL_STND, + S_SKULL_STND2, + S_SKULL_RUN1, + S_SKULL_RUN2, + S_SKULL_ATK1, + S_SKULL_ATK2, + S_SKULL_ATK3, + S_SKULL_ATK4, + S_SKULL_PAIN, + S_SKULL_PAIN2, + S_SKULL_DIE1, + S_SKULL_DIE2, + S_SKULL_DIE3, + S_SKULL_DIE4, + S_SKULL_DIE5, + S_SKULL_DIE6, + S_SPID_STND, + S_SPID_STND2, + S_SPID_RUN1, + S_SPID_RUN2, + S_SPID_RUN3, + S_SPID_RUN4, + S_SPID_RUN5, + S_SPID_RUN6, + S_SPID_RUN7, + S_SPID_RUN8, + S_SPID_RUN9, + S_SPID_RUN10, + S_SPID_RUN11, + S_SPID_RUN12, + S_SPID_ATK1, + S_SPID_ATK2, + S_SPID_ATK3, + S_SPID_ATK4, + S_SPID_PAIN, + S_SPID_PAIN2, + S_SPID_DIE1, + S_SPID_DIE2, + S_SPID_DIE3, + S_SPID_DIE4, + S_SPID_DIE5, + S_SPID_DIE6, + S_SPID_DIE7, + S_SPID_DIE8, + S_SPID_DIE9, + S_SPID_DIE10, + S_SPID_DIE11, + S_BSPI_STND, + S_BSPI_STND2, + S_BSPI_SIGHT, + S_BSPI_RUN1, + S_BSPI_RUN2, + S_BSPI_RUN3, + S_BSPI_RUN4, + S_BSPI_RUN5, + S_BSPI_RUN6, + S_BSPI_RUN7, + S_BSPI_RUN8, + S_BSPI_RUN9, + S_BSPI_RUN10, + S_BSPI_RUN11, + S_BSPI_RUN12, + S_BSPI_ATK1, + S_BSPI_ATK2, + S_BSPI_ATK3, + S_BSPI_ATK4, + S_BSPI_PAIN, + S_BSPI_PAIN2, + S_BSPI_DIE1, + S_BSPI_DIE2, + S_BSPI_DIE3, + S_BSPI_DIE4, + S_BSPI_DIE5, + S_BSPI_DIE6, + S_BSPI_DIE7, + S_BSPI_RAISE1, + S_BSPI_RAISE2, + S_BSPI_RAISE3, + S_BSPI_RAISE4, + S_BSPI_RAISE5, + S_BSPI_RAISE6, + S_BSPI_RAISE7, + S_ARACH_PLAZ, + S_ARACH_PLAZ2, + S_ARACH_PLEX, + S_ARACH_PLEX2, + S_ARACH_PLEX3, + S_ARACH_PLEX4, + S_ARACH_PLEX5, + S_CYBER_STND, + S_CYBER_STND2, + S_CYBER_RUN1, + S_CYBER_RUN2, + S_CYBER_RUN3, + S_CYBER_RUN4, + S_CYBER_RUN5, + S_CYBER_RUN6, + S_CYBER_RUN7, + S_CYBER_RUN8, + S_CYBER_ATK1, + S_CYBER_ATK2, + S_CYBER_ATK3, + S_CYBER_ATK4, + S_CYBER_ATK5, + S_CYBER_ATK6, + S_CYBER_PAIN, + S_CYBER_DIE1, + S_CYBER_DIE2, + S_CYBER_DIE3, + S_CYBER_DIE4, + S_CYBER_DIE5, + S_CYBER_DIE6, + S_CYBER_DIE7, + S_CYBER_DIE8, + S_CYBER_DIE9, + S_CYBER_DIE10, + S_PAIN_STND, + S_PAIN_RUN1, + S_PAIN_RUN2, + S_PAIN_RUN3, + S_PAIN_RUN4, + S_PAIN_RUN5, + S_PAIN_RUN6, + S_PAIN_ATK1, + S_PAIN_ATK2, + S_PAIN_ATK3, + S_PAIN_ATK4, + S_PAIN_PAIN, + S_PAIN_PAIN2, + S_PAIN_DIE1, + S_PAIN_DIE2, + S_PAIN_DIE3, + S_PAIN_DIE4, + S_PAIN_DIE5, + S_PAIN_DIE6, + S_PAIN_RAISE1, + S_PAIN_RAISE2, + S_PAIN_RAISE3, + S_PAIN_RAISE4, + S_PAIN_RAISE5, + S_PAIN_RAISE6, + S_SSWV_STND, + S_SSWV_STND2, + S_SSWV_RUN1, + S_SSWV_RUN2, + S_SSWV_RUN3, + S_SSWV_RUN4, + S_SSWV_RUN5, + S_SSWV_RUN6, + S_SSWV_RUN7, + S_SSWV_RUN8, + S_SSWV_ATK1, + S_SSWV_ATK2, + S_SSWV_ATK3, + S_SSWV_ATK4, + S_SSWV_ATK5, + S_SSWV_ATK6, + S_SSWV_PAIN, + S_SSWV_PAIN2, + S_SSWV_DIE1, + S_SSWV_DIE2, + S_SSWV_DIE3, + S_SSWV_DIE4, + S_SSWV_DIE5, + S_SSWV_XDIE1, + S_SSWV_XDIE2, + S_SSWV_XDIE3, + S_SSWV_XDIE4, + S_SSWV_XDIE5, + S_SSWV_XDIE6, + S_SSWV_XDIE7, + S_SSWV_XDIE8, + S_SSWV_XDIE9, + S_SSWV_RAISE1, + S_SSWV_RAISE2, + S_SSWV_RAISE3, + S_SSWV_RAISE4, + S_SSWV_RAISE5, + S_KEENSTND, + S_COMMKEEN, + S_COMMKEEN2, + S_COMMKEEN3, + S_COMMKEEN4, + S_COMMKEEN5, + S_COMMKEEN6, + S_COMMKEEN7, + S_COMMKEEN8, + S_COMMKEEN9, + S_COMMKEEN10, + S_COMMKEEN11, + S_COMMKEEN12, + S_KEENPAIN, + S_KEENPAIN2, + S_BRAIN, + S_BRAIN_PAIN, + S_BRAIN_DIE1, + S_BRAIN_DIE2, + S_BRAIN_DIE3, + S_BRAIN_DIE4, + S_BRAINEYE, + S_BRAINEYESEE, + S_BRAINEYE1, + S_SPAWN1, + S_SPAWN2, + S_SPAWN3, + S_SPAWN4, + S_SPAWNFIRE1, + S_SPAWNFIRE2, + S_SPAWNFIRE3, + S_SPAWNFIRE4, + S_SPAWNFIRE5, + S_SPAWNFIRE6, + S_SPAWNFIRE7, + S_SPAWNFIRE8, + S_BRAINEXPLODE1, + S_BRAINEXPLODE2, + S_BRAINEXPLODE3, + S_ARM1, + S_ARM1A, + S_ARM2, + S_ARM2A, + S_BAR1, + S_BAR2, + S_BEXP, + S_BEXP2, + S_BEXP3, + S_BEXP4, + S_BEXP5, + S_BBAR1, + S_BBAR2, + S_BBAR3, + S_BON1, + S_BON1A, + S_BON1B, + S_BON1C, + S_BON1D, + S_BON1E, + S_BON2, + S_BON2A, + S_BON2B, + S_BON2C, + S_BON2D, + S_BON2E, + S_BKEY, + S_BKEY2, + S_RKEY, + S_RKEY2, + S_YKEY, + S_YKEY2, + S_BSKULL, + S_BSKULL2, + S_RSKULL, + S_RSKULL2, + S_YSKULL, + S_YSKULL2, + S_STIM, + S_MEDI, + S_SOUL, + S_SOUL2, + S_SOUL3, + S_SOUL4, + S_SOUL5, + S_SOUL6, + S_PINV, + S_PINV2, + S_PINV3, + S_PINV4, + S_PSTR, + S_PINS, + S_PINS2, + S_PINS3, + S_PINS4, + S_MEGA, + S_MEGA2, + S_MEGA3, + S_MEGA4, + S_SUIT, + S_PMAP, + S_PMAP2, + S_PMAP3, + S_PMAP4, + S_PMAP5, + S_PMAP6, + S_PVIS, + S_PVIS2, + S_CLIP, + S_AMMO, + S_ROCK, + S_BROK, + S_CELL, + S_CELP, + S_SHEL, + S_SBOX, + S_BPAK, + S_BFUG, + S_MGUN, + S_CSAW, + S_LAUN, + S_PLAS, + S_SHOT, + S_SHOT2, + S_COLU, + S_STALAG, + S_BLOODYTWITCH, + S_BLOODYTWITCH2, + S_BLOODYTWITCH3, + S_BLOODYTWITCH4, + S_DEADTORSO, + S_DEADBOTTOM, + S_HEADSONSTICK, + S_GIBS, + S_HEADONASTICK, + S_HEADCANDLES, + S_HEADCANDLES2, + S_DEADSTICK, + S_LIVESTICK, + S_LIVESTICK2, + S_MEAT2, + S_MEAT3, + S_MEAT4, + S_MEAT5, + S_STALAGTITE, + S_TALLGRNCOL, + S_SHRTGRNCOL, + S_TALLREDCOL, + S_SHRTREDCOL, + S_CANDLESTIK, + S_CANDELABRA, + S_SKULLCOL, + S_TORCHTREE, + S_BIGTREE, + S_TECHPILLAR, + S_EVILEYE, + S_EVILEYE2, + S_EVILEYE3, + S_EVILEYE4, + S_FLOATSKULL, + S_FLOATSKULL2, + S_FLOATSKULL3, + S_HEARTCOL, + S_HEARTCOL2, + S_BLUETORCH, + S_BLUETORCH2, + S_BLUETORCH3, + S_BLUETORCH4, + S_GREENTORCH, + S_GREENTORCH2, + S_GREENTORCH3, + S_GREENTORCH4, + S_REDTORCH, + S_REDTORCH2, + S_REDTORCH3, + S_REDTORCH4, + S_BTORCHSHRT, + S_BTORCHSHRT2, + S_BTORCHSHRT3, + S_BTORCHSHRT4, + S_GTORCHSHRT, + S_GTORCHSHRT2, + S_GTORCHSHRT3, + S_GTORCHSHRT4, + S_RTORCHSHRT, + S_RTORCHSHRT2, + S_RTORCHSHRT3, + S_RTORCHSHRT4, + S_HANGNOGUTS, + S_HANGBNOBRAIN, + S_HANGTLOOKDN, + S_HANGTSKULL, + S_HANGTLOOKUP, + S_HANGTNOBRAIN, + S_COLONGIBS, + S_SMALLPOOL, + S_BRAINSTEM, + S_TECHLAMP, + S_TECHLAMP2, + S_TECHLAMP3, + S_TECHLAMP4, + S_TECH2LAMP, + S_TECH2LAMP2, + S_TECH2LAMP3, + S_TECH2LAMP4, + NUMSTATES +} statenum_t; + + +typedef struct +{ + spritenum_t sprite; + int frame; + int tics; + // void (*action) (); + actionf_t action; + statenum_t nextstate; + int misc1; + int misc2; +} state_t; + +extern state_t states[NUMSTATES]; +extern char *sprnames[]; + +typedef enum { + MT_PLAYER, + MT_POSSESSED, + MT_SHOTGUY, + MT_VILE, + MT_FIRE, + MT_UNDEAD, + MT_TRACER, + MT_SMOKE, + MT_FATSO, + MT_FATSHOT, + MT_CHAINGUY, + MT_TROOP, + MT_SERGEANT, + MT_SHADOWS, + MT_HEAD, + MT_BRUISER, + MT_BRUISERSHOT, + MT_KNIGHT, + MT_SKULL, + MT_SPIDER, + MT_BABY, + MT_CYBORG, + MT_PAIN, + MT_WOLFSS, + MT_KEEN, + MT_BOSSBRAIN, + MT_BOSSSPIT, + MT_BOSSTARGET, + MT_SPAWNSHOT, + MT_SPAWNFIRE, + MT_BARREL, + MT_TROOPSHOT, + MT_HEADSHOT, + MT_ROCKET, + MT_PLASMA, + MT_BFG, + MT_ARACHPLAZ, + MT_PUFF, + MT_BLOOD, + MT_TFOG, + MT_IFOG, + MT_TELEPORTMAN, + MT_EXTRABFG, + MT_MISC0, + MT_MISC1, + MT_MISC2, + MT_MISC3, + MT_MISC4, + MT_MISC5, + MT_MISC6, + MT_MISC7, + MT_MISC8, + MT_MISC9, + MT_MISC10, + MT_MISC11, + MT_MISC12, + MT_INV, + MT_MISC13, + MT_INS, + MT_MISC14, + MT_MISC15, + MT_MISC16, + MT_MEGA, + MT_CLIP, + MT_MISC17, + MT_MISC18, + MT_MISC19, + MT_MISC20, + MT_MISC21, + MT_MISC22, + MT_MISC23, + MT_MISC24, + MT_MISC25, + MT_CHAINGUN, + MT_MISC26, + MT_MISC27, + MT_MISC28, + MT_SHOTGUN, + MT_SUPERSHOTGUN, + MT_MISC29, + MT_MISC30, + MT_MISC31, + MT_MISC32, + MT_MISC33, + MT_MISC34, + MT_MISC35, + MT_MISC36, + MT_MISC37, + MT_MISC38, + MT_MISC39, + MT_MISC40, + MT_MISC41, + MT_MISC42, + MT_MISC43, + MT_MISC44, + MT_MISC45, + MT_MISC46, + MT_MISC47, + MT_MISC48, + MT_MISC49, + MT_MISC50, + MT_MISC51, + MT_MISC52, + MT_MISC53, + MT_MISC54, + MT_MISC55, + MT_MISC56, + MT_MISC57, + MT_MISC58, + MT_MISC59, + MT_MISC60, + MT_MISC61, + MT_MISC62, + MT_MISC63, + MT_MISC64, + MT_MISC65, + MT_MISC66, + MT_MISC67, + MT_MISC68, + MT_MISC69, + MT_MISC70, + MT_MISC71, + MT_MISC72, + MT_MISC73, + MT_MISC74, + MT_MISC75, + MT_MISC76, + MT_MISC77, + MT_MISC78, + MT_MISC79, + MT_MISC80, + MT_MISC81, + MT_MISC82, + MT_MISC83, + MT_MISC84, + MT_MISC85, + MT_MISC86, + NUMMOBJTYPES + +} mobjtype_t; + +typedef struct +{ + int doomednum; + int spawnstate; + int spawnhealth; + int seestate; + int seesound; + int reactiontime; + int attacksound; + int painstate; + int painchance; + int painsound; + int meleestate; + int missilestate; + int deathstate; + int xdeathstate; + int deathsound; + int speed; + int radius; + int height; + int mass; + int damage; + int activesound; + int flags; + int raisestate; + +} mobjinfo_t; + +extern mobjinfo_t mobjinfo[NUMMOBJTYPES]; + +#endif diff --git a/firmware_p4/components/Applications/doom/linker.lf b/firmware_p4/components/Applications/doom/linker.lf new file mode 100644 index 000000000..1562f4de9 --- /dev/null +++ b/firmware_p4/components/Applications/doom/linker.lf @@ -0,0 +1,22 @@ +# Move the DOOM engine's large static .bss/.common out of scarce internal SRAM +# and into PSRAM. libdoom.a parks ~300 KB in internal RAM at link time (visplanes +# ~85 KB, openings ~41 KB, ticdata, viewangletox, drawsegs, zlight, vissprites, +# ...), which starves the whole system to ~14 KB free internal — so entering DOOM +# fails to get the ST7789 SPI-DMA bounce buffer ("Failed to allocate priv TX +# buffer") and every blit dies. +# +# These are CPU-only renderer scratch buffers (the real double framebuffer is +# malloc'd in PSRAM already), so PSRAM placement is safe — no DMA touches them. +# Only .bss/.common move; initialized .data (states, mobjinfo, S_sfx) stays +# internal. Requires CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y for the +# extern_ram region to exist. + +[scheme:doom_extram] +entries: + bss -> extern_ram + common -> extern_ram + +[mapping:doom] +archive: libdoom.a +entries: + * (doom_extram) diff --git a/firmware_p4/components/Applications/doom/m_argv.c b/firmware_p4/components/Applications/doom/m_argv.c new file mode 100644 index 000000000..1582450fe --- /dev/null +++ b/firmware_p4/components/Applications/doom/m_argv.c @@ -0,0 +1,265 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// + + +#include +#include +#include +#include + +#include "doomtype.h" +#include "i_system.h" +#include "m_misc.h" +#include "m_argv.h" // haleyjd 20110212: warning fix + +int myargc; +char** myargv; + + + + +// +// M_CheckParm +// Checks for the given parameter +// in the program's command line arguments. +// Returns the argument number (1 to argc-1) +// or 0 if not present +// + +int M_CheckParmWithArgs(char *check, int num_args) +{ + int i; + + for (i = 1; i < myargc - num_args; i++) + { + if (!strcasecmp(check, myargv[i])) + return i; + } + + return 0; +} + +// +// M_ParmExists +// +// Returns true if the given parameter exists in the program's command +// line arguments, false if not. +// + +boolean M_ParmExists(char *check) +{ + return M_CheckParm(check) != 0; +} + +int M_CheckParm(char *check) +{ + return M_CheckParmWithArgs(check, 0); +} + +#define MAXARGVS 100 + +static void LoadResponseFile(int argv_index) +{ +#if ORIGCODE + FILE *handle; + int size; + char *infile; + char *file; + char *response_filename; + char **newargv; + int newargc; + int i, k; + + response_filename = myargv[argv_index] + 1; + + // Read the response file into memory + handle = fopen(response_filename, "rb"); + + if (handle == NULL) + { + printf ("\nNo such response file!"); +#if ORIGCODE + exit(1); +#endif + } + + printf("Found response file %s!\n", response_filename); + + size = M_FileLength(handle); + + // Read in the entire file + // Allocate one byte extra - this is in case there is an argument + // at the end of the response file, in which case a '\0' will be + // needed. + + file = malloc(size + 1); + + i = 0; + + while (i < size) + { + k = fread(file + i, 1, size - i, handle); + + if (k < 0) + { + I_Error("Failed to read full contents of '%s'", response_filename); + } + + i += k; + } + + fclose(handle); + + // Create new arguments list array + + newargv = malloc(sizeof(char *) * MAXARGVS); + newargc = 0; + memset(newargv, 0, sizeof(char *) * MAXARGVS); + + // Copy all the arguments in the list up to the response file + + for (i=0; i= size) + { + break; + } + + // If the next argument is enclosed in quote marks, treat + // the contents as a single argument. This allows long filenames + // to be specified. + + if (infile[k] == '\"') + { + // Skip the first character(") + ++k; + + newargv[newargc++] = &infile[k]; + + // Read all characters between quotes + + while (k < size && infile[k] != '\"' && infile[k] != '\n') + { + ++k; + } + + if (k >= size || infile[k] == '\n') + { + I_Error("Quotes unclosed in response file '%s'", + response_filename); + } + + // Cut off the string at the closing quote + + infile[k] = '\0'; + ++k; + } + else + { + // Read in the next argument until a space is reached + + newargv[newargc++] = &infile[k]; + + while(k < size && !isspace((int)infile[k])) + { + ++k; + } + + // Cut off the end of the argument at the first space + + infile[k] = '\0'; + + ++k; + } + } + + // Add arguments following the response file argument + + for (i=argv_index + 1; ibox[BOXRIGHT]) + box[BOXRIGHT] = x; + if (ybox[BOXTOP]) + box[BOXTOP] = y; +} + + + + + diff --git a/firmware_p4/components/Applications/doom/m_bbox.h b/firmware_p4/components/Applications/doom/m_bbox.h new file mode 100644 index 000000000..5a7af57c4 --- /dev/null +++ b/firmware_p4/components/Applications/doom/m_bbox.h @@ -0,0 +1,47 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Nil. +// + + +#ifndef __M_BBOX__ +#define __M_BBOX__ + +#include + +#include "m_fixed.h" + + +// Bounding box coordinate storage. +enum +{ + BOXTOP, + BOXBOTTOM, + BOXLEFT, + BOXRIGHT +}; // bbox coordinates + +// Bounding box functions. +void M_ClearBox (fixed_t* box); + +void +M_AddToBox +( fixed_t* box, + fixed_t x, + fixed_t y ); + + +#endif diff --git a/firmware_p4/components/Applications/doom/m_cheat.c b/firmware_p4/components/Applications/doom/m_cheat.c new file mode 100644 index 000000000..1565f9ef5 --- /dev/null +++ b/firmware_p4/components/Applications/doom/m_cheat.c @@ -0,0 +1,89 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Cheat sequence checking. +// + + + +#include + +#include "doomtype.h" +#include "m_cheat.h" + +// +// CHEAT SEQUENCE PACKAGE +// + +// +// Called in st_stuff module, which handles the input. +// Returns a 1 if the cheat was successful, 0 if failed. +// +int +cht_CheckCheat +( cheatseq_t* cht, + char key ) +{ + // if we make a short sequence on a cheat with parameters, this + // will not work in vanilla doom. behave the same. + + if (cht->parameter_chars > 0 && strlen(cht->sequence) < cht->sequence_len) + return false; + + if (cht->chars_read < strlen(cht->sequence)) + { + // still reading characters from the cheat code + // and verifying. reset back to the beginning + // if a key is wrong + + if (key == cht->sequence[cht->chars_read]) + ++cht->chars_read; + else + cht->chars_read = 0; + + cht->param_chars_read = 0; + } + else if (cht->param_chars_read < cht->parameter_chars) + { + // we have passed the end of the cheat sequence and are + // entering parameters now + + cht->parameter_buf[cht->param_chars_read] = key; + + ++cht->param_chars_read; + } + + if (cht->chars_read >= strlen(cht->sequence) + && cht->param_chars_read >= cht->parameter_chars) + { + cht->chars_read = cht->param_chars_read = 0; + + return true; + } + + // cheat not matched yet + + return false; +} + +void +cht_GetParam +( cheatseq_t* cht, + char* buffer ) +{ + memcpy(buffer, cht->parameter_buf, cht->parameter_chars); +} + + diff --git a/firmware_p4/components/Applications/doom/m_cheat.h b/firmware_p4/components/Applications/doom/m_cheat.h new file mode 100644 index 000000000..6775e709e --- /dev/null +++ b/firmware_p4/components/Applications/doom/m_cheat.h @@ -0,0 +1,62 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Cheat code checking. +// + + +#ifndef __M_CHEAT__ +#define __M_CHEAT__ + +// +// CHEAT SEQUENCE PACKAGE +// + +// declaring a cheat + +#define CHEAT(value, parameters) \ + { value, sizeof(value) - 1, parameters, 0, 0, "" } + +#define MAX_CHEAT_LEN 25 +#define MAX_CHEAT_PARAMS 5 + +typedef struct +{ + // settings for this cheat + + char sequence[MAX_CHEAT_LEN]; + size_t sequence_len; + int parameter_chars; + + // state used during the game + + size_t chars_read; + int param_chars_read; + char parameter_buf[MAX_CHEAT_PARAMS]; +} cheatseq_t; + +int +cht_CheckCheat +( cheatseq_t* cht, + char key ); + + +void +cht_GetParam +( cheatseq_t* cht, + char* buffer ); + + +#endif diff --git a/firmware_p4/components/Applications/doom/m_config.c b/firmware_p4/components/Applications/doom/m_config.c new file mode 100644 index 000000000..865e16842 --- /dev/null +++ b/firmware_p4/components/Applications/doom/m_config.c @@ -0,0 +1,2128 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Configuration file interface. +// + + +#include +#include +#include +#include +#include + +#include "config.h" + +#include "doomtype.h" +#include "doomkeys.h" +#include "doomfeatures.h" +#include "i_system.h" +#include "m_argv.h" +#include "m_misc.h" + +#include "z_zone.h" + +// +// DEFAULTS +// + +// Location where all configuration data is stored - +// default.cfg, savegames, etc. + +char *configdir; + +// Default filenames for configuration files. + +static char *default_main_config; +static char *default_extra_config; + +typedef enum +{ + DEFAULT_INT, + DEFAULT_INT_HEX, + DEFAULT_STRING, + DEFAULT_FLOAT, + DEFAULT_KEY, +} default_type_t; + +typedef struct +{ + // Name of the variable + char *name; + + // Pointer to the location in memory of the variable + void *location; + + // Type of the variable + default_type_t type; + + // If this is a key value, the original integer scancode we read from + // the config file before translating it to the internal key value. + // If zero, we didn't read this value from a config file. + int untranslated; + + // The value we translated the scancode into when we read the + // config file on startup. If the variable value is different from + // this, it has been changed and needs to be converted; otherwise, + // use the 'untranslated' value. + int original_translated; + + // If true, this config variable has been bound to a variable + // and is being used. + boolean bound; +} default_t; + +typedef struct +{ + default_t *defaults; + int numdefaults; + char *filename; +} default_collection_t; + +#define CONFIG_VARIABLE_GENERIC(name, type) \ + { #name, NULL, type, 0, 0, false } + +#define CONFIG_VARIABLE_KEY(name) \ + CONFIG_VARIABLE_GENERIC(name, DEFAULT_KEY) +#define CONFIG_VARIABLE_INT(name) \ + CONFIG_VARIABLE_GENERIC(name, DEFAULT_INT) +#define CONFIG_VARIABLE_INT_HEX(name) \ + CONFIG_VARIABLE_GENERIC(name, DEFAULT_INT_HEX) +#define CONFIG_VARIABLE_FLOAT(name) \ + CONFIG_VARIABLE_GENERIC(name, DEFAULT_FLOAT) +#define CONFIG_VARIABLE_STRING(name) \ + CONFIG_VARIABLE_GENERIC(name, DEFAULT_STRING) + +//! @begin_config_file default + +static default_t doom_defaults_list[] = +{ + //! + // Mouse sensitivity. This value is used to multiply input mouse + // movement to control the effect of moving the mouse. + // + // The "normal" maximum value available for this through the + // in-game options menu is 9. A value of 31 or greater will cause + // the game to crash when entering the options menu. + // + + CONFIG_VARIABLE_INT(mouse_sensitivity), + + //! + // Volume of sound effects, range 0-15. + // + + CONFIG_VARIABLE_INT(sfx_volume), + + //! + // Volume of in-game music, range 0-15. + // + + CONFIG_VARIABLE_INT(music_volume), + + //! + // @game strife + // + // If non-zero, dialogue text is displayed over characters' pictures + // when engaging actors who have voices. + // + + CONFIG_VARIABLE_INT(show_talk), + + //! + // @game strife + // + // Volume of voice sound effects, range 0-15. + // + + CONFIG_VARIABLE_INT(voice_volume), + + //! + // @game doom + // + // If non-zero, messages are displayed on the heads-up display + // in the game ("picked up a clip", etc). If zero, these messages + // are not displayed. + // + + CONFIG_VARIABLE_INT(show_messages), + + //! + // Keyboard key to turn right. + // + + CONFIG_VARIABLE_KEY(key_right), + + //! + // Keyboard key to turn left. + // + + CONFIG_VARIABLE_KEY(key_left), + + //! + // Keyboard key to move forward. + // + + CONFIG_VARIABLE_KEY(key_up), + + //! + // Keyboard key to move backward. + // + + CONFIG_VARIABLE_KEY(key_down), + + //! + // Keyboard key to strafe left. + // + + CONFIG_VARIABLE_KEY(key_strafeleft), + + //! + // Keyboard key to strafe right. + // + + CONFIG_VARIABLE_KEY(key_straferight), + + //! + // @game strife + // + // Keyboard key to use health. + // + + CONFIG_VARIABLE_KEY(key_useHealth), + + //! + // @game hexen + // + // Keyboard key to jump. + // + + CONFIG_VARIABLE_KEY(key_jump), + + //! + // @game heretic hexen + // + // Keyboard key to fly upward. + // + + CONFIG_VARIABLE_KEY(key_flyup), + + //! + // @game heretic hexen + // + // Keyboard key to fly downwards. + // + + CONFIG_VARIABLE_KEY(key_flydown), + + //! + // @game heretic hexen + // + // Keyboard key to center flying. + // + + CONFIG_VARIABLE_KEY(key_flycenter), + + //! + // @game heretic hexen + // + // Keyboard key to look up. + // + + CONFIG_VARIABLE_KEY(key_lookup), + + //! + // @game heretic hexen + // + // Keyboard key to look down. + // + + CONFIG_VARIABLE_KEY(key_lookdown), + + //! + // @game heretic hexen + // + // Keyboard key to center the view. + // + + CONFIG_VARIABLE_KEY(key_lookcenter), + + //! + // @game strife + // + // Keyboard key to query inventory. + // + + CONFIG_VARIABLE_KEY(key_invquery), + + //! + // @game strife + // + // Keyboard key to display mission objective. + // + + CONFIG_VARIABLE_KEY(key_mission), + + //! + // @game strife + // + // Keyboard key to display inventory popup. + // + + CONFIG_VARIABLE_KEY(key_invPop), + + //! + // @game strife + // + // Keyboard key to display keys popup. + // + + CONFIG_VARIABLE_KEY(key_invKey), + + //! + // @game strife + // + // Keyboard key to jump to start of inventory. + // + + CONFIG_VARIABLE_KEY(key_invHome), + + //! + // @game strife + // + // Keyboard key to jump to end of inventory. + // + + CONFIG_VARIABLE_KEY(key_invEnd), + + //! + // @game heretic hexen + // + // Keyboard key to scroll left in the inventory. + // + + CONFIG_VARIABLE_KEY(key_invleft), + + //! + // @game heretic hexen + // + // Keyboard key to scroll right in the inventory. + // + + CONFIG_VARIABLE_KEY(key_invright), + + //! + // @game strife + // + // Keyboard key to scroll left in the inventory. + // + + CONFIG_VARIABLE_KEY(key_invLeft), + + //! + // @game strife + // + // Keyboard key to scroll right in the inventory. + // + + CONFIG_VARIABLE_KEY(key_invRight), + + //! + // @game heretic hexen + // + // Keyboard key to use the current item in the inventory. + // + + CONFIG_VARIABLE_KEY(key_useartifact), + + //! + // @game strife + // + // Keyboard key to use inventory item. + // + + CONFIG_VARIABLE_KEY(key_invUse), + + //! + // @game strife + // + // Keyboard key to drop an inventory item. + // + + CONFIG_VARIABLE_KEY(key_invDrop), + + //! + // @game strife + // + // Keyboard key to look up. + // + + CONFIG_VARIABLE_KEY(key_lookUp), + + //! + // @game strife + // + // Keyboard key to look down. + // + + CONFIG_VARIABLE_KEY(key_lookDown), + + //! + // Keyboard key to fire the currently selected weapon. + // + + CONFIG_VARIABLE_KEY(key_fire), + + //! + // Keyboard key to "use" an object, eg. a door or switch. + // + + CONFIG_VARIABLE_KEY(key_use), + + //! + // Keyboard key to turn on strafing. When held down, pressing the + // key to turn left or right causes the player to strafe left or + // right instead. + // + + CONFIG_VARIABLE_KEY(key_strafe), + + //! + // Keyboard key to make the player run. + // + + CONFIG_VARIABLE_KEY(key_speed), + + //! + // If non-zero, mouse input is enabled. If zero, mouse input is + // disabled. + // + + CONFIG_VARIABLE_INT(use_mouse), + + //! + // Mouse button to fire the currently selected weapon. + // + + CONFIG_VARIABLE_INT(mouseb_fire), + + //! + // Mouse button to turn on strafing. When held down, the player + // will strafe left and right instead of turning left and right. + // + + CONFIG_VARIABLE_INT(mouseb_strafe), + + //! + // Mouse button to move forward. + // + + CONFIG_VARIABLE_INT(mouseb_forward), + + //! + // @game hexen strife + // + // Mouse button to jump. + // + + CONFIG_VARIABLE_INT(mouseb_jump), + + //! + // If non-zero, joystick input is enabled. + // + + CONFIG_VARIABLE_INT(use_joystick), + + //! + // Joystick virtual button that fires the current weapon. + // + + CONFIG_VARIABLE_INT(joyb_fire), + + //! + // Joystick virtual button that makes the player strafe while + // held down. + // + + CONFIG_VARIABLE_INT(joyb_strafe), + + //! + // Joystick virtual button to "use" an object, eg. a door or switch. + // + + CONFIG_VARIABLE_INT(joyb_use), + + //! + // Joystick virtual button that makes the player run while held + // down. + // + // If this has a value of 20 or greater, the player will always run, + // even if use_joystick is 0. + // + + CONFIG_VARIABLE_INT(joyb_speed), + + //! + // @game hexen strife + // + // Joystick virtual button that makes the player jump. + // + + CONFIG_VARIABLE_INT(joyb_jump), + + //! + // @game doom heretic hexen + // + // Screen size, range 3-11. + // + // A value of 11 gives a full-screen view with the status bar not + // displayed. A value of 10 gives a full-screen view with the + // status bar displayed. + // + + CONFIG_VARIABLE_INT(screenblocks), + + //! + // @game strife + // + // Screen size, range 3-11. + // + // A value of 11 gives a full-screen view with the status bar not + // displayed. A value of 10 gives a full-screen view with the + // status bar displayed. + // + + CONFIG_VARIABLE_INT(screensize), + + //! + // @game doom + // + // Screen detail. Zero gives normal "high detail" mode, while + // a non-zero value gives "low detail" mode. + // + + CONFIG_VARIABLE_INT(detaillevel), + + //! + // Number of sounds that will be played simultaneously. + // + + CONFIG_VARIABLE_INT(snd_channels), + + //! + // Music output device. A non-zero value gives MIDI sound output, + // while a value of zero disables music. + // + + CONFIG_VARIABLE_INT(snd_musicdevice), + + //! + // Sound effects device. A value of zero disables in-game sound + // effects, a value of 1 enables PC speaker sound effects, while + // a value in the range 2-9 enables the "normal" digital sound + // effects. + // + + CONFIG_VARIABLE_INT(snd_sfxdevice), + + //! + // SoundBlaster I/O port. Unused. + // + + CONFIG_VARIABLE_INT(snd_sbport), + + //! + // SoundBlaster IRQ. Unused. + // + + CONFIG_VARIABLE_INT(snd_sbirq), + + //! + // SoundBlaster DMA channel. Unused. + // + + CONFIG_VARIABLE_INT(snd_sbdma), + + //! + // Output port to use for OPL MIDI playback. Unused. + // + + CONFIG_VARIABLE_INT(snd_mport), + + //! + // Gamma correction level. A value of zero disables gamma + // correction, while a value in the range 1-4 gives increasing + // levels of gamma correction. + // + + CONFIG_VARIABLE_INT(usegamma), + + //! + // @game hexen + // + // Directory in which to store savegames. + // + + CONFIG_VARIABLE_STRING(savedir), + + //! + // @game hexen + // + // Controls whether messages are displayed in the heads-up display. + // If this has a non-zero value, messages are displayed. + // + + CONFIG_VARIABLE_INT(messageson), + + //! + // @game strife + // + // Name of background flat used by view border. + // + + CONFIG_VARIABLE_STRING(back_flat), + + //! + // @game strife + // + // Multiplayer nickname (?). + // + + CONFIG_VARIABLE_STRING(nickname), + + //! + // Multiplayer chat macro: message to send when alt+0 is pressed. + // + + CONFIG_VARIABLE_STRING(chatmacro0), + + //! + // Multiplayer chat macro: message to send when alt+1 is pressed. + // + + CONFIG_VARIABLE_STRING(chatmacro1), + + //! + // Multiplayer chat macro: message to send when alt+2 is pressed. + // + + CONFIG_VARIABLE_STRING(chatmacro2), + + //! + // Multiplayer chat macro: message to send when alt+3 is pressed. + // + + CONFIG_VARIABLE_STRING(chatmacro3), + + //! + // Multiplayer chat macro: message to send when alt+4 is pressed. + // + + CONFIG_VARIABLE_STRING(chatmacro4), + + //! + // Multiplayer chat macro: message to send when alt+5 is pressed. + // + + CONFIG_VARIABLE_STRING(chatmacro5), + + //! + // Multiplayer chat macro: message to send when alt+6 is pressed. + // + + CONFIG_VARIABLE_STRING(chatmacro6), + + //! + // Multiplayer chat macro: message to send when alt+7 is pressed. + // + + CONFIG_VARIABLE_STRING(chatmacro7), + + //! + // Multiplayer chat macro: message to send when alt+8 is pressed. + // + + CONFIG_VARIABLE_STRING(chatmacro8), + + //! + // Multiplayer chat macro: message to send when alt+9 is pressed. + // + + CONFIG_VARIABLE_STRING(chatmacro9), + + //! + // @game strife + // + // Serial port number to use for SERSETUP.EXE (unused). + // + + CONFIG_VARIABLE_INT(comport), +}; + +static default_collection_t doom_defaults = +{ + doom_defaults_list, + arrlen(doom_defaults_list), + NULL, +}; + +//! @begin_config_file extended + +static default_t extra_defaults_list[] = +{ + //! + // @game heretic hexen strife + // + // If non-zero, display the graphical startup screen. + // + + CONFIG_VARIABLE_INT(graphical_startup), + + //! + // If non-zero, video settings will be autoadjusted to a valid + // configuration when the screen_width and screen_height variables + // do not match any valid configuration. + // + + CONFIG_VARIABLE_INT(autoadjust_video_settings), + + //! + // If non-zero, the game will run in full screen mode. If zero, + // the game will run in a window. + // + + CONFIG_VARIABLE_INT(fullscreen), + + //! + // If non-zero, the screen will be stretched vertically to display + // correctly on a square pixel video mode. + // + + CONFIG_VARIABLE_INT(aspect_ratio_correct), + + //! + // Number of milliseconds to wait on startup after the video mode + // has been set, before the game will start. This allows the + // screen to settle on some monitors that do not display an image + // for a brief interval after changing video modes. + // + + CONFIG_VARIABLE_INT(startup_delay), + + //! + // Screen width in pixels. If running in full screen mode, this is + // the X dimension of the video mode to use. If running in + // windowed mode, this is the width of the window in which the game + // will run. + // + + CONFIG_VARIABLE_INT(screen_width), + + //! + // Screen height in pixels. If running in full screen mode, this is + // the Y dimension of the video mode to use. If running in + // windowed mode, this is the height of the window in which the game + // will run. + // + + CONFIG_VARIABLE_INT(screen_height), + + //! + // Color depth of the screen, in bits. + // If this is set to zero, the color depth will be automatically set + // on startup to the machine's default/native color depth. + // + + CONFIG_VARIABLE_INT(screen_bpp), + + //! + // If this is non-zero, the mouse will be "grabbed" when running + // in windowed mode so that it can be used as an input device. + // When running full screen, this has no effect. + // + + CONFIG_VARIABLE_INT(grabmouse), + + //! + // If non-zero, all vertical mouse movement is ignored. This + // emulates the behavior of the "novert" tool available under DOS + // that performs the same function. + // + + CONFIG_VARIABLE_INT(novert), + + //! + // Mouse acceleration factor. When the speed of mouse movement + // exceeds the threshold value (mouse_threshold), the speed is + // multiplied by this value. + // + + CONFIG_VARIABLE_FLOAT(mouse_acceleration), + + //! + // Mouse acceleration threshold. When the speed of mouse movement + // exceeds this threshold value, the speed is multiplied by an + // acceleration factor (mouse_acceleration). + // + + CONFIG_VARIABLE_INT(mouse_threshold), + + //! + // Sound output sample rate, in Hz. Typical values to use are + // 11025, 22050, 44100 and 48000. + // + + CONFIG_VARIABLE_INT(snd_samplerate), + + //! + // Maximum number of bytes to allocate for caching converted sound + // effects in memory. If set to zero, there is no limit applied. + // + + CONFIG_VARIABLE_INT(snd_cachesize), + + //! + // Maximum size of the output sound buffer size in milliseconds. + // Sound output is generated periodically in slices. Higher values + // might be more efficient but will introduce latency to the + // sound output. The default is 28ms (one slice per tic with the + // 35fps timer). + + CONFIG_VARIABLE_INT(snd_maxslicetime_ms), + + //! + // External command to invoke to perform MIDI playback. If set to + // the empty string, SDL_mixer's internal MIDI playback is used. + // This only has any effect when snd_musicdevice is set to General + // MIDI output. + + CONFIG_VARIABLE_STRING(snd_musiccmd), + + //! + // The I/O port to use to access the OPL chip. Only relevant when + // using native OPL music playback. + // + + CONFIG_VARIABLE_INT_HEX(opl_io_port), + + //! + // @game doom heretic strife + // + // If non-zero, the ENDOOM text screen is displayed when exiting the + // game. If zero, the ENDOOM screen is not displayed. + // + + CONFIG_VARIABLE_INT(show_endoom), + + //! + // If non-zero, save screenshots in PNG format. + // + + CONFIG_VARIABLE_INT(png_screenshots), + + //! + // @game doom strife + // + // If non-zero, the Vanilla savegame limit is enforced; if the + // savegame exceeds 180224 bytes in size, the game will exit with + // an error. If this has a value of zero, there is no limit to + // the size of savegames. + // + + CONFIG_VARIABLE_INT(vanilla_savegame_limit), + + //! + // @game doom strife + // + // If non-zero, the Vanilla demo size limit is enforced; the game + // exits with an error when a demo exceeds the demo size limit + // (128KiB by default). If this has a value of zero, there is no + // limit to the size of demos. + // + + CONFIG_VARIABLE_INT(vanilla_demo_limit), + + //! + // If non-zero, the game behaves like Vanilla Doom, always assuming + // an American keyboard mapping. If this has a value of zero, the + // native keyboard mapping of the keyboard is used. + // + + CONFIG_VARIABLE_INT(vanilla_keyboard_mapping), + + //! + // Name of the SDL video driver to use. If this is an empty string, + // the default video driver is used. + // + + CONFIG_VARIABLE_STRING(video_driver), + + //! + // Position of the window on the screen when running in windowed + // mode. Accepted values are: "" (empty string) - don't care, + // "center" - place window at center of screen, "x,y" - place + // window at the specified coordinates. + + CONFIG_VARIABLE_STRING(window_position), + +#ifdef FEATURE_MULTIPLAYER + + //! + // Name to use in network games for identification. This is only + // used on the "waiting" screen while waiting for the game to start. + // + + CONFIG_VARIABLE_STRING(player_name), + +#endif + + //! + // Joystick number to use; '0' is the first joystick. A negative + // value ('-1') indicates that no joystick is configured. + // + + CONFIG_VARIABLE_INT(joystick_index), + + //! + // Joystick axis to use to for horizontal (X) movement. + // + + CONFIG_VARIABLE_INT(joystick_x_axis), + + //! + // If non-zero, movement on the horizontal joystick axis is inverted. + // + + CONFIG_VARIABLE_INT(joystick_x_invert), + + //! + // Joystick axis to use to for vertical (Y) movement. + // + + CONFIG_VARIABLE_INT(joystick_y_axis), + + //! + // If non-zero, movement on the vertical joystick axis is inverted. + // + + CONFIG_VARIABLE_INT(joystick_y_invert), + + //! + // Joystick axis to use to for strafing movement. + // + + CONFIG_VARIABLE_INT(joystick_strafe_axis), + + //! + // If non-zero, movement on the joystick axis used for strafing + // is inverted. + // + + CONFIG_VARIABLE_INT(joystick_strafe_invert), + + //! + // The physical joystick button that corresponds to joystick + // virtual button #0. + // + + CONFIG_VARIABLE_INT(joystick_physical_button0), + + //! + // The physical joystick button that corresponds to joystick + // virtual button #1. + // + + CONFIG_VARIABLE_INT(joystick_physical_button1), + + //! + // The physical joystick button that corresponds to joystick + // virtual button #2. + // + + CONFIG_VARIABLE_INT(joystick_physical_button2), + + //! + // The physical joystick button that corresponds to joystick + // virtual button #3. + // + + CONFIG_VARIABLE_INT(joystick_physical_button3), + + //! + // The physical joystick button that corresponds to joystick + // virtual button #4. + // + + CONFIG_VARIABLE_INT(joystick_physical_button4), + + //! + // The physical joystick button that corresponds to joystick + // virtual button #5. + // + + CONFIG_VARIABLE_INT(joystick_physical_button5), + + //! + // The physical joystick button that corresponds to joystick + // virtual button #6. + // + + CONFIG_VARIABLE_INT(joystick_physical_button6), + + //! + // The physical joystick button that corresponds to joystick + // virtual button #7. + // + + CONFIG_VARIABLE_INT(joystick_physical_button7), + + //! + // The physical joystick button that corresponds to joystick + // virtual button #8. + // + + CONFIG_VARIABLE_INT(joystick_physical_button8), + + //! + // The physical joystick button that corresponds to joystick + // virtual button #9. + // + + CONFIG_VARIABLE_INT(joystick_physical_button9), + + //! + // Joystick virtual button to make the player strafe left. + // + + CONFIG_VARIABLE_INT(joyb_strafeleft), + + //! + // Joystick virtual button to make the player strafe right. + // + + CONFIG_VARIABLE_INT(joyb_straferight), + + //! + // Joystick virtual button to activate the menu. + // + + CONFIG_VARIABLE_INT(joyb_menu_activate), + + //! + // Joystick virtual button that cycles to the previous weapon. + // + + CONFIG_VARIABLE_INT(joyb_prevweapon), + + //! + // Joystick virtual button that cycles to the next weapon. + // + + CONFIG_VARIABLE_INT(joyb_nextweapon), + + //! + // Mouse button to strafe left. + // + + CONFIG_VARIABLE_INT(mouseb_strafeleft), + + //! + // Mouse button to strafe right. + // + + CONFIG_VARIABLE_INT(mouseb_straferight), + + //! + // Mouse button to "use" an object, eg. a door or switch. + // + + CONFIG_VARIABLE_INT(mouseb_use), + + //! + // Mouse button to move backwards. + // + + CONFIG_VARIABLE_INT(mouseb_backward), + + //! + // Mouse button to cycle to the previous weapon. + // + + CONFIG_VARIABLE_INT(mouseb_prevweapon), + + //! + // Mouse button to cycle to the next weapon. + // + + CONFIG_VARIABLE_INT(mouseb_nextweapon), + + //! + // If non-zero, double-clicking a mouse button acts like pressing + // the "use" key to use an object in-game, eg. a door or switch. + // + + CONFIG_VARIABLE_INT(dclick_use), + +#ifdef FEATURE_SOUND + + //! + // Controls whether libsamplerate support is used for performing + // sample rate conversions of sound effects. Support for this + // must be compiled into the program. + // + // If zero, libsamplerate support is disabled. If non-zero, + // libsamplerate is enabled. Increasing values roughly correspond + // to higher quality conversion; the higher the quality, the + // slower the conversion process. Linear conversion = 1; + // Zero order hold = 2; Fast Sinc filter = 3; Medium quality + // Sinc filter = 4; High quality Sinc filter = 5. + // + + CONFIG_VARIABLE_INT(use_libsamplerate), + + //! + // Scaling factor used by libsamplerate. This is used when converting + // sounds internally back into integer form; normally it should not + // be necessary to change it from the default value. The only time + // it might be needed is if a PWAD file is loaded that contains very + // loud sounds, in which case the conversion may cause sound clipping + // and the scale factor should be reduced. The lower the value, the + // quieter the sound effects become, so it should be set as high as is + // possible without clipping occurring. + + CONFIG_VARIABLE_FLOAT(libsamplerate_scale), + + //! + // Full path to a Timidity configuration file to use for MIDI + // playback. The file will be evaluated from the directory where + // it is evaluated, so there is no need to add "dir" commands + // into it. + // + + CONFIG_VARIABLE_STRING(timidity_cfg_path), + + //! + // Path to GUS patch files to use when operating in GUS emulation + // mode. + // + + CONFIG_VARIABLE_STRING(gus_patch_path), + + //! + // Number of kilobytes of RAM to use in GUS emulation mode. Valid + // values are 256, 512, 768 or 1024. + // + + CONFIG_VARIABLE_INT(gus_ram_kb), + +#endif + + //! + // Key to pause or unpause the game. + // + + CONFIG_VARIABLE_KEY(key_pause), + + //! + // Key that activates the menu when pressed. + // + + CONFIG_VARIABLE_KEY(key_menu_activate), + + //! + // Key that moves the cursor up on the menu. + // + + CONFIG_VARIABLE_KEY(key_menu_up), + + //! + // Key that moves the cursor down on the menu. + // + + CONFIG_VARIABLE_KEY(key_menu_down), + + //! + // Key that moves the currently selected slider on the menu left. + // + + CONFIG_VARIABLE_KEY(key_menu_left), + + //! + // Key that moves the currently selected slider on the menu right. + // + + CONFIG_VARIABLE_KEY(key_menu_right), + + //! + // Key to go back to the previous menu. + // + + CONFIG_VARIABLE_KEY(key_menu_back), + + //! + // Key to activate the currently selected menu item. + // + + CONFIG_VARIABLE_KEY(key_menu_forward), + + //! + // Key to answer 'yes' to a question in the menu. + // + + CONFIG_VARIABLE_KEY(key_menu_confirm), + + //! + // Key to answer 'no' to a question in the menu. + // + + CONFIG_VARIABLE_KEY(key_menu_abort), + + //! + // Keyboard shortcut to bring up the help screen. + // + + CONFIG_VARIABLE_KEY(key_menu_help), + + //! + // Keyboard shortcut to bring up the save game menu. + // + + CONFIG_VARIABLE_KEY(key_menu_save), + + //! + // Keyboard shortcut to bring up the load game menu. + // + + CONFIG_VARIABLE_KEY(key_menu_load), + + //! + // Keyboard shortcut to bring up the sound volume menu. + // + + CONFIG_VARIABLE_KEY(key_menu_volume), + + //! + // Keyboard shortcut to toggle the detail level. + // + + CONFIG_VARIABLE_KEY(key_menu_detail), + + //! + // Keyboard shortcut to quicksave the current game. + // + + CONFIG_VARIABLE_KEY(key_menu_qsave), + + //! + // Keyboard shortcut to end the game. + // + + CONFIG_VARIABLE_KEY(key_menu_endgame), + + //! + // Keyboard shortcut to toggle heads-up messages. + // + + CONFIG_VARIABLE_KEY(key_menu_messages), + + //! + // Keyboard shortcut to load the last quicksave. + // + + CONFIG_VARIABLE_KEY(key_menu_qload), + + //! + // Keyboard shortcut to quit the game. + // + + CONFIG_VARIABLE_KEY(key_menu_quit), + + //! + // Keyboard shortcut to toggle the gamma correction level. + // + + CONFIG_VARIABLE_KEY(key_menu_gamma), + + //! + // Keyboard shortcut to switch view in multiplayer. + // + + CONFIG_VARIABLE_KEY(key_spy), + + //! + // Keyboard shortcut to increase the screen size. + // + + CONFIG_VARIABLE_KEY(key_menu_incscreen), + + //! + // Keyboard shortcut to decrease the screen size. + // + + CONFIG_VARIABLE_KEY(key_menu_decscreen), + + //! + // Keyboard shortcut to save a screenshot. + // + + CONFIG_VARIABLE_KEY(key_menu_screenshot), + + //! + // Key to toggle the map view. + // + + CONFIG_VARIABLE_KEY(key_map_toggle), + + //! + // Key to pan north when in the map view. + // + + CONFIG_VARIABLE_KEY(key_map_north), + + //! + // Key to pan south when in the map view. + // + + CONFIG_VARIABLE_KEY(key_map_south), + + //! + // Key to pan east when in the map view. + // + + CONFIG_VARIABLE_KEY(key_map_east), + + //! + // Key to pan west when in the map view. + // + + CONFIG_VARIABLE_KEY(key_map_west), + + //! + // Key to zoom in when in the map view. + // + + CONFIG_VARIABLE_KEY(key_map_zoomin), + + //! + // Key to zoom out when in the map view. + // + + CONFIG_VARIABLE_KEY(key_map_zoomout), + + //! + // Key to zoom out the maximum amount when in the map view. + // + + CONFIG_VARIABLE_KEY(key_map_maxzoom), + + //! + // Key to toggle follow mode when in the map view. + // + + CONFIG_VARIABLE_KEY(key_map_follow), + + //! + // Key to toggle the grid display when in the map view. + // + + CONFIG_VARIABLE_KEY(key_map_grid), + + //! + // Key to set a mark when in the map view. + // + + CONFIG_VARIABLE_KEY(key_map_mark), + + //! + // Key to clear all marks when in the map view. + // + + CONFIG_VARIABLE_KEY(key_map_clearmark), + + //! + // Key to select weapon 1. + // + + CONFIG_VARIABLE_KEY(key_weapon1), + + //! + // Key to select weapon 2. + // + + CONFIG_VARIABLE_KEY(key_weapon2), + + //! + // Key to select weapon 3. + // + + CONFIG_VARIABLE_KEY(key_weapon3), + + //! + // Key to select weapon 4. + // + + CONFIG_VARIABLE_KEY(key_weapon4), + + //! + // Key to select weapon 5. + // + + CONFIG_VARIABLE_KEY(key_weapon5), + + //! + // Key to select weapon 6. + // + + CONFIG_VARIABLE_KEY(key_weapon6), + + //! + // Key to select weapon 7. + // + + CONFIG_VARIABLE_KEY(key_weapon7), + + //! + // Key to select weapon 8. + // + + CONFIG_VARIABLE_KEY(key_weapon8), + + //! + // Key to cycle to the previous weapon. + // + + CONFIG_VARIABLE_KEY(key_prevweapon), + + //! + // Key to cycle to the next weapon. + // + + CONFIG_VARIABLE_KEY(key_nextweapon), + + //! + // @game hexen + // + // Key to use one of each artifact. + // + + CONFIG_VARIABLE_KEY(key_arti_all), + + //! + // @game hexen + // + // Key to use "quartz flask" artifact. + // + + CONFIG_VARIABLE_KEY(key_arti_health), + + //! + // @game hexen + // + // Key to use "flechette" artifact. + // + + CONFIG_VARIABLE_KEY(key_arti_poisonbag), + + //! + // @game hexen + // + // Key to use "disc of repulsion" artifact. + // + + CONFIG_VARIABLE_KEY(key_arti_blastradius), + + //! + // @game hexen + // + // Key to use "chaos device" artifact. + // + + CONFIG_VARIABLE_KEY(key_arti_teleport), + + //! + // @game hexen + // + // Key to use "banishment device" artifact. + // + + CONFIG_VARIABLE_KEY(key_arti_teleportother), + + //! + // @game hexen + // + // Key to use "porkalator" artifact. + // + + CONFIG_VARIABLE_KEY(key_arti_egg), + + //! + // @game hexen + // + // Key to use "icon of the defender" artifact. + // + + CONFIG_VARIABLE_KEY(key_arti_invulnerability), + + //! + // Key to re-display last message. + // + + CONFIG_VARIABLE_KEY(key_message_refresh), + + //! + // Key to quit the game when recording a demo. + // + + CONFIG_VARIABLE_KEY(key_demo_quit), + + //! + // Key to send a message during multiplayer games. + // + + CONFIG_VARIABLE_KEY(key_multi_msg), + + //! + // Key to send a message to player 1 during multiplayer games. + // + + CONFIG_VARIABLE_KEY(key_multi_msgplayer1), + + //! + // Key to send a message to player 2 during multiplayer games. + // + + CONFIG_VARIABLE_KEY(key_multi_msgplayer2), + + //! + // Key to send a message to player 3 during multiplayer games. + // + + CONFIG_VARIABLE_KEY(key_multi_msgplayer3), + + //! + // Key to send a message to player 4 during multiplayer games. + // + + CONFIG_VARIABLE_KEY(key_multi_msgplayer4), + + //! + // @game hexen strife + // + // Key to send a message to player 5 during multiplayer games. + // + + CONFIG_VARIABLE_KEY(key_multi_msgplayer5), + + //! + // @game hexen strife + // + // Key to send a message to player 6 during multiplayer games. + // + + CONFIG_VARIABLE_KEY(key_multi_msgplayer6), + + //! + // @game hexen strife + // + // Key to send a message to player 7 during multiplayer games. + // + + CONFIG_VARIABLE_KEY(key_multi_msgplayer7), + + //! + // @game hexen strife + // + // Key to send a message to player 8 during multiplayer games. + // + + CONFIG_VARIABLE_KEY(key_multi_msgplayer8), +}; + +static default_collection_t extra_defaults = +{ + extra_defaults_list, + arrlen(extra_defaults_list), + NULL, +}; + +// Search a collection for a variable + +static default_t *SearchCollection(default_collection_t *collection, char *name) +{ + int i; + + for (i=0; inumdefaults; ++i) + { + if (!strcmp(name, collection->defaults[i].name)) + { + return &collection->defaults[i]; + } + } + + return NULL; +} + +// Mapping from DOS keyboard scan code to internal key code (as defined +// in doomkey.h). I think I (fraggle) reused this from somewhere else +// but I can't find where. Anyway, notes: +// * KEY_PAUSE is wrong - it's in the KEY_NUMLOCK spot. This shouldn't +// matter in terms of Vanilla compatibility because neither of +// those were valid for key bindings. +// * There is no proper scan code for PrintScreen (on DOS machines it +// sends an interrupt). So I added a fake scan code of 126 for it. +// The presence of this is important so we can bind PrintScreen as +// a screenshot key. +static const int scantokey[128] = +{ + 0 , 27, '1', '2', '3', '4', '5', '6', + '7', '8', '9', '0', '-', '=', KEY_BACKSPACE, 9, + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', + 'o', 'p', '[', ']', 13, KEY_RCTRL, 'a', 's', + 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', + '\'', '`', KEY_RSHIFT,'\\', 'z', 'x', 'c', 'v', + 'b', 'n', 'm', ',', '.', '/', KEY_RSHIFT,KEYP_MULTIPLY, + KEY_RALT, ' ', KEY_CAPSLOCK,KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, + KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, /*KEY_NUMLOCK?*/KEY_PAUSE,KEY_SCRLCK,KEY_HOME, + KEY_UPARROW,KEY_PGUP,KEY_MINUS,KEY_LEFTARROW,KEYP_5,KEY_RIGHTARROW,KEYP_PLUS,KEY_END, + KEY_DOWNARROW,KEY_PGDN,KEY_INS,KEY_DEL,0, 0, 0, KEY_F11, + KEY_F12, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, KEY_PRTSCR, 0 +}; + + +static void SaveDefaultCollection(default_collection_t *collection) +{ +#if ORIGCODE + default_t *defaults; + int i, v; + FILE *f; + + f = fopen (collection->filename, "w"); + if (!f) + return; // can't write the file, but don't complain + + defaults = collection->defaults; + + for (i=0 ; inumdefaults ; i++) + { + int chars_written; + + // Ignore unbound variables + + if (!defaults[i].bound) + { + continue; + } + + // Print the name and line up all values at 30 characters + + chars_written = fprintf(f, "%s ", defaults[i].name); + + for (; chars_written < 30; ++chars_written) + fprintf(f, " "); + + // Print the value + + switch (defaults[i].type) + { + case DEFAULT_KEY: + + // use the untranslated version if we can, to reduce + // the possibility of screwing up the user's config + // file + + v = * (int *) defaults[i].location; + + if (v == KEY_RSHIFT) + { + // Special case: for shift, force scan code for + // right shift, as this is what Vanilla uses. + // This overrides the change check below, to fix + // configuration files made by old versions that + // mistakenly used the scan code for left shift. + + v = 54; + } + else if (defaults[i].untranslated + && v == defaults[i].original_translated) + { + // Has not been changed since the last time we + // read the config file. + + v = defaults[i].untranslated; + } + else + { + // search for a reverse mapping back to a scancode + // in the scantokey table + + int s; + + for (s=0; s<128; ++s) + { + if (scantokey[s] == v) + { + v = s; + break; + } + } + } + + fprintf(f, "%i", v); + break; + + case DEFAULT_INT: + fprintf(f, "%i", * (int *) defaults[i].location); + break; + + case DEFAULT_INT_HEX: + fprintf(f, "0x%x", * (int *) defaults[i].location); + break; + + case DEFAULT_FLOAT: + fprintf(f, "%f", * (float *) defaults[i].location); + break; + + case DEFAULT_STRING: + fprintf(f,"\"%s\"", * (char **) (defaults[i].location)); + break; + } + + fprintf(f, "\n"); + } + + fclose (f); +#endif +} + +// Parses integer values in the configuration file + +static int ParseIntParameter(char *strparm) +{ + int parm; + + if (strparm[0] == '0' && strparm[1] == 'x') + sscanf(strparm+2, "%x", &parm); + else + sscanf(strparm, "%i", &parm); + + return parm; +} + +static void SetVariable(default_t *def, char *value) +{ + int intparm; + + // parameter found + + switch (def->type) + { + case DEFAULT_STRING: + * (char **) def->location = strdup(value); + break; + + case DEFAULT_INT: + case DEFAULT_INT_HEX: + * (int *) def->location = ParseIntParameter(value); + break; + + case DEFAULT_KEY: + + // translate scancodes read from config + // file (save the old value in untranslated) + + intparm = ParseIntParameter(value); + def->untranslated = intparm; + if (intparm >= 0 && intparm < 128) + { + intparm = scantokey[intparm]; + } + else + { + intparm = 0; + } + + def->original_translated = intparm; + * (int *) def->location = intparm; + break; + + case DEFAULT_FLOAT: + * (float *) def->location = (float) atof(value); + break; + } +} + +static void LoadDefaultCollection(default_collection_t *collection) +{ +#if ORIGCODE + FILE *f; + default_t *def; + char defname[80]; + char strparm[100]; + + // read the file in, overriding any set defaults + f = fopen(collection->filename, "r"); + + if (f == NULL) + { + // File not opened, but don't complain. + // It's probably just the first time they ran the game. + + return; + } + + while (!feof(f)) + { + if (fscanf(f, "%79s %99[^\n]\n", defname, strparm) != 2) + { + // This line doesn't match + + continue; + } + + // Find the setting in the list + + def = SearchCollection(collection, defname); + + if (def == NULL || !def->bound) + { + // Unknown variable? Unbound variables are also treated + // as unknown. + + continue; + } + + // Strip off trailing non-printable characters (\r characters + // from DOS text files) + + while (strlen(strparm) > 0 && !isprint(strparm[strlen(strparm)-1])) + { + strparm[strlen(strparm)-1] = '\0'; + } + + // Surrounded by quotes? If so, remove them. + if (strlen(strparm) >= 2 + && strparm[0] == '"' && strparm[strlen(strparm) - 1] == '"') + { + strparm[strlen(strparm) - 1] = '\0'; + memmove(strparm, strparm + 1, sizeof(strparm) - 1); + } + + SetVariable(def, strparm); + } + + fclose (f); +#endif +} + +// Set the default filenames to use for configuration files. + +void M_SetConfigFilenames(char *main_config, char *extra_config) +{ + default_main_config = main_config; + default_extra_config = extra_config; +} + +// +// M_SaveDefaults +// + +void M_SaveDefaults (void) +{ + SaveDefaultCollection(&doom_defaults); + SaveDefaultCollection(&extra_defaults); +} + +// +// Save defaults to alternate filenames +// + +void M_SaveDefaultsAlternate(char *main, char *extra) +{ + char *orig_main; + char *orig_extra; + + // Temporarily change the filenames + + orig_main = doom_defaults.filename; + orig_extra = extra_defaults.filename; + + doom_defaults.filename = main; + extra_defaults.filename = extra; + + M_SaveDefaults(); + + // Restore normal filenames + + doom_defaults.filename = orig_main; + extra_defaults.filename = orig_extra; +} + +// +// M_LoadDefaults +// + +void M_LoadDefaults (void) +{ + int i; + + // check for a custom default file + + //! + // @arg + // @vanilla + // + // Load main configuration from the specified file, instead of the + // default. + // + + i = M_CheckParmWithArgs("-config", 1); + + if (i) + { + doom_defaults.filename = myargv[i+1]; + printf (" default file: %s\n",doom_defaults.filename); + } + else + { + doom_defaults.filename + = M_StringJoin(configdir, default_main_config, NULL); + } + + printf("saving config in %s\n", doom_defaults.filename); + + //! + // @arg + // + // Load additional configuration from the specified file, instead of + // the default. + // + + i = M_CheckParmWithArgs("-extraconfig", 1); + + if (i) + { + extra_defaults.filename = myargv[i+1]; + printf(" extra configuration file: %s\n", + extra_defaults.filename); + } + else + { + extra_defaults.filename + = M_StringJoin(configdir, default_extra_config, NULL); + } + + LoadDefaultCollection(&doom_defaults); + LoadDefaultCollection(&extra_defaults); +} + +// Get a configuration file variable by its name + +static default_t *GetDefaultForName(char *name) +{ + default_t *result; + + // Try the main list and the extras + + result = SearchCollection(&doom_defaults, name); + + if (result == NULL) + { + result = SearchCollection(&extra_defaults, name); + } + + // Not found? Internal error. + + if (result == NULL) + { + I_Error("Unknown configuration variable: '%s'", name); + } + + return result; +} + +// +// Bind a variable to a given configuration file variable, by name. +// + +void M_BindVariable(char *name, void *location) +{ + default_t *variable; + + variable = GetDefaultForName(name); + + variable->location = location; + variable->bound = true; +} + +// Set the value of a particular variable; an API function for other +// parts of the program to assign values to config variables by name. + +boolean M_SetVariable(char *name, char *value) +{ + default_t *variable; + + variable = GetDefaultForName(name); + + if (variable == NULL || !variable->bound) + { + return false; + } + + SetVariable(variable, value); + + return true; +} + +// Get the value of a variable. + +int M_GetIntVariable(char *name) +{ + default_t *variable; + + variable = GetDefaultForName(name); + + if (variable == NULL || !variable->bound + || (variable->type != DEFAULT_INT && variable->type != DEFAULT_INT_HEX)) + { + return 0; + } + + return *((int *) variable->location); +} + +const char *M_GetStrVariable(char *name) +{ + default_t *variable; + + variable = GetDefaultForName(name); + + if (variable == NULL || !variable->bound + || variable->type != DEFAULT_STRING) + { + return NULL; + } + + return *((const char **) variable->location); +} + +float M_GetFloatVariable(char *name) +{ + default_t *variable; + + variable = GetDefaultForName(name); + + if (variable == NULL || !variable->bound + || variable->type != DEFAULT_FLOAT) + { + return 0; + } + + return *((float *) variable->location); +} + +// Get the path to the default configuration dir to use, if NULL +// is passed to M_SetConfigDir. + +static char *GetDefaultConfigDir(void) +{ + char *result = (char *)malloc(2); + result[0] = '.'; + result[1] = '\0'; + + return result; +} + +// +// SetConfigDir: +// +// Sets the location of the configuration directory, where configuration +// files are stored - default.cfg, chocolate-doom.cfg, savegames, etc. +// + +void M_SetConfigDir(char *dir) +{ + // Use the directory that was passed, or find the default. + + if (dir != NULL) + { + configdir = dir; + } + else + { + configdir = GetDefaultConfigDir(); + } + + if (strcmp(configdir, "") != 0) + { + printf("Using %s for configuration and saves\n", configdir); + } + + // Make the directory if it doesn't already exist: + + M_MakeDirectory(configdir); +} + +// +// Calculate the path to the directory to use to store save games. +// Creates the directory as necessary. +// + +char *M_GetSaveGameDir(char *iwadname) +{ + char *savegamedir; +#if ORIGCODE + char *topdir; +#endif + + // If not "doing" a configuration directory (Windows), don't "do" + // a savegame directory, either. + + if (!strcmp(configdir, "")) + { + savegamedir = strdup(""); + } + else + { +#if ORIGCODE + // ~/.chocolate-doom/savegames + + topdir = M_StringJoin(configdir, "savegame", NULL); + M_MakeDirectory(topdir); + + // eg. ~/.chocolate-doom/savegames/doom2.wad/ + + savegamedir = M_StringJoin(topdir, DIR_SEPARATOR_S, iwadname, + DIR_SEPARATOR_S, NULL); + + M_MakeDirectory(savegamedir); + + free(topdir); +#else + savegamedir = M_StringJoin(configdir, DIR_SEPARATOR_S, ".savegame/", NULL); + + M_MakeDirectory(savegamedir); + + printf ("Using %s for savegames\n", savegamedir); +#endif + } + + return savegamedir; +} + diff --git a/firmware_p4/components/Applications/doom/m_config.h b/firmware_p4/components/Applications/doom/m_config.h new file mode 100644 index 000000000..a17de2b45 --- /dev/null +++ b/firmware_p4/components/Applications/doom/m_config.h @@ -0,0 +1,39 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Configuration file interface. +// + + +#ifndef __M_CONFIG__ +#define __M_CONFIG__ + +#include "doomtype.h" + +void M_LoadDefaults(void); +void M_SaveDefaults(void); +void M_SaveDefaultsAlternate(char *main, char *extra); +void M_SetConfigDir(char *dir); +void M_BindVariable(char *name, void *variable); +boolean M_SetVariable(char *name, char *value); +int M_GetIntVariable(char *name); +const char *M_GetStrVariable(char *name); +float M_GetFloatVariable(char *name); +void M_SetConfigFilenames(char *main_config, char *extra_config); +char *M_GetSaveGameDir(char *iwadname); + +extern char *configdir; + +#endif diff --git a/firmware_p4/components/Applications/doom/m_controls.c b/firmware_p4/components/Applications/doom/m_controls.c new file mode 100644 index 000000000..f0ffa0157 --- /dev/null +++ b/firmware_p4/components/Applications/doom/m_controls.c @@ -0,0 +1,398 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// + +#include + +#include "doomtype.h" +#include "doomkeys.h" + +#include "m_config.h" +#include "m_misc.h" + +// +// Keyboard controls +// + +int key_right = KEY_RIGHTARROW; +int key_left = KEY_LEFTARROW; +int key_up = KEY_UPARROW; +int key_down = KEY_DOWNARROW; +int key_strafeleft = KEY_STRAFE_L; +int key_straferight = KEY_STRAFE_R; +int key_fire = KEY_FIRE; +int key_use = KEY_USE; +int key_strafe = KEY_RALT; +int key_speed = KEY_RSHIFT; + +// +// Heretic keyboard controls +// + +int key_flyup = KEY_PGUP; +int key_flydown = KEY_INS; +int key_flycenter = KEY_HOME; + +int key_lookup = KEY_PGDN; +int key_lookdown = KEY_DEL; +int key_lookcenter = KEY_END; + +int key_invleft = '['; +int key_invright = ']'; +int key_useartifact = KEY_ENTER; + +// +// Hexen key controls +// + +int key_jump = '/'; + +int key_arti_all = KEY_BACKSPACE; +int key_arti_health = '\\'; +int key_arti_poisonbag = '0'; +int key_arti_blastradius = '9'; +int key_arti_teleport = '8'; +int key_arti_teleportother = '7'; +int key_arti_egg = '6'; +int key_arti_invulnerability = '5'; + +// +// Strife key controls +// +// haleyjd 09/01/10 +// + +// Note: Strife also uses key_invleft, key_invright, key_jump, key_lookup, and +// key_lookdown, but with different default values. + +int key_usehealth = 'h'; +int key_invquery = 'q'; +int key_mission = 'w'; +int key_invpop = 'z'; +int key_invkey = 'k'; +int key_invhome = KEY_HOME; +int key_invend = KEY_END; +int key_invuse = KEY_ENTER; +int key_invdrop = KEY_BACKSPACE; + + +// +// Mouse controls +// + +int mousebfire = 0; +int mousebstrafe = 1; +int mousebforward = 2; + +int mousebjump = -1; + +int mousebstrafeleft = -1; +int mousebstraferight = -1; +int mousebbackward = -1; +int mousebuse = -1; + +int mousebprevweapon = -1; +int mousebnextweapon = -1; + + +int key_message_refresh = KEY_ENTER; +int key_pause = KEY_PAUSE; +int key_demo_quit = 'q'; +int key_spy = KEY_F12; + +// Multiplayer chat keys: + +int key_multi_msg = 't'; +int key_multi_msgplayer[8]; + +// Weapon selection keys: + +int key_weapon1 = '1'; +int key_weapon2 = '2'; +int key_weapon3 = '3'; +int key_weapon4 = '4'; +int key_weapon5 = '5'; +int key_weapon6 = '6'; +int key_weapon7 = '7'; +int key_weapon8 = '8'; +int key_prevweapon = 0; +int key_nextweapon = 0; + +// Map control keys: + +int key_map_north = KEY_UPARROW; +int key_map_south = KEY_DOWNARROW; +int key_map_east = KEY_RIGHTARROW; +int key_map_west = KEY_LEFTARROW; +int key_map_zoomin = '='; +int key_map_zoomout = '-'; +int key_map_toggle = KEY_TAB; +int key_map_maxzoom = '0'; +int key_map_follow = 'f'; +int key_map_grid = 'g'; +int key_map_mark = 'm'; +int key_map_clearmark = 'c'; + +// menu keys: + +int key_menu_activate = KEY_ESCAPE; +int key_menu_up = KEY_UPARROW; +int key_menu_down = KEY_DOWNARROW; +int key_menu_left = KEY_LEFTARROW; +int key_menu_right = KEY_RIGHTARROW; +int key_menu_back = KEY_BACKSPACE; +int key_menu_forward = KEY_ENTER; +int key_menu_confirm = 'y'; +int key_menu_abort = 'n'; + +int key_menu_help = KEY_F1; +int key_menu_save = KEY_F2; +int key_menu_load = KEY_F3; +int key_menu_volume = KEY_F4; +int key_menu_detail = KEY_F5; +int key_menu_qsave = KEY_F6; +int key_menu_endgame = KEY_F7; +int key_menu_messages = KEY_F8; +int key_menu_qload = KEY_F9; +int key_menu_quit = KEY_F10; +int key_menu_gamma = KEY_F11; + +int key_menu_incscreen = KEY_EQUALS; +int key_menu_decscreen = KEY_MINUS; +int key_menu_screenshot = 0; + +// +// Joystick controls +// + +int joybfire = 0; +int joybstrafe = 1; +int joybuse = 3; +int joybspeed = 2; + +int joybstrafeleft = -1; +int joybstraferight = -1; + +int joybjump = -1; + +int joybprevweapon = -1; +int joybnextweapon = -1; + +int joybmenu = -1; + +// Control whether if a mouse button is double clicked, it acts like +// "use" has been pressed + +int dclick_use = 1; + +// +// Bind all of the common controls used by Doom and all other games. +// + +void M_BindBaseControls(void) +{ + M_BindVariable("key_right", &key_right); + M_BindVariable("key_left", &key_left); + M_BindVariable("key_up", &key_up); + M_BindVariable("key_down", &key_down); + M_BindVariable("key_strafeleft", &key_strafeleft); + M_BindVariable("key_straferight", &key_straferight); + M_BindVariable("key_fire", &key_fire); + M_BindVariable("key_use", &key_use); + M_BindVariable("key_strafe", &key_strafe); + M_BindVariable("key_speed", &key_speed); + + M_BindVariable("mouseb_fire", &mousebfire); + M_BindVariable("mouseb_strafe", &mousebstrafe); + M_BindVariable("mouseb_forward", &mousebforward); + + M_BindVariable("joyb_fire", &joybfire); + M_BindVariable("joyb_strafe", &joybstrafe); + M_BindVariable("joyb_use", &joybuse); + M_BindVariable("joyb_speed", &joybspeed); + + M_BindVariable("joyb_menu_activate", &joybmenu); + + // Extra controls that are not in the Vanilla versions: + + M_BindVariable("joyb_strafeleft", &joybstrafeleft); + M_BindVariable("joyb_straferight", &joybstraferight); + M_BindVariable("mouseb_strafeleft", &mousebstrafeleft); + M_BindVariable("mouseb_straferight", &mousebstraferight); + M_BindVariable("mouseb_use", &mousebuse); + M_BindVariable("mouseb_backward", &mousebbackward); + M_BindVariable("dclick_use", &dclick_use); + M_BindVariable("key_pause", &key_pause); + M_BindVariable("key_message_refresh", &key_message_refresh); +} + +void M_BindHereticControls(void) +{ + M_BindVariable("key_flyup", &key_flyup); + M_BindVariable("key_flydown", &key_flydown); + M_BindVariable("key_flycenter", &key_flycenter); + + M_BindVariable("key_lookup", &key_lookup); + M_BindVariable("key_lookdown", &key_lookdown); + M_BindVariable("key_lookcenter", &key_lookcenter); + + M_BindVariable("key_invleft", &key_invleft); + M_BindVariable("key_invright", &key_invright); + M_BindVariable("key_useartifact", &key_useartifact); +} + +void M_BindHexenControls(void) +{ + M_BindVariable("key_jump", &key_jump); + M_BindVariable("mouseb_jump", &mousebjump); + M_BindVariable("joyb_jump", &joybjump); + + M_BindVariable("key_arti_all", &key_arti_all); + M_BindVariable("key_arti_health", &key_arti_health); + M_BindVariable("key_arti_poisonbag", &key_arti_poisonbag); + M_BindVariable("key_arti_blastradius", &key_arti_blastradius); + M_BindVariable("key_arti_teleport", &key_arti_teleport); + M_BindVariable("key_arti_teleportother", &key_arti_teleportother); + M_BindVariable("key_arti_egg", &key_arti_egg); + M_BindVariable("key_arti_invulnerability", &key_arti_invulnerability); +} + +void M_BindStrifeControls(void) +{ + // These are shared with all games, but have different defaults: + key_message_refresh = '/'; + + // These keys are shared with Heretic/Hexen but have different defaults: + key_jump = 'a'; + key_lookup = KEY_PGUP; + key_lookdown = KEY_PGDN; + key_invleft = KEY_INS; + key_invright = KEY_DEL; + + M_BindVariable("key_jump", &key_jump); + M_BindVariable("key_lookUp", &key_lookup); + M_BindVariable("key_lookDown", &key_lookdown); + M_BindVariable("key_invLeft", &key_invleft); + M_BindVariable("key_invRight", &key_invright); + + // Custom Strife-only Keys: + M_BindVariable("key_useHealth", &key_usehealth); + M_BindVariable("key_invquery", &key_invquery); + M_BindVariable("key_mission", &key_mission); + M_BindVariable("key_invPop", &key_invpop); + M_BindVariable("key_invKey", &key_invkey); + M_BindVariable("key_invHome", &key_invhome); + M_BindVariable("key_invEnd", &key_invend); + M_BindVariable("key_invUse", &key_invuse); + M_BindVariable("key_invDrop", &key_invdrop); + + // Strife also supports jump on mouse and joystick, and in the exact same + // manner as Hexen! + M_BindVariable("mouseb_jump", &mousebjump); + M_BindVariable("joyb_jump", &joybjump); +} + +void M_BindWeaponControls(void) +{ + M_BindVariable("key_weapon1", &key_weapon1); + M_BindVariable("key_weapon2", &key_weapon2); + M_BindVariable("key_weapon3", &key_weapon3); + M_BindVariable("key_weapon4", &key_weapon4); + M_BindVariable("key_weapon5", &key_weapon5); + M_BindVariable("key_weapon6", &key_weapon6); + M_BindVariable("key_weapon7", &key_weapon7); + M_BindVariable("key_weapon8", &key_weapon8); + + M_BindVariable("key_prevweapon", &key_prevweapon); + M_BindVariable("key_nextweapon", &key_nextweapon); + + M_BindVariable("joyb_prevweapon", &joybprevweapon); + M_BindVariable("joyb_nextweapon", &joybnextweapon); + + M_BindVariable("mouseb_prevweapon", &mousebprevweapon); + M_BindVariable("mouseb_nextweapon", &mousebnextweapon); +} + +void M_BindMapControls(void) +{ + M_BindVariable("key_map_north", &key_map_north); + M_BindVariable("key_map_south", &key_map_south); + M_BindVariable("key_map_east", &key_map_east); + M_BindVariable("key_map_west", &key_map_west); + M_BindVariable("key_map_zoomin", &key_map_zoomin); + M_BindVariable("key_map_zoomout", &key_map_zoomout); + M_BindVariable("key_map_toggle", &key_map_toggle); + M_BindVariable("key_map_maxzoom", &key_map_maxzoom); + M_BindVariable("key_map_follow", &key_map_follow); + M_BindVariable("key_map_grid", &key_map_grid); + M_BindVariable("key_map_mark", &key_map_mark); + M_BindVariable("key_map_clearmark", &key_map_clearmark); +} + +void M_BindMenuControls(void) +{ + M_BindVariable("key_menu_activate", &key_menu_activate); + M_BindVariable("key_menu_up", &key_menu_up); + M_BindVariable("key_menu_down", &key_menu_down); + M_BindVariable("key_menu_left", &key_menu_left); + M_BindVariable("key_menu_right", &key_menu_right); + M_BindVariable("key_menu_back", &key_menu_back); + M_BindVariable("key_menu_forward", &key_menu_forward); + M_BindVariable("key_menu_confirm", &key_menu_confirm); + M_BindVariable("key_menu_abort", &key_menu_abort); + + M_BindVariable("key_menu_help", &key_menu_help); + M_BindVariable("key_menu_save", &key_menu_save); + M_BindVariable("key_menu_load", &key_menu_load); + M_BindVariable("key_menu_volume", &key_menu_volume); + M_BindVariable("key_menu_detail", &key_menu_detail); + M_BindVariable("key_menu_qsave", &key_menu_qsave); + M_BindVariable("key_menu_endgame", &key_menu_endgame); + M_BindVariable("key_menu_messages", &key_menu_messages); + M_BindVariable("key_menu_qload", &key_menu_qload); + M_BindVariable("key_menu_quit", &key_menu_quit); + M_BindVariable("key_menu_gamma", &key_menu_gamma); + + M_BindVariable("key_menu_incscreen", &key_menu_incscreen); + M_BindVariable("key_menu_decscreen", &key_menu_decscreen); + M_BindVariable("key_menu_screenshot",&key_menu_screenshot); + M_BindVariable("key_demo_quit", &key_demo_quit); + M_BindVariable("key_spy", &key_spy); +} + +void M_BindChatControls(unsigned int num_players) +{ + char name[32]; // haleyjd: 20 not large enough - Thank you, come again! + unsigned int i; // haleyjd: signedness conflict + + M_BindVariable("key_multi_msg", &key_multi_msg); + + for (i=0; i> FRACBITS; +} + + + +// +// FixedDiv, C version. +// + +fixed_t FixedDiv(fixed_t a, fixed_t b) +{ + if ((abs(a) >> 14) >= abs(b)) + { + return (a^b) < 0 ? INT_MIN : INT_MAX; + } + else + { + int64_t result; + + result = ((int64_t) a << 16) / b; + + return (fixed_t) result; + } +} + diff --git a/firmware_p4/components/Applications/doom/m_fixed.h b/firmware_p4/components/Applications/doom/m_fixed.h new file mode 100644 index 000000000..733b2901c --- /dev/null +++ b/firmware_p4/components/Applications/doom/m_fixed.h @@ -0,0 +1,39 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Fixed point arithemtics, implementation. +// + + +#ifndef __M_FIXED__ +#define __M_FIXED__ + + + + +// +// Fixed point, 32bit as 16.16. +// +#define FRACBITS 16 +#define FRACUNIT (1< +#include + + +#include "doomdef.h" +#include "doomkeys.h" +#include "dstrings.h" + +#include "d_main.h" +#include "deh_main.h" + +#include "i_swap.h" +#include "i_system.h" +#include "i_timer.h" +#include "i_video.h" +#include "m_misc.h" +#include "v_video.h" +#include "w_wad.h" +#include "z_zone.h" + +#include "r_local.h" + + +#include "hu_stuff.h" + +#include "g_game.h" + +#include "m_argv.h" +#include "m_controls.h" +#include "p_saveg.h" + +#include "s_sound.h" + +#include "doomstat.h" + +// Data. +#include "sounds.h" + +#include "m_menu.h" + + +extern patch_t* hu_font[HU_FONTSIZE]; +extern boolean message_dontfuckwithme; + +extern boolean chat_on; // in heads-up code + +// +// defaulted values +// +int mouseSensitivity = 5; + +// Show messages has default, 0 = off, 1 = on +int showMessages = 1; + + +// Blocky mode, has default, 0 = high, 1 = normal +int detailLevel = 0; +int screenblocks = 10; + +// temp for screenblocks (0-9) +int screenSize; + +// -1 = no quicksave slot picked! +int quickSaveSlot; + + // 1 = message to be printed +int messageToPrint; +// ...and here is the message string! +char* messageString; + +// message x & y +int messx; +int messy; +int messageLastMenuActive; + +// timed message = no input from user +boolean messageNeedsInput; + +void (*messageRoutine)(int response); + +char gammamsg[5][26] = +{ + GAMMALVL0, + GAMMALVL1, + GAMMALVL2, + GAMMALVL3, + GAMMALVL4 +}; + +// we are going to be entering a savegame string +int saveStringEnter; +int saveSlot; // which slot to save in +int saveCharIndex; // which char we're editing +// old save description before edit +char saveOldString[SAVESTRINGSIZE]; + +boolean inhelpscreens; +boolean menuactive; + +#define SKULLXOFF -32 +#define LINEHEIGHT 16 + +extern boolean sendpause; +char savegamestrings[10][SAVESTRINGSIZE]; + +char endstring[160]; + +//static boolean opldev; + +// +// MENU TYPEDEFS +// +typedef struct +{ + // 0 = no cursor here, 1 = ok, 2 = arrows ok + short status; + + char name[10]; + + // choice = menu item #. + // if status = 2, + // choice=0:leftarrow,1:rightarrow + void (*routine)(int choice); + + // hotkey in menu + char alphaKey; +} menuitem_t; + + + +typedef struct menu_s +{ + short numitems; // # of menu items + struct menu_s* prevMenu; // previous menu + menuitem_t* menuitems; // menu items + void (*routine)(); // draw routine + short x; + short y; // x,y of menu + short lastOn; // last item user was on in menu +} menu_t; + +short itemOn; // menu item skull is on +short skullAnimCounter; // skull animation counter +short whichSkull; // which skull to draw + +// graphic name of skulls +// warning: initializer-string for array of chars is too long +char *skullName[2] = {"M_SKULL1","M_SKULL2"}; + +// current menudef +menu_t* currentMenu; + +// +// PROTOTYPES +// +void M_NewGame(int choice); +void M_Episode(int choice); +void M_ChooseSkill(int choice); +void M_LoadGame(int choice); +void M_SaveGame(int choice); +void M_Options(int choice); +void M_EndGame(int choice); +void M_ReadThis(int choice); +void M_ReadThis2(int choice); +void M_QuitDOOM(int choice); + +void M_ChangeMessages(int choice); +void M_ChangeSensitivity(int choice); +void M_SfxVol(int choice); +void M_MusicVol(int choice); +void M_ChangeDetail(int choice); +void M_SizeDisplay(int choice); +void M_StartGame(int choice); +void M_Sound(int choice); + +void M_FinishReadThis(int choice); +void M_LoadSelect(int choice); +void M_SaveSelect(int choice); +void M_ReadSaveStrings(void); +void M_QuickSave(void); +void M_QuickLoad(void); + +void M_DrawMainMenu(void); +void M_DrawReadThis1(void); +void M_DrawReadThis2(void); +void M_DrawNewGame(void); +void M_DrawEpisode(void); +void M_DrawOptions(void); +void M_DrawSound(void); +void M_DrawLoad(void); +void M_DrawSave(void); + +void M_DrawSaveLoadBorder(int x,int y); +void M_SetupNextMenu(menu_t *menudef); +void M_DrawThermo(int x,int y,int thermWidth,int thermDot); +void M_DrawEmptyCell(menu_t *menu,int item); +void M_DrawSelCell(menu_t *menu,int item); +void M_WriteText(int x, int y, char *string); +int M_StringWidth(char *string); +int M_StringHeight(char *string); +void M_StartMessage(char *string,void *routine,boolean input); +void M_StopMessage(void); +void M_ClearMenus (void); + + + + +// +// DOOM MENU +// +enum +{ + newgame = 0, + options, + loadgame, + savegame, + readthis, + quitdoom, + main_end +} main_e; + +menuitem_t MainMenu[]= +{ + {1,"M_NGAME",M_NewGame,'n'}, + {1,"M_OPTION",M_Options,'o'}, + {1,"M_LOADG",M_LoadGame,'l'}, + {1,"M_SAVEG",M_SaveGame,'s'}, + // Another hickup with Special edition. + {1,"M_RDTHIS",M_ReadThis,'r'}, + {1,"M_QUITG",M_QuitDOOM,'q'} +}; + +menu_t MainDef = +{ + main_end, + NULL, + MainMenu, + M_DrawMainMenu, + 97,64, + 0 +}; + + +// +// EPISODE SELECT +// +enum +{ + ep1, + ep2, + ep3, + ep4, + ep_end +} episodes_e; + +menuitem_t EpisodeMenu[]= +{ + {1,"M_EPI1", M_Episode,'k'}, + {1,"M_EPI2", M_Episode,'t'}, + {1,"M_EPI3", M_Episode,'i'}, + {1,"M_EPI4", M_Episode,'t'} +}; + +menu_t EpiDef = +{ + ep_end, // # of menu items + &MainDef, // previous menu + EpisodeMenu, // menuitem_t -> + M_DrawEpisode, // drawing routine -> + 48,63, // x,y + ep1 // lastOn +}; + +// +// NEW GAME +// +enum +{ + killthings, + toorough, + hurtme, + violence, + nightmare, + newg_end +} newgame_e; + +menuitem_t NewGameMenu[]= +{ + {1,"M_JKILL", M_ChooseSkill, 'i'}, + {1,"M_ROUGH", M_ChooseSkill, 'h'}, + {1,"M_HURT", M_ChooseSkill, 'h'}, + {1,"M_ULTRA", M_ChooseSkill, 'u'}, + {1,"M_NMARE", M_ChooseSkill, 'n'} +}; + +menu_t NewDef = +{ + newg_end, // # of menu items + &EpiDef, // previous menu + NewGameMenu, // menuitem_t -> + M_DrawNewGame, // drawing routine -> + 48,63, // x,y + hurtme // lastOn +}; + + + +// +// OPTIONS MENU +// +enum +{ + endgame, + messages, + detail, + scrnsize, + option_empty1, + mousesens, + option_empty2, + soundvol, + opt_end +} options_e; + +menuitem_t OptionsMenu[]= +{ + {1,"M_ENDGAM", M_EndGame,'e'}, + {1,"M_MESSG", M_ChangeMessages,'m'}, + {1,"M_DETAIL", M_ChangeDetail,'g'}, + {2,"M_SCRNSZ", M_SizeDisplay,'s'}, + {-1,"",0,'\0'}, + {2,"M_MSENS", M_ChangeSensitivity,'m'}, + {-1,"",0,'\0'}, + {1,"M_SVOL", M_Sound,'s'} +}; + +menu_t OptionsDef = +{ + opt_end, + &MainDef, + OptionsMenu, + M_DrawOptions, + 60,37, + 0 +}; + +// +// Read This! MENU 1 & 2 +// +enum +{ + rdthsempty1, + read1_end +} read_e; + +menuitem_t ReadMenu1[] = +{ + {1,"",M_ReadThis2,0} +}; + +menu_t ReadDef1 = +{ + read1_end, + &MainDef, + ReadMenu1, + M_DrawReadThis1, + 280,185, + 0 +}; + +enum +{ + rdthsempty2, + read2_end +} read_e2; + +menuitem_t ReadMenu2[]= +{ + {1,"",M_FinishReadThis,0} +}; + +menu_t ReadDef2 = +{ + read2_end, + &ReadDef1, + ReadMenu2, + M_DrawReadThis2, + 330,175, + 0 +}; + +// +// SOUND VOLUME MENU +// +enum +{ + sfx_vol, + sfx_empty1, + music_vol, + sfx_empty2, + sound_end +} sound_e; + +menuitem_t SoundMenu[]= +{ + {2,"M_SFXVOL",M_SfxVol,'s'}, + {-1,"",0,'\0'}, + {2,"M_MUSVOL",M_MusicVol,'m'}, + {-1,"",0,'\0'} +}; + +menu_t SoundDef = +{ + sound_end, + &OptionsDef, + SoundMenu, + M_DrawSound, + 80,64, + 0 +}; + +// +// LOAD GAME MENU +// +enum +{ + load1, + load2, + load3, + load4, + load5, + load6, + load_end +} load_e; + +menuitem_t LoadMenu[]= +{ + {1,"", M_LoadSelect,'1'}, + {1,"", M_LoadSelect,'2'}, + {1,"", M_LoadSelect,'3'}, + {1,"", M_LoadSelect,'4'}, + {1,"", M_LoadSelect,'5'}, + {1,"", M_LoadSelect,'6'} +}; + +menu_t LoadDef = +{ + load_end, + &MainDef, + LoadMenu, + M_DrawLoad, + 80,54, + 0 +}; + +// +// SAVE GAME MENU +// +menuitem_t SaveMenu[]= +{ + {1,"", M_SaveSelect,'1'}, + {1,"", M_SaveSelect,'2'}, + {1,"", M_SaveSelect,'3'}, + {1,"", M_SaveSelect,'4'}, + {1,"", M_SaveSelect,'5'}, + {1,"", M_SaveSelect,'6'} +}; + +menu_t SaveDef = +{ + load_end, + &MainDef, + SaveMenu, + M_DrawSave, + 80,54, + 0 +}; + + +// +// M_ReadSaveStrings +// read the strings from the savegame files +// +void M_ReadSaveStrings(void) +{ + FILE *handle; + int i; + char name[256]; + + for (i = 0;i < load_end;i++) + { + M_StringCopy(name, P_SaveGameFile(i), sizeof(name)); + + handle = fopen(name, "rb"); + if (handle == NULL) + { + M_StringCopy(savegamestrings[i], EMPTYSTRING, SAVESTRINGSIZE); + LoadMenu[i].status = 0; + continue; + } + fread(&savegamestrings[i], 1, SAVESTRINGSIZE, handle); + fclose(handle); + LoadMenu[i].status = 1; + } +} + + +// +// M_LoadGame & Cie. +// +void M_DrawLoad(void) +{ + int i; + + V_DrawPatchDirect(72, 28, + W_CacheLumpName(DEH_String("M_LOADG"), PU_CACHE)); + + for (i = 0;i < load_end; i++) + { + M_DrawSaveLoadBorder(LoadDef.x,LoadDef.y+LINEHEIGHT*i); + M_WriteText(LoadDef.x,LoadDef.y+LINEHEIGHT*i,savegamestrings[i]); + } +} + + + +// +// Draw border for the savegame description +// +void M_DrawSaveLoadBorder(int x,int y) +{ + int i; + + V_DrawPatchDirect(x - 8, y + 7, + W_CacheLumpName(DEH_String("M_LSLEFT"), PU_CACHE)); + + for (i = 0;i < 24;i++) + { + V_DrawPatchDirect(x, y + 7, + W_CacheLumpName(DEH_String("M_LSCNTR"), PU_CACHE)); + x += 8; + } + + V_DrawPatchDirect(x, y + 7, + W_CacheLumpName(DEH_String("M_LSRGHT"), PU_CACHE)); +} + + + +// +// User wants to load this game +// +void M_LoadSelect(int choice) +{ + char name[256]; + + M_StringCopy(name, P_SaveGameFile(choice), sizeof(name)); + + G_LoadGame (name); + M_ClearMenus (); +} + +// +// Selected from DOOM menu +// +void M_LoadGame (int choice) +{ + if (netgame) + { + M_StartMessage(DEH_String(LOADNET),NULL,false); + return; + } + + M_SetupNextMenu(&LoadDef); + M_ReadSaveStrings(); +} + + +// +// M_SaveGame & Cie. +// +void M_DrawSave(void) +{ + int i; + + V_DrawPatchDirect(72, 28, W_CacheLumpName(DEH_String("M_SAVEG"), PU_CACHE)); + for (i = 0;i < load_end; i++) + { + M_DrawSaveLoadBorder(LoadDef.x,LoadDef.y+LINEHEIGHT*i); + M_WriteText(LoadDef.x,LoadDef.y+LINEHEIGHT*i,savegamestrings[i]); + } + + if (saveStringEnter) + { + i = M_StringWidth(savegamestrings[saveSlot]); + M_WriteText(LoadDef.x + i,LoadDef.y+LINEHEIGHT*saveSlot,"_"); + } +} + +// +// M_Responder calls this when user is finished +// +void M_DoSave(int slot) +{ + G_SaveGame (slot,savegamestrings[slot]); + M_ClearMenus (); + + // PICK QUICKSAVE SLOT YET? + if (quickSaveSlot == -2) + quickSaveSlot = slot; +} + +// +// User wants to save. Start string input for M_Responder +// +void M_SaveSelect(int choice) +{ + // we are going to be intercepting all chars + saveStringEnter = 1; + + saveSlot = choice; + M_StringCopy(saveOldString,savegamestrings[choice], SAVESTRINGSIZE); + if (!strcmp(savegamestrings[choice], EMPTYSTRING)) + savegamestrings[choice][0] = 0; + saveCharIndex = strlen(savegamestrings[choice]); +} + +// +// Selected from DOOM menu +// +void M_SaveGame (int choice) +{ + if (!usergame) + { + M_StartMessage(DEH_String(SAVEDEAD),NULL,false); + return; + } + + if (gamestate != GS_LEVEL) + return; + + M_SetupNextMenu(&SaveDef); + M_ReadSaveStrings(); +} + + + +// +// M_QuickSave +// +char tempstring[80]; + +void M_QuickSaveResponse(int key) +{ + if (key == key_menu_confirm) + { + M_DoSave(quickSaveSlot); + S_StartSound(NULL,sfx_swtchx); + } +} + +void M_QuickSave(void) +{ + if (!usergame) + { + S_StartSound(NULL,sfx_oof); + return; + } + + if (gamestate != GS_LEVEL) + return; + + if (quickSaveSlot < 0) + { + M_StartControlPanel(); + M_ReadSaveStrings(); + M_SetupNextMenu(&SaveDef); + quickSaveSlot = -2; // means to pick a slot now + return; + } + DEH_snprintf(tempstring, 80, QSPROMPT, savegamestrings[quickSaveSlot]); + M_StartMessage(tempstring,M_QuickSaveResponse,true); +} + + + +// +// M_QuickLoad +// +void M_QuickLoadResponse(int key) +{ + if (key == key_menu_confirm) + { + M_LoadSelect(quickSaveSlot); + S_StartSound(NULL,sfx_swtchx); + } +} + + +void M_QuickLoad(void) +{ + if (netgame) + { + M_StartMessage(DEH_String(QLOADNET),NULL,false); + return; + } + + if (quickSaveSlot < 0) + { + M_StartMessage(DEH_String(QSAVESPOT),NULL,false); + return; + } + DEH_snprintf(tempstring, 80, QLPROMPT, savegamestrings[quickSaveSlot]); + M_StartMessage(tempstring,M_QuickLoadResponse,true); +} + + + + +// +// Read This Menus +// Had a "quick hack to fix romero bug" +// +void M_DrawReadThis1(void) +{ + char *lumpname = "CREDIT"; + int skullx = 330, skully = 175; + + inhelpscreens = true; + + // Different versions of Doom 1.9 work differently + + switch (gameversion) + { + case exe_doom_1_666: + case exe_doom_1_7: + case exe_doom_1_8: + case exe_doom_1_9: + case exe_hacx: + + if (gamemode == commercial) + { + // Doom 2 + + lumpname = "HELP"; + + skullx = 330; + skully = 165; + } + else + { + // Doom 1 + // HELP2 is the first screen shown in Doom 1 + + lumpname = "HELP2"; + + skullx = 280; + skully = 185; + } + break; + + case exe_ultimate: + case exe_chex: + + // Ultimate Doom always displays "HELP1". + + // Chex Quest version also uses "HELP1", even though it is based + // on Final Doom. + + lumpname = "HELP1"; + + break; + + case exe_final: + case exe_final2: + + // Final Doom always displays "HELP". + + lumpname = "HELP"; + + break; + + default: + I_Error("Unhandled game version"); + break; + } + + lumpname = DEH_String(lumpname); + + V_DrawPatchDirect (0, 0, W_CacheLumpName(lumpname, PU_CACHE)); + + ReadDef1.x = skullx; + ReadDef1.y = skully; +} + + + +// +// Read This Menus - optional second page. +// +void M_DrawReadThis2(void) +{ + inhelpscreens = true; + + // We only ever draw the second page if this is + // gameversion == exe_doom_1_9 and gamemode == registered + + V_DrawPatchDirect(0, 0, W_CacheLumpName(DEH_String("HELP1"), PU_CACHE)); +} + + +// +// Change Sfx & Music volumes +// +void M_DrawSound(void) +{ + V_DrawPatchDirect (60, 38, W_CacheLumpName(DEH_String("M_SVOL"), PU_CACHE)); + + M_DrawThermo(SoundDef.x,SoundDef.y+LINEHEIGHT*(sfx_vol+1), + 16,sfxVolume); + + M_DrawThermo(SoundDef.x,SoundDef.y+LINEHEIGHT*(music_vol+1), + 16,musicVolume); +} + +void M_Sound(int choice) +{ + M_SetupNextMenu(&SoundDef); +} + +void M_SfxVol(int choice) +{ + switch(choice) + { + case 0: + if (sfxVolume) + sfxVolume--; + break; + case 1: + if (sfxVolume < 15) + sfxVolume++; + break; + } + + S_SetSfxVolume(sfxVolume * 8); +} + +void M_MusicVol(int choice) +{ + switch(choice) + { + case 0: + if (musicVolume) + musicVolume--; + break; + case 1: + if (musicVolume < 15) + musicVolume++; + break; + } + + S_SetMusicVolume(musicVolume * 8); +} + + + + +// +// M_DrawMainMenu +// +void M_DrawMainMenu(void) +{ + V_DrawPatchDirect(94, 2, + W_CacheLumpName(DEH_String("M_DOOM"), PU_CACHE)); +} + + + + +// +// M_NewGame +// +void M_DrawNewGame(void) +{ + V_DrawPatchDirect(96, 14, W_CacheLumpName(DEH_String("M_NEWG"), PU_CACHE)); + V_DrawPatchDirect(54, 38, W_CacheLumpName(DEH_String("M_SKILL"), PU_CACHE)); +} + +void M_NewGame(int choice) +{ + if (netgame && !demoplayback) + { + M_StartMessage(DEH_String(NEWGAME),NULL,false); + return; + } + + // Chex Quest disabled the episode select screen, as did Doom II. + + if (gamemode == commercial || gameversion == exe_chex) + M_SetupNextMenu(&NewDef); + else + M_SetupNextMenu(&EpiDef); +} + + +// +// M_Episode +// +int epi; + +void M_DrawEpisode(void) +{ + V_DrawPatchDirect(54, 38, W_CacheLumpName(DEH_String("M_EPISOD"), PU_CACHE)); +} + +void M_VerifyNightmare(int key) +{ + if (key != key_menu_confirm) + return; + + G_DeferedInitNew(nightmare,epi+1,1); + M_ClearMenus (); +} + +void M_ChooseSkill(int choice) +{ + if (choice == nightmare) + { + M_StartMessage(DEH_String(NIGHTMARE),M_VerifyNightmare,true); + return; + } + + G_DeferedInitNew(choice,epi+1,1); + M_ClearMenus (); +} + +void M_Episode(int choice) +{ + if ( (gamemode == shareware) + && choice) + { + M_StartMessage(DEH_String(SWSTRING),NULL,false); + M_SetupNextMenu(&ReadDef1); + return; + } + + // Yet another hack... + if ( (gamemode == registered) + && (choice > 2)) + { + fprintf( stderr, + "M_Episode: 4th episode requires UltimateDOOM\n"); + choice = 0; + } + + epi = choice; + M_SetupNextMenu(&NewDef); +} + + + +// +// M_Options +// +static char *detailNames[2] = {"M_GDHIGH","M_GDLOW"}; +static char *msgNames[2] = {"M_MSGOFF","M_MSGON"}; + +void M_DrawOptions(void) +{ + V_DrawPatchDirect(108, 15, W_CacheLumpName(DEH_String("M_OPTTTL"), + PU_CACHE)); + + V_DrawPatchDirect(OptionsDef.x + 175, OptionsDef.y + LINEHEIGHT * detail, + W_CacheLumpName(DEH_String(detailNames[detailLevel]), + PU_CACHE)); + + V_DrawPatchDirect(OptionsDef.x + 120, OptionsDef.y + LINEHEIGHT * messages, + W_CacheLumpName(DEH_String(msgNames[showMessages]), + PU_CACHE)); + + M_DrawThermo(OptionsDef.x, OptionsDef.y + LINEHEIGHT * (mousesens + 1), + 10, mouseSensitivity); + + M_DrawThermo(OptionsDef.x,OptionsDef.y+LINEHEIGHT*(scrnsize+1), + 9,screenSize); +} + +void M_Options(int choice) +{ + M_SetupNextMenu(&OptionsDef); +} + + + +// +// Toggle messages on/off +// +void M_ChangeMessages(int choice) +{ + // warning: unused parameter `int choice' + choice = 0; + showMessages = 1 - showMessages; + + if (!showMessages) + players[consoleplayer].message = DEH_String(MSGOFF); + else + players[consoleplayer].message = DEH_String(MSGON); + + message_dontfuckwithme = true; +} + + +// +// M_EndGame +// +void M_EndGameResponse(int key) +{ + if (key != key_menu_confirm) + return; + + currentMenu->lastOn = itemOn; + M_ClearMenus (); + D_StartTitle (); +} + +void M_EndGame(int choice) +{ + choice = 0; + if (!usergame) + { + S_StartSound(NULL,sfx_oof); + return; + } + + if (netgame) + { + M_StartMessage(DEH_String(NETEND),NULL,false); + return; + } + + M_StartMessage(DEH_String(ENDGAME),M_EndGameResponse,true); +} + + + + +// +// M_ReadThis +// +void M_ReadThis(int choice) +{ + choice = 0; + M_SetupNextMenu(&ReadDef1); +} + +void M_ReadThis2(int choice) +{ + // Doom 1.9 had two menus when playing Doom 1 + // All others had only one + + if (gameversion <= exe_doom_1_9 && gamemode != commercial) + { + choice = 0; + M_SetupNextMenu(&ReadDef2); + } + else + { + // Close the menu + + M_FinishReadThis(0); + } +} + +void M_FinishReadThis(int choice) +{ + choice = 0; + M_SetupNextMenu(&MainDef); +} + + + + +// +// M_QuitDOOM +// +int quitsounds[8] = +{ + sfx_pldeth, + sfx_dmpain, + sfx_popain, + sfx_slop, + sfx_telept, + sfx_posit1, + sfx_posit3, + sfx_sgtatk +}; + +int quitsounds2[8] = +{ + sfx_vilact, + sfx_getpow, + sfx_boscub, + sfx_slop, + sfx_skeswg, + sfx_kntdth, + sfx_bspact, + sfx_sgtatk +}; + + + +void M_QuitResponse(int key) +{ + if (key != key_menu_confirm) + return; + if (!netgame) + { + if (gamemode == commercial) + S_StartSound(NULL,quitsounds2[(gametic>>2)&7]); + else + S_StartSound(NULL,quitsounds[(gametic>>2)&7]); + I_WaitVBL(105); + } + I_Quit (); +} + + +static char *M_SelectEndMessage(void) +{ + char **endmsg; + + if (logical_gamemission == doom) + { + // Doom 1 + + endmsg = doom1_endmsg; + } + else + { + // Doom 2 + + endmsg = doom2_endmsg; + } + + return endmsg[gametic % NUM_QUITMESSAGES]; +} + + +void M_QuitDOOM(int choice) +{ + DEH_snprintf(endstring, sizeof(endstring), "%s\n\n" DOSY, + DEH_String(M_SelectEndMessage())); + + M_StartMessage(endstring,M_QuitResponse,true); +} + + + + +void M_ChangeSensitivity(int choice) +{ + switch(choice) + { + case 0: + if (mouseSensitivity) + mouseSensitivity--; + break; + case 1: + if (mouseSensitivity < 9) + mouseSensitivity++; + break; + } +} + + + + +void M_ChangeDetail(int choice) +{ + choice = 0; + detailLevel = 1 - detailLevel; + + R_SetViewSize (screenblocks, detailLevel); + + if (!detailLevel) + players[consoleplayer].message = DEH_String(DETAILHI); + else + players[consoleplayer].message = DEH_String(DETAILLO); +} + + + + +void M_SizeDisplay(int choice) +{ + switch(choice) + { + case 0: + if (screenSize > 0) + { + screenblocks--; + screenSize--; + } + break; + case 1: + if (screenSize < 8) + { + screenblocks++; + screenSize++; + } + break; + } + + + R_SetViewSize (screenblocks, detailLevel); +} + + + + +// +// Menu Functions +// +void +M_DrawThermo +( int x, + int y, + int thermWidth, + int thermDot ) +{ + int xx; + int i; + + xx = x; + V_DrawPatchDirect(xx, y, W_CacheLumpName(DEH_String("M_THERML"), PU_CACHE)); + xx += 8; + for (i=0;ix - 10, menu->y + item * LINEHEIGHT - 1, + W_CacheLumpName(DEH_String("M_CELL1"), PU_CACHE)); +} + +void +M_DrawSelCell +( menu_t* menu, + int item ) +{ + V_DrawPatchDirect(menu->x - 10, menu->y + item * LINEHEIGHT - 1, + W_CacheLumpName(DEH_String("M_CELL2"), PU_CACHE)); +} + + +void +M_StartMessage +( char* string, + void* routine, + boolean input ) +{ + messageLastMenuActive = menuactive; + messageToPrint = 1; + messageString = string; + messageRoutine = routine; + messageNeedsInput = input; + menuactive = true; + return; +} + + +void M_StopMessage(void) +{ + menuactive = messageLastMenuActive; + messageToPrint = 0; +} + + + +// +// Find string width from hu_font chars +// +int M_StringWidth(char* string) +{ + size_t i; + int w = 0; + int c; + + for (i = 0;i < strlen(string);i++) + { + c = toupper(string[i]) - HU_FONTSTART; + if (c < 0 || c >= HU_FONTSIZE) + w += 4; + else + w += SHORT (hu_font[c]->width); + } + + return w; +} + + + +// +// Find string height from hu_font chars +// +int M_StringHeight(char* string) +{ + size_t i; + int h; + int height = SHORT(hu_font[0]->height); + + h = height; + for (i = 0;i < strlen(string);i++) + if (string[i] == '\n') + h += height; + + return h; +} + + +// +// Write a string using the hu_font +// +void +M_WriteText +( int x, + int y, + char* string) +{ + int w; + char* ch; + int c; + int cx; + int cy; + + + ch = string; + cx = x; + cy = y; + + while(1) + { + c = *ch++; + if (!c) + break; + if (c == '\n') + { + cx = x; + cy += 12; + continue; + } + + c = toupper(c) - HU_FONTSTART; + if (c < 0 || c>= HU_FONTSIZE) + { + cx += 4; + continue; + } + + w = SHORT (hu_font[c]->width); + if (cx+w > SCREENWIDTH) + break; + V_DrawPatchDirect(cx, cy, hu_font[c]); + cx+=w; + } +} + +// These keys evaluate to a "null" key in Vanilla Doom that allows weird +// jumping in the menus. Preserve this behavior for accuracy. + +static boolean IsNullKey(int key) +{ + return key == KEY_PAUSE || key == KEY_CAPSLOCK + || key == KEY_SCRLCK || key == KEY_NUMLOCK; +} + +// +// CONTROL PANEL +// + +// +// M_Responder +// +boolean M_Responder (event_t* ev) +{ + int ch; + int key; + int i; + static int joywait = 0; + static int mousewait = 0; + static int mousey = 0; + static int lasty = 0; + static int mousex = 0; + static int lastx = 0; + + // In testcontrols mode, none of the function keys should do anything + // - the only key is escape to quit. + + if (testcontrols) + { + if (ev->type == ev_quit + || (ev->type == ev_keydown + && (ev->data1 == key_menu_activate || ev->data1 == key_menu_quit))) + { + I_Quit(); + return true; + } + + return false; + } + + // "close" button pressed on window? + if (ev->type == ev_quit) + { + // First click on close button = bring up quit confirm message. + // Second click on close button = confirm quit + + if (menuactive && messageToPrint && messageRoutine == M_QuitResponse) + { + M_QuitResponse(key_menu_confirm); + } + else + { + S_StartSound(NULL,sfx_swtchn); + M_QuitDOOM(0); + } + + return true; + } + + // key is the key pressed, ch is the actual character typed + + ch = 0; + key = -1; + + if (ev->type == ev_joystick && joywait < I_GetTime()) + { + if (ev->data3 < 0) + { + key = key_menu_up; + joywait = I_GetTime() + 5; + } + else if (ev->data3 > 0) + { + key = key_menu_down; + joywait = I_GetTime() + 5; + } + + if (ev->data2 < 0) + { + key = key_menu_left; + joywait = I_GetTime() + 2; + } + else if (ev->data2 > 0) + { + key = key_menu_right; + joywait = I_GetTime() + 2; + } + + if (ev->data1&1) + { + key = key_menu_forward; + joywait = I_GetTime() + 5; + } + if (ev->data1&2) + { + key = key_menu_back; + joywait = I_GetTime() + 5; + } + if (joybmenu >= 0 && (ev->data1 & (1 << joybmenu)) != 0) + { + key = key_menu_activate; + joywait = I_GetTime() + 5; + } + } + else + { + if (ev->type == ev_mouse && mousewait < I_GetTime()) + { + mousey += ev->data3; + if (mousey < lasty-30) + { + key = key_menu_down; + mousewait = I_GetTime() + 5; + mousey = lasty -= 30; + } + else if (mousey > lasty+30) + { + key = key_menu_up; + mousewait = I_GetTime() + 5; + mousey = lasty += 30; + } + + mousex += ev->data2; + if (mousex < lastx-30) + { + key = key_menu_left; + mousewait = I_GetTime() + 5; + mousex = lastx -= 30; + } + else if (mousex > lastx+30) + { + key = key_menu_right; + mousewait = I_GetTime() + 5; + mousex = lastx += 30; + } + + if (ev->data1&1) + { + key = key_menu_forward; + mousewait = I_GetTime() + 15; + } + + if (ev->data1&2) + { + key = key_menu_back; + mousewait = I_GetTime() + 15; + } + } + else + { + if (ev->type == ev_keydown) + { + key = ev->data1; + ch = ev->data2; + } + } + } + + if (key == -1) + return false; + + // Save Game string input + if (saveStringEnter) + { + switch(key) + { + case KEY_BACKSPACE: + if (saveCharIndex > 0) + { + saveCharIndex--; + savegamestrings[saveSlot][saveCharIndex] = 0; + } + break; + + case KEY_ESCAPE: + saveStringEnter = 0; + M_StringCopy(savegamestrings[saveSlot], saveOldString, + SAVESTRINGSIZE); + break; + + case KEY_ENTER: + saveStringEnter = 0; + if (savegamestrings[saveSlot][0]) + M_DoSave(saveSlot); + break; + + default: + // This is complicated. + // Vanilla has a bug where the shift key is ignored when entering + // a savegame name. If vanilla_keyboard_mapping is on, we want + // to emulate this bug by using 'data1'. But if it's turned off, + // it implies the user doesn't care about Vanilla emulation: just + // use the correct 'data2'. + + if (vanilla_keyboard_mapping) + { + ch = key; + } + + ch = toupper(ch); + + if (ch != ' ' + && (ch - HU_FONTSTART < 0 || ch - HU_FONTSTART >= HU_FONTSIZE)) + { + break; + } + + if (ch >= 32 && ch <= 127 && + saveCharIndex < SAVESTRINGSIZE-1 && + M_StringWidth(savegamestrings[saveSlot]) < + (SAVESTRINGSIZE-2)*8) + { + savegamestrings[saveSlot][saveCharIndex++] = ch; + savegamestrings[saveSlot][saveCharIndex] = 0; + } + break; + } + return true; + } + + // Take care of any messages that need input + if (messageToPrint) + { + if (messageNeedsInput) + { + if (key != ' ' && key != KEY_ESCAPE + && key != key_menu_confirm && key != key_menu_abort) + { + return false; + } + } + + menuactive = messageLastMenuActive; + messageToPrint = 0; + if (messageRoutine) + messageRoutine(key); + + menuactive = false; + S_StartSound(NULL,sfx_swtchx); + return true; + } + + if ((devparm && key == key_menu_help) || + (key != 0 && key == key_menu_screenshot)) + { + G_ScreenShot (); + return true; + } + + // F-Keys + if (!menuactive) + { + if (key == key_menu_decscreen) // Screen size down + { + if (automapactive || chat_on) + return false; + M_SizeDisplay(0); + S_StartSound(NULL,sfx_stnmov); + return true; + } + else if (key == key_menu_incscreen) // Screen size up + { + if (automapactive || chat_on) + return false; + M_SizeDisplay(1); + S_StartSound(NULL,sfx_stnmov); + return true; + } + else if (key == key_menu_help) // Help key + { + M_StartControlPanel (); + + if ( gamemode == retail ) + currentMenu = &ReadDef2; + else + currentMenu = &ReadDef1; + + itemOn = 0; + S_StartSound(NULL,sfx_swtchn); + return true; + } + else if (key == key_menu_save) // Save + { + M_StartControlPanel(); + S_StartSound(NULL,sfx_swtchn); + M_SaveGame(0); + return true; + } + else if (key == key_menu_load) // Load + { + M_StartControlPanel(); + S_StartSound(NULL,sfx_swtchn); + M_LoadGame(0); + return true; + } + else if (key == key_menu_volume) // Sound Volume + { + M_StartControlPanel (); + currentMenu = &SoundDef; + itemOn = sfx_vol; + S_StartSound(NULL,sfx_swtchn); + return true; + } + else if (key == key_menu_detail) // Detail toggle + { + M_ChangeDetail(0); + S_StartSound(NULL,sfx_swtchn); + return true; + } + else if (key == key_menu_qsave) // Quicksave + { + S_StartSound(NULL,sfx_swtchn); + M_QuickSave(); + return true; + } + else if (key == key_menu_endgame) // End game + { + S_StartSound(NULL,sfx_swtchn); + M_EndGame(0); + return true; + } + else if (key == key_menu_messages) // Toggle messages + { + M_ChangeMessages(0); + S_StartSound(NULL,sfx_swtchn); + return true; + } + else if (key == key_menu_qload) // Quickload + { + S_StartSound(NULL,sfx_swtchn); + M_QuickLoad(); + return true; + } + else if (key == key_menu_quit) // Quit DOOM + { + S_StartSound(NULL,sfx_swtchn); + M_QuitDOOM(0); + return true; + } + else if (key == key_menu_gamma) // gamma toggle + { + usegamma++; + if (usegamma > 4) + usegamma = 0; + players[consoleplayer].message = DEH_String(gammamsg[usegamma]); + I_SetPalette (W_CacheLumpName (DEH_String("PLAYPAL"),PU_CACHE)); + return true; + } + } + + // Pop-up menu? + if (!menuactive) + { + if (key == key_menu_activate) + { + M_StartControlPanel (); + S_StartSound(NULL,sfx_swtchn); + return true; + } + return false; + } + + // Keys usable within menu + + if (key == key_menu_down) + { + // Move down to next item + + do + { + if (itemOn+1 > currentMenu->numitems-1) + itemOn = 0; + else itemOn++; + S_StartSound(NULL,sfx_pstop); + } while(currentMenu->menuitems[itemOn].status==-1); + + return true; + } + else if (key == key_menu_up) + { + // Move back up to previous item + + do + { + if (!itemOn) + itemOn = currentMenu->numitems-1; + else itemOn--; + S_StartSound(NULL,sfx_pstop); + } while(currentMenu->menuitems[itemOn].status==-1); + + return true; + } + else if (key == key_menu_left) + { + // Slide slider left + + if (currentMenu->menuitems[itemOn].routine && + currentMenu->menuitems[itemOn].status == 2) + { + S_StartSound(NULL,sfx_stnmov); + currentMenu->menuitems[itemOn].routine(0); + } + return true; + } + else if (key == key_menu_right) + { + // Slide slider right + + if (currentMenu->menuitems[itemOn].routine && + currentMenu->menuitems[itemOn].status == 2) + { + S_StartSound(NULL,sfx_stnmov); + currentMenu->menuitems[itemOn].routine(1); + } + return true; + } + else if (key == key_menu_forward) + { + // Activate menu item + + if (currentMenu->menuitems[itemOn].routine && + currentMenu->menuitems[itemOn].status) + { + currentMenu->lastOn = itemOn; + if (currentMenu->menuitems[itemOn].status == 2) + { + currentMenu->menuitems[itemOn].routine(1); // right arrow + S_StartSound(NULL,sfx_stnmov); + } + else + { + currentMenu->menuitems[itemOn].routine(itemOn); + S_StartSound(NULL,sfx_pistol); + } + } + return true; + } + else if (key == key_menu_activate) + { + // Deactivate menu + + currentMenu->lastOn = itemOn; + M_ClearMenus (); + S_StartSound(NULL,sfx_swtchx); + return true; + } + else if (key == key_menu_back) + { + // Go back to previous menu + + currentMenu->lastOn = itemOn; + if (currentMenu->prevMenu) + { + currentMenu = currentMenu->prevMenu; + itemOn = currentMenu->lastOn; + S_StartSound(NULL,sfx_swtchn); + } + return true; + } + + // Keyboard shortcut? + // Vanilla Doom has a weird behavior where it jumps to the scroll bars + // when the certain keys are pressed, so emulate this. + + else if (ch != 0 || IsNullKey(key)) + { + for (i = itemOn+1;i < currentMenu->numitems;i++) + { + if (currentMenu->menuitems[i].alphaKey == ch) + { + itemOn = i; + S_StartSound(NULL,sfx_pstop); + return true; + } + } + + for (i = 0;i <= itemOn;i++) + { + if (currentMenu->menuitems[i].alphaKey == ch) + { + itemOn = i; + S_StartSound(NULL,sfx_pstop); + return true; + } + } + } + + return false; +} + + + +// +// M_StartControlPanel +// +void M_StartControlPanel (void) +{ + // intro might call this repeatedly + if (menuactive) + return; + + menuactive = 1; + currentMenu = &MainDef; // JDC + itemOn = currentMenu->lastOn; // JDC +} + +// Display OPL debug messages - hack for GENMIDI development. + +#if 0 +static void M_DrawOPLDev(void) +{ + extern void I_OPL_DevMessages(char *, size_t); + char debug[1024]; + char *curr, *p; + int line; + + //XXX I_OPL_DevMessages(debug, sizeof(debug)); + curr = debug; + line = 0; + + for (;;) + { + p = strchr(curr, '\n'); + + if (p != NULL) + { + *p = '\0'; + } + + M_WriteText(0, line * 8, curr); + ++line; + + if (p == NULL) + { + break; + } + + curr = p + 1; + } +} +#endif + +// +// M_Drawer +// Called after the view has been rendered, +// but before it has been blitted. +// +void M_Drawer (void) +{ + static short x; + static short y; + unsigned int i; + unsigned int max; + char string[80]; + char *name; + int start; + + inhelpscreens = false; + + // Horiz. & Vertically center string and print it. + if (messageToPrint) + { + start = 0; + y = SCREENHEIGHT/2 - M_StringHeight(messageString) / 2; + while (messageString[start] != '\0') + { + int foundnewline = 0; + + for (i = 0; i < strlen(messageString + start); i++) + { + if (messageString[start + i] == '\n') + { + M_StringCopy(string, messageString + start, + sizeof(string)); + if (i < sizeof(string)) + { + string[i] = '\0'; + } + + foundnewline = 1; + start += i + 1; + break; + } + } + + if (!foundnewline) + { + M_StringCopy(string, messageString + start, sizeof(string)); + start += strlen(string); + } + + x = SCREENWIDTH/2 - M_StringWidth(string) / 2; + M_WriteText(x, y, string); + y += SHORT(hu_font[0]->height); + } + + return; + } + + //if (opldev) + //{ + // M_DrawOPLDev(); + //} + + if (!menuactive) + return; + + if (currentMenu->routine) + currentMenu->routine(); // call Draw routine + + // DRAW MENU + x = currentMenu->x; + y = currentMenu->y; + max = currentMenu->numitems; + + for (i=0;imenuitems[i].name); + + if (name[0]) + { + V_DrawPatchDirect (x, y, W_CacheLumpName(name, PU_CACHE)); + } + y += LINEHEIGHT; + } + + + // DRAW SKULL + V_DrawPatchDirect(x + SKULLXOFF, currentMenu->y - 5 + itemOn*LINEHEIGHT, + W_CacheLumpName(DEH_String(skullName[whichSkull]), + PU_CACHE)); +} + + +// +// M_ClearMenus +// +void M_ClearMenus (void) +{ + menuactive = 0; + // if (!netgame && usergame && paused) + // sendpause = true; +} + + + + +// +// M_SetupNextMenu +// +void M_SetupNextMenu(menu_t *menudef) +{ + currentMenu = menudef; + itemOn = currentMenu->lastOn; +} + + +// +// M_Ticker +// +void M_Ticker (void) +{ + if (--skullAnimCounter <= 0) + { + whichSkull ^= 1; + skullAnimCounter = 8; + } +} + + +// +// M_Init +// +void M_Init (void) +{ + currentMenu = &MainDef; + menuactive = 0; + itemOn = currentMenu->lastOn; + whichSkull = 0; + skullAnimCounter = 10; + screenSize = screenblocks - 3; + messageToPrint = 0; + messageString = NULL; + messageLastMenuActive = menuactive; + quickSaveSlot = -1; + + // Here we could catch other version dependencies, + // like HELP1/2, and four episodes. + + + switch ( gamemode ) + { + case commercial: + // Commercial has no "read this" entry. + MainMenu[readthis] = MainMenu[quitdoom]; + MainDef.numitems--; + MainDef.y += 8; + NewDef.prevMenu = &MainDef; + break; + case shareware: + // Episode 2 and 3 are handled, + // branching to an ad screen. + case registered: + break; + case retail: + // We are fine. + default: + break; + } + + // Versions of doom.exe before the Ultimate Doom release only had + // three episodes; if we're emulating one of those then don't try + // to show episode four. If we are, then do show episode four + // (should crash if missing). + if (gameversion < exe_ultimate) + { + EpiDef.numitems--; + } + + //opldev = M_CheckParm("-opldev") > 0; +} + diff --git a/firmware_p4/components/Applications/doom/m_menu.h b/firmware_p4/components/Applications/doom/m_menu.h new file mode 100644 index 000000000..ce41db39a --- /dev/null +++ b/firmware_p4/components/Applications/doom/m_menu.h @@ -0,0 +1,61 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Menu widget stuff, episode selection and such. +// + + +#ifndef __M_MENU__ +#define __M_MENU__ + + + +#include "d_event.h" + +// +// MENUS +// +// Called by main loop, +// saves config file and calls I_Quit when user exits. +// Even when the menu is not displayed, +// this can resize the view and change game parameters. +// Does all the real work of the menu interaction. +boolean M_Responder (event_t *ev); + + +// Called by main loop, +// only used for menu (skull cursor) animation. +void M_Ticker (void); + +// Called by main loop, +// draws the menus directly into the screen buffer. +void M_Drawer (void); + +// Called by D_DoomMain, +// loads the config file. +void M_Init (void); + +// Called by intro code to force menu up upon a keypress, +// does nothing if menu is already up. +void M_StartControlPanel (void); + + + +extern int detailLevel; +extern int screenblocks; + + + +#endif diff --git a/firmware_p4/components/Applications/doom/m_misc.c b/firmware_p4/components/Applications/doom/m_misc.c new file mode 100644 index 000000000..54b261344 --- /dev/null +++ b/firmware_p4/components/Applications/doom/m_misc.c @@ -0,0 +1,536 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Miscellaneous. +// + + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#ifdef _MSC_VER +#include +#endif +#else +#include +#include +#endif + +#include "doomtype.h" + +#include "deh_str.h" + +#include "i_swap.h" +#include "i_system.h" +#include "i_video.h" +#include "m_misc.h" +#include "v_video.h" +#include "w_wad.h" +#include "z_zone.h" + +// +// Create a directory +// + +void M_MakeDirectory(char *path) +{ +#ifdef _WIN32 + mkdir(path); +#else + mkdir(path, 0755); +#endif +} + +// Check if a file exists + +boolean M_FileExists(char *filename) +{ + FILE *fstream; + + fstream = fopen(filename, "r"); + + if (fstream != NULL) + { + fclose(fstream); + return true; + } + else + { + // If we can't open because the file is a directory, the + // "file" exists at least! + + return errno == EISDIR; + } +} + +// +// Determine the length of an open file. +// + +long M_FileLength(FILE *handle) +{ + long savedpos; + long length; + + // save the current position in the file + savedpos = ftell(handle); + + // jump to the end and find the length + fseek(handle, 0, SEEK_END); + length = ftell(handle); + + // go back to the old location + fseek(handle, savedpos, SEEK_SET); + + return length; +} + +// +// M_WriteFile +// + +boolean M_WriteFile(char *name, void *source, int length) +{ + FILE *handle; + int count; + + handle = fopen(name, "wb"); + + if (handle == NULL) + return false; + + count = fwrite(source, 1, length, handle); + fclose(handle); + + if (count < length) + return false; + + return true; +} + + +// +// M_ReadFile +// + +int M_ReadFile(char *name, byte **buffer) +{ + FILE *handle; + int count, length; + byte *buf; + + handle = fopen(name, "rb"); + if (handle == NULL) + I_Error ("Couldn't read file %s", name); + + // find the size of the file by seeking to the end and + // reading the current position + + length = M_FileLength(handle); + + buf = Z_Malloc (length, PU_STATIC, NULL); + count = fread(buf, 1, length, handle); + fclose (handle); + + if (count < length) + I_Error ("Couldn't read file %s", name); + + *buffer = buf; + return length; +} + +// Returns the path to a temporary file of the given name, stored +// inside the system temporary directory. +// +// The returned value must be freed with Z_Free after use. + +char *M_TempFile(char *s) +{ + char *tempdir; + +#if defined(_WIN32) || defined(__DJGPP__) + + // Check the TEMP environment variable to find the location. + + tempdir = getenv("TEMP"); + + if (tempdir == NULL) + { + tempdir = "."; + } +#else + // In Unix, just use /tmp. + + tempdir = "/tmp"; +#endif + + return M_StringJoin(tempdir, DIR_SEPARATOR_S, s, NULL); +} + +boolean M_StrToInt(const char *str, int *result) +{ + return sscanf(str, " 0x%x", result) == 1 + || sscanf(str, " 0X%x", result) == 1 + || sscanf(str, " 0%o", result) == 1 + || sscanf(str, " %d", result) == 1; +} + +void M_ExtractFileBase(char *path, char *dest) +{ + char *src; + char *filename; + int length; + + src = path + strlen(path) - 1; + + // back up until a \ or the start + while (src != path && *(src - 1) != DIR_SEPARATOR) + { + src--; + } + + filename = src; + + // Copy up to eight characters + // Note: Vanilla Doom exits with an error if a filename is specified + // with a base of more than eight characters. To remove the 8.3 + // filename limit, instead we simply truncate the name. + + length = 0; + memset(dest, 0, 8); + + while (*src != '\0' && *src != '.') + { + if (length >= 8) + { + printf("Warning: Truncated '%s' lump name to '%.8s'.\n", + filename, dest); + break; + } + + dest[length++] = toupper((int)*src++); + } +} + +//--------------------------------------------------------------------------- +// +// PROC M_ForceUppercase +// +// Change string to uppercase. +// +//--------------------------------------------------------------------------- + +void M_ForceUppercase(char *text) +{ + char *p; + + for (p = text; *p != '\0'; ++p) + { + *p = toupper(*p); + } +} + +// +// M_StrCaseStr +// +// Case-insensitive version of strstr() +// + +char *M_StrCaseStr(char *haystack, char *needle) +{ + unsigned int haystack_len; + unsigned int needle_len; + unsigned int len; + unsigned int i; + + haystack_len = strlen(haystack); + needle_len = strlen(needle); + + if (haystack_len < needle_len) + { + return NULL; + } + + len = haystack_len - needle_len; + + for (i = 0; i <= len; ++i) + { + if (!strncasecmp(haystack + i, needle, needle_len)) + { + return haystack + i; + } + } + + return NULL; +} + +// +// Safe version of strdup() that checks the string was successfully +// allocated. +// + +char *M_StringDuplicate(const char *orig) +{ + char *result; + + result = strdup(orig); + + if (result == NULL) + { + I_Error("Failed to duplicate string (length %i)\n", + strlen(orig)); + } + + return result; +} + +// +// String replace function. +// + +char *M_StringReplace(const char *haystack, const char *needle, + const char *replacement) +{ + char *result, *dst; + const char *p; + size_t needle_len = strlen(needle); + size_t result_len, dst_len; + + // Iterate through occurrences of 'needle' and calculate the size of + // the new string. + result_len = strlen(haystack) + 1; + p = haystack; + + for (;;) + { + p = strstr(p, needle); + if (p == NULL) + { + break; + } + + p += needle_len; + result_len += strlen(replacement) - needle_len; + } + + // Construct new string. + + result = malloc(result_len); + if (result == NULL) + { + I_Error("M_StringReplace: Failed to allocate new string"); + return NULL; + } + + dst = result; dst_len = result_len; + p = haystack; + + while (*p != '\0') + { + if (!strncmp(p, needle, needle_len)) + { + M_StringCopy(dst, replacement, dst_len); + p += needle_len; + dst += strlen(replacement); + dst_len -= strlen(replacement); + } + else + { + *dst = *p; + ++dst; --dst_len; + ++p; + } + } + + *dst = '\0'; + + return result; +} + +// Safe string copy function that works like OpenBSD's strlcpy(). +// Returns true if the string was not truncated. + +boolean M_StringCopy(char *dest, const char *src, size_t dest_size) +{ + size_t len; + + if (dest_size >= 1) + { + dest[dest_size - 1] = '\0'; + strncpy(dest, src, dest_size - 1); + } + else + { + return false; + } + + len = strlen(dest); + return src[len] == '\0'; +} + +// Safe string concat function that works like OpenBSD's strlcat(). +// Returns true if string not truncated. + +boolean M_StringConcat(char *dest, const char *src, size_t dest_size) +{ + size_t offset; + + offset = strlen(dest); + if (offset > dest_size) + { + offset = dest_size; + } + + return M_StringCopy(dest + offset, src, dest_size - offset); +} + +// Returns true if 's' begins with the specified prefix. + +boolean M_StringStartsWith(const char *s, const char *prefix) +{ + return strlen(s) > strlen(prefix) + && strncmp(s, prefix, strlen(prefix)) == 0; +} + +// Returns true if 's' ends with the specified suffix. + +boolean M_StringEndsWith(const char *s, const char *suffix) +{ + return strlen(s) >= strlen(suffix) + && strcmp(s + strlen(s) - strlen(suffix), suffix) == 0; +} + +// Return a newly-malloced string with all the strings given as arguments +// concatenated together. + +char *M_StringJoin(const char *s, ...) +{ + char *result; + const char *v; + va_list args; + size_t result_len; + + result_len = strlen(s) + 1; + + va_start(args, s); + for (;;) + { + v = va_arg(args, const char *); + if (v == NULL) + { + break; + } + + result_len += strlen(v); + } + va_end(args); + + result = malloc(result_len); + + if (result == NULL) + { + I_Error("M_StringJoin: Failed to allocate new string."); + return NULL; + } + + M_StringCopy(result, s, result_len); + + va_start(args, s); + for (;;) + { + v = va_arg(args, const char *); + if (v == NULL) + { + break; + } + + M_StringConcat(result, v, result_len); + } + va_end(args); + + return result; +} + +// On Windows, vsnprintf() is _vsnprintf(). +#ifdef _WIN32 +#if _MSC_VER < 1400 /* not needed for Visual Studio 2008 */ +#define vsnprintf _vsnprintf +#endif +#endif + +// Safe, portable vsnprintf(). +int M_vsnprintf(char *buf, size_t buf_len, const char *s, va_list args) +{ + int result; + + if (buf_len < 1) + { + return 0; + } + + // Windows (and other OSes?) has a vsnprintf() that doesn't always + // append a trailing \0. So we must do it, and write into a buffer + // that is one byte shorter; otherwise this function is unsafe. + result = vsnprintf(buf, buf_len, s, args); + + // If truncated, change the final char in the buffer to a \0. + // A negative result indicates a truncated buffer on Windows. + if (result < 0 || result >= buf_len) + { + buf[buf_len - 1] = '\0'; + result = buf_len - 1; + } + + return result; +} + +// Safe, portable snprintf(). +int M_snprintf(char *buf, size_t buf_len, const char *s, ...) +{ + va_list args; + int result; + va_start(args, s); + result = M_vsnprintf(buf, buf_len, s, args); + va_end(args); + return result; +} + +#ifdef _WIN32 + +char *M_OEMToUTF8(const char *oem) +{ + unsigned int len = strlen(oem) + 1; + wchar_t *tmp; + char *result; + + tmp = malloc(len * sizeof(wchar_t)); + MultiByteToWideChar(CP_OEMCP, 0, oem, len, tmp, len); + result = malloc(len * 4); + WideCharToMultiByte(CP_UTF8, 0, tmp, len, result, len * 4, NULL, NULL); + free(tmp); + + return result; +} + +#endif + diff --git a/firmware_p4/components/Applications/doom/m_misc.h b/firmware_p4/components/Applications/doom/m_misc.h new file mode 100644 index 000000000..844b48595 --- /dev/null +++ b/firmware_p4/components/Applications/doom/m_misc.h @@ -0,0 +1,51 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Miscellaneous. +// + + +#ifndef __M_MISC__ +#define __M_MISC__ + +#include +#include + +#include "doomtype.h" + +boolean M_WriteFile(char *name, void *source, int length); +int M_ReadFile(char *name, byte **buffer); +void M_MakeDirectory(char *dir); +char *M_TempFile(char *s); +boolean M_FileExists(char *file); +long M_FileLength(FILE *handle); +boolean M_StrToInt(const char *str, int *result); +void M_ExtractFileBase(char *path, char *dest); +void M_ForceUppercase(char *text); +char *M_StrCaseStr(char *haystack, char *needle); +char *M_StringDuplicate(const char *orig); +boolean M_StringCopy(char *dest, const char *src, size_t dest_size); +boolean M_StringConcat(char *dest, const char *src, size_t dest_size); +char *M_StringReplace(const char *haystack, const char *needle, + const char *replacement); +char *M_StringJoin(const char *s, ...); +boolean M_StringStartsWith(const char *s, const char *prefix); +boolean M_StringEndsWith(const char *s, const char *suffix); +int M_vsnprintf(char *buf, size_t buf_len, const char *s, va_list args); +int M_snprintf(char *buf, size_t buf_len, const char *s, ...); +char *M_OEMToUTF8(const char *ansi); + +#endif + diff --git a/firmware_p4/components/Applications/doom/m_random.c b/firmware_p4/components/Applications/doom/m_random.c new file mode 100644 index 000000000..8e3b4e189 --- /dev/null +++ b/firmware_p4/components/Applications/doom/m_random.c @@ -0,0 +1,65 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Random number LUT. +// + +// +// M_Random +// Returns a 0-255 number +// + +static const unsigned char rndtable[256] = { + 0, 8, 109, 220, 222, 241, 149, 107, 75, 248, 254, 140, 16, 66 , + 74, 21, 211, 47, 80, 242, 154, 27, 205, 128, 161, 89, 77, 36 , + 95, 110, 85, 48, 212, 140, 211, 249, 22, 79, 200, 50, 28, 188 , + 52, 140, 202, 120, 68, 145, 62, 70, 184, 190, 91, 197, 152, 224 , + 149, 104, 25, 178, 252, 182, 202, 182, 141, 197, 4, 81, 181, 242 , + 145, 42, 39, 227, 156, 198, 225, 193, 219, 93, 122, 175, 249, 0 , + 175, 143, 70, 239, 46, 246, 163, 53, 163, 109, 168, 135, 2, 235 , + 25, 92, 20, 145, 138, 77, 69, 166, 78, 176, 173, 212, 166, 113 , + 94, 161, 41, 50, 239, 49, 111, 164, 70, 60, 2, 37, 171, 75 , + 136, 156, 11, 56, 42, 146, 138, 229, 73, 146, 77, 61, 98, 196 , + 135, 106, 63, 197, 195, 86, 96, 203, 113, 101, 170, 247, 181, 113 , + 80, 250, 108, 7, 255, 237, 129, 226, 79, 107, 112, 166, 103, 241 , + 24, 223, 239, 120, 198, 58, 60, 82, 128, 3, 184, 66, 143, 224 , + 145, 224, 81, 206, 163, 45, 63, 90, 168, 114, 59, 33, 159, 95 , + 28, 139, 123, 98, 125, 196, 15, 70, 194, 253, 54, 14, 109, 226 , + 71, 17, 161, 93, 186, 87, 244, 138, 20, 52, 123, 251, 26, 36 , + 17, 46, 52, 231, 232, 76, 31, 221, 84, 37, 216, 165, 212, 106 , + 197, 242, 98, 43, 39, 175, 254, 145, 190, 84, 118, 222, 187, 136 , + 120, 163, 236, 249 +}; + +int rndindex = 0; +int prndindex = 0; + +// Which one is deterministic? +int P_Random (void) +{ + prndindex = (prndindex+1)&0xff; + return rndtable[prndindex]; +} + +int M_Random (void) +{ + rndindex = (rndindex+1)&0xff; + return rndtable[rndindex]; +} + +void M_ClearRandom (void) +{ + rndindex = prndindex = 0; +} diff --git a/firmware_p4/components/Applications/doom/m_random.h b/firmware_p4/components/Applications/doom/m_random.h new file mode 100644 index 000000000..aa6291ae9 --- /dev/null +++ b/firmware_p4/components/Applications/doom/m_random.h @@ -0,0 +1,39 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// +// + + +#ifndef __M_RANDOM__ +#define __M_RANDOM__ + + +#include "doomtype.h" + + + +// Returns a number from 0 to 255, +// from a lookup table. +int M_Random (void); + +// As M_Random, but used only by the play simulation. +int P_Random (void); + +// Fix randoms for demos. +void M_ClearRandom (void); + + +#endif diff --git a/firmware_p4/components/Applications/doom/memio.c b/firmware_p4/components/Applications/doom/memio.c new file mode 100644 index 000000000..3cc769a35 --- /dev/null +++ b/firmware_p4/components/Applications/doom/memio.c @@ -0,0 +1,197 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// Emulates the IO functions in C stdio.h reading and writing to +// memory. +// + +#include +#include +#include + +#include "memio.h" + +#include "z_zone.h" + +typedef enum { + MODE_READ, + MODE_WRITE, +} memfile_mode_t; + +struct _MEMFILE { + unsigned char *buf; + size_t buflen; + size_t alloced; + unsigned int position; + memfile_mode_t mode; +}; + +// Open a memory area for reading + +MEMFILE *mem_fopen_read(void *buf, size_t buflen) +{ + MEMFILE *file; + + file = Z_Malloc(sizeof(MEMFILE), PU_STATIC, 0); + + file->buf = (unsigned char *) buf; + file->buflen = buflen; + file->position = 0; + file->mode = MODE_READ; + + return file; +} + +// Read bytes + +size_t mem_fread(void *buf, size_t size, size_t nmemb, MEMFILE *stream) +{ + size_t items; + + if (stream->mode != MODE_READ) + { + printf("not a read stream\n"); + return -1; + } + + // Trying to read more bytes than we have left? + + items = nmemb; + + if (items * size > stream->buflen - stream->position) + { + items = (stream->buflen - stream->position) / size; + } + + // Copy bytes to buffer + + memcpy(buf, stream->buf + stream->position, items * size); + + // Update position + + stream->position += items * size; + + return items; +} + +// Open a memory area for writing + +MEMFILE *mem_fopen_write(void) +{ + MEMFILE *file; + + file = Z_Malloc(sizeof(MEMFILE), PU_STATIC, 0); + + file->alloced = 1024; + file->buf = Z_Malloc(file->alloced, PU_STATIC, 0); + file->buflen = 0; + file->position = 0; + file->mode = MODE_WRITE; + + return file; +} + +// Write bytes to stream + +size_t mem_fwrite(const void *ptr, size_t size, size_t nmemb, MEMFILE *stream) +{ + size_t bytes; + + if (stream->mode != MODE_WRITE) + { + return -1; + } + + // More bytes than can fit in the buffer? + // If so, reallocate bigger. + + bytes = size * nmemb; + + while (bytes > stream->alloced - stream->position) + { + unsigned char *newbuf; + + newbuf = Z_Malloc(stream->alloced * 2, PU_STATIC, 0); + memcpy(newbuf, stream->buf, stream->alloced); + Z_Free(stream->buf); + stream->buf = newbuf; + stream->alloced *= 2; + } + + // Copy into buffer + + memcpy(stream->buf + stream->position, ptr, bytes); + stream->position += bytes; + + if (stream->position > stream->buflen) + stream->buflen = stream->position; + + return nmemb; +} + +void mem_get_buf(MEMFILE *stream, void **buf, size_t *buflen) +{ + *buf = stream->buf; + *buflen = stream->buflen; +} + +void mem_fclose(MEMFILE *stream) +{ + if (stream->mode == MODE_WRITE) + { + Z_Free(stream->buf); + } + + Z_Free(stream); +} + +long mem_ftell(MEMFILE *stream) +{ + return stream->position; +} + +int mem_fseek(MEMFILE *stream, signed long position, mem_rel_t whence) +{ + unsigned int newpos; + + switch (whence) + { + case MEM_SEEK_SET: + newpos = (int) position; + break; + + case MEM_SEEK_CUR: + newpos = (int) (stream->position + position); + break; + + case MEM_SEEK_END: + newpos = (int) (stream->buflen + position); + break; + default: + return -1; + } + + if (newpos < stream->buflen) + { + stream->position = newpos; + return 0; + } + else + { + printf("Error seeking to %i\n", newpos); + return -1; + } +} + + diff --git a/firmware_p4/components/Applications/doom/memio.h b/firmware_p4/components/Applications/doom/memio.h new file mode 100644 index 000000000..03706a317 --- /dev/null +++ b/firmware_p4/components/Applications/doom/memio.h @@ -0,0 +1,38 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// + +#ifndef MEMIO_H +#define MEMIO_H + +typedef struct _MEMFILE MEMFILE; + +typedef enum +{ + MEM_SEEK_SET, + MEM_SEEK_CUR, + MEM_SEEK_END, +} mem_rel_t; + +MEMFILE *mem_fopen_read(void *buf, size_t buflen); +size_t mem_fread(void *buf, size_t size, size_t nmemb, MEMFILE *stream); +MEMFILE *mem_fopen_write(void); +size_t mem_fwrite(const void *ptr, size_t size, size_t nmemb, MEMFILE *stream); +void mem_get_buf(MEMFILE *stream, void **buf, size_t *buflen); +void mem_fclose(MEMFILE *stream); +long mem_ftell(MEMFILE *stream); +int mem_fseek(MEMFILE *stream, signed long offset, mem_rel_t whence); + +#endif /* #ifndef MEMIO_H */ + diff --git a/firmware_p4/components/Applications/doom/mus2mid.c b/firmware_p4/components/Applications/doom/mus2mid.c new file mode 100644 index 000000000..bcbeb00c0 --- /dev/null +++ b/firmware_p4/components/Applications/doom/mus2mid.c @@ -0,0 +1,737 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2006 Ben Ryves 2006 +// +// 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 +// of the License, 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. +// +// mus2mid.c - Ben Ryves 2006 - http://benryves.com - benryves@benryves.com +// Use to convert a MUS file into a single track, type 0 MIDI file. + +#include + +#include "doomtype.h" +#include "i_swap.h" + +#include "memio.h" +#include "mus2mid.h" + +#define NUM_CHANNELS 16 + +#define MIDI_PERCUSSION_CHAN 9 +#define MUS_PERCUSSION_CHAN 15 + +// MUS event codes +typedef enum +{ + mus_releasekey = 0x00, + mus_presskey = 0x10, + mus_pitchwheel = 0x20, + mus_systemevent = 0x30, + mus_changecontroller = 0x40, + mus_scoreend = 0x60 +} musevent; + +// MIDI event codes +typedef enum +{ + midi_releasekey = 0x80, + midi_presskey = 0x90, + midi_aftertouchkey = 0xA0, + midi_changecontroller = 0xB0, + midi_changepatch = 0xC0, + midi_aftertouchchannel = 0xD0, + midi_pitchwheel = 0xE0 +} midievent; + +// Structure to hold MUS file header +typedef struct +{ + byte id[4]; + unsigned short scorelength; + unsigned short scorestart; + unsigned short primarychannels; + unsigned short secondarychannels; + unsigned short instrumentcount; +} musheader; + +// Standard MIDI type 0 header + track header +static const byte midiheader[] = +{ + 'M', 'T', 'h', 'd', // Main header + 0x00, 0x00, 0x00, 0x06, // Header size + 0x00, 0x00, // MIDI type (0) + 0x00, 0x01, // Number of tracks + 0x00, 0x46, // Resolution + 'M', 'T', 'r', 'k', // Start of track + 0x00, 0x00, 0x00, 0x00 // Placeholder for track length +}; + +// Cached channel velocities +static byte channelvelocities[] = +{ + 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127 +}; + +// Timestamps between sequences of MUS events + +static unsigned int queuedtime = 0; + +// Counter for the length of the track + +static unsigned int tracksize; + +static const byte controller_map[] = +{ + 0x00, 0x20, 0x01, 0x07, 0x0A, 0x0B, 0x5B, 0x5D, + 0x40, 0x43, 0x78, 0x7B, 0x7E, 0x7F, 0x79 +}; + +static int channel_map[NUM_CHANNELS]; + +// Write timestamp to a MIDI file. + +static boolean WriteTime(unsigned int time, MEMFILE *midioutput) +{ + unsigned int buffer = time & 0x7F; + byte writeval; + + while ((time >>= 7) != 0) + { + buffer <<= 8; + buffer |= ((time & 0x7F) | 0x80); + } + + for (;;) + { + writeval = (byte)(buffer & 0xFF); + + if (mem_fwrite(&writeval, 1, 1, midioutput) != 1) + { + return true; + } + + ++tracksize; + + if ((buffer & 0x80) != 0) + { + buffer >>= 8; + } + else + { + queuedtime = 0; + return false; + } + } +} + + +// Write the end of track marker +static boolean WriteEndTrack(MEMFILE *midioutput) +{ + byte endtrack[] = {0xFF, 0x2F, 0x00}; + + if (WriteTime(queuedtime, midioutput)) + { + return true; + } + + if (mem_fwrite(endtrack, 1, 3, midioutput) != 3) + { + return true; + } + + tracksize += 3; + return false; +} + +// Write a key press event +static boolean WritePressKey(byte channel, byte key, + byte velocity, MEMFILE *midioutput) +{ + byte working = midi_presskey | channel; + + if (WriteTime(queuedtime, midioutput)) + { + return true; + } + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + working = key & 0x7F; + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + working = velocity & 0x7F; + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + tracksize += 3; + + return false; +} + +// Write a key release event +static boolean WriteReleaseKey(byte channel, byte key, + MEMFILE *midioutput) +{ + byte working = midi_releasekey | channel; + + if (WriteTime(queuedtime, midioutput)) + { + return true; + } + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + working = key & 0x7F; + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + working = 0; + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + tracksize += 3; + + return false; +} + +// Write a pitch wheel/bend event +static boolean WritePitchWheel(byte channel, short wheel, + MEMFILE *midioutput) +{ + byte working = midi_pitchwheel | channel; + + if (WriteTime(queuedtime, midioutput)) + { + return true; + } + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + working = wheel & 0x7F; + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + working = (wheel >> 7) & 0x7F; + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + tracksize += 3; + return false; +} + +// Write a patch change event +static boolean WriteChangePatch(byte channel, byte patch, + MEMFILE *midioutput) +{ + byte working = midi_changepatch | channel; + + if (WriteTime(queuedtime, midioutput)) + { + return true; + } + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + working = patch & 0x7F; + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + tracksize += 2; + + return false; +} + +// Write a valued controller change event + +static boolean WriteChangeController_Valued(byte channel, + byte control, + byte value, + MEMFILE *midioutput) +{ + byte working = midi_changecontroller | channel; + + if (WriteTime(queuedtime, midioutput)) + { + return true; + } + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + working = control & 0x7F; + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + // Quirk in vanilla DOOM? MUS controller values should be + // 7-bit, not 8-bit. + + working = value;// & 0x7F; + + // Fix on said quirk to stop MIDI players from complaining that + // the value is out of range: + + if (working & 0x80) + { + working = 0x7F; + } + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + tracksize += 3; + + return false; +} + +// Write a valueless controller change event +static boolean WriteChangeController_Valueless(byte channel, + byte control, + MEMFILE *midioutput) +{ + return WriteChangeController_Valued(channel, control, 0, + midioutput); +} + +// Allocate a free MIDI channel. + +static int AllocateMIDIChannel(void) +{ + int result; + int max; + int i; + + // Find the current highest-allocated channel. + + max = -1; + + for (i=0; i max) + { + max = channel_map[i]; + } + } + + // max is now equal to the highest-allocated MIDI channel. We can + // now allocate the next available channel. This also works if + // no channels are currently allocated (max=-1) + + result = max + 1; + + // Don't allocate the MIDI percussion channel! + + if (result == MIDI_PERCUSSION_CHAN) + { + ++result; + } + + return result; +} + +// Given a MUS channel number, get the MIDI channel number to use +// in the outputted file. + +static int GetMIDIChannel(int mus_channel, MEMFILE *midioutput) +{ + // Find the MIDI channel to use for this MUS channel. + // MUS channel 15 is the percusssion channel. + + if (mus_channel == MUS_PERCUSSION_CHAN) + { + return MIDI_PERCUSSION_CHAN; + } + else + { + // If a MIDI channel hasn't been allocated for this MUS channel + // yet, allocate the next free MIDI channel. + + if (channel_map[mus_channel] == -1) + { + channel_map[mus_channel] = AllocateMIDIChannel(); + + // First time using the channel, send an "all notes off" + // event. This fixes "The D_DDTBLU disease" described here: + // https://www.doomworld.com/vb/source-ports/66802-the + WriteChangeController_Valueless(channel_map[mus_channel], 0x7b, + midioutput); + } + + return channel_map[mus_channel]; + } +} + +static boolean ReadMusHeader(MEMFILE *file, musheader *header) +{ + boolean result; + + result = mem_fread(&header->id, sizeof(byte), 4, file) == 4 + && mem_fread(&header->scorelength, sizeof(short), 1, file) == 1 + && mem_fread(&header->scorestart, sizeof(short), 1, file) == 1 + && mem_fread(&header->primarychannels, sizeof(short), 1, file) == 1 + && mem_fread(&header->secondarychannels, sizeof(short), 1, file) == 1 + && mem_fread(&header->instrumentcount, sizeof(short), 1, file) == 1; + + if (result) + { + header->scorelength = SHORT(header->scorelength); + header->scorestart = SHORT(header->scorestart); + header->primarychannels = SHORT(header->primarychannels); + header->secondarychannels = SHORT(header->secondarychannels); + header->instrumentcount = SHORT(header->instrumentcount); + } + + return result; +} + + +// Read a MUS file from a stream (musinput) and output a MIDI file to +// a stream (midioutput). +// +// Returns 0 on success or 1 on failure. + +boolean mus2mid(MEMFILE *musinput, MEMFILE *midioutput) +{ + // Header for the MUS file + musheader musfileheader; + + // Descriptor for the current MUS event + byte eventdescriptor; + int channel; // Channel number + musevent event; + + + // Bunch of vars read from MUS lump + byte key; + byte controllernumber; + byte controllervalue; + + // Buffer used for MIDI track size record + byte tracksizebuffer[4]; + + // Flag for when the score end marker is hit. + int hitscoreend = 0; + + // Temp working byte + byte working; + // Used in building up time delays + unsigned int timedelay; + + // Initialise channel map to mark all channels as unused. + + for (channel=0; channel 14) + { + return true; + } + + if (WriteChangeController_Valueless(channel, + controller_map[controllernumber], + midioutput)) + { + return true; + } + + break; + + case mus_changecontroller: + if (mem_fread(&controllernumber, 1, 1, musinput) != 1) + { + return true; + } + + if (mem_fread(&controllervalue, 1, 1, musinput) != 1) + { + return true; + } + + if (controllernumber == 0) + { + if (WriteChangePatch(channel, controllervalue, + midioutput)) + { + return true; + } + } + else + { + if (controllernumber < 1 || controllernumber > 9) + { + return true; + } + + if (WriteChangeController_Valued(channel, + controller_map[controllernumber], + controllervalue, + midioutput)) + { + return true; + } + } + + break; + + case mus_scoreend: + hitscoreend = 1; + break; + + default: + return true; + break; + } + + if (eventdescriptor & 0x80) + { + break; + } + } + // Now we need to read the time code: + if (!hitscoreend) + { + timedelay = 0; + for (;;) + { + if (mem_fread(&working, 1, 1, musinput) != 1) + { + return true; + } + + timedelay = timedelay * 128 + (working & 0x7F); + if ((working & 0x80) == 0) + { + break; + } + } + queuedtime += timedelay; + } + } + + // End of track + if (WriteEndTrack(midioutput)) + { + return true; + } + + // Write the track size into the stream + if (mem_fseek(midioutput, 18, MEM_SEEK_SET)) + { + return true; + } + + tracksizebuffer[0] = (tracksize >> 24) & 0xff; + tracksizebuffer[1] = (tracksize >> 16) & 0xff; + tracksizebuffer[2] = (tracksize >> 8) & 0xff; + tracksizebuffer[3] = tracksize & 0xff; + + if (mem_fwrite(tracksizebuffer, 1, 4, midioutput) != 4) + { + return true; + } + + return false; +} + +#ifdef STANDALONE + +#include "m_misc.h" +#include "z_zone.h" + +int main(int argc, char *argv[]) +{ + MEMFILE *src, *dst; + byte *infile; + long infile_len; + void *outfile; + size_t outfile_len; + + if (argc != 3) + { + printf("Usage: %s \n", argv[0]); + exit(-1); + } + + Z_Init(); + + infile_len = M_ReadFile(argv[1], &infile); + + src = mem_fopen_read(infile, infile_len); + dst = mem_fopen_write(); + + if (mus2mid(src, dst)) + { + fprintf(stderr, "mus2mid() failed\n"); + exit(-1); + } + + // Write result to output file: + + mem_get_buf(dst, &outfile, &outfile_len); + + M_WriteFile(argv[2], outfile, outfile_len); + + return 0; +} + +#endif + diff --git a/firmware_p4/components/Applications/doom/mus2mid.h b/firmware_p4/components/Applications/doom/mus2mid.h new file mode 100644 index 000000000..d21516c70 --- /dev/null +++ b/firmware_p4/components/Applications/doom/mus2mid.h @@ -0,0 +1,9 @@ +#ifndef MUS2MID_H +#define MUS2MID_H + +#include "doomtype.h" +#include "memio.h" + +boolean mus2mid(MEMFILE *musinput, MEMFILE *midioutput); + +#endif /* #ifndef MUS2MID_H */ \ No newline at end of file diff --git a/firmware_p4/components/Applications/doom/net_client.h b/firmware_p4/components/Applications/doom/net_client.h new file mode 100644 index 000000000..31dc9e4af --- /dev/null +++ b/firmware_p4/components/Applications/doom/net_client.h @@ -0,0 +1,52 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// Network client code +// + +#ifndef NET_CLIENT_H +#define NET_CLIENT_H + +#include "doomtype.h" +#include "d_ticcmd.h" +#include "sha1.h" +#include "net_defs.h" + +boolean NET_CL_Connect(net_addr_t *addr, net_connect_data_t *data); +void NET_CL_Disconnect(void); +void NET_CL_Run(void); +void NET_CL_Init(void); +void NET_CL_LaunchGame(void); +void NET_CL_StartGame(net_gamesettings_t *settings); +void NET_CL_SendTiccmd(ticcmd_t *ticcmd, int maketic); +boolean NET_CL_GetSettings(net_gamesettings_t *_settings); +void NET_Init(void); + +void NET_BindVariables(void); + +extern boolean net_client_connected; +extern boolean net_client_received_wait_data; +extern net_waitdata_t net_client_wait_data; +extern boolean net_waiting_for_launch; +extern char *net_player_name; + +extern sha1_digest_t net_server_wad_sha1sum; +extern sha1_digest_t net_server_deh_sha1sum; +extern unsigned int net_server_is_freedoom; +extern sha1_digest_t net_local_wad_sha1sum; +extern sha1_digest_t net_local_deh_sha1sum; +extern unsigned int net_local_is_freedoom; + +extern boolean drone; + +#endif /* #ifndef NET_CLIENT_H */ diff --git a/firmware_p4/components/Applications/doom/net_dedicated.h b/firmware_p4/components/Applications/doom/net_dedicated.h new file mode 100644 index 000000000..3d7387b57 --- /dev/null +++ b/firmware_p4/components/Applications/doom/net_dedicated.h @@ -0,0 +1,25 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// +// Dedicated server code. +// + +#ifndef NET_DEDICATED_H +#define NET_DEDICATED_H + +void NET_DedicatedServer(void); + +#endif /* #ifndef NET_DEDICATED_H */ + + diff --git a/firmware_p4/components/Applications/doom/net_defs.h b/firmware_p4/components/Applications/doom/net_defs.h new file mode 100644 index 000000000..bedfb95b9 --- /dev/null +++ b/firmware_p4/components/Applications/doom/net_defs.h @@ -0,0 +1,248 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Definitions for use in networking code. +// + +#ifndef NET_DEFS_H +#define NET_DEFS_H + +#include + +#include "doomtype.h" +#include "d_ticcmd.h" +#include "sha1.h" + +// Absolute maximum number of "nodes" in the game. This is different to +// NET_MAXPLAYERS, as there may be observers that are not participating +// (eg. left/right monitors) + +#define MAXNETNODES 16 + +// The maximum number of players, multiplayer/networking. +// This is the maximum supported by the networking code; individual games +// have their own values for MAXPLAYERS that can be smaller. + +#define NET_MAXPLAYERS 8 + +// Maximum length of a player's name. + +#define MAXPLAYERNAME 30 + +// Networking and tick handling related. + +#define BACKUPTICS 128 + +typedef struct _net_module_s net_module_t; +typedef struct _net_packet_s net_packet_t; +typedef struct _net_addr_s net_addr_t; +typedef struct _net_context_s net_context_t; + +struct _net_packet_s +{ + byte *data; + size_t len; + size_t alloced; + unsigned int pos; +}; + +struct _net_module_s +{ + // Initialize this module for use as a client + + boolean (*InitClient)(void); + + // Initialize this module for use as a server + + boolean (*InitServer)(void); + + // Send a packet + + void (*SendPacket)(net_addr_t *addr, net_packet_t *packet); + + // Check for new packets to receive + // + // Returns true if packet received + + boolean (*RecvPacket)(net_addr_t **addr, net_packet_t **packet); + + // Converts an address to a string + + void (*AddrToString)(net_addr_t *addr, char *buffer, int buffer_len); + + // Free back an address when no longer in use + + void (*FreeAddress)(net_addr_t *addr); + + // Try to resolve a name to an address + + net_addr_t *(*ResolveAddress)(char *addr); +}; + +// net_addr_t + +struct _net_addr_s +{ + net_module_t *module; + void *handle; +}; + +// magic number sent when connecting to check this is a valid client + +#define NET_MAGIC_NUMBER 3436803284U + +// header field value indicating that the packet is a reliable packet + +#define NET_RELIABLE_PACKET (1 << 15) + +// packet types + +typedef enum +{ + NET_PACKET_TYPE_SYN, + NET_PACKET_TYPE_ACK, + NET_PACKET_TYPE_REJECTED, + NET_PACKET_TYPE_KEEPALIVE, + NET_PACKET_TYPE_WAITING_DATA, + NET_PACKET_TYPE_GAMESTART, + NET_PACKET_TYPE_GAMEDATA, + NET_PACKET_TYPE_GAMEDATA_ACK, + NET_PACKET_TYPE_DISCONNECT, + NET_PACKET_TYPE_DISCONNECT_ACK, + NET_PACKET_TYPE_RELIABLE_ACK, + NET_PACKET_TYPE_GAMEDATA_RESEND, + NET_PACKET_TYPE_CONSOLE_MESSAGE, + NET_PACKET_TYPE_QUERY, + NET_PACKET_TYPE_QUERY_RESPONSE, + NET_PACKET_TYPE_LAUNCH, +} net_packet_type_t; + +typedef enum +{ + NET_MASTER_PACKET_TYPE_ADD, + NET_MASTER_PACKET_TYPE_ADD_RESPONSE, + NET_MASTER_PACKET_TYPE_QUERY, + NET_MASTER_PACKET_TYPE_QUERY_RESPONSE, + NET_MASTER_PACKET_TYPE_GET_METADATA, + NET_MASTER_PACKET_TYPE_GET_METADATA_RESPONSE, + NET_MASTER_PACKET_TYPE_SIGN_START, + NET_MASTER_PACKET_TYPE_SIGN_START_RESPONSE, + NET_MASTER_PACKET_TYPE_SIGN_END, + NET_MASTER_PACKET_TYPE_SIGN_END_RESPONSE, +} net_master_packet_type_t; + +// Settings specified when the client connects to the server. + +typedef struct +{ + int gamemode; + int gamemission; + int lowres_turn; + int drone; + int max_players; + int is_freedoom; + sha1_digest_t wad_sha1sum; + sha1_digest_t deh_sha1sum; + int player_class; +} net_connect_data_t; + +// Game settings sent by client to server when initiating game start, +// and received from the server by clients when the game starts. + +typedef struct +{ + int ticdup; + int extratics; + int deathmatch; + int episode; + int nomonsters; + int fast_monsters; + int respawn_monsters; + int map; + int skill; + int gameversion; + int lowres_turn; + int new_sync; + int timelimit; + int loadgame; + int random; // [Strife only] + + // These fields are only used by the server when sending a game + // start message: + + int num_players; + int consoleplayer; + + // Hexen player classes: + + int player_classes[NET_MAXPLAYERS]; + +} net_gamesettings_t; + +#define NET_TICDIFF_FORWARD (1 << 0) +#define NET_TICDIFF_SIDE (1 << 1) +#define NET_TICDIFF_TURN (1 << 2) +#define NET_TICDIFF_BUTTONS (1 << 3) +#define NET_TICDIFF_CONSISTANCY (1 << 4) +#define NET_TICDIFF_CHATCHAR (1 << 5) +#define NET_TICDIFF_RAVEN (1 << 6) +#define NET_TICDIFF_STRIFE (1 << 7) + +typedef struct +{ + unsigned int diff; + ticcmd_t cmd; +} net_ticdiff_t; + +// Complete set of ticcmds from all players + +typedef struct +{ + signed int latency; + unsigned int seq; + boolean playeringame[NET_MAXPLAYERS]; + net_ticdiff_t cmds[NET_MAXPLAYERS]; +} net_full_ticcmd_t; + +// Data sent in response to server queries + +typedef struct +{ + char *version; + int server_state; + int num_players; + int max_players; + int gamemode; + int gamemission; + char *description; +} net_querydata_t; + +// Data sent by the server while waiting for the game to start. + +typedef struct +{ + int num_players; + int num_drones; + int ready_players; + int max_players; + int is_controller; + int consoleplayer; + char player_names[NET_MAXPLAYERS][MAXPLAYERNAME]; + char player_addrs[NET_MAXPLAYERS][MAXPLAYERNAME]; + sha1_digest_t wad_sha1sum; + sha1_digest_t deh_sha1sum; + int is_freedoom; +} net_waitdata_t; + +#endif /* #ifndef NET_DEFS_H */ diff --git a/firmware_p4/components/Applications/doom/net_gui.h b/firmware_p4/components/Applications/doom/net_gui.h new file mode 100644 index 000000000..4f4198b60 --- /dev/null +++ b/firmware_p4/components/Applications/doom/net_gui.h @@ -0,0 +1,29 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// Graphical stuff related to the networking code: +// +// * The client waiting screen when we are waiting for the server to +// start the game. +// + + +#ifndef NET_GUI_H +#define NET_GUI_H + +#include "doomtype.h" + +extern void NET_WaitForLaunch(void); + +#endif /* #ifndef NET_GUI_H */ + diff --git a/firmware_p4/components/Applications/doom/net_io.h b/firmware_p4/components/Applications/doom/net_io.h new file mode 100644 index 000000000..535022230 --- /dev/null +++ b/firmware_p4/components/Applications/doom/net_io.h @@ -0,0 +1,36 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Network packet manipulation (net_packet_t) +// + +#ifndef NET_IO_H +#define NET_IO_H + +#include "net_defs.h" + +extern net_addr_t net_broadcast_addr; + +net_context_t *NET_NewContext(void); +void NET_AddModule(net_context_t *context, net_module_t *module); +void NET_SendPacket(net_addr_t *addr, net_packet_t *packet); +void NET_SendBroadcast(net_context_t *context, net_packet_t *packet); +boolean NET_RecvPacket(net_context_t *context, net_addr_t **addr, + net_packet_t **packet); +char *NET_AddrToString(net_addr_t *addr); +void NET_FreeAddress(net_addr_t *addr); +net_addr_t *NET_ResolveAddress(net_context_t *context, char *address); + +#endif /* #ifndef NET_IO_H */ + diff --git a/firmware_p4/components/Applications/doom/net_loop.h b/firmware_p4/components/Applications/doom/net_loop.h new file mode 100644 index 000000000..5a2e58ee1 --- /dev/null +++ b/firmware_p4/components/Applications/doom/net_loop.h @@ -0,0 +1,27 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Loopback network module for server compiled into the client +// + +#ifndef NET_LOOP_H +#define NET_LOOP_H + +#include "net_defs.h" + +extern net_module_t net_loop_client_module; +extern net_module_t net_loop_server_module; + +#endif /* #ifndef NET_LOOP_H */ + diff --git a/firmware_p4/components/Applications/doom/net_packet.h b/firmware_p4/components/Applications/doom/net_packet.h new file mode 100644 index 000000000..ced4e4358 --- /dev/null +++ b/firmware_p4/components/Applications/doom/net_packet.h @@ -0,0 +1,44 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Definitions for use in networking code. +// + +#ifndef NET_PACKET_H +#define NET_PACKET_H + +#include "net_defs.h" + +net_packet_t *NET_NewPacket(int initial_size); +net_packet_t *NET_PacketDup(net_packet_t *packet); +void NET_FreePacket(net_packet_t *packet); + +boolean NET_ReadInt8(net_packet_t *packet, unsigned int *data); +boolean NET_ReadInt16(net_packet_t *packet, unsigned int *data); +boolean NET_ReadInt32(net_packet_t *packet, unsigned int *data); + +boolean NET_ReadSInt8(net_packet_t *packet, signed int *data); +boolean NET_ReadSInt16(net_packet_t *packet, signed int *data); +boolean NET_ReadSInt32(net_packet_t *packet, signed int *data); + +char *NET_ReadString(net_packet_t *packet); + +void NET_WriteInt8(net_packet_t *packet, unsigned int i); +void NET_WriteInt16(net_packet_t *packet, unsigned int i); +void NET_WriteInt32(net_packet_t *packet, unsigned int i); + +void NET_WriteString(net_packet_t *packet, char *string); + +#endif /* #ifndef NET_PACKET_H */ + diff --git a/firmware_p4/components/Applications/doom/net_query.h b/firmware_p4/components/Applications/doom/net_query.h new file mode 100644 index 000000000..563a055b9 --- /dev/null +++ b/firmware_p4/components/Applications/doom/net_query.h @@ -0,0 +1,44 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Querying servers to find their current status. +// + +#ifndef NET_QUERY_H +#define NET_QUERY_H + +#include "net_defs.h" + +typedef void (*net_query_callback_t)(net_addr_t *addr, + net_querydata_t *querydata, + unsigned int ping_time, + void *user_data); + +extern int NET_StartLANQuery(void); +extern int NET_StartMasterQuery(void); + +extern void NET_LANQuery(void); +extern void NET_MasterQuery(void); +extern void NET_QueryAddress(char *addr); +extern net_addr_t *NET_FindLANServer(void); + +extern int NET_Query_Poll(net_query_callback_t callback, void *user_data); + +extern net_addr_t *NET_Query_ResolveMaster(net_context_t *context); +extern void NET_Query_AddToMaster(net_addr_t *master_addr); +extern boolean NET_Query_CheckAddedToMaster(boolean *result); +extern void NET_Query_MasterResponse(net_packet_t *packet); + +#endif /* #ifndef NET_QUERY_H */ + diff --git a/firmware_p4/components/Applications/doom/net_sdl.h b/firmware_p4/components/Applications/doom/net_sdl.h new file mode 100644 index 000000000..c249de1c4 --- /dev/null +++ b/firmware_p4/components/Applications/doom/net_sdl.h @@ -0,0 +1,26 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Networking module which uses SDL_net +// + +#ifndef NET_SDL_H +#define NET_SDL_H + +#include "net_defs.h" + +extern net_module_t net_sdl_module; + +#endif /* #ifndef NET_SDL_H */ + diff --git a/firmware_p4/components/Applications/doom/net_server.h b/firmware_p4/components/Applications/doom/net_server.h new file mode 100644 index 000000000..b9de7456e --- /dev/null +++ b/firmware_p4/components/Applications/doom/net_server.h @@ -0,0 +1,42 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// Network server code +// + +#ifndef NET_SERVER_H +#define NET_SERVER_H + +// initialize server and wait for connections + +void NET_SV_Init(void); + +// run server: check for new packets received etc. + +void NET_SV_Run(void); + +// Shut down the server +// Blocks until all clients disconnect, or until a 5 second timeout + +void NET_SV_Shutdown(void); + +// Add a network module to the context used by the server + +void NET_SV_AddModule(net_module_t *module); + +// Register server with master server. + +void NET_SV_RegisterWithMaster(void); + +#endif /* #ifndef NET_SERVER_H */ + diff --git a/firmware_p4/components/Applications/doom/p_ceilng.c b/firmware_p4/components/Applications/doom/p_ceilng.c new file mode 100644 index 000000000..d1e61c898 --- /dev/null +++ b/firmware_p4/components/Applications/doom/p_ceilng.c @@ -0,0 +1,324 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: Ceiling aninmation (lowering, crushing, raising) +// + + + +#include "z_zone.h" +#include "doomdef.h" +#include "p_local.h" + +#include "s_sound.h" + +// State. +#include "doomstat.h" +#include "r_state.h" + +// Data. +#include "sounds.h" + +// +// CEILINGS +// + + +ceiling_t* activeceilings[MAXCEILINGS]; + + +// +// T_MoveCeiling +// + +void T_MoveCeiling (ceiling_t* ceiling) +{ + result_e res; + + switch(ceiling->direction) + { + case 0: + // IN STASIS + break; + case 1: + // UP + res = T_MovePlane(ceiling->sector, + ceiling->speed, + ceiling->topheight, + false,1,ceiling->direction); + + if (!(leveltime&7)) + { + switch(ceiling->type) + { + case silentCrushAndRaise: + break; + default: + S_StartSound(&ceiling->sector->soundorg, sfx_stnmov); + // ? + break; + } + } + + if (res == pastdest) + { + switch(ceiling->type) + { + case raiseToHighest: + P_RemoveActiveCeiling(ceiling); + break; + + case silentCrushAndRaise: + S_StartSound(&ceiling->sector->soundorg, sfx_pstop); + case fastCrushAndRaise: + case crushAndRaise: + ceiling->direction = -1; + break; + + default: + break; + } + + } + break; + + case -1: + // DOWN + res = T_MovePlane(ceiling->sector, + ceiling->speed, + ceiling->bottomheight, + ceiling->crush,1,ceiling->direction); + + if (!(leveltime&7)) + { + switch(ceiling->type) + { + case silentCrushAndRaise: break; + default: + S_StartSound(&ceiling->sector->soundorg, sfx_stnmov); + } + } + + if (res == pastdest) + { + switch(ceiling->type) + { + case silentCrushAndRaise: + S_StartSound(&ceiling->sector->soundorg, sfx_pstop); + case crushAndRaise: + ceiling->speed = CEILSPEED; + case fastCrushAndRaise: + ceiling->direction = 1; + break; + + case lowerAndCrush: + case lowerToFloor: + P_RemoveActiveCeiling(ceiling); + break; + + default: + break; + } + } + else // ( res != pastdest ) + { + if (res == crushed) + { + switch(ceiling->type) + { + case silentCrushAndRaise: + case crushAndRaise: + case lowerAndCrush: + ceiling->speed = CEILSPEED / 8; + break; + + default: + break; + } + } + } + break; + } +} + + +// +// EV_DoCeiling +// Move a ceiling up/down and all around! +// +int +EV_DoCeiling +( line_t* line, + ceiling_e type ) +{ + int secnum; + int rtn; + sector_t* sec; + ceiling_t* ceiling; + + secnum = -1; + rtn = 0; + + // Reactivate in-stasis ceilings...for certain types. + switch(type) + { + case fastCrushAndRaise: + case silentCrushAndRaise: + case crushAndRaise: + P_ActivateInStasisCeiling(line); + default: + break; + } + + while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0) + { + sec = §ors[secnum]; + if (sec->specialdata) + continue; + + // new door thinker + rtn = 1; + ceiling = Z_Malloc (sizeof(*ceiling), PU_LEVSPEC, 0); + P_AddThinker (&ceiling->thinker); + sec->specialdata = ceiling; + ceiling->thinker.function.acp1 = (actionf_p1)T_MoveCeiling; + ceiling->sector = sec; + ceiling->crush = false; + + switch(type) + { + case fastCrushAndRaise: + ceiling->crush = true; + ceiling->topheight = sec->ceilingheight; + ceiling->bottomheight = sec->floorheight + (8*FRACUNIT); + ceiling->direction = -1; + ceiling->speed = CEILSPEED * 2; + break; + + case silentCrushAndRaise: + case crushAndRaise: + ceiling->crush = true; + ceiling->topheight = sec->ceilingheight; + case lowerAndCrush: + case lowerToFloor: + ceiling->bottomheight = sec->floorheight; + if (type != lowerToFloor) + ceiling->bottomheight += 8*FRACUNIT; + ceiling->direction = -1; + ceiling->speed = CEILSPEED; + break; + + case raiseToHighest: + ceiling->topheight = P_FindHighestCeilingSurrounding(sec); + ceiling->direction = 1; + ceiling->speed = CEILSPEED; + break; + } + + ceiling->tag = sec->tag; + ceiling->type = type; + P_AddActiveCeiling(ceiling); + } + return rtn; +} + + +// +// Add an active ceiling +// +void P_AddActiveCeiling(ceiling_t* c) +{ + int i; + + for (i = 0; i < MAXCEILINGS;i++) + { + if (activeceilings[i] == NULL) + { + activeceilings[i] = c; + return; + } + } +} + + + +// +// Remove a ceiling's thinker +// +void P_RemoveActiveCeiling(ceiling_t* c) +{ + int i; + + for (i = 0;i < MAXCEILINGS;i++) + { + if (activeceilings[i] == c) + { + activeceilings[i]->sector->specialdata = NULL; + P_RemoveThinker (&activeceilings[i]->thinker); + activeceilings[i] = NULL; + break; + } + } +} + + + +// +// Restart a ceiling that's in-stasis +// +void P_ActivateInStasisCeiling(line_t* line) +{ + int i; + + for (i = 0;i < MAXCEILINGS;i++) + { + if (activeceilings[i] + && (activeceilings[i]->tag == line->tag) + && (activeceilings[i]->direction == 0)) + { + activeceilings[i]->direction = activeceilings[i]->olddirection; + activeceilings[i]->thinker.function.acp1 + = (actionf_p1)T_MoveCeiling; + } + } +} + + + +// +// EV_CeilingCrushStop +// Stop a ceiling from crushing! +// +int EV_CeilingCrushStop(line_t *line) +{ + int i; + int rtn; + + rtn = 0; + for (i = 0;i < MAXCEILINGS;i++) + { + if (activeceilings[i] + && (activeceilings[i]->tag == line->tag) + && (activeceilings[i]->direction != 0)) + { + activeceilings[i]->olddirection = activeceilings[i]->direction; + activeceilings[i]->thinker.function.acv = (actionf_v)NULL; + activeceilings[i]->direction = 0; // in-stasis + rtn = 1; + } + } + + + return rtn; +} diff --git a/firmware_p4/components/Applications/doom/p_doors.c b/firmware_p4/components/Applications/doom/p_doors.c new file mode 100644 index 000000000..cafab0f43 --- /dev/null +++ b/firmware_p4/components/Applications/doom/p_doors.c @@ -0,0 +1,778 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: Door animation code (opening/closing) +// + + + +#include "z_zone.h" +#include "doomdef.h" +#include "deh_main.h" +#include "p_local.h" + +#include "s_sound.h" + + +// State. +#include "doomstat.h" +#include "r_state.h" + +// Data. +#include "dstrings.h" +#include "sounds.h" + +#if 0 +// +// Sliding door frame information +// +slidename_t slideFrameNames[MAXSLIDEDOORS] = +{ + {"GDOORF1","GDOORF2","GDOORF3","GDOORF4", // front + "GDOORB1","GDOORB2","GDOORB3","GDOORB4"}, // back + + {"\0","\0","\0","\0"} +}; +#endif + + +// +// VERTICAL DOORS +// + +// +// T_VerticalDoor +// +void T_VerticalDoor (vldoor_t* door) +{ + result_e res; + + switch(door->direction) + { + case 0: + // WAITING + if (!--door->topcountdown) + { + switch(door->type) + { + case vld_blazeRaise: + door->direction = -1; // time to go back down + S_StartSound(&door->sector->soundorg, sfx_bdcls); + break; + + case vld_normal: + door->direction = -1; // time to go back down + S_StartSound(&door->sector->soundorg, sfx_dorcls); + break; + + case vld_close30ThenOpen: + door->direction = 1; + S_StartSound(&door->sector->soundorg, sfx_doropn); + break; + + default: + break; + } + } + break; + + case 2: + // INITIAL WAIT + if (!--door->topcountdown) + { + switch(door->type) + { + case vld_raiseIn5Mins: + door->direction = 1; + door->type = vld_normal; + S_StartSound(&door->sector->soundorg, sfx_doropn); + break; + + default: + break; + } + } + break; + + case -1: + // DOWN + res = T_MovePlane(door->sector, + door->speed, + door->sector->floorheight, + false,1,door->direction); + if (res == pastdest) + { + switch(door->type) + { + case vld_blazeRaise: + case vld_blazeClose: + door->sector->specialdata = NULL; + P_RemoveThinker (&door->thinker); // unlink and free + S_StartSound(&door->sector->soundorg, sfx_bdcls); + break; + + case vld_normal: + case vld_close: + door->sector->specialdata = NULL; + P_RemoveThinker (&door->thinker); // unlink and free + break; + + case vld_close30ThenOpen: + door->direction = 0; + door->topcountdown = TICRATE*30; + break; + + default: + break; + } + } + else if (res == crushed) + { + switch(door->type) + { + case vld_blazeClose: + case vld_close: // DO NOT GO BACK UP! + break; + + default: + door->direction = 1; + S_StartSound(&door->sector->soundorg, sfx_doropn); + break; + } + } + break; + + case 1: + // UP + res = T_MovePlane(door->sector, + door->speed, + door->topheight, + false,1,door->direction); + + if (res == pastdest) + { + switch(door->type) + { + case vld_blazeRaise: + case vld_normal: + door->direction = 0; // wait at top + door->topcountdown = door->topwait; + break; + + case vld_close30ThenOpen: + case vld_blazeOpen: + case vld_open: + door->sector->specialdata = NULL; + P_RemoveThinker (&door->thinker); // unlink and free + break; + + default: + break; + } + } + break; + } +} + + +// +// EV_DoLockedDoor +// Move a locked door up/down +// + +int +EV_DoLockedDoor +( line_t* line, + vldoor_e type, + mobj_t* thing ) +{ + player_t* p; + + p = thing->player; + + if (!p) + return 0; + + switch(line->special) + { + case 99: // Blue Lock + case 133: + if ( !p ) + return 0; + if (!p->cards[it_bluecard] && !p->cards[it_blueskull]) + { + p->message = DEH_String(PD_BLUEO); + S_StartSound(NULL,sfx_oof); + return 0; + } + break; + + case 134: // Red Lock + case 135: + if ( !p ) + return 0; + if (!p->cards[it_redcard] && !p->cards[it_redskull]) + { + p->message = DEH_String(PD_REDO); + S_StartSound(NULL,sfx_oof); + return 0; + } + break; + + case 136: // Yellow Lock + case 137: + if ( !p ) + return 0; + if (!p->cards[it_yellowcard] && + !p->cards[it_yellowskull]) + { + p->message = DEH_String(PD_YELLOWO); + S_StartSound(NULL,sfx_oof); + return 0; + } + break; + } + + return EV_DoDoor(line,type); +} + + +int +EV_DoDoor +( line_t* line, + vldoor_e type ) +{ + int secnum,rtn; + sector_t* sec; + vldoor_t* door; + + secnum = -1; + rtn = 0; + + while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0) + { + sec = §ors[secnum]; + if (sec->specialdata) + continue; + + + // new door thinker + rtn = 1; + door = Z_Malloc (sizeof(*door), PU_LEVSPEC, 0); + P_AddThinker (&door->thinker); + sec->specialdata = door; + + door->thinker.function.acp1 = (actionf_p1) T_VerticalDoor; + door->sector = sec; + door->type = type; + door->topwait = VDOORWAIT; + door->speed = VDOORSPEED; + + switch(type) + { + case vld_blazeClose: + door->topheight = P_FindLowestCeilingSurrounding(sec); + door->topheight -= 4*FRACUNIT; + door->direction = -1; + door->speed = VDOORSPEED * 4; + S_StartSound(&door->sector->soundorg, sfx_bdcls); + break; + + case vld_close: + door->topheight = P_FindLowestCeilingSurrounding(sec); + door->topheight -= 4*FRACUNIT; + door->direction = -1; + S_StartSound(&door->sector->soundorg, sfx_dorcls); + break; + + case vld_close30ThenOpen: + door->topheight = sec->ceilingheight; + door->direction = -1; + S_StartSound(&door->sector->soundorg, sfx_dorcls); + break; + + case vld_blazeRaise: + case vld_blazeOpen: + door->direction = 1; + door->topheight = P_FindLowestCeilingSurrounding(sec); + door->topheight -= 4*FRACUNIT; + door->speed = VDOORSPEED * 4; + if (door->topheight != sec->ceilingheight) + S_StartSound(&door->sector->soundorg, sfx_bdopn); + break; + + case vld_normal: + case vld_open: + door->direction = 1; + door->topheight = P_FindLowestCeilingSurrounding(sec); + door->topheight -= 4*FRACUNIT; + if (door->topheight != sec->ceilingheight) + S_StartSound(&door->sector->soundorg, sfx_doropn); + break; + + default: + break; + } + + } + return rtn; +} + + +// +// EV_VerticalDoor : open a door manually, no tag value +// +void +EV_VerticalDoor +( line_t* line, + mobj_t* thing ) +{ + player_t* player; + sector_t* sec; + vldoor_t* door; + int side; + + side = 0; // only front sides can be used + + // Check for locks + player = thing->player; + + switch(line->special) + { + case 26: // Blue Lock + case 32: + if ( !player ) + return; + + if (!player->cards[it_bluecard] && !player->cards[it_blueskull]) + { + player->message = DEH_String(PD_BLUEK); + S_StartSound(NULL,sfx_oof); + return; + } + break; + + case 27: // Yellow Lock + case 34: + if ( !player ) + return; + + if (!player->cards[it_yellowcard] && + !player->cards[it_yellowskull]) + { + player->message = DEH_String(PD_YELLOWK); + S_StartSound(NULL,sfx_oof); + return; + } + break; + + case 28: // Red Lock + case 33: + if ( !player ) + return; + + if (!player->cards[it_redcard] && !player->cards[it_redskull]) + { + player->message = DEH_String(PD_REDK); + S_StartSound(NULL,sfx_oof); + return; + } + break; + } + + // if the sector has an active thinker, use it + sec = sides[ line->sidenum[side^1]] .sector; + + if (sec->specialdata) + { + door = sec->specialdata; + switch(line->special) + { + case 1: // ONLY FOR "RAISE" DOORS, NOT "OPEN"s + case 26: + case 27: + case 28: + case 117: + if (door->direction == -1) + door->direction = 1; // go back up + else + { + if (!thing->player) + return; // JDC: bad guys never close doors + + // When is a door not a door? + // In Vanilla, door->direction is set, even though + // "specialdata" might not actually point at a door. + + if (door->thinker.function.acp1 == (actionf_p1) T_VerticalDoor) + { + door->direction = -1; // start going down immediately + } + else if (door->thinker.function.acp1 == (actionf_p1) T_PlatRaise) + { + // Erm, this is a plat, not a door. + // This notably causes a problem in ep1-0500.lmp where + // a plat and a door are cross-referenced; the door + // doesn't open on 64-bit. + // The direction field in vldoor_t corresponds to the wait + // field in plat_t. Let's set that to -1 instead. + + plat_t *plat; + + plat = (plat_t *) door; + plat->wait = -1; + } + else + { + // This isn't a door OR a plat. Now we're in trouble. + + fprintf(stderr, "EV_VerticalDoor: Tried to close " + "something that wasn't a door.\n"); + + // Try closing it anyway. At least it will work on 32-bit + // machines. + + door->direction = -1; + } + } + return; + } + } + + // for proper sound + switch(line->special) + { + case 117: // BLAZING DOOR RAISE + case 118: // BLAZING DOOR OPEN + S_StartSound(&sec->soundorg,sfx_bdopn); + break; + + case 1: // NORMAL DOOR SOUND + case 31: + S_StartSound(&sec->soundorg,sfx_doropn); + break; + + default: // LOCKED DOOR SOUND + S_StartSound(&sec->soundorg,sfx_doropn); + break; + } + + + // new door thinker + door = Z_Malloc (sizeof(*door), PU_LEVSPEC, 0); + P_AddThinker (&door->thinker); + sec->specialdata = door; + door->thinker.function.acp1 = (actionf_p1) T_VerticalDoor; + door->sector = sec; + door->direction = 1; + door->speed = VDOORSPEED; + door->topwait = VDOORWAIT; + + switch(line->special) + { + case 1: + case 26: + case 27: + case 28: + door->type = vld_normal; + break; + + case 31: + case 32: + case 33: + case 34: + door->type = vld_open; + line->special = 0; + break; + + case 117: // blazing door raise + door->type = vld_blazeRaise; + door->speed = VDOORSPEED*4; + break; + case 118: // blazing door open + door->type = vld_blazeOpen; + line->special = 0; + door->speed = VDOORSPEED*4; + break; + } + + // find the top and bottom of the movement range + door->topheight = P_FindLowestCeilingSurrounding(sec); + door->topheight -= 4*FRACUNIT; +} + + +// +// Spawn a door that closes after 30 seconds +// +void P_SpawnDoorCloseIn30 (sector_t* sec) +{ + vldoor_t* door; + + door = Z_Malloc ( sizeof(*door), PU_LEVSPEC, 0); + + P_AddThinker (&door->thinker); + + sec->specialdata = door; + sec->special = 0; + + door->thinker.function.acp1 = (actionf_p1)T_VerticalDoor; + door->sector = sec; + door->direction = 0; + door->type = vld_normal; + door->speed = VDOORSPEED; + door->topcountdown = 30 * TICRATE; +} + +// +// Spawn a door that opens after 5 minutes +// +void +P_SpawnDoorRaiseIn5Mins +( sector_t* sec, + int secnum ) +{ + vldoor_t* door; + + door = Z_Malloc ( sizeof(*door), PU_LEVSPEC, 0); + + P_AddThinker (&door->thinker); + + sec->specialdata = door; + sec->special = 0; + + door->thinker.function.acp1 = (actionf_p1)T_VerticalDoor; + door->sector = sec; + door->direction = 2; + door->type = vld_raiseIn5Mins; + door->speed = VDOORSPEED; + door->topheight = P_FindLowestCeilingSurrounding(sec); + door->topheight -= 4*FRACUNIT; + door->topwait = VDOORWAIT; + door->topcountdown = 5 * 60 * TICRATE; +} + + + +// UNUSED +// Separate into p_slidoor.c? + +#if 0 // ABANDONED TO THE MISTS OF TIME!!! +// +// EV_SlidingDoor : slide a door horizontally +// (animate midtexture, then set noblocking line) +// + + +slideframe_t slideFrames[MAXSLIDEDOORS]; + +void P_InitSlidingDoorFrames(void) +{ + int i; + int f1; + int f2; + int f3; + int f4; + + // DOOM II ONLY... + if ( gamemode != commercial) + return; + + for (i = 0;i < MAXSLIDEDOORS; i++) + { + if (!slideFrameNames[i].frontFrame1[0]) + break; + + f1 = R_TextureNumForName(slideFrameNames[i].frontFrame1); + f2 = R_TextureNumForName(slideFrameNames[i].frontFrame2); + f3 = R_TextureNumForName(slideFrameNames[i].frontFrame3); + f4 = R_TextureNumForName(slideFrameNames[i].frontFrame4); + + slideFrames[i].frontFrames[0] = f1; + slideFrames[i].frontFrames[1] = f2; + slideFrames[i].frontFrames[2] = f3; + slideFrames[i].frontFrames[3] = f4; + + f1 = R_TextureNumForName(slideFrameNames[i].backFrame1); + f2 = R_TextureNumForName(slideFrameNames[i].backFrame2); + f3 = R_TextureNumForName(slideFrameNames[i].backFrame3); + f4 = R_TextureNumForName(slideFrameNames[i].backFrame4); + + slideFrames[i].backFrames[0] = f1; + slideFrames[i].backFrames[1] = f2; + slideFrames[i].backFrames[2] = f3; + slideFrames[i].backFrames[3] = f4; + } +} + + +// +// Return index into "slideFrames" array +// for which door type to use +// +int P_FindSlidingDoorType(line_t* line) +{ + int i; + int val; + + for (i = 0;i < MAXSLIDEDOORS;i++) + { + val = sides[line->sidenum[0]].midtexture; + if (val == slideFrames[i].frontFrames[0]) + return i; + } + + return -1; +} + +void T_SlidingDoor (slidedoor_t* door) +{ + switch(door->status) + { + case sd_opening: + if (!door->timer--) + { + if (++door->frame == SNUMFRAMES) + { + // IF DOOR IS DONE OPENING... + sides[door->line->sidenum[0]].midtexture = 0; + sides[door->line->sidenum[1]].midtexture = 0; + door->line->flags &= ML_BLOCKING^0xff; + + if (door->type == sdt_openOnly) + { + door->frontsector->specialdata = NULL; + P_RemoveThinker (&door->thinker); + break; + } + + door->timer = SDOORWAIT; + door->status = sd_waiting; + } + else + { + // IF DOOR NEEDS TO ANIMATE TO NEXT FRAME... + door->timer = SWAITTICS; + + sides[door->line->sidenum[0]].midtexture = + slideFrames[door->whichDoorIndex]. + frontFrames[door->frame]; + sides[door->line->sidenum[1]].midtexture = + slideFrames[door->whichDoorIndex]. + backFrames[door->frame]; + } + } + break; + + case sd_waiting: + // IF DOOR IS DONE WAITING... + if (!door->timer--) + { + // CAN DOOR CLOSE? + if (door->frontsector->thinglist != NULL || + door->backsector->thinglist != NULL) + { + door->timer = SDOORWAIT; + break; + } + + //door->frame = SNUMFRAMES-1; + door->status = sd_closing; + door->timer = SWAITTICS; + } + break; + + case sd_closing: + if (!door->timer--) + { + if (--door->frame < 0) + { + // IF DOOR IS DONE CLOSING... + door->line->flags |= ML_BLOCKING; + door->frontsector->specialdata = NULL; + P_RemoveThinker (&door->thinker); + break; + } + else + { + // IF DOOR NEEDS TO ANIMATE TO NEXT FRAME... + door->timer = SWAITTICS; + + sides[door->line->sidenum[0]].midtexture = + slideFrames[door->whichDoorIndex]. + frontFrames[door->frame]; + sides[door->line->sidenum[1]].midtexture = + slideFrames[door->whichDoorIndex]. + backFrames[door->frame]; + } + } + break; + } +} + + + +void +EV_SlidingDoor +( line_t* line, + mobj_t* thing ) +{ + sector_t* sec; + slidedoor_t* door; + + // DOOM II ONLY... + if (gamemode != commercial) + return; + + // Make sure door isn't already being animated + sec = line->frontsector; + door = NULL; + if (sec->specialdata) + { + if (!thing->player) + return; + + door = sec->specialdata; + if (door->type == sdt_openAndClose) + { + if (door->status == sd_waiting) + door->status = sd_closing; + } + else + return; + } + + // Init sliding door vars + if (!door) + { + door = Z_Malloc (sizeof(*door), PU_LEVSPEC, 0); + P_AddThinker (&door->thinker); + sec->specialdata = door; + + door->type = sdt_openAndClose; + door->status = sd_opening; + door->whichDoorIndex = P_FindSlidingDoorType(line); + + if (door->whichDoorIndex < 0) + I_Error("EV_SlidingDoor: Can't use texture for sliding door!"); + + door->frontsector = sec; + door->backsector = line->backsector; + door->thinker.function = T_SlidingDoor; + door->timer = SWAITTICS; + door->frame = 0; + door->line = line; + } +} +#endif diff --git a/firmware_p4/components/Applications/doom/p_enemy.c b/firmware_p4/components/Applications/doom/p_enemy.c new file mode 100644 index 000000000..f2b44d139 --- /dev/null +++ b/firmware_p4/components/Applications/doom/p_enemy.c @@ -0,0 +1,2006 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Enemy thinking, AI. +// Action Pointer Functions +// that are associated with states/frames. +// + +#include +#include + +#include "m_random.h" +#include "i_system.h" + +#include "doomdef.h" +#include "p_local.h" + +#include "s_sound.h" + +#include "g_game.h" + +// State. +#include "doomstat.h" +#include "r_state.h" + +// Data. +#include "sounds.h" + + + + +typedef enum +{ + DI_EAST, + DI_NORTHEAST, + DI_NORTH, + DI_NORTHWEST, + DI_WEST, + DI_SOUTHWEST, + DI_SOUTH, + DI_SOUTHEAST, + DI_NODIR, + NUMDIRS + +} dirtype_t; + + +// +// P_NewChaseDir related LUT. +// +dirtype_t opposite[] = +{ + DI_WEST, DI_SOUTHWEST, DI_SOUTH, DI_SOUTHEAST, + DI_EAST, DI_NORTHEAST, DI_NORTH, DI_NORTHWEST, DI_NODIR +}; + +dirtype_t diags[] = +{ + DI_NORTHWEST, DI_NORTHEAST, DI_SOUTHWEST, DI_SOUTHEAST +}; + + + + + +void A_Fall (mobj_t *actor); + + +// +// ENEMY THINKING +// Enemies are allways spawned +// with targetplayer = -1, threshold = 0 +// Most monsters are spawned unaware of all players, +// but some can be made preaware +// + + +// +// Called by P_NoiseAlert. +// Recursively traverse adjacent sectors, +// sound blocking lines cut off traversal. +// + +mobj_t* soundtarget; + +void +P_RecursiveSound +( sector_t* sec, + int soundblocks ) +{ + int i; + line_t* check; + sector_t* other; + + // wake up all monsters in this sector + if (sec->validcount == validcount + && sec->soundtraversed <= soundblocks+1) + { + return; // already flooded + } + + sec->validcount = validcount; + sec->soundtraversed = soundblocks+1; + sec->soundtarget = soundtarget; + + for (i=0 ;ilinecount ; i++) + { + check = sec->lines[i]; + if (! (check->flags & ML_TWOSIDED) ) + continue; + + P_LineOpening (check); + + if (openrange <= 0) + continue; // closed door + + if ( sides[ check->sidenum[0] ].sector == sec) + other = sides[ check->sidenum[1] ] .sector; + else + other = sides[ check->sidenum[0] ].sector; + + if (check->flags & ML_SOUNDBLOCK) + { + if (!soundblocks) + P_RecursiveSound (other, 1); + } + else + P_RecursiveSound (other, soundblocks); + } +} + + + +// +// P_NoiseAlert +// If a monster yells at a player, +// it will alert other monsters to the player. +// +void +P_NoiseAlert +( mobj_t* target, + mobj_t* emmiter ) +{ + soundtarget = target; + validcount++; + P_RecursiveSound (emmiter->subsector->sector, 0); +} + + + + +// +// P_CheckMeleeRange +// +boolean P_CheckMeleeRange (mobj_t* actor) +{ + mobj_t* pl; + fixed_t dist; + + if (!actor->target) + return false; + + pl = actor->target; + dist = P_AproxDistance (pl->x-actor->x, pl->y-actor->y); + + if (dist >= MELEERANGE-20*FRACUNIT+pl->info->radius) + return false; + + if (! P_CheckSight (actor, actor->target) ) + return false; + + return true; +} + +// +// P_CheckMissileRange +// +boolean P_CheckMissileRange (mobj_t* actor) +{ + fixed_t dist; + + if (! P_CheckSight (actor, actor->target) ) + return false; + + if ( actor->flags & MF_JUSTHIT ) + { + // the target just hit the enemy, + // so fight back! + actor->flags &= ~MF_JUSTHIT; + return true; + } + + if (actor->reactiontime) + return false; // do not attack yet + + // OPTIMIZE: get this from a global checksight + dist = P_AproxDistance ( actor->x-actor->target->x, + actor->y-actor->target->y) - 64*FRACUNIT; + + if (!actor->info->meleestate) + dist -= 128*FRACUNIT; // no melee attack, so fire more + + dist >>= 16; + + if (actor->type == MT_VILE) + { + if (dist > 14*64) + return false; // too far away + } + + + if (actor->type == MT_UNDEAD) + { + if (dist < 196) + return false; // close for fist attack + dist >>= 1; + } + + + if (actor->type == MT_CYBORG + || actor->type == MT_SPIDER + || actor->type == MT_SKULL) + { + dist >>= 1; + } + + if (dist > 200) + dist = 200; + + if (actor->type == MT_CYBORG && dist > 160) + dist = 160; + + if (P_Random () < dist) + return false; + + return true; +} + + +// +// P_Move +// Move in the current direction, +// returns false if the move is blocked. +// +fixed_t xspeed[8] = {FRACUNIT,47000,0,-47000,-FRACUNIT,-47000,0,47000}; +fixed_t yspeed[8] = {0,47000,FRACUNIT,47000,0,-47000,-FRACUNIT,-47000}; + +boolean P_Move (mobj_t* actor) +{ + fixed_t tryx; + fixed_t tryy; + + line_t* ld; + + // warning: 'catch', 'throw', and 'try' + // are all C++ reserved words + boolean try_ok; + boolean good; + + if (actor->movedir == DI_NODIR) + return false; + + if ((unsigned)actor->movedir >= 8) + I_Error ("Weird actor->movedir!"); + + tryx = actor->x + actor->info->speed*xspeed[actor->movedir]; + tryy = actor->y + actor->info->speed*yspeed[actor->movedir]; + + try_ok = P_TryMove (actor, tryx, tryy); + + if (!try_ok) + { + // open any specials + if (actor->flags & MF_FLOAT && floatok) + { + // must adjust height + if (actor->z < tmfloorz) + actor->z += FLOATSPEED; + else + actor->z -= FLOATSPEED; + + actor->flags |= MF_INFLOAT; + return true; + } + + if (!numspechit) + return false; + + actor->movedir = DI_NODIR; + good = false; + while (numspechit--) + { + ld = spechit[numspechit]; + // if the special is not a door + // that can be opened, + // return false + if (P_UseSpecialLine (actor, ld,0)) + good = true; + } + return good; + } + else + { + actor->flags &= ~MF_INFLOAT; + } + + + if (! (actor->flags & MF_FLOAT) ) + actor->z = actor->floorz; + return true; +} + + +// +// TryWalk +// Attempts to move actor on +// in its current (ob->moveangle) direction. +// If blocked by either a wall or an actor +// returns FALSE +// If move is either clear or blocked only by a door, +// returns TRUE and sets... +// If a door is in the way, +// an OpenDoor call is made to start it opening. +// +boolean P_TryWalk (mobj_t* actor) +{ + if (!P_Move (actor)) + { + return false; + } + + actor->movecount = P_Random()&15; + return true; +} + + + + +void P_NewChaseDir (mobj_t* actor) +{ + fixed_t deltax; + fixed_t deltay; + + dirtype_t d[3]; + + int tdir; + dirtype_t olddir; + + dirtype_t turnaround; + + if (!actor->target) + I_Error ("P_NewChaseDir: called with no target"); + + olddir = actor->movedir; + turnaround=opposite[olddir]; + + deltax = actor->target->x - actor->x; + deltay = actor->target->y - actor->y; + + if (deltax>10*FRACUNIT) + d[1]= DI_EAST; + else if (deltax<-10*FRACUNIT) + d[1]= DI_WEST; + else + d[1]=DI_NODIR; + + if (deltay<-10*FRACUNIT) + d[2]= DI_SOUTH; + else if (deltay>10*FRACUNIT) + d[2]= DI_NORTH; + else + d[2]=DI_NODIR; + + // try direct route + if (d[1] != DI_NODIR + && d[2] != DI_NODIR) + { + actor->movedir = diags[((deltay<0)<<1)+(deltax>0)]; + if (actor->movedir != (int) turnaround && P_TryWalk(actor)) + return; + } + + // try other directions + if (P_Random() > 200 + || abs(deltay)>abs(deltax)) + { + tdir=d[1]; + d[1]=d[2]; + d[2]=tdir; + } + + if (d[1]==turnaround) + d[1]=DI_NODIR; + if (d[2]==turnaround) + d[2]=DI_NODIR; + + if (d[1]!=DI_NODIR) + { + actor->movedir = d[1]; + if (P_TryWalk(actor)) + { + // either moved forward or attacked + return; + } + } + + if (d[2]!=DI_NODIR) + { + actor->movedir =d[2]; + + if (P_TryWalk(actor)) + return; + } + + // there is no direct path to the player, + // so pick another direction. + if (olddir!=DI_NODIR) + { + actor->movedir =olddir; + + if (P_TryWalk(actor)) + return; + } + + // randomly determine direction of search + if (P_Random()&1) + { + for ( tdir=DI_EAST; + tdir<=DI_SOUTHEAST; + tdir++ ) + { + if (tdir != (int) turnaround) + { + actor->movedir =tdir; + + if ( P_TryWalk(actor) ) + return; + } + } + } + else + { + for ( tdir=DI_SOUTHEAST; + tdir != (DI_EAST-1); + tdir-- ) + { + if (tdir != (int) turnaround) + { + actor->movedir = tdir; + + if ( P_TryWalk(actor) ) + return; + } + } + } + + if (turnaround != DI_NODIR) + { + actor->movedir =turnaround; + if ( P_TryWalk(actor) ) + return; + } + + actor->movedir = DI_NODIR; // can not move +} + + + +// +// P_LookForPlayers +// If allaround is false, only look 180 degrees in front. +// Returns true if a player is targeted. +// +boolean +P_LookForPlayers +( mobj_t* actor, + boolean allaround ) +{ + int c; + int stop; + player_t* player; + angle_t an; + fixed_t dist; + + c = 0; + stop = (actor->lastlook-1)&3; + + for ( ; ; actor->lastlook = (actor->lastlook+1)&3 ) + { + if (!playeringame[actor->lastlook]) + continue; + + if (c++ == 2 + || actor->lastlook == stop) + { + // done looking + return false; + } + + player = &players[actor->lastlook]; + + if (player->health <= 0) + continue; // dead + + if (!P_CheckSight (actor, player->mo)) + continue; // out of sight + + if (!allaround) + { + an = R_PointToAngle2 (actor->x, + actor->y, + player->mo->x, + player->mo->y) + - actor->angle; + + if (an > ANG90 && an < ANG270) + { + dist = P_AproxDistance (player->mo->x - actor->x, + player->mo->y - actor->y); + // if real close, react anyway + if (dist > MELEERANGE) + continue; // behind back + } + } + + actor->target = player->mo; + return true; + } + + return false; +} + + +// +// A_KeenDie +// DOOM II special, map 32. +// Uses special tag 666. +// +void A_KeenDie (mobj_t* mo) +{ + thinker_t* th; + mobj_t* mo2; + line_t junk; + + A_Fall (mo); + + // scan the remaining thinkers + // to see if all Keens are dead + for (th = thinkercap.next ; th != &thinkercap ; th=th->next) + { + if (th->function.acp1 != (actionf_p1)P_MobjThinker) + continue; + + mo2 = (mobj_t *)th; + if (mo2 != mo + && mo2->type == mo->type + && mo2->health > 0) + { + // other Keen not dead + return; + } + } + + junk.tag = 666; + EV_DoDoor(&junk, vld_open); +} + + +// +// ACTION ROUTINES +// + +// +// A_Look +// Stay in state until a player is sighted. +// +void A_Look (mobj_t* actor) +{ + mobj_t* targ; + + actor->threshold = 0; // any shot will wake up + targ = actor->subsector->sector->soundtarget; + + if (targ + && (targ->flags & MF_SHOOTABLE) ) + { + actor->target = targ; + + if ( actor->flags & MF_AMBUSH ) + { + if (P_CheckSight (actor, actor->target)) + goto seeyou; + } + else + goto seeyou; + } + + + if (!P_LookForPlayers (actor, false) ) + return; + + // go into chase state + seeyou: + if (actor->info->seesound) + { + int sound; + + switch (actor->info->seesound) + { + case sfx_posit1: + case sfx_posit2: + case sfx_posit3: + sound = sfx_posit1+P_Random()%3; + break; + + case sfx_bgsit1: + case sfx_bgsit2: + sound = sfx_bgsit1+P_Random()%2; + break; + + default: + sound = actor->info->seesound; + break; + } + + if (actor->type==MT_SPIDER + || actor->type == MT_CYBORG) + { + // full volume + S_StartSound (NULL, sound); + } + else + S_StartSound (actor, sound); + } + + P_SetMobjState (actor, actor->info->seestate); +} + + +// +// A_Chase +// Actor has a melee attack, +// so it tries to close as fast as possible +// +void A_Chase (mobj_t* actor) +{ + int delta; + + if (actor->reactiontime) + actor->reactiontime--; + + + // modify target threshold + if (actor->threshold) + { + if (!actor->target + || actor->target->health <= 0) + { + actor->threshold = 0; + } + else + actor->threshold--; + } + + // turn towards movement direction if not there yet + if (actor->movedir < 8) + { + actor->angle &= (7<<29); + delta = actor->angle - (actor->movedir << 29); + + if (delta > 0) + actor->angle -= ANG90/2; + else if (delta < 0) + actor->angle += ANG90/2; + } + + if (!actor->target + || !(actor->target->flags&MF_SHOOTABLE)) + { + // look for a new target + if (P_LookForPlayers(actor,true)) + return; // got a new target + + P_SetMobjState (actor, actor->info->spawnstate); + return; + } + + // do not attack twice in a row + if (actor->flags & MF_JUSTATTACKED) + { + actor->flags &= ~MF_JUSTATTACKED; + if (gameskill != sk_nightmare && !fastparm) + P_NewChaseDir (actor); + return; + } + + // check for melee attack + if (actor->info->meleestate + && P_CheckMeleeRange (actor)) + { + if (actor->info->attacksound) + S_StartSound (actor, actor->info->attacksound); + + P_SetMobjState (actor, actor->info->meleestate); + return; + } + + // check for missile attack + if (actor->info->missilestate) + { + if (gameskill < sk_nightmare + && !fastparm && actor->movecount) + { + goto nomissile; + } + + if (!P_CheckMissileRange (actor)) + goto nomissile; + + P_SetMobjState (actor, actor->info->missilestate); + actor->flags |= MF_JUSTATTACKED; + return; + } + + // ? + nomissile: + // possibly choose another target + if (netgame + && !actor->threshold + && !P_CheckSight (actor, actor->target) ) + { + if (P_LookForPlayers(actor,true)) + return; // got a new target + } + + // chase towards player + if (--actor->movecount<0 + || !P_Move (actor)) + { + P_NewChaseDir (actor); + } + + // make active sound + if (actor->info->activesound + && P_Random () < 3) + { + S_StartSound (actor, actor->info->activesound); + } +} + + +// +// A_FaceTarget +// +void A_FaceTarget (mobj_t* actor) +{ + if (!actor->target) + return; + + actor->flags &= ~MF_AMBUSH; + + actor->angle = R_PointToAngle2 (actor->x, + actor->y, + actor->target->x, + actor->target->y); + + if (actor->target->flags & MF_SHADOW) + actor->angle += (P_Random()-P_Random())<<21; +} + + +// +// A_PosAttack +// +void A_PosAttack (mobj_t* actor) +{ + int angle; + int damage; + int slope; + + if (!actor->target) + return; + + A_FaceTarget (actor); + angle = actor->angle; + slope = P_AimLineAttack (actor, angle, MISSILERANGE); + + S_StartSound (actor, sfx_pistol); + angle += (P_Random()-P_Random())<<20; + damage = ((P_Random()%5)+1)*3; + P_LineAttack (actor, angle, MISSILERANGE, slope, damage); +} + +void A_SPosAttack (mobj_t* actor) +{ + int i; + int angle; + int bangle; + int damage; + int slope; + + if (!actor->target) + return; + + S_StartSound (actor, sfx_shotgn); + A_FaceTarget (actor); + bangle = actor->angle; + slope = P_AimLineAttack (actor, bangle, MISSILERANGE); + + for (i=0 ; i<3 ; i++) + { + angle = bangle + ((P_Random()-P_Random())<<20); + damage = ((P_Random()%5)+1)*3; + P_LineAttack (actor, angle, MISSILERANGE, slope, damage); + } +} + +void A_CPosAttack (mobj_t* actor) +{ + int angle; + int bangle; + int damage; + int slope; + + if (!actor->target) + return; + + S_StartSound (actor, sfx_shotgn); + A_FaceTarget (actor); + bangle = actor->angle; + slope = P_AimLineAttack (actor, bangle, MISSILERANGE); + + angle = bangle + ((P_Random()-P_Random())<<20); + damage = ((P_Random()%5)+1)*3; + P_LineAttack (actor, angle, MISSILERANGE, slope, damage); +} + +void A_CPosRefire (mobj_t* actor) +{ + // keep firing unless target got out of sight + A_FaceTarget (actor); + + if (P_Random () < 40) + return; + + if (!actor->target + || actor->target->health <= 0 + || !P_CheckSight (actor, actor->target) ) + { + P_SetMobjState (actor, actor->info->seestate); + } +} + + +void A_SpidRefire (mobj_t* actor) +{ + // keep firing unless target got out of sight + A_FaceTarget (actor); + + if (P_Random () < 10) + return; + + if (!actor->target + || actor->target->health <= 0 + || !P_CheckSight (actor, actor->target) ) + { + P_SetMobjState (actor, actor->info->seestate); + } +} + +void A_BspiAttack (mobj_t *actor) +{ + if (!actor->target) + return; + + A_FaceTarget (actor); + + // launch a missile + P_SpawnMissile (actor, actor->target, MT_ARACHPLAZ); +} + + +// +// A_TroopAttack +// +void A_TroopAttack (mobj_t* actor) +{ + int damage; + + if (!actor->target) + return; + + A_FaceTarget (actor); + if (P_CheckMeleeRange (actor)) + { + S_StartSound (actor, sfx_claw); + damage = (P_Random()%8+1)*3; + P_DamageMobj (actor->target, actor, actor, damage); + return; + } + + + // launch a missile + P_SpawnMissile (actor, actor->target, MT_TROOPSHOT); +} + + +void A_SargAttack (mobj_t* actor) +{ + int damage; + + if (!actor->target) + return; + + A_FaceTarget (actor); + if (P_CheckMeleeRange (actor)) + { + damage = ((P_Random()%10)+1)*4; + P_DamageMobj (actor->target, actor, actor, damage); + } +} + +void A_HeadAttack (mobj_t* actor) +{ + int damage; + + if (!actor->target) + return; + + A_FaceTarget (actor); + if (P_CheckMeleeRange (actor)) + { + damage = (P_Random()%6+1)*10; + P_DamageMobj (actor->target, actor, actor, damage); + return; + } + + // launch a missile + P_SpawnMissile (actor, actor->target, MT_HEADSHOT); +} + +void A_CyberAttack (mobj_t* actor) +{ + if (!actor->target) + return; + + A_FaceTarget (actor); + P_SpawnMissile (actor, actor->target, MT_ROCKET); +} + + +void A_BruisAttack (mobj_t* actor) +{ + int damage; + + if (!actor->target) + return; + + if (P_CheckMeleeRange (actor)) + { + S_StartSound (actor, sfx_claw); + damage = (P_Random()%8+1)*10; + P_DamageMobj (actor->target, actor, actor, damage); + return; + } + + // launch a missile + P_SpawnMissile (actor, actor->target, MT_BRUISERSHOT); +} + + +// +// A_SkelMissile +// +void A_SkelMissile (mobj_t* actor) +{ + mobj_t* mo; + + if (!actor->target) + return; + + A_FaceTarget (actor); + actor->z += 16*FRACUNIT; // so missile spawns higher + mo = P_SpawnMissile (actor, actor->target, MT_TRACER); + actor->z -= 16*FRACUNIT; // back to normal + + mo->x += mo->momx; + mo->y += mo->momy; + mo->tracer = actor->target; +} + +int TRACEANGLE = 0xc000000; + +void A_Tracer (mobj_t* actor) +{ + angle_t exact; + fixed_t dist; + fixed_t slope; + mobj_t* dest; + mobj_t* th; + + if (gametic & 3) + return; + + // spawn a puff of smoke behind the rocket + P_SpawnPuff (actor->x, actor->y, actor->z); + + th = P_SpawnMobj (actor->x-actor->momx, + actor->y-actor->momy, + actor->z, MT_SMOKE); + + th->momz = FRACUNIT; + th->tics -= P_Random()&3; + if (th->tics < 1) + th->tics = 1; + + // adjust direction + dest = actor->tracer; + + if (!dest || dest->health <= 0) + return; + + // change angle + exact = R_PointToAngle2 (actor->x, + actor->y, + dest->x, + dest->y); + + if (exact != actor->angle) + { + if (exact - actor->angle > 0x80000000) + { + actor->angle -= TRACEANGLE; + if (exact - actor->angle < 0x80000000) + actor->angle = exact; + } + else + { + actor->angle += TRACEANGLE; + if (exact - actor->angle > 0x80000000) + actor->angle = exact; + } + } + + exact = actor->angle>>ANGLETOFINESHIFT; + actor->momx = FixedMul (actor->info->speed, finecosine[exact]); + actor->momy = FixedMul (actor->info->speed, finesine[exact]); + + // change slope + dist = P_AproxDistance (dest->x - actor->x, + dest->y - actor->y); + + dist = dist / actor->info->speed; + + if (dist < 1) + dist = 1; + slope = (dest->z+40*FRACUNIT - actor->z) / dist; + + if (slope < actor->momz) + actor->momz -= FRACUNIT/8; + else + actor->momz += FRACUNIT/8; +} + + +void A_SkelWhoosh (mobj_t* actor) +{ + if (!actor->target) + return; + A_FaceTarget (actor); + S_StartSound (actor,sfx_skeswg); +} + +void A_SkelFist (mobj_t* actor) +{ + int damage; + + if (!actor->target) + return; + + A_FaceTarget (actor); + + if (P_CheckMeleeRange (actor)) + { + damage = ((P_Random()%10)+1)*6; + S_StartSound (actor, sfx_skepch); + P_DamageMobj (actor->target, actor, actor, damage); + } +} + + + +// +// PIT_VileCheck +// Detect a corpse that could be raised. +// +mobj_t* corpsehit; +mobj_t* vileobj; +fixed_t viletryx; +fixed_t viletryy; + +boolean PIT_VileCheck (mobj_t* thing) +{ + int maxdist; + boolean check; + + if (!(thing->flags & MF_CORPSE) ) + return true; // not a monster + + if (thing->tics != -1) + return true; // not lying still yet + + if (thing->info->raisestate == S_NULL) + return true; // monster doesn't have a raise state + + maxdist = thing->info->radius + mobjinfo[MT_VILE].radius; + + if ( abs(thing->x - viletryx) > maxdist + || abs(thing->y - viletryy) > maxdist ) + return true; // not actually touching + + corpsehit = thing; + corpsehit->momx = corpsehit->momy = 0; + corpsehit->height <<= 2; + check = P_CheckPosition (corpsehit, corpsehit->x, corpsehit->y); + corpsehit->height >>= 2; + + if (!check) + return true; // doesn't fit here + + return false; // got one, so stop checking +} + + + +// +// A_VileChase +// Check for ressurecting a body +// +void A_VileChase (mobj_t* actor) +{ + int xl; + int xh; + int yl; + int yh; + + int bx; + int by; + + mobjinfo_t* info; + mobj_t* temp; + + if (actor->movedir != DI_NODIR) + { + // check for corpses to raise + viletryx = + actor->x + actor->info->speed*xspeed[actor->movedir]; + viletryy = + actor->y + actor->info->speed*yspeed[actor->movedir]; + + xl = (viletryx - bmaporgx - MAXRADIUS*2)>>MAPBLOCKSHIFT; + xh = (viletryx - bmaporgx + MAXRADIUS*2)>>MAPBLOCKSHIFT; + yl = (viletryy - bmaporgy - MAXRADIUS*2)>>MAPBLOCKSHIFT; + yh = (viletryy - bmaporgy + MAXRADIUS*2)>>MAPBLOCKSHIFT; + + vileobj = actor; + for (bx=xl ; bx<=xh ; bx++) + { + for (by=yl ; by<=yh ; by++) + { + // Call PIT_VileCheck to check + // whether object is a corpse + // that canbe raised. + if (!P_BlockThingsIterator(bx,by,PIT_VileCheck)) + { + // got one! + temp = actor->target; + actor->target = corpsehit; + A_FaceTarget (actor); + actor->target = temp; + + P_SetMobjState (actor, S_VILE_HEAL1); + S_StartSound (corpsehit, sfx_slop); + info = corpsehit->info; + + P_SetMobjState (corpsehit,info->raisestate); + corpsehit->height <<= 2; + corpsehit->flags = info->flags; + corpsehit->health = info->spawnhealth; + corpsehit->target = NULL; + + return; + } + } + } + } + + // Return to normal attack. + A_Chase (actor); +} + + +// +// A_VileStart +// +void A_VileStart (mobj_t* actor) +{ + S_StartSound (actor, sfx_vilatk); +} + + +// +// A_Fire +// Keep fire in front of player unless out of sight +// +void A_Fire (mobj_t* actor); + +void A_StartFire (mobj_t* actor) +{ + S_StartSound(actor,sfx_flamst); + A_Fire(actor); +} + +void A_FireCrackle (mobj_t* actor) +{ + S_StartSound(actor,sfx_flame); + A_Fire(actor); +} + +void A_Fire (mobj_t* actor) +{ + mobj_t* dest; + mobj_t* target; + unsigned an; + + dest = actor->tracer; + if (!dest) + return; + + target = P_SubstNullMobj(actor->target); + + // don't move it if the vile lost sight + if (!P_CheckSight (target, dest) ) + return; + + an = dest->angle >> ANGLETOFINESHIFT; + + P_UnsetThingPosition (actor); + actor->x = dest->x + FixedMul (24*FRACUNIT, finecosine[an]); + actor->y = dest->y + FixedMul (24*FRACUNIT, finesine[an]); + actor->z = dest->z; + P_SetThingPosition (actor); +} + + + +// +// A_VileTarget +// Spawn the hellfire +// +void A_VileTarget (mobj_t* actor) +{ + mobj_t* fog; + + if (!actor->target) + return; + + A_FaceTarget (actor); + + fog = P_SpawnMobj (actor->target->x, + actor->target->x, + actor->target->z, MT_FIRE); + + actor->tracer = fog; + fog->target = actor; + fog->tracer = actor->target; + A_Fire (fog); +} + + + + +// +// A_VileAttack +// +void A_VileAttack (mobj_t* actor) +{ + mobj_t* fire; + int an; + + if (!actor->target) + return; + + A_FaceTarget (actor); + + if (!P_CheckSight (actor, actor->target) ) + return; + + S_StartSound (actor, sfx_barexp); + P_DamageMobj (actor->target, actor, actor, 20); + actor->target->momz = 1000*FRACUNIT/actor->target->info->mass; + + an = actor->angle >> ANGLETOFINESHIFT; + + fire = actor->tracer; + + if (!fire) + return; + + // move the fire between the vile and the player + fire->x = actor->target->x - FixedMul (24*FRACUNIT, finecosine[an]); + fire->y = actor->target->y - FixedMul (24*FRACUNIT, finesine[an]); + P_RadiusAttack (fire, actor, 70 ); +} + + + + +// +// Mancubus attack, +// firing three missiles (bruisers) +// in three different directions? +// Doesn't look like it. +// +#define FATSPREAD (ANG90/8) + +void A_FatRaise (mobj_t *actor) +{ + A_FaceTarget (actor); + S_StartSound (actor, sfx_manatk); +} + + +void A_FatAttack1 (mobj_t* actor) +{ + mobj_t* mo; + mobj_t* target; + int an; + + A_FaceTarget (actor); + + // Change direction to ... + actor->angle += FATSPREAD; + target = P_SubstNullMobj(actor->target); + P_SpawnMissile (actor, target, MT_FATSHOT); + + mo = P_SpawnMissile (actor, target, MT_FATSHOT); + mo->angle += FATSPREAD; + an = mo->angle >> ANGLETOFINESHIFT; + mo->momx = FixedMul (mo->info->speed, finecosine[an]); + mo->momy = FixedMul (mo->info->speed, finesine[an]); +} + +void A_FatAttack2 (mobj_t* actor) +{ + mobj_t* mo; + mobj_t* target; + int an; + + A_FaceTarget (actor); + // Now here choose opposite deviation. + actor->angle -= FATSPREAD; + target = P_SubstNullMobj(actor->target); + P_SpawnMissile (actor, target, MT_FATSHOT); + + mo = P_SpawnMissile (actor, target, MT_FATSHOT); + mo->angle -= FATSPREAD*2; + an = mo->angle >> ANGLETOFINESHIFT; + mo->momx = FixedMul (mo->info->speed, finecosine[an]); + mo->momy = FixedMul (mo->info->speed, finesine[an]); +} + +void A_FatAttack3 (mobj_t* actor) +{ + mobj_t* mo; + mobj_t* target; + int an; + + A_FaceTarget (actor); + + target = P_SubstNullMobj(actor->target); + + mo = P_SpawnMissile (actor, target, MT_FATSHOT); + mo->angle -= FATSPREAD/2; + an = mo->angle >> ANGLETOFINESHIFT; + mo->momx = FixedMul (mo->info->speed, finecosine[an]); + mo->momy = FixedMul (mo->info->speed, finesine[an]); + + mo = P_SpawnMissile (actor, target, MT_FATSHOT); + mo->angle += FATSPREAD/2; + an = mo->angle >> ANGLETOFINESHIFT; + mo->momx = FixedMul (mo->info->speed, finecosine[an]); + mo->momy = FixedMul (mo->info->speed, finesine[an]); +} + + +// +// SkullAttack +// Fly at the player like a missile. +// +#define SKULLSPEED (20*FRACUNIT) + +void A_SkullAttack (mobj_t* actor) +{ + mobj_t* dest; + angle_t an; + int dist; + + if (!actor->target) + return; + + dest = actor->target; + actor->flags |= MF_SKULLFLY; + + S_StartSound (actor, actor->info->attacksound); + A_FaceTarget (actor); + an = actor->angle >> ANGLETOFINESHIFT; + actor->momx = FixedMul (SKULLSPEED, finecosine[an]); + actor->momy = FixedMul (SKULLSPEED, finesine[an]); + dist = P_AproxDistance (dest->x - actor->x, dest->y - actor->y); + dist = dist / SKULLSPEED; + + if (dist < 1) + dist = 1; + actor->momz = (dest->z+(dest->height>>1) - actor->z) / dist; +} + + +// +// A_PainShootSkull +// Spawn a lost soul and launch it at the target +// +void +A_PainShootSkull +( mobj_t* actor, + angle_t angle ) +{ + fixed_t x; + fixed_t y; + fixed_t z; + + mobj_t* newmobj; + angle_t an; + int prestep; + int count; + thinker_t* currentthinker; + + // count total number of skull currently on the level + count = 0; + + currentthinker = thinkercap.next; + while (currentthinker != &thinkercap) + { + if ( (currentthinker->function.acp1 == (actionf_p1)P_MobjThinker) + && ((mobj_t *)currentthinker)->type == MT_SKULL) + count++; + currentthinker = currentthinker->next; + } + + // if there are allready 20 skulls on the level, + // don't spit another one + if (count > 20) + return; + + + // okay, there's playe for another one + an = angle >> ANGLETOFINESHIFT; + + prestep = + 4*FRACUNIT + + 3*(actor->info->radius + mobjinfo[MT_SKULL].radius)/2; + + x = actor->x + FixedMul (prestep, finecosine[an]); + y = actor->y + FixedMul (prestep, finesine[an]); + z = actor->z + 8*FRACUNIT; + + newmobj = P_SpawnMobj (x , y, z, MT_SKULL); + + // Check for movements. + if (!P_TryMove (newmobj, newmobj->x, newmobj->y)) + { + // kill it immediately + P_DamageMobj (newmobj,actor,actor,10000); + return; + } + + newmobj->target = actor->target; + A_SkullAttack (newmobj); +} + + +// +// A_PainAttack +// Spawn a lost soul and launch it at the target +// +void A_PainAttack (mobj_t* actor) +{ + if (!actor->target) + return; + + A_FaceTarget (actor); + A_PainShootSkull (actor, actor->angle); +} + + +void A_PainDie (mobj_t* actor) +{ + A_Fall (actor); + A_PainShootSkull (actor, actor->angle+ANG90); + A_PainShootSkull (actor, actor->angle+ANG180); + A_PainShootSkull (actor, actor->angle+ANG270); +} + + + + + + +void A_Scream (mobj_t* actor) +{ + int sound; + + switch (actor->info->deathsound) + { + case 0: + return; + + case sfx_podth1: + case sfx_podth2: + case sfx_podth3: + sound = sfx_podth1 + P_Random ()%3; + break; + + case sfx_bgdth1: + case sfx_bgdth2: + sound = sfx_bgdth1 + P_Random ()%2; + break; + + default: + sound = actor->info->deathsound; + break; + } + + // Check for bosses. + if (actor->type==MT_SPIDER + || actor->type == MT_CYBORG) + { + // full volume + S_StartSound (NULL, sound); + } + else + S_StartSound (actor, sound); +} + + +void A_XScream (mobj_t* actor) +{ + S_StartSound (actor, sfx_slop); +} + +void A_Pain (mobj_t* actor) +{ + if (actor->info->painsound) + S_StartSound (actor, actor->info->painsound); +} + + + +void A_Fall (mobj_t *actor) +{ + // actor is on ground, it can be walked over + actor->flags &= ~MF_SOLID; + + // So change this if corpse objects + // are meant to be obstacles. +} + + +// +// A_Explode +// +void A_Explode (mobj_t* thingy) +{ + P_RadiusAttack(thingy, thingy->target, 128); +} + +// Check whether the death of the specified monster type is allowed +// to trigger the end of episode special action. +// +// This behavior changed in v1.9, the most notable effect of which +// was to break uac_dead.wad + +static boolean CheckBossEnd(mobjtype_t motype) +{ + if (gameversion < exe_ultimate) + { + if (gamemap != 8) + { + return false; + } + + // Baron death on later episodes is nothing special. + + if (motype == MT_BRUISER && gameepisode != 1) + { + return false; + } + + return true; + } + else + { + // New logic that appeared in Ultimate Doom. + // Looks like the logic was overhauled while adding in the + // episode 4 support. Now bosses only trigger on their + // specific episode. + + switch(gameepisode) + { + case 1: + return gamemap == 8 && motype == MT_BRUISER; + + case 2: + return gamemap == 8 && motype == MT_CYBORG; + + case 3: + return gamemap == 8 && motype == MT_SPIDER; + + case 4: + return (gamemap == 6 && motype == MT_CYBORG) + || (gamemap == 8 && motype == MT_SPIDER); + + default: + return gamemap == 8; + } + } +} + +// +// A_BossDeath +// Possibly trigger special effects +// if on first boss level +// +void A_BossDeath (mobj_t* mo) +{ + thinker_t* th; + mobj_t* mo2; + line_t junk; + int i; + + if ( gamemode == commercial) + { + if (gamemap != 7) + return; + + if ((mo->type != MT_FATSO) + && (mo->type != MT_BABY)) + return; + } + else + { + if (!CheckBossEnd(mo->type)) + { + return; + } + } + + // make sure there is a player alive for victory + for (i=0 ; i 0) + break; + + if (i==MAXPLAYERS) + return; // no one left alive, so do not end game + + // scan the remaining thinkers to see + // if all bosses are dead + for (th = thinkercap.next ; th != &thinkercap ; th=th->next) + { + if (th->function.acp1 != (actionf_p1)P_MobjThinker) + continue; + + mo2 = (mobj_t *)th; + if (mo2 != mo + && mo2->type == mo->type + && mo2->health > 0) + { + // other boss not dead + return; + } + } + + // victory! + if ( gamemode == commercial) + { + if (gamemap == 7) + { + if (mo->type == MT_FATSO) + { + junk.tag = 666; + EV_DoFloor(&junk,lowerFloorToLowest); + return; + } + + if (mo->type == MT_BABY) + { + junk.tag = 667; + EV_DoFloor(&junk,raiseToTexture); + return; + } + } + } + else + { + switch(gameepisode) + { + case 1: + junk.tag = 666; + EV_DoFloor (&junk, lowerFloorToLowest); + return; + break; + + case 4: + switch(gamemap) + { + case 6: + junk.tag = 666; + EV_DoDoor (&junk, vld_blazeOpen); + return; + break; + + case 8: + junk.tag = 666; + EV_DoFloor (&junk, lowerFloorToLowest); + return; + break; + } + } + } + + G_ExitLevel (); +} + + +void A_Hoof (mobj_t* mo) +{ + S_StartSound (mo, sfx_hoof); + A_Chase (mo); +} + +void A_Metal (mobj_t* mo) +{ + S_StartSound (mo, sfx_metal); + A_Chase (mo); +} + +void A_BabyMetal (mobj_t* mo) +{ + S_StartSound (mo, sfx_bspwlk); + A_Chase (mo); +} + +void +A_OpenShotgun2 +( player_t* player, + pspdef_t* psp ) +{ + S_StartSound (player->mo, sfx_dbopn); +} + +void +A_LoadShotgun2 +( player_t* player, + pspdef_t* psp ) +{ + S_StartSound (player->mo, sfx_dbload); +} + +void +A_ReFire +( player_t* player, + pspdef_t* psp ); + +void +A_CloseShotgun2 +( player_t* player, + pspdef_t* psp ) +{ + S_StartSound (player->mo, sfx_dbcls); + A_ReFire(player,psp); +} + + + +mobj_t* braintargets[32]; +int numbraintargets; +int braintargeton = 0; + +void A_BrainAwake (mobj_t* mo) +{ + thinker_t* thinker; + mobj_t* m; + + // find all the target spots + numbraintargets = 0; + braintargeton = 0; + + thinker = thinkercap.next; + for (thinker = thinkercap.next ; + thinker != &thinkercap ; + thinker = thinker->next) + { + if (thinker->function.acp1 != (actionf_p1)P_MobjThinker) + continue; // not a mobj + + m = (mobj_t *)thinker; + + if (m->type == MT_BOSSTARGET ) + { + braintargets[numbraintargets] = m; + numbraintargets++; + } + } + + S_StartSound (NULL,sfx_bossit); +} + + +void A_BrainPain (mobj_t* mo) +{ + S_StartSound (NULL,sfx_bospn); +} + + +void A_BrainScream (mobj_t* mo) +{ + int x; + int y; + int z; + mobj_t* th; + + for (x=mo->x - 196*FRACUNIT ; x< mo->x + 320*FRACUNIT ; x+= FRACUNIT*8) + { + y = mo->y - 320*FRACUNIT; + z = 128 + P_Random()*2*FRACUNIT; + th = P_SpawnMobj (x,y,z, MT_ROCKET); + th->momz = P_Random()*512; + + P_SetMobjState (th, S_BRAINEXPLODE1); + + th->tics -= P_Random()&7; + if (th->tics < 1) + th->tics = 1; + } + + S_StartSound (NULL,sfx_bosdth); +} + + + +void A_BrainExplode (mobj_t* mo) +{ + int x; + int y; + int z; + mobj_t* th; + + x = mo->x + (P_Random () - P_Random ())*2048; + y = mo->y; + z = 128 + P_Random()*2*FRACUNIT; + th = P_SpawnMobj (x,y,z, MT_ROCKET); + th->momz = P_Random()*512; + + P_SetMobjState (th, S_BRAINEXPLODE1); + + th->tics -= P_Random()&7; + if (th->tics < 1) + th->tics = 1; +} + + +void A_BrainDie (mobj_t* mo) +{ + G_ExitLevel (); +} + +void A_BrainSpit (mobj_t* mo) +{ + mobj_t* targ; + mobj_t* newmobj; + + static int easy = 0; + + easy ^= 1; + if (gameskill <= sk_easy && (!easy)) + return; + + // shoot a cube at current target + targ = braintargets[braintargeton]; + braintargeton = (braintargeton+1)%numbraintargets; + + // spawn brain missile + newmobj = P_SpawnMissile (mo, targ, MT_SPAWNSHOT); + newmobj->target = targ; + newmobj->reactiontime = + ((targ->y - mo->y)/newmobj->momy) / newmobj->state->tics; + + S_StartSound(NULL, sfx_bospit); +} + + + +void A_SpawnFly (mobj_t* mo); + +// travelling cube sound +void A_SpawnSound (mobj_t* mo) +{ + S_StartSound (mo,sfx_boscub); + A_SpawnFly(mo); +} + +void A_SpawnFly (mobj_t* mo) +{ + mobj_t* newmobj; + mobj_t* fog; + mobj_t* targ; + int r; + mobjtype_t type; + + if (--mo->reactiontime) + return; // still flying + + targ = P_SubstNullMobj(mo->target); + + // First spawn teleport fog. + fog = P_SpawnMobj (targ->x, targ->y, targ->z, MT_SPAWNFIRE); + S_StartSound (fog, sfx_telept); + + // Randomly select monster to spawn. + r = P_Random (); + + // Probability distribution (kind of :), + // decreasing likelihood. + if ( r<50 ) + type = MT_TROOP; + else if (r<90) + type = MT_SERGEANT; + else if (r<120) + type = MT_SHADOWS; + else if (r<130) + type = MT_PAIN; + else if (r<160) + type = MT_HEAD; + else if (r<162) + type = MT_VILE; + else if (r<172) + type = MT_UNDEAD; + else if (r<192) + type = MT_BABY; + else if (r<222) + type = MT_FATSO; + else if (r<246) + type = MT_KNIGHT; + else + type = MT_BRUISER; + + newmobj = P_SpawnMobj (targ->x, targ->y, targ->z, type); + if (P_LookForPlayers (newmobj, true) ) + P_SetMobjState (newmobj, newmobj->info->seestate); + + // telefrag anything in this spot + P_TeleportMove (newmobj, newmobj->x, newmobj->y); + + // remove self (i.e., cube). + P_RemoveMobj (mo); +} + + + +void A_PlayerScream (mobj_t* mo) +{ + // Default death sound. + int sound = sfx_pldeth; + + if ( (gamemode == commercial) + && (mo->health < -50)) + { + // IF THE PLAYER DIES + // LESS THAN -50% WITHOUT GIBBING + sound = sfx_pdiehi; + } + + S_StartSound (mo, sound); +} diff --git a/firmware_p4/components/Applications/doom/p_floor.c b/firmware_p4/components/Applications/doom/p_floor.c new file mode 100644 index 000000000..1384ee6b4 --- /dev/null +++ b/firmware_p4/components/Applications/doom/p_floor.c @@ -0,0 +1,546 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Floor animation: raising stairs. +// + + + +#include "z_zone.h" +#include "doomdef.h" +#include "p_local.h" + +#include "s_sound.h" + +// State. +#include "doomstat.h" +#include "r_state.h" +// Data. +#include "sounds.h" + + +// +// FLOORS +// + +// +// Move a plane (floor or ceiling) and check for crushing +// +result_e +T_MovePlane +( sector_t* sector, + fixed_t speed, + fixed_t dest, + boolean crush, + int floorOrCeiling, + int direction ) +{ + boolean flag; + fixed_t lastpos; + + switch(floorOrCeiling) + { + case 0: + // FLOOR + switch(direction) + { + case -1: + // DOWN + if (sector->floorheight - speed < dest) + { + lastpos = sector->floorheight; + sector->floorheight = dest; + flag = P_ChangeSector(sector,crush); + if (flag == true) + { + sector->floorheight =lastpos; + P_ChangeSector(sector,crush); + //return crushed; + } + return pastdest; + } + else + { + lastpos = sector->floorheight; + sector->floorheight -= speed; + flag = P_ChangeSector(sector,crush); + if (flag == true) + { + sector->floorheight = lastpos; + P_ChangeSector(sector,crush); + return crushed; + } + } + break; + + case 1: + // UP + if (sector->floorheight + speed > dest) + { + lastpos = sector->floorheight; + sector->floorheight = dest; + flag = P_ChangeSector(sector,crush); + if (flag == true) + { + sector->floorheight = lastpos; + P_ChangeSector(sector,crush); + //return crushed; + } + return pastdest; + } + else + { + // COULD GET CRUSHED + lastpos = sector->floorheight; + sector->floorheight += speed; + flag = P_ChangeSector(sector,crush); + if (flag == true) + { + if (crush == true) + return crushed; + sector->floorheight = lastpos; + P_ChangeSector(sector,crush); + return crushed; + } + } + break; + } + break; + + case 1: + // CEILING + switch(direction) + { + case -1: + // DOWN + if (sector->ceilingheight - speed < dest) + { + lastpos = sector->ceilingheight; + sector->ceilingheight = dest; + flag = P_ChangeSector(sector,crush); + + if (flag == true) + { + sector->ceilingheight = lastpos; + P_ChangeSector(sector,crush); + //return crushed; + } + return pastdest; + } + else + { + // COULD GET CRUSHED + lastpos = sector->ceilingheight; + sector->ceilingheight -= speed; + flag = P_ChangeSector(sector,crush); + + if (flag == true) + { + if (crush == true) + return crushed; + sector->ceilingheight = lastpos; + P_ChangeSector(sector,crush); + return crushed; + } + } + break; + + case 1: + // UP + if (sector->ceilingheight + speed > dest) + { + lastpos = sector->ceilingheight; + sector->ceilingheight = dest; + flag = P_ChangeSector(sector,crush); + if (flag == true) + { + sector->ceilingheight = lastpos; + P_ChangeSector(sector,crush); + //return crushed; + } + return pastdest; + } + else + { + lastpos = sector->ceilingheight; + sector->ceilingheight += speed; + flag = P_ChangeSector(sector,crush); +// UNUSED +#if 0 + if (flag == true) + { + sector->ceilingheight = lastpos; + P_ChangeSector(sector,crush); + return crushed; + } +#endif + } + break; + } + break; + + } + return ok; +} + + +// +// MOVE A FLOOR TO IT'S DESTINATION (UP OR DOWN) +// +void T_MoveFloor(floormove_t* floor) +{ + result_e res; + + res = T_MovePlane(floor->sector, + floor->speed, + floor->floordestheight, + floor->crush,0,floor->direction); + + if (!(leveltime&7)) + S_StartSound(&floor->sector->soundorg, sfx_stnmov); + + if (res == pastdest) + { + floor->sector->specialdata = NULL; + + if (floor->direction == 1) + { + switch(floor->type) + { + case donutRaise: + floor->sector->special = floor->newspecial; + floor->sector->floorpic = floor->texture; + default: + break; + } + } + else if (floor->direction == -1) + { + switch(floor->type) + { + case lowerAndChange: + floor->sector->special = floor->newspecial; + floor->sector->floorpic = floor->texture; + default: + break; + } + } + P_RemoveThinker(&floor->thinker); + + S_StartSound(&floor->sector->soundorg, sfx_pstop); + } + +} + +// +// HANDLE FLOOR TYPES +// +int +EV_DoFloor +( line_t* line, + floor_e floortype ) +{ + int secnum; + int rtn; + int i; + sector_t* sec; + floormove_t* floor; + + secnum = -1; + rtn = 0; + while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0) + { + sec = §ors[secnum]; + + // ALREADY MOVING? IF SO, KEEP GOING... + if (sec->specialdata) + continue; + + // new floor thinker + rtn = 1; + floor = Z_Malloc (sizeof(*floor), PU_LEVSPEC, 0); + P_AddThinker (&floor->thinker); + sec->specialdata = floor; + floor->thinker.function.acp1 = (actionf_p1) T_MoveFloor; + floor->type = floortype; + floor->crush = false; + + switch(floortype) + { + case lowerFloor: + floor->direction = -1; + floor->sector = sec; + floor->speed = FLOORSPEED; + floor->floordestheight = + P_FindHighestFloorSurrounding(sec); + break; + + case lowerFloorToLowest: + floor->direction = -1; + floor->sector = sec; + floor->speed = FLOORSPEED; + floor->floordestheight = + P_FindLowestFloorSurrounding(sec); + break; + + case turboLower: + floor->direction = -1; + floor->sector = sec; + floor->speed = FLOORSPEED * 4; + floor->floordestheight = + P_FindHighestFloorSurrounding(sec); + if (floor->floordestheight != sec->floorheight) + floor->floordestheight += 8*FRACUNIT; + break; + + case raiseFloorCrush: + floor->crush = true; + case raiseFloor: + floor->direction = 1; + floor->sector = sec; + floor->speed = FLOORSPEED; + floor->floordestheight = + P_FindLowestCeilingSurrounding(sec); + if (floor->floordestheight > sec->ceilingheight) + floor->floordestheight = sec->ceilingheight; + floor->floordestheight -= (8*FRACUNIT)* + (floortype == raiseFloorCrush); + break; + + case raiseFloorTurbo: + floor->direction = 1; + floor->sector = sec; + floor->speed = FLOORSPEED*4; + floor->floordestheight = + P_FindNextHighestFloor(sec,sec->floorheight); + break; + + case raiseFloorToNearest: + floor->direction = 1; + floor->sector = sec; + floor->speed = FLOORSPEED; + floor->floordestheight = + P_FindNextHighestFloor(sec,sec->floorheight); + break; + + case raiseFloor24: + floor->direction = 1; + floor->sector = sec; + floor->speed = FLOORSPEED; + floor->floordestheight = floor->sector->floorheight + + 24 * FRACUNIT; + break; + case raiseFloor512: + floor->direction = 1; + floor->sector = sec; + floor->speed = FLOORSPEED; + floor->floordestheight = floor->sector->floorheight + + 512 * FRACUNIT; + break; + + case raiseFloor24AndChange: + floor->direction = 1; + floor->sector = sec; + floor->speed = FLOORSPEED; + floor->floordestheight = floor->sector->floorheight + + 24 * FRACUNIT; + sec->floorpic = line->frontsector->floorpic; + sec->special = line->frontsector->special; + break; + + case raiseToTexture: + { + int minsize = INT_MAX; + side_t* side; + + floor->direction = 1; + floor->sector = sec; + floor->speed = FLOORSPEED; + for (i = 0; i < sec->linecount; i++) + { + if (twoSided (secnum, i) ) + { + side = getSide(secnum,i,0); + if (side->bottomtexture >= 0) + if (textureheight[side->bottomtexture] < + minsize) + minsize = + textureheight[side->bottomtexture]; + side = getSide(secnum,i,1); + if (side->bottomtexture >= 0) + if (textureheight[side->bottomtexture] < + minsize) + minsize = + textureheight[side->bottomtexture]; + } + } + floor->floordestheight = + floor->sector->floorheight + minsize; + } + break; + + case lowerAndChange: + floor->direction = -1; + floor->sector = sec; + floor->speed = FLOORSPEED; + floor->floordestheight = + P_FindLowestFloorSurrounding(sec); + floor->texture = sec->floorpic; + + for (i = 0; i < sec->linecount; i++) + { + if ( twoSided(secnum, i) ) + { + if (getSide(secnum,i,0)->sector-sectors == secnum) + { + sec = getSector(secnum,i,1); + + if (sec->floorheight == floor->floordestheight) + { + floor->texture = sec->floorpic; + floor->newspecial = sec->special; + break; + } + } + else + { + sec = getSector(secnum,i,0); + + if (sec->floorheight == floor->floordestheight) + { + floor->texture = sec->floorpic; + floor->newspecial = sec->special; + break; + } + } + } + } + default: + break; + } + } + return rtn; +} + + + + +// +// BUILD A STAIRCASE! +// +int +EV_BuildStairs +( line_t* line, + stair_e type ) +{ + int secnum; + int height; + int i; + int newsecnum; + int texture; + int ok; + int rtn; + + sector_t* sec; + sector_t* tsec; + + floormove_t* floor; + + fixed_t stairsize = 0; + fixed_t speed = 0; + + secnum = -1; + rtn = 0; + while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0) + { + sec = §ors[secnum]; + + // ALREADY MOVING? IF SO, KEEP GOING... + if (sec->specialdata) + continue; + + // new floor thinker + rtn = 1; + floor = Z_Malloc (sizeof(*floor), PU_LEVSPEC, 0); + P_AddThinker (&floor->thinker); + sec->specialdata = floor; + floor->thinker.function.acp1 = (actionf_p1) T_MoveFloor; + floor->direction = 1; + floor->sector = sec; + switch(type) + { + case build8: + speed = FLOORSPEED/4; + stairsize = 8*FRACUNIT; + break; + case turbo16: + speed = FLOORSPEED*4; + stairsize = 16*FRACUNIT; + break; + } + floor->speed = speed; + height = sec->floorheight + stairsize; + floor->floordestheight = height; + + texture = sec->floorpic; + + // Find next sector to raise + // 1. Find 2-sided line with same sector side[0] + // 2. Other side is the next sector to raise + do + { + ok = 0; + for (i = 0;i < sec->linecount;i++) + { + if ( !((sec->lines[i])->flags & ML_TWOSIDED) ) + continue; + + tsec = (sec->lines[i])->frontsector; + newsecnum = tsec-sectors; + + if (secnum != newsecnum) + continue; + + tsec = (sec->lines[i])->backsector; + newsecnum = tsec - sectors; + + if (tsec->floorpic != texture) + continue; + + height += stairsize; + + if (tsec->specialdata) + continue; + + sec = tsec; + secnum = newsecnum; + floor = Z_Malloc (sizeof(*floor), PU_LEVSPEC, 0); + + P_AddThinker (&floor->thinker); + + sec->specialdata = floor; + floor->thinker.function.acp1 = (actionf_p1) T_MoveFloor; + floor->direction = 1; + floor->sector = sec; + floor->speed = speed; + floor->floordestheight = height; + ok = 1; + break; + } + } while(ok); + } + return rtn; +} + diff --git a/firmware_p4/components/Applications/doom/p_inter.c b/firmware_p4/components/Applications/doom/p_inter.c new file mode 100644 index 000000000..1eb58cfca --- /dev/null +++ b/firmware_p4/components/Applications/doom/p_inter.c @@ -0,0 +1,922 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Handling interactions (i.e., collisions). +// + + + + +// Data. +#include "doomdef.h" +#include "dstrings.h" +#include "sounds.h" + +#include "deh_main.h" +#include "deh_misc.h" +#include "doomstat.h" + +#include "m_random.h" +#include "i_system.h" + +#include "am_map.h" + +#include "p_local.h" + +#include "s_sound.h" + +#include "p_inter.h" + + +#define BONUSADD 6 + + + + +// a weapon is found with two clip loads, +// a big item has five clip loads +int maxammo[NUMAMMO] = {200, 50, 300, 50}; +int clipammo[NUMAMMO] = {10, 4, 20, 1}; + + +// +// GET STUFF +// + +// +// P_GiveAmmo +// Num is the number of clip loads, +// not the individual count (0= 1/2 clip). +// Returns false if the ammo can't be picked up at all +// + +boolean +P_GiveAmmo +( player_t* player, + ammotype_t ammo, + int num ) +{ + int oldammo; + + if (ammo == am_noammo) + return false; + + if (ammo > NUMAMMO) + I_Error ("P_GiveAmmo: bad type %i", ammo); + + if ( player->ammo[ammo] == player->maxammo[ammo] ) + return false; + + if (num) + num *= clipammo[ammo]; + else + num = clipammo[ammo]/2; + + if (gameskill == sk_baby + || gameskill == sk_nightmare) + { + // give double ammo in trainer mode, + // you'll need in nightmare + num <<= 1; + } + + + oldammo = player->ammo[ammo]; + player->ammo[ammo] += num; + + if (player->ammo[ammo] > player->maxammo[ammo]) + player->ammo[ammo] = player->maxammo[ammo]; + + // If non zero ammo, + // don't change up weapons, + // player was lower on purpose. + if (oldammo) + return true; + + // We were down to zero, + // so select a new weapon. + // Preferences are not user selectable. + switch (ammo) + { + case am_clip: + if (player->readyweapon == wp_fist) + { + if (player->weaponowned[wp_chaingun]) + player->pendingweapon = wp_chaingun; + else + player->pendingweapon = wp_pistol; + } + break; + + case am_shell: + if (player->readyweapon == wp_fist + || player->readyweapon == wp_pistol) + { + if (player->weaponowned[wp_shotgun]) + player->pendingweapon = wp_shotgun; + } + break; + + case am_cell: + if (player->readyweapon == wp_fist + || player->readyweapon == wp_pistol) + { + if (player->weaponowned[wp_plasma]) + player->pendingweapon = wp_plasma; + } + break; + + case am_misl: + if (player->readyweapon == wp_fist) + { + if (player->weaponowned[wp_missile]) + player->pendingweapon = wp_missile; + } + default: + break; + } + + return true; +} + + +// +// P_GiveWeapon +// The weapon name may have a MF_DROPPED flag ored in. +// +boolean +P_GiveWeapon +( player_t* player, + weapontype_t weapon, + boolean dropped ) +{ + boolean gaveammo; + boolean gaveweapon; + + if (netgame && (deathmatch!=2) && !dropped ) + { + // leave placed weapons forever on net games + if (player->weaponowned[weapon]) + return false; + + player->bonuscount += BONUSADD; + player->weaponowned[weapon] = true; + + if (deathmatch) + P_GiveAmmo (player, weaponinfo[weapon].ammo, 5); + else + P_GiveAmmo (player, weaponinfo[weapon].ammo, 2); + player->pendingweapon = weapon; + + if (player == &players[consoleplayer]) + S_StartSound (NULL, sfx_wpnup); + return false; + } + + if (weaponinfo[weapon].ammo != am_noammo) + { + // give one clip with a dropped weapon, + // two clips with a found weapon + if (dropped) + gaveammo = P_GiveAmmo (player, weaponinfo[weapon].ammo, 1); + else + gaveammo = P_GiveAmmo (player, weaponinfo[weapon].ammo, 2); + } + else + { + gaveammo = false; + } + + if (player->weaponowned[weapon]) + { + gaveweapon = false; + } + else + { + gaveweapon = true; + player->weaponowned[weapon] = true; + player->pendingweapon = weapon; + } + + return (gaveweapon || gaveammo); +} + + + +// +// P_GiveBody +// Returns false if the body isn't needed at all +// +boolean +P_GiveBody +( player_t* player, + int num ) +{ + if (player->health >= MAXHEALTH) + return false; + + player->health += num; + if (player->health > MAXHEALTH) + player->health = MAXHEALTH; + player->mo->health = player->health; + + return true; +} + + + +// +// P_GiveArmor +// Returns false if the armor is worse +// than the current armor. +// +boolean +P_GiveArmor +( player_t* player, + int armortype ) +{ + int hits; + + hits = armortype*100; + if (player->armorpoints >= hits) + return false; // don't pick up + + player->armortype = armortype; + player->armorpoints = hits; + + return true; +} + + + +// +// P_GiveCard +// +void +P_GiveCard +( player_t* player, + card_t card ) +{ + if (player->cards[card]) + return; + + player->bonuscount = BONUSADD; + player->cards[card] = 1; +} + + +// +// P_GivePower +// +boolean +P_GivePower +( player_t* player, + int /*powertype_t*/ power ) +{ + if (power == pw_invulnerability) + { + player->powers[power] = INVULNTICS; + return true; + } + + if (power == pw_invisibility) + { + player->powers[power] = INVISTICS; + player->mo->flags |= MF_SHADOW; + return true; + } + + if (power == pw_infrared) + { + player->powers[power] = INFRATICS; + return true; + } + + if (power == pw_ironfeet) + { + player->powers[power] = IRONTICS; + return true; + } + + if (power == pw_strength) + { + P_GiveBody (player, 100); + player->powers[power] = 1; + return true; + } + + if (player->powers[power]) + return false; // already got it + + player->powers[power] = 1; + return true; +} + + + +// +// P_TouchSpecialThing +// +void +P_TouchSpecialThing +( mobj_t* special, + mobj_t* toucher ) +{ + player_t* player; + int i; + fixed_t delta; + int sound; + + delta = special->z - toucher->z; + + if (delta > toucher->height + || delta < -8*FRACUNIT) + { + // out of reach + return; + } + + + sound = sfx_itemup; + player = toucher->player; + + // Dead thing touching. + // Can happen with a sliding player corpse. + if (toucher->health <= 0) + return; + + // Identify by sprite. + switch (special->sprite) + { + // armor + case SPR_ARM1: + if (!P_GiveArmor (player, deh_green_armor_class)) + return; + player->message = DEH_String(GOTARMOR); + break; + + case SPR_ARM2: + if (!P_GiveArmor (player, deh_blue_armor_class)) + return; + player->message = DEH_String(GOTMEGA); + break; + + // bonus items + case SPR_BON1: + player->health++; // can go over 100% + if (player->health > deh_max_health) + player->health = deh_max_health; + player->mo->health = player->health; + player->message = DEH_String(GOTHTHBONUS); + break; + + case SPR_BON2: + player->armorpoints++; // can go over 100% + if (player->armorpoints > deh_max_armor) + player->armorpoints = deh_max_armor; + // deh_green_armor_class only applies to the green armor shirt; + // for the armor helmets, armortype 1 is always used. + if (!player->armortype) + player->armortype = 1; + player->message = DEH_String(GOTARMBONUS); + break; + + case SPR_SOUL: + player->health += deh_soulsphere_health; + if (player->health > deh_max_soulsphere) + player->health = deh_max_soulsphere; + player->mo->health = player->health; + player->message = DEH_String(GOTSUPER); + sound = sfx_getpow; + break; + + case SPR_MEGA: + if (gamemode != commercial) + return; + player->health = deh_megasphere_health; + player->mo->health = player->health; + // We always give armor type 2 for the megasphere; dehacked only + // affects the MegaArmor. + P_GiveArmor (player, 2); + player->message = DEH_String(GOTMSPHERE); + sound = sfx_getpow; + break; + + // cards + // leave cards for everyone + case SPR_BKEY: + if (!player->cards[it_bluecard]) + player->message = DEH_String(GOTBLUECARD); + P_GiveCard (player, it_bluecard); + if (!netgame) + break; + return; + + case SPR_YKEY: + if (!player->cards[it_yellowcard]) + player->message = DEH_String(GOTYELWCARD); + P_GiveCard (player, it_yellowcard); + if (!netgame) + break; + return; + + case SPR_RKEY: + if (!player->cards[it_redcard]) + player->message = DEH_String(GOTREDCARD); + P_GiveCard (player, it_redcard); + if (!netgame) + break; + return; + + case SPR_BSKU: + if (!player->cards[it_blueskull]) + player->message = DEH_String(GOTBLUESKUL); + P_GiveCard (player, it_blueskull); + if (!netgame) + break; + return; + + case SPR_YSKU: + if (!player->cards[it_yellowskull]) + player->message = DEH_String(GOTYELWSKUL); + P_GiveCard (player, it_yellowskull); + if (!netgame) + break; + return; + + case SPR_RSKU: + if (!player->cards[it_redskull]) + player->message = DEH_String(GOTREDSKULL); + P_GiveCard (player, it_redskull); + if (!netgame) + break; + return; + + // medikits, heals + case SPR_STIM: + if (!P_GiveBody (player, 10)) + return; + player->message = DEH_String(GOTSTIM); + break; + + case SPR_MEDI: + if (!P_GiveBody (player, 25)) + return; + + if (player->health < 25) + player->message = DEH_String(GOTMEDINEED); + else + player->message = DEH_String(GOTMEDIKIT); + break; + + + // power ups + case SPR_PINV: + if (!P_GivePower (player, pw_invulnerability)) + return; + player->message = DEH_String(GOTINVUL); + sound = sfx_getpow; + break; + + case SPR_PSTR: + if (!P_GivePower (player, pw_strength)) + return; + player->message = DEH_String(GOTBERSERK); + if (player->readyweapon != wp_fist) + player->pendingweapon = wp_fist; + sound = sfx_getpow; + break; + + case SPR_PINS: + if (!P_GivePower (player, pw_invisibility)) + return; + player->message = DEH_String(GOTINVIS); + sound = sfx_getpow; + break; + + case SPR_SUIT: + if (!P_GivePower (player, pw_ironfeet)) + return; + player->message = DEH_String(GOTSUIT); + sound = sfx_getpow; + break; + + case SPR_PMAP: + if (!P_GivePower (player, pw_allmap)) + return; + player->message = DEH_String(GOTMAP); + sound = sfx_getpow; + break; + + case SPR_PVIS: + if (!P_GivePower (player, pw_infrared)) + return; + player->message = DEH_String(GOTVISOR); + sound = sfx_getpow; + break; + + // ammo + case SPR_CLIP: + if (special->flags & MF_DROPPED) + { + if (!P_GiveAmmo (player,am_clip,0)) + return; + } + else + { + if (!P_GiveAmmo (player,am_clip,1)) + return; + } + player->message = DEH_String(GOTCLIP); + break; + + case SPR_AMMO: + if (!P_GiveAmmo (player, am_clip,5)) + return; + player->message = DEH_String(GOTCLIPBOX); + break; + + case SPR_ROCK: + if (!P_GiveAmmo (player, am_misl,1)) + return; + player->message = DEH_String(GOTROCKET); + break; + + case SPR_BROK: + if (!P_GiveAmmo (player, am_misl,5)) + return; + player->message = DEH_String(GOTROCKBOX); + break; + + case SPR_CELL: + if (!P_GiveAmmo (player, am_cell,1)) + return; + player->message = DEH_String(GOTCELL); + break; + + case SPR_CELP: + if (!P_GiveAmmo (player, am_cell,5)) + return; + player->message = DEH_String(GOTCELLBOX); + break; + + case SPR_SHEL: + if (!P_GiveAmmo (player, am_shell,1)) + return; + player->message = DEH_String(GOTSHELLS); + break; + + case SPR_SBOX: + if (!P_GiveAmmo (player, am_shell,5)) + return; + player->message = DEH_String(GOTSHELLBOX); + break; + + case SPR_BPAK: + if (!player->backpack) + { + for (i=0 ; imaxammo[i] *= 2; + player->backpack = true; + } + for (i=0 ; imessage = DEH_String(GOTBACKPACK); + break; + + // weapons + case SPR_BFUG: + if (!P_GiveWeapon (player, wp_bfg, false) ) + return; + player->message = DEH_String(GOTBFG9000); + sound = sfx_wpnup; + break; + + case SPR_MGUN: + if (!P_GiveWeapon (player, wp_chaingun, (special->flags&MF_DROPPED) != 0) ) + return; + player->message = DEH_String(GOTCHAINGUN); + sound = sfx_wpnup; + break; + + case SPR_CSAW: + if (!P_GiveWeapon (player, wp_chainsaw, false) ) + return; + player->message = DEH_String(GOTCHAINSAW); + sound = sfx_wpnup; + break; + + case SPR_LAUN: + if (!P_GiveWeapon (player, wp_missile, false) ) + return; + player->message = DEH_String(GOTLAUNCHER); + sound = sfx_wpnup; + break; + + case SPR_PLAS: + if (!P_GiveWeapon (player, wp_plasma, false) ) + return; + player->message = DEH_String(GOTPLASMA); + sound = sfx_wpnup; + break; + + case SPR_SHOT: + if (!P_GiveWeapon (player, wp_shotgun, (special->flags&MF_DROPPED) != 0 ) ) + return; + player->message = DEH_String(GOTSHOTGUN); + sound = sfx_wpnup; + break; + + case SPR_SGN2: + if (!P_GiveWeapon (player, wp_supershotgun, (special->flags&MF_DROPPED) != 0 ) ) + return; + player->message = DEH_String(GOTSHOTGUN2); + sound = sfx_wpnup; + break; + + default: + I_Error ("P_SpecialThing: Unknown gettable thing"); + } + + if (special->flags & MF_COUNTITEM) + player->itemcount++; + P_RemoveMobj (special); + player->bonuscount += BONUSADD; + if (player == &players[consoleplayer]) + S_StartSound (NULL, sound); +} + + +// +// KillMobj +// +void +P_KillMobj +( mobj_t* source, + mobj_t* target ) +{ + mobjtype_t item; + mobj_t* mo; + + target->flags &= ~(MF_SHOOTABLE|MF_FLOAT|MF_SKULLFLY); + + if (target->type != MT_SKULL) + target->flags &= ~MF_NOGRAVITY; + + target->flags |= MF_CORPSE|MF_DROPOFF; + target->height >>= 2; + + if (source && source->player) + { + // count for intermission + if (target->flags & MF_COUNTKILL) + source->player->killcount++; + + if (target->player) + source->player->frags[target->player-players]++; + } + else if (!netgame && (target->flags & MF_COUNTKILL) ) + { + // count all monster deaths, + // even those caused by other monsters + players[0].killcount++; + } + + if (target->player) + { + // count environment kills against you + if (!source) + target->player->frags[target->player-players]++; + + target->flags &= ~MF_SOLID; + target->player->playerstate = PST_DEAD; + P_DropWeapon (target->player); + + if (target->player == &players[consoleplayer] + && automapactive) + { + // don't die in auto map, + // switch view prior to dying + AM_Stop (); + } + + } + + if (target->health < -target->info->spawnhealth + && target->info->xdeathstate) + { + P_SetMobjState (target, target->info->xdeathstate); + } + else + P_SetMobjState (target, target->info->deathstate); + target->tics -= P_Random()&3; + + if (target->tics < 1) + target->tics = 1; + + // I_StartSound (&actor->r, actor->info->deathsound); + + // In Chex Quest, monsters don't drop items. + + if (gameversion == exe_chex) + { + return; + } + + // Drop stuff. + // This determines the kind of object spawned + // during the death frame of a thing. + switch (target->type) + { + case MT_WOLFSS: + case MT_POSSESSED: + item = MT_CLIP; + break; + + case MT_SHOTGUY: + item = MT_SHOTGUN; + break; + + case MT_CHAINGUY: + item = MT_CHAINGUN; + break; + + default: + return; + } + + mo = P_SpawnMobj (target->x,target->y,ONFLOORZ, item); + mo->flags |= MF_DROPPED; // special versions of items +} + + + + +// +// P_DamageMobj +// Damages both enemies and players +// "inflictor" is the thing that caused the damage +// creature or missile, can be NULL (slime, etc) +// "source" is the thing to target after taking damage +// creature or NULL +// Source and inflictor are the same for melee attacks. +// Source can be NULL for slime, barrel explosions +// and other environmental stuff. +// +void +P_DamageMobj +( mobj_t* target, + mobj_t* inflictor, + mobj_t* source, + int damage ) +{ + unsigned ang; + int saved; + player_t* player; + fixed_t thrust; + int temp; + + if ( !(target->flags & MF_SHOOTABLE) ) + return; // shouldn't happen... + + if (target->health <= 0) + return; + + if ( target->flags & MF_SKULLFLY ) + { + target->momx = target->momy = target->momz = 0; + } + + player = target->player; + if (player && gameskill == sk_baby) + damage >>= 1; // take half damage in trainer mode + + + // Some close combat weapons should not + // inflict thrust and push the victim out of reach, + // thus kick away unless using the chainsaw. + if (inflictor + && !(target->flags & MF_NOCLIP) + && (!source + || !source->player + || source->player->readyweapon != wp_chainsaw)) + { + ang = R_PointToAngle2 ( inflictor->x, + inflictor->y, + target->x, + target->y); + + thrust = damage*(FRACUNIT>>3)*100/target->info->mass; + + // make fall forwards sometimes + if ( damage < 40 + && damage > target->health + && target->z - inflictor->z > 64*FRACUNIT + && (P_Random ()&1) ) + { + ang += ANG180; + thrust *= 4; + } + + ang >>= ANGLETOFINESHIFT; + target->momx += FixedMul (thrust, finecosine[ang]); + target->momy += FixedMul (thrust, finesine[ang]); + } + + // player specific + if (player) + { + // end of game hell hack + if (target->subsector->sector->special == 11 + && damage >= target->health) + { + damage = target->health - 1; + } + + + // Below certain threshold, + // ignore damage in GOD mode, or with INVUL power. + if ( damage < 1000 + && ( (player->cheats&CF_GODMODE) + || player->powers[pw_invulnerability] ) ) + { + return; + } + + if (player->armortype) + { + if (player->armortype == 1) + saved = damage/3; + else + saved = damage/2; + + if (player->armorpoints <= saved) + { + // armor is used up + saved = player->armorpoints; + player->armortype = 0; + } + player->armorpoints -= saved; + damage -= saved; + } + player->health -= damage; // mirror mobj health here for Dave + if (player->health < 0) + player->health = 0; + + player->attacker = source; + player->damagecount += damage; // add damage after armor / invuln + + if (player->damagecount > 100) + player->damagecount = 100; // teleport stomp does 10k points... + + temp = damage < 100 ? damage : 100; + + if (player == &players[consoleplayer]) + I_Tactile (40,10,40+temp*2); + } + + // do the damage + target->health -= damage; + if (target->health <= 0) + { + P_KillMobj (source, target); + return; + } + + if ( (P_Random () < target->info->painchance) + && !(target->flags&MF_SKULLFLY) ) + { + target->flags |= MF_JUSTHIT; // fight back! + + P_SetMobjState (target, target->info->painstate); + } + + target->reactiontime = 0; // we're awake now... + + if ( (!target->threshold || target->type == MT_VILE) + && source && source != target + && source->type != MT_VILE) + { + // if not intent on another player, + // chase after this one + target->target = source; + target->threshold = BASETHRESHOLD; + if (target->state == &states[target->info->spawnstate] + && target->info->seestate != S_NULL) + P_SetMobjState (target, target->info->seestate); + } + +} + diff --git a/firmware_p4/components/Applications/doom/p_inter.h b/firmware_p4/components/Applications/doom/p_inter.h new file mode 100644 index 000000000..5764d5dab --- /dev/null +++ b/firmware_p4/components/Applications/doom/p_inter.h @@ -0,0 +1,30 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// +// + + +#ifndef __P_INTER__ +#define __P_INTER__ + + + + +boolean P_GivePower(player_t*, int); + + + +#endif diff --git a/firmware_p4/components/Applications/doom/p_lights.c b/firmware_p4/components/Applications/doom/p_lights.c new file mode 100644 index 000000000..863338dcf --- /dev/null +++ b/firmware_p4/components/Applications/doom/p_lights.c @@ -0,0 +1,350 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Handle Sector base lighting effects. +// Muzzle flash? +// + + + +#include "z_zone.h" +#include "m_random.h" + +#include "doomdef.h" +#include "p_local.h" + + +// State. +#include "r_state.h" + +// +// FIRELIGHT FLICKER +// + +// +// T_FireFlicker +// +void T_FireFlicker (fireflicker_t* flick) +{ + int amount; + + if (--flick->count) + return; + + amount = (P_Random()&3)*16; + + if (flick->sector->lightlevel - amount < flick->minlight) + flick->sector->lightlevel = flick->minlight; + else + flick->sector->lightlevel = flick->maxlight - amount; + + flick->count = 4; +} + + + +// +// P_SpawnFireFlicker +// +void P_SpawnFireFlicker (sector_t* sector) +{ + fireflicker_t* flick; + + // Note that we are resetting sector attributes. + // Nothing special about it during gameplay. + sector->special = 0; + + flick = Z_Malloc ( sizeof(*flick), PU_LEVSPEC, 0); + + P_AddThinker (&flick->thinker); + + flick->thinker.function.acp1 = (actionf_p1) T_FireFlicker; + flick->sector = sector; + flick->maxlight = sector->lightlevel; + flick->minlight = P_FindMinSurroundingLight(sector,sector->lightlevel)+16; + flick->count = 4; +} + + + +// +// BROKEN LIGHT FLASHING +// + + +// +// T_LightFlash +// Do flashing lights. +// +void T_LightFlash (lightflash_t* flash) +{ + if (--flash->count) + return; + + if (flash->sector->lightlevel == flash->maxlight) + { + flash-> sector->lightlevel = flash->minlight; + flash->count = (P_Random()&flash->mintime)+1; + } + else + { + flash-> sector->lightlevel = flash->maxlight; + flash->count = (P_Random()&flash->maxtime)+1; + } + +} + + + + +// +// P_SpawnLightFlash +// After the map has been loaded, scan each sector +// for specials that spawn thinkers +// +void P_SpawnLightFlash (sector_t* sector) +{ + lightflash_t* flash; + + // nothing special about it during gameplay + sector->special = 0; + + flash = Z_Malloc ( sizeof(*flash), PU_LEVSPEC, 0); + + P_AddThinker (&flash->thinker); + + flash->thinker.function.acp1 = (actionf_p1) T_LightFlash; + flash->sector = sector; + flash->maxlight = sector->lightlevel; + + flash->minlight = P_FindMinSurroundingLight(sector,sector->lightlevel); + flash->maxtime = 64; + flash->mintime = 7; + flash->count = (P_Random()&flash->maxtime)+1; +} + + + +// +// STROBE LIGHT FLASHING +// + + +// +// T_StrobeFlash +// +void T_StrobeFlash (strobe_t* flash) +{ + if (--flash->count) + return; + + if (flash->sector->lightlevel == flash->minlight) + { + flash-> sector->lightlevel = flash->maxlight; + flash->count = flash->brighttime; + } + else + { + flash-> sector->lightlevel = flash->minlight; + flash->count =flash->darktime; + } + +} + + + +// +// P_SpawnStrobeFlash +// After the map has been loaded, scan each sector +// for specials that spawn thinkers +// +void +P_SpawnStrobeFlash +( sector_t* sector, + int fastOrSlow, + int inSync ) +{ + strobe_t* flash; + + flash = Z_Malloc ( sizeof(*flash), PU_LEVSPEC, 0); + + P_AddThinker (&flash->thinker); + + flash->sector = sector; + flash->darktime = fastOrSlow; + flash->brighttime = STROBEBRIGHT; + flash->thinker.function.acp1 = (actionf_p1) T_StrobeFlash; + flash->maxlight = sector->lightlevel; + flash->minlight = P_FindMinSurroundingLight(sector, sector->lightlevel); + + if (flash->minlight == flash->maxlight) + flash->minlight = 0; + + // nothing special about it during gameplay + sector->special = 0; + + if (!inSync) + flash->count = (P_Random()&7)+1; + else + flash->count = 1; +} + + +// +// Start strobing lights (usually from a trigger) +// +void EV_StartLightStrobing(line_t* line) +{ + int secnum; + sector_t* sec; + + secnum = -1; + while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0) + { + sec = §ors[secnum]; + if (sec->specialdata) + continue; + + P_SpawnStrobeFlash (sec,SLOWDARK, 0); + } +} + + + +// +// TURN LINE'S TAG LIGHTS OFF +// +void EV_TurnTagLightsOff(line_t* line) +{ + int i; + int j; + int min; + sector_t* sector; + sector_t* tsec; + line_t* templine; + + sector = sectors; + + for (j = 0;j < numsectors; j++, sector++) + { + if (sector->tag == line->tag) + { + min = sector->lightlevel; + for (i = 0;i < sector->linecount; i++) + { + templine = sector->lines[i]; + tsec = getNextSector(templine,sector); + if (!tsec) + continue; + if (tsec->lightlevel < min) + min = tsec->lightlevel; + } + sector->lightlevel = min; + } + } +} + + +// +// TURN LINE'S TAG LIGHTS ON +// +void +EV_LightTurnOn +( line_t* line, + int bright ) +{ + int i; + int j; + sector_t* sector; + sector_t* temp; + line_t* templine; + + sector = sectors; + + for (i=0;itag == line->tag) + { + // bright = 0 means to search + // for highest light level + // surrounding sector + if (!bright) + { + for (j = 0;j < sector->linecount; j++) + { + templine = sector->lines[j]; + temp = getNextSector(templine,sector); + + if (!temp) + continue; + + if (temp->lightlevel > bright) + bright = temp->lightlevel; + } + } + sector-> lightlevel = bright; + } + } +} + + +// +// Spawn glowing light +// + +void T_Glow(glow_t* g) +{ + switch(g->direction) + { + case -1: + // DOWN + g->sector->lightlevel -= GLOWSPEED; + if (g->sector->lightlevel <= g->minlight) + { + g->sector->lightlevel += GLOWSPEED; + g->direction = 1; + } + break; + + case 1: + // UP + g->sector->lightlevel += GLOWSPEED; + if (g->sector->lightlevel >= g->maxlight) + { + g->sector->lightlevel -= GLOWSPEED; + g->direction = -1; + } + break; + } +} + + +void P_SpawnGlowingLight(sector_t* sector) +{ + glow_t* g; + + g = Z_Malloc( sizeof(*g), PU_LEVSPEC, 0); + + P_AddThinker(&g->thinker); + + g->sector = sector; + g->minlight = P_FindMinSurroundingLight(sector,sector->lightlevel); + g->maxlight = sector->lightlevel; + g->thinker.function.acp1 = (actionf_p1) T_Glow; + g->direction = -1; + + sector->special = 0; +} + diff --git a/firmware_p4/components/Applications/doom/p_local.h b/firmware_p4/components/Applications/doom/p_local.h new file mode 100644 index 000000000..95fa40534 --- /dev/null +++ b/firmware_p4/components/Applications/doom/p_local.h @@ -0,0 +1,297 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Play functions, animation, global header. +// + + +#ifndef __P_LOCAL__ +#define __P_LOCAL__ + +#ifndef __R_LOCAL__ +#include "r_local.h" +#endif + +#define FLOATSPEED (FRACUNIT*4) + + +#define MAXHEALTH 100 +#define VIEWHEIGHT (41*FRACUNIT) + +// mapblocks are used to check movement +// against lines and things +#define MAPBLOCKUNITS 128 +#define MAPBLOCKSIZE (MAPBLOCKUNITS*FRACUNIT) +#define MAPBLOCKSHIFT (FRACBITS+7) +#define MAPBMASK (MAPBLOCKSIZE-1) +#define MAPBTOFRAC (MAPBLOCKSHIFT-FRACBITS) + + +// player radius for movement checking +#define PLAYERRADIUS 16*FRACUNIT + +// MAXRADIUS is for precalculated sector block boxes +// the spider demon is larger, +// but we do not have any moving sectors nearby +#define MAXRADIUS 32*FRACUNIT + +#define GRAVITY FRACUNIT +#define MAXMOVE (30*FRACUNIT) + +#define USERANGE (64*FRACUNIT) +#define MELEERANGE (64*FRACUNIT) +#define MISSILERANGE (32*64*FRACUNIT) + +// follow a player exlusively for 3 seconds +#define BASETHRESHOLD 100 + + + +// +// P_TICK +// + +// both the head and tail of the thinker list +extern thinker_t thinkercap; + + +void P_InitThinkers (void); +void P_AddThinker (thinker_t* thinker); +void P_RemoveThinker (thinker_t* thinker); + + +// +// P_PSPR +// +void P_SetupPsprites (player_t* curplayer); +void P_MovePsprites (player_t* curplayer); +void P_DropWeapon (player_t* player); + + +// +// P_USER +// +void P_PlayerThink (player_t* player); + + +// +// P_MOBJ +// +#define ONFLOORZ INT_MIN +#define ONCEILINGZ INT_MAX + +// Time interval for item respawning. +#define ITEMQUESIZE 128 + +extern mapthing_t itemrespawnque[ITEMQUESIZE]; +extern int itemrespawntime[ITEMQUESIZE]; +extern int iquehead; +extern int iquetail; + + +void P_RespawnSpecials (void); + +mobj_t* +P_SpawnMobj +( fixed_t x, + fixed_t y, + fixed_t z, + mobjtype_t type ); + +void P_RemoveMobj (mobj_t* th); +mobj_t* P_SubstNullMobj (mobj_t* th); +boolean P_SetMobjState (mobj_t* mobj, statenum_t state); +void P_MobjThinker (mobj_t* mobj); + +void P_SpawnPuff (fixed_t x, fixed_t y, fixed_t z); +void P_SpawnBlood (fixed_t x, fixed_t y, fixed_t z, int damage); +mobj_t* P_SpawnMissile (mobj_t* source, mobj_t* dest, mobjtype_t type); +void P_SpawnPlayerMissile (mobj_t* source, mobjtype_t type); + + +// +// P_ENEMY +// +void P_NoiseAlert (mobj_t* target, mobj_t* emmiter); + + +// +// P_MAPUTL +// +typedef struct +{ + fixed_t x; + fixed_t y; + fixed_t dx; + fixed_t dy; + +} divline_t; + +typedef struct +{ + fixed_t frac; // along trace line + boolean isaline; + union { + mobj_t* thing; + line_t* line; + } d; +} intercept_t; + +// Extended MAXINTERCEPTS, to allow for intercepts overrun emulation. + +#define MAXINTERCEPTS_ORIGINAL 128 +#define MAXINTERCEPTS (MAXINTERCEPTS_ORIGINAL + 61) + +extern intercept_t intercepts[MAXINTERCEPTS]; +extern intercept_t* intercept_p; + +typedef boolean (*traverser_t) (intercept_t *in); + +fixed_t P_AproxDistance (fixed_t dx, fixed_t dy); +int P_PointOnLineSide (fixed_t x, fixed_t y, line_t* line); +int P_PointOnDivlineSide (fixed_t x, fixed_t y, divline_t* line); +void P_MakeDivline (line_t* li, divline_t* dl); +fixed_t P_InterceptVector (divline_t* v2, divline_t* v1); +int P_BoxOnLineSide (fixed_t* tmbox, line_t* ld); + +extern fixed_t opentop; +extern fixed_t openbottom; +extern fixed_t openrange; +extern fixed_t lowfloor; + +void P_LineOpening (line_t* linedef); + +boolean P_BlockLinesIterator (int x, int y, boolean(*func)(line_t*) ); +boolean P_BlockThingsIterator (int x, int y, boolean(*func)(mobj_t*) ); + +#define PT_ADDLINES 1 +#define PT_ADDTHINGS 2 +#define PT_EARLYOUT 4 + +extern divline_t trace; + +boolean +P_PathTraverse +( fixed_t x1, + fixed_t y1, + fixed_t x2, + fixed_t y2, + int flags, + boolean (*trav) (intercept_t *)); + +void P_UnsetThingPosition (mobj_t* thing); +void P_SetThingPosition (mobj_t* thing); + + +// +// P_MAP +// + +// If "floatok" true, move would be ok +// if within "tmfloorz - tmceilingz". +extern boolean floatok; +extern fixed_t tmfloorz; +extern fixed_t tmceilingz; + + +extern line_t* ceilingline; + +// fraggle: I have increased the size of this buffer. In the original Doom, +// overrunning past this limit caused other bits of memory to be overwritten, +// affecting demo playback. However, in doing so, the limit was still +// exceeded. So we have to support more than 8 specials. +// +// We keep the original limit, to detect what variables in memory were +// overwritten (see SpechitOverrun()) + +#define MAXSPECIALCROSS 20 +#define MAXSPECIALCROSS_ORIGINAL 8 + +extern line_t* spechit[MAXSPECIALCROSS]; +extern int numspechit; + +boolean P_CheckPosition (mobj_t *thing, fixed_t x, fixed_t y); +boolean P_TryMove (mobj_t* thing, fixed_t x, fixed_t y); +boolean P_TeleportMove (mobj_t* thing, fixed_t x, fixed_t y); +void P_SlideMove (mobj_t* mo); +boolean P_CheckSight (mobj_t* t1, mobj_t* t2); +void P_UseLines (player_t* player); + +boolean P_ChangeSector (sector_t* sector, boolean crunch); + +extern mobj_t* linetarget; // who got hit (or NULL) + +fixed_t +P_AimLineAttack +( mobj_t* t1, + angle_t angle, + fixed_t distance ); + +void +P_LineAttack +( mobj_t* t1, + angle_t angle, + fixed_t distance, + fixed_t slope, + int damage ); + +void +P_RadiusAttack +( mobj_t* spot, + mobj_t* source, + int damage ); + + + +// +// P_SETUP +// +extern byte* rejectmatrix; // for fast sight rejection +extern short* blockmaplump; // offsets in blockmap are from here +extern short* blockmap; +extern int bmapwidth; +extern int bmapheight; // in mapblocks +extern fixed_t bmaporgx; +extern fixed_t bmaporgy; // origin of block map +extern mobj_t** blocklinks; // for thing chains + + + +// +// P_INTER +// +extern int maxammo[NUMAMMO]; +extern int clipammo[NUMAMMO]; + +void +P_TouchSpecialThing +( mobj_t* special, + mobj_t* toucher ); + +void +P_DamageMobj +( mobj_t* target, + mobj_t* inflictor, + mobj_t* source, + int damage ); + + +// +// P_SPEC +// +#include "p_spec.h" + + +#endif // __P_LOCAL__ diff --git a/firmware_p4/components/Applications/doom/p_map.c b/firmware_p4/components/Applications/doom/p_map.c new file mode 100644 index 000000000..e371869a7 --- /dev/null +++ b/firmware_p4/components/Applications/doom/p_map.c @@ -0,0 +1,1448 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard, Andrey Budko +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Movement, collision handling. +// Shooting and aiming. +// + +#include +#include + +#include "deh_misc.h" + +#include "m_bbox.h" +#include "m_random.h" +#include "i_system.h" + +#include "doomdef.h" +#include "m_argv.h" +#include "m_misc.h" +#include "p_local.h" + +#include "s_sound.h" + +// State. +#include "doomstat.h" +#include "r_state.h" +// Data. +#include "sounds.h" + +// Spechit overrun magic value. +// +// This is the value used by PrBoom-plus. I think the value below is +// actually better and works with more demos. However, I think +// it's better for the spechits emulation to be compatible with +// PrBoom-plus, at least so that the big spechits emulation list +// on Doomworld can also be used with Chocolate Doom. + +#define DEFAULT_SPECHIT_MAGIC 0x01C09C98 + +// This is from a post by myk on the Doomworld forums, +// outputted from entryway's spechit_magic generator for +// s205n546.lmp. The _exact_ value of this isn't too +// important; as long as it is in the right general +// range, it will usually work. Otherwise, we can use +// the generator (hacked doom2.exe) and provide it +// with -spechit. + +//#define DEFAULT_SPECHIT_MAGIC 0x84f968e8 + + +fixed_t tmbbox[4]; +mobj_t* tmthing; +int tmflags; +fixed_t tmx; +fixed_t tmy; + + +// If "floatok" true, move would be ok +// if within "tmfloorz - tmceilingz". +boolean floatok; + +fixed_t tmfloorz; +fixed_t tmceilingz; +fixed_t tmdropoffz; + +// keep track of the line that lowers the ceiling, +// so missiles don't explode against sky hack walls +line_t* ceilingline; + +// keep track of special lines as they are hit, +// but don't process them until the move is proven valid + +line_t* spechit[MAXSPECIALCROSS]; +int numspechit; + + + +// +// TELEPORT MOVE +// + +// +// PIT_StompThing +// +boolean PIT_StompThing (mobj_t* thing) +{ + fixed_t blockdist; + + if (!(thing->flags & MF_SHOOTABLE) ) + return true; + + blockdist = thing->radius + tmthing->radius; + + if ( abs(thing->x - tmx) >= blockdist + || abs(thing->y - tmy) >= blockdist ) + { + // didn't hit it + return true; + } + + // don't clip against self + if (thing == tmthing) + return true; + + // monsters don't stomp things except on boss level + if ( !tmthing->player && gamemap != 30) + return false; + + P_DamageMobj (thing, tmthing, tmthing, 10000); + + return true; +} + + +// +// P_TeleportMove +// +boolean +P_TeleportMove +( mobj_t* thing, + fixed_t x, + fixed_t y ) +{ + int xl; + int xh; + int yl; + int yh; + int bx; + int by; + + subsector_t* newsubsec; + + // kill anything occupying the position + tmthing = thing; + tmflags = thing->flags; + + tmx = x; + tmy = y; + + tmbbox[BOXTOP] = y + tmthing->radius; + tmbbox[BOXBOTTOM] = y - tmthing->radius; + tmbbox[BOXRIGHT] = x + tmthing->radius; + tmbbox[BOXLEFT] = x - tmthing->radius; + + newsubsec = R_PointInSubsector (x,y); + ceilingline = NULL; + + // The base floor/ceiling is from the subsector + // that contains the point. + // Any contacted lines the step closer together + // will adjust them. + tmfloorz = tmdropoffz = newsubsec->sector->floorheight; + tmceilingz = newsubsec->sector->ceilingheight; + + validcount++; + numspechit = 0; + + // stomp on any things contacted + xl = (tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT; + xh = (tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT; + yl = (tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT; + yh = (tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT; + + for (bx=xl ; bx<=xh ; bx++) + for (by=yl ; by<=yh ; by++) + if (!P_BlockThingsIterator(bx,by,PIT_StompThing)) + return false; + + // the move is ok, + // so link the thing into its new position + P_UnsetThingPosition (thing); + + thing->floorz = tmfloorz; + thing->ceilingz = tmceilingz; + thing->x = x; + thing->y = y; + + P_SetThingPosition (thing); + + return true; +} + + +// +// MOVEMENT ITERATOR FUNCTIONS +// + +static void SpechitOverrun(line_t *ld); + +// +// PIT_CheckLine +// Adjusts tmfloorz and tmceilingz as lines are contacted +// +boolean PIT_CheckLine (line_t* ld) +{ + if (tmbbox[BOXRIGHT] <= ld->bbox[BOXLEFT] + || tmbbox[BOXLEFT] >= ld->bbox[BOXRIGHT] + || tmbbox[BOXTOP] <= ld->bbox[BOXBOTTOM] + || tmbbox[BOXBOTTOM] >= ld->bbox[BOXTOP] ) + return true; + + if (P_BoxOnLineSide (tmbbox, ld) != -1) + return true; + + // A line has been hit + + // The moving thing's destination position will cross + // the given line. + // If this should not be allowed, return false. + // If the line is special, keep track of it + // to process later if the move is proven ok. + // NOTE: specials are NOT sorted by order, + // so two special lines that are only 8 pixels apart + // could be crossed in either order. + + if (!ld->backsector) + return false; // one sided line + + if (!(tmthing->flags & MF_MISSILE) ) + { + if ( ld->flags & ML_BLOCKING ) + return false; // explicitly blocking everything + + if ( !tmthing->player && ld->flags & ML_BLOCKMONSTERS ) + return false; // block monsters only + } + + // set openrange, opentop, openbottom + P_LineOpening (ld); + + // adjust floor / ceiling heights + if (opentop < tmceilingz) + { + tmceilingz = opentop; + ceilingline = ld; + } + + if (openbottom > tmfloorz) + tmfloorz = openbottom; + + if (lowfloor < tmdropoffz) + tmdropoffz = lowfloor; + + // if contacted a special line, add it to the list + if (ld->special) + { + spechit[numspechit] = ld; + numspechit++; + + // fraggle: spechits overrun emulation code from prboom-plus + if (numspechit > MAXSPECIALCROSS_ORIGINAL) + { + SpechitOverrun(ld); + } + } + + return true; +} + +// +// PIT_CheckThing +// +boolean PIT_CheckThing (mobj_t* thing) +{ + fixed_t blockdist; + boolean solid; + int damage; + + if (!(thing->flags & (MF_SOLID|MF_SPECIAL|MF_SHOOTABLE) )) + return true; + + blockdist = thing->radius + tmthing->radius; + + if ( abs(thing->x - tmx) >= blockdist + || abs(thing->y - tmy) >= blockdist ) + { + // didn't hit it + return true; + } + + // don't clip against self + if (thing == tmthing) + return true; + + // check for skulls slamming into things + if (tmthing->flags & MF_SKULLFLY) + { + damage = ((P_Random()%8)+1)*tmthing->info->damage; + + P_DamageMobj (thing, tmthing, tmthing, damage); + + tmthing->flags &= ~MF_SKULLFLY; + tmthing->momx = tmthing->momy = tmthing->momz = 0; + + P_SetMobjState (tmthing, tmthing->info->spawnstate); + + return false; // stop moving + } + + + // missiles can hit other things + if (tmthing->flags & MF_MISSILE) + { + // see if it went over / under + if (tmthing->z > thing->z + thing->height) + return true; // overhead + if (tmthing->z+tmthing->height < thing->z) + return true; // underneath + + if (tmthing->target + && (tmthing->target->type == thing->type || + (tmthing->target->type == MT_KNIGHT && thing->type == MT_BRUISER)|| + (tmthing->target->type == MT_BRUISER && thing->type == MT_KNIGHT) ) ) + { + // Don't hit same species as originator. + if (thing == tmthing->target) + return true; + + // sdh: Add deh_species_infighting here. We can override the + // "monsters of the same species cant hurt each other" behavior + // through dehacked patches + + if (thing->type != MT_PLAYER && !deh_species_infighting) + { + // Explode, but do no damage. + // Let players missile other players. + return false; + } + } + + if (! (thing->flags & MF_SHOOTABLE) ) + { + // didn't do any damage + return !(thing->flags & MF_SOLID); + } + + // damage / explode + damage = ((P_Random()%8)+1)*tmthing->info->damage; + P_DamageMobj (thing, tmthing, tmthing->target, damage); + + // don't traverse any more + return false; + } + + // check for special pickup + if (thing->flags & MF_SPECIAL) + { + solid = thing->flags&MF_SOLID; + if (tmflags&MF_PICKUP) + { + // can remove thing + P_TouchSpecialThing (thing, tmthing); + } + return !solid; + } + + return !(thing->flags & MF_SOLID); +} + + +// +// MOVEMENT CLIPPING +// + +// +// P_CheckPosition +// This is purely informative, nothing is modified +// (except things picked up). +// +// in: +// a mobj_t (can be valid or invalid) +// a position to be checked +// (doesn't need to be related to the mobj_t->x,y) +// +// during: +// special things are touched if MF_PICKUP +// early out on solid lines? +// +// out: +// newsubsec +// floorz +// ceilingz +// tmdropoffz +// the lowest point contacted +// (monsters won't move to a dropoff) +// speciallines[] +// numspeciallines +// +boolean +P_CheckPosition +( mobj_t* thing, + fixed_t x, + fixed_t y ) +{ + int xl; + int xh; + int yl; + int yh; + int bx; + int by; + subsector_t* newsubsec; + + tmthing = thing; + tmflags = thing->flags; + + tmx = x; + tmy = y; + + tmbbox[BOXTOP] = y + tmthing->radius; + tmbbox[BOXBOTTOM] = y - tmthing->radius; + tmbbox[BOXRIGHT] = x + tmthing->radius; + tmbbox[BOXLEFT] = x - tmthing->radius; + + newsubsec = R_PointInSubsector (x,y); + ceilingline = NULL; + + // The base floor / ceiling is from the subsector + // that contains the point. + // Any contacted lines the step closer together + // will adjust them. + tmfloorz = tmdropoffz = newsubsec->sector->floorheight; + tmceilingz = newsubsec->sector->ceilingheight; + + validcount++; + numspechit = 0; + + if ( tmflags & MF_NOCLIP ) + return true; + + // Check things first, possibly picking things up. + // The bounding box is extended by MAXRADIUS + // because mobj_ts are grouped into mapblocks + // based on their origin point, and can overlap + // into adjacent blocks by up to MAXRADIUS units. + xl = (tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT; + xh = (tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT; + yl = (tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT; + yh = (tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT; + + for (bx=xl ; bx<=xh ; bx++) + for (by=yl ; by<=yh ; by++) + if (!P_BlockThingsIterator(bx,by,PIT_CheckThing)) + return false; + + // check lines + xl = (tmbbox[BOXLEFT] - bmaporgx)>>MAPBLOCKSHIFT; + xh = (tmbbox[BOXRIGHT] - bmaporgx)>>MAPBLOCKSHIFT; + yl = (tmbbox[BOXBOTTOM] - bmaporgy)>>MAPBLOCKSHIFT; + yh = (tmbbox[BOXTOP] - bmaporgy)>>MAPBLOCKSHIFT; + + for (bx=xl ; bx<=xh ; bx++) + for (by=yl ; by<=yh ; by++) + if (!P_BlockLinesIterator (bx,by,PIT_CheckLine)) + return false; + + return true; +} + + +// +// P_TryMove +// Attempt to move to a new position, +// crossing special lines unless MF_TELEPORT is set. +// +boolean +P_TryMove +( mobj_t* thing, + fixed_t x, + fixed_t y ) +{ + fixed_t oldx; + fixed_t oldy; + int side; + int oldside; + line_t* ld; + + floatok = false; + if (!P_CheckPosition (thing, x, y)) + return false; // solid wall or thing + + if ( !(thing->flags & MF_NOCLIP) ) + { + if (tmceilingz - tmfloorz < thing->height) + return false; // doesn't fit + + floatok = true; + + if ( !(thing->flags&MF_TELEPORT) + &&tmceilingz - thing->z < thing->height) + return false; // mobj must lower itself to fit + + if ( !(thing->flags&MF_TELEPORT) + && tmfloorz - thing->z > 24*FRACUNIT ) + return false; // too big a step up + + if ( !(thing->flags&(MF_DROPOFF|MF_FLOAT)) + && tmfloorz - tmdropoffz > 24*FRACUNIT ) + return false; // don't stand over a dropoff + } + + // the move is ok, + // so link the thing into its new position + P_UnsetThingPosition (thing); + + oldx = thing->x; + oldy = thing->y; + thing->floorz = tmfloorz; + thing->ceilingz = tmceilingz; + thing->x = x; + thing->y = y; + + P_SetThingPosition (thing); + + // if any special lines were hit, do the effect + if (! (thing->flags&(MF_TELEPORT|MF_NOCLIP)) ) + { + while (numspechit--) + { + // see if the line was crossed + ld = spechit[numspechit]; + side = P_PointOnLineSide (thing->x, thing->y, ld); + oldside = P_PointOnLineSide (oldx, oldy, ld); + if (side != oldside) + { + if (ld->special) + P_CrossSpecialLine (ld-lines, oldside, thing); + } + } + } + + return true; +} + + +// +// P_ThingHeightClip +// Takes a valid thing and adjusts the thing->floorz, +// thing->ceilingz, and possibly thing->z. +// This is called for all nearby monsters +// whenever a sector changes height. +// If the thing doesn't fit, +// the z will be set to the lowest value +// and false will be returned. +// +boolean P_ThingHeightClip (mobj_t* thing) +{ + boolean onfloor; + + onfloor = (thing->z == thing->floorz); + + P_CheckPosition (thing, thing->x, thing->y); + // what about stranding a monster partially off an edge? + + thing->floorz = tmfloorz; + thing->ceilingz = tmceilingz; + + if (onfloor) + { + // walking monsters rise and fall with the floor + thing->z = thing->floorz; + } + else + { + // don't adjust a floating monster unless forced to + if (thing->z+thing->height > thing->ceilingz) + thing->z = thing->ceilingz - thing->height; + } + + if (thing->ceilingz - thing->floorz < thing->height) + return false; + + return true; +} + + + +// +// SLIDE MOVE +// Allows the player to slide along any angled walls. +// +fixed_t bestslidefrac; +fixed_t secondslidefrac; + +line_t* bestslideline; +line_t* secondslideline; + +mobj_t* slidemo; + +fixed_t tmxmove; +fixed_t tmymove; + + + +// +// P_HitSlideLine +// Adjusts the xmove / ymove +// so that the next move will slide along the wall. +// +void P_HitSlideLine (line_t* ld) +{ + int side; + + angle_t lineangle; + angle_t moveangle; + angle_t deltaangle; + + fixed_t movelen; + fixed_t newlen; + + + if (ld->slopetype == ST_HORIZONTAL) + { + tmymove = 0; + return; + } + + if (ld->slopetype == ST_VERTICAL) + { + tmxmove = 0; + return; + } + + side = P_PointOnLineSide (slidemo->x, slidemo->y, ld); + + lineangle = R_PointToAngle2 (0,0, ld->dx, ld->dy); + + if (side == 1) + lineangle += ANG180; + + moveangle = R_PointToAngle2 (0,0, tmxmove, tmymove); + deltaangle = moveangle-lineangle; + + if (deltaangle > ANG180) + deltaangle += ANG180; + // I_Error ("SlideLine: ang>ANG180"); + + lineangle >>= ANGLETOFINESHIFT; + deltaangle >>= ANGLETOFINESHIFT; + + movelen = P_AproxDistance (tmxmove, tmymove); + newlen = FixedMul (movelen, finecosine[deltaangle]); + + tmxmove = FixedMul (newlen, finecosine[lineangle]); + tmymove = FixedMul (newlen, finesine[lineangle]); +} + + +// +// PTR_SlideTraverse +// +boolean PTR_SlideTraverse (intercept_t* in) +{ + line_t* li; + + if (!in->isaline) + I_Error ("PTR_SlideTraverse: not a line?"); + + li = in->d.line; + + if ( ! (li->flags & ML_TWOSIDED) ) + { + if (P_PointOnLineSide (slidemo->x, slidemo->y, li)) + { + // don't hit the back side + return true; + } + goto isblocking; + } + + // set openrange, opentop, openbottom + P_LineOpening (li); + + if (openrange < slidemo->height) + goto isblocking; // doesn't fit + + if (opentop - slidemo->z < slidemo->height) + goto isblocking; // mobj is too high + + if (openbottom - slidemo->z > 24*FRACUNIT ) + goto isblocking; // too big a step up + + // this line doesn't block movement + return true; + + // the line does block movement, + // see if it is closer than best so far + isblocking: + if (in->frac < bestslidefrac) + { + secondslidefrac = bestslidefrac; + secondslideline = bestslideline; + bestslidefrac = in->frac; + bestslideline = li; + } + + return false; // stop +} + + + +// +// P_SlideMove +// The momx / momy move is bad, so try to slide +// along a wall. +// Find the first line hit, move flush to it, +// and slide along it +// +// This is a kludgy mess. +// +void P_SlideMove (mobj_t* mo) +{ + fixed_t leadx; + fixed_t leady; + fixed_t trailx; + fixed_t traily; + fixed_t newx; + fixed_t newy; + int hitcount; + + slidemo = mo; + hitcount = 0; + + retry: + if (++hitcount == 3) + goto stairstep; // don't loop forever + + + // trace along the three leading corners + if (mo->momx > 0) + { + leadx = mo->x + mo->radius; + trailx = mo->x - mo->radius; + } + else + { + leadx = mo->x - mo->radius; + trailx = mo->x + mo->radius; + } + + if (mo->momy > 0) + { + leady = mo->y + mo->radius; + traily = mo->y - mo->radius; + } + else + { + leady = mo->y - mo->radius; + traily = mo->y + mo->radius; + } + + bestslidefrac = FRACUNIT+1; + + P_PathTraverse ( leadx, leady, leadx+mo->momx, leady+mo->momy, + PT_ADDLINES, PTR_SlideTraverse ); + P_PathTraverse ( trailx, leady, trailx+mo->momx, leady+mo->momy, + PT_ADDLINES, PTR_SlideTraverse ); + P_PathTraverse ( leadx, traily, leadx+mo->momx, traily+mo->momy, + PT_ADDLINES, PTR_SlideTraverse ); + + // move up to the wall + if (bestslidefrac == FRACUNIT+1) + { + // the move most have hit the middle, so stairstep + stairstep: + if (!P_TryMove (mo, mo->x, mo->y + mo->momy)) + P_TryMove (mo, mo->x + mo->momx, mo->y); + return; + } + + // fudge a bit to make sure it doesn't hit + bestslidefrac -= 0x800; + if (bestslidefrac > 0) + { + newx = FixedMul (mo->momx, bestslidefrac); + newy = FixedMul (mo->momy, bestslidefrac); + + if (!P_TryMove (mo, mo->x+newx, mo->y+newy)) + goto stairstep; + } + + // Now continue along the wall. + // First calculate remainder. + bestslidefrac = FRACUNIT-(bestslidefrac+0x800); + + if (bestslidefrac > FRACUNIT) + bestslidefrac = FRACUNIT; + + if (bestslidefrac <= 0) + return; + + tmxmove = FixedMul (mo->momx, bestslidefrac); + tmymove = FixedMul (mo->momy, bestslidefrac); + + P_HitSlideLine (bestslideline); // clip the moves + + mo->momx = tmxmove; + mo->momy = tmymove; + + if (!P_TryMove (mo, mo->x+tmxmove, mo->y+tmymove)) + { + goto retry; + } +} + + +// +// P_LineAttack +// +mobj_t* linetarget; // who got hit (or NULL) +mobj_t* shootthing; + +// Height if not aiming up or down +// ???: use slope for monsters? +fixed_t shootz; + +int la_damage; +fixed_t attackrange; + +fixed_t aimslope; + +// slopes to top and bottom of target +extern fixed_t topslope; +extern fixed_t bottomslope; + + +// +// PTR_AimTraverse +// Sets linetaget and aimslope when a target is aimed at. +// +boolean +PTR_AimTraverse (intercept_t* in) +{ + line_t* li; + mobj_t* th; + fixed_t slope; + fixed_t thingtopslope; + fixed_t thingbottomslope; + fixed_t dist; + + if (in->isaline) + { + li = in->d.line; + + if ( !(li->flags & ML_TWOSIDED) ) + return false; // stop + + // Crosses a two sided line. + // A two sided line will restrict + // the possible target ranges. + P_LineOpening (li); + + if (openbottom >= opentop) + return false; // stop + + dist = FixedMul (attackrange, in->frac); + + if (li->backsector == NULL + || li->frontsector->floorheight != li->backsector->floorheight) + { + slope = FixedDiv (openbottom - shootz , dist); + if (slope > bottomslope) + bottomslope = slope; + } + + if (li->backsector == NULL + || li->frontsector->ceilingheight != li->backsector->ceilingheight) + { + slope = FixedDiv (opentop - shootz , dist); + if (slope < topslope) + topslope = slope; + } + + if (topslope <= bottomslope) + return false; // stop + + return true; // shot continues + } + + // shoot a thing + th = in->d.thing; + if (th == shootthing) + return true; // can't shoot self + + if (!(th->flags&MF_SHOOTABLE)) + return true; // corpse or something + + // check angles to see if the thing can be aimed at + dist = FixedMul (attackrange, in->frac); + thingtopslope = FixedDiv (th->z+th->height - shootz , dist); + + if (thingtopslope < bottomslope) + return true; // shot over the thing + + thingbottomslope = FixedDiv (th->z - shootz, dist); + + if (thingbottomslope > topslope) + return true; // shot under the thing + + // this thing can be hit! + if (thingtopslope > topslope) + thingtopslope = topslope; + + if (thingbottomslope < bottomslope) + thingbottomslope = bottomslope; + + aimslope = (thingtopslope+thingbottomslope)/2; + linetarget = th; + + return false; // don't go any farther +} + + +// +// PTR_ShootTraverse +// +boolean PTR_ShootTraverse (intercept_t* in) +{ + fixed_t x; + fixed_t y; + fixed_t z; + fixed_t frac; + + line_t* li; + + mobj_t* th; + + fixed_t slope; + fixed_t dist; + fixed_t thingtopslope; + fixed_t thingbottomslope; + + if (in->isaline) + { + li = in->d.line; + + if (li->special) + P_ShootSpecialLine (shootthing, li); + + if ( !(li->flags & ML_TWOSIDED) ) + goto hitline; + + // crosses a two sided line + P_LineOpening (li); + + dist = FixedMul (attackrange, in->frac); + + // e6y: emulation of missed back side on two-sided lines. + // backsector can be NULL when emulating missing back side. + + if (li->backsector == NULL) + { + slope = FixedDiv (openbottom - shootz , dist); + if (slope > aimslope) + goto hitline; + + slope = FixedDiv (opentop - shootz , dist); + if (slope < aimslope) + goto hitline; + } + else + { + if (li->frontsector->floorheight != li->backsector->floorheight) + { + slope = FixedDiv (openbottom - shootz , dist); + if (slope > aimslope) + goto hitline; + } + + if (li->frontsector->ceilingheight != li->backsector->ceilingheight) + { + slope = FixedDiv (opentop - shootz , dist); + if (slope < aimslope) + goto hitline; + } + } + + // shot continues + return true; + + + // hit line + hitline: + // position a bit closer + frac = in->frac - FixedDiv (4*FRACUNIT,attackrange); + x = trace.x + FixedMul (trace.dx, frac); + y = trace.y + FixedMul (trace.dy, frac); + z = shootz + FixedMul (aimslope, FixedMul(frac, attackrange)); + + if (li->frontsector->ceilingpic == skyflatnum) + { + // don't shoot the sky! + if (z > li->frontsector->ceilingheight) + return false; + + // it's a sky hack wall + if (li->backsector && li->backsector->ceilingpic == skyflatnum) + return false; + } + + // Spawn bullet puffs. + P_SpawnPuff (x,y,z); + + // don't go any farther + return false; + } + + // shoot a thing + th = in->d.thing; + if (th == shootthing) + return true; // can't shoot self + + if (!(th->flags&MF_SHOOTABLE)) + return true; // corpse or something + + // check angles to see if the thing can be aimed at + dist = FixedMul (attackrange, in->frac); + thingtopslope = FixedDiv (th->z+th->height - shootz , dist); + + if (thingtopslope < aimslope) + return true; // shot over the thing + + thingbottomslope = FixedDiv (th->z - shootz, dist); + + if (thingbottomslope > aimslope) + return true; // shot under the thing + + + // hit thing + // position a bit closer + frac = in->frac - FixedDiv (10*FRACUNIT,attackrange); + + x = trace.x + FixedMul (trace.dx, frac); + y = trace.y + FixedMul (trace.dy, frac); + z = shootz + FixedMul (aimslope, FixedMul(frac, attackrange)); + + // Spawn bullet puffs or blod spots, + // depending on target type. + if (in->d.thing->flags & MF_NOBLOOD) + P_SpawnPuff (x,y,z); + else + P_SpawnBlood (x,y,z, la_damage); + + if (la_damage) + P_DamageMobj (th, shootthing, shootthing, la_damage); + + // don't go any farther + return false; + +} + + +// +// P_AimLineAttack +// +fixed_t +P_AimLineAttack +( mobj_t* t1, + angle_t angle, + fixed_t distance ) +{ + fixed_t x2; + fixed_t y2; + + t1 = P_SubstNullMobj(t1); + + angle >>= ANGLETOFINESHIFT; + shootthing = t1; + + x2 = t1->x + (distance>>FRACBITS)*finecosine[angle]; + y2 = t1->y + (distance>>FRACBITS)*finesine[angle]; + shootz = t1->z + (t1->height>>1) + 8*FRACUNIT; + + // can't shoot outside view angles + topslope = 100*FRACUNIT/160; + bottomslope = -100*FRACUNIT/160; + + attackrange = distance; + linetarget = NULL; + + P_PathTraverse ( t1->x, t1->y, + x2, y2, + PT_ADDLINES|PT_ADDTHINGS, + PTR_AimTraverse ); + + if (linetarget) + return aimslope; + + return 0; +} + + +// +// P_LineAttack +// If damage == 0, it is just a test trace +// that will leave linetarget set. +// +void +P_LineAttack +( mobj_t* t1, + angle_t angle, + fixed_t distance, + fixed_t slope, + int damage ) +{ + fixed_t x2; + fixed_t y2; + + angle >>= ANGLETOFINESHIFT; + shootthing = t1; + la_damage = damage; + x2 = t1->x + (distance>>FRACBITS)*finecosine[angle]; + y2 = t1->y + (distance>>FRACBITS)*finesine[angle]; + shootz = t1->z + (t1->height>>1) + 8*FRACUNIT; + attackrange = distance; + aimslope = slope; + + P_PathTraverse ( t1->x, t1->y, + x2, y2, + PT_ADDLINES|PT_ADDTHINGS, + PTR_ShootTraverse ); +} + + + +// +// USE LINES +// +mobj_t* usething; + +boolean PTR_UseTraverse (intercept_t* in) +{ + int side; + + if (!in->d.line->special) + { + P_LineOpening (in->d.line); + if (openrange <= 0) + { + S_StartSound (usething, sfx_noway); + + // can't use through a wall + return false; + } + // not a special line, but keep checking + return true ; + } + + side = 0; + if (P_PointOnLineSide (usething->x, usething->y, in->d.line) == 1) + side = 1; + + // return false; // don't use back side + + P_UseSpecialLine (usething, in->d.line, side); + + // can't use for than one special line in a row + return false; +} + + +// +// P_UseLines +// Looks for special lines in front of the player to activate. +// +void P_UseLines (player_t* player) +{ + int angle; + fixed_t x1; + fixed_t y1; + fixed_t x2; + fixed_t y2; + + usething = player->mo; + + angle = player->mo->angle >> ANGLETOFINESHIFT; + + x1 = player->mo->x; + y1 = player->mo->y; + x2 = x1 + (USERANGE>>FRACBITS)*finecosine[angle]; + y2 = y1 + (USERANGE>>FRACBITS)*finesine[angle]; + + P_PathTraverse ( x1, y1, x2, y2, PT_ADDLINES, PTR_UseTraverse ); +} + + +// +// RADIUS ATTACK +// +mobj_t* bombsource; +mobj_t* bombspot; +int bombdamage; + + +// +// PIT_RadiusAttack +// "bombsource" is the creature +// that caused the explosion at "bombspot". +// +boolean PIT_RadiusAttack (mobj_t* thing) +{ + fixed_t dx; + fixed_t dy; + fixed_t dist; + + if (!(thing->flags & MF_SHOOTABLE) ) + return true; + + // Boss spider and cyborg + // take no damage from concussion. + if (thing->type == MT_CYBORG + || thing->type == MT_SPIDER) + return true; + + dx = abs(thing->x - bombspot->x); + dy = abs(thing->y - bombspot->y); + + dist = dx>dy ? dx : dy; + dist = (dist - thing->radius) >> FRACBITS; + + if (dist < 0) + dist = 0; + + if (dist >= bombdamage) + return true; // out of range + + if ( P_CheckSight (thing, bombspot) ) + { + // must be in direct path + P_DamageMobj (thing, bombspot, bombsource, bombdamage - dist); + } + + return true; +} + + +// +// P_RadiusAttack +// Source is the creature that caused the explosion at spot. +// +void +P_RadiusAttack +( mobj_t* spot, + mobj_t* source, + int damage ) +{ + int x; + int y; + + int xl; + int xh; + int yl; + int yh; + + fixed_t dist; + + dist = (damage+MAXRADIUS)<y + dist - bmaporgy)>>MAPBLOCKSHIFT; + yl = (spot->y - dist - bmaporgy)>>MAPBLOCKSHIFT; + xh = (spot->x + dist - bmaporgx)>>MAPBLOCKSHIFT; + xl = (spot->x - dist - bmaporgx)>>MAPBLOCKSHIFT; + bombspot = spot; + bombsource = source; + bombdamage = damage; + + for (y=yl ; y<=yh ; y++) + for (x=xl ; x<=xh ; x++) + P_BlockThingsIterator (x, y, PIT_RadiusAttack ); +} + + + +// +// SECTOR HEIGHT CHANGING +// After modifying a sectors floor or ceiling height, +// call this routine to adjust the positions +// of all things that touch the sector. +// +// If anything doesn't fit anymore, true will be returned. +// If crunch is true, they will take damage +// as they are being crushed. +// If Crunch is false, you should set the sector height back +// the way it was and call P_ChangeSector again +// to undo the changes. +// +boolean crushchange; +boolean nofit; + + +// +// PIT_ChangeSector +// +boolean PIT_ChangeSector (mobj_t* thing) +{ + mobj_t* mo; + + if (P_ThingHeightClip (thing)) + { + // keep checking + return true; + } + + + // crunch bodies to giblets + if (thing->health <= 0) + { + P_SetMobjState (thing, S_GIBS); + + thing->flags &= ~MF_SOLID; + thing->height = 0; + thing->radius = 0; + + // keep checking + return true; + } + + // crunch dropped items + if (thing->flags & MF_DROPPED) + { + P_RemoveMobj (thing); + + // keep checking + return true; + } + + if (! (thing->flags & MF_SHOOTABLE) ) + { + // assume it is bloody gibs or something + return true; + } + + nofit = true; + + if (crushchange && !(leveltime&3) ) + { + P_DamageMobj(thing,NULL,NULL,10); + + // spray blood in a random direction + mo = P_SpawnMobj (thing->x, + thing->y, + thing->z + thing->height/2, MT_BLOOD); + + mo->momx = (P_Random() - P_Random ())<<12; + mo->momy = (P_Random() - P_Random ())<<12; + } + + // keep checking (crush other things) + return true; +} + + + +// +// P_ChangeSector +// +boolean +P_ChangeSector +( sector_t* sector, + boolean crunch ) +{ + int x; + int y; + + nofit = false; + crushchange = crunch; + + // re-check heights for all things near the moving sector + for (x=sector->blockbox[BOXLEFT] ; x<= sector->blockbox[BOXRIGHT] ; x++) + for (y=sector->blockbox[BOXBOTTOM];y<= sector->blockbox[BOXTOP] ; y++) + P_BlockThingsIterator (x, y, PIT_ChangeSector); + + + return nofit; +} + +// Code to emulate the behavior of Vanilla Doom when encountering an overrun +// of the spechit array. This is by Andrey Budko (e6y) and comes from his +// PrBoom plus port. A big thanks to Andrey for this. + +static void SpechitOverrun(line_t *ld) +{ + static unsigned int baseaddr = 0; + unsigned int addr; + + if (baseaddr == 0) + { + int p; + + // This is the first time we have had an overrun. Work out + // what base address we are going to use. + // Allow a spechit value to be specified on the command line. + + //! + // @category compat + // @arg + // + // Use the specified magic value when emulating spechit overruns. + // + + p = M_CheckParmWithArgs("-spechit", 1); + + if (p > 0) + { + M_StrToInt(myargv[p+1], (int *) &baseaddr); + } + else + { + baseaddr = DEFAULT_SPECHIT_MAGIC; + } + } + + // Calculate address used in doom2.exe + + addr = baseaddr + (ld - lines) * 0x3E; + + switch(numspechit) + { + case 9: + case 10: + case 11: + case 12: + tmbbox[numspechit-9] = addr; + break; + case 13: + crushchange = addr; + break; + case 14: + nofit = addr; + break; + default: + fprintf(stderr, "SpechitOverrun: Warning: unable to emulate" + "an overrun where numspechit=%i\n", + numspechit); + break; + } +} + diff --git a/firmware_p4/components/Applications/doom/p_maputl.c b/firmware_p4/components/Applications/doom/p_maputl.c new file mode 100644 index 000000000..916f2b643 --- /dev/null +++ b/firmware_p4/components/Applications/doom/p_maputl.c @@ -0,0 +1,1001 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2005, 2006 Andrey Budko +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Movement/collision utility functions, +// as used by function in p_map.c. +// BLOCKMAP Iterator functions, +// and some PIT_* functions to use for iteration. +// + + + +#include + + +#include "m_bbox.h" + +#include "doomdef.h" +#include "doomstat.h" +#include "p_local.h" + + +// State. +#include "r_state.h" + +// +// P_AproxDistance +// Gives an estimation of distance (not exact) +// + +fixed_t +P_AproxDistance +( fixed_t dx, + fixed_t dy ) +{ + dx = abs(dx); + dy = abs(dy); + if (dx < dy) + return dx+dy-(dx>>1); + return dx+dy-(dy>>1); +} + + +// +// P_PointOnLineSide +// Returns 0 or 1 +// +int +P_PointOnLineSide +( fixed_t x, + fixed_t y, + line_t* line ) +{ + fixed_t dx; + fixed_t dy; + fixed_t left; + fixed_t right; + + if (!line->dx) + { + if (x <= line->v1->x) + return line->dy > 0; + + return line->dy < 0; + } + if (!line->dy) + { + if (y <= line->v1->y) + return line->dx < 0; + + return line->dx > 0; + } + + dx = (x - line->v1->x); + dy = (y - line->v1->y); + + left = FixedMul ( line->dy>>FRACBITS , dx ); + right = FixedMul ( dy , line->dx>>FRACBITS ); + + if (right < left) + return 0; // front side + return 1; // back side +} + + + +// +// P_BoxOnLineSide +// Considers the line to be infinite +// Returns side 0 or 1, -1 if box crosses the line. +// +int +P_BoxOnLineSide +( fixed_t* tmbox, + line_t* ld ) +{ + int p1 = 0; + int p2 = 0; + + switch (ld->slopetype) + { + case ST_HORIZONTAL: + p1 = tmbox[BOXTOP] > ld->v1->y; + p2 = tmbox[BOXBOTTOM] > ld->v1->y; + if (ld->dx < 0) + { + p1 ^= 1; + p2 ^= 1; + } + break; + + case ST_VERTICAL: + p1 = tmbox[BOXRIGHT] < ld->v1->x; + p2 = tmbox[BOXLEFT] < ld->v1->x; + if (ld->dy < 0) + { + p1 ^= 1; + p2 ^= 1; + } + break; + + case ST_POSITIVE: + p1 = P_PointOnLineSide (tmbox[BOXLEFT], tmbox[BOXTOP], ld); + p2 = P_PointOnLineSide (tmbox[BOXRIGHT], tmbox[BOXBOTTOM], ld); + break; + + case ST_NEGATIVE: + p1 = P_PointOnLineSide (tmbox[BOXRIGHT], tmbox[BOXTOP], ld); + p2 = P_PointOnLineSide (tmbox[BOXLEFT], tmbox[BOXBOTTOM], ld); + break; + } + + if (p1 == p2) + return p1; + return -1; +} + + +// +// P_PointOnDivlineSide +// Returns 0 or 1. +// +int +P_PointOnDivlineSide +( fixed_t x, + fixed_t y, + divline_t* line ) +{ + fixed_t dx; + fixed_t dy; + fixed_t left; + fixed_t right; + + if (!line->dx) + { + if (x <= line->x) + return line->dy > 0; + + return line->dy < 0; + } + if (!line->dy) + { + if (y <= line->y) + return line->dx < 0; + + return line->dx > 0; + } + + dx = (x - line->x); + dy = (y - line->y); + + // try to quickly decide by looking at sign bits + if ( (line->dy ^ line->dx ^ dx ^ dy)&0x80000000 ) + { + if ( (line->dy ^ dx) & 0x80000000 ) + return 1; // (left is negative) + return 0; + } + + left = FixedMul ( line->dy>>8, dx>>8 ); + right = FixedMul ( dy>>8 , line->dx>>8 ); + + if (right < left) + return 0; // front side + return 1; // back side +} + + + +// +// P_MakeDivline +// +void +P_MakeDivline +( line_t* li, + divline_t* dl ) +{ + dl->x = li->v1->x; + dl->y = li->v1->y; + dl->dx = li->dx; + dl->dy = li->dy; +} + + + +// +// P_InterceptVector +// Returns the fractional intercept point +// along the first divline. +// This is only called by the addthings +// and addlines traversers. +// +fixed_t +P_InterceptVector +( divline_t* v2, + divline_t* v1 ) +{ +#if 1 + fixed_t frac; + fixed_t num; + fixed_t den; + + den = FixedMul (v1->dy>>8,v2->dx) - FixedMul(v1->dx>>8,v2->dy); + + if (den == 0) + return 0; + // I_Error ("P_InterceptVector: parallel"); + + num = + FixedMul ( (v1->x - v2->x)>>8 ,v1->dy ) + +FixedMul ( (v2->y - v1->y)>>8, v1->dx ); + + frac = FixedDiv (num , den); + + return frac; +#else // UNUSED, float debug. + float frac; + float num; + float den; + float v1x; + float v1y; + float v1dx; + float v1dy; + float v2x; + float v2y; + float v2dx; + float v2dy; + + v1x = (float)v1->x/FRACUNIT; + v1y = (float)v1->y/FRACUNIT; + v1dx = (float)v1->dx/FRACUNIT; + v1dy = (float)v1->dy/FRACUNIT; + v2x = (float)v2->x/FRACUNIT; + v2y = (float)v2->y/FRACUNIT; + v2dx = (float)v2->dx/FRACUNIT; + v2dy = (float)v2->dy/FRACUNIT; + + den = v1dy*v2dx - v1dx*v2dy; + + if (den == 0) + return 0; // parallel + + num = (v1x - v2x)*v1dy + (v2y - v1y)*v1dx; + frac = num / den; + + return frac*FRACUNIT; +#endif +} + + +// +// P_LineOpening +// Sets opentop and openbottom to the window +// through a two sided line. +// OPTIMIZE: keep this precalculated +// +fixed_t opentop; +fixed_t openbottom; +fixed_t openrange; +fixed_t lowfloor; + + +void P_LineOpening (line_t* linedef) +{ + sector_t* front; + sector_t* back; + + if (linedef->sidenum[1] == -1) + { + // single sided line + openrange = 0; + return; + } + + front = linedef->frontsector; + back = linedef->backsector; + + if (front->ceilingheight < back->ceilingheight) + opentop = front->ceilingheight; + else + opentop = back->ceilingheight; + + if (front->floorheight > back->floorheight) + { + openbottom = front->floorheight; + lowfloor = back->floorheight; + } + else + { + openbottom = back->floorheight; + lowfloor = front->floorheight; + } + + openrange = opentop - openbottom; +} + + +// +// THING POSITION SETTING +// + + +// +// P_UnsetThingPosition +// Unlinks a thing from block map and sectors. +// On each position change, BLOCKMAP and other +// lookups maintaining lists ot things inside +// these structures need to be updated. +// +void P_UnsetThingPosition (mobj_t* thing) +{ + int blockx; + int blocky; + + if ( ! (thing->flags & MF_NOSECTOR) ) + { + // inert things don't need to be in blockmap? + // unlink from subsector + if (thing->snext) + thing->snext->sprev = thing->sprev; + + if (thing->sprev) + thing->sprev->snext = thing->snext; + else + thing->subsector->sector->thinglist = thing->snext; + } + + if ( ! (thing->flags & MF_NOBLOCKMAP) ) + { + // inert things don't need to be in blockmap + // unlink from block map + if (thing->bnext) + thing->bnext->bprev = thing->bprev; + + if (thing->bprev) + thing->bprev->bnext = thing->bnext; + else + { + blockx = (thing->x - bmaporgx)>>MAPBLOCKSHIFT; + blocky = (thing->y - bmaporgy)>>MAPBLOCKSHIFT; + + if (blockx>=0 && blockx < bmapwidth + && blocky>=0 && blocky bnext; + } + } + } +} + + +// +// P_SetThingPosition +// Links a thing into both a block and a subsector +// based on it's x y. +// Sets thing->subsector properly +// +void +P_SetThingPosition (mobj_t* thing) +{ + subsector_t* ss; + sector_t* sec; + int blockx; + int blocky; + mobj_t** link; + + + // link into subsector + ss = R_PointInSubsector (thing->x,thing->y); + thing->subsector = ss; + + if ( ! (thing->flags & MF_NOSECTOR) ) + { + // invisible things don't go into the sector links + sec = ss->sector; + + thing->sprev = NULL; + thing->snext = sec->thinglist; + + if (sec->thinglist) + sec->thinglist->sprev = thing; + + sec->thinglist = thing; + } + + + // link into blockmap + if ( ! (thing->flags & MF_NOBLOCKMAP) ) + { + // inert things don't need to be in blockmap + blockx = (thing->x - bmaporgx)>>MAPBLOCKSHIFT; + blocky = (thing->y - bmaporgy)>>MAPBLOCKSHIFT; + + if (blockx>=0 + && blockx < bmapwidth + && blocky>=0 + && blocky < bmapheight) + { + link = &blocklinks[blocky*bmapwidth+blockx]; + thing->bprev = NULL; + thing->bnext = *link; + if (*link) + (*link)->bprev = thing; + + *link = thing; + } + else + { + // thing is off the map + thing->bnext = thing->bprev = NULL; + } + } +} + + + +// +// BLOCK MAP ITERATORS +// For each line/thing in the given mapblock, +// call the passed PIT_* function. +// If the function returns false, +// exit with false without checking anything else. +// + + +// +// P_BlockLinesIterator +// The validcount flags are used to avoid checking lines +// that are marked in multiple mapblocks, +// so increment validcount before the first call +// to P_BlockLinesIterator, then make one or more calls +// to it. +// +boolean +P_BlockLinesIterator +( int x, + int y, + boolean(*func)(line_t*) ) +{ + int offset; + short* list; + line_t* ld; + + if (x<0 + || y<0 + || x>=bmapwidth + || y>=bmapheight) + { + return true; + } + + offset = y*bmapwidth+x; + + offset = *(blockmap+offset); + + for ( list = blockmaplump+offset ; *list != -1 ; list++) + { + ld = &lines[*list]; + + if (ld->validcount == validcount) + continue; // line has already been checked + + ld->validcount = validcount; + + if ( !func(ld) ) + return false; + } + return true; // everything was checked +} + + +// +// P_BlockThingsIterator +// +boolean +P_BlockThingsIterator +( int x, + int y, + boolean(*func)(mobj_t*) ) +{ + mobj_t* mobj; + + if ( x<0 + || y<0 + || x>=bmapwidth + || y>=bmapheight) + { + return true; + } + + + for (mobj = blocklinks[y*bmapwidth+x] ; + mobj ; + mobj = mobj->bnext) + { + if (!func( mobj ) ) + return false; + } + return true; +} + + + +// +// INTERCEPT ROUTINES +// +intercept_t intercepts[MAXINTERCEPTS]; +intercept_t* intercept_p; + +divline_t trace; +boolean earlyout; +int ptflags; + +static void InterceptsOverrun(int num_intercepts, intercept_t *intercept); + +// +// PIT_AddLineIntercepts. +// Looks for lines in the given block +// that intercept the given trace +// to add to the intercepts list. +// +// A line is crossed if its endpoints +// are on opposite sides of the trace. +// Returns true if earlyout and a solid line hit. +// +boolean +PIT_AddLineIntercepts (line_t* ld) +{ + int s1; + int s2; + fixed_t frac; + divline_t dl; + + // avoid precision problems with two routines + if ( trace.dx > FRACUNIT*16 + || trace.dy > FRACUNIT*16 + || trace.dx < -FRACUNIT*16 + || trace.dy < -FRACUNIT*16) + { + s1 = P_PointOnDivlineSide (ld->v1->x, ld->v1->y, &trace); + s2 = P_PointOnDivlineSide (ld->v2->x, ld->v2->y, &trace); + } + else + { + s1 = P_PointOnLineSide (trace.x, trace.y, ld); + s2 = P_PointOnLineSide (trace.x+trace.dx, trace.y+trace.dy, ld); + } + + if (s1 == s2) + return true; // line isn't crossed + + // hit the line + P_MakeDivline (ld, &dl); + frac = P_InterceptVector (&trace, &dl); + + if (frac < 0) + return true; // behind source + + // try to early out the check + if (earlyout + && frac < FRACUNIT + && !ld->backsector) + { + return false; // stop checking + } + + + intercept_p->frac = frac; + intercept_p->isaline = true; + intercept_p->d.line = ld; + InterceptsOverrun(intercept_p - intercepts, intercept_p); + intercept_p++; + + return true; // continue +} + + + +// +// PIT_AddThingIntercepts +// +boolean PIT_AddThingIntercepts (mobj_t* thing) +{ + fixed_t x1; + fixed_t y1; + fixed_t x2; + fixed_t y2; + + int s1; + int s2; + + boolean tracepositive; + + divline_t dl; + + fixed_t frac; + + tracepositive = (trace.dx ^ trace.dy)>0; + + // check a corner to corner crossection for hit + if (tracepositive) + { + x1 = thing->x - thing->radius; + y1 = thing->y + thing->radius; + + x2 = thing->x + thing->radius; + y2 = thing->y - thing->radius; + } + else + { + x1 = thing->x - thing->radius; + y1 = thing->y - thing->radius; + + x2 = thing->x + thing->radius; + y2 = thing->y + thing->radius; + } + + s1 = P_PointOnDivlineSide (x1, y1, &trace); + s2 = P_PointOnDivlineSide (x2, y2, &trace); + + if (s1 == s2) + return true; // line isn't crossed + + dl.x = x1; + dl.y = y1; + dl.dx = x2-x1; + dl.dy = y2-y1; + + frac = P_InterceptVector (&trace, &dl); + + if (frac < 0) + return true; // behind source + + intercept_p->frac = frac; + intercept_p->isaline = false; + intercept_p->d.thing = thing; + InterceptsOverrun(intercept_p - intercepts, intercept_p); + intercept_p++; + + return true; // keep going +} + + +// +// P_TraverseIntercepts +// Returns true if the traverser function returns true +// for all lines. +// +boolean +P_TraverseIntercepts +( traverser_t func, + fixed_t maxfrac ) +{ + int count; + fixed_t dist; + intercept_t* scan; + intercept_t* in; + + count = intercept_p - intercepts; + + in = 0; // shut up compiler warning + + while (count--) + { + dist = INT_MAX; + for (scan = intercepts ; scanfrac < dist) + { + dist = scan->frac; + in = scan; + } + } + + if (dist > maxfrac) + return true; // checked everything in range + +#if 0 // UNUSED + { + // don't check these yet, there may be others inserted + in = scan = intercepts; + for ( scan = intercepts ; scanfrac > maxfrac) + *in++ = *scan; + intercept_p = in; + return false; + } +#endif + + if ( !func (in) ) + return false; // don't bother going farther + + in->frac = INT_MAX; + } + + return true; // everything was traversed +} + +extern fixed_t bulletslope; + +// Intercepts Overrun emulation, from PrBoom-plus. +// Thanks to Andrey Budko (entryway) for researching this and his +// implementation of Intercepts Overrun emulation in PrBoom-plus +// which this is based on. + +typedef struct +{ + int len; + void *addr; + boolean int16_array; +} intercepts_overrun_t; + +// Intercepts memory table. This is where various variables are located +// in memory in Vanilla Doom. When the intercepts table overflows, we +// need to write to them. +// +// Almost all of the values to overwrite are 32-bit integers, except for +// playerstarts, which is effectively an array of 16-bit integers and +// must be treated differently. + +static intercepts_overrun_t intercepts_overrun[] = +{ + {4, NULL, false}, + {4, NULL, /* &earlyout, */ false}, + {4, NULL, /* &intercept_p, */ false}, + {4, &lowfloor, false}, + {4, &openbottom, false}, + {4, &opentop, false}, + {4, &openrange, false}, + {4, NULL, false}, + {120, NULL, /* &activeplats, */ false}, + {8, NULL, false}, + {4, &bulletslope, false}, + {4, NULL, /* &swingx, */ false}, + {4, NULL, /* &swingy, */ false}, + {4, NULL, false}, + {40, &playerstarts, true}, + {4, NULL, /* &blocklinks, */ false}, + {4, &bmapwidth, false}, + {4, NULL, /* &blockmap, */ false}, + {4, &bmaporgx, false}, + {4, &bmaporgy, false}, + {4, NULL, /* &blockmaplump, */ false}, + {4, &bmapheight, false}, + {0, NULL, false}, +}; + +// Overwrite a specific memory location with a value. + +static void InterceptsMemoryOverrun(int location, int value) +{ + int i, offset; + int index; + void *addr; + + i = 0; + offset = 0; + + // Search down the array until we find the right entry + + while (intercepts_overrun[i].len != 0) + { + if (offset + intercepts_overrun[i].len > location) + { + addr = intercepts_overrun[i].addr; + + // Write the value to the memory location. + // 16-bit and 32-bit values are written differently. + + if (addr != NULL) + { + if (intercepts_overrun[i].int16_array) + { + index = (location - offset) / 2; + ((short *) addr)[index] = value & 0xffff; + ((short *) addr)[index + 1] = (value >> 16) & 0xffff; + } + else + { + index = (location - offset) / 4; + ((int *) addr)[index] = value; + } + } + + break; + } + + offset += intercepts_overrun[i].len; + ++i; + } +} + +// Emulate overruns of the intercepts[] array. + +static void InterceptsOverrun(int num_intercepts, intercept_t *intercept) +{ + int location; + + if (num_intercepts <= MAXINTERCEPTS_ORIGINAL) + { + // No overrun + + return; + } + + location = (num_intercepts - MAXINTERCEPTS_ORIGINAL - 1) * 12; + + // Overwrite memory that is overwritten in Vanilla Doom, using + // the values from the intercept structure. + // + // Note: the ->d.{thing,line} member should really have its + // address translated into the correct address value for + // Vanilla Doom. + + InterceptsMemoryOverrun(location, intercept->frac); + InterceptsMemoryOverrun(location + 4, intercept->isaline); + InterceptsMemoryOverrun(location + 8, (int) intercept->d.thing); +} + + +// +// P_PathTraverse +// Traces a line from x1,y1 to x2,y2, +// calling the traverser function for each. +// Returns true if the traverser function returns true +// for all lines. +// +boolean +P_PathTraverse +( fixed_t x1, + fixed_t y1, + fixed_t x2, + fixed_t y2, + int flags, + boolean (*trav) (intercept_t *)) +{ + fixed_t xt1; + fixed_t yt1; + fixed_t xt2; + fixed_t yt2; + + fixed_t xstep; + fixed_t ystep; + + fixed_t partial; + + fixed_t xintercept; + fixed_t yintercept; + + int mapx; + int mapy; + + int mapxstep; + int mapystep; + + int count; + + earlyout = flags & PT_EARLYOUT; + + validcount++; + intercept_p = intercepts; + + if ( ((x1-bmaporgx)&(MAPBLOCKSIZE-1)) == 0) + x1 += FRACUNIT; // don't side exactly on a line + + if ( ((y1-bmaporgy)&(MAPBLOCKSIZE-1)) == 0) + y1 += FRACUNIT; // don't side exactly on a line + + trace.x = x1; + trace.y = y1; + trace.dx = x2 - x1; + trace.dy = y2 - y1; + + x1 -= bmaporgx; + y1 -= bmaporgy; + xt1 = x1>>MAPBLOCKSHIFT; + yt1 = y1>>MAPBLOCKSHIFT; + + x2 -= bmaporgx; + y2 -= bmaporgy; + xt2 = x2>>MAPBLOCKSHIFT; + yt2 = y2>>MAPBLOCKSHIFT; + + if (xt2 > xt1) + { + mapxstep = 1; + partial = FRACUNIT - ((x1>>MAPBTOFRAC)&(FRACUNIT-1)); + ystep = FixedDiv (y2-y1,abs(x2-x1)); + } + else if (xt2 < xt1) + { + mapxstep = -1; + partial = (x1>>MAPBTOFRAC)&(FRACUNIT-1); + ystep = FixedDiv (y2-y1,abs(x2-x1)); + } + else + { + mapxstep = 0; + partial = FRACUNIT; + ystep = 256*FRACUNIT; + } + + yintercept = (y1>>MAPBTOFRAC) + FixedMul (partial, ystep); + + + if (yt2 > yt1) + { + mapystep = 1; + partial = FRACUNIT - ((y1>>MAPBTOFRAC)&(FRACUNIT-1)); + xstep = FixedDiv (x2-x1,abs(y2-y1)); + } + else if (yt2 < yt1) + { + mapystep = -1; + partial = (y1>>MAPBTOFRAC)&(FRACUNIT-1); + xstep = FixedDiv (x2-x1,abs(y2-y1)); + } + else + { + mapystep = 0; + partial = FRACUNIT; + xstep = 256*FRACUNIT; + } + xintercept = (x1>>MAPBTOFRAC) + FixedMul (partial, xstep); + + // Step through map blocks. + // Count is present to prevent a round off error + // from skipping the break. + mapx = xt1; + mapy = yt1; + + for (count = 0 ; count < 64 ; count++) + { + if (flags & PT_ADDLINES) + { + if (!P_BlockLinesIterator (mapx, mapy,PIT_AddLineIntercepts)) + return false; // early out + } + + if (flags & PT_ADDTHINGS) + { + if (!P_BlockThingsIterator (mapx, mapy,PIT_AddThingIntercepts)) + return false; // early out + } + + if (mapx == xt2 + && mapy == yt2) + { + break; + } + + if ( (yintercept >> FRACBITS) == mapy) + { + yintercept += ystep; + mapx += mapxstep; + } + else if ( (xintercept >> FRACBITS) == mapx) + { + xintercept += xstep; + mapy += mapystep; + } + + } + // go through the sorted list + return P_TraverseIntercepts ( trav, FRACUNIT ); +} + + + diff --git a/firmware_p4/components/Applications/doom/p_mobj.c b/firmware_p4/components/Applications/doom/p_mobj.c new file mode 100644 index 000000000..a3b9c4322 --- /dev/null +++ b/firmware_p4/components/Applications/doom/p_mobj.c @@ -0,0 +1,1049 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Moving object handling. Spawn functions. +// + +#include + +#include "i_system.h" +#include "z_zone.h" +#include "m_random.h" + +#include "doomdef.h" +#include "p_local.h" +#include "sounds.h" + +#include "st_stuff.h" +#include "hu_stuff.h" + +#include "s_sound.h" + +#include "doomstat.h" + + +void G_PlayerReborn (int player); +void P_SpawnMapThing (mapthing_t* mthing); + + +// +// P_SetMobjState +// Returns true if the mobj is still present. +// +int test; + +boolean +P_SetMobjState +( mobj_t* mobj, + statenum_t state ) +{ + state_t* st; + + do + { + if (state == S_NULL) + { + mobj->state = (state_t *) S_NULL; + P_RemoveMobj (mobj); + return false; + } + + st = &states[state]; + mobj->state = st; + mobj->tics = st->tics; + mobj->sprite = st->sprite; + mobj->frame = st->frame; + + // Modified handling. + // Call action functions when the state is set + if (st->action.acp1) + st->action.acp1(mobj); + + state = st->nextstate; + } while (!mobj->tics); + + return true; +} + + +// +// P_ExplodeMissile +// +void P_ExplodeMissile (mobj_t* mo) +{ + mo->momx = mo->momy = mo->momz = 0; + + P_SetMobjState (mo, mobjinfo[mo->type].deathstate); + + mo->tics -= P_Random()&3; + + if (mo->tics < 1) + mo->tics = 1; + + mo->flags &= ~MF_MISSILE; + + if (mo->info->deathsound) + S_StartSound (mo, mo->info->deathsound); +} + + +// +// P_XYMovement +// +#define STOPSPEED 0x1000 +#define FRICTION 0xe800 + +void P_XYMovement (mobj_t* mo) +{ + fixed_t ptryx; + fixed_t ptryy; + player_t* player; + fixed_t xmove; + fixed_t ymove; + + if (!mo->momx && !mo->momy) + { + if (mo->flags & MF_SKULLFLY) + { + // the skull slammed into something + mo->flags &= ~MF_SKULLFLY; + mo->momx = mo->momy = mo->momz = 0; + + P_SetMobjState (mo, mo->info->spawnstate); + } + return; + } + + player = mo->player; + + if (mo->momx > MAXMOVE) + mo->momx = MAXMOVE; + else if (mo->momx < -MAXMOVE) + mo->momx = -MAXMOVE; + + if (mo->momy > MAXMOVE) + mo->momy = MAXMOVE; + else if (mo->momy < -MAXMOVE) + mo->momy = -MAXMOVE; + + xmove = mo->momx; + ymove = mo->momy; + + do + { + if (xmove > MAXMOVE/2 || ymove > MAXMOVE/2) + { + ptryx = mo->x + xmove/2; + ptryy = mo->y + ymove/2; + xmove >>= 1; + ymove >>= 1; + } + else + { + ptryx = mo->x + xmove; + ptryy = mo->y + ymove; + xmove = ymove = 0; + } + + if (!P_TryMove (mo, ptryx, ptryy)) + { + // blocked move + if (mo->player) + { // try to slide along it + P_SlideMove (mo); + } + else if (mo->flags & MF_MISSILE) + { + // explode a missile + if (ceilingline && + ceilingline->backsector && + ceilingline->backsector->ceilingpic == skyflatnum) + { + // Hack to prevent missiles exploding + // against the sky. + // Does not handle sky floors. + P_RemoveMobj (mo); + return; + } + P_ExplodeMissile (mo); + } + else + mo->momx = mo->momy = 0; + } + } while (xmove || ymove); + + // slow down + if (player && player->cheats & CF_NOMOMENTUM) + { + // debug option for no sliding at all + mo->momx = mo->momy = 0; + return; + } + + if (mo->flags & (MF_MISSILE | MF_SKULLFLY) ) + return; // no friction for missiles ever + + if (mo->z > mo->floorz) + return; // no friction when airborne + + if (mo->flags & MF_CORPSE) + { + // do not stop sliding + // if halfway off a step with some momentum + if (mo->momx > FRACUNIT/4 + || mo->momx < -FRACUNIT/4 + || mo->momy > FRACUNIT/4 + || mo->momy < -FRACUNIT/4) + { + if (mo->floorz != mo->subsector->sector->floorheight) + return; + } + } + + if (mo->momx > -STOPSPEED + && mo->momx < STOPSPEED + && mo->momy > -STOPSPEED + && mo->momy < STOPSPEED + && (!player + || (player->cmd.forwardmove== 0 + && player->cmd.sidemove == 0 ) ) ) + { + // if in a walking frame, stop moving + if ( player&&(unsigned)((player->mo->state - states)- S_PLAY_RUN1) < 4) + P_SetMobjState (player->mo, S_PLAY); + + mo->momx = 0; + mo->momy = 0; + } + else + { + mo->momx = FixedMul (mo->momx, FRICTION); + mo->momy = FixedMul (mo->momy, FRICTION); + } +} + +// +// P_ZMovement +// +void P_ZMovement (mobj_t* mo) +{ + fixed_t dist; + fixed_t delta; + + // check for smooth step up + if (mo->player && mo->z < mo->floorz) + { + mo->player->viewheight -= mo->floorz-mo->z; + + mo->player->deltaviewheight + = (VIEWHEIGHT - mo->player->viewheight)>>3; + } + + // adjust height + mo->z += mo->momz; + + if ( mo->flags & MF_FLOAT + && mo->target) + { + // float down towards target if too close + if ( !(mo->flags & MF_SKULLFLY) + && !(mo->flags & MF_INFLOAT) ) + { + dist = P_AproxDistance (mo->x - mo->target->x, + mo->y - mo->target->y); + + delta =(mo->target->z + (mo->height>>1)) - mo->z; + + if (delta<0 && dist < -(delta*3) ) + mo->z -= FLOATSPEED; + else if (delta>0 && dist < (delta*3) ) + mo->z += FLOATSPEED; + } + + } + + // clip movement + if (mo->z <= mo->floorz) + { + // hit the floor + + // Note (id): + // somebody left this after the setting momz to 0, + // kinda useless there. + // + // cph - This was the a bug in the linuxdoom-1.10 source which + // caused it not to sync Doom 2 v1.9 demos. Someone + // added the above comment and moved up the following code. So + // demos would desync in close lost soul fights. + // Note that this only applies to original Doom 1 or Doom2 demos - not + // Final Doom and Ultimate Doom. So we test demo_compatibility *and* + // gamemission. (Note we assume that Doom1 is always Ult Doom, which + // seems to hold for most published demos.) + // + // fraggle - cph got the logic here slightly wrong. There are three + // versions of Doom 1.9: + // + // * The version used in registered doom 1.9 + doom2 - no bounce + // * The version used in ultimate doom - has bounce + // * The version used in final doom - has bounce + // + // So we need to check that this is either retail or commercial + // (but not doom2) + + int correct_lost_soul_bounce = gameversion >= exe_ultimate; + + if (correct_lost_soul_bounce && mo->flags & MF_SKULLFLY) + { + // the skull slammed into something + mo->momz = -mo->momz; + } + + if (mo->momz < 0) + { + if (mo->player + && mo->momz < -GRAVITY*8) + { + // Squat down. + // Decrease viewheight for a moment + // after hitting the ground (hard), + // and utter appropriate sound. + mo->player->deltaviewheight = mo->momz>>3; + S_StartSound (mo, sfx_oof); + } + mo->momz = 0; + } + mo->z = mo->floorz; + + + // cph 2001/05/26 - + // See lost soul bouncing comment above. We need this here for bug + // compatibility with original Doom2 v1.9 - if a soul is charging and + // hit by a raising floor this incorrectly reverses its Y momentum. + // + + if (!correct_lost_soul_bounce && mo->flags & MF_SKULLFLY) + mo->momz = -mo->momz; + + if ( (mo->flags & MF_MISSILE) + && !(mo->flags & MF_NOCLIP) ) + { + P_ExplodeMissile (mo); + return; + } + } + else if (! (mo->flags & MF_NOGRAVITY) ) + { + if (mo->momz == 0) + mo->momz = -GRAVITY*2; + else + mo->momz -= GRAVITY; + } + + if (mo->z + mo->height > mo->ceilingz) + { + // hit the ceiling + if (mo->momz > 0) + mo->momz = 0; + { + mo->z = mo->ceilingz - mo->height; + } + + if (mo->flags & MF_SKULLFLY) + { // the skull slammed into something + mo->momz = -mo->momz; + } + + if ( (mo->flags & MF_MISSILE) + && !(mo->flags & MF_NOCLIP) ) + { + P_ExplodeMissile (mo); + return; + } + } +} + + + +// +// P_NightmareRespawn +// +void +P_NightmareRespawn (mobj_t* mobj) +{ + fixed_t x; + fixed_t y; + fixed_t z; + subsector_t* ss; + mobj_t* mo; + mapthing_t* mthing; + + x = mobj->spawnpoint.x << FRACBITS; + y = mobj->spawnpoint.y << FRACBITS; + + // somthing is occupying it's position? + if (!P_CheckPosition (mobj, x, y) ) + return; // no respwan + + // spawn a teleport fog at old spot + // because of removal of the body? + mo = P_SpawnMobj (mobj->x, + mobj->y, + mobj->subsector->sector->floorheight , MT_TFOG); + // initiate teleport sound + S_StartSound (mo, sfx_telept); + + // spawn a teleport fog at the new spot + ss = R_PointInSubsector (x,y); + + mo = P_SpawnMobj (x, y, ss->sector->floorheight , MT_TFOG); + + S_StartSound (mo, sfx_telept); + + // spawn the new monster + mthing = &mobj->spawnpoint; + + // spawn it + if (mobj->info->flags & MF_SPAWNCEILING) + z = ONCEILINGZ; + else + z = ONFLOORZ; + + // inherit attributes from deceased one + mo = P_SpawnMobj (x,y,z, mobj->type); + mo->spawnpoint = mobj->spawnpoint; + mo->angle = ANG45 * (mthing->angle/45); + + if (mthing->options & MTF_AMBUSH) + mo->flags |= MF_AMBUSH; + + mo->reactiontime = 18; + + // remove the old monster, + P_RemoveMobj (mobj); +} + + +// +// P_MobjThinker +// +void P_MobjThinker (mobj_t* mobj) +{ + // momentum movement + if (mobj->momx + || mobj->momy + || (mobj->flags&MF_SKULLFLY) ) + { + P_XYMovement (mobj); + + // FIXME: decent NOP/NULL/Nil function pointer please. + if (mobj->thinker.function.acv == (actionf_v) (-1)) + return; // mobj was removed + } + if ( (mobj->z != mobj->floorz) + || mobj->momz ) + { + P_ZMovement (mobj); + + // FIXME: decent NOP/NULL/Nil function pointer please. + if (mobj->thinker.function.acv == (actionf_v) (-1)) + return; // mobj was removed + } + + + // cycle through states, + // calling action functions at transitions + if (mobj->tics != -1) + { + mobj->tics--; + + // you can cycle through multiple states in a tic + if (!mobj->tics) + if (!P_SetMobjState (mobj, mobj->state->nextstate) ) + return; // freed itself + } + else + { + // check for nightmare respawn + if (! (mobj->flags & MF_COUNTKILL) ) + return; + + if (!respawnmonsters) + return; + + mobj->movecount++; + + if (mobj->movecount < 12*TICRATE) + return; + + if ( leveltime&31 ) + return; + + if (P_Random () > 4) + return; + + P_NightmareRespawn (mobj); + } + +} + + +// +// P_SpawnMobj +// +mobj_t* +P_SpawnMobj +( fixed_t x, + fixed_t y, + fixed_t z, + mobjtype_t type ) +{ + mobj_t* mobj; + state_t* st; + mobjinfo_t* info; + + mobj = Z_Malloc (sizeof(*mobj), PU_LEVEL, NULL); + memset (mobj, 0, sizeof (*mobj)); + info = &mobjinfo[type]; + + mobj->type = type; + mobj->info = info; + mobj->x = x; + mobj->y = y; + mobj->radius = info->radius; + mobj->height = info->height; + mobj->flags = info->flags; + mobj->health = info->spawnhealth; + + if (gameskill != sk_nightmare) + mobj->reactiontime = info->reactiontime; + + mobj->lastlook = P_Random () % MAXPLAYERS; + // do not set the state with P_SetMobjState, + // because action routines can not be called yet + st = &states[info->spawnstate]; + + mobj->state = st; + mobj->tics = st->tics; + mobj->sprite = st->sprite; + mobj->frame = st->frame; + + // set subsector and/or block links + P_SetThingPosition (mobj); + + mobj->floorz = mobj->subsector->sector->floorheight; + mobj->ceilingz = mobj->subsector->sector->ceilingheight; + + if (z == ONFLOORZ) + mobj->z = mobj->floorz; + else if (z == ONCEILINGZ) + mobj->z = mobj->ceilingz - mobj->info->height; + else + mobj->z = z; + + mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker; + + P_AddThinker (&mobj->thinker); + + return mobj; +} + + +// +// P_RemoveMobj +// +mapthing_t itemrespawnque[ITEMQUESIZE]; +int itemrespawntime[ITEMQUESIZE]; +int iquehead; +int iquetail; + + +void P_RemoveMobj (mobj_t* mobj) +{ + if ((mobj->flags & MF_SPECIAL) + && !(mobj->flags & MF_DROPPED) + && (mobj->type != MT_INV) + && (mobj->type != MT_INS)) + { + itemrespawnque[iquehead] = mobj->spawnpoint; + itemrespawntime[iquehead] = leveltime; + iquehead = (iquehead+1)&(ITEMQUESIZE-1); + + // lose one off the end? + if (iquehead == iquetail) + iquetail = (iquetail+1)&(ITEMQUESIZE-1); + } + + // unlink from sector and block lists + P_UnsetThingPosition (mobj); + + // stop any playing sound + S_StopSound (mobj); + + // free block + P_RemoveThinker ((thinker_t*)mobj); +} + + + + +// +// P_RespawnSpecials +// +void P_RespawnSpecials (void) +{ + fixed_t x; + fixed_t y; + fixed_t z; + + subsector_t* ss; + mobj_t* mo; + mapthing_t* mthing; + + int i; + + // only respawn items in deathmatch + if (deathmatch != 2) + return; // + + // nothing left to respawn? + if (iquehead == iquetail) + return; + + // wait at least 30 seconds + if (leveltime - itemrespawntime[iquetail] < 30*TICRATE) + return; + + mthing = &itemrespawnque[iquetail]; + + x = mthing->x << FRACBITS; + y = mthing->y << FRACBITS; + + // spawn a teleport fog at the new spot + ss = R_PointInSubsector (x,y); + mo = P_SpawnMobj (x, y, ss->sector->floorheight , MT_IFOG); + S_StartSound (mo, sfx_itmbk); + + // find which type to spawn + for (i=0 ; i< NUMMOBJTYPES ; i++) + { + if (mthing->type == mobjinfo[i].doomednum) + break; + } + + // spawn it + if (mobjinfo[i].flags & MF_SPAWNCEILING) + z = ONCEILINGZ; + else + z = ONFLOORZ; + + mo = P_SpawnMobj (x,y,z, i); + mo->spawnpoint = *mthing; + mo->angle = ANG45 * (mthing->angle/45); + + // pull it from the que + iquetail = (iquetail+1)&(ITEMQUESIZE-1); +} + + + + +// +// P_SpawnPlayer +// Called when a player is spawned on the level. +// Most of the player structure stays unchanged +// between levels. +// +void P_SpawnPlayer (mapthing_t* mthing) +{ + player_t* p; + fixed_t x; + fixed_t y; + fixed_t z; + + mobj_t* mobj; + + int i; + + if (mthing->type == 0) + { + return; + } + + // not playing? + if (!playeringame[mthing->type-1]) + return; + + p = &players[mthing->type-1]; + + if (p->playerstate == PST_REBORN) + G_PlayerReborn (mthing->type-1); + + x = mthing->x << FRACBITS; + y = mthing->y << FRACBITS; + z = ONFLOORZ; + mobj = P_SpawnMobj (x,y,z, MT_PLAYER); + + // set color translations for player sprites + if (mthing->type > 1) + mobj->flags |= (mthing->type-1)<angle = ANG45 * (mthing->angle/45); + mobj->player = p; + mobj->health = p->health; + + p->mo = mobj; + p->playerstate = PST_LIVE; + p->refire = 0; + p->message = NULL; + p->damagecount = 0; + p->bonuscount = 0; + p->extralight = 0; + p->fixedcolormap = 0; + p->viewheight = VIEWHEIGHT; + + // setup gun psprite + P_SetupPsprites (p); + + // give all cards in death match mode + if (deathmatch) + for (i=0 ; icards[i] = true; + + if (mthing->type-1 == consoleplayer) + { + // wake up the status bar + ST_Start (); + // wake up the heads up text + HU_Start (); + } +} + + +// +// P_SpawnMapThing +// The fields of the mapthing should +// already be in host byte order. +// +void P_SpawnMapThing (mapthing_t* mthing) +{ + int i; + int bit; + mobj_t* mobj; + fixed_t x; + fixed_t y; + fixed_t z; + + // count deathmatch start positions + if (mthing->type == 11) + { + if (deathmatch_p < &deathmatchstarts[10]) + { + memcpy (deathmatch_p, mthing, sizeof(*mthing)); + deathmatch_p++; + } + return; + } + + if (mthing->type <= 0) + { + // Thing type 0 is actually "player -1 start". + // For some reason, Vanilla Doom accepts/ignores this. + + return; + } + + // check for players specially + if (mthing->type <= 4) + { + // save spots for respawning in network games + playerstarts[mthing->type-1] = *mthing; + if (!deathmatch) + P_SpawnPlayer (mthing); + + return; + } + + // check for apropriate skill level + if (!netgame && (mthing->options & 16) ) + return; + + if (gameskill == sk_baby) + bit = 1; + else if (gameskill == sk_nightmare) + bit = 4; + else + bit = 1<<(gameskill-1); + + if (!(mthing->options & bit) ) + return; + + // find which type to spawn + for (i=0 ; i< NUMMOBJTYPES ; i++) + if (mthing->type == mobjinfo[i].doomednum) + break; + + if (i==NUMMOBJTYPES) + I_Error ("P_SpawnMapThing: Unknown type %i at (%i, %i)", + mthing->type, + mthing->x, mthing->y); + + // don't spawn keycards and players in deathmatch + if (deathmatch && mobjinfo[i].flags & MF_NOTDMATCH) + return; + + // don't spawn any monsters if -nomonsters + if (nomonsters + && ( i == MT_SKULL + || (mobjinfo[i].flags & MF_COUNTKILL)) ) + { + return; + } + + // spawn it + x = mthing->x << FRACBITS; + y = mthing->y << FRACBITS; + + if (mobjinfo[i].flags & MF_SPAWNCEILING) + z = ONCEILINGZ; + else + z = ONFLOORZ; + + mobj = P_SpawnMobj (x,y,z, i); + mobj->spawnpoint = *mthing; + + if (mobj->tics > 0) + mobj->tics = 1 + (P_Random () % mobj->tics); + if (mobj->flags & MF_COUNTKILL) + totalkills++; + if (mobj->flags & MF_COUNTITEM) + totalitems++; + + mobj->angle = ANG45 * (mthing->angle/45); + if (mthing->options & MTF_AMBUSH) + mobj->flags |= MF_AMBUSH; +} + + + +// +// GAME SPAWN FUNCTIONS +// + + +// +// P_SpawnPuff +// +extern fixed_t attackrange; + +void +P_SpawnPuff +( fixed_t x, + fixed_t y, + fixed_t z ) +{ + mobj_t* th; + + z += ((P_Random()-P_Random())<<10); + + th = P_SpawnMobj (x,y,z, MT_PUFF); + th->momz = FRACUNIT; + th->tics -= P_Random()&3; + + if (th->tics < 1) + th->tics = 1; + + // don't make punches spark on the wall + if (attackrange == MELEERANGE) + P_SetMobjState (th, S_PUFF3); +} + + + +// +// P_SpawnBlood +// +void +P_SpawnBlood +( fixed_t x, + fixed_t y, + fixed_t z, + int damage ) +{ + mobj_t* th; + + z += ((P_Random()-P_Random())<<10); + th = P_SpawnMobj (x,y,z, MT_BLOOD); + th->momz = FRACUNIT*2; + th->tics -= P_Random()&3; + + if (th->tics < 1) + th->tics = 1; + + if (damage <= 12 && damage >= 9) + P_SetMobjState (th,S_BLOOD2); + else if (damage < 9) + P_SetMobjState (th,S_BLOOD3); +} + + + +// +// P_CheckMissileSpawn +// Moves the missile forward a bit +// and possibly explodes it right there. +// +void P_CheckMissileSpawn (mobj_t* th) +{ + th->tics -= P_Random()&3; + if (th->tics < 1) + th->tics = 1; + + // move a little forward so an angle can + // be computed if it immediately explodes + th->x += (th->momx>>1); + th->y += (th->momy>>1); + th->z += (th->momz>>1); + + if (!P_TryMove (th, th->x, th->y)) + P_ExplodeMissile (th); +} + +// Certain functions assume that a mobj_t pointer is non-NULL, +// causing a crash in some situations where it is NULL. Vanilla +// Doom did not crash because of the lack of proper memory +// protection. This function substitutes NULL pointers for +// pointers to a dummy mobj, to avoid a crash. + +mobj_t *P_SubstNullMobj(mobj_t *mobj) +{ + if (mobj == NULL) + { + static mobj_t dummy_mobj; + + dummy_mobj.x = 0; + dummy_mobj.y = 0; + dummy_mobj.z = 0; + dummy_mobj.flags = 0; + + mobj = &dummy_mobj; + } + + return mobj; +} + +// +// P_SpawnMissile +// +mobj_t* +P_SpawnMissile +( mobj_t* source, + mobj_t* dest, + mobjtype_t type ) +{ + mobj_t* th; + angle_t an; + int dist; + + th = P_SpawnMobj (source->x, + source->y, + source->z + 4*8*FRACUNIT, type); + + if (th->info->seesound) + S_StartSound (th, th->info->seesound); + + th->target = source; // where it came from + an = R_PointToAngle2 (source->x, source->y, dest->x, dest->y); + + // fuzzy player + if (dest->flags & MF_SHADOW) + an += (P_Random()-P_Random())<<20; + + th->angle = an; + an >>= ANGLETOFINESHIFT; + th->momx = FixedMul (th->info->speed, finecosine[an]); + th->momy = FixedMul (th->info->speed, finesine[an]); + + dist = P_AproxDistance (dest->x - source->x, dest->y - source->y); + dist = dist / th->info->speed; + + if (dist < 1) + dist = 1; + + th->momz = (dest->z - source->z) / dist; + P_CheckMissileSpawn (th); + + return th; +} + + +// +// P_SpawnPlayerMissile +// Tries to aim at a nearby monster +// +void +P_SpawnPlayerMissile +( mobj_t* source, + mobjtype_t type ) +{ + mobj_t* th; + angle_t an; + + fixed_t x; + fixed_t y; + fixed_t z; + fixed_t slope; + + // see which target is to be aimed at + an = source->angle; + slope = P_AimLineAttack (source, an, 16*64*FRACUNIT); + + if (!linetarget) + { + an += 1<<26; + slope = P_AimLineAttack (source, an, 16*64*FRACUNIT); + + if (!linetarget) + { + an -= 2<<26; + slope = P_AimLineAttack (source, an, 16*64*FRACUNIT); + } + + if (!linetarget) + { + an = source->angle; + slope = 0; + } + } + + x = source->x; + y = source->y; + z = source->z + 4*8*FRACUNIT; + + th = P_SpawnMobj (x,y,z, type); + + if (th->info->seesound) + S_StartSound (th, th->info->seesound); + + th->target = source; + th->angle = an; + th->momx = FixedMul( th->info->speed, + finecosine[an>>ANGLETOFINESHIFT]); + th->momy = FixedMul( th->info->speed, + finesine[an>>ANGLETOFINESHIFT]); + th->momz = FixedMul( th->info->speed, slope); + + P_CheckMissileSpawn (th); +} + diff --git a/firmware_p4/components/Applications/doom/p_mobj.h b/firmware_p4/components/Applications/doom/p_mobj.h new file mode 100644 index 000000000..90ed764b9 --- /dev/null +++ b/firmware_p4/components/Applications/doom/p_mobj.h @@ -0,0 +1,284 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Map Objects, MObj, definition and handling. +// + + +#ifndef __P_MOBJ__ +#define __P_MOBJ__ + +// Basics. +#include "tables.h" +#include "m_fixed.h" + +// We need the thinker_t stuff. +#include "d_think.h" + +// We need the WAD data structure for Map things, +// from the THINGS lump. +#include "doomdata.h" + +// States are tied to finite states are +// tied to animation frames. +// Needs precompiled tables/data structures. +#include "info.h" + + + + + + +// +// NOTES: mobj_t +// +// mobj_ts are used to tell the refresh where to draw an image, +// tell the world simulation when objects are contacted, +// and tell the sound driver how to position a sound. +// +// The refresh uses the next and prev links to follow +// lists of things in sectors as they are being drawn. +// The sprite, frame, and angle elements determine which patch_t +// is used to draw the sprite if it is visible. +// The sprite and frame values are allmost allways set +// from state_t structures. +// The statescr.exe utility generates the states.h and states.c +// files that contain the sprite/frame numbers from the +// statescr.txt source file. +// The xyz origin point represents a point at the bottom middle +// of the sprite (between the feet of a biped). +// This is the default origin position for patch_ts grabbed +// with lumpy.exe. +// A walking creature will have its z equal to the floor +// it is standing on. +// +// The sound code uses the x,y, and subsector fields +// to do stereo positioning of any sound effited by the mobj_t. +// +// The play simulation uses the blocklinks, x,y,z, radius, height +// to determine when mobj_ts are touching each other, +// touching lines in the map, or hit by trace lines (gunshots, +// lines of sight, etc). +// The mobj_t->flags element has various bit flags +// used by the simulation. +// +// Every mobj_t is linked into a single sector +// based on its origin coordinates. +// The subsector_t is found with R_PointInSubsector(x,y), +// and the sector_t can be found with subsector->sector. +// The sector links are only used by the rendering code, +// the play simulation does not care about them at all. +// +// Any mobj_t that needs to be acted upon by something else +// in the play world (block movement, be shot, etc) will also +// need to be linked into the blockmap. +// If the thing has the MF_NOBLOCK flag set, it will not use +// the block links. It can still interact with other things, +// but only as the instigator (missiles will run into other +// things, but nothing can run into a missile). +// Each block in the grid is 128*128 units, and knows about +// every line_t that it contains a piece of, and every +// interactable mobj_t that has its origin contained. +// +// A valid mobj_t is a mobj_t that has the proper subsector_t +// filled in for its xy coordinates and is linked into the +// sector from which the subsector was made, or has the +// MF_NOSECTOR flag set (the subsector_t needs to be valid +// even if MF_NOSECTOR is set), and is linked into a blockmap +// block or has the MF_NOBLOCKMAP flag set. +// Links should only be modified by the P_[Un]SetThingPosition() +// functions. +// Do not change the MF_NO? flags while a thing is valid. +// +// Any questions? +// + +// +// Misc. mobj flags +// +typedef enum +{ + // Call P_SpecialThing when touched. + MF_SPECIAL = 1, + // Blocks. + MF_SOLID = 2, + // Can be hit. + MF_SHOOTABLE = 4, + // Don't use the sector links (invisible but touchable). + MF_NOSECTOR = 8, + // Don't use the blocklinks (inert but displayable) + MF_NOBLOCKMAP = 16, + + // Not to be activated by sound, deaf monster. + MF_AMBUSH = 32, + // Will try to attack right back. + MF_JUSTHIT = 64, + // Will take at least one step before attacking. + MF_JUSTATTACKED = 128, + // On level spawning (initial position), + // hang from ceiling instead of stand on floor. + MF_SPAWNCEILING = 256, + // Don't apply gravity (every tic), + // that is, object will float, keeping current height + // or changing it actively. + MF_NOGRAVITY = 512, + + // Movement flags. + // This allows jumps from high places. + MF_DROPOFF = 0x400, + // For players, will pick up items. + MF_PICKUP = 0x800, + // Player cheat. ??? + MF_NOCLIP = 0x1000, + // Player: keep info about sliding along walls. + MF_SLIDE = 0x2000, + // Allow moves to any height, no gravity. + // For active floaters, e.g. cacodemons, pain elementals. + MF_FLOAT = 0x4000, + // Don't cross lines + // ??? or look at heights on teleport. + MF_TELEPORT = 0x8000, + // Don't hit same species, explode on block. + // Player missiles as well as fireballs of various kinds. + MF_MISSILE = 0x10000, + // Dropped by a demon, not level spawned. + // E.g. ammo clips dropped by dying former humans. + MF_DROPPED = 0x20000, + // Use fuzzy draw (shadow demons or spectres), + // temporary player invisibility powerup. + MF_SHADOW = 0x40000, + // Flag: don't bleed when shot (use puff), + // barrels and shootable furniture shall not bleed. + MF_NOBLOOD = 0x80000, + // Don't stop moving halfway off a step, + // that is, have dead bodies slide down all the way. + MF_CORPSE = 0x100000, + // Floating to a height for a move, ??? + // don't auto float to target's height. + MF_INFLOAT = 0x200000, + + // On kill, count this enemy object + // towards intermission kill total. + // Happy gathering. + MF_COUNTKILL = 0x400000, + + // On picking up, count this item object + // towards intermission item total. + MF_COUNTITEM = 0x800000, + + // Special handling: skull in flight. + // Neither a cacodemon nor a missile. + MF_SKULLFLY = 0x1000000, + + // Don't spawn this object + // in death match mode (e.g. key cards). + MF_NOTDMATCH = 0x2000000, + + // Player sprites in multiplayer modes are modified + // using an internal color lookup table for re-indexing. + // If 0x4 0x8 or 0xc, + // use a translation table for player colormaps + MF_TRANSLATION = 0xc000000, + // Hmm ???. + MF_TRANSSHIFT = 26 + +} mobjflag_t; + + +// Map Object definition. +typedef struct mobj_s +{ + // List: thinker links. + thinker_t thinker; + + // Info for drawing: position. + fixed_t x; + fixed_t y; + fixed_t z; + + // More list: links in sector (if needed) + struct mobj_s* snext; + struct mobj_s* sprev; + + //More drawing info: to determine current sprite. + angle_t angle; // orientation + spritenum_t sprite; // used to find patch_t and flip value + int frame; // might be ORed with FF_FULLBRIGHT + + // Interaction info, by BLOCKMAP. + // Links in blocks (if needed). + struct mobj_s* bnext; + struct mobj_s* bprev; + + struct subsector_s* subsector; + + // The closest interval over all contacted Sectors. + fixed_t floorz; + fixed_t ceilingz; + + // For movement checking. + fixed_t radius; + fixed_t height; + + // Momentums, used to update position. + fixed_t momx; + fixed_t momy; + fixed_t momz; + + // If == validcount, already checked. + int validcount; + + mobjtype_t type; + mobjinfo_t* info; // &mobjinfo[mobj->type] + + int tics; // state tic counter + state_t* state; + int flags; + int health; + + // Movement direction, movement generation (zig-zagging). + int movedir; // 0-7 + int movecount; // when 0, select a new dir + + // Thing being chased/attacked (or NULL), + // also the originator for missiles. + struct mobj_s* target; + + // Reaction time: if non 0, don't attack yet. + // Used by player to freeze a bit after teleporting. + int reactiontime; + + // If >0, the target will be chased + // no matter what (even if shot) + int threshold; + + // Additional info record for player avatars only. + // Only valid if type == MT_PLAYER + struct player_s* player; + + // Player number last looked for. + int lastlook; + + // For nightmare respawn. + mapthing_t spawnpoint; + + // Thing being chased/attacked for tracers. + struct mobj_s* tracer; + +} mobj_t; + + + +#endif diff --git a/firmware_p4/components/Applications/doom/p_plats.c b/firmware_p4/components/Applications/doom/p_plats.c new file mode 100644 index 000000000..9e773d55c --- /dev/null +++ b/firmware_p4/components/Applications/doom/p_plats.c @@ -0,0 +1,304 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Plats (i.e. elevator platforms) code, raising/lowering. +// + +#include + +#include "i_system.h" +#include "z_zone.h" +#include "m_random.h" + +#include "doomdef.h" +#include "p_local.h" + +#include "s_sound.h" + +// State. +#include "doomstat.h" +#include "r_state.h" + +// Data. +#include "sounds.h" + + +plat_t* activeplats[MAXPLATS]; + + + +// +// Move a plat up and down +// +void T_PlatRaise(plat_t* plat) +{ + result_e res; + + switch(plat->status) + { + case up: + res = T_MovePlane(plat->sector, + plat->speed, + plat->high, + plat->crush,0,1); + + if (plat->type == raiseAndChange + || plat->type == raiseToNearestAndChange) + { + if (!(leveltime&7)) + S_StartSound(&plat->sector->soundorg, sfx_stnmov); + } + + + if (res == crushed && (!plat->crush)) + { + plat->count = plat->wait; + plat->status = down; + S_StartSound(&plat->sector->soundorg, sfx_pstart); + } + else + { + if (res == pastdest) + { + plat->count = plat->wait; + plat->status = waiting; + S_StartSound(&plat->sector->soundorg, sfx_pstop); + + switch(plat->type) + { + case blazeDWUS: + case downWaitUpStay: + P_RemoveActivePlat(plat); + break; + + case raiseAndChange: + case raiseToNearestAndChange: + P_RemoveActivePlat(plat); + break; + + default: + break; + } + } + } + break; + + case down: + res = T_MovePlane(plat->sector,plat->speed,plat->low,false,0,-1); + + if (res == pastdest) + { + plat->count = plat->wait; + plat->status = waiting; + S_StartSound(&plat->sector->soundorg,sfx_pstop); + } + break; + + case waiting: + if (!--plat->count) + { + if (plat->sector->floorheight == plat->low) + plat->status = up; + else + plat->status = down; + S_StartSound(&plat->sector->soundorg,sfx_pstart); + } + case in_stasis: + break; + } +} + + +// +// Do Platforms +// "amount" is only used for SOME platforms. +// +int +EV_DoPlat +( line_t* line, + plattype_e type, + int amount ) +{ + plat_t* plat; + int secnum; + int rtn; + sector_t* sec; + + secnum = -1; + rtn = 0; + + + // Activate all plats that are in_stasis + switch(type) + { + case perpetualRaise: + P_ActivateInStasis(line->tag); + break; + + default: + break; + } + + while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0) + { + sec = §ors[secnum]; + + if (sec->specialdata) + continue; + + // Find lowest & highest floors around sector + rtn = 1; + plat = Z_Malloc( sizeof(*plat), PU_LEVSPEC, 0); + P_AddThinker(&plat->thinker); + + plat->type = type; + plat->sector = sec; + plat->sector->specialdata = plat; + plat->thinker.function.acp1 = (actionf_p1) T_PlatRaise; + plat->crush = false; + plat->tag = line->tag; + + switch(type) + { + case raiseToNearestAndChange: + plat->speed = PLATSPEED/2; + sec->floorpic = sides[line->sidenum[0]].sector->floorpic; + plat->high = P_FindNextHighestFloor(sec,sec->floorheight); + plat->wait = 0; + plat->status = up; + // NO MORE DAMAGE, IF APPLICABLE + sec->special = 0; + + S_StartSound(&sec->soundorg,sfx_stnmov); + break; + + case raiseAndChange: + plat->speed = PLATSPEED/2; + sec->floorpic = sides[line->sidenum[0]].sector->floorpic; + plat->high = sec->floorheight + amount*FRACUNIT; + plat->wait = 0; + plat->status = up; + + S_StartSound(&sec->soundorg,sfx_stnmov); + break; + + case downWaitUpStay: + plat->speed = PLATSPEED * 4; + plat->low = P_FindLowestFloorSurrounding(sec); + + if (plat->low > sec->floorheight) + plat->low = sec->floorheight; + + plat->high = sec->floorheight; + plat->wait = TICRATE*PLATWAIT; + plat->status = down; + S_StartSound(&sec->soundorg,sfx_pstart); + break; + + case blazeDWUS: + plat->speed = PLATSPEED * 8; + plat->low = P_FindLowestFloorSurrounding(sec); + + if (plat->low > sec->floorheight) + plat->low = sec->floorheight; + + plat->high = sec->floorheight; + plat->wait = TICRATE*PLATWAIT; + plat->status = down; + S_StartSound(&sec->soundorg,sfx_pstart); + break; + + case perpetualRaise: + plat->speed = PLATSPEED; + plat->low = P_FindLowestFloorSurrounding(sec); + + if (plat->low > sec->floorheight) + plat->low = sec->floorheight; + + plat->high = P_FindHighestFloorSurrounding(sec); + + if (plat->high < sec->floorheight) + plat->high = sec->floorheight; + + plat->wait = TICRATE*PLATWAIT; + plat->status = P_Random()&1; + + S_StartSound(&sec->soundorg,sfx_pstart); + break; + } + P_AddActivePlat(plat); + } + return rtn; +} + + + +void P_ActivateInStasis(int tag) +{ + int i; + + for (i = 0;i < MAXPLATS;i++) + if (activeplats[i] + && (activeplats[i])->tag == tag + && (activeplats[i])->status == in_stasis) + { + (activeplats[i])->status = (activeplats[i])->oldstatus; + (activeplats[i])->thinker.function.acp1 + = (actionf_p1) T_PlatRaise; + } +} + +void EV_StopPlat(line_t* line) +{ + int j; + + for (j = 0;j < MAXPLATS;j++) + if (activeplats[j] + && ((activeplats[j])->status != in_stasis) + && ((activeplats[j])->tag == line->tag)) + { + (activeplats[j])->oldstatus = (activeplats[j])->status; + (activeplats[j])->status = in_stasis; + (activeplats[j])->thinker.function.acv = (actionf_v)NULL; + } +} + +void P_AddActivePlat(plat_t* plat) +{ + int i; + + for (i = 0;i < MAXPLATS;i++) + if (activeplats[i] == NULL) + { + activeplats[i] = plat; + return; + } + I_Error ("P_AddActivePlat: no more plats!"); +} + +void P_RemoveActivePlat(plat_t* plat) +{ + int i; + for (i = 0;i < MAXPLATS;i++) + if (plat == activeplats[i]) + { + (activeplats[i])->sector->specialdata = NULL; + P_RemoveThinker(&(activeplats[i])->thinker); + activeplats[i] = NULL; + + return; + } + I_Error ("P_RemoveActivePlat: can't find plat!"); +} diff --git a/firmware_p4/components/Applications/doom/p_pspr.c b/firmware_p4/components/Applications/doom/p_pspr.c new file mode 100644 index 000000000..e4774c702 --- /dev/null +++ b/firmware_p4/components/Applications/doom/p_pspr.c @@ -0,0 +1,888 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Weapon sprite animation, weapon objects. +// Action functions for weapons. +// + + +#include "doomdef.h" +#include "d_event.h" + +#include "deh_misc.h" + +#include "m_random.h" +#include "p_local.h" +#include "s_sound.h" + +// State. +#include "doomstat.h" + +// Data. +#include "sounds.h" + +#include "p_pspr.h" + +#define LOWERSPEED FRACUNIT*6 +#define RAISESPEED FRACUNIT*6 + +#define WEAPONBOTTOM 128*FRACUNIT +#define WEAPONTOP 32*FRACUNIT + + + +// +// P_SetPsprite +// +void +P_SetPsprite +( player_t* player, + int position, + statenum_t stnum ) +{ + pspdef_t* psp; + state_t* state; + + psp = &player->psprites[position]; + + do + { + if (!stnum) + { + // object removed itself + psp->state = NULL; + break; + } + + state = &states[stnum]; + psp->state = state; + psp->tics = state->tics; // could be 0 + + if (state->misc1) + { + // coordinate set + psp->sx = state->misc1 << FRACBITS; + psp->sy = state->misc2 << FRACBITS; + } + + // Call action routine. + // Modified handling. + if (state->action.acp2) + { + state->action.acp2(player, psp); + if (!psp->state) + break; + } + + stnum = psp->state->nextstate; + + } while (!psp->tics); + // an initial state of 0 could cycle through +} + + + +// +// P_CalcSwing +// +fixed_t swingx; +fixed_t swingy; + +void P_CalcSwing (player_t* player) +{ + fixed_t swing; + int angle; + + // OPTIMIZE: tablify this. + // A LUT would allow for different modes, + // and add flexibility. + + swing = player->bob; + + angle = (FINEANGLES/70*leveltime)&FINEMASK; + swingx = FixedMul ( swing, finesine[angle]); + + angle = (FINEANGLES/70*leveltime+FINEANGLES/2)&FINEMASK; + swingy = -FixedMul ( swingx, finesine[angle]); +} + + + +// +// P_BringUpWeapon +// Starts bringing the pending weapon up +// from the bottom of the screen. +// Uses player +// +void P_BringUpWeapon (player_t* player) +{ + statenum_t newstate; + + if (player->pendingweapon == wp_nochange) + player->pendingweapon = player->readyweapon; + + if (player->pendingweapon == wp_chainsaw) + S_StartSound (player->mo, sfx_sawup); + + newstate = weaponinfo[player->pendingweapon].upstate; + + player->pendingweapon = wp_nochange; + player->psprites[ps_weapon].sy = WEAPONBOTTOM; + + P_SetPsprite (player, ps_weapon, newstate); +} + +// +// P_CheckAmmo +// Returns true if there is enough ammo to shoot. +// If not, selects the next weapon to use. +// +boolean P_CheckAmmo (player_t* player) +{ + ammotype_t ammo; + int count; + + ammo = weaponinfo[player->readyweapon].ammo; + + // Minimal amount for one shot varies. + if (player->readyweapon == wp_bfg) + count = deh_bfg_cells_per_shot; + else if (player->readyweapon == wp_supershotgun) + count = 2; // Double barrel. + else + count = 1; // Regular. + + // Some do not need ammunition anyway. + // Return if current ammunition sufficient. + if (ammo == am_noammo || player->ammo[ammo] >= count) + return true; + + // Out of ammo, pick a weapon to change to. + // Preferences are set here. + do + { + if (player->weaponowned[wp_plasma] + && player->ammo[am_cell] + && (gamemode != shareware) ) + { + player->pendingweapon = wp_plasma; + } + else if (player->weaponowned[wp_supershotgun] + && player->ammo[am_shell]>2 + && (gamemode == commercial) ) + { + player->pendingweapon = wp_supershotgun; + } + else if (player->weaponowned[wp_chaingun] + && player->ammo[am_clip]) + { + player->pendingweapon = wp_chaingun; + } + else if (player->weaponowned[wp_shotgun] + && player->ammo[am_shell]) + { + player->pendingweapon = wp_shotgun; + } + else if (player->ammo[am_clip]) + { + player->pendingweapon = wp_pistol; + } + else if (player->weaponowned[wp_chainsaw]) + { + player->pendingweapon = wp_chainsaw; + } + else if (player->weaponowned[wp_missile] + && player->ammo[am_misl]) + { + player->pendingweapon = wp_missile; + } + else if (player->weaponowned[wp_bfg] + && player->ammo[am_cell]>40 + && (gamemode != shareware) ) + { + player->pendingweapon = wp_bfg; + } + else + { + // If everything fails. + player->pendingweapon = wp_fist; + } + + } while (player->pendingweapon == wp_nochange); + + // Now set appropriate weapon overlay. + P_SetPsprite (player, + ps_weapon, + weaponinfo[player->readyweapon].downstate); + + return false; +} + + +// +// P_FireWeapon. +// +void P_FireWeapon (player_t* player) +{ + statenum_t newstate; + + if (!P_CheckAmmo (player)) + return; + + P_SetMobjState (player->mo, S_PLAY_ATK1); + newstate = weaponinfo[player->readyweapon].atkstate; + P_SetPsprite (player, ps_weapon, newstate); + P_NoiseAlert (player->mo, player->mo); +} + + + +// +// P_DropWeapon +// Player died, so put the weapon away. +// +void P_DropWeapon (player_t* player) +{ + P_SetPsprite (player, + ps_weapon, + weaponinfo[player->readyweapon].downstate); +} + + + +// +// A_WeaponReady +// The player can fire the weapon +// or change to another weapon at this time. +// Follows after getting weapon up, +// or after previous attack/fire sequence. +// +void +A_WeaponReady +( player_t* player, + pspdef_t* psp ) +{ + statenum_t newstate; + int angle; + + // get out of attack state + if (player->mo->state == &states[S_PLAY_ATK1] + || player->mo->state == &states[S_PLAY_ATK2] ) + { + P_SetMobjState (player->mo, S_PLAY); + } + + if (player->readyweapon == wp_chainsaw + && psp->state == &states[S_SAW]) + { + S_StartSound (player->mo, sfx_sawidl); + } + + // check for change + // if player is dead, put the weapon away + if (player->pendingweapon != wp_nochange || !player->health) + { + // change weapon + // (pending weapon should allready be validated) + newstate = weaponinfo[player->readyweapon].downstate; + P_SetPsprite (player, ps_weapon, newstate); + return; + } + + // check for fire + // the missile launcher and bfg do not auto fire + if (player->cmd.buttons & BT_ATTACK) + { + if ( !player->attackdown + || (player->readyweapon != wp_missile + && player->readyweapon != wp_bfg) ) + { + player->attackdown = true; + P_FireWeapon (player); + return; + } + } + else + player->attackdown = false; + + // bob the weapon based on movement speed + angle = (128*leveltime)&FINEMASK; + psp->sx = FRACUNIT + FixedMul (player->bob, finecosine[angle]); + angle &= FINEANGLES/2-1; + psp->sy = WEAPONTOP + FixedMul (player->bob, finesine[angle]); +} + + + +// +// A_ReFire +// The player can re-fire the weapon +// without lowering it entirely. +// +void A_ReFire +( player_t* player, + pspdef_t* psp ) +{ + + // check for fire + // (if a weaponchange is pending, let it go through instead) + if ( (player->cmd.buttons & BT_ATTACK) + && player->pendingweapon == wp_nochange + && player->health) + { + player->refire++; + P_FireWeapon (player); + } + else + { + player->refire = 0; + P_CheckAmmo (player); + } +} + + +void +A_CheckReload +( player_t* player, + pspdef_t* psp ) +{ + P_CheckAmmo (player); +#if 0 + if (player->ammo[am_shell]<2) + P_SetPsprite (player, ps_weapon, S_DSNR1); +#endif +} + + + +// +// A_Lower +// Lowers current weapon, +// and changes weapon at bottom. +// +void +A_Lower +( player_t* player, + pspdef_t* psp ) +{ + psp->sy += LOWERSPEED; + + // Is already down. + if (psp->sy < WEAPONBOTTOM ) + return; + + // Player is dead. + if (player->playerstate == PST_DEAD) + { + psp->sy = WEAPONBOTTOM; + + // don't bring weapon back up + return; + } + + // The old weapon has been lowered off the screen, + // so change the weapon and start raising it + if (!player->health) + { + // Player is dead, so keep the weapon off screen. + P_SetPsprite (player, ps_weapon, S_NULL); + return; + } + + player->readyweapon = player->pendingweapon; + + P_BringUpWeapon (player); +} + + +// +// A_Raise +// +void +A_Raise +( player_t* player, + pspdef_t* psp ) +{ + statenum_t newstate; + + psp->sy -= RAISESPEED; + + if (psp->sy > WEAPONTOP ) + return; + + psp->sy = WEAPONTOP; + + // The weapon has been raised all the way, + // so change to the ready state. + newstate = weaponinfo[player->readyweapon].readystate; + + P_SetPsprite (player, ps_weapon, newstate); +} + + + +// +// A_GunFlash +// +void +A_GunFlash +( player_t* player, + pspdef_t* psp ) +{ + P_SetMobjState (player->mo, S_PLAY_ATK2); + P_SetPsprite (player,ps_flash,weaponinfo[player->readyweapon].flashstate); +} + + + +// +// WEAPON ATTACKS +// + + +// +// A_Punch +// +void +A_Punch +( player_t* player, + pspdef_t* psp ) +{ + angle_t angle; + int damage; + int slope; + + damage = (P_Random ()%10+1)<<1; + + if (player->powers[pw_strength]) + damage *= 10; + + angle = player->mo->angle; + angle += (P_Random()-P_Random())<<18; + slope = P_AimLineAttack (player->mo, angle, MELEERANGE); + P_LineAttack (player->mo, angle, MELEERANGE, slope, damage); + + // turn to face target + if (linetarget) + { + S_StartSound (player->mo, sfx_punch); + player->mo->angle = R_PointToAngle2 (player->mo->x, + player->mo->y, + linetarget->x, + linetarget->y); + } +} + + +// +// A_Saw +// +void +A_Saw +( player_t* player, + pspdef_t* psp ) +{ + angle_t angle; + int damage; + int slope; + + damage = 2*(P_Random ()%10+1); + angle = player->mo->angle; + angle += (P_Random()-P_Random())<<18; + + // use meleerange + 1 se the puff doesn't skip the flash + slope = P_AimLineAttack (player->mo, angle, MELEERANGE+1); + P_LineAttack (player->mo, angle, MELEERANGE+1, slope, damage); + + if (!linetarget) + { + S_StartSound (player->mo, sfx_sawful); + return; + } + S_StartSound (player->mo, sfx_sawhit); + + // turn to face target + angle = R_PointToAngle2 (player->mo->x, player->mo->y, + linetarget->x, linetarget->y); + if (angle - player->mo->angle > ANG180) + { + if ((signed int) (angle - player->mo->angle) < -ANG90/20) + player->mo->angle = angle + ANG90/21; + else + player->mo->angle -= ANG90/20; + } + else + { + if (angle - player->mo->angle > ANG90/20) + player->mo->angle = angle - ANG90/21; + else + player->mo->angle += ANG90/20; + } + player->mo->flags |= MF_JUSTATTACKED; +} + +// Doom does not check the bounds of the ammo array. As a result, +// it is possible to use an ammo type > 4 that overflows into the +// maxammo array and affects that instead. Through dehacked, for +// example, it is possible to make a weapon that decreases the max +// number of ammo for another weapon. Emulate this. + +static void DecreaseAmmo(player_t *player, int ammonum, int amount) +{ + if (ammonum < NUMAMMO) + { + player->ammo[ammonum] -= amount; + } + else + { + player->maxammo[ammonum - NUMAMMO] -= amount; + } +} + + +// +// A_FireMissile +// +void +A_FireMissile +( player_t* player, + pspdef_t* psp ) +{ + DecreaseAmmo(player, weaponinfo[player->readyweapon].ammo, 1); + P_SpawnPlayerMissile (player->mo, MT_ROCKET); +} + + +// +// A_FireBFG +// +void +A_FireBFG +( player_t* player, + pspdef_t* psp ) +{ + DecreaseAmmo(player, weaponinfo[player->readyweapon].ammo, + deh_bfg_cells_per_shot); + P_SpawnPlayerMissile (player->mo, MT_BFG); +} + + + +// +// A_FirePlasma +// +void +A_FirePlasma +( player_t* player, + pspdef_t* psp ) +{ + DecreaseAmmo(player, weaponinfo[player->readyweapon].ammo, 1); + + P_SetPsprite (player, + ps_flash, + weaponinfo[player->readyweapon].flashstate+(P_Random ()&1) ); + + P_SpawnPlayerMissile (player->mo, MT_PLASMA); +} + + + +// +// P_BulletSlope +// Sets a slope so a near miss is at aproximately +// the height of the intended target +// +fixed_t bulletslope; + + +void P_BulletSlope (mobj_t* mo) +{ + angle_t an; + + // see which target is to be aimed at + an = mo->angle; + bulletslope = P_AimLineAttack (mo, an, 16*64*FRACUNIT); + + if (!linetarget) + { + an += 1<<26; + bulletslope = P_AimLineAttack (mo, an, 16*64*FRACUNIT); + if (!linetarget) + { + an -= 2<<26; + bulletslope = P_AimLineAttack (mo, an, 16*64*FRACUNIT); + } + } +} + + +// +// P_GunShot +// +void +P_GunShot +( mobj_t* mo, + boolean accurate ) +{ + angle_t angle; + int damage; + + damage = 5*(P_Random ()%3+1); + angle = mo->angle; + + if (!accurate) + angle += (P_Random()-P_Random())<<18; + + P_LineAttack (mo, angle, MISSILERANGE, bulletslope, damage); +} + + +// +// A_FirePistol +// +void +A_FirePistol +( player_t* player, + pspdef_t* psp ) +{ + S_StartSound (player->mo, sfx_pistol); + + P_SetMobjState (player->mo, S_PLAY_ATK2); + DecreaseAmmo(player, weaponinfo[player->readyweapon].ammo, 1); + + P_SetPsprite (player, + ps_flash, + weaponinfo[player->readyweapon].flashstate); + + P_BulletSlope (player->mo); + P_GunShot (player->mo, !player->refire); +} + + +// +// A_FireShotgun +// +void +A_FireShotgun +( player_t* player, + pspdef_t* psp ) +{ + int i; + + S_StartSound (player->mo, sfx_shotgn); + P_SetMobjState (player->mo, S_PLAY_ATK2); + + DecreaseAmmo(player, weaponinfo[player->readyweapon].ammo, 1); + + P_SetPsprite (player, + ps_flash, + weaponinfo[player->readyweapon].flashstate); + + P_BulletSlope (player->mo); + + for (i=0 ; i<7 ; i++) + P_GunShot (player->mo, false); +} + + + +// +// A_FireShotgun2 +// +void +A_FireShotgun2 +( player_t* player, + pspdef_t* psp ) +{ + int i; + angle_t angle; + int damage; + + + S_StartSound (player->mo, sfx_dshtgn); + P_SetMobjState (player->mo, S_PLAY_ATK2); + + DecreaseAmmo(player, weaponinfo[player->readyweapon].ammo, 2); + + P_SetPsprite (player, + ps_flash, + weaponinfo[player->readyweapon].flashstate); + + P_BulletSlope (player->mo); + + for (i=0 ; i<20 ; i++) + { + damage = 5*(P_Random ()%3+1); + angle = player->mo->angle; + angle += (P_Random()-P_Random())<<19; + P_LineAttack (player->mo, + angle, + MISSILERANGE, + bulletslope + ((P_Random()-P_Random())<<5), damage); + } +} + + +// +// A_FireCGun +// +void +A_FireCGun +( player_t* player, + pspdef_t* psp ) +{ + S_StartSound (player->mo, sfx_pistol); + + if (!player->ammo[weaponinfo[player->readyweapon].ammo]) + return; + + P_SetMobjState (player->mo, S_PLAY_ATK2); + DecreaseAmmo(player, weaponinfo[player->readyweapon].ammo, 1); + + P_SetPsprite (player, + ps_flash, + weaponinfo[player->readyweapon].flashstate + + psp->state + - &states[S_CHAIN1] ); + + P_BulletSlope (player->mo); + + P_GunShot (player->mo, !player->refire); +} + + + +// +// ? +// +void A_Light0 (player_t *player, pspdef_t *psp) +{ + player->extralight = 0; +} + +void A_Light1 (player_t *player, pspdef_t *psp) +{ + player->extralight = 1; +} + +void A_Light2 (player_t *player, pspdef_t *psp) +{ + player->extralight = 2; +} + + +// +// A_BFGSpray +// Spawn a BFG explosion on every monster in view +// +void A_BFGSpray (mobj_t* mo) +{ + int i; + int j; + int damage; + angle_t an; + + // offset angles from its attack angle + for (i=0 ; i<40 ; i++) + { + an = mo->angle - ANG90/2 + ANG90/40*i; + + // mo->target is the originator (player) + // of the missile + P_AimLineAttack (mo->target, an, 16*64*FRACUNIT); + + if (!linetarget) + continue; + + P_SpawnMobj (linetarget->x, + linetarget->y, + linetarget->z + (linetarget->height>>2), + MT_EXTRABFG); + + damage = 0; + for (j=0;j<15;j++) + damage += (P_Random()&7) + 1; + + P_DamageMobj (linetarget, mo->target,mo->target, damage); + } +} + + +// +// A_BFGsound +// +void +A_BFGsound +( player_t* player, + pspdef_t* psp ) +{ + S_StartSound (player->mo, sfx_bfg); +} + + + +// +// P_SetupPsprites +// Called at start of level for each player. +// +void P_SetupPsprites (player_t* player) +{ + int i; + + // remove all psprites + for (i=0 ; ipsprites[i].state = NULL; + + // spawn the gun + player->pendingweapon = player->readyweapon; + P_BringUpWeapon (player); +} + + + + +// +// P_MovePsprites +// Called every tic by player thinking routine. +// +void P_MovePsprites (player_t* player) +{ + int i; + pspdef_t* psp; + state_t* state; + + psp = &player->psprites[0]; + for (i=0 ; istate) ) + { + // drop tic count and possibly change state + + // a -1 tic count never changes + if (psp->tics != -1) + { + psp->tics--; + if (!psp->tics) + P_SetPsprite (player, i, psp->state->nextstate); + } + } + } + + player->psprites[ps_flash].sx = player->psprites[ps_weapon].sx; + player->psprites[ps_flash].sy = player->psprites[ps_weapon].sy; +} + + diff --git a/firmware_p4/components/Applications/doom/p_pspr.h b/firmware_p4/components/Applications/doom/p_pspr.h new file mode 100644 index 000000000..f98fe3533 --- /dev/null +++ b/firmware_p4/components/Applications/doom/p_pspr.h @@ -0,0 +1,71 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Sprite animation. +// + + +#ifndef __P_PSPR__ +#define __P_PSPR__ + +// Basic data types. +// Needs fixed point, and BAM angles. +#include "m_fixed.h" +#include "tables.h" + + +// +// Needs to include the precompiled +// sprite animation tables. +// Header generated by multigen utility. +// This includes all the data for thing animation, +// i.e. the Thing Atrributes table +// and the Frame Sequence table. +#include "info.h" + + + +// +// Frame flags: +// handles maximum brightness (torches, muzzle flare, light sources) +// +#define FF_FULLBRIGHT 0x8000 // flag in thing->frame +#define FF_FRAMEMASK 0x7fff + + + +// +// Overlay psprites are scaled shapes +// drawn directly on the view screen, +// coordinates are given for a 320*200 view screen. +// +typedef enum +{ + ps_weapon, + ps_flash, + NUMPSPRITES + +} psprnum_t; + +typedef struct +{ + state_t* state; // a NULL state means not active + int tics; + fixed_t sx; + fixed_t sy; + +} pspdef_t; + +#endif diff --git a/firmware_p4/components/Applications/doom/p_saveg.c b/firmware_p4/components/Applications/doom/p_saveg.c new file mode 100644 index 000000000..5cb81967a --- /dev/null +++ b/firmware_p4/components/Applications/doom/p_saveg.c @@ -0,0 +1,1891 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Archiving: SaveGame I/O. +// + + +#include +#include + +#include "dstrings.h" +#include "deh_main.h" +#include "i_system.h" +#include "z_zone.h" +#include "p_local.h" +#include "p_saveg.h" + +// State. +#include "doomstat.h" +#include "g_game.h" +#include "m_misc.h" +#include "r_state.h" + +#define SAVEGAME_EOF 0x1d +#define VERSIONSIZE 16 + +FILE *save_stream; +int savegamelength; +boolean savegame_error; + +// Get the filename of a temporary file to write the savegame to. After +// the file has been successfully saved, it will be renamed to the +// real file. + +char *P_TempSaveGameFile(void) +{ + static char *filename = NULL; + + if (filename == NULL) + { + filename = M_StringJoin(savegamedir, "temp.dsg", NULL); + } + + return filename; +} + +// Get the filename of the save game file to use for the specified slot. + +char *P_SaveGameFile(int slot) +{ + static char *filename = NULL; + static size_t filename_size = 0; + char basename[32]; + + if (filename == NULL) + { + filename_size = strlen(savegamedir) + 32; + filename = malloc(filename_size); + } + + DEH_snprintf(basename, 32, SAVEGAMENAME "%d.dsg", slot); + M_snprintf(filename, filename_size, "%s%s", savegamedir, basename); + + return filename; +} + +// Endian-safe integer read/write functions + +static byte saveg_read8(void) +{ + byte result; + + if (fread(&result, 1, 1, save_stream) < 1) + { + if (!savegame_error) + { + fprintf(stderr, "saveg_read8: Unexpected end of file while " + "reading save game\n"); + + savegame_error = true; + } + } + + return result; +} + +static void saveg_write8(byte value) +{ + if (fwrite(&value, 1, 1, save_stream) < 1) + { + if (!savegame_error) + { + fprintf(stderr, "saveg_write8: Error while writing save game\n"); + + savegame_error = true; + } + } +} + +static short saveg_read16(void) +{ + int result; + + result = saveg_read8(); + result |= saveg_read8() << 8; + + return result; +} + +static void saveg_write16(short value) +{ + saveg_write8(value & 0xff); + saveg_write8((value >> 8) & 0xff); +} + +static int saveg_read32(void) +{ + int result; + + result = saveg_read8(); + result |= saveg_read8() << 8; + result |= saveg_read8() << 16; + result |= saveg_read8() << 24; + + return result; +} + +static void saveg_write32(int value) +{ + saveg_write8(value & 0xff); + saveg_write8((value >> 8) & 0xff); + saveg_write8((value >> 16) & 0xff); + saveg_write8((value >> 24) & 0xff); +} + +// Pad to 4-byte boundaries + +static void saveg_read_pad(void) +{ + unsigned long pos; + int padding; + int i; + + pos = ftell(save_stream); + + padding = (4 - (pos & 3)) & 3; + + for (i=0; ix = saveg_read16(); + + // short y; + str->y = saveg_read16(); + + // short angle; + str->angle = saveg_read16(); + + // short type; + str->type = saveg_read16(); + + // short options; + str->options = saveg_read16(); +} + +static void saveg_write_mapthing_t(mapthing_t *str) +{ + // short x; + saveg_write16(str->x); + + // short y; + saveg_write16(str->y); + + // short angle; + saveg_write16(str->angle); + + // short type; + saveg_write16(str->type); + + // short options; + saveg_write16(str->options); +} + +// +// actionf_t +// + +static void saveg_read_actionf_t(actionf_t *str) +{ + // actionf_p1 acp1; + str->acp1 = saveg_readp(); +} + +static void saveg_write_actionf_t(actionf_t *str) +{ + // actionf_p1 acp1; + saveg_writep(str->acp1); +} + +// +// think_t +// +// This is just an actionf_t. +// + +#define saveg_read_think_t saveg_read_actionf_t +#define saveg_write_think_t saveg_write_actionf_t + +// +// thinker_t +// + +static void saveg_read_thinker_t(thinker_t *str) +{ + // struct thinker_s* prev; + str->prev = saveg_readp(); + + // struct thinker_s* next; + str->next = saveg_readp(); + + // think_t function; + saveg_read_think_t(&str->function); +} + +static void saveg_write_thinker_t(thinker_t *str) +{ + // struct thinker_s* prev; + saveg_writep(str->prev); + + // struct thinker_s* next; + saveg_writep(str->next); + + // think_t function; + saveg_write_think_t(&str->function); +} + +// +// mobj_t +// + +static void saveg_read_mobj_t(mobj_t *str) +{ + int pl; + + // thinker_t thinker; + saveg_read_thinker_t(&str->thinker); + + // fixed_t x; + str->x = saveg_read32(); + + // fixed_t y; + str->y = saveg_read32(); + + // fixed_t z; + str->z = saveg_read32(); + + // struct mobj_s* snext; + str->snext = saveg_readp(); + + // struct mobj_s* sprev; + str->sprev = saveg_readp(); + + // angle_t angle; + str->angle = saveg_read32(); + + // spritenum_t sprite; + str->sprite = saveg_read_enum(); + + // int frame; + str->frame = saveg_read32(); + + // struct mobj_s* bnext; + str->bnext = saveg_readp(); + + // struct mobj_s* bprev; + str->bprev = saveg_readp(); + + // struct subsector_s* subsector; + str->subsector = saveg_readp(); + + // fixed_t floorz; + str->floorz = saveg_read32(); + + // fixed_t ceilingz; + str->ceilingz = saveg_read32(); + + // fixed_t radius; + str->radius = saveg_read32(); + + // fixed_t height; + str->height = saveg_read32(); + + // fixed_t momx; + str->momx = saveg_read32(); + + // fixed_t momy; + str->momy = saveg_read32(); + + // fixed_t momz; + str->momz = saveg_read32(); + + // int validcount; + str->validcount = saveg_read32(); + + // mobjtype_t type; + str->type = saveg_read_enum(); + + // mobjinfo_t* info; + str->info = saveg_readp(); + + // int tics; + str->tics = saveg_read32(); + + // state_t* state; + str->state = &states[saveg_read32()]; + + // int flags; + str->flags = saveg_read32(); + + // int health; + str->health = saveg_read32(); + + // int movedir; + str->movedir = saveg_read32(); + + // int movecount; + str->movecount = saveg_read32(); + + // struct mobj_s* target; + str->target = saveg_readp(); + + // int reactiontime; + str->reactiontime = saveg_read32(); + + // int threshold; + str->threshold = saveg_read32(); + + // struct player_s* player; + pl = saveg_read32(); + + if (pl > 0) + { + str->player = &players[pl - 1]; + str->player->mo = str; + } + else + { + str->player = NULL; + } + + // int lastlook; + str->lastlook = saveg_read32(); + + // mapthing_t spawnpoint; + saveg_read_mapthing_t(&str->spawnpoint); + + // struct mobj_s* tracer; + str->tracer = saveg_readp(); +} + +static void saveg_write_mobj_t(mobj_t *str) +{ + // thinker_t thinker; + saveg_write_thinker_t(&str->thinker); + + // fixed_t x; + saveg_write32(str->x); + + // fixed_t y; + saveg_write32(str->y); + + // fixed_t z; + saveg_write32(str->z); + + // struct mobj_s* snext; + saveg_writep(str->snext); + + // struct mobj_s* sprev; + saveg_writep(str->sprev); + + // angle_t angle; + saveg_write32(str->angle); + + // spritenum_t sprite; + saveg_write_enum(str->sprite); + + // int frame; + saveg_write32(str->frame); + + // struct mobj_s* bnext; + saveg_writep(str->bnext); + + // struct mobj_s* bprev; + saveg_writep(str->bprev); + + // struct subsector_s* subsector; + saveg_writep(str->subsector); + + // fixed_t floorz; + saveg_write32(str->floorz); + + // fixed_t ceilingz; + saveg_write32(str->ceilingz); + + // fixed_t radius; + saveg_write32(str->radius); + + // fixed_t height; + saveg_write32(str->height); + + // fixed_t momx; + saveg_write32(str->momx); + + // fixed_t momy; + saveg_write32(str->momy); + + // fixed_t momz; + saveg_write32(str->momz); + + // int validcount; + saveg_write32(str->validcount); + + // mobjtype_t type; + saveg_write_enum(str->type); + + // mobjinfo_t* info; + saveg_writep(str->info); + + // int tics; + saveg_write32(str->tics); + + // state_t* state; + saveg_write32(str->state - states); + + // int flags; + saveg_write32(str->flags); + + // int health; + saveg_write32(str->health); + + // int movedir; + saveg_write32(str->movedir); + + // int movecount; + saveg_write32(str->movecount); + + // struct mobj_s* target; + saveg_writep(str->target); + + // int reactiontime; + saveg_write32(str->reactiontime); + + // int threshold; + saveg_write32(str->threshold); + + // struct player_s* player; + if (str->player) + { + saveg_write32(str->player - players + 1); + } + else + { + saveg_write32(0); + } + + // int lastlook; + saveg_write32(str->lastlook); + + // mapthing_t spawnpoint; + saveg_write_mapthing_t(&str->spawnpoint); + + // struct mobj_s* tracer; + saveg_writep(str->tracer); +} + + +// +// ticcmd_t +// + +static void saveg_read_ticcmd_t(ticcmd_t *str) +{ + + // signed char forwardmove; + str->forwardmove = saveg_read8(); + + // signed char sidemove; + str->sidemove = saveg_read8(); + + // short angleturn; + str->angleturn = saveg_read16(); + + // short consistancy; + str->consistancy = saveg_read16(); + + // byte chatchar; + str->chatchar = saveg_read8(); + + // byte buttons; + str->buttons = saveg_read8(); +} + +static void saveg_write_ticcmd_t(ticcmd_t *str) +{ + + // signed char forwardmove; + saveg_write8(str->forwardmove); + + // signed char sidemove; + saveg_write8(str->sidemove); + + // short angleturn; + saveg_write16(str->angleturn); + + // short consistancy; + saveg_write16(str->consistancy); + + // byte chatchar; + saveg_write8(str->chatchar); + + // byte buttons; + saveg_write8(str->buttons); +} + +// +// pspdef_t +// + +static void saveg_read_pspdef_t(pspdef_t *str) +{ + int state; + + // state_t* state; + state = saveg_read32(); + + if (state > 0) + { + str->state = &states[state]; + } + else + { + str->state = NULL; + } + + // int tics; + str->tics = saveg_read32(); + + // fixed_t sx; + str->sx = saveg_read32(); + + // fixed_t sy; + str->sy = saveg_read32(); +} + +static void saveg_write_pspdef_t(pspdef_t *str) +{ + // state_t* state; + if (str->state) + { + saveg_write32(str->state - states); + } + else + { + saveg_write32(0); + } + + // int tics; + saveg_write32(str->tics); + + // fixed_t sx; + saveg_write32(str->sx); + + // fixed_t sy; + saveg_write32(str->sy); +} + +// +// player_t +// + +static void saveg_read_player_t(player_t *str) +{ + int i; + + // mobj_t* mo; + str->mo = saveg_readp(); + + // playerstate_t playerstate; + str->playerstate = saveg_read_enum(); + + // ticcmd_t cmd; + saveg_read_ticcmd_t(&str->cmd); + + // fixed_t viewz; + str->viewz = saveg_read32(); + + // fixed_t viewheight; + str->viewheight = saveg_read32(); + + // fixed_t deltaviewheight; + str->deltaviewheight = saveg_read32(); + + // fixed_t bob; + str->bob = saveg_read32(); + + // int health; + str->health = saveg_read32(); + + // int armorpoints; + str->armorpoints = saveg_read32(); + + // int armortype; + str->armortype = saveg_read32(); + + // int powers[NUMPOWERS]; + for (i=0; ipowers[i] = saveg_read32(); + } + + // boolean cards[NUMCARDS]; + for (i=0; icards[i] = saveg_read32(); + } + + // boolean backpack; + str->backpack = saveg_read32(); + + // int frags[MAXPLAYERS]; + for (i=0; ifrags[i] = saveg_read32(); + } + + // weapontype_t readyweapon; + str->readyweapon = saveg_read_enum(); + + // weapontype_t pendingweapon; + str->pendingweapon = saveg_read_enum(); + + // boolean weaponowned[NUMWEAPONS]; + for (i=0; iweaponowned[i] = saveg_read32(); + } + + // int ammo[NUMAMMO]; + for (i=0; iammo[i] = saveg_read32(); + } + + // int maxammo[NUMAMMO]; + for (i=0; imaxammo[i] = saveg_read32(); + } + + // int attackdown; + str->attackdown = saveg_read32(); + + // int usedown; + str->usedown = saveg_read32(); + + // int cheats; + str->cheats = saveg_read32(); + + // int refire; + str->refire = saveg_read32(); + + // int killcount; + str->killcount = saveg_read32(); + + // int itemcount; + str->itemcount = saveg_read32(); + + // int secretcount; + str->secretcount = saveg_read32(); + + // char* message; + str->message = saveg_readp(); + + // int damagecount; + str->damagecount = saveg_read32(); + + // int bonuscount; + str->bonuscount = saveg_read32(); + + // mobj_t* attacker; + str->attacker = saveg_readp(); + + // int extralight; + str->extralight = saveg_read32(); + + // int fixedcolormap; + str->fixedcolormap = saveg_read32(); + + // int colormap; + str->colormap = saveg_read32(); + + // pspdef_t psprites[NUMPSPRITES]; + for (i=0; ipsprites[i]); + } + + // boolean didsecret; + str->didsecret = saveg_read32(); +} + +static void saveg_write_player_t(player_t *str) +{ + int i; + + // mobj_t* mo; + saveg_writep(str->mo); + + // playerstate_t playerstate; + saveg_write_enum(str->playerstate); + + // ticcmd_t cmd; + saveg_write_ticcmd_t(&str->cmd); + + // fixed_t viewz; + saveg_write32(str->viewz); + + // fixed_t viewheight; + saveg_write32(str->viewheight); + + // fixed_t deltaviewheight; + saveg_write32(str->deltaviewheight); + + // fixed_t bob; + saveg_write32(str->bob); + + // int health; + saveg_write32(str->health); + + // int armorpoints; + saveg_write32(str->armorpoints); + + // int armortype; + saveg_write32(str->armortype); + + // int powers[NUMPOWERS]; + for (i=0; ipowers[i]); + } + + // boolean cards[NUMCARDS]; + for (i=0; icards[i]); + } + + // boolean backpack; + saveg_write32(str->backpack); + + // int frags[MAXPLAYERS]; + for (i=0; ifrags[i]); + } + + // weapontype_t readyweapon; + saveg_write_enum(str->readyweapon); + + // weapontype_t pendingweapon; + saveg_write_enum(str->pendingweapon); + + // boolean weaponowned[NUMWEAPONS]; + for (i=0; iweaponowned[i]); + } + + // int ammo[NUMAMMO]; + for (i=0; iammo[i]); + } + + // int maxammo[NUMAMMO]; + for (i=0; imaxammo[i]); + } + + // int attackdown; + saveg_write32(str->attackdown); + + // int usedown; + saveg_write32(str->usedown); + + // int cheats; + saveg_write32(str->cheats); + + // int refire; + saveg_write32(str->refire); + + // int killcount; + saveg_write32(str->killcount); + + // int itemcount; + saveg_write32(str->itemcount); + + // int secretcount; + saveg_write32(str->secretcount); + + // char* message; + saveg_writep(str->message); + + // int damagecount; + saveg_write32(str->damagecount); + + // int bonuscount; + saveg_write32(str->bonuscount); + + // mobj_t* attacker; + saveg_writep(str->attacker); + + // int extralight; + saveg_write32(str->extralight); + + // int fixedcolormap; + saveg_write32(str->fixedcolormap); + + // int colormap; + saveg_write32(str->colormap); + + // pspdef_t psprites[NUMPSPRITES]; + for (i=0; ipsprites[i]); + } + + // boolean didsecret; + saveg_write32(str->didsecret); +} + + +// +// ceiling_t +// + +static void saveg_read_ceiling_t(ceiling_t *str) +{ + int sector; + + // thinker_t thinker; + saveg_read_thinker_t(&str->thinker); + + // ceiling_e type; + str->type = saveg_read_enum(); + + // sector_t* sector; + sector = saveg_read32(); + str->sector = §ors[sector]; + + // fixed_t bottomheight; + str->bottomheight = saveg_read32(); + + // fixed_t topheight; + str->topheight = saveg_read32(); + + // fixed_t speed; + str->speed = saveg_read32(); + + // boolean crush; + str->crush = saveg_read32(); + + // int direction; + str->direction = saveg_read32(); + + // int tag; + str->tag = saveg_read32(); + + // int olddirection; + str->olddirection = saveg_read32(); +} + +static void saveg_write_ceiling_t(ceiling_t *str) +{ + // thinker_t thinker; + saveg_write_thinker_t(&str->thinker); + + // ceiling_e type; + saveg_write_enum(str->type); + + // sector_t* sector; + saveg_write32(str->sector - sectors); + + // fixed_t bottomheight; + saveg_write32(str->bottomheight); + + // fixed_t topheight; + saveg_write32(str->topheight); + + // fixed_t speed; + saveg_write32(str->speed); + + // boolean crush; + saveg_write32(str->crush); + + // int direction; + saveg_write32(str->direction); + + // int tag; + saveg_write32(str->tag); + + // int olddirection; + saveg_write32(str->olddirection); +} + +// +// vldoor_t +// + +static void saveg_read_vldoor_t(vldoor_t *str) +{ + int sector; + + // thinker_t thinker; + saveg_read_thinker_t(&str->thinker); + + // vldoor_e type; + str->type = saveg_read_enum(); + + // sector_t* sector; + sector = saveg_read32(); + str->sector = §ors[sector]; + + // fixed_t topheight; + str->topheight = saveg_read32(); + + // fixed_t speed; + str->speed = saveg_read32(); + + // int direction; + str->direction = saveg_read32(); + + // int topwait; + str->topwait = saveg_read32(); + + // int topcountdown; + str->topcountdown = saveg_read32(); +} + +static void saveg_write_vldoor_t(vldoor_t *str) +{ + // thinker_t thinker; + saveg_write_thinker_t(&str->thinker); + + // vldoor_e type; + saveg_write_enum(str->type); + + // sector_t* sector; + saveg_write32(str->sector - sectors); + + // fixed_t topheight; + saveg_write32(str->topheight); + + // fixed_t speed; + saveg_write32(str->speed); + + // int direction; + saveg_write32(str->direction); + + // int topwait; + saveg_write32(str->topwait); + + // int topcountdown; + saveg_write32(str->topcountdown); +} + +// +// floormove_t +// + +static void saveg_read_floormove_t(floormove_t *str) +{ + int sector; + + // thinker_t thinker; + saveg_read_thinker_t(&str->thinker); + + // floor_e type; + str->type = saveg_read_enum(); + + // boolean crush; + str->crush = saveg_read32(); + + // sector_t* sector; + sector = saveg_read32(); + str->sector = §ors[sector]; + + // int direction; + str->direction = saveg_read32(); + + // int newspecial; + str->newspecial = saveg_read32(); + + // short texture; + str->texture = saveg_read16(); + + // fixed_t floordestheight; + str->floordestheight = saveg_read32(); + + // fixed_t speed; + str->speed = saveg_read32(); +} + +static void saveg_write_floormove_t(floormove_t *str) +{ + // thinker_t thinker; + saveg_write_thinker_t(&str->thinker); + + // floor_e type; + saveg_write_enum(str->type); + + // boolean crush; + saveg_write32(str->crush); + + // sector_t* sector; + saveg_write32(str->sector - sectors); + + // int direction; + saveg_write32(str->direction); + + // int newspecial; + saveg_write32(str->newspecial); + + // short texture; + saveg_write16(str->texture); + + // fixed_t floordestheight; + saveg_write32(str->floordestheight); + + // fixed_t speed; + saveg_write32(str->speed); +} + +// +// plat_t +// + +static void saveg_read_plat_t(plat_t *str) +{ + int sector; + + // thinker_t thinker; + saveg_read_thinker_t(&str->thinker); + + // sector_t* sector; + sector = saveg_read32(); + str->sector = §ors[sector]; + + // fixed_t speed; + str->speed = saveg_read32(); + + // fixed_t low; + str->low = saveg_read32(); + + // fixed_t high; + str->high = saveg_read32(); + + // int wait; + str->wait = saveg_read32(); + + // int count; + str->count = saveg_read32(); + + // plat_e status; + str->status = saveg_read_enum(); + + // plat_e oldstatus; + str->oldstatus = saveg_read_enum(); + + // boolean crush; + str->crush = saveg_read32(); + + // int tag; + str->tag = saveg_read32(); + + // plattype_e type; + str->type = saveg_read_enum(); +} + +static void saveg_write_plat_t(plat_t *str) +{ + // thinker_t thinker; + saveg_write_thinker_t(&str->thinker); + + // sector_t* sector; + saveg_write32(str->sector - sectors); + + // fixed_t speed; + saveg_write32(str->speed); + + // fixed_t low; + saveg_write32(str->low); + + // fixed_t high; + saveg_write32(str->high); + + // int wait; + saveg_write32(str->wait); + + // int count; + saveg_write32(str->count); + + // plat_e status; + saveg_write_enum(str->status); + + // plat_e oldstatus; + saveg_write_enum(str->oldstatus); + + // boolean crush; + saveg_write32(str->crush); + + // int tag; + saveg_write32(str->tag); + + // plattype_e type; + saveg_write_enum(str->type); +} + +// +// lightflash_t +// + +static void saveg_read_lightflash_t(lightflash_t *str) +{ + int sector; + + // thinker_t thinker; + saveg_read_thinker_t(&str->thinker); + + // sector_t* sector; + sector = saveg_read32(); + str->sector = §ors[sector]; + + // int count; + str->count = saveg_read32(); + + // int maxlight; + str->maxlight = saveg_read32(); + + // int minlight; + str->minlight = saveg_read32(); + + // int maxtime; + str->maxtime = saveg_read32(); + + // int mintime; + str->mintime = saveg_read32(); +} + +static void saveg_write_lightflash_t(lightflash_t *str) +{ + // thinker_t thinker; + saveg_write_thinker_t(&str->thinker); + + // sector_t* sector; + saveg_write32(str->sector - sectors); + + // int count; + saveg_write32(str->count); + + // int maxlight; + saveg_write32(str->maxlight); + + // int minlight; + saveg_write32(str->minlight); + + // int maxtime; + saveg_write32(str->maxtime); + + // int mintime; + saveg_write32(str->mintime); +} + +// +// strobe_t +// + +static void saveg_read_strobe_t(strobe_t *str) +{ + int sector; + + // thinker_t thinker; + saveg_read_thinker_t(&str->thinker); + + // sector_t* sector; + sector = saveg_read32(); + str->sector = §ors[sector]; + + // int count; + str->count = saveg_read32(); + + // int minlight; + str->minlight = saveg_read32(); + + // int maxlight; + str->maxlight = saveg_read32(); + + // int darktime; + str->darktime = saveg_read32(); + + // int brighttime; + str->brighttime = saveg_read32(); +} + +static void saveg_write_strobe_t(strobe_t *str) +{ + // thinker_t thinker; + saveg_write_thinker_t(&str->thinker); + + // sector_t* sector; + saveg_write32(str->sector - sectors); + + // int count; + saveg_write32(str->count); + + // int minlight; + saveg_write32(str->minlight); + + // int maxlight; + saveg_write32(str->maxlight); + + // int darktime; + saveg_write32(str->darktime); + + // int brighttime; + saveg_write32(str->brighttime); +} + +// +// glow_t +// + +static void saveg_read_glow_t(glow_t *str) +{ + int sector; + + // thinker_t thinker; + saveg_read_thinker_t(&str->thinker); + + // sector_t* sector; + sector = saveg_read32(); + str->sector = §ors[sector]; + + // int minlight; + str->minlight = saveg_read32(); + + // int maxlight; + str->maxlight = saveg_read32(); + + // int direction; + str->direction = saveg_read32(); +} + +static void saveg_write_glow_t(glow_t *str) +{ + // thinker_t thinker; + saveg_write_thinker_t(&str->thinker); + + // sector_t* sector; + saveg_write32(str->sector - sectors); + + // int minlight; + saveg_write32(str->minlight); + + // int maxlight; + saveg_write32(str->maxlight); + + // int direction; + saveg_write32(str->direction); +} + +// +// Write the header for a savegame +// + +void P_WriteSaveGameHeader(char *description) +{ + char name[VERSIONSIZE]; + int i; + + for (i=0; description[i] != '\0'; ++i) + saveg_write8(description[i]); + for (; i> 16) & 0xff); + saveg_write8((leveltime >> 8) & 0xff); + saveg_write8(leveltime & 0xff); +} + +// +// Read the header for a savegame +// + +boolean P_ReadSaveGameHeader(void) +{ + int i; + byte a, b, c; + char vcheck[VERSIONSIZE]; + char read_vcheck[VERSIONSIZE]; + + // skip the description field + + for (i=0; ifloorheight >> FRACBITS); + saveg_write16(sec->ceilingheight >> FRACBITS); + saveg_write16(sec->floorpic); + saveg_write16(sec->ceilingpic); + saveg_write16(sec->lightlevel); + saveg_write16(sec->special); // needed? + saveg_write16(sec->tag); // needed? + } + + + // do lines + for (i=0, li = lines ; iflags); + saveg_write16(li->special); + saveg_write16(li->tag); + for (j=0 ; j<2 ; j++) + { + if (li->sidenum[j] == -1) + continue; + + si = &sides[li->sidenum[j]]; + + saveg_write16(si->textureoffset >> FRACBITS); + saveg_write16(si->rowoffset >> FRACBITS); + saveg_write16(si->toptexture); + saveg_write16(si->bottomtexture); + saveg_write16(si->midtexture); + } + } +} + + + +// +// P_UnArchiveWorld +// +void P_UnArchiveWorld (void) +{ + int i; + int j; + sector_t* sec; + line_t* li; + side_t* si; + + // do sectors + for (i=0, sec = sectors ; ifloorheight = saveg_read16() << FRACBITS; + sec->ceilingheight = saveg_read16() << FRACBITS; + sec->floorpic = saveg_read16(); + sec->ceilingpic = saveg_read16(); + sec->lightlevel = saveg_read16(); + sec->special = saveg_read16(); // needed? + sec->tag = saveg_read16(); // needed? + sec->specialdata = 0; + sec->soundtarget = 0; + } + + // do lines + for (i=0, li = lines ; iflags = saveg_read16(); + li->special = saveg_read16(); + li->tag = saveg_read16(); + for (j=0 ; j<2 ; j++) + { + if (li->sidenum[j] == -1) + continue; + si = &sides[li->sidenum[j]]; + si->textureoffset = saveg_read16() << FRACBITS; + si->rowoffset = saveg_read16() << FRACBITS; + si->toptexture = saveg_read16(); + si->bottomtexture = saveg_read16(); + si->midtexture = saveg_read16(); + } + } +} + + + + + +// +// Thinkers +// +typedef enum +{ + tc_end, + tc_mobj + +} thinkerclass_t; + + +// +// P_ArchiveThinkers +// +void P_ArchiveThinkers (void) +{ + thinker_t* th; + + // save off the current thinkers + for (th = thinkercap.next ; th != &thinkercap ; th=th->next) + { + if (th->function.acp1 == (actionf_p1)P_MobjThinker) + { + saveg_write8(tc_mobj); + saveg_write_pad(); + saveg_write_mobj_t((mobj_t *) th); + + continue; + } + + // I_Error ("P_ArchiveThinkers: Unknown thinker function"); + } + + // add a terminating marker + saveg_write8(tc_end); +} + + + +// +// P_UnArchiveThinkers +// +void P_UnArchiveThinkers (void) +{ + byte tclass; + thinker_t* currentthinker; + thinker_t* next; + mobj_t* mobj; + + // remove all the current thinkers + currentthinker = thinkercap.next; + while (currentthinker != &thinkercap) + { + next = currentthinker->next; + + if (currentthinker->function.acp1 == (actionf_p1)P_MobjThinker) + P_RemoveMobj ((mobj_t *)currentthinker); + else + Z_Free (currentthinker); + + currentthinker = next; + } + P_InitThinkers (); + + // read in saved thinkers + while (1) + { + tclass = saveg_read8(); + switch (tclass) + { + case tc_end: + return; // end of list + + case tc_mobj: + saveg_read_pad(); + mobj = Z_Malloc (sizeof(*mobj), PU_LEVEL, NULL); + saveg_read_mobj_t(mobj); + + mobj->target = NULL; + mobj->tracer = NULL; + P_SetThingPosition (mobj); + mobj->info = &mobjinfo[mobj->type]; + mobj->floorz = mobj->subsector->sector->floorheight; + mobj->ceilingz = mobj->subsector->sector->ceilingheight; + mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker; + P_AddThinker (&mobj->thinker); + break; + + default: + I_Error ("Unknown tclass %i in savegame",tclass); + } + + } + +} + + +// +// P_ArchiveSpecials +// +enum +{ + tc_ceiling, + tc_door, + tc_floor, + tc_plat, + tc_flash, + tc_strobe, + tc_glow, + tc_endspecials + +} specials_e; + + + +// +// Things to handle: +// +// T_MoveCeiling, (ceiling_t: sector_t * swizzle), - active list +// T_VerticalDoor, (vldoor_t: sector_t * swizzle), +// T_MoveFloor, (floormove_t: sector_t * swizzle), +// T_LightFlash, (lightflash_t: sector_t * swizzle), +// T_StrobeFlash, (strobe_t: sector_t *), +// T_Glow, (glow_t: sector_t *), +// T_PlatRaise, (plat_t: sector_t *), - active list +// +void P_ArchiveSpecials (void) +{ + thinker_t* th; + int i; + + // save off the current thinkers + for (th = thinkercap.next ; th != &thinkercap ; th=th->next) + { + if (th->function.acv == (actionf_v)NULL) + { + for (i = 0; i < MAXCEILINGS;i++) + if (activeceilings[i] == (ceiling_t *)th) + break; + + if (ifunction.acp1 == (actionf_p1)T_MoveCeiling) + { + saveg_write8(tc_ceiling); + saveg_write_pad(); + saveg_write_ceiling_t((ceiling_t *) th); + continue; + } + + if (th->function.acp1 == (actionf_p1)T_VerticalDoor) + { + saveg_write8(tc_door); + saveg_write_pad(); + saveg_write_vldoor_t((vldoor_t *) th); + continue; + } + + if (th->function.acp1 == (actionf_p1)T_MoveFloor) + { + saveg_write8(tc_floor); + saveg_write_pad(); + saveg_write_floormove_t((floormove_t *) th); + continue; + } + + if (th->function.acp1 == (actionf_p1)T_PlatRaise) + { + saveg_write8(tc_plat); + saveg_write_pad(); + saveg_write_plat_t((plat_t *) th); + continue; + } + + if (th->function.acp1 == (actionf_p1)T_LightFlash) + { + saveg_write8(tc_flash); + saveg_write_pad(); + saveg_write_lightflash_t((lightflash_t *) th); + continue; + } + + if (th->function.acp1 == (actionf_p1)T_StrobeFlash) + { + saveg_write8(tc_strobe); + saveg_write_pad(); + saveg_write_strobe_t((strobe_t *) th); + continue; + } + + if (th->function.acp1 == (actionf_p1)T_Glow) + { + saveg_write8(tc_glow); + saveg_write_pad(); + saveg_write_glow_t((glow_t *) th); + continue; + } + } + + // add a terminating marker + saveg_write8(tc_endspecials); + +} + + +// +// P_UnArchiveSpecials +// +void P_UnArchiveSpecials (void) +{ + byte tclass; + ceiling_t* ceiling; + vldoor_t* door; + floormove_t* floor; + plat_t* plat; + lightflash_t* flash; + strobe_t* strobe; + glow_t* glow; + + + // read in saved thinkers + while (1) + { + tclass = saveg_read8(); + + switch (tclass) + { + case tc_endspecials: + return; // end of list + + case tc_ceiling: + saveg_read_pad(); + ceiling = Z_Malloc (sizeof(*ceiling), PU_LEVEL, NULL); + saveg_read_ceiling_t(ceiling); + ceiling->sector->specialdata = ceiling; + + if (ceiling->thinker.function.acp1) + ceiling->thinker.function.acp1 = (actionf_p1)T_MoveCeiling; + + P_AddThinker (&ceiling->thinker); + P_AddActiveCeiling(ceiling); + break; + + case tc_door: + saveg_read_pad(); + door = Z_Malloc (sizeof(*door), PU_LEVEL, NULL); + saveg_read_vldoor_t(door); + door->sector->specialdata = door; + door->thinker.function.acp1 = (actionf_p1)T_VerticalDoor; + P_AddThinker (&door->thinker); + break; + + case tc_floor: + saveg_read_pad(); + floor = Z_Malloc (sizeof(*floor), PU_LEVEL, NULL); + saveg_read_floormove_t(floor); + floor->sector->specialdata = floor; + floor->thinker.function.acp1 = (actionf_p1)T_MoveFloor; + P_AddThinker (&floor->thinker); + break; + + case tc_plat: + saveg_read_pad(); + plat = Z_Malloc (sizeof(*plat), PU_LEVEL, NULL); + saveg_read_plat_t(plat); + plat->sector->specialdata = plat; + + if (plat->thinker.function.acp1) + plat->thinker.function.acp1 = (actionf_p1)T_PlatRaise; + + P_AddThinker (&plat->thinker); + P_AddActivePlat(plat); + break; + + case tc_flash: + saveg_read_pad(); + flash = Z_Malloc (sizeof(*flash), PU_LEVEL, NULL); + saveg_read_lightflash_t(flash); + flash->thinker.function.acp1 = (actionf_p1)T_LightFlash; + P_AddThinker (&flash->thinker); + break; + + case tc_strobe: + saveg_read_pad(); + strobe = Z_Malloc (sizeof(*strobe), PU_LEVEL, NULL); + saveg_read_strobe_t(strobe); + strobe->thinker.function.acp1 = (actionf_p1)T_StrobeFlash; + P_AddThinker (&strobe->thinker); + break; + + case tc_glow: + saveg_read_pad(); + glow = Z_Malloc (sizeof(*glow), PU_LEVEL, NULL); + saveg_read_glow_t(glow); + glow->thinker.function.acp1 = (actionf_p1)T_Glow; + P_AddThinker (&glow->thinker); + break; + + default: + I_Error ("P_UnarchiveSpecials:Unknown tclass %i " + "in savegame",tclass); + } + + } + +} + diff --git a/firmware_p4/components/Applications/doom/p_saveg.h b/firmware_p4/components/Applications/doom/p_saveg.h new file mode 100644 index 000000000..2d7beba3c --- /dev/null +++ b/firmware_p4/components/Applications/doom/p_saveg.h @@ -0,0 +1,62 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Savegame I/O, archiving, persistence. +// + + +#ifndef __P_SAVEG__ +#define __P_SAVEG__ + +#include + +// maximum size of a savegame description + +#define SAVESTRINGSIZE 24 + +// temporary filename to use while saving. + +char *P_TempSaveGameFile(void); + +// filename to use for a savegame slot + +char *P_SaveGameFile(int slot); + +// Savegame file header read/write functions + +boolean P_ReadSaveGameHeader(void); +void P_WriteSaveGameHeader(char *description); + +// Savegame end-of-file read/write functions + +boolean P_ReadSaveGameEOF(void); +void P_WriteSaveGameEOF(void); + +// Persistent storage/archiving. +// These are the load / save game routines. +void P_ArchivePlayers (void); +void P_UnArchivePlayers (void); +void P_ArchiveWorld (void); +void P_UnArchiveWorld (void); +void P_ArchiveThinkers (void); +void P_UnArchiveThinkers (void); +void P_ArchiveSpecials (void); +void P_UnArchiveSpecials (void); + +extern FILE *save_stream; +extern boolean savegame_error; + + +#endif diff --git a/firmware_p4/components/Applications/doom/p_setup.c b/firmware_p4/components/Applications/doom/p_setup.c new file mode 100644 index 000000000..00306e84c --- /dev/null +++ b/firmware_p4/components/Applications/doom/p_setup.c @@ -0,0 +1,855 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Do all the WAD I/O, get map description, +// set up initial state and misc. LUTs. +// + + + +#include + +#include "z_zone.h" + +#include "deh_main.h" +#include "i_swap.h" +#include "m_argv.h" +#include "m_bbox.h" + +#include "g_game.h" + +#include "i_system.h" +#include "w_wad.h" + +#include "doomdef.h" +#include "p_local.h" + +#include "s_sound.h" + +#include "doomstat.h" + + +void P_SpawnMapThing (mapthing_t* mthing); + + +// +// MAP related Lookup tables. +// Store VERTEXES, LINEDEFS, SIDEDEFS, etc. +// +int numvertexes; +vertex_t* vertexes; + +int numsegs; +seg_t* segs; + +int numsectors; +sector_t* sectors; + +int numsubsectors; +subsector_t* subsectors; + +int numnodes; +node_t* nodes; + +int numlines; +line_t* lines; + +int numsides; +side_t* sides; + +static int totallines; + +// BLOCKMAP +// Created from axis aligned bounding box +// of the map, a rectangular array of +// blocks of size ... +// Used to speed up collision detection +// by spatial subdivision in 2D. +// +// Blockmap size. +int bmapwidth; +int bmapheight; // size in mapblocks +short* blockmap; // int for larger maps +// offsets in blockmap are from here +short* blockmaplump; +// origin of block map +fixed_t bmaporgx; +fixed_t bmaporgy; +// for thing chains +mobj_t** blocklinks; + + +// REJECT +// For fast sight rejection. +// Speeds up enemy AI by skipping detailed +// LineOf Sight calculation. +// Without special effect, this could be +// used as a PVS lookup as well. +// +byte* rejectmatrix; + + +// Maintain single and multi player starting spots. +#define MAX_DEATHMATCH_STARTS 10 + +mapthing_t deathmatchstarts[MAX_DEATHMATCH_STARTS]; +mapthing_t* deathmatch_p; +mapthing_t playerstarts[MAXPLAYERS]; + + + + + +// +// P_LoadVertexes +// +void P_LoadVertexes (int lump) +{ + byte* data; + int i; + mapvertex_t* ml; + vertex_t* li; + + // Determine number of lumps: + // total lump length / vertex record length. + numvertexes = W_LumpLength (lump) / sizeof(mapvertex_t); + + // Allocate zone memory for buffer. + vertexes = Z_Malloc (numvertexes*sizeof(vertex_t),PU_LEVEL,0); + + // Load data into cache. + data = W_CacheLumpNum (lump, PU_STATIC); + + ml = (mapvertex_t *)data; + li = vertexes; + + // Copy and convert vertex coordinates, + // internal representation as fixed. + for (i=0 ; ix = SHORT(ml->x)<y = SHORT(ml->y)<v1 = &vertexes[SHORT(ml->v1)]; + li->v2 = &vertexes[SHORT(ml->v2)]; + + li->angle = (SHORT(ml->angle))<<16; + li->offset = (SHORT(ml->offset))<<16; + linedef = SHORT(ml->linedef); + ldef = &lines[linedef]; + li->linedef = ldef; + side = SHORT(ml->side); + li->sidedef = &sides[ldef->sidenum[side]]; + li->frontsector = sides[ldef->sidenum[side]].sector; + + if (ldef-> flags & ML_TWOSIDED) + { + sidenum = ldef->sidenum[side ^ 1]; + + // If the sidenum is out of range, this may be a "glass hack" + // impassible window. Point at side #0 (this may not be + // the correct Vanilla behavior; however, it seems to work for + // OTTAWAU.WAD, which is the one place I've seen this trick + // used). + + if (sidenum < 0 || sidenum >= numsides) + { + li->backsector = GetSectorAtNullAddress(); + } + else + { + li->backsector = sides[sidenum].sector; + } + } + else + { + li->backsector = 0; + } + } + + W_ReleaseLumpNum(lump); +} + + +// +// P_LoadSubsectors +// +void P_LoadSubsectors (int lump) +{ + byte* data; + int i; + mapsubsector_t* ms; + subsector_t* ss; + + numsubsectors = W_LumpLength (lump) / sizeof(mapsubsector_t); + subsectors = Z_Malloc (numsubsectors*sizeof(subsector_t),PU_LEVEL,0); + data = W_CacheLumpNum (lump,PU_STATIC); + + ms = (mapsubsector_t *)data; + memset (subsectors,0, numsubsectors*sizeof(subsector_t)); + ss = subsectors; + + for (i=0 ; inumlines = SHORT(ms->numsegs); + ss->firstline = SHORT(ms->firstseg); + } + + W_ReleaseLumpNum(lump); +} + + + +// +// P_LoadSectors +// +void P_LoadSectors (int lump) +{ + byte* data; + int i; + mapsector_t* ms; + sector_t* ss; + + numsectors = W_LumpLength (lump) / sizeof(mapsector_t); + sectors = Z_Malloc (numsectors*sizeof(sector_t),PU_LEVEL,0); + memset (sectors, 0, numsectors*sizeof(sector_t)); + data = W_CacheLumpNum (lump,PU_STATIC); + + ms = (mapsector_t *)data; + ss = sectors; + for (i=0 ; ifloorheight = SHORT(ms->floorheight)<ceilingheight = SHORT(ms->ceilingheight)<floorpic = R_FlatNumForName(ms->floorpic); + ss->ceilingpic = R_FlatNumForName(ms->ceilingpic); + ss->lightlevel = SHORT(ms->lightlevel); + ss->special = SHORT(ms->special); + ss->tag = SHORT(ms->tag); + ss->thinglist = NULL; + } + + W_ReleaseLumpNum(lump); +} + + +// +// P_LoadNodes +// +void P_LoadNodes (int lump) +{ + byte* data; + int i; + int j; + int k; + mapnode_t* mn; + node_t* no; + + numnodes = W_LumpLength (lump) / sizeof(mapnode_t); + nodes = Z_Malloc (numnodes*sizeof(node_t),PU_LEVEL,0); + data = W_CacheLumpNum (lump,PU_STATIC); + + mn = (mapnode_t *)data; + no = nodes; + + for (i=0 ; ix = SHORT(mn->x)<y = SHORT(mn->y)<dx = SHORT(mn->dx)<dy = SHORT(mn->dy)<children[j] = SHORT(mn->children[j]); + for (k=0 ; k<4 ; k++) + no->bbox[j][k] = SHORT(mn->bbox[j][k])<type)) + { + case 68: // Arachnotron + case 64: // Archvile + case 88: // Boss Brain + case 89: // Boss Shooter + case 69: // Hell Knight + case 67: // Mancubus + case 71: // Pain Elemental + case 65: // Former Human Commando + case 66: // Revenant + case 84: // Wolf SS + spawn = false; + break; + } + } + if (spawn == false) + break; + + // Do spawn all other stuff. + spawnthing.x = SHORT(mt->x); + spawnthing.y = SHORT(mt->y); + spawnthing.angle = SHORT(mt->angle); + spawnthing.type = SHORT(mt->type); + spawnthing.options = SHORT(mt->options); + + P_SpawnMapThing(&spawnthing); + } + + W_ReleaseLumpNum(lump); +} + + +// +// P_LoadLineDefs +// Also counts secret lines for intermissions. +// +void P_LoadLineDefs (int lump) +{ + byte* data; + int i; + maplinedef_t* mld; + line_t* ld; + vertex_t* v1; + vertex_t* v2; + + numlines = W_LumpLength (lump) / sizeof(maplinedef_t); + lines = Z_Malloc (numlines*sizeof(line_t),PU_LEVEL,0); + memset (lines, 0, numlines*sizeof(line_t)); + data = W_CacheLumpNum (lump,PU_STATIC); + + mld = (maplinedef_t *)data; + ld = lines; + for (i=0 ; iflags = SHORT(mld->flags); + ld->special = SHORT(mld->special); + ld->tag = SHORT(mld->tag); + v1 = ld->v1 = &vertexes[SHORT(mld->v1)]; + v2 = ld->v2 = &vertexes[SHORT(mld->v2)]; + ld->dx = v2->x - v1->x; + ld->dy = v2->y - v1->y; + + if (!ld->dx) + ld->slopetype = ST_VERTICAL; + else if (!ld->dy) + ld->slopetype = ST_HORIZONTAL; + else + { + if (FixedDiv (ld->dy , ld->dx) > 0) + ld->slopetype = ST_POSITIVE; + else + ld->slopetype = ST_NEGATIVE; + } + + if (v1->x < v2->x) + { + ld->bbox[BOXLEFT] = v1->x; + ld->bbox[BOXRIGHT] = v2->x; + } + else + { + ld->bbox[BOXLEFT] = v2->x; + ld->bbox[BOXRIGHT] = v1->x; + } + + if (v1->y < v2->y) + { + ld->bbox[BOXBOTTOM] = v1->y; + ld->bbox[BOXTOP] = v2->y; + } + else + { + ld->bbox[BOXBOTTOM] = v2->y; + ld->bbox[BOXTOP] = v1->y; + } + + ld->sidenum[0] = SHORT(mld->sidenum[0]); + ld->sidenum[1] = SHORT(mld->sidenum[1]); + + if (ld->sidenum[0] != -1) + ld->frontsector = sides[ld->sidenum[0]].sector; + else + ld->frontsector = 0; + + if (ld->sidenum[1] != -1) + ld->backsector = sides[ld->sidenum[1]].sector; + else + ld->backsector = 0; + } + + W_ReleaseLumpNum(lump); +} + + +// +// P_LoadSideDefs +// +void P_LoadSideDefs (int lump) +{ + byte* data; + int i; + mapsidedef_t* msd; + side_t* sd; + + numsides = W_LumpLength (lump) / sizeof(mapsidedef_t); + sides = Z_Malloc (numsides*sizeof(side_t),PU_LEVEL,0); + memset (sides, 0, numsides*sizeof(side_t)); + data = W_CacheLumpNum (lump,PU_STATIC); + + msd = (mapsidedef_t *)data; + sd = sides; + for (i=0 ; itextureoffset = SHORT(msd->textureoffset)<rowoffset = SHORT(msd->rowoffset)<toptexture = R_TextureNumForName(msd->toptexture); + sd->bottomtexture = R_TextureNumForName(msd->bottomtexture); + sd->midtexture = R_TextureNumForName(msd->midtexture); + sd->sector = §ors[SHORT(msd->sector)]; + } + + W_ReleaseLumpNum(lump); +} + + +// +// P_LoadBlockMap +// +void P_LoadBlockMap (int lump) +{ + int i; + int count; + int lumplen; + + lumplen = W_LumpLength(lump); + count = lumplen / 2; + + blockmaplump = Z_Malloc(lumplen, PU_LEVEL, NULL); + W_ReadLump(lump, blockmaplump); + blockmap = blockmaplump + 4; + + // Swap all short integers to native byte ordering. + + for (i=0; ifirstline]; + ss->sector = seg->sidedef->sector; + } + + // count number of lines in each sector + li = lines; + totallines = 0; + for (i=0 ; ifrontsector->linecount++; + + if (li->backsector && li->backsector != li->frontsector) + { + li->backsector->linecount++; + totallines++; + } + } + + // build line tables for each sector + linebuffer = Z_Malloc (totallines*sizeof(line_t *), PU_LEVEL, 0); + + for (i=0; ifrontsector != NULL) + { + sector = li->frontsector; + + sector->lines[sector->linecount] = li; + ++sector->linecount; + } + + if (li->backsector != NULL && li->frontsector != li->backsector) + { + sector = li->backsector; + + sector->lines[sector->linecount] = li; + ++sector->linecount; + } + } + + // Generate bounding boxes for sectors + + sector = sectors; + for (i=0 ; ilinecount; j++) + { + li = sector->lines[j]; + + M_AddToBox (bbox, li->v1->x, li->v1->y); + M_AddToBox (bbox, li->v2->x, li->v2->y); + } + + // set the degenmobj_t to the middle of the bounding box + sector->soundorg.x = (bbox[BOXRIGHT]+bbox[BOXLEFT])/2; + sector->soundorg.y = (bbox[BOXTOP]+bbox[BOXBOTTOM])/2; + + // adjust bounding box to map blocks + block = (bbox[BOXTOP]-bmaporgy+MAXRADIUS)>>MAPBLOCKSHIFT; + block = block >= bmapheight ? bmapheight-1 : block; + sector->blockbox[BOXTOP]=block; + + block = (bbox[BOXBOTTOM]-bmaporgy-MAXRADIUS)>>MAPBLOCKSHIFT; + block = block < 0 ? 0 : block; + sector->blockbox[BOXBOTTOM]=block; + + block = (bbox[BOXRIGHT]-bmaporgx+MAXRADIUS)>>MAPBLOCKSHIFT; + block = block >= bmapwidth ? bmapwidth-1 : block; + sector->blockbox[BOXRIGHT]=block; + + block = (bbox[BOXLEFT]-bmaporgx-MAXRADIUS)>>MAPBLOCKSHIFT; + block = block < 0 ? 0 : block; + sector->blockbox[BOXLEFT]=block; + } + +} + +// Pad the REJECT lump with extra data when the lump is too small, +// to simulate a REJECT buffer overflow in Vanilla Doom. + +static void PadRejectArray(byte *array, unsigned int len) +{ + unsigned int i; + unsigned int byte_num; + byte *dest; + unsigned int padvalue; + + // Values to pad the REJECT array with: + + unsigned int rejectpad[4] = + { + ((totallines * 4 + 3) & ~3) + 24, // Size + 0, // Part of z_zone block header + 50, // PU_LEVEL + 0x1d4a11 // DOOM_CONST_ZONEID + }; + + // Copy values from rejectpad into the destination array. + + dest = array; + + for (i=0; i> (byte_num * 8)) & 0xff; + ++dest; + } + + // We only have a limited pad size. Print a warning if the + // REJECT lump is too small. + + if (len > sizeof(rejectpad)) + { + fprintf(stderr, "PadRejectArray: REJECT lump too short to pad! (%i > %i)\n", + len, (int) sizeof(rejectpad)); + + // Pad remaining space with 0 (or 0xff, if specified on command line). + + if (M_CheckParm("-reject_pad_with_ff")) + { + padvalue = 0xff; + } + else + { + padvalue = 0xf00; + } + + memset(array + sizeof(rejectpad), padvalue, len - sizeof(rejectpad)); + } +} + +static void P_LoadReject(int lumpnum) +{ + int minlength; + int lumplen; + + // Calculate the size that the REJECT lump *should* be. + + minlength = (numsectors * numsectors + 7) / 8; + + // If the lump meets the minimum length, it can be loaded directly. + // Otherwise, we need to allocate a buffer of the correct size + // and pad it with appropriate data. + + lumplen = W_LumpLength(lumpnum); + + if (lumplen >= minlength) + { + rejectmatrix = W_CacheLumpNum(lumpnum, PU_LEVEL); + } + else + { + rejectmatrix = Z_Malloc(minlength, PU_LEVEL, &rejectmatrix); + W_ReadLump(lumpnum, rejectmatrix); + + PadRejectArray(rejectmatrix + lumplen, minlength - lumplen); + } +} + +// +// P_SetupLevel +// +void +P_SetupLevel +( int episode, + int map, + int playermask, + skill_t skill) +{ + int i; + char lumpname[9]; + int lumpnum; + + totalkills = totalitems = totalsecret = wminfo.maxfrags = 0; + wminfo.partime = 180; + for (i=0 ; idx) + { + if (x==node->x) + return 2; + + if (x <= node->x) + return node->dy > 0; + + return node->dy < 0; + } + + if (!node->dy) + { + if (x==node->y) + return 2; + + if (y <= node->y) + return node->dx < 0; + + return node->dx > 0; + } + + dx = (x - node->x); + dy = (y - node->y); + + left = (node->dy>>FRACBITS) * (dx>>FRACBITS); + right = (dy>>FRACBITS) * (node->dx>>FRACBITS); + + if (right < left) + return 0; // front side + + if (left == right) + return 2; + return 1; // back side +} + + +// +// P_InterceptVector2 +// Returns the fractional intercept point +// along the first divline. +// This is only called by the addthings and addlines traversers. +// +fixed_t +P_InterceptVector2 +( divline_t* v2, + divline_t* v1 ) +{ + fixed_t frac; + fixed_t num; + fixed_t den; + + den = FixedMul (v1->dy>>8,v2->dx) - FixedMul(v1->dx>>8,v2->dy); + + if (den == 0) + return 0; + // I_Error ("P_InterceptVector: parallel"); + + num = FixedMul ( (v1->x - v2->x)>>8 ,v1->dy) + + FixedMul ( (v2->y - v1->y)>>8 , v1->dx); + frac = FixedDiv (num , den); + + return frac; +} + +// +// P_CrossSubsector +// Returns true +// if strace crosses the given subsector successfully. +// +boolean P_CrossSubsector (int num) +{ + seg_t* seg; + line_t* line; + int s1; + int s2; + int count; + subsector_t* sub; + sector_t* front; + sector_t* back; + fixed_t opentop; + fixed_t openbottom; + divline_t divl; + vertex_t* v1; + vertex_t* v2; + fixed_t frac; + fixed_t slope; + +#ifdef RANGECHECK + if (num>=numsubsectors) + I_Error ("P_CrossSubsector: ss %i with numss = %i", + num, + numsubsectors); +#endif + + sub = &subsectors[num]; + + // check lines + count = sub->numlines; + seg = &segs[sub->firstline]; + + for ( ; count ; seg++, count--) + { + line = seg->linedef; + + // allready checked other side? + if (line->validcount == validcount) + continue; + + line->validcount = validcount; + + v1 = line->v1; + v2 = line->v2; + s1 = P_DivlineSide (v1->x,v1->y, &strace); + s2 = P_DivlineSide (v2->x, v2->y, &strace); + + // line isn't crossed? + if (s1 == s2) + continue; + + divl.x = v1->x; + divl.y = v1->y; + divl.dx = v2->x - v1->x; + divl.dy = v2->y - v1->y; + s1 = P_DivlineSide (strace.x, strace.y, &divl); + s2 = P_DivlineSide (t2x, t2y, &divl); + + // line isn't crossed? + if (s1 == s2) + continue; + + // Backsector may be NULL if this is an "impassible + // glass" hack line. + + if (line->backsector == NULL) + { + return false; + } + + // stop because it is not two sided anyway + // might do this after updating validcount? + if ( !(line->flags & ML_TWOSIDED) ) + return false; + + // crosses a two sided line + front = seg->frontsector; + back = seg->backsector; + + // no wall to block sight with? + if (front->floorheight == back->floorheight + && front->ceilingheight == back->ceilingheight) + continue; + + // possible occluder + // because of ceiling height differences + if (front->ceilingheight < back->ceilingheight) + opentop = front->ceilingheight; + else + opentop = back->ceilingheight; + + // because of ceiling height differences + if (front->floorheight > back->floorheight) + openbottom = front->floorheight; + else + openbottom = back->floorheight; + + // quick test for totally closed doors + if (openbottom >= opentop) + return false; // stop + + frac = P_InterceptVector2 (&strace, &divl); + + if (front->floorheight != back->floorheight) + { + slope = FixedDiv (openbottom - sightzstart , frac); + if (slope > bottomslope) + bottomslope = slope; + } + + if (front->ceilingheight != back->ceilingheight) + { + slope = FixedDiv (opentop - sightzstart , frac); + if (slope < topslope) + topslope = slope; + } + + if (topslope <= bottomslope) + return false; // stop + } + // passed the subsector ok + return true; +} + + + +// +// P_CrossBSPNode +// Returns true +// if strace crosses the given node successfully. +// +boolean P_CrossBSPNode (int bspnum) +{ + node_t* bsp; + int side; + + if (bspnum & NF_SUBSECTOR) + { + if (bspnum == -1) + return P_CrossSubsector (0); + else + return P_CrossSubsector (bspnum&(~NF_SUBSECTOR)); + } + + bsp = &nodes[bspnum]; + + // decide which side the start point is on + side = P_DivlineSide (strace.x, strace.y, (divline_t *)bsp); + if (side == 2) + side = 0; // an "on" should cross both sides + + // cross the starting side + if (!P_CrossBSPNode (bsp->children[side]) ) + return false; + + // the partition plane is crossed here + if (side == P_DivlineSide (t2x, t2y,(divline_t *)bsp)) + { + // the line doesn't touch the other side + return true; + } + + // cross the ending side + return P_CrossBSPNode (bsp->children[side^1]); +} + + +// +// P_CheckSight +// Returns true +// if a straight line between t1 and t2 is unobstructed. +// Uses REJECT. +// +boolean +P_CheckSight +( mobj_t* t1, + mobj_t* t2 ) +{ + int s1; + int s2; + int pnum; + int bytenum; + int bitnum; + + // First check for trivial rejection. + + // Determine subsector entries in REJECT table. + s1 = (t1->subsector->sector - sectors); + s2 = (t2->subsector->sector - sectors); + pnum = s1*numsectors + s2; + bytenum = pnum>>3; + bitnum = 1 << (pnum&7); + + // Check in REJECT table. + if (rejectmatrix[bytenum]&bitnum) + { + sightcounts[0]++; + + // can't possibly be connected + return false; + } + + // An unobstructed LOS is possible. + // Now look from eyes of t1 to any part of t2. + sightcounts[1]++; + + validcount++; + + sightzstart = t1->z + t1->height - (t1->height>>2); + topslope = (t2->z+t2->height) - sightzstart; + bottomslope = (t2->z) - sightzstart; + + strace.x = t1->x; + strace.y = t1->y; + t2x = t2->x; + t2y = t2->y; + strace.dx = t2->x - t1->x; + strace.dy = t2->y - t1->y; + + // the head node is the last node output + return P_CrossBSPNode (numnodes-1); +} + + diff --git a/firmware_p4/components/Applications/doom/p_spec.c b/firmware_p4/components/Applications/doom/p_spec.c new file mode 100644 index 000000000..17446b5df --- /dev/null +++ b/firmware_p4/components/Applications/doom/p_spec.c @@ -0,0 +1,1489 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Implements special effects: +// Texture animation, height or lighting changes +// according to adjacent sectors, respective +// utility functions, etc. +// Line Tag handling. Line and Sector triggers. +// + + +#include + +#include "doomdef.h" +#include "doomstat.h" + +#include "deh_main.h" +#include "i_system.h" +#include "z_zone.h" +#include "m_argv.h" +#include "m_misc.h" +#include "m_random.h" +#include "w_wad.h" + +#include "r_local.h" +#include "p_local.h" + +#include "g_game.h" + +#include "s_sound.h" + +// State. +#include "r_state.h" + +// Data. +#include "sounds.h" + + +// +// Animating textures and planes +// There is another anim_t used in wi_stuff, unrelated. +// +typedef struct +{ + boolean istexture; + int picnum; + int basepic; + int numpics; + int speed; + +} anim_t; + +// +// source animation definition +// +typedef struct +{ + int istexture; // if false, it is a flat + char endname[9]; + char startname[9]; + int speed; +} animdef_t; + + + +#define MAXANIMS 32 + +extern anim_t anims[MAXANIMS]; +extern anim_t* lastanim; + +// +// P_InitPicAnims +// + +// Floor/ceiling animation sequences, +// defined by first and last frame, +// i.e. the flat (64x64 tile) name to +// be used. +// The full animation sequence is given +// using all the flats between the start +// and end entry, in the order found in +// the WAD file. +// +animdef_t animdefs[] = +{ + {false, "NUKAGE3", "NUKAGE1", 8}, + {false, "FWATER4", "FWATER1", 8}, + {false, "SWATER4", "SWATER1", 8}, + {false, "LAVA4", "LAVA1", 8}, + {false, "BLOOD3", "BLOOD1", 8}, + + // DOOM II flat animations. + {false, "RROCK08", "RROCK05", 8}, + {false, "SLIME04", "SLIME01", 8}, + {false, "SLIME08", "SLIME05", 8}, + {false, "SLIME12", "SLIME09", 8}, + + {true, "BLODGR4", "BLODGR1", 8}, + {true, "SLADRIP3", "SLADRIP1", 8}, + + {true, "BLODRIP4", "BLODRIP1", 8}, + {true, "FIREWALL", "FIREWALA", 8}, + {true, "GSTFONT3", "GSTFONT1", 8}, + {true, "FIRELAVA", "FIRELAV3", 8}, + {true, "FIREMAG3", "FIREMAG1", 8}, + {true, "FIREBLU2", "FIREBLU1", 8}, + {true, "ROCKRED3", "ROCKRED1", 8}, + + {true, "BFALL4", "BFALL1", 8}, + {true, "SFALL4", "SFALL1", 8}, + {true, "WFALL4", "WFALL1", 8}, + {true, "DBRAIN4", "DBRAIN1", 8}, + + {-1, "", "", 0}, +}; + +anim_t anims[MAXANIMS]; +anim_t* lastanim; + + +// +// Animating line specials +// +#define MAXLINEANIMS 64 + +extern short numlinespecials; +extern line_t* linespeciallist[MAXLINEANIMS]; + + + +void P_InitPicAnims (void) +{ + int i; + + + // Init animation + lastanim = anims; + for (i=0 ; animdefs[i].istexture != -1 ; i++) + { + char *startname, *endname; + + startname = DEH_String(animdefs[i].startname); + endname = DEH_String(animdefs[i].endname); + + if (animdefs[i].istexture) + { + // different episode ? + if (R_CheckTextureNumForName(startname) == -1) + continue; + + lastanim->picnum = R_TextureNumForName(endname); + lastanim->basepic = R_TextureNumForName(startname); + } + else + { + if (W_CheckNumForName(startname) == -1) + continue; + + lastanim->picnum = R_FlatNumForName(endname); + lastanim->basepic = R_FlatNumForName(startname); + } + + lastanim->istexture = animdefs[i].istexture; + lastanim->numpics = lastanim->picnum - lastanim->basepic + 1; + + if (lastanim->numpics < 2) + I_Error ("P_InitPicAnims: bad cycle from %s to %s", + startname, endname); + + lastanim->speed = animdefs[i].speed; + lastanim++; + } + +} + + + +// +// UTILITIES +// + + + +// +// getSide() +// Will return a side_t* +// given the number of the current sector, +// the line number, and the side (0/1) that you want. +// +side_t* +getSide +( int currentSector, + int line, + int side ) +{ + return &sides[ (sectors[currentSector].lines[line])->sidenum[side] ]; +} + + +// +// getSector() +// Will return a sector_t* +// given the number of the current sector, +// the line number and the side (0/1) that you want. +// +sector_t* +getSector +( int currentSector, + int line, + int side ) +{ + return sides[ (sectors[currentSector].lines[line])->sidenum[side] ].sector; +} + + +// +// twoSided() +// Given the sector number and the line number, +// it will tell you whether the line is two-sided or not. +// +int +twoSided +( int sector, + int line ) +{ + return (sectors[sector].lines[line])->flags & ML_TWOSIDED; +} + + + + +// +// getNextSector() +// Return sector_t * of sector next to current. +// NULL if not two-sided line +// +sector_t* +getNextSector +( line_t* line, + sector_t* sec ) +{ + if (!(line->flags & ML_TWOSIDED)) + return NULL; + + if (line->frontsector == sec) + return line->backsector; + + return line->frontsector; +} + + + +// +// P_FindLowestFloorSurrounding() +// FIND LOWEST FLOOR HEIGHT IN SURROUNDING SECTORS +// +fixed_t P_FindLowestFloorSurrounding(sector_t* sec) +{ + int i; + line_t* check; + sector_t* other; + fixed_t floor = sec->floorheight; + + for (i=0 ;i < sec->linecount ; i++) + { + check = sec->lines[i]; + other = getNextSector(check,sec); + + if (!other) + continue; + + if (other->floorheight < floor) + floor = other->floorheight; + } + return floor; +} + + + +// +// P_FindHighestFloorSurrounding() +// FIND HIGHEST FLOOR HEIGHT IN SURROUNDING SECTORS +// +fixed_t P_FindHighestFloorSurrounding(sector_t *sec) +{ + int i; + line_t* check; + sector_t* other; + fixed_t floor = -500*FRACUNIT; + + for (i=0 ;i < sec->linecount ; i++) + { + check = sec->lines[i]; + other = getNextSector(check,sec); + + if (!other) + continue; + + if (other->floorheight > floor) + floor = other->floorheight; + } + return floor; +} + + + +// +// P_FindNextHighestFloor +// FIND NEXT HIGHEST FLOOR IN SURROUNDING SECTORS +// Note: this should be doable w/o a fixed array. + +// Thanks to entryway for the Vanilla overflow emulation. + +// 20 adjoining sectors max! +#define MAX_ADJOINING_SECTORS 20 + +fixed_t +P_FindNextHighestFloor +( sector_t* sec, + int currentheight ) +{ + int i; + int h; + int min; + line_t* check; + sector_t* other; + fixed_t height = currentheight; + fixed_t heightlist[MAX_ADJOINING_SECTORS + 2]; + + for (i=0, h=0; i < sec->linecount; i++) + { + check = sec->lines[i]; + other = getNextSector(check,sec); + + if (!other) + continue; + + if (other->floorheight > height) + { + // Emulation of memory (stack) overflow + if (h == MAX_ADJOINING_SECTORS + 1) + { + height = other->floorheight; + } + else if (h == MAX_ADJOINING_SECTORS + 2) + { + // Fatal overflow: game crashes at 22 textures + I_Error("Sector with more than 22 adjoining sectors. " + "Vanilla will crash here"); + } + + heightlist[h++] = other->floorheight; + } + } + + // Find lowest height in list + if (!h) + { + return currentheight; + } + + min = heightlist[0]; + + // Range checking? + for (i = 1; i < h; i++) + { + if (heightlist[i] < min) + { + min = heightlist[i]; + } + } + + return min; +} + +// +// FIND LOWEST CEILING IN THE SURROUNDING SECTORS +// +fixed_t +P_FindLowestCeilingSurrounding(sector_t* sec) +{ + int i; + line_t* check; + sector_t* other; + fixed_t height = INT_MAX; + + for (i=0 ;i < sec->linecount ; i++) + { + check = sec->lines[i]; + other = getNextSector(check,sec); + + if (!other) + continue; + + if (other->ceilingheight < height) + height = other->ceilingheight; + } + return height; +} + + +// +// FIND HIGHEST CEILING IN THE SURROUNDING SECTORS +// +fixed_t P_FindHighestCeilingSurrounding(sector_t* sec) +{ + int i; + line_t* check; + sector_t* other; + fixed_t height = 0; + + for (i=0 ;i < sec->linecount ; i++) + { + check = sec->lines[i]; + other = getNextSector(check,sec); + + if (!other) + continue; + + if (other->ceilingheight > height) + height = other->ceilingheight; + } + return height; +} + + + +// +// RETURN NEXT SECTOR # THAT LINE TAG REFERS TO +// +int +P_FindSectorFromLineTag +( line_t* line, + int start ) +{ + int i; + + for (i=start+1;itag) + return i; + + return -1; +} + + + + +// +// Find minimum light from an adjacent sector +// +int +P_FindMinSurroundingLight +( sector_t* sector, + int max ) +{ + int i; + int min; + line_t* line; + sector_t* check; + + min = max; + for (i=0 ; i < sector->linecount ; i++) + { + line = sector->lines[i]; + check = getNextSector(line,sector); + + if (!check) + continue; + + if (check->lightlevel < min) + min = check->lightlevel; + } + return min; +} + + + +// +// EVENTS +// Events are operations triggered by using, crossing, +// or shooting special lines, or by timed thinkers. +// + +// +// P_CrossSpecialLine - TRIGGER +// Called every time a thing origin is about +// to cross a line with a non 0 special. +// +void +P_CrossSpecialLine +( int linenum, + int side, + mobj_t* thing ) +{ + line_t* line; + int ok; + + line = &lines[linenum]; + + // Triggers that other things can activate + if (!thing->player) + { + // Things that should NOT trigger specials... + switch(thing->type) + { + case MT_ROCKET: + case MT_PLASMA: + case MT_BFG: + case MT_TROOPSHOT: + case MT_HEADSHOT: + case MT_BRUISERSHOT: + return; + break; + + default: break; + } + + ok = 0; + switch(line->special) + { + case 39: // TELEPORT TRIGGER + case 97: // TELEPORT RETRIGGER + case 125: // TELEPORT MONSTERONLY TRIGGER + case 126: // TELEPORT MONSTERONLY RETRIGGER + case 4: // RAISE DOOR + case 10: // PLAT DOWN-WAIT-UP-STAY TRIGGER + case 88: // PLAT DOWN-WAIT-UP-STAY RETRIGGER + ok = 1; + break; + } + if (!ok) + return; + } + + + // Note: could use some const's here. + switch (line->special) + { + // TRIGGERS. + // All from here to RETRIGGERS. + case 2: + // Open Door + EV_DoDoor(line,vld_open); + line->special = 0; + break; + + case 3: + // Close Door + EV_DoDoor(line,vld_close); + line->special = 0; + break; + + case 4: + // Raise Door + EV_DoDoor(line,vld_normal); + line->special = 0; + break; + + case 5: + // Raise Floor + EV_DoFloor(line,raiseFloor); + line->special = 0; + break; + + case 6: + // Fast Ceiling Crush & Raise + EV_DoCeiling(line,fastCrushAndRaise); + line->special = 0; + break; + + case 8: + // Build Stairs + EV_BuildStairs(line,build8); + line->special = 0; + break; + + case 10: + // PlatDownWaitUp + EV_DoPlat(line,downWaitUpStay,0); + line->special = 0; + break; + + case 12: + // Light Turn On - brightest near + EV_LightTurnOn(line,0); + line->special = 0; + break; + + case 13: + // Light Turn On 255 + EV_LightTurnOn(line,255); + line->special = 0; + break; + + case 16: + // Close Door 30 + EV_DoDoor(line,vld_close30ThenOpen); + line->special = 0; + break; + + case 17: + // Start Light Strobing + EV_StartLightStrobing(line); + line->special = 0; + break; + + case 19: + // Lower Floor + EV_DoFloor(line,lowerFloor); + line->special = 0; + break; + + case 22: + // Raise floor to nearest height and change texture + EV_DoPlat(line,raiseToNearestAndChange,0); + line->special = 0; + break; + + case 25: + // Ceiling Crush and Raise + EV_DoCeiling(line,crushAndRaise); + line->special = 0; + break; + + case 30: + // Raise floor to shortest texture height + // on either side of lines. + EV_DoFloor(line,raiseToTexture); + line->special = 0; + break; + + case 35: + // Lights Very Dark + EV_LightTurnOn(line,35); + line->special = 0; + break; + + case 36: + // Lower Floor (TURBO) + EV_DoFloor(line,turboLower); + line->special = 0; + break; + + case 37: + // LowerAndChange + EV_DoFloor(line,lowerAndChange); + line->special = 0; + break; + + case 38: + // Lower Floor To Lowest + EV_DoFloor( line, lowerFloorToLowest ); + line->special = 0; + break; + + case 39: + // TELEPORT! + EV_Teleport( line, side, thing ); + line->special = 0; + break; + + case 40: + // RaiseCeilingLowerFloor + EV_DoCeiling( line, raiseToHighest ); + EV_DoFloor( line, lowerFloorToLowest ); + line->special = 0; + break; + + case 44: + // Ceiling Crush + EV_DoCeiling( line, lowerAndCrush ); + line->special = 0; + break; + + case 52: + // EXIT! + G_ExitLevel (); + break; + + case 53: + // Perpetual Platform Raise + EV_DoPlat(line,perpetualRaise,0); + line->special = 0; + break; + + case 54: + // Platform Stop + EV_StopPlat(line); + line->special = 0; + break; + + case 56: + // Raise Floor Crush + EV_DoFloor(line,raiseFloorCrush); + line->special = 0; + break; + + case 57: + // Ceiling Crush Stop + EV_CeilingCrushStop(line); + line->special = 0; + break; + + case 58: + // Raise Floor 24 + EV_DoFloor(line,raiseFloor24); + line->special = 0; + break; + + case 59: + // Raise Floor 24 And Change + EV_DoFloor(line,raiseFloor24AndChange); + line->special = 0; + break; + + case 104: + // Turn lights off in sector(tag) + EV_TurnTagLightsOff(line); + line->special = 0; + break; + + case 108: + // Blazing Door Raise (faster than TURBO!) + EV_DoDoor (line,vld_blazeRaise); + line->special = 0; + break; + + case 109: + // Blazing Door Open (faster than TURBO!) + EV_DoDoor (line,vld_blazeOpen); + line->special = 0; + break; + + case 100: + // Build Stairs Turbo 16 + EV_BuildStairs(line,turbo16); + line->special = 0; + break; + + case 110: + // Blazing Door Close (faster than TURBO!) + EV_DoDoor (line,vld_blazeClose); + line->special = 0; + break; + + case 119: + // Raise floor to nearest surr. floor + EV_DoFloor(line,raiseFloorToNearest); + line->special = 0; + break; + + case 121: + // Blazing PlatDownWaitUpStay + EV_DoPlat(line,blazeDWUS,0); + line->special = 0; + break; + + case 124: + // Secret EXIT + G_SecretExitLevel (); + break; + + case 125: + // TELEPORT MonsterONLY + if (!thing->player) + { + EV_Teleport( line, side, thing ); + line->special = 0; + } + break; + + case 130: + // Raise Floor Turbo + EV_DoFloor(line,raiseFloorTurbo); + line->special = 0; + break; + + case 141: + // Silent Ceiling Crush & Raise + EV_DoCeiling(line,silentCrushAndRaise); + line->special = 0; + break; + + // RETRIGGERS. All from here till end. + case 72: + // Ceiling Crush + EV_DoCeiling( line, lowerAndCrush ); + break; + + case 73: + // Ceiling Crush and Raise + EV_DoCeiling(line,crushAndRaise); + break; + + case 74: + // Ceiling Crush Stop + EV_CeilingCrushStop(line); + break; + + case 75: + // Close Door + EV_DoDoor(line,vld_close); + break; + + case 76: + // Close Door 30 + EV_DoDoor(line,vld_close30ThenOpen); + break; + + case 77: + // Fast Ceiling Crush & Raise + EV_DoCeiling(line,fastCrushAndRaise); + break; + + case 79: + // Lights Very Dark + EV_LightTurnOn(line,35); + break; + + case 80: + // Light Turn On - brightest near + EV_LightTurnOn(line,0); + break; + + case 81: + // Light Turn On 255 + EV_LightTurnOn(line,255); + break; + + case 82: + // Lower Floor To Lowest + EV_DoFloor( line, lowerFloorToLowest ); + break; + + case 83: + // Lower Floor + EV_DoFloor(line,lowerFloor); + break; + + case 84: + // LowerAndChange + EV_DoFloor(line,lowerAndChange); + break; + + case 86: + // Open Door + EV_DoDoor(line,vld_open); + break; + + case 87: + // Perpetual Platform Raise + EV_DoPlat(line,perpetualRaise,0); + break; + + case 88: + // PlatDownWaitUp + EV_DoPlat(line,downWaitUpStay,0); + break; + + case 89: + // Platform Stop + EV_StopPlat(line); + break; + + case 90: + // Raise Door + EV_DoDoor(line,vld_normal); + break; + + case 91: + // Raise Floor + EV_DoFloor(line,raiseFloor); + break; + + case 92: + // Raise Floor 24 + EV_DoFloor(line,raiseFloor24); + break; + + case 93: + // Raise Floor 24 And Change + EV_DoFloor(line,raiseFloor24AndChange); + break; + + case 94: + // Raise Floor Crush + EV_DoFloor(line,raiseFloorCrush); + break; + + case 95: + // Raise floor to nearest height + // and change texture. + EV_DoPlat(line,raiseToNearestAndChange,0); + break; + + case 96: + // Raise floor to shortest texture height + // on either side of lines. + EV_DoFloor(line,raiseToTexture); + break; + + case 97: + // TELEPORT! + EV_Teleport( line, side, thing ); + break; + + case 98: + // Lower Floor (TURBO) + EV_DoFloor(line,turboLower); + break; + + case 105: + // Blazing Door Raise (faster than TURBO!) + EV_DoDoor (line,vld_blazeRaise); + break; + + case 106: + // Blazing Door Open (faster than TURBO!) + EV_DoDoor (line,vld_blazeOpen); + break; + + case 107: + // Blazing Door Close (faster than TURBO!) + EV_DoDoor (line,vld_blazeClose); + break; + + case 120: + // Blazing PlatDownWaitUpStay. + EV_DoPlat(line,blazeDWUS,0); + break; + + case 126: + // TELEPORT MonsterONLY. + if (!thing->player) + EV_Teleport( line, side, thing ); + break; + + case 128: + // Raise To Nearest Floor + EV_DoFloor(line,raiseFloorToNearest); + break; + + case 129: + // Raise Floor Turbo + EV_DoFloor(line,raiseFloorTurbo); + break; + } +} + + + +// +// P_ShootSpecialLine - IMPACT SPECIALS +// Called when a thing shoots a special line. +// +void +P_ShootSpecialLine +( mobj_t* thing, + line_t* line ) +{ + int ok; + + // Impacts that other things can activate. + if (!thing->player) + { + ok = 0; + switch(line->special) + { + case 46: + // OPEN DOOR IMPACT + ok = 1; + break; + } + if (!ok) + return; + } + + switch(line->special) + { + case 24: + // RAISE FLOOR + EV_DoFloor(line,raiseFloor); + P_ChangeSwitchTexture(line,0); + break; + + case 46: + // OPEN DOOR + EV_DoDoor(line,vld_open); + P_ChangeSwitchTexture(line,1); + break; + + case 47: + // RAISE FLOOR NEAR AND CHANGE + EV_DoPlat(line,raiseToNearestAndChange,0); + P_ChangeSwitchTexture(line,0); + break; + } +} + + + +// +// P_PlayerInSpecialSector +// Called every tic frame +// that the player origin is in a special sector +// +void P_PlayerInSpecialSector (player_t* player) +{ + sector_t* sector; + + sector = player->mo->subsector->sector; + + // Falling, not all the way down yet? + if (player->mo->z != sector->floorheight) + return; + + // Has hitten ground. + switch (sector->special) + { + case 5: + // HELLSLIME DAMAGE + if (!player->powers[pw_ironfeet]) + if (!(leveltime&0x1f)) + P_DamageMobj (player->mo, NULL, NULL, 10); + break; + + case 7: + // NUKAGE DAMAGE + if (!player->powers[pw_ironfeet]) + if (!(leveltime&0x1f)) + P_DamageMobj (player->mo, NULL, NULL, 5); + break; + + case 16: + // SUPER HELLSLIME DAMAGE + case 4: + // STROBE HURT + if (!player->powers[pw_ironfeet] + || (P_Random()<5) ) + { + if (!(leveltime&0x1f)) + P_DamageMobj (player->mo, NULL, NULL, 20); + } + break; + + case 9: + // SECRET SECTOR + player->secretcount++; + sector->special = 0; + break; + + case 11: + // EXIT SUPER DAMAGE! (for E1M8 finale) + player->cheats &= ~CF_GODMODE; + + if (!(leveltime&0x1f)) + P_DamageMobj (player->mo, NULL, NULL, 20); + + if (player->health <= 10) + G_ExitLevel(); + break; + + default: + I_Error ("P_PlayerInSpecialSector: " + "unknown special %i", + sector->special); + break; + }; +} + + + + +// +// P_UpdateSpecials +// Animate planes, scroll walls, etc. +// +boolean levelTimer; +int levelTimeCount; + +void P_UpdateSpecials (void) +{ + anim_t* anim; + int pic; + int i; + line_t* line; + + + // LEVEL TIMER + if (levelTimer == true) + { + levelTimeCount--; + if (!levelTimeCount) + G_ExitLevel(); + } + + // ANIMATE FLATS AND TEXTURES GLOBALLY + for (anim = anims ; anim < lastanim ; anim++) + { + for (i=anim->basepic ; ibasepic+anim->numpics ; i++) + { + pic = anim->basepic + ( (leveltime/anim->speed + i)%anim->numpics ); + if (anim->istexture) + texturetranslation[i] = pic; + else + flattranslation[i] = pic; + } + } + + + // ANIMATE LINE SPECIALS + for (i = 0; i < numlinespecials; i++) + { + line = linespeciallist[i]; + switch(line->special) + { + case 48: + // EFFECT FIRSTCOL SCROLL + + sides[line->sidenum[0]].textureoffset += FRACUNIT; + break; + } + } + + + // DO BUTTONS + for (i = 0; i < MAXBUTTONS; i++) + if (buttonlist[i].btimer) + { + buttonlist[i].btimer--; + if (!buttonlist[i].btimer) + { + switch(buttonlist[i].where) + { + case top: + sides[buttonlist[i].line->sidenum[0]].toptexture = + buttonlist[i].btexture; + break; + + case middle: + sides[buttonlist[i].line->sidenum[0]].midtexture = + buttonlist[i].btexture; + break; + + case bottom: + sides[buttonlist[i].line->sidenum[0]].bottomtexture = + buttonlist[i].btexture; + break; + } + S_StartSound(&buttonlist[i].soundorg,sfx_swtchn); + memset(&buttonlist[i],0,sizeof(button_t)); + } + } +} + + +// +// Donut overrun emulation +// +// Derived from the code from PrBoom+. Thanks go to Andrey Budko (entryway) +// as usual :-) +// + +#define DONUT_FLOORHEIGHT_DEFAULT 0x00000000 +#define DONUT_FLOORPIC_DEFAULT 0x16 + +static void DonutOverrun(fixed_t *s3_floorheight, short *s3_floorpic, + line_t *line, sector_t *pillar_sector) +{ + static int first = 1; + static int tmp_s3_floorheight; + static int tmp_s3_floorpic; + + extern int numflats; + + if (first) + { + int p; + + // This is the first time we have had an overrun. + first = 0; + + // Default values + tmp_s3_floorheight = DONUT_FLOORHEIGHT_DEFAULT; + tmp_s3_floorpic = DONUT_FLOORPIC_DEFAULT; + + //! + // @category compat + // @arg + // + // Use the specified magic values when emulating behavior caused + // by memory overruns from improperly constructed donuts. + // In Vanilla Doom this can differ depending on the operating + // system. The default (if this option is not specified) is to + // emulate the behavior when running under Windows 98. + + p = M_CheckParmWithArgs("-donut", 2); + + if (p > 0) + { + // Dump of needed memory: (fixed_t)0000:0000 and (short)0000:0008 + // + // C:\>debug + // -d 0:0 + // + // DOS 6.22: + // 0000:0000 (57 92 19 00) F4 06 70 00-(16 00) + // DOS 7.1: + // 0000:0000 (9E 0F C9 00) 65 04 70 00-(16 00) + // Win98: + // 0000:0000 (00 00 00 00) 65 04 70 00-(16 00) + // DOSBox under XP: + // 0000:0000 (00 00 00 F1) ?? ?? ?? 00-(07 00) + + M_StrToInt(myargv[p + 1], &tmp_s3_floorheight); + M_StrToInt(myargv[p + 2], &tmp_s3_floorpic); + + if (tmp_s3_floorpic >= numflats) + { + fprintf(stderr, + "DonutOverrun: The second parameter for \"-donut\" " + "switch should be greater than 0 and less than number " + "of flats (%d). Using default value (%d) instead. \n", + numflats, DONUT_FLOORPIC_DEFAULT); + tmp_s3_floorpic = DONUT_FLOORPIC_DEFAULT; + } + } + } + + /* + fprintf(stderr, + "Linedef: %d; Sector: %d; " + "New floor height: %d; New floor pic: %d\n", + line->iLineID, pillar_sector->iSectorID, + tmp_s3_floorheight >> 16, tmp_s3_floorpic); + */ + + *s3_floorheight = (fixed_t) tmp_s3_floorheight; + *s3_floorpic = (short) tmp_s3_floorpic; +} + + +// +// Special Stuff that can not be categorized +// +int EV_DoDonut(line_t* line) +{ + sector_t* s1; + sector_t* s2; + sector_t* s3; + int secnum; + int rtn; + int i; + floormove_t* floor; + fixed_t s3_floorheight; + short s3_floorpic; + + secnum = -1; + rtn = 0; + while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0) + { + s1 = §ors[secnum]; + + // ALREADY MOVING? IF SO, KEEP GOING... + if (s1->specialdata) + continue; + + rtn = 1; + s2 = getNextSector(s1->lines[0],s1); + + // Vanilla Doom does not check if the linedef is one sided. The + // game does not crash, but reads invalid memory and causes the + // sector floor to move "down" to some unknown height. + // DOSbox prints a warning about an invalid memory access. + // + // I'm not sure exactly what invalid memory is being read. This + // isn't something that should be done, anyway. + // Just print a warning and return. + + if (s2 == NULL) + { + fprintf(stderr, + "EV_DoDonut: linedef had no second sidedef! " + "Unexpected behavior may occur in Vanilla Doom. \n"); + break; + } + + for (i = 0; i < s2->linecount; i++) + { + s3 = s2->lines[i]->backsector; + + if (s3 == s1) + continue; + + if (s3 == NULL) + { + // e6y + // s3 is NULL, so + // s3->floorheight is an int at 0000:0000 + // s3->floorpic is a short at 0000:0008 + // Trying to emulate + + fprintf(stderr, + "EV_DoDonut: WARNING: emulating buffer overrun due to " + "NULL back sector. " + "Unexpected behavior may occur in Vanilla Doom.\n"); + + DonutOverrun(&s3_floorheight, &s3_floorpic, line, s1); + } + else + { + s3_floorheight = s3->floorheight; + s3_floorpic = s3->floorpic; + } + + // Spawn rising slime + floor = Z_Malloc (sizeof(*floor), PU_LEVSPEC, 0); + P_AddThinker (&floor->thinker); + s2->specialdata = floor; + floor->thinker.function.acp1 = (actionf_p1) T_MoveFloor; + floor->type = donutRaise; + floor->crush = false; + floor->direction = 1; + floor->sector = s2; + floor->speed = FLOORSPEED / 2; + floor->texture = s3_floorpic; + floor->newspecial = 0; + floor->floordestheight = s3_floorheight; + + // Spawn lowering donut-hole + floor = Z_Malloc (sizeof(*floor), PU_LEVSPEC, 0); + P_AddThinker (&floor->thinker); + s1->specialdata = floor; + floor->thinker.function.acp1 = (actionf_p1) T_MoveFloor; + floor->type = lowerFloor; + floor->crush = false; + floor->direction = -1; + floor->sector = s1; + floor->speed = FLOORSPEED / 2; + floor->floordestheight = s3_floorheight; + break; + } + } + return rtn; +} + + + +// +// SPECIAL SPAWNING +// + +// +// P_SpawnSpecials +// After the map has been loaded, scan for specials +// that spawn thinkers +// +short numlinespecials; +line_t* linespeciallist[MAXLINEANIMS]; + + +// Parses command line parameters. +void P_SpawnSpecials (void) +{ + sector_t* sector; + int i; + + // See if -TIMER was specified. + + if (timelimit > 0 && deathmatch) + { + levelTimer = true; + levelTimeCount = timelimit * 60 * TICRATE; + } + else + { + levelTimer = false; + } + + // Init special SECTORs. + sector = sectors; + for (i=0 ; ispecial) + continue; + + switch (sector->special) + { + case 1: + // FLICKERING LIGHTS + P_SpawnLightFlash (sector); + break; + + case 2: + // STROBE FAST + P_SpawnStrobeFlash(sector,FASTDARK,0); + break; + + case 3: + // STROBE SLOW + P_SpawnStrobeFlash(sector,SLOWDARK,0); + break; + + case 4: + // STROBE FAST/DEATH SLIME + P_SpawnStrobeFlash(sector,FASTDARK,0); + sector->special = 4; + break; + + case 8: + // GLOWING LIGHT + P_SpawnGlowingLight(sector); + break; + case 9: + // SECRET SECTOR + totalsecret++; + break; + + case 10: + // DOOR CLOSE IN 30 SECONDS + P_SpawnDoorCloseIn30 (sector); + break; + + case 12: + // SYNC STROBE SLOW + P_SpawnStrobeFlash (sector, SLOWDARK, 1); + break; + + case 13: + // SYNC STROBE FAST + P_SpawnStrobeFlash (sector, FASTDARK, 1); + break; + + case 14: + // DOOR RAISE IN 5 MINUTES + P_SpawnDoorRaiseIn5Mins (sector, i); + break; + + case 17: + P_SpawnFireFlicker(sector); + break; + } + } + + + // Init line EFFECTs + numlinespecials = 0; + for (i = 0;i < numlines; i++) + { + switch(lines[i].special) + { + case 48: + if (numlinespecials >= MAXLINEANIMS) + { + I_Error("Too many scrolling wall linedefs! " + "(Vanilla limit is 64)"); + } + // EFFECT FIRSTCOL SCROLL+ + linespeciallist[numlinespecials] = &lines[i]; + numlinespecials++; + break; + } + } + + + // Init other misc stuff + for (i = 0;i < MAXCEILINGS;i++) + activeceilings[i] = NULL; + + for (i = 0;i < MAXPLATS;i++) + activeplats[i] = NULL; + + for (i = 0;i < MAXBUTTONS;i++) + memset(&buttonlist[i],0,sizeof(button_t)); + + // UNUSED: no horizonal sliders. + // P_InitSlidingDoorFrames(); +} diff --git a/firmware_p4/components/Applications/doom/p_spec.h b/firmware_p4/components/Applications/doom/p_spec.h new file mode 100644 index 000000000..a1343bfa3 --- /dev/null +++ b/firmware_p4/components/Applications/doom/p_spec.h @@ -0,0 +1,637 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: none +// Implements special effects: +// Texture animation, height or lighting changes +// according to adjacent sectors, respective +// utility functions, etc. +// + + +#ifndef __P_SPEC__ +#define __P_SPEC__ + + +// +// End-level timer (-TIMER option) +// +extern boolean levelTimer; +extern int levelTimeCount; + + +// Define values for map objects +#define MO_TELEPORTMAN 14 + + +// at game start +void P_InitPicAnims (void); + +// at map load +void P_SpawnSpecials (void); + +// every tic +void P_UpdateSpecials (void); + +// when needed +boolean +P_UseSpecialLine +( mobj_t* thing, + line_t* line, + int side ); + +void +P_ShootSpecialLine +( mobj_t* thing, + line_t* line ); + +void +P_CrossSpecialLine +( int linenum, + int side, + mobj_t* thing ); + +void P_PlayerInSpecialSector (player_t* player); + +int +twoSided +( int sector, + int line ); + +sector_t* +getSector +( int currentSector, + int line, + int side ); + +side_t* +getSide +( int currentSector, + int line, + int side ); + +fixed_t P_FindLowestFloorSurrounding(sector_t* sec); +fixed_t P_FindHighestFloorSurrounding(sector_t* sec); + +fixed_t +P_FindNextHighestFloor +( sector_t* sec, + int currentheight ); + +fixed_t P_FindLowestCeilingSurrounding(sector_t* sec); +fixed_t P_FindHighestCeilingSurrounding(sector_t* sec); + +int +P_FindSectorFromLineTag +( line_t* line, + int start ); + +int +P_FindMinSurroundingLight +( sector_t* sector, + int max ); + +sector_t* +getNextSector +( line_t* line, + sector_t* sec ); + + +// +// SPECIAL +// +int EV_DoDonut(line_t* line); + + + +// +// P_LIGHTS +// +typedef struct +{ + thinker_t thinker; + sector_t* sector; + int count; + int maxlight; + int minlight; + +} fireflicker_t; + + + +typedef struct +{ + thinker_t thinker; + sector_t* sector; + int count; + int maxlight; + int minlight; + int maxtime; + int mintime; + +} lightflash_t; + + + +typedef struct +{ + thinker_t thinker; + sector_t* sector; + int count; + int minlight; + int maxlight; + int darktime; + int brighttime; + +} strobe_t; + + + + +typedef struct +{ + thinker_t thinker; + sector_t* sector; + int minlight; + int maxlight; + int direction; + +} glow_t; + + +#define GLOWSPEED 8 +#define STROBEBRIGHT 5 +#define FASTDARK 15 +#define SLOWDARK 35 + +void P_SpawnFireFlicker (sector_t* sector); +void T_LightFlash (lightflash_t* flash); +void P_SpawnLightFlash (sector_t* sector); +void T_StrobeFlash (strobe_t* flash); + +void +P_SpawnStrobeFlash +( sector_t* sector, + int fastOrSlow, + int inSync ); + +void EV_StartLightStrobing(line_t* line); +void EV_TurnTagLightsOff(line_t* line); + +void +EV_LightTurnOn +( line_t* line, + int bright ); + +void T_Glow(glow_t* g); +void P_SpawnGlowingLight(sector_t* sector); + + + + +// +// P_SWITCH +// +typedef struct +{ + char name1[9]; + char name2[9]; + short episode; + +} switchlist_t; + + +typedef enum +{ + top, + middle, + bottom + +} bwhere_e; + + +typedef struct +{ + line_t* line; + bwhere_e where; + int btexture; + int btimer; + degenmobj_t *soundorg; + +} button_t; + + + + + // max # of wall switches in a level +#define MAXSWITCHES 50 + + // 4 players, 4 buttons each at once, max. +#define MAXBUTTONS 16 + + // 1 second, in ticks. +#define BUTTONTIME 35 + +extern button_t buttonlist[MAXBUTTONS]; + +void +P_ChangeSwitchTexture +( line_t* line, + int useAgain ); + +void P_InitSwitchList(void); + + +// +// P_PLATS +// +typedef enum +{ + up, + down, + waiting, + in_stasis + +} plat_e; + + + +typedef enum +{ + perpetualRaise, + downWaitUpStay, + raiseAndChange, + raiseToNearestAndChange, + blazeDWUS + +} plattype_e; + + + +typedef struct +{ + thinker_t thinker; + sector_t* sector; + fixed_t speed; + fixed_t low; + fixed_t high; + int wait; + int count; + plat_e status; + plat_e oldstatus; + boolean crush; + int tag; + plattype_e type; + +} plat_t; + + + +#define PLATWAIT 3 +#define PLATSPEED FRACUNIT +#define MAXPLATS 30 + + +extern plat_t* activeplats[MAXPLATS]; + +void T_PlatRaise(plat_t* plat); + +int +EV_DoPlat +( line_t* line, + plattype_e type, + int amount ); + +void P_AddActivePlat(plat_t* plat); +void P_RemoveActivePlat(plat_t* plat); +void EV_StopPlat(line_t* line); +void P_ActivateInStasis(int tag); + + +// +// P_DOORS +// +typedef enum +{ + vld_normal, + vld_close30ThenOpen, + vld_close, + vld_open, + vld_raiseIn5Mins, + vld_blazeRaise, + vld_blazeOpen, + vld_blazeClose + +} vldoor_e; + + + +typedef struct +{ + thinker_t thinker; + vldoor_e type; + sector_t* sector; + fixed_t topheight; + fixed_t speed; + + // 1 = up, 0 = waiting at top, -1 = down + int direction; + + // tics to wait at the top + int topwait; + // (keep in case a door going down is reset) + // when it reaches 0, start going down + int topcountdown; + +} vldoor_t; + + + +#define VDOORSPEED FRACUNIT*2 +#define VDOORWAIT 150 + +void +EV_VerticalDoor +( line_t* line, + mobj_t* thing ); + +int +EV_DoDoor +( line_t* line, + vldoor_e type ); + +int +EV_DoLockedDoor +( line_t* line, + vldoor_e type, + mobj_t* thing ); + +void T_VerticalDoor (vldoor_t* door); +void P_SpawnDoorCloseIn30 (sector_t* sec); + +void +P_SpawnDoorRaiseIn5Mins +( sector_t* sec, + int secnum ); + + + +#if 0 // UNUSED +// +// Sliding doors... +// +typedef enum +{ + sd_opening, + sd_waiting, + sd_closing + +} sd_e; + + + +typedef enum +{ + sdt_openOnly, + sdt_closeOnly, + sdt_openAndClose + +} sdt_e; + + + + +typedef struct +{ + thinker_t thinker; + sdt_e type; + line_t* line; + int frame; + int whichDoorIndex; + int timer; + sector_t* frontsector; + sector_t* backsector; + sd_e status; + +} slidedoor_t; + + + +typedef struct +{ + char frontFrame1[9]; + char frontFrame2[9]; + char frontFrame3[9]; + char frontFrame4[9]; + char backFrame1[9]; + char backFrame2[9]; + char backFrame3[9]; + char backFrame4[9]; + +} slidename_t; + + + +typedef struct +{ + int frontFrames[4]; + int backFrames[4]; + +} slideframe_t; + + + +// how many frames of animation +#define SNUMFRAMES 4 + +#define SDOORWAIT 35*3 +#define SWAITTICS 4 + +// how many diff. types of anims +#define MAXSLIDEDOORS 5 + +void P_InitSlidingDoorFrames(void); + +void +EV_SlidingDoor +( line_t* line, + mobj_t* thing ); +#endif + + + +// +// P_CEILNG +// +typedef enum +{ + lowerToFloor, + raiseToHighest, + lowerAndCrush, + crushAndRaise, + fastCrushAndRaise, + silentCrushAndRaise + +} ceiling_e; + + + +typedef struct +{ + thinker_t thinker; + ceiling_e type; + sector_t* sector; + fixed_t bottomheight; + fixed_t topheight; + fixed_t speed; + boolean crush; + + // 1 = up, 0 = waiting, -1 = down + int direction; + + // ID + int tag; + int olddirection; + +} ceiling_t; + + + + + +#define CEILSPEED FRACUNIT +#define CEILWAIT 150 +#define MAXCEILINGS 30 + +extern ceiling_t* activeceilings[MAXCEILINGS]; + +int +EV_DoCeiling +( line_t* line, + ceiling_e type ); + +void T_MoveCeiling (ceiling_t* ceiling); +void P_AddActiveCeiling(ceiling_t* c); +void P_RemoveActiveCeiling(ceiling_t* c); +int EV_CeilingCrushStop(line_t* line); +void P_ActivateInStasisCeiling(line_t* line); + + +// +// P_FLOOR +// +typedef enum +{ + // lower floor to highest surrounding floor + lowerFloor, + + // lower floor to lowest surrounding floor + lowerFloorToLowest, + + // lower floor to highest surrounding floor VERY FAST + turboLower, + + // raise floor to lowest surrounding CEILING + raiseFloor, + + // raise floor to next highest surrounding floor + raiseFloorToNearest, + + // raise floor to shortest height texture around it + raiseToTexture, + + // lower floor to lowest surrounding floor + // and change floorpic + lowerAndChange, + + raiseFloor24, + raiseFloor24AndChange, + raiseFloorCrush, + + // raise to next highest floor, turbo-speed + raiseFloorTurbo, + donutRaise, + raiseFloor512 + +} floor_e; + + + + +typedef enum +{ + build8, // slowly build by 8 + turbo16 // quickly build by 16 + +} stair_e; + + + +typedef struct +{ + thinker_t thinker; + floor_e type; + boolean crush; + sector_t* sector; + int direction; + int newspecial; + short texture; + fixed_t floordestheight; + fixed_t speed; + +} floormove_t; + + + +#define FLOORSPEED FRACUNIT + +typedef enum +{ + ok, + crushed, + pastdest + +} result_e; + +result_e +T_MovePlane +( sector_t* sector, + fixed_t speed, + fixed_t dest, + boolean crush, + int floorOrCeiling, + int direction ); + +int +EV_BuildStairs +( line_t* line, + stair_e type ); + +int +EV_DoFloor +( line_t* line, + floor_e floortype ); + +void T_MoveFloor( floormove_t* floor); + +// +// P_TELEPT +// +int +EV_Teleport +( line_t* line, + int side, + mobj_t* thing ); + +#endif diff --git a/firmware_p4/components/Applications/doom/p_switch.c b/firmware_p4/components/Applications/doom/p_switch.c new file mode 100644 index 000000000..ed4feeca0 --- /dev/null +++ b/firmware_p4/components/Applications/doom/p_switch.c @@ -0,0 +1,648 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// +// DESCRIPTION: +// Switches, buttons. Two-state animation. Exits. +// + +#include + +#include "i_system.h" +#include "deh_main.h" +#include "doomdef.h" +#include "p_local.h" + +#include "g_game.h" + +#include "s_sound.h" + +// Data. +#include "sounds.h" + +// State. +#include "doomstat.h" +#include "r_state.h" + + +// +// CHANGE THE TEXTURE OF A WALL SWITCH TO ITS OPPOSITE +// +switchlist_t alphSwitchList[] = +{ + // Doom shareware episode 1 switches + {"SW1BRCOM", "SW2BRCOM", 1}, + {"SW1BRN1", "SW2BRN1", 1}, + {"SW1BRN2", "SW2BRN2", 1}, + {"SW1BRNGN", "SW2BRNGN", 1}, + {"SW1BROWN", "SW2BROWN", 1}, + {"SW1COMM", "SW2COMM", 1}, + {"SW1COMP", "SW2COMP", 1}, + {"SW1DIRT", "SW2DIRT", 1}, + {"SW1EXIT", "SW2EXIT", 1}, + {"SW1GRAY", "SW2GRAY", 1}, + {"SW1GRAY1", "SW2GRAY1", 1}, + {"SW1METAL", "SW2METAL", 1}, + {"SW1PIPE", "SW2PIPE", 1}, + {"SW1SLAD", "SW2SLAD", 1}, + {"SW1STARG", "SW2STARG", 1}, + {"SW1STON1", "SW2STON1", 1}, + {"SW1STON2", "SW2STON2", 1}, + {"SW1STONE", "SW2STONE", 1}, + {"SW1STRTN", "SW2STRTN", 1}, + + // Doom registered episodes 2&3 switches + {"SW1BLUE", "SW2BLUE", 2}, + {"SW1CMT", "SW2CMT", 2}, + {"SW1GARG", "SW2GARG", 2}, + {"SW1GSTON", "SW2GSTON", 2}, + {"SW1HOT", "SW2HOT", 2}, + {"SW1LION", "SW2LION", 2}, + {"SW1SATYR", "SW2SATYR", 2}, + {"SW1SKIN", "SW2SKIN", 2}, + {"SW1VINE", "SW2VINE", 2}, + {"SW1WOOD", "SW2WOOD", 2}, + + // Doom II switches + {"SW1PANEL", "SW2PANEL", 3}, + {"SW1ROCK", "SW2ROCK", 3}, + {"SW1MET2", "SW2MET2", 3}, + {"SW1WDMET", "SW2WDMET", 3}, + {"SW1BRIK", "SW2BRIK", 3}, + {"SW1MOD1", "SW2MOD1", 3}, + {"SW1ZIM", "SW2ZIM", 3}, + {"SW1STON6", "SW2STON6", 3}, + {"SW1TEK", "SW2TEK", 3}, + {"SW1MARB", "SW2MARB", 3}, + {"SW1SKULL", "SW2SKULL", 3}, + + {"\0", "\0", 0} +}; + +int switchlist[MAXSWITCHES * 2]; +int numswitches; +button_t buttonlist[MAXBUTTONS]; + +// +// P_InitSwitchList +// Only called at game initialization. +// +void P_InitSwitchList(void) +{ + int i; + int index; + int episode; + + episode = 1; + + if (gamemode == registered || gamemode == retail) + episode = 2; + else + if ( gamemode == commercial ) + episode = 3; + + for (index = 0,i = 0;i < MAXSWITCHES;i++) + { + if (!alphSwitchList[i].episode) + { + numswitches = index/2; + switchlist[index] = -1; + break; + } + + if (alphSwitchList[i].episode <= episode) + { +#if 0 // UNUSED - debug? + int value; + + if (R_CheckTextureNumForName(alphSwitchList[i].name1) < 0) + { + I_Error("Can't find switch texture '%s'!", + alphSwitchList[i].name1); + continue; + } + + value = R_TextureNumForName(alphSwitchList[i].name1); +#endif + switchlist[index++] = R_TextureNumForName(DEH_String(alphSwitchList[i].name1)); + switchlist[index++] = R_TextureNumForName(DEH_String(alphSwitchList[i].name2)); + } + } +} + + +// +// Start a button counting down till it turns off. +// +void +P_StartButton +( line_t* line, + bwhere_e w, + int texture, + int time ) +{ + int i; + + // See if button is already pressed + for (i = 0;i < MAXBUTTONS;i++) + { + if (buttonlist[i].btimer + && buttonlist[i].line == line) + { + + return; + } + } + + + + for (i = 0;i < MAXBUTTONS;i++) + { + if (!buttonlist[i].btimer) + { + buttonlist[i].line = line; + buttonlist[i].where = w; + buttonlist[i].btexture = texture; + buttonlist[i].btimer = time; + buttonlist[i].soundorg = &line->frontsector->soundorg; + return; + } + } + + I_Error("P_StartButton: no button slots left!"); +} + + + + + +// +// Function that changes wall texture. +// Tell it if switch is ok to use again (1=yes, it's a button). +// +void +P_ChangeSwitchTexture +( line_t* line, + int useAgain ) +{ + int texTop; + int texMid; + int texBot; + int i; + int sound; + + if (!useAgain) + line->special = 0; + + texTop = sides[line->sidenum[0]].toptexture; + texMid = sides[line->sidenum[0]].midtexture; + texBot = sides[line->sidenum[0]].bottomtexture; + + sound = sfx_swtchn; + + // EXIT SWITCH? + if (line->special == 11) + sound = sfx_swtchx; + + for (i = 0;i < numswitches*2;i++) + { + if (switchlist[i] == texTop) + { + S_StartSound(buttonlist->soundorg,sound); + sides[line->sidenum[0]].toptexture = switchlist[i^1]; + + if (useAgain) + P_StartButton(line,top,switchlist[i],BUTTONTIME); + + return; + } + else + { + if (switchlist[i] == texMid) + { + S_StartSound(buttonlist->soundorg,sound); + sides[line->sidenum[0]].midtexture = switchlist[i^1]; + + if (useAgain) + P_StartButton(line, middle,switchlist[i],BUTTONTIME); + + return; + } + else + { + if (switchlist[i] == texBot) + { + S_StartSound(buttonlist->soundorg,sound); + sides[line->sidenum[0]].bottomtexture = switchlist[i^1]; + + if (useAgain) + P_StartButton(line, bottom,switchlist[i],BUTTONTIME); + + return; + } + } + } + } +} + + + + + + +// +// P_UseSpecialLine +// Called when a thing uses a special line. +// Only the front sides of lines are usable. +// +boolean +P_UseSpecialLine +( mobj_t* thing, + line_t* line, + int side ) +{ + + // Err... + // Use the back sides of VERY SPECIAL lines... + if (side) + { + switch(line->special) + { + case 124: + // Sliding door open&close + // UNUSED? + break; + + default: + return false; + break; + } + } + + + // Switches that other things can activate. + if (!thing->player) + { + // never open secret doors + if (line->flags & ML_SECRET) + return false; + + switch(line->special) + { + case 1: // MANUAL DOOR RAISE + case 32: // MANUAL BLUE + case 33: // MANUAL RED + case 34: // MANUAL YELLOW + break; + + default: + return false; + break; + } + } + + + // do something + switch (line->special) + { + // MANUALS + case 1: // Vertical Door + case 26: // Blue Door/Locked + case 27: // Yellow Door /Locked + case 28: // Red Door /Locked + + case 31: // Manual door open + case 32: // Blue locked door open + case 33: // Red locked door open + case 34: // Yellow locked door open + + case 117: // Blazing door raise + case 118: // Blazing door open + EV_VerticalDoor (line, thing); + break; + + //UNUSED - Door Slide Open&Close + // case 124: + // EV_SlidingDoor (line, thing); + // break; + + // SWITCHES + case 7: + // Build Stairs + if (EV_BuildStairs(line,build8)) + P_ChangeSwitchTexture(line,0); + break; + + case 9: + // Change Donut + if (EV_DoDonut(line)) + P_ChangeSwitchTexture(line,0); + break; + + case 11: + // Exit level + P_ChangeSwitchTexture(line,0); + G_ExitLevel (); + break; + + case 14: + // Raise Floor 32 and change texture + if (EV_DoPlat(line,raiseAndChange,32)) + P_ChangeSwitchTexture(line,0); + break; + + case 15: + // Raise Floor 24 and change texture + if (EV_DoPlat(line,raiseAndChange,24)) + P_ChangeSwitchTexture(line,0); + break; + + case 18: + // Raise Floor to next highest floor + if (EV_DoFloor(line, raiseFloorToNearest)) + P_ChangeSwitchTexture(line,0); + break; + + case 20: + // Raise Plat next highest floor and change texture + if (EV_DoPlat(line,raiseToNearestAndChange,0)) + P_ChangeSwitchTexture(line,0); + break; + + case 21: + // PlatDownWaitUpStay + if (EV_DoPlat(line,downWaitUpStay,0)) + P_ChangeSwitchTexture(line,0); + break; + + case 23: + // Lower Floor to Lowest + if (EV_DoFloor(line,lowerFloorToLowest)) + P_ChangeSwitchTexture(line,0); + break; + + case 29: + // Raise Door + if (EV_DoDoor(line,vld_normal)) + P_ChangeSwitchTexture(line,0); + break; + + case 41: + // Lower Ceiling to Floor + if (EV_DoCeiling(line,lowerToFloor)) + P_ChangeSwitchTexture(line,0); + break; + + case 71: + // Turbo Lower Floor + if (EV_DoFloor(line,turboLower)) + P_ChangeSwitchTexture(line,0); + break; + + case 49: + // Ceiling Crush And Raise + if (EV_DoCeiling(line,crushAndRaise)) + P_ChangeSwitchTexture(line,0); + break; + + case 50: + // Close Door + if (EV_DoDoor(line,vld_close)) + P_ChangeSwitchTexture(line,0); + break; + + case 51: + // Secret EXIT + P_ChangeSwitchTexture(line,0); + G_SecretExitLevel (); + break; + + case 55: + // Raise Floor Crush + if (EV_DoFloor(line,raiseFloorCrush)) + P_ChangeSwitchTexture(line,0); + break; + + case 101: + // Raise Floor + if (EV_DoFloor(line,raiseFloor)) + P_ChangeSwitchTexture(line,0); + break; + + case 102: + // Lower Floor to Surrounding floor height + if (EV_DoFloor(line,lowerFloor)) + P_ChangeSwitchTexture(line,0); + break; + + case 103: + // Open Door + if (EV_DoDoor(line,vld_open)) + P_ChangeSwitchTexture(line,0); + break; + + case 111: + // Blazing Door Raise (faster than TURBO!) + if (EV_DoDoor (line,vld_blazeRaise)) + P_ChangeSwitchTexture(line,0); + break; + + case 112: + // Blazing Door Open (faster than TURBO!) + if (EV_DoDoor (line,vld_blazeOpen)) + P_ChangeSwitchTexture(line,0); + break; + + case 113: + // Blazing Door Close (faster than TURBO!) + if (EV_DoDoor (line,vld_blazeClose)) + P_ChangeSwitchTexture(line,0); + break; + + case 122: + // Blazing PlatDownWaitUpStay + if (EV_DoPlat(line,blazeDWUS,0)) + P_ChangeSwitchTexture(line,0); + break; + + case 127: + // Build Stairs Turbo 16 + if (EV_BuildStairs(line,turbo16)) + P_ChangeSwitchTexture(line,0); + break; + + case 131: + // Raise Floor Turbo + if (EV_DoFloor(line,raiseFloorTurbo)) + P_ChangeSwitchTexture(line,0); + break; + + case 133: + // BlzOpenDoor BLUE + case 135: + // BlzOpenDoor RED + case 137: + // BlzOpenDoor YELLOW + if (EV_DoLockedDoor (line,vld_blazeOpen,thing)) + P_ChangeSwitchTexture(line,0); + break; + + case 140: + // Raise Floor 512 + if (EV_DoFloor(line,raiseFloor512)) + P_ChangeSwitchTexture(line,0); + break; + + // BUTTONS + case 42: + // Close Door + if (EV_DoDoor(line,vld_close)) + P_ChangeSwitchTexture(line,1); + break; + + case 43: + // Lower Ceiling to Floor + if (EV_DoCeiling(line,lowerToFloor)) + P_ChangeSwitchTexture(line,1); + break; + + case 45: + // Lower Floor to Surrounding floor height + if (EV_DoFloor(line,lowerFloor)) + P_ChangeSwitchTexture(line,1); + break; + + case 60: + // Lower Floor to Lowest + if (EV_DoFloor(line,lowerFloorToLowest)) + P_ChangeSwitchTexture(line,1); + break; + + case 61: + // Open Door + if (EV_DoDoor(line,vld_open)) + P_ChangeSwitchTexture(line,1); + break; + + case 62: + // PlatDownWaitUpStay + if (EV_DoPlat(line,downWaitUpStay,1)) + P_ChangeSwitchTexture(line,1); + break; + + case 63: + // Raise Door + if (EV_DoDoor(line,vld_normal)) + P_ChangeSwitchTexture(line,1); + break; + + case 64: + // Raise Floor to ceiling + if (EV_DoFloor(line,raiseFloor)) + P_ChangeSwitchTexture(line,1); + break; + + case 66: + // Raise Floor 24 and change texture + if (EV_DoPlat(line,raiseAndChange,24)) + P_ChangeSwitchTexture(line,1); + break; + + case 67: + // Raise Floor 32 and change texture + if (EV_DoPlat(line,raiseAndChange,32)) + P_ChangeSwitchTexture(line,1); + break; + + case 65: + // Raise Floor Crush + if (EV_DoFloor(line,raiseFloorCrush)) + P_ChangeSwitchTexture(line,1); + break; + + case 68: + // Raise Plat to next highest floor and change texture + if (EV_DoPlat(line,raiseToNearestAndChange,0)) + P_ChangeSwitchTexture(line,1); + break; + + case 69: + // Raise Floor to next highest floor + if (EV_DoFloor(line, raiseFloorToNearest)) + P_ChangeSwitchTexture(line,1); + break; + + case 70: + // Turbo Lower Floor + if (EV_DoFloor(line,turboLower)) + P_ChangeSwitchTexture(line,1); + break; + + case 114: + // Blazing Door Raise (faster than TURBO!) + if (EV_DoDoor (line,vld_blazeRaise)) + P_ChangeSwitchTexture(line,1); + break; + + case 115: + // Blazing Door Open (faster than TURBO!) + if (EV_DoDoor (line,vld_blazeOpen)) + P_ChangeSwitchTexture(line,1); + break; + + case 116: + // Blazing Door Close (faster than TURBO!) + if (EV_DoDoor (line,vld_blazeClose)) + P_ChangeSwitchTexture(line,1); + break; + + case 123: + // Blazing PlatDownWaitUpStay + if (EV_DoPlat(line,blazeDWUS,0)) + P_ChangeSwitchTexture(line,1); + break; + + case 132: + // Raise Floor Turbo + if (EV_DoFloor(line,raiseFloorTurbo)) + P_ChangeSwitchTexture(line,1); + break; + + case 99: + // BlzOpenDoor BLUE + case 134: + // BlzOpenDoor RED + case 136: + // BlzOpenDoor YELLOW + if (EV_DoLockedDoor (line,vld_blazeOpen,thing)) + P_ChangeSwitchTexture(line,1); + break; + + case 138: + // Light Turn On + EV_LightTurnOn(line,255); + P_ChangeSwitchTexture(line,1); + break; + + case 139: + // Light Turn Off + EV_LightTurnOn(line,35); + P_ChangeSwitchTexture(line,1); + break; + + } + + return true; +} + diff --git a/firmware_p4/components/Applications/doom/p_telept.c b/firmware_p4/components/Applications/doom/p_telept.c new file mode 100644 index 000000000..45cdfb016 --- /dev/null +++ b/firmware_p4/components/Applications/doom/p_telept.c @@ -0,0 +1,133 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Teleportation. +// + + + + +#include "doomdef.h" +#include "doomstat.h" + +#include "s_sound.h" + +#include "p_local.h" + + +// Data. +#include "sounds.h" + +// State. +#include "r_state.h" + + + +// +// TELEPORTATION +// +int +EV_Teleport +( line_t* line, + int side, + mobj_t* thing ) +{ + int i; + int tag; + mobj_t* m; + mobj_t* fog; + unsigned an; + thinker_t* thinker; + sector_t* sector; + fixed_t oldx; + fixed_t oldy; + fixed_t oldz; + + // don't teleport missiles + if (thing->flags & MF_MISSILE) + return 0; + + // Don't teleport if hit back of line, + // so you can get out of teleporter. + if (side == 1) + return 0; + + + tag = line->tag; + for (i = 0; i < numsectors; i++) + { + if (sectors[ i ].tag == tag ) + { + thinker = thinkercap.next; + for (thinker = thinkercap.next; + thinker != &thinkercap; + thinker = thinker->next) + { + // not a mobj + if (thinker->function.acp1 != (actionf_p1)P_MobjThinker) + continue; + + m = (mobj_t *)thinker; + + // not a teleportman + if (m->type != MT_TELEPORTMAN ) + continue; + + sector = m->subsector->sector; + // wrong sector + if (sector-sectors != i ) + continue; + + oldx = thing->x; + oldy = thing->y; + oldz = thing->z; + + if (!P_TeleportMove (thing, m->x, m->y)) + return 0; + + // The first Final Doom executable does not set thing->z + // when teleporting. This quirk is unique to this + // particular version; the later version included in + // some versions of the Id Anthology fixed this. + + if (gameversion != exe_final) + thing->z = thing->floorz; + + if (thing->player) + thing->player->viewz = thing->z+thing->player->viewheight; + + // spawn teleport fog at source and destination + fog = P_SpawnMobj (oldx, oldy, oldz, MT_TFOG); + S_StartSound (fog, sfx_telept); + an = m->angle >> ANGLETOFINESHIFT; + fog = P_SpawnMobj (m->x+20*finecosine[an], m->y+20*finesine[an] + , thing->z, MT_TFOG); + + // emit sound, where? + S_StartSound (fog, sfx_telept); + + // don't move for a bit + if (thing->player) + thing->reactiontime = 18; + + thing->angle = m->angle; + thing->momx = thing->momy = thing->momz = 0; + return 1; + } + } + } + return 0; +} + diff --git a/firmware_p4/components/Applications/doom/p_tick.c b/firmware_p4/components/Applications/doom/p_tick.c new file mode 100644 index 000000000..228935001 --- /dev/null +++ b/firmware_p4/components/Applications/doom/p_tick.c @@ -0,0 +1,151 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Archiving: SaveGame I/O. +// Thinker, Ticker. +// + + +#include "z_zone.h" +#include "p_local.h" + +#include "doomstat.h" + + +int leveltime; + +// +// THINKERS +// All thinkers should be allocated by Z_Malloc +// so they can be operated on uniformly. +// The actual structures will vary in size, +// but the first element must be thinker_t. +// + + + +// Both the head and tail of the thinker list. +thinker_t thinkercap; + + +// +// P_InitThinkers +// +void P_InitThinkers (void) +{ + thinkercap.prev = thinkercap.next = &thinkercap; +} + + + + +// +// P_AddThinker +// Adds a new thinker at the end of the list. +// +void P_AddThinker (thinker_t* thinker) +{ + thinkercap.prev->next = thinker; + thinker->next = &thinkercap; + thinker->prev = thinkercap.prev; + thinkercap.prev = thinker; +} + + + +// +// P_RemoveThinker +// Deallocation is lazy -- it will not actually be freed +// until its thinking turn comes up. +// +void P_RemoveThinker (thinker_t* thinker) +{ + // FIXME: NOP. + thinker->function.acv = (actionf_v)(-1); +} + + + +// +// P_AllocateThinker +// Allocates memory and adds a new thinker at the end of the list. +// +void P_AllocateThinker (thinker_t* thinker) +{ +} + + + +// +// P_RunThinkers +// +void P_RunThinkers (void) +{ + thinker_t* currentthinker; + + currentthinker = thinkercap.next; + while (currentthinker != &thinkercap) + { + if ( currentthinker->function.acv == (actionf_v)(-1) ) + { + // time to remove it + currentthinker->next->prev = currentthinker->prev; + currentthinker->prev->next = currentthinker->next; + Z_Free (currentthinker); + } + else + { + if (currentthinker->function.acp1) + currentthinker->function.acp1 (currentthinker); + } + currentthinker = currentthinker->next; + } +} + + + +// +// P_Ticker +// + +void P_Ticker (void) +{ + int i; + + // run the tic + if (paused) + return; + + // pause if in menu and at least one tic has been run + if ( !netgame + && menuactive + && !demoplayback + && players[consoleplayer].viewz != 1) + { + return; + } + + + for (i=0 ; i>= ANGLETOFINESHIFT; + + player->mo->momx += FixedMul(move,finecosine[angle]); + player->mo->momy += FixedMul(move,finesine[angle]); +} + + + + +// +// P_CalcHeight +// Calculate the walking / running height adjustment +// +void P_CalcHeight (player_t* player) +{ + int angle; + fixed_t bob; + + // Regular movement bobbing + // (needs to be calculated for gun swing + // even if not on ground) + // OPTIMIZE: tablify angle + // Note: a LUT allows for effects + // like a ramp with low health. + player->bob = + FixedMul (player->mo->momx, player->mo->momx) + + FixedMul (player->mo->momy,player->mo->momy); + + player->bob >>= 2; + + if (player->bob>MAXBOB) + player->bob = MAXBOB; + + if ((player->cheats & CF_NOMOMENTUM) || !onground) + { + player->viewz = player->mo->z + VIEWHEIGHT; + + if (player->viewz > player->mo->ceilingz-4*FRACUNIT) + player->viewz = player->mo->ceilingz-4*FRACUNIT; + + player->viewz = player->mo->z + player->viewheight; + return; + } + + angle = (FINEANGLES/20*leveltime)&FINEMASK; + bob = FixedMul ( player->bob/2, finesine[angle]); + + + // move viewheight + if (player->playerstate == PST_LIVE) + { + player->viewheight += player->deltaviewheight; + + if (player->viewheight > VIEWHEIGHT) + { + player->viewheight = VIEWHEIGHT; + player->deltaviewheight = 0; + } + + if (player->viewheight < VIEWHEIGHT/2) + { + player->viewheight = VIEWHEIGHT/2; + if (player->deltaviewheight <= 0) + player->deltaviewheight = 1; + } + + if (player->deltaviewheight) + { + player->deltaviewheight += FRACUNIT/4; + if (!player->deltaviewheight) + player->deltaviewheight = 1; + } + } + player->viewz = player->mo->z + player->viewheight + bob; + + if (player->viewz > player->mo->ceilingz-4*FRACUNIT) + player->viewz = player->mo->ceilingz-4*FRACUNIT; +} + + + +// +// P_MovePlayer +// +void P_MovePlayer (player_t* player) +{ + ticcmd_t* cmd; + + cmd = &player->cmd; + + player->mo->angle += (cmd->angleturn<<16); + + // Do not let the player control movement + // if not onground. + onground = (player->mo->z <= player->mo->floorz); + + if (cmd->forwardmove && onground) + P_Thrust (player, player->mo->angle, cmd->forwardmove*2048); + + if (cmd->sidemove && onground) + P_Thrust (player, player->mo->angle-ANG90, cmd->sidemove*2048); + + if ( (cmd->forwardmove || cmd->sidemove) + && player->mo->state == &states[S_PLAY] ) + { + P_SetMobjState (player->mo, S_PLAY_RUN1); + } +} + + + +// +// P_DeathThink +// Fall on your face when dying. +// Decrease POV height to floor height. +// +#define ANG5 (ANG90/18) + +void P_DeathThink (player_t* player) +{ + angle_t angle; + angle_t delta; + + P_MovePsprites (player); + + // fall to the ground + if (player->viewheight > 6*FRACUNIT) + player->viewheight -= FRACUNIT; + + if (player->viewheight < 6*FRACUNIT) + player->viewheight = 6*FRACUNIT; + + player->deltaviewheight = 0; + onground = (player->mo->z <= player->mo->floorz); + P_CalcHeight (player); + + if (player->attacker && player->attacker != player->mo) + { + angle = R_PointToAngle2 (player->mo->x, + player->mo->y, + player->attacker->x, + player->attacker->y); + + delta = angle - player->mo->angle; + + if (delta < ANG5 || delta > (unsigned)-ANG5) + { + // Looking at killer, + // so fade damage flash down. + player->mo->angle = angle; + + if (player->damagecount) + player->damagecount--; + } + else if (delta < ANG180) + player->mo->angle += ANG5; + else + player->mo->angle -= ANG5; + } + else if (player->damagecount) + player->damagecount--; + + + if (player->cmd.buttons & BT_USE) + player->playerstate = PST_REBORN; +} + + + +// +// P_PlayerThink +// +void P_PlayerThink (player_t* player) +{ + ticcmd_t* cmd; + weapontype_t newweapon; + + // fixme: do this in the cheat code + if (player->cheats & CF_NOCLIP) + player->mo->flags |= MF_NOCLIP; + else + player->mo->flags &= ~MF_NOCLIP; + + // chain saw run forward + cmd = &player->cmd; + if (player->mo->flags & MF_JUSTATTACKED) + { + cmd->angleturn = 0; + cmd->forwardmove = 0xc800/512; + cmd->sidemove = 0; + player->mo->flags &= ~MF_JUSTATTACKED; + } + + + if (player->playerstate == PST_DEAD) + { + P_DeathThink (player); + return; + } + + // Move around. + // Reactiontime is used to prevent movement + // for a bit after a teleport. + if (player->mo->reactiontime) + player->mo->reactiontime--; + else + P_MovePlayer (player); + + P_CalcHeight (player); + + if (player->mo->subsector->sector->special) + P_PlayerInSpecialSector (player); + + // Check for weapon change. + + // A special event has no other buttons. + if (cmd->buttons & BT_SPECIAL) + cmd->buttons = 0; + + if (cmd->buttons & BT_CHANGE) + { + // The actual changing of the weapon is done + // when the weapon psprite can do it + // (read: not in the middle of an attack). + newweapon = (cmd->buttons&BT_WEAPONMASK)>>BT_WEAPONSHIFT; + + if (newweapon == wp_fist + && player->weaponowned[wp_chainsaw] + && !(player->readyweapon == wp_chainsaw + && player->powers[pw_strength])) + { + newweapon = wp_chainsaw; + } + + if ( (gamemode == commercial) + && newweapon == wp_shotgun + && player->weaponowned[wp_supershotgun] + && player->readyweapon != wp_supershotgun) + { + newweapon = wp_supershotgun; + } + + + if (player->weaponowned[newweapon] + && newweapon != player->readyweapon) + { + // Do not go to plasma or BFG in shareware, + // even if cheated. + if ((newweapon != wp_plasma + && newweapon != wp_bfg) + || (gamemode != shareware) ) + { + player->pendingweapon = newweapon; + } + } + } + + // check for use + if (cmd->buttons & BT_USE) + { + if (!player->usedown) + { + P_UseLines (player); + player->usedown = true; + } + } + else + player->usedown = false; + + // cycle psprites + P_MovePsprites (player); + + // Counters, time dependend power ups. + + // Strength counts up to diminish fade. + if (player->powers[pw_strength]) + player->powers[pw_strength]++; + + if (player->powers[pw_invulnerability]) + player->powers[pw_invulnerability]--; + + if (player->powers[pw_invisibility]) + if (! --player->powers[pw_invisibility] ) + player->mo->flags &= ~MF_SHADOW; + + if (player->powers[pw_infrared]) + player->powers[pw_infrared]--; + + if (player->powers[pw_ironfeet]) + player->powers[pw_ironfeet]--; + + if (player->damagecount) + player->damagecount--; + + if (player->bonuscount) + player->bonuscount--; + + + // Handling colormaps. + if (player->powers[pw_invulnerability]) + { + if (player->powers[pw_invulnerability] > 4*32 + || (player->powers[pw_invulnerability]&8) ) + player->fixedcolormap = INVERSECOLORMAP; + else + player->fixedcolormap = 0; + } + else if (player->powers[pw_infrared]) + { + if (player->powers[pw_infrared] > 4*32 + || (player->powers[pw_infrared]&8) ) + { + // almost full bright + player->fixedcolormap = 1; + } + else + player->fixedcolormap = 0; + } + else + player->fixedcolormap = 0; +} + + diff --git a/firmware_p4/components/Applications/doom/r_bsp.c b/firmware_p4/components/Applications/doom/r_bsp.c new file mode 100644 index 000000000..9a788122a --- /dev/null +++ b/firmware_p4/components/Applications/doom/r_bsp.c @@ -0,0 +1,573 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// BSP traversal, handling of LineSegs for rendering. +// + + + + +#include "doomdef.h" + +#include "m_bbox.h" + +#include "i_system.h" + +#include "r_main.h" +#include "r_plane.h" +#include "r_things.h" + +// State. +#include "doomstat.h" +#include "r_state.h" + +//#include "r_local.h" + + + +seg_t* curline; +side_t* sidedef; +line_t* linedef; +sector_t* frontsector; +sector_t* backsector; + +drawseg_t drawsegs[MAXDRAWSEGS]; +drawseg_t* ds_p; + + +void +R_StoreWallRange +( int start, + int stop ); + + + + +// +// R_ClearDrawSegs +// +void R_ClearDrawSegs (void) +{ + ds_p = drawsegs; +} + + + +// +// ClipWallSegment +// Clips the given range of columns +// and includes it in the new clip list. +// +typedef struct +{ + int first; + int last; + +} cliprange_t; + + +#define MAXSEGS 32 + +// newend is one past the last valid seg +cliprange_t* newend; +cliprange_t solidsegs[MAXSEGS]; + + + + +// +// R_ClipSolidWallSegment +// Does handle solid walls, +// e.g. single sided LineDefs (middle texture) +// that entirely block the view. +// +void +R_ClipSolidWallSegment +( int first, + int last ) +{ + cliprange_t* next; + cliprange_t* start; + + // Find the first range that touches the range + // (adjacent pixels are touching). + start = solidsegs; + while (start->last < first-1) + start++; + + if (first < start->first) + { + if (last < start->first-1) + { + // Post is entirely visible (above start), + // so insert a new clippost. + R_StoreWallRange (first, last); + next = newend; + newend++; + + while (next != start) + { + *next = *(next-1); + next--; + } + next->first = first; + next->last = last; + return; + } + + // There is a fragment above *start. + R_StoreWallRange (first, start->first - 1); + // Now adjust the clip size. + start->first = first; + } + + // Bottom contained in start? + if (last <= start->last) + return; + + next = start; + while (last >= (next+1)->first-1) + { + // There is a fragment between two posts. + R_StoreWallRange (next->last + 1, (next+1)->first - 1); + next++; + + if (last <= next->last) + { + // Bottom is contained in next. + // Adjust the clip size. + start->last = next->last; + goto crunch; + } + } + + // There is a fragment after *next. + R_StoreWallRange (next->last + 1, last); + // Adjust the clip size. + start->last = last; + + // Remove start+1 to next from the clip list, + // because start now covers their area. + crunch: + if (next == start) + { + // Post just extended past the bottom of one post. + return; + } + + + while (next++ != newend) + { + // Remove a post. + *++start = *next; + } + + newend = start+1; +} + + + +// +// R_ClipPassWallSegment +// Clips the given range of columns, +// but does not includes it in the clip list. +// Does handle windows, +// e.g. LineDefs with upper and lower texture. +// +void +R_ClipPassWallSegment +( int first, + int last ) +{ + cliprange_t* start; + + // Find the first range that touches the range + // (adjacent pixels are touching). + start = solidsegs; + while (start->last < first-1) + start++; + + if (first < start->first) + { + if (last < start->first-1) + { + // Post is entirely visible (above start). + R_StoreWallRange (first, last); + return; + } + + // There is a fragment above *start. + R_StoreWallRange (first, start->first - 1); + } + + // Bottom contained in start? + if (last <= start->last) + return; + + while (last >= (start+1)->first-1) + { + // There is a fragment between two posts. + R_StoreWallRange (start->last + 1, (start+1)->first - 1); + start++; + + if (last <= start->last) + return; + } + + // There is a fragment after *next. + R_StoreWallRange (start->last + 1, last); +} + + + +// +// R_ClearClipSegs +// +void R_ClearClipSegs (void) +{ + solidsegs[0].first = -0x7fffffff; + solidsegs[0].last = -1; + solidsegs[1].first = viewwidth; + solidsegs[1].last = 0x7fffffff; + newend = solidsegs+2; +} + +// +// R_AddLine +// Clips the given segment +// and adds any visible pieces to the line list. +// +void R_AddLine (seg_t* line) +{ + int x1; + int x2; + angle_t angle1; + angle_t angle2; + angle_t span; + angle_t tspan; + + curline = line; + + // OPTIMIZE: quickly reject orthogonal back sides. + angle1 = R_PointToAngle (line->v1->x, line->v1->y); + angle2 = R_PointToAngle (line->v2->x, line->v2->y); + + // Clip to view edges. + // OPTIMIZE: make constant out of 2*clipangle (FIELDOFVIEW). + span = angle1 - angle2; + + // Back side? I.e. backface culling? + if (span >= ANG180) + return; + + // Global angle needed by segcalc. + rw_angle1 = angle1; + angle1 -= viewangle; + angle2 -= viewangle; + + tspan = angle1 + clipangle; + if (tspan > 2*clipangle) + { + tspan -= 2*clipangle; + + // Totally off the left edge? + if (tspan >= span) + return; + + angle1 = clipangle; + } + tspan = clipangle - angle2; + if (tspan > 2*clipangle) + { + tspan -= 2*clipangle; + + // Totally off the left edge? + if (tspan >= span) + return; + angle2 = -clipangle; + } + + // The seg is in the view range, + // but not necessarily visible. + angle1 = (angle1+ANG90)>>ANGLETOFINESHIFT; + angle2 = (angle2+ANG90)>>ANGLETOFINESHIFT; + x1 = viewangletox[angle1]; + x2 = viewangletox[angle2]; + + // Does not cross a pixel? + if (x1 == x2) + return; + + backsector = line->backsector; + + // Single sided line? + if (!backsector) + goto clipsolid; + + // Closed door. + if (backsector->ceilingheight <= frontsector->floorheight + || backsector->floorheight >= frontsector->ceilingheight) + goto clipsolid; + + // Window. + if (backsector->ceilingheight != frontsector->ceilingheight + || backsector->floorheight != frontsector->floorheight) + goto clippass; + + // Reject empty lines used for triggers + // and special events. + // Identical floor and ceiling on both sides, + // identical light levels on both sides, + // and no middle texture. + if (backsector->ceilingpic == frontsector->ceilingpic + && backsector->floorpic == frontsector->floorpic + && backsector->lightlevel == frontsector->lightlevel + && curline->sidedef->midtexture == 0) + { + return; + } + + + clippass: + R_ClipPassWallSegment (x1, x2-1); + return; + + clipsolid: + R_ClipSolidWallSegment (x1, x2-1); +} + + +// +// R_CheckBBox +// Checks BSP node/subtree bounding box. +// Returns true +// if some part of the bbox might be visible. +// +int checkcoord[12][4] = +{ + {3,0,2,1}, + {3,0,2,0}, + {3,1,2,0}, + {0}, + {2,0,2,1}, + {0,0,0,0}, + {3,1,3,0}, + {0}, + {2,0,3,1}, + {2,1,3,1}, + {2,1,3,0} +}; + + +boolean R_CheckBBox (fixed_t* bspcoord) +{ + int boxx; + int boxy; + int boxpos; + + fixed_t x1; + fixed_t y1; + fixed_t x2; + fixed_t y2; + + angle_t angle1; + angle_t angle2; + angle_t span; + angle_t tspan; + + cliprange_t* start; + + int sx1; + int sx2; + + // Find the corners of the box + // that define the edges from current viewpoint. + if (viewx <= bspcoord[BOXLEFT]) + boxx = 0; + else if (viewx < bspcoord[BOXRIGHT]) + boxx = 1; + else + boxx = 2; + + if (viewy >= bspcoord[BOXTOP]) + boxy = 0; + else if (viewy > bspcoord[BOXBOTTOM]) + boxy = 1; + else + boxy = 2; + + boxpos = (boxy<<2)+boxx; + if (boxpos == 5) + return true; + + x1 = bspcoord[checkcoord[boxpos][0]]; + y1 = bspcoord[checkcoord[boxpos][1]]; + x2 = bspcoord[checkcoord[boxpos][2]]; + y2 = bspcoord[checkcoord[boxpos][3]]; + + // check clip list for an open space + angle1 = R_PointToAngle (x1, y1) - viewangle; + angle2 = R_PointToAngle (x2, y2) - viewangle; + + span = angle1 - angle2; + + // Sitting on a line? + if (span >= ANG180) + return true; + + tspan = angle1 + clipangle; + + if (tspan > 2*clipangle) + { + tspan -= 2*clipangle; + + // Totally off the left edge? + if (tspan >= span) + return false; + + angle1 = clipangle; + } + tspan = clipangle - angle2; + if (tspan > 2*clipangle) + { + tspan -= 2*clipangle; + + // Totally off the left edge? + if (tspan >= span) + return false; + + angle2 = -clipangle; + } + + + // Find the first clippost + // that touches the source post + // (adjacent pixels are touching). + angle1 = (angle1+ANG90)>>ANGLETOFINESHIFT; + angle2 = (angle2+ANG90)>>ANGLETOFINESHIFT; + sx1 = viewangletox[angle1]; + sx2 = viewangletox[angle2]; + + // Does not cross a pixel. + if (sx1 == sx2) + return false; + sx2--; + + start = solidsegs; + while (start->last < sx2) + start++; + + if (sx1 >= start->first + && sx2 <= start->last) + { + // The clippost contains the new span. + return false; + } + + return true; +} + + + +// +// R_Subsector +// Determine floor/ceiling planes. +// Add sprites of things in sector. +// Draw one or more line segments. +// +void R_Subsector (int num) +{ + int count; + seg_t* line; + subsector_t* sub; + +#ifdef RANGECHECK + if (num>=numsubsectors) + I_Error ("R_Subsector: ss %i with numss = %i", + num, + numsubsectors); +#endif + + sscount++; + sub = &subsectors[num]; + frontsector = sub->sector; + count = sub->numlines; + line = &segs[sub->firstline]; + + if (frontsector->floorheight < viewz) + { + floorplane = R_FindPlane (frontsector->floorheight, + frontsector->floorpic, + frontsector->lightlevel); + } + else + floorplane = NULL; + + if (frontsector->ceilingheight > viewz + || frontsector->ceilingpic == skyflatnum) + { + ceilingplane = R_FindPlane (frontsector->ceilingheight, + frontsector->ceilingpic, + frontsector->lightlevel); + } + else + ceilingplane = NULL; + + R_AddSprites (frontsector); + + while (count--) + { + R_AddLine (line); + line++; + } +} + + + + +// +// RenderBSPNode +// Renders all subsectors below a given node, +// traversing subtree recursively. +// Just call with BSP root. +void R_RenderBSPNode (int bspnum) +{ + node_t* bsp; + int side; + + // Found a subsector? + if (bspnum & NF_SUBSECTOR) + { + if (bspnum == -1) + R_Subsector (0); + else + R_Subsector (bspnum&(~NF_SUBSECTOR)); + return; + } + + bsp = &nodes[bspnum]; + + // Decide which side the view point is on. + side = R_PointOnSide (viewx, viewy, bsp); + + // Recursively divide front space. + R_RenderBSPNode (bsp->children[side]); + + // Possibly divide back space. + if (R_CheckBBox (bsp->bbox[side^1])) + R_RenderBSPNode (bsp->children[side^1]); +} + + diff --git a/firmware_p4/components/Applications/doom/r_bsp.h b/firmware_p4/components/Applications/doom/r_bsp.h new file mode 100644 index 000000000..1723e6861 --- /dev/null +++ b/firmware_p4/components/Applications/doom/r_bsp.h @@ -0,0 +1,61 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Refresh module, BSP traversal and handling. +// + + +#ifndef __R_BSP__ +#define __R_BSP__ + + + +extern seg_t* curline; +extern side_t* sidedef; +extern line_t* linedef; +extern sector_t* frontsector; +extern sector_t* backsector; + +extern int rw_x; +extern int rw_stopx; + +extern boolean segtextured; + +// false if the back side is the same plane +extern boolean markfloor; +extern boolean markceiling; + +extern boolean skymap; + +extern drawseg_t drawsegs[MAXDRAWSEGS]; +extern drawseg_t* ds_p; + +extern lighttable_t** hscalelight; +extern lighttable_t** vscalelight; +extern lighttable_t** dscalelight; + + +typedef void (*drawfunc_t) (int start, int stop); + + +// BSP? +void R_ClearClipSegs (void); +void R_ClearDrawSegs (void); + + +void R_RenderBSPNode (int bspnum); + + +#endif diff --git a/firmware_p4/components/Applications/doom/r_data.c b/firmware_p4/components/Applications/doom/r_data.c new file mode 100644 index 000000000..2d4b65bb5 --- /dev/null +++ b/firmware_p4/components/Applications/doom/r_data.c @@ -0,0 +1,912 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Preparation of data for rendering, +// generation of lookups, caching, retrieval by name. +// + +#include + +#include "deh_main.h" +#include "i_swap.h" +#include "i_system.h" +#include "z_zone.h" + + +#include "w_wad.h" + +#include "doomdef.h" +#include "m_misc.h" +#include "r_local.h" +#include "p_local.h" + +#include "doomstat.h" +#include "r_sky.h" + + +#include "r_data.h" + +// +// Graphics. +// DOOM graphics for walls and sprites +// is stored in vertical runs of opaque pixels (posts). +// A column is composed of zero or more posts, +// a patch or sprite is composed of zero or more columns. +// + + + +// +// Texture definition. +// Each texture is composed of one or more patches, +// with patches being lumps stored in the WAD. +// The lumps are referenced by number, and patched +// into the rectangular texture space using origin +// and possibly other attributes. +// +typedef struct +{ + short originx; + short originy; + short patch; + short stepdir; + short colormap; +} PACKEDATTR mappatch_t; + + +// +// Texture definition. +// A DOOM wall texture is a list of patches +// which are to be combined in a predefined order. +// +typedef struct +{ + char name[8]; + int masked; + short width; + short height; + int obsolete; + short patchcount; + mappatch_t patches[1]; +} PACKEDATTR maptexture_t; + + +// A single patch from a texture definition, +// basically a rectangular area within +// the texture rectangle. +typedef struct +{ + // Block origin (allways UL), + // which has allready accounted + // for the internal origin of the patch. + short originx; + short originy; + int patch; +} texpatch_t; + + +// A maptexturedef_t describes a rectangular texture, +// which is composed of one or more mappatch_t structures +// that arrange graphic patches. + +typedef struct texture_s texture_t; + +struct texture_s +{ + // Keep name for switch changing, etc. + char name[8]; + short width; + short height; + + // Index in textures list + + int index; + + // Next in hash table chain + + texture_t *next; + + // All the patches[patchcount] + // are drawn back to front into the cached texture. + short patchcount; + texpatch_t patches[1]; +}; + + + +int firstflat; +int lastflat; +int numflats; + +int firstpatch; +int lastpatch; +int numpatches; + +int firstspritelump; +int lastspritelump; +int numspritelumps; + +int numtextures; +texture_t** textures; +texture_t** textures_hashtable; + + +int* texturewidthmask; +// needed for texture pegging +fixed_t* textureheight; +int* texturecompositesize; +short** texturecolumnlump; +unsigned short** texturecolumnofs; +byte** texturecomposite; + +// for global animation +int* flattranslation; +int* texturetranslation; + +// needed for pre rendering +fixed_t* spritewidth; +fixed_t* spriteoffset; +fixed_t* spritetopoffset; + +lighttable_t *colormaps; + + +// +// MAPTEXTURE_T CACHING +// When a texture is first needed, +// it counts the number of composite columns +// required in the texture and allocates space +// for a column directory and any new columns. +// The directory will simply point inside other patches +// if there is only one patch in a given column, +// but any columns with multiple patches +// will have new column_ts generated. +// + + + +// +// R_DrawColumnInCache +// Clip and draw a column +// from a patch into a cached post. +// +void +R_DrawColumnInCache +( column_t* patch, + byte* cache, + int originy, + int cacheheight ) +{ + int count; + int position; + byte* source; + + while (patch->topdelta != 0xff) + { + source = (byte *)patch + 3; + count = patch->length; + position = originy + patch->topdelta; + + if (position < 0) + { + count += position; + position = 0; + } + + if (position + count > cacheheight) + count = cacheheight - position; + + if (count > 0) + memcpy (cache + position, source, count); + + patch = (column_t *)( (byte *)patch + patch->length + 4); + } +} + + + +// +// R_GenerateComposite +// Using the texture definition, +// the composite texture is created from the patches, +// and each column is cached. +// +void R_GenerateComposite (int texnum) +{ + byte* block; + texture_t* texture; + texpatch_t* patch; + patch_t* realpatch; + int x; + int x1; + int x2; + int i; + column_t* patchcol; + short* collump; + unsigned short* colofs; + + texture = textures[texnum]; + + block = Z_Malloc (texturecompositesize[texnum], + PU_STATIC, + &texturecomposite[texnum]); + + collump = texturecolumnlump[texnum]; + colofs = texturecolumnofs[texnum]; + + // Composite the columns together. + patch = texture->patches; + + for (i=0 , patch = texture->patches; + ipatchcount; + i++, patch++) + { + realpatch = W_CacheLumpNum (patch->patch, PU_CACHE); + x1 = patch->originx; + x2 = x1 + SHORT(realpatch->width); + + if (x1<0) + x = 0; + else + x = x1; + + if (x2 > texture->width) + x2 = texture->width; + + for ( ; x= 0) + continue; + + patchcol = (column_t *)((byte *)realpatch + + LONG(realpatch->columnofs[x-x1])); + R_DrawColumnInCache (patchcol, + block + colofs[x], + patch->originy, + texture->height); + } + + } + + // Now that the texture has been built in column cache, + // it is purgable from zone memory. + Z_ChangeTag (block, PU_CACHE); +} + + + +// +// R_GenerateLookup +// +void R_GenerateLookup (int texnum) +{ + texture_t* texture; + byte* patchcount; // patchcount[texture->width] + texpatch_t* patch; + patch_t* realpatch; + int x; + int x1; + int x2; + int i; + short* collump; + unsigned short* colofs; + + texture = textures[texnum]; + + // Composited texture not created yet. + texturecomposite[texnum] = 0; + + texturecompositesize[texnum] = 0; + collump = texturecolumnlump[texnum]; + colofs = texturecolumnofs[texnum]; + + // Now count the number of columns + // that are covered by more than one patch. + // Fill in the lump / offset, so columns + // with only a single patch are all done. + patchcount = (byte *) Z_Malloc(texture->width, PU_STATIC, &patchcount); + memset (patchcount, 0, texture->width); + patch = texture->patches; + + for (i=0 , patch = texture->patches; + ipatchcount; + i++, patch++) + { + realpatch = W_CacheLumpNum (patch->patch, PU_CACHE); + x1 = patch->originx; + x2 = x1 + SHORT(realpatch->width); + + if (x1 < 0) + x = 0; + else + x = x1; + + if (x2 > texture->width) + x2 = texture->width; + for ( ; xpatch; + colofs[x] = LONG(realpatch->columnofs[x-x1])+3; + } + } + + for (x=0 ; xwidth ; x++) + { + if (!patchcount[x]) + { + printf ("R_GenerateLookup: column without a patch (%s)\n", + texture->name); + return; + } + // I_Error ("R_GenerateLookup: column without a patch"); + + if (patchcount[x] > 1) + { + // Use the cached block. + collump[x] = -1; + colofs[x] = texturecompositesize[texnum]; + + if (texturecompositesize[texnum] > 0x10000-texture->height) + { + I_Error ("R_GenerateLookup: texture %i is >64k", + texnum); + } + + texturecompositesize[texnum] += texture->height; + } + } + + Z_Free(patchcount); +} + + + + +// +// R_GetColumn +// +byte* +R_GetColumn +( int tex, + int col ) +{ + int lump; + int ofs; + + col &= texturewidthmask[tex]; + lump = texturecolumnlump[tex][col]; + ofs = texturecolumnofs[tex][col]; + + if (lump > 0) + return (byte *)W_CacheLumpNum(lump,PU_CACHE)+ofs; + + if (!texturecomposite[tex]) + R_GenerateComposite (tex); + + return texturecomposite[tex] + ofs; +} + + +static void GenerateTextureHashTable(void) +{ + texture_t **rover; + int i; + int key; + + textures_hashtable + = Z_Malloc(sizeof(texture_t *) * numtextures, PU_STATIC, 0); + + memset(textures_hashtable, 0, sizeof(texture_t *) * numtextures); + + // Add all textures to hash table + + for (i=0; iindex = i; + + // Vanilla Doom does a linear search of the texures array + // and stops at the first entry it finds. If there are two + // entries with the same name, the first one in the array + // wins. The new entry must therefore be added at the end + // of the hash chain, so that earlier entries win. + + key = W_LumpNameHash(textures[i]->name) % numtextures; + + rover = &textures_hashtable[key]; + + while (*rover != NULL) + { + rover = &(*rover)->next; + } + + // Hook into hash table + + textures[i]->next = NULL; + *rover = textures[i]; + } +} + + +// +// R_InitTextures +// Initializes the texture list +// with the textures from the world map. +// +void R_InitTextures (void) +{ + maptexture_t* mtexture; + texture_t* texture; + mappatch_t* mpatch; + texpatch_t* patch; + + int i; + int j; + + int* maptex; + int* maptex2; + int* maptex1; + + char name[9]; + char* names; + char* name_p; + + int* patchlookup; + + int totalwidth; + int nummappatches; + int offset; + int maxoff; + int maxoff2; + int numtextures1; + int numtextures2; + + int* directory; + + int temp1; + int temp2; + int temp3; + + + // Load the patch names from pnames.lmp. + name[8] = 0; + names = W_CacheLumpName (DEH_String("PNAMES"), PU_STATIC); + nummappatches = LONG ( *((int *)names) ); + name_p = names + 4; + patchlookup = Z_Malloc(nummappatches*sizeof(*patchlookup), PU_STATIC, NULL); + + for (i = 0; i < nummappatches; i++) + { + M_StringCopy(name, name_p + i * 8, sizeof(name)); + patchlookup[i] = W_CheckNumForName(name); + } + W_ReleaseLumpName(DEH_String("PNAMES")); + + // Load the map texture definitions from textures.lmp. + // The data is contained in one or two lumps, + // TEXTURE1 for shareware, plus TEXTURE2 for commercial. + maptex = maptex1 = W_CacheLumpName (DEH_String("TEXTURE1"), PU_STATIC); + numtextures1 = LONG(*maptex); + maxoff = W_LumpLength (W_GetNumForName (DEH_String("TEXTURE1"))); + directory = maptex+1; + + if (W_CheckNumForName (DEH_String("TEXTURE2")) != -1) + { + maptex2 = W_CacheLumpName (DEH_String("TEXTURE2"), PU_STATIC); + numtextures2 = LONG(*maptex2); + maxoff2 = W_LumpLength (W_GetNumForName (DEH_String("TEXTURE2"))); + } + else + { + maptex2 = NULL; + numtextures2 = 0; + maxoff2 = 0; + } + numtextures = numtextures1 + numtextures2; + + textures = Z_Malloc (numtextures * sizeof(*textures), PU_STATIC, 0); + texturecolumnlump = Z_Malloc (numtextures * sizeof(*texturecolumnlump), PU_STATIC, 0); + texturecolumnofs = Z_Malloc (numtextures * sizeof(*texturecolumnofs), PU_STATIC, 0); + texturecomposite = Z_Malloc (numtextures * sizeof(*texturecomposite), PU_STATIC, 0); + texturecompositesize = Z_Malloc (numtextures * sizeof(*texturecompositesize), PU_STATIC, 0); + texturewidthmask = Z_Malloc (numtextures * sizeof(*texturewidthmask), PU_STATIC, 0); + textureheight = Z_Malloc (numtextures * sizeof(*textureheight), PU_STATIC, 0); + + totalwidth = 0; + + // Really complex printing shit... + temp1 = W_GetNumForName (DEH_String("S_START")); // P_??????? + temp2 = W_GetNumForName (DEH_String("S_END")) - 1; + temp3 = ((temp2-temp1+63)/64) + ((numtextures+63)/64); + + // If stdout is a real console, use the classic vanilla "filling + // up the box" effect, which uses backspace to "step back" inside + // the box. If stdout is a file, don't draw the box. + + if (I_ConsoleStdout()) + { + printf("["); + for (i = 0; i < temp3 + 9; i++) + printf(" "); + printf("]"); + for (i = 0; i < temp3 + 10; i++) + printf("\b"); + } + + for (i=0 ; i maxoff) + I_Error ("R_InitTextures: bad texture directory"); + + mtexture = (maptexture_t *) ( (byte *)maptex + offset); + + texture = textures[i] = + Z_Malloc (sizeof(texture_t) + + sizeof(texpatch_t)*(SHORT(mtexture->patchcount)-1), + PU_STATIC, 0); + + texture->width = SHORT(mtexture->width); + texture->height = SHORT(mtexture->height); + texture->patchcount = SHORT(mtexture->patchcount); + + memcpy (texture->name, mtexture->name, sizeof(texture->name)); + mpatch = &mtexture->patches[0]; + patch = &texture->patches[0]; + + for (j=0 ; jpatchcount ; j++, mpatch++, patch++) + { + patch->originx = SHORT(mpatch->originx); + patch->originy = SHORT(mpatch->originy); + patch->patch = patchlookup[SHORT(mpatch->patch)]; + if (patch->patch == -1) + { + I_Error ("R_InitTextures: Missing patch in texture %s", + texture->name); + } + } + texturecolumnlump[i] = Z_Malloc (texture->width*sizeof(**texturecolumnlump), PU_STATIC,0); + texturecolumnofs[i] = Z_Malloc (texture->width*sizeof(**texturecolumnofs), PU_STATIC,0); + + j = 1; + while (j*2 <= texture->width) + j<<=1; + + texturewidthmask[i] = j-1; + textureheight[i] = texture->height<width; + } + + Z_Free(patchlookup); + + W_ReleaseLumpName(DEH_String("TEXTURE1")); + if (maptex2) + W_ReleaseLumpName(DEH_String("TEXTURE2")); + + // Precalculate whatever possible. + + for (i=0 ; iwidth)<leftoffset)<topoffset)<name, name, 8) ) + return texture->index; + + texture = texture->next; + } + + return -1; +} + + + +// +// R_TextureNumForName +// Calls R_CheckTextureNumForName, +// aborts with error message. +// +int R_TextureNumForName (char* name) +{ + int i; + + i = R_CheckTextureNumForName (name); + + if (i==-1) + { + I_Error ("R_TextureNumForName: %s not found", + name); + } + return i; +} + + + + +// +// R_PrecacheLevel +// Preloads all relevant graphics for the level. +// +int flatmemory; +int texturememory; +int spritememory; + +void R_PrecacheLevel (void) +{ + char* flatpresent; + char* texturepresent; + char* spritepresent; + + int i; + int j; + int k; + int lump; + + texture_t* texture; + thinker_t* th; + spriteframe_t* sf; + + if (demoplayback) + return; + + // Precache flats. + flatpresent = Z_Malloc(numflats, PU_STATIC, NULL); + memset (flatpresent,0,numflats); + + for (i=0 ; ipatchcount ; j++) + { + lump = texture->patches[j].patch; + texturememory += lumpinfo[lump].size; + W_CacheLumpNum(lump , PU_CACHE); + } + } + + Z_Free(texturepresent); + + // Precache sprites. + spritepresent = Z_Malloc(numsprites, PU_STATIC, NULL); + memset (spritepresent,0, numsprites); + + for (th = thinkercap.next ; th != &thinkercap ; th=th->next) + { + if (th->function.acp1 == (actionf_p1)P_MobjThinker) + spritepresent[((mobj_t *)th)->sprite] = 1; + } + + spritememory = 0; + for (i=0 ; ilump[k]; + spritememory += lumpinfo[lump].size; + W_CacheLumpNum(lump , PU_CACHE); + } + } + } + + Z_Free(spritepresent); +} + + + + diff --git a/firmware_p4/components/Applications/doom/r_data.h b/firmware_p4/components/Applications/doom/r_data.h new file mode 100644 index 000000000..66425afad --- /dev/null +++ b/firmware_p4/components/Applications/doom/r_data.h @@ -0,0 +1,51 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Refresh module, data I/O, caching, retrieval of graphics +// by name. +// + + +#ifndef __R_DATA__ +#define __R_DATA__ + +#include "r_defs.h" +#include "r_state.h" + + +// Retrieve column data for span blitting. +byte* +R_GetColumn +( int tex, + int col ); + + +// I/O, setting up the stuff. +void R_InitData (void); +void R_PrecacheLevel (void); + + +// Retrieval. +// Floor/ceiling opaque texture tiles, +// lookup by name. For animation? +int R_FlatNumForName (char* name); + + +// Called by P_Ticker for switches and animations, +// returns the texture number for the texture name. +int R_TextureNumForName (char *name); +int R_CheckTextureNumForName (char *name); + +#endif diff --git a/firmware_p4/components/Applications/doom/r_defs.h b/firmware_p4/components/Applications/doom/r_defs.h new file mode 100644 index 000000000..a64ac8424 --- /dev/null +++ b/firmware_p4/components/Applications/doom/r_defs.h @@ -0,0 +1,448 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Refresh/rendering module, shared data struct definitions. +// + + +#ifndef __R_DEFS__ +#define __R_DEFS__ + + +// Screenwidth. +#include "doomdef.h" + +// Some more or less basic data types +// we depend on. +#include "m_fixed.h" + +// We rely on the thinker data struct +// to handle sound origins in sectors. +#include "d_think.h" +// SECTORS do store MObjs anyway. +#include "p_mobj.h" + +#include "i_video.h" + +#include "v_patch.h" + + + + +// Silhouette, needed for clipping Segs (mainly) +// and sprites representing things. +#define SIL_NONE 0 +#define SIL_BOTTOM 1 +#define SIL_TOP 2 +#define SIL_BOTH 3 + +#define MAXDRAWSEGS 256 + + + + + +// +// INTERNAL MAP TYPES +// used by play and refresh +// + +// +// Your plain vanilla vertex. +// Note: transformed values not buffered locally, +// like some DOOM-alikes ("wt", "WebView") did. +// +typedef struct +{ + fixed_t x; + fixed_t y; + +} vertex_t; + + +// Forward of LineDefs, for Sectors. +struct line_s; + +// Each sector has a degenmobj_t in its center +// for sound origin purposes. +// I suppose this does not handle sound from +// moving objects (doppler), because +// position is prolly just buffered, not +// updated. +typedef struct +{ + thinker_t thinker; // not used for anything + fixed_t x; + fixed_t y; + fixed_t z; + +} degenmobj_t; + +// +// The SECTORS record, at runtime. +// Stores things/mobjs. +// +typedef struct +{ + fixed_t floorheight; + fixed_t ceilingheight; + short floorpic; + short ceilingpic; + short lightlevel; + short special; + short tag; + + // 0 = untraversed, 1,2 = sndlines -1 + int soundtraversed; + + // thing that made a sound (or null) + mobj_t* soundtarget; + + // mapblock bounding box for height changes + int blockbox[4]; + + // origin for any sounds played by the sector + degenmobj_t soundorg; + + // if == validcount, already checked + int validcount; + + // list of mobjs in sector + mobj_t* thinglist; + + // thinker_t for reversable actions + void* specialdata; + + int linecount; + struct line_s** lines; // [linecount] size + +} sector_t; + + + + +// +// The SideDef. +// + +typedef struct +{ + // add this to the calculated texture column + fixed_t textureoffset; + + // add this to the calculated texture top + fixed_t rowoffset; + + // Texture indices. + // We do not maintain names here. + short toptexture; + short bottomtexture; + short midtexture; + + // Sector the SideDef is facing. + sector_t* sector; + +} side_t; + + + +// +// Move clipping aid for LineDefs. +// +typedef enum +{ + ST_HORIZONTAL, + ST_VERTICAL, + ST_POSITIVE, + ST_NEGATIVE + +} slopetype_t; + + + +typedef struct line_s +{ + // Vertices, from v1 to v2. + vertex_t* v1; + vertex_t* v2; + + // Precalculated v2 - v1 for side checking. + fixed_t dx; + fixed_t dy; + + // Animation related. + short flags; + short special; + short tag; + + // Visual appearance: SideDefs. + // sidenum[1] will be -1 if one sided + short sidenum[2]; + + // Neat. Another bounding box, for the extent + // of the LineDef. + fixed_t bbox[4]; + + // To aid move clipping. + slopetype_t slopetype; + + // Front and back sector. + // Note: redundant? Can be retrieved from SideDefs. + sector_t* frontsector; + sector_t* backsector; + + // if == validcount, already checked + int validcount; + + // thinker_t for reversable actions + void* specialdata; +} line_t; + + + + +// +// A SubSector. +// References a Sector. +// Basically, this is a list of LineSegs, +// indicating the visible walls that define +// (all or some) sides of a convex BSP leaf. +// +typedef struct subsector_s +{ + sector_t* sector; + short numlines; + short firstline; + +} subsector_t; + + + +// +// The LineSeg. +// +typedef struct +{ + vertex_t* v1; + vertex_t* v2; + + fixed_t offset; + + angle_t angle; + + side_t* sidedef; + line_t* linedef; + + // Sector references. + // Could be retrieved from linedef, too. + // backsector is NULL for one sided lines + sector_t* frontsector; + sector_t* backsector; + +} seg_t; + + + +// +// BSP node. +// +typedef struct +{ + // Partition line. + fixed_t x; + fixed_t y; + fixed_t dx; + fixed_t dy; + + // Bounding box for each child. + fixed_t bbox[2][4]; + + // If NF_SUBSECTOR its a subsector. + unsigned short children[2]; + +} node_t; + + + + +// PC direct to screen pointers +//B UNUSED - keep till detailshift in r_draw.c resolved +//extern byte* destview; +//extern byte* destscreen; + + + + + +// +// OTHER TYPES +// + +// This could be wider for >8 bit display. +// Indeed, true color support is posibble +// precalculating 24bpp lightmap/colormap LUT. +// from darkening PLAYPAL to all black. +// Could even us emore than 32 levels. +typedef byte lighttable_t; + + + + +// +// ? +// +typedef struct drawseg_s +{ + seg_t* curline; + int x1; + int x2; + + fixed_t scale1; + fixed_t scale2; + fixed_t scalestep; + + // 0=none, 1=bottom, 2=top, 3=both + int silhouette; + + // do not clip sprites above this + fixed_t bsilheight; + + // do not clip sprites below this + fixed_t tsilheight; + + // Pointers to lists for sprite clipping, + // all three adjusted so [x1] is first value. + short* sprtopclip; + short* sprbottomclip; + short* maskedtexturecol; + +} drawseg_t; + + + +// A vissprite_t is a thing +// that will be drawn during a refresh. +// I.e. a sprite object that is partly visible. +typedef struct vissprite_s +{ + // Doubly linked list. + struct vissprite_s* prev; + struct vissprite_s* next; + + int x1; + int x2; + + // for line side calculation + fixed_t gx; + fixed_t gy; + + // global bottom / top for silhouette clipping + fixed_t gz; + fixed_t gzt; + + // horizontal position of x1 + fixed_t startfrac; + + fixed_t scale; + + // negative if flipped + fixed_t xiscale; + + fixed_t texturemid; + int patch; + + // for color translation and shadow draw, + // maxbright frames as well + lighttable_t* colormap; + + int mobjflags; + +} vissprite_t; + + +// +// Sprites are patches with a special naming convention +// so they can be recognized by R_InitSprites. +// The base name is NNNNFx or NNNNFxFx, with +// x indicating the rotation, x = 0, 1-7. +// The sprite and frame specified by a thing_t +// is range checked at run time. +// A sprite is a patch_t that is assumed to represent +// a three dimensional object and may have multiple +// rotations pre drawn. +// Horizontal flipping is used to save space, +// thus NNNNF2F5 defines a mirrored patch. +// Some sprites will only have one picture used +// for all views: NNNNF0 +// +typedef struct +{ + // If false use 0 for any position. + // Note: as eight entries are available, + // we might as well insert the same name eight times. + boolean rotate; + + // Lump to use for view angles 0-7. + short lump[8]; + + // Flip bit (1 = flip) to use for view angles 0-7. + byte flip[8]; + +} spriteframe_t; + + + +// +// A sprite definition: +// a number of animation frames. +// +typedef struct +{ + int numframes; + spriteframe_t* spriteframes; + +} spritedef_t; + + + +// +// Now what is a visplane, anyway? +// +typedef struct +{ + fixed_t height; + int picnum; + int lightlevel; + int minx; + int maxx; + + // leave pads for [minx-1]/[maxx+1] + + byte pad1; + // Here lies the rub for all + // dynamic resize/change of resolution. + byte top[SCREENWIDTH]; + byte pad2; + byte pad3; + // See above. + byte bottom[SCREENWIDTH]; + byte pad4; + +} visplane_t; + + + + +#endif diff --git a/firmware_p4/components/Applications/doom/r_draw.c b/firmware_p4/components/Applications/doom/r_draw.c new file mode 100644 index 000000000..9271bcd55 --- /dev/null +++ b/firmware_p4/components/Applications/doom/r_draw.c @@ -0,0 +1,975 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// The actual span/column drawing functions. +// Here find the main potential for optimization, +// e.g. inline assembly, different algorithms. +// + + + + +#include "doomdef.h" +#include "deh_main.h" + +#include "i_system.h" +#include "z_zone.h" +#include "w_wad.h" + +#include "r_local.h" + +// Needs access to LFB (guess what). +#include "v_video.h" + +// State. +#include "doomstat.h" + + +// ? +#define MAXWIDTH 1120 +#define MAXHEIGHT 832 + +// status bar height at bottom of screen +#define SBARHEIGHT 32 + +// +// All drawing to the view buffer is accomplished in this file. +// The other refresh files only know about ccordinates, +// not the architecture of the frame buffer. +// Conveniently, the frame buffer is a linear one, +// and we need only the base address, +// and the total size == width*height*depth/8., +// + + +byte* viewimage; +int viewwidth; +int scaledviewwidth; +int viewheight; +int viewwindowx; +int viewwindowy; +byte* ylookup[MAXHEIGHT]; +int columnofs[MAXWIDTH]; + +// Color tables for different players, +// translate a limited part to another +// (color ramps used for suit colors). +// +byte translations[3][256]; + +// Backing buffer containing the bezel drawn around the screen and +// surrounding background. + +static byte *background_buffer = NULL; + + +// +// R_DrawColumn +// Source is the top of the column to scale. +// +lighttable_t* dc_colormap; +int dc_x; +int dc_yl; +int dc_yh; +fixed_t dc_iscale; +fixed_t dc_texturemid; + +// first pixel in a column (possibly virtual) +byte* dc_source; + +// just for profiling +int dccount; + +// +// A column is a vertical slice/span from a wall texture that, +// given the DOOM style restrictions on the view orientation, +// will always have constant z depth. +// Thus a special case loop for very fast rendering can +// be used. It has also been used with Wolfenstein 3D. +// +void R_DrawColumn (void) +{ + int count; + byte* dest; + fixed_t frac; + fixed_t fracstep; + + count = dc_yh - dc_yl; + + // Zero length, column does not exceed a pixel. + if (count < 0) + return; + +#ifdef RANGECHECK + if ((unsigned)dc_x >= SCREENWIDTH + || dc_yl < 0 + || dc_yh >= SCREENHEIGHT) + I_Error ("R_DrawColumn: %i to %i at %i", dc_yl, dc_yh, dc_x); +#endif + + // Framebuffer destination address. + // Use ylookup LUT to avoid multiply with ScreenWidth. + // Use columnofs LUT for subwindows? + dest = ylookup[dc_yl] + columnofs[dc_x]; + + // Determine scaling, + // which is the only mapping to be done. + fracstep = dc_iscale; + frac = dc_texturemid + (dc_yl-centery)*fracstep; + + // Inner loop that does the actual texture mapping, + // e.g. a DDA-lile scaling. + // This is as fast as it gets. + do + { + // Re-map color indices from wall texture column + // using a lighting/special effects LUT. + *dest = dc_colormap[dc_source[(frac>>FRACBITS)&127]]; + + dest += SCREENWIDTH; + frac += fracstep; + + } while (count--); +} + + + +// UNUSED. +// Loop unrolled. +#if 0 +void R_DrawColumn (void) +{ + int count; + byte* source; + byte* dest; + byte* colormap; + + unsigned frac; + unsigned fracstep; + unsigned fracstep2; + unsigned fracstep3; + unsigned fracstep4; + + count = dc_yh - dc_yl + 1; + + source = dc_source; + colormap = dc_colormap; + dest = ylookup[dc_yl] + columnofs[dc_x]; + + fracstep = dc_iscale<<9; + frac = (dc_texturemid + (dc_yl-centery)*dc_iscale)<<9; + + fracstep2 = fracstep+fracstep; + fracstep3 = fracstep2+fracstep; + fracstep4 = fracstep3+fracstep; + + while (count >= 8) + { + dest[0] = colormap[source[frac>>25]]; + dest[SCREENWIDTH] = colormap[source[(frac+fracstep)>>25]]; + dest[SCREENWIDTH*2] = colormap[source[(frac+fracstep2)>>25]]; + dest[SCREENWIDTH*3] = colormap[source[(frac+fracstep3)>>25]]; + + frac += fracstep4; + + dest[SCREENWIDTH*4] = colormap[source[frac>>25]]; + dest[SCREENWIDTH*5] = colormap[source[(frac+fracstep)>>25]]; + dest[SCREENWIDTH*6] = colormap[source[(frac+fracstep2)>>25]]; + dest[SCREENWIDTH*7] = colormap[source[(frac+fracstep3)>>25]]; + + frac += fracstep4; + dest += SCREENWIDTH*8; + count -= 8; + } + + while (count > 0) + { + *dest = colormap[source[frac>>25]]; + dest += SCREENWIDTH; + frac += fracstep; + count--; + } +} +#endif + + +void R_DrawColumnLow (void) +{ + int count; + byte* dest; + byte* dest2; + fixed_t frac; + fixed_t fracstep; + int x; + + count = dc_yh - dc_yl; + + // Zero length. + if (count < 0) + return; + +#ifdef RANGECHECK + if ((unsigned)dc_x >= SCREENWIDTH + || dc_yl < 0 + || dc_yh >= SCREENHEIGHT) + { + + I_Error ("R_DrawColumn: %i to %i at %i", dc_yl, dc_yh, dc_x); + } + // dccount++; +#endif + // Blocky mode, need to multiply by 2. + x = dc_x << 1; + + dest = ylookup[dc_yl] + columnofs[x]; + dest2 = ylookup[dc_yl] + columnofs[x+1]; + + fracstep = dc_iscale; + frac = dc_texturemid + (dc_yl-centery)*fracstep; + + do + { + // Hack. Does not work corretly. + *dest2 = *dest = dc_colormap[dc_source[(frac>>FRACBITS)&127]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + + } while (count--); +} + + +// +// Spectre/Invisibility. +// +#define FUZZTABLE 50 +#define FUZZOFF (SCREENWIDTH) + + +int fuzzoffset[FUZZTABLE] = +{ + FUZZOFF,-FUZZOFF,FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF, + FUZZOFF,FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF, + FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF,-FUZZOFF,-FUZZOFF,-FUZZOFF, + FUZZOFF,-FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF, + FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF,-FUZZOFF,FUZZOFF, + FUZZOFF,-FUZZOFF,-FUZZOFF,-FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF, + FUZZOFF,FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF,FUZZOFF +}; + +int fuzzpos = 0; + + +// +// Framebuffer postprocessing. +// Creates a fuzzy image by copying pixels +// from adjacent ones to left and right. +// Used with an all black colormap, this +// could create the SHADOW effect, +// i.e. spectres and invisible players. +// +void R_DrawFuzzColumn (void) +{ + int count; + byte* dest; + fixed_t frac; + fixed_t fracstep; + + // Adjust borders. Low... + if (!dc_yl) + dc_yl = 1; + + // .. and high. + if (dc_yh == viewheight-1) + dc_yh = viewheight - 2; + + count = dc_yh - dc_yl; + + // Zero length. + if (count < 0) + return; + +#ifdef RANGECHECK + if ((unsigned)dc_x >= SCREENWIDTH + || dc_yl < 0 || dc_yh >= SCREENHEIGHT) + { + I_Error ("R_DrawFuzzColumn: %i to %i at %i", + dc_yl, dc_yh, dc_x); + } +#endif + + dest = ylookup[dc_yl] + columnofs[dc_x]; + + // Looks familiar. + fracstep = dc_iscale; + frac = dc_texturemid + (dc_yl-centery)*fracstep; + + // Looks like an attempt at dithering, + // using the colormap #6 (of 0-31, a bit + // brighter than average). + do + { + // Lookup framebuffer, and retrieve + // a pixel that is either one column + // left or right of the current one. + // Add index from colormap to index. + *dest = colormaps[6*256+dest[fuzzoffset[fuzzpos]]]; + + // Clamp table lookup index. + if (++fuzzpos == FUZZTABLE) + fuzzpos = 0; + + dest += SCREENWIDTH; + + frac += fracstep; + } while (count--); +} + +// low detail mode version + +void R_DrawFuzzColumnLow (void) +{ + int count; + byte* dest; + byte* dest2; + fixed_t frac; + fixed_t fracstep; + int x; + + // Adjust borders. Low... + if (!dc_yl) + dc_yl = 1; + + // .. and high. + if (dc_yh == viewheight-1) + dc_yh = viewheight - 2; + + count = dc_yh - dc_yl; + + // Zero length. + if (count < 0) + return; + + // low detail mode, need to multiply by 2 + + x = dc_x << 1; + +#ifdef RANGECHECK + if ((unsigned)x >= SCREENWIDTH + || dc_yl < 0 || dc_yh >= SCREENHEIGHT) + { + I_Error ("R_DrawFuzzColumn: %i to %i at %i", + dc_yl, dc_yh, dc_x); + } +#endif + + dest = ylookup[dc_yl] + columnofs[x]; + dest2 = ylookup[dc_yl] + columnofs[x+1]; + + // Looks familiar. + fracstep = dc_iscale; + frac = dc_texturemid + (dc_yl-centery)*fracstep; + + // Looks like an attempt at dithering, + // using the colormap #6 (of 0-31, a bit + // brighter than average). + do + { + // Lookup framebuffer, and retrieve + // a pixel that is either one column + // left or right of the current one. + // Add index from colormap to index. + *dest = colormaps[6*256+dest[fuzzoffset[fuzzpos]]]; + *dest2 = colormaps[6*256+dest2[fuzzoffset[fuzzpos]]]; + + // Clamp table lookup index. + if (++fuzzpos == FUZZTABLE) + fuzzpos = 0; + + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + + frac += fracstep; + } while (count--); +} + + + + + +// +// R_DrawTranslatedColumn +// Used to draw player sprites +// with the green colorramp mapped to others. +// Could be used with different translation +// tables, e.g. the lighter colored version +// of the BaronOfHell, the HellKnight, uses +// identical sprites, kinda brightened up. +// +byte* dc_translation; +byte* translationtables; + +void R_DrawTranslatedColumn (void) +{ + int count; + byte* dest; + fixed_t frac; + fixed_t fracstep; + + count = dc_yh - dc_yl; + if (count < 0) + return; + +#ifdef RANGECHECK + if ((unsigned)dc_x >= SCREENWIDTH + || dc_yl < 0 + || dc_yh >= SCREENHEIGHT) + { + I_Error ( "R_DrawColumn: %i to %i at %i", + dc_yl, dc_yh, dc_x); + } + +#endif + + + dest = ylookup[dc_yl] + columnofs[dc_x]; + + // Looks familiar. + fracstep = dc_iscale; + frac = dc_texturemid + (dc_yl-centery)*fracstep; + + // Here we do an additional index re-mapping. + do + { + // Translation tables are used + // to map certain colorramps to other ones, + // used with PLAY sprites. + // Thus the "green" ramp of the player 0 sprite + // is mapped to gray, red, black/indigo. + *dest = dc_colormap[dc_translation[dc_source[frac>>FRACBITS]]]; + dest += SCREENWIDTH; + + frac += fracstep; + } while (count--); +} + +void R_DrawTranslatedColumnLow (void) +{ + int count; + byte* dest; + byte* dest2; + fixed_t frac; + fixed_t fracstep; + int x; + + count = dc_yh - dc_yl; + if (count < 0) + return; + + // low detail, need to scale by 2 + x = dc_x << 1; + +#ifdef RANGECHECK + if ((unsigned)x >= SCREENWIDTH + || dc_yl < 0 + || dc_yh >= SCREENHEIGHT) + { + I_Error ( "R_DrawColumn: %i to %i at %i", + dc_yl, dc_yh, x); + } + +#endif + + + dest = ylookup[dc_yl] + columnofs[x]; + dest2 = ylookup[dc_yl] + columnofs[x+1]; + + // Looks familiar. + fracstep = dc_iscale; + frac = dc_texturemid + (dc_yl-centery)*fracstep; + + // Here we do an additional index re-mapping. + do + { + // Translation tables are used + // to map certain colorramps to other ones, + // used with PLAY sprites. + // Thus the "green" ramp of the player 0 sprite + // is mapped to gray, red, black/indigo. + *dest = dc_colormap[dc_translation[dc_source[frac>>FRACBITS]]]; + *dest2 = dc_colormap[dc_translation[dc_source[frac>>FRACBITS]]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + + frac += fracstep; + } while (count--); +} + + + + +// +// R_InitTranslationTables +// Creates the translation tables to map +// the green color ramp to gray, brown, red. +// Assumes a given structure of the PLAYPAL. +// Could be read from a lump instead. +// +void R_InitTranslationTables (void) +{ + int i; + + translationtables = Z_Malloc (256*3, PU_STATIC, 0); + + // translate just the 16 green colors + for (i=0 ; i<256 ; i++) + { + if (i >= 0x70 && i<= 0x7f) + { + // map green ramp to gray, brown, red + translationtables[i] = 0x60 + (i&0xf); + translationtables [i+256] = 0x40 + (i&0xf); + translationtables [i+512] = 0x20 + (i&0xf); + } + else + { + // Keep all other colors as is. + translationtables[i] = translationtables[i+256] + = translationtables[i+512] = i; + } + } +} + + + + +// +// R_DrawSpan +// With DOOM style restrictions on view orientation, +// the floors and ceilings consist of horizontal slices +// or spans with constant z depth. +// However, rotation around the world z axis is possible, +// thus this mapping, while simpler and faster than +// perspective correct texture mapping, has to traverse +// the texture at an angle in all but a few cases. +// In consequence, flats are not stored by column (like walls), +// and the inner loop has to step in texture space u and v. +// +int ds_y; +int ds_x1; +int ds_x2; + +lighttable_t* ds_colormap; + +fixed_t ds_xfrac; +fixed_t ds_yfrac; +fixed_t ds_xstep; +fixed_t ds_ystep; + +// start of a 64*64 tile image +byte* ds_source; + +// just for profiling +int dscount; + + +// +// Draws the actual span. +void R_DrawSpan (void) +{ + unsigned int position, step; + byte *dest; + int count; + int spot; + unsigned int xtemp, ytemp; + +#ifdef RANGECHECK + if (ds_x2 < ds_x1 + || ds_x1<0 + || ds_x2>=SCREENWIDTH + || (unsigned)ds_y>SCREENHEIGHT) + { + I_Error( "R_DrawSpan: %i to %i at %i", + ds_x1,ds_x2,ds_y); + } +// dscount++; +#endif + + // Pack position and step variables into a single 32-bit integer, + // with x in the top 16 bits and y in the bottom 16 bits. For + // each 16-bit part, the top 6 bits are the integer part and the + // bottom 10 bits are the fractional part of the pixel position. + + position = ((ds_xfrac << 10) & 0xffff0000) + | ((ds_yfrac >> 6) & 0x0000ffff); + step = ((ds_xstep << 10) & 0xffff0000) + | ((ds_ystep >> 6) & 0x0000ffff); + + dest = ylookup[ds_y] + columnofs[ds_x1]; + + // We do not check for zero spans here? + count = ds_x2 - ds_x1; + + do + { + // Calculate current texture index in u,v. + ytemp = (position >> 4) & 0x0fc0; + xtemp = (position >> 26); + spot = xtemp | ytemp; + + // Lookup pixel from flat texture tile, + // re-index using light/colormap. + *dest++ = ds_colormap[ds_source[spot]]; + + position += step; + + } while (count--); +} + + + +// UNUSED. +// Loop unrolled by 4. +#if 0 +void R_DrawSpan (void) +{ + unsigned position, step; + + byte* source; + byte* colormap; + byte* dest; + + unsigned count; + usingned spot; + unsigned value; + unsigned temp; + unsigned xtemp; + unsigned ytemp; + + position = ((ds_xfrac<<10)&0xffff0000) | ((ds_yfrac>>6)&0xffff); + step = ((ds_xstep<<10)&0xffff0000) | ((ds_ystep>>6)&0xffff); + + source = ds_source; + colormap = ds_colormap; + dest = ylookup[ds_y] + columnofs[ds_x1]; + count = ds_x2 - ds_x1 + 1; + + while (count >= 4) + { + ytemp = position>>4; + ytemp = ytemp & 4032; + xtemp = position>>26; + spot = xtemp | ytemp; + position += step; + dest[0] = colormap[source[spot]]; + + ytemp = position>>4; + ytemp = ytemp & 4032; + xtemp = position>>26; + spot = xtemp | ytemp; + position += step; + dest[1] = colormap[source[spot]]; + + ytemp = position>>4; + ytemp = ytemp & 4032; + xtemp = position>>26; + spot = xtemp | ytemp; + position += step; + dest[2] = colormap[source[spot]]; + + ytemp = position>>4; + ytemp = ytemp & 4032; + xtemp = position>>26; + spot = xtemp | ytemp; + position += step; + dest[3] = colormap[source[spot]]; + + count -= 4; + dest += 4; + } + while (count > 0) + { + ytemp = position>>4; + ytemp = ytemp & 4032; + xtemp = position>>26; + spot = xtemp | ytemp; + position += step; + *dest++ = colormap[source[spot]]; + count--; + } +} +#endif + + +// +// Again.. +// +void R_DrawSpanLow (void) +{ + unsigned int position, step; + unsigned int xtemp, ytemp; + byte *dest; + int count; + int spot; + +#ifdef RANGECHECK + if (ds_x2 < ds_x1 + || ds_x1<0 + || ds_x2>=SCREENWIDTH + || (unsigned)ds_y>SCREENHEIGHT) + { + I_Error( "R_DrawSpan: %i to %i at %i", + ds_x1,ds_x2,ds_y); + } +// dscount++; +#endif + + position = ((ds_xfrac << 10) & 0xffff0000) + | ((ds_yfrac >> 6) & 0x0000ffff); + step = ((ds_xstep << 10) & 0xffff0000) + | ((ds_ystep >> 6) & 0x0000ffff); + + count = (ds_x2 - ds_x1); + + // Blocky mode, need to multiply by 2. + ds_x1 <<= 1; + ds_x2 <<= 1; + + dest = ylookup[ds_y] + columnofs[ds_x1]; + + do + { + // Calculate current texture index in u,v. + ytemp = (position >> 4) & 0x0fc0; + xtemp = (position >> 26); + spot = xtemp | ytemp; + + // Lowres/blocky mode does it twice, + // while scale is adjusted appropriately. + *dest++ = ds_colormap[ds_source[spot]]; + *dest++ = ds_colormap[ds_source[spot]]; + + position += step; + + } while (count--); +} + +// +// R_InitBuffer +// Creats lookup tables that avoid +// multiplies and other hazzles +// for getting the framebuffer address +// of a pixel to draw. +// +void +R_InitBuffer +( int width, + int height ) +{ + int i; + + // Handle resize, + // e.g. smaller view windows + // with border and/or status bar. + viewwindowx = (SCREENWIDTH-width) >> 1; + + // Column offset. For windows. + for (i=0 ; i> 1; + + // Preclaculate all row offsets. + for (i=0 ; i +#include + + +#include "doomdef.h" +#include "d_loop.h" + +#include "m_bbox.h" +#include "m_menu.h" + +#include "r_local.h" +#include "r_sky.h" + + + + + +// Fineangles in the SCREENWIDTH wide window. +#define FIELDOFVIEW 2048 + + + +int viewangleoffset; + +// increment every time a check is made +int validcount = 1; + + +lighttable_t* fixedcolormap; +extern lighttable_t** walllights; + +int centerx; +int centery; + +fixed_t centerxfrac; +fixed_t centeryfrac; +fixed_t projection; + +// just for profiling purposes +int framecount; + +int sscount; +int linecount; +int loopcount; + +fixed_t viewx; +fixed_t viewy; +fixed_t viewz; + +angle_t viewangle; + +fixed_t viewcos; +fixed_t viewsin; + +player_t* viewplayer; + +// 0 = high, 1 = low +int detailshift; + +// +// precalculated math tables +// +angle_t clipangle; + +// The viewangletox[viewangle + FINEANGLES/4] lookup +// maps the visible view angles to screen X coordinates, +// flattening the arc to a flat projection plane. +// There will be many angles mapped to the same X. +int viewangletox[FINEANGLES/2]; + +// The xtoviewangleangle[] table maps a screen pixel +// to the lowest viewangle that maps back to x ranges +// from clipangle to -clipangle. +angle_t xtoviewangle[SCREENWIDTH+1]; + +lighttable_t* scalelight[LIGHTLEVELS][MAXLIGHTSCALE]; +lighttable_t* scalelightfixed[MAXLIGHTSCALE]; +lighttable_t* zlight[LIGHTLEVELS][MAXLIGHTZ]; + +// bumped light from gun blasts +int extralight; + + + +void (*colfunc) (void); +void (*basecolfunc) (void); +void (*fuzzcolfunc) (void); +void (*transcolfunc) (void); +void (*spanfunc) (void); + + + +// +// R_AddPointToBox +// Expand a given bbox +// so that it encloses a given point. +// +void +R_AddPointToBox +( int x, + int y, + fixed_t* box ) +{ + if (x< box[BOXLEFT]) + box[BOXLEFT] = x; + if (x> box[BOXRIGHT]) + box[BOXRIGHT] = x; + if (y< box[BOXBOTTOM]) + box[BOXBOTTOM] = y; + if (y> box[BOXTOP]) + box[BOXTOP] = y; +} + + +// +// R_PointOnSide +// Traverse BSP (sub) tree, +// check point against partition plane. +// Returns side 0 (front) or 1 (back). +// +int +R_PointOnSide +( fixed_t x, + fixed_t y, + node_t* node ) +{ + fixed_t dx; + fixed_t dy; + fixed_t left; + fixed_t right; + + if (!node->dx) + { + if (x <= node->x) + return node->dy > 0; + + return node->dy < 0; + } + if (!node->dy) + { + if (y <= node->y) + return node->dx < 0; + + return node->dx > 0; + } + + dx = (x - node->x); + dy = (y - node->y); + + // Try to quickly decide by looking at sign bits. + if ( (node->dy ^ node->dx ^ dx ^ dy)&0x80000000 ) + { + if ( (node->dy ^ dx) & 0x80000000 ) + { + // (left is negative) + return 1; + } + return 0; + } + + left = FixedMul ( node->dy>>FRACBITS , dx ); + right = FixedMul ( dy , node->dx>>FRACBITS ); + + if (right < left) + { + // front side + return 0; + } + // back side + return 1; +} + + +int +R_PointOnSegSide +( fixed_t x, + fixed_t y, + seg_t* line ) +{ + fixed_t lx; + fixed_t ly; + fixed_t ldx; + fixed_t ldy; + fixed_t dx; + fixed_t dy; + fixed_t left; + fixed_t right; + + lx = line->v1->x; + ly = line->v1->y; + + ldx = line->v2->x - lx; + ldy = line->v2->y - ly; + + if (!ldx) + { + if (x <= lx) + return ldy > 0; + + return ldy < 0; + } + if (!ldy) + { + if (y <= ly) + return ldx < 0; + + return ldx > 0; + } + + dx = (x - lx); + dy = (y - ly); + + // Try to quickly decide by looking at sign bits. + if ( (ldy ^ ldx ^ dx ^ dy)&0x80000000 ) + { + if ( (ldy ^ dx) & 0x80000000 ) + { + // (left is negative) + return 1; + } + return 0; + } + + left = FixedMul ( ldy>>FRACBITS , dx ); + right = FixedMul ( dy , ldx>>FRACBITS ); + + if (right < left) + { + // front side + return 0; + } + // back side + return 1; +} + + +// +// R_PointToAngle +// To get a global angle from cartesian coordinates, +// the coordinates are flipped until they are in +// the first octant of the coordinate system, then +// the y (<=x) is scaled and divided by x to get a +// tangent (slope) value which is looked up in the +// tantoangle[] table. + +// + + + + +angle_t +R_PointToAngle +( fixed_t x, + fixed_t y ) +{ + x -= viewx; + y -= viewy; + + if ( (!x) && (!y) ) + return 0; + + if (x>= 0) + { + // x >=0 + if (y>= 0) + { + // y>= 0 + + if (x>y) + { + // octant 0 + return tantoangle[ SlopeDiv(y,x)]; + } + else + { + // octant 1 + return ANG90-1-tantoangle[ SlopeDiv(x,y)]; + } + } + else + { + // y<0 + y = -y; + + if (x>y) + { + // octant 8 + return -tantoangle[SlopeDiv(y,x)]; + } + else + { + // octant 7 + return ANG270+tantoangle[ SlopeDiv(x,y)]; + } + } + } + else + { + // x<0 + x = -x; + + if (y>= 0) + { + // y>= 0 + if (x>y) + { + // octant 3 + return ANG180-1-tantoangle[ SlopeDiv(y,x)]; + } + else + { + // octant 2 + return ANG90+ tantoangle[ SlopeDiv(x,y)]; + } + } + else + { + // y<0 + y = -y; + + if (x>y) + { + // octant 4 + return ANG180+tantoangle[ SlopeDiv(y,x)]; + } + else + { + // octant 5 + return ANG270-1-tantoangle[ SlopeDiv(x,y)]; + } + } + } + return 0; +} + + +angle_t +R_PointToAngle2 +( fixed_t x1, + fixed_t y1, + fixed_t x2, + fixed_t y2 ) +{ + viewx = x1; + viewy = y1; + + return R_PointToAngle (x2, y2); +} + + +fixed_t +R_PointToDist +( fixed_t x, + fixed_t y ) +{ + int angle; + fixed_t dx; + fixed_t dy; + fixed_t temp; + fixed_t dist; + fixed_t frac; + + dx = abs(x - viewx); + dy = abs(y - viewy); + + if (dy>dx) + { + temp = dx; + dx = dy; + dy = temp; + } + + // Fix crashes in udm1.wad + + if (dx != 0) + { + frac = FixedDiv(dy, dx); + } + else + { + frac = 0; + } + + angle = (tantoangle[frac>>DBITS]+ANG90) >> ANGLETOFINESHIFT; + + // use as cosine + dist = FixedDiv (dx, finesine[angle] ); + + return dist; +} + + + + +// +// R_InitPointToAngle +// +void R_InitPointToAngle (void) +{ + // UNUSED - now getting from tables.c +#if 0 + int i; + long t; + float f; +// +// slope (tangent) to angle lookup +// + for (i=0 ; i<=SLOPERANGE ; i++) + { + f = atan( (float)i/SLOPERANGE )/(3.141592657*2); + t = 0xffffffff*f; + tantoangle[i] = t; + } +#endif +} + + +// +// R_ScaleFromGlobalAngle +// Returns the texture mapping scale +// for the current line (horizontal span) +// at the given angle. +// rw_distance must be calculated first. +// +fixed_t R_ScaleFromGlobalAngle (angle_t visangle) +{ + fixed_t scale; + angle_t anglea; + angle_t angleb; + int sinea; + int sineb; + fixed_t num; + int den; + + // UNUSED +#if 0 +{ + fixed_t dist; + fixed_t z; + fixed_t sinv; + fixed_t cosv; + + sinv = finesine[(visangle-rw_normalangle)>>ANGLETOFINESHIFT]; + dist = FixedDiv (rw_distance, sinv); + cosv = finecosine[(viewangle-visangle)>>ANGLETOFINESHIFT]; + z = abs(FixedMul (dist, cosv)); + scale = FixedDiv(projection, z); + return scale; +} +#endif + + anglea = ANG90 + (visangle-viewangle); + angleb = ANG90 + (visangle-rw_normalangle); + + // both sines are allways positive + sinea = finesine[anglea>>ANGLETOFINESHIFT]; + sineb = finesine[angleb>>ANGLETOFINESHIFT]; + num = FixedMul(projection,sineb)< num>>16) + { + scale = FixedDiv (num, den); + + if (scale > 64*FRACUNIT) + scale = 64*FRACUNIT; + else if (scale < 256) + scale = 256; + } + else + scale = 64*FRACUNIT; + + return scale; +} + + + +// +// R_InitTables +// +void R_InitTables (void) +{ + // UNUSED: now getting from tables.c +#if 0 + int i; + float a; + float fv; + int t; + + // viewangle tangent table + for (i=0 ; i FRACUNIT*2) + t = -1; + else if (finetangent[i] < -FRACUNIT*2) + t = viewwidth+1; + else + { + t = FixedMul (finetangent[i], focallength); + t = (centerxfrac - t+FRACUNIT-1)>>FRACBITS; + + if (t < -1) + t = -1; + else if (t>viewwidth+1) + t = viewwidth+1; + } + viewangletox[i] = t; + } + + // Scan viewangletox[] to generate xtoviewangle[]: + // xtoviewangle will give the smallest view angle + // that maps to x. + for (x=0;x<=viewwidth;x++) + { + i = 0; + while (viewangletox[i]>x) + i++; + xtoviewangle[x] = (i<>= LIGHTSCALESHIFT; + level = startmap - scale/DISTMAP; + + if (level < 0) + level = 0; + + if (level >= NUMCOLORMAPS) + level = NUMCOLORMAPS-1; + + zlight[i][j] = colormaps + level*256; + } + } +} + + + +// +// R_SetViewSize +// Do not really change anything here, +// because it might be in the middle of a refresh. +// The change will take effect next refresh. +// +boolean setsizeneeded; +int setblocks; +int setdetail; + + +void +R_SetViewSize +( int blocks, + int detail ) +{ + setsizeneeded = true; + setblocks = blocks; + setdetail = detail; +} + + +// +// R_ExecuteSetViewSize +// +void R_ExecuteSetViewSize (void) +{ + fixed_t cosadj; + fixed_t dy; + int i; + int j; + int level; + int startmap; + + setsizeneeded = false; + + if (setblocks == 11) + { + scaledviewwidth = SCREENWIDTH; + viewheight = SCREENHEIGHT; + } + else + { + scaledviewwidth = setblocks*32; + viewheight = (setblocks*168/10)&~7; + } + + detailshift = setdetail; + viewwidth = scaledviewwidth>>detailshift; + + centery = viewheight/2; + centerx = viewwidth/2; + centerxfrac = centerx<>ANGLETOFINESHIFT]); + distscale[i] = FixedDiv (FRACUNIT,cosadj); + } + + // Calculate the light levels to use + // for each level / scale combination. + for (i=0 ; i< LIGHTLEVELS ; i++) + { + startmap = ((LIGHTLEVELS-1-i)*2)*NUMCOLORMAPS/LIGHTLEVELS; + for (j=0 ; j= NUMCOLORMAPS) + level = NUMCOLORMAPS-1; + + scalelight[i][j] = colormaps + level*256; + } + } +} + + + +// +// R_Init +// + + + +void R_Init (void) +{ + R_InitData (); + printf ("."); + R_InitPointToAngle (); + printf ("."); + R_InitTables (); + // viewwidth / viewheight / detailLevel are set by the defaults + printf ("."); + + R_SetViewSize (screenblocks, detailLevel); + R_InitPlanes (); + printf ("."); + R_InitLightTables (); + printf ("."); + R_InitSkyMap (); + R_InitTranslationTables (); + printf ("."); + + framecount = 0; +} + + +// +// R_PointInSubsector +// +subsector_t* +R_PointInSubsector +( fixed_t x, + fixed_t y ) +{ + node_t* node; + int side; + int nodenum; + + // single subsector is a special case + if (!numnodes) + return subsectors; + + nodenum = numnodes-1; + + while (! (nodenum & NF_SUBSECTOR) ) + { + node = &nodes[nodenum]; + side = R_PointOnSide (x, y, node); + nodenum = node->children[side]; + } + + return &subsectors[nodenum & ~NF_SUBSECTOR]; +} + + + +// +// R_SetupFrame +// +void R_SetupFrame (player_t* player) +{ + int i; + + viewplayer = player; + viewx = player->mo->x; + viewy = player->mo->y; + viewangle = player->mo->angle + viewangleoffset; + extralight = player->extralight; + + viewz = player->viewz; + + viewsin = finesine[viewangle>>ANGLETOFINESHIFT]; + viewcos = finecosine[viewangle>>ANGLETOFINESHIFT]; + + sscount = 0; + + if (player->fixedcolormap) + { + fixedcolormap = + colormaps + + player->fixedcolormap*256*sizeof(lighttable_t); + + walllights = scalelightfixed; + + for (i=0 ; i +#include + +#include "i_system.h" +#include "z_zone.h" +#include "w_wad.h" + +#include "doomdef.h" +#include "doomstat.h" + +#include "r_local.h" +#include "r_sky.h" + + + +planefunction_t floorfunc; +planefunction_t ceilingfunc; + +// +// opening +// + +// Here comes the obnoxious "visplane". +#define MAXVISPLANES 128 +visplane_t visplanes[MAXVISPLANES]; +visplane_t* lastvisplane; +visplane_t* floorplane; +visplane_t* ceilingplane; + +// ? +#define MAXOPENINGS SCREENWIDTH*64 +short openings[MAXOPENINGS]; +short* lastopening; + + +// +// Clip values are the solid pixel bounding the range. +// floorclip starts out SCREENHEIGHT +// ceilingclip starts out -1 +// +short floorclip[SCREENWIDTH]; +short ceilingclip[SCREENWIDTH]; + +// +// spanstart holds the start of a plane span +// initialized to 0 at start +// +int spanstart[SCREENHEIGHT]; +int spanstop[SCREENHEIGHT]; + +// +// texture mapping +// +lighttable_t** planezlight; +fixed_t planeheight; + +fixed_t yslope[SCREENHEIGHT]; +fixed_t distscale[SCREENWIDTH]; +fixed_t basexscale; +fixed_t baseyscale; + +fixed_t cachedheight[SCREENHEIGHT]; +fixed_t cacheddistance[SCREENHEIGHT]; +fixed_t cachedxstep[SCREENHEIGHT]; +fixed_t cachedystep[SCREENHEIGHT]; + + + +// +// R_InitPlanes +// Only at game startup. +// +void R_InitPlanes (void) +{ + // Doh! +} + + +// +// R_MapPlane +// +// Uses global vars: +// planeheight +// ds_source +// basexscale +// baseyscale +// viewx +// viewy +// +// BASIC PRIMITIVE +// +void +R_MapPlane +( int y, + int x1, + int x2 ) +{ + angle_t angle; + fixed_t distance; + fixed_t length; + unsigned index; + +#ifdef RANGECHECK + if (x2 < x1 + || x1 < 0 + || x2 >= viewwidth + || y > viewheight) + { + I_Error ("R_MapPlane: %i, %i at %i",x1,x2,y); + } +#endif + + if (planeheight != cachedheight[y]) + { + cachedheight[y] = planeheight; + distance = cacheddistance[y] = FixedMul (planeheight, yslope[y]); + ds_xstep = cachedxstep[y] = FixedMul (distance,basexscale); + ds_ystep = cachedystep[y] = FixedMul (distance,baseyscale); + } + else + { + distance = cacheddistance[y]; + ds_xstep = cachedxstep[y]; + ds_ystep = cachedystep[y]; + } + + length = FixedMul (distance,distscale[x1]); + angle = (viewangle + xtoviewangle[x1])>>ANGLETOFINESHIFT; + ds_xfrac = viewx + FixedMul(finecosine[angle], length); + ds_yfrac = -viewy - FixedMul(finesine[angle], length); + + if (fixedcolormap) + ds_colormap = fixedcolormap; + else + { + index = distance >> LIGHTZSHIFT; + + if (index >= MAXLIGHTZ ) + index = MAXLIGHTZ-1; + + ds_colormap = planezlight[index]; + } + + ds_y = y; + ds_x1 = x1; + ds_x2 = x2; + + // high or low detail + spanfunc (); +} + + +// +// R_ClearPlanes +// At begining of frame. +// +void R_ClearPlanes (void) +{ + int i; + angle_t angle; + + // opening / clipping determination + for (i=0 ; i>ANGLETOFINESHIFT; + + // scale will be unit scale at SCREENWIDTH/2 distance + basexscale = FixedDiv (finecosine[angle],centerxfrac); + baseyscale = -FixedDiv (finesine[angle],centerxfrac); +} + + + + +// +// R_FindPlane +// +visplane_t* +R_FindPlane +( fixed_t height, + int picnum, + int lightlevel ) +{ + visplane_t* check; + + if (picnum == skyflatnum) + { + height = 0; // all skys map together + lightlevel = 0; + } + + for (check=visplanes; checkheight + && picnum == check->picnum + && lightlevel == check->lightlevel) + { + break; + } + } + + + if (check < lastvisplane) + return check; + + if (lastvisplane - visplanes == MAXVISPLANES) + I_Error ("R_FindPlane: no more visplanes"); + + lastvisplane++; + + check->height = height; + check->picnum = picnum; + check->lightlevel = lightlevel; + check->minx = SCREENWIDTH; + check->maxx = -1; + + memset (check->top,0xff,sizeof(check->top)); + + return check; +} + + +// +// R_CheckPlane +// +visplane_t* +R_CheckPlane +( visplane_t* pl, + int start, + int stop ) +{ + int intrl; + int intrh; + int unionl; + int unionh; + int x; + + if (start < pl->minx) + { + intrl = pl->minx; + unionl = start; + } + else + { + unionl = pl->minx; + intrl = start; + } + + if (stop > pl->maxx) + { + intrh = pl->maxx; + unionh = stop; + } + else + { + unionh = pl->maxx; + intrh = stop; + } + + for (x=intrl ; x<= intrh ; x++) + if (pl->top[x] != 0xff) + break; + + if (x > intrh) + { + pl->minx = unionl; + pl->maxx = unionh; + + // use the same one + return pl; + } + + // make a new visplane + lastvisplane->height = pl->height; + lastvisplane->picnum = pl->picnum; + lastvisplane->lightlevel = pl->lightlevel; + + pl = lastvisplane++; + pl->minx = start; + pl->maxx = stop; + + memset (pl->top,0xff,sizeof(pl->top)); + + return pl; +} + + +// +// R_MakeSpans +// +void +R_MakeSpans +( int x, + int t1, + int b1, + int t2, + int b2 ) +{ + while (t1 < t2 && t1<=b1) + { + R_MapPlane (t1,spanstart[t1],x-1); + t1++; + } + while (b1 > b2 && b1>=t1) + { + R_MapPlane (b1,spanstart[b1],x-1); + b1--; + } + + while (t2 < t1 && t2<=b2) + { + spanstart[t2] = x; + t2++; + } + while (b2 > b1 && b2>=t2) + { + spanstart[b2] = x; + b2--; + } +} + + + +// +// R_DrawPlanes +// At the end of each frame. +// +void R_DrawPlanes (void) +{ + visplane_t* pl; + int light; + int x; + int stop; + int angle; + int lumpnum; + +#ifdef RANGECHECK + if (ds_p - drawsegs > MAXDRAWSEGS) + I_Error ("R_DrawPlanes: drawsegs overflow (%i)", + ds_p - drawsegs); + + if (lastvisplane - visplanes > MAXVISPLANES) + I_Error ("R_DrawPlanes: visplane overflow (%i)", + lastvisplane - visplanes); + + if (lastopening - openings > MAXOPENINGS) + I_Error ("R_DrawPlanes: opening overflow (%i)", + lastopening - openings); +#endif + + for (pl = visplanes ; pl < lastvisplane ; pl++) + { + if (pl->minx > pl->maxx) + continue; + + + // sky flat + if (pl->picnum == skyflatnum) + { + dc_iscale = pspriteiscale>>detailshift; + + // Sky is allways drawn full bright, + // i.e. colormaps[0] is used. + // Because of this hack, sky is not affected + // by INVUL inverse mapping. + dc_colormap = colormaps; + dc_texturemid = skytexturemid; + for (x=pl->minx ; x <= pl->maxx ; x++) + { + dc_yl = pl->top[x]; + dc_yh = pl->bottom[x]; + + if (dc_yl <= dc_yh) + { + angle = (viewangle + xtoviewangle[x])>>ANGLETOSKYSHIFT; + dc_x = x; + dc_source = R_GetColumn(skytexture, angle); + colfunc (); + } + } + continue; + } + + // regular flat + lumpnum = firstflat + flattranslation[pl->picnum]; + ds_source = W_CacheLumpNum(lumpnum, PU_STATIC); + + planeheight = abs(pl->height-viewz); + light = (pl->lightlevel >> LIGHTSEGSHIFT)+extralight; + + if (light >= LIGHTLEVELS) + light = LIGHTLEVELS-1; + + if (light < 0) + light = 0; + + planezlight = zlight[light]; + + pl->top[pl->maxx+1] = 0xff; + pl->top[pl->minx-1] = 0xff; + + stop = pl->maxx + 1; + + for (x=pl->minx ; x<= stop ; x++) + { + R_MakeSpans(x,pl->top[x-1], + pl->bottom[x-1], + pl->top[x], + pl->bottom[x]); + } + + W_ReleaseLumpNum(lumpnum); + } +} diff --git a/firmware_p4/components/Applications/doom/r_plane.h b/firmware_p4/components/Applications/doom/r_plane.h new file mode 100644 index 000000000..57b50e5bc --- /dev/null +++ b/firmware_p4/components/Applications/doom/r_plane.h @@ -0,0 +1,76 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Refresh, visplane stuff (floor, ceilings). +// + + +#ifndef __R_PLANE__ +#define __R_PLANE__ + + +#include "r_data.h" + + + +// Visplane related. +extern short* lastopening; + + +typedef void (*planefunction_t) (int top, int bottom); + +extern planefunction_t floorfunc; +extern planefunction_t ceilingfunc_t; + +extern short floorclip[SCREENWIDTH]; +extern short ceilingclip[SCREENWIDTH]; + +extern fixed_t yslope[SCREENHEIGHT]; +extern fixed_t distscale[SCREENWIDTH]; + +void R_InitPlanes (void); +void R_ClearPlanes (void); + +void +R_MapPlane +( int y, + int x1, + int x2 ); + +void +R_MakeSpans +( int x, + int t1, + int b1, + int t2, + int b2 ); + +void R_DrawPlanes (void); + +visplane_t* +R_FindPlane +( fixed_t height, + int picnum, + int lightlevel ); + +visplane_t* +R_CheckPlane +( visplane_t* pl, + int start, + int stop ); + + + +#endif diff --git a/firmware_p4/components/Applications/doom/r_segs.c b/firmware_p4/components/Applications/doom/r_segs.c new file mode 100644 index 000000000..9b4e413c8 --- /dev/null +++ b/firmware_p4/components/Applications/doom/r_segs.c @@ -0,0 +1,743 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// All the clipping: columns, horizontal spans, sky columns. +// + + + + + + +#include +#include + +#include "i_system.h" + +#include "doomdef.h" +#include "doomstat.h" + +#include "r_local.h" +#include "r_sky.h" + + +// OPTIMIZE: closed two sided lines as single sided + +// True if any of the segs textures might be visible. +boolean segtextured; + +// False if the back side is the same plane. +boolean markfloor; +boolean markceiling; + +boolean maskedtexture; +int toptexture; +int bottomtexture; +int midtexture; + + +angle_t rw_normalangle; +// angle to line origin +int rw_angle1; + +// +// regular wall +// +int rw_x; +int rw_stopx; +angle_t rw_centerangle; +fixed_t rw_offset; +fixed_t rw_distance; +fixed_t rw_scale; +fixed_t rw_scalestep; +fixed_t rw_midtexturemid; +fixed_t rw_toptexturemid; +fixed_t rw_bottomtexturemid; + +int worldtop; +int worldbottom; +int worldhigh; +int worldlow; + +fixed_t pixhigh; +fixed_t pixlow; +fixed_t pixhighstep; +fixed_t pixlowstep; + +fixed_t topfrac; +fixed_t topstep; + +fixed_t bottomfrac; +fixed_t bottomstep; + + +lighttable_t** walllights; + +short* maskedtexturecol; + + + +// +// R_RenderMaskedSegRange +// +void +R_RenderMaskedSegRange +( drawseg_t* ds, + int x1, + int x2 ) +{ + unsigned index; + column_t* col; + int lightnum; + int texnum; + + // Calculate light table. + // Use different light tables + // for horizontal / vertical / diagonal. Diagonal? + // OPTIMIZE: get rid of LIGHTSEGSHIFT globally + curline = ds->curline; + frontsector = curline->frontsector; + backsector = curline->backsector; + texnum = texturetranslation[curline->sidedef->midtexture]; + + lightnum = (frontsector->lightlevel >> LIGHTSEGSHIFT)+extralight; + + if (curline->v1->y == curline->v2->y) + lightnum--; + else if (curline->v1->x == curline->v2->x) + lightnum++; + + if (lightnum < 0) + walllights = scalelight[0]; + else if (lightnum >= LIGHTLEVELS) + walllights = scalelight[LIGHTLEVELS-1]; + else + walllights = scalelight[lightnum]; + + maskedtexturecol = ds->maskedtexturecol; + + rw_scalestep = ds->scalestep; + spryscale = ds->scale1 + (x1 - ds->x1)*rw_scalestep; + mfloorclip = ds->sprbottomclip; + mceilingclip = ds->sprtopclip; + + // find positioning + if (curline->linedef->flags & ML_DONTPEGBOTTOM) + { + dc_texturemid = frontsector->floorheight > backsector->floorheight + ? frontsector->floorheight : backsector->floorheight; + dc_texturemid = dc_texturemid + textureheight[texnum] - viewz; + } + else + { + dc_texturemid =frontsector->ceilingheightceilingheight + ? frontsector->ceilingheight : backsector->ceilingheight; + dc_texturemid = dc_texturemid - viewz; + } + dc_texturemid += curline->sidedef->rowoffset; + + if (fixedcolormap) + dc_colormap = fixedcolormap; + + // draw the columns + for (dc_x = x1 ; dc_x <= x2 ; dc_x++) + { + // calculate lighting + if (maskedtexturecol[dc_x] != SHRT_MAX) + { + if (!fixedcolormap) + { + index = spryscale>>LIGHTSCALESHIFT; + + if (index >= MAXLIGHTSCALE ) + index = MAXLIGHTSCALE-1; + + dc_colormap = walllights[index]; + } + + sprtopscreen = centeryfrac - FixedMul(dc_texturemid, spryscale); + dc_iscale = 0xffffffffu / (unsigned)spryscale; + + // draw the texture + col = (column_t *)( + (byte *)R_GetColumn(texnum,maskedtexturecol[dc_x]) -3); + + R_DrawMaskedColumn (col); + maskedtexturecol[dc_x] = SHRT_MAX; + } + spryscale += rw_scalestep; + } + +} + + + + +// +// R_RenderSegLoop +// Draws zero, one, or two textures (and possibly a masked +// texture) for walls. +// Can draw or mark the starting pixel of floor and ceiling +// textures. +// CALLED: CORE LOOPING ROUTINE. +// +#define HEIGHTBITS 12 +#define HEIGHTUNIT (1<>HEIGHTBITS; + + // no space above wall? + if (yl < ceilingclip[rw_x]+1) + yl = ceilingclip[rw_x]+1; + + if (markceiling) + { + top = ceilingclip[rw_x]+1; + bottom = yl-1; + + if (bottom >= floorclip[rw_x]) + bottom = floorclip[rw_x]-1; + + if (top <= bottom) + { + ceilingplane->top[rw_x] = top; + ceilingplane->bottom[rw_x] = bottom; + } + } + + yh = bottomfrac>>HEIGHTBITS; + + if (yh >= floorclip[rw_x]) + yh = floorclip[rw_x]-1; + + if (markfloor) + { + top = yh+1; + bottom = floorclip[rw_x]-1; + if (top <= ceilingclip[rw_x]) + top = ceilingclip[rw_x]+1; + if (top <= bottom) + { + floorplane->top[rw_x] = top; + floorplane->bottom[rw_x] = bottom; + } + } + + // texturecolumn and lighting are independent of wall tiers + if (segtextured) + { + // calculate texture offset + angle = (rw_centerangle + xtoviewangle[rw_x])>>ANGLETOFINESHIFT; + texturecolumn = rw_offset-FixedMul(finetangent[angle],rw_distance); + texturecolumn >>= FRACBITS; + // calculate lighting + index = rw_scale>>LIGHTSCALESHIFT; + + if (index >= MAXLIGHTSCALE ) + index = MAXLIGHTSCALE-1; + + dc_colormap = walllights[index]; + dc_x = rw_x; + dc_iscale = 0xffffffffu / (unsigned)rw_scale; + } + else + { + // purely to shut up the compiler + + texturecolumn = 0; + } + + // draw the wall tiers + if (midtexture) + { + // single sided line + dc_yl = yl; + dc_yh = yh; + dc_texturemid = rw_midtexturemid; + dc_source = R_GetColumn(midtexture,texturecolumn); + colfunc (); + ceilingclip[rw_x] = viewheight; + floorclip[rw_x] = -1; + } + else + { + // two sided line + if (toptexture) + { + // top wall + mid = pixhigh>>HEIGHTBITS; + pixhigh += pixhighstep; + + if (mid >= floorclip[rw_x]) + mid = floorclip[rw_x]-1; + + if (mid >= yl) + { + dc_yl = yl; + dc_yh = mid; + dc_texturemid = rw_toptexturemid; + dc_source = R_GetColumn(toptexture,texturecolumn); + colfunc (); + ceilingclip[rw_x] = mid; + } + else + ceilingclip[rw_x] = yl-1; + } + else + { + // no top wall + if (markceiling) + ceilingclip[rw_x] = yl-1; + } + + if (bottomtexture) + { + // bottom wall + mid = (pixlow+HEIGHTUNIT-1)>>HEIGHTBITS; + pixlow += pixlowstep; + + // no space above wall? + if (mid <= ceilingclip[rw_x]) + mid = ceilingclip[rw_x]+1; + + if (mid <= yh) + { + dc_yl = mid; + dc_yh = yh; + dc_texturemid = rw_bottomtexturemid; + dc_source = R_GetColumn(bottomtexture, + texturecolumn); + colfunc (); + floorclip[rw_x] = mid; + } + else + floorclip[rw_x] = yh+1; + } + else + { + // no bottom wall + if (markfloor) + floorclip[rw_x] = yh+1; + } + + if (maskedtexture) + { + // save texturecol + // for backdrawing of masked mid texture + maskedtexturecol[rw_x] = texturecolumn; + } + } + + rw_scale += rw_scalestep; + topfrac += topstep; + bottomfrac += bottomstep; + } +} + + + + +// +// R_StoreWallRange +// A wall segment will be drawn +// between start and stop pixels (inclusive). +// +void +R_StoreWallRange +( int start, + int stop ) +{ + fixed_t hyp; + fixed_t sineval; + angle_t distangle, offsetangle; + fixed_t vtop; + int lightnum; + + // don't overflow and crash + if (ds_p == &drawsegs[MAXDRAWSEGS]) + return; + +#ifdef RANGECHECK + if (start >=viewwidth || start > stop) + I_Error ("Bad R_RenderWallRange: %i to %i", start , stop); +#endif + + sidedef = curline->sidedef; + linedef = curline->linedef; + + // mark the segment as visible for auto map + linedef->flags |= ML_MAPPED; + + // calculate rw_distance for scale calculation + rw_normalangle = curline->angle + ANG90; + offsetangle = abs(rw_normalangle-rw_angle1); + + if (offsetangle > ANG90) + offsetangle = ANG90; + + distangle = ANG90 - offsetangle; + hyp = R_PointToDist (curline->v1->x, curline->v1->y); + sineval = finesine[distangle>>ANGLETOFINESHIFT]; + rw_distance = FixedMul (hyp, sineval); + + + ds_p->x1 = rw_x = start; + ds_p->x2 = stop; + ds_p->curline = curline; + rw_stopx = stop+1; + + // calculate scale at both ends and step + ds_p->scale1 = rw_scale = + R_ScaleFromGlobalAngle (viewangle + xtoviewangle[start]); + + if (stop > start ) + { + ds_p->scale2 = R_ScaleFromGlobalAngle (viewangle + xtoviewangle[stop]); + ds_p->scalestep = rw_scalestep = + (ds_p->scale2 - rw_scale) / (stop-start); + } + else + { + // UNUSED: try to fix the stretched line bug +#if 0 + if (rw_distance < FRACUNIT/2) + { + fixed_t trx,try; + fixed_t gxt,gyt; + + trx = curline->v1->x - viewx; + try = curline->v1->y - viewy; + + gxt = FixedMul(trx,viewcos); + gyt = -FixedMul(try,viewsin); + ds_p->scale1 = FixedDiv(projection, gxt-gyt)<scale2 = ds_p->scale1; + } + + // calculate texture boundaries + // and decide if floor / ceiling marks are needed + worldtop = frontsector->ceilingheight - viewz; + worldbottom = frontsector->floorheight - viewz; + + midtexture = toptexture = bottomtexture = maskedtexture = 0; + ds_p->maskedtexturecol = NULL; + + if (!backsector) + { + // single sided line + midtexture = texturetranslation[sidedef->midtexture]; + // a single sided line is terminal, so it must mark ends + markfloor = markceiling = true; + if (linedef->flags & ML_DONTPEGBOTTOM) + { + vtop = frontsector->floorheight + + textureheight[sidedef->midtexture]; + // bottom of texture at bottom + rw_midtexturemid = vtop - viewz; + } + else + { + // top of texture at top + rw_midtexturemid = worldtop; + } + rw_midtexturemid += sidedef->rowoffset; + + ds_p->silhouette = SIL_BOTH; + ds_p->sprtopclip = screenheightarray; + ds_p->sprbottomclip = negonearray; + ds_p->bsilheight = INT_MAX; + ds_p->tsilheight = INT_MIN; + } + else + { + // two sided line + ds_p->sprtopclip = ds_p->sprbottomclip = NULL; + ds_p->silhouette = 0; + + if (frontsector->floorheight > backsector->floorheight) + { + ds_p->silhouette = SIL_BOTTOM; + ds_p->bsilheight = frontsector->floorheight; + } + else if (backsector->floorheight > viewz) + { + ds_p->silhouette = SIL_BOTTOM; + ds_p->bsilheight = INT_MAX; + // ds_p->sprbottomclip = negonearray; + } + + if (frontsector->ceilingheight < backsector->ceilingheight) + { + ds_p->silhouette |= SIL_TOP; + ds_p->tsilheight = frontsector->ceilingheight; + } + else if (backsector->ceilingheight < viewz) + { + ds_p->silhouette |= SIL_TOP; + ds_p->tsilheight = INT_MIN; + // ds_p->sprtopclip = screenheightarray; + } + + if (backsector->ceilingheight <= frontsector->floorheight) + { + ds_p->sprbottomclip = negonearray; + ds_p->bsilheight = INT_MAX; + ds_p->silhouette |= SIL_BOTTOM; + } + + if (backsector->floorheight >= frontsector->ceilingheight) + { + ds_p->sprtopclip = screenheightarray; + ds_p->tsilheight = INT_MIN; + ds_p->silhouette |= SIL_TOP; + } + + worldhigh = backsector->ceilingheight - viewz; + worldlow = backsector->floorheight - viewz; + + // hack to allow height changes in outdoor areas + if (frontsector->ceilingpic == skyflatnum + && backsector->ceilingpic == skyflatnum) + { + worldtop = worldhigh; + } + + + if (worldlow != worldbottom + || backsector->floorpic != frontsector->floorpic + || backsector->lightlevel != frontsector->lightlevel) + { + markfloor = true; + } + else + { + // same plane on both sides + markfloor = false; + } + + + if (worldhigh != worldtop + || backsector->ceilingpic != frontsector->ceilingpic + || backsector->lightlevel != frontsector->lightlevel) + { + markceiling = true; + } + else + { + // same plane on both sides + markceiling = false; + } + + if (backsector->ceilingheight <= frontsector->floorheight + || backsector->floorheight >= frontsector->ceilingheight) + { + // closed door + markceiling = markfloor = true; + } + + + if (worldhigh < worldtop) + { + // top texture + toptexture = texturetranslation[sidedef->toptexture]; + if (linedef->flags & ML_DONTPEGTOP) + { + // top of texture at top + rw_toptexturemid = worldtop; + } + else + { + vtop = + backsector->ceilingheight + + textureheight[sidedef->toptexture]; + + // bottom of texture + rw_toptexturemid = vtop - viewz; + } + } + if (worldlow > worldbottom) + { + // bottom texture + bottomtexture = texturetranslation[sidedef->bottomtexture]; + + if (linedef->flags & ML_DONTPEGBOTTOM ) + { + // bottom of texture at bottom + // top of texture at top + rw_bottomtexturemid = worldtop; + } + else // top of texture at top + rw_bottomtexturemid = worldlow; + } + rw_toptexturemid += sidedef->rowoffset; + rw_bottomtexturemid += sidedef->rowoffset; + + // allocate space for masked texture tables + if (sidedef->midtexture) + { + // masked midtexture + maskedtexture = true; + ds_p->maskedtexturecol = maskedtexturecol = lastopening - rw_x; + lastopening += rw_stopx - rw_x; + } + } + + // calculate rw_offset (only needed for textured lines) + segtextured = midtexture | toptexture | bottomtexture | maskedtexture; + + if (segtextured) + { + offsetangle = rw_normalangle-rw_angle1; + + if (offsetangle > ANG180) + offsetangle = -offsetangle; + + if (offsetangle > ANG90) + offsetangle = ANG90; + + sineval = finesine[offsetangle >>ANGLETOFINESHIFT]; + rw_offset = FixedMul (hyp, sineval); + + if (rw_normalangle-rw_angle1 < ANG180) + rw_offset = -rw_offset; + + rw_offset += sidedef->textureoffset + curline->offset; + rw_centerangle = ANG90 + viewangle - rw_normalangle; + + // calculate light table + // use different light tables + // for horizontal / vertical / diagonal + // OPTIMIZE: get rid of LIGHTSEGSHIFT globally + if (!fixedcolormap) + { + lightnum = (frontsector->lightlevel >> LIGHTSEGSHIFT)+extralight; + + if (curline->v1->y == curline->v2->y) + lightnum--; + else if (curline->v1->x == curline->v2->x) + lightnum++; + + if (lightnum < 0) + walllights = scalelight[0]; + else if (lightnum >= LIGHTLEVELS) + walllights = scalelight[LIGHTLEVELS-1]; + else + walllights = scalelight[lightnum]; + } + } + + // if a floor / ceiling plane is on the wrong side + // of the view plane, it is definitely invisible + // and doesn't need to be marked. + + + if (frontsector->floorheight >= viewz) + { + // above view plane + markfloor = false; + } + + if (frontsector->ceilingheight <= viewz + && frontsector->ceilingpic != skyflatnum) + { + // below view plane + markceiling = false; + } + + + // calculate incremental stepping values for texture edges + worldtop >>= 4; + worldbottom >>= 4; + + topstep = -FixedMul (rw_scalestep, worldtop); + topfrac = (centeryfrac>>4) - FixedMul (worldtop, rw_scale); + + bottomstep = -FixedMul (rw_scalestep,worldbottom); + bottomfrac = (centeryfrac>>4) - FixedMul (worldbottom, rw_scale); + + if (backsector) + { + worldhigh >>= 4; + worldlow >>= 4; + + if (worldhigh < worldtop) + { + pixhigh = (centeryfrac>>4) - FixedMul (worldhigh, rw_scale); + pixhighstep = -FixedMul (rw_scalestep,worldhigh); + } + + if (worldlow > worldbottom) + { + pixlow = (centeryfrac>>4) - FixedMul (worldlow, rw_scale); + pixlowstep = -FixedMul (rw_scalestep,worldlow); + } + } + + // render it + if (markceiling) + ceilingplane = R_CheckPlane (ceilingplane, rw_x, rw_stopx-1); + + if (markfloor) + floorplane = R_CheckPlane (floorplane, rw_x, rw_stopx-1); + + R_RenderSegLoop (); + + + // save sprite clipping info + if ( ((ds_p->silhouette & SIL_TOP) || maskedtexture) + && !ds_p->sprtopclip) + { + memcpy (lastopening, ceilingclip+start, 2*(rw_stopx-start)); + ds_p->sprtopclip = lastopening - start; + lastopening += rw_stopx - start; + } + + if ( ((ds_p->silhouette & SIL_BOTTOM) || maskedtexture) + && !ds_p->sprbottomclip) + { + memcpy (lastopening, floorclip+start, 2*(rw_stopx-start)); + ds_p->sprbottomclip = lastopening - start; + lastopening += rw_stopx - start; + } + + if (maskedtexture && !(ds_p->silhouette&SIL_TOP)) + { + ds_p->silhouette |= SIL_TOP; + ds_p->tsilheight = INT_MIN; + } + if (maskedtexture && !(ds_p->silhouette&SIL_BOTTOM)) + { + ds_p->silhouette |= SIL_BOTTOM; + ds_p->bsilheight = INT_MAX; + } + ds_p++; +} + diff --git a/firmware_p4/components/Applications/doom/r_segs.h b/firmware_p4/components/Applications/doom/r_segs.h new file mode 100644 index 000000000..d4a4d8996 --- /dev/null +++ b/firmware_p4/components/Applications/doom/r_segs.h @@ -0,0 +1,33 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Refresh module, drawing LineSegs from BSP. +// + + +#ifndef __R_SEGS__ +#define __R_SEGS__ + + + + +void +R_RenderMaskedSegRange +( drawseg_t* ds, + int x1, + int x2 ); + + +#endif diff --git a/firmware_p4/components/Applications/doom/r_sky.c b/firmware_p4/components/Applications/doom/r_sky.c new file mode 100644 index 000000000..667c88d33 --- /dev/null +++ b/firmware_p4/components/Applications/doom/r_sky.c @@ -0,0 +1,52 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Sky rendering. The DOOM sky is a texture map like any +// wall, wrapping around. A 1024 columns equal 360 degrees. +// The default sky map is 256 columns and repeats 4 times +// on a 320 screen? +// +// + + + +// Needed for FRACUNIT. +#include "m_fixed.h" + +// Needed for Flat retrieval. +#include "r_data.h" + + +#include "r_sky.h" + +// +// sky mapping +// +int skyflatnum; +int skytexture; +int skytexturemid; + + + +// +// R_InitSkyMap +// Called whenever the view size changes. +// +void R_InitSkyMap (void) +{ + // skyflatnum = R_FlatNumForName ( SKYFLATNAME ); + skytexturemid = 100*FRACUNIT; +} + diff --git a/firmware_p4/components/Applications/doom/r_sky.h b/firmware_p4/components/Applications/doom/r_sky.h new file mode 100644 index 000000000..8ad6680aa --- /dev/null +++ b/firmware_p4/components/Applications/doom/r_sky.h @@ -0,0 +1,37 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Sky rendering. +// + + +#ifndef __R_SKY__ +#define __R_SKY__ + + + +// SKY, store the number for name. +#define SKYFLATNAME "F_SKY1" + +// The sky map is 256*128*4 maps. +#define ANGLETOSKYSHIFT 22 + +extern int skytexture; +extern int skytexturemid; + +// Called whenever the view size changes. +void R_InitSkyMap (void); + +#endif diff --git a/firmware_p4/components/Applications/doom/r_state.h b/firmware_p4/components/Applications/doom/r_state.h new file mode 100644 index 000000000..2a60e2fe8 --- /dev/null +++ b/firmware_p4/components/Applications/doom/r_state.h @@ -0,0 +1,127 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Refresh/render internal state variables (global). +// + + +#ifndef __R_STATE__ +#define __R_STATE__ + +// Need data structure definitions. +#include "d_player.h" +#include "r_data.h" + + + + + + +// +// Refresh internal data structures, +// for rendering. +// + +// needed for texture pegging +extern fixed_t* textureheight; + +// needed for pre rendering (fracs) +extern fixed_t* spritewidth; + +extern fixed_t* spriteoffset; +extern fixed_t* spritetopoffset; + +extern lighttable_t* colormaps; + +extern int viewwidth; +extern int scaledviewwidth; +extern int viewheight; + +extern int firstflat; + +// for global animation +extern int* flattranslation; +extern int* texturetranslation; + + +// Sprite.... +extern int firstspritelump; +extern int lastspritelump; +extern int numspritelumps; + + + +// +// Lookup tables for map data. +// +extern int numsprites; +extern spritedef_t* sprites; + +extern int numvertexes; +extern vertex_t* vertexes; + +extern int numsegs; +extern seg_t* segs; + +extern int numsectors; +extern sector_t* sectors; + +extern int numsubsectors; +extern subsector_t* subsectors; + +extern int numnodes; +extern node_t* nodes; + +extern int numlines; +extern line_t* lines; + +extern int numsides; +extern side_t* sides; + + +// +// POV data. +// +extern fixed_t viewx; +extern fixed_t viewy; +extern fixed_t viewz; + +extern angle_t viewangle; +extern player_t* viewplayer; + + +// ? +extern angle_t clipangle; + +extern int viewangletox[FINEANGLES/2]; +extern angle_t xtoviewangle[SCREENWIDTH+1]; +//extern fixed_t finetangent[FINEANGLES/2]; + +extern fixed_t rw_distance; +extern angle_t rw_normalangle; + + + +// angle to line origin +extern int rw_angle1; + +// Segs count? +extern int sscount; + +extern visplane_t* floorplane; +extern visplane_t* ceilingplane; + + +#endif diff --git a/firmware_p4/components/Applications/doom/r_things.c b/firmware_p4/components/Applications/doom/r_things.c new file mode 100644 index 000000000..74e7369b8 --- /dev/null +++ b/firmware_p4/components/Applications/doom/r_things.c @@ -0,0 +1,982 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Refresh of things, i.e. objects represented by sprites. +// + + + + +#include +#include + + +#include "deh_main.h" +#include "doomdef.h" + +#include "i_swap.h" +#include "i_system.h" +#include "z_zone.h" +#include "w_wad.h" + +#include "r_local.h" + +#include "doomstat.h" + + + +#define MINZ (FRACUNIT*4) +#define BASEYCENTER 100 + +//void R_DrawColumn (void); +//void R_DrawFuzzColumn (void); + + + +typedef struct +{ + int x1; + int x2; + + int column; + int topclip; + int bottomclip; + +} maskdraw_t; + + + +// +// Sprite rotation 0 is facing the viewer, +// rotation 1 is one angle turn CLOCKWISE around the axis. +// This is not the same as the angle, +// which increases counter clockwise (protractor). +// There was a lot of stuff grabbed wrong, so I changed it... +// +fixed_t pspritescale; +fixed_t pspriteiscale; + +lighttable_t** spritelights; + +// constant arrays +// used for psprite clipping and initializing clipping +short negonearray[SCREENWIDTH]; +short screenheightarray[SCREENWIDTH]; + + +// +// INITIALIZATION FUNCTIONS +// + +// variables used to look up +// and range check thing_t sprites patches +spritedef_t* sprites; +int numsprites; + +spriteframe_t sprtemp[29]; +int maxframe; +char* spritename; + + + + +// +// R_InstallSpriteLump +// Local function for R_InitSprites. +// +void +R_InstallSpriteLump +( int lump, + unsigned frame, + unsigned rotation, + boolean flipped ) +{ + int r; + + if (frame >= 29 || rotation > 8) + I_Error("R_InstallSpriteLump: " + "Bad frame characters in lump %i", lump); + + if ((int)frame > maxframe) + maxframe = frame; + + if (rotation == 0) + { + // the lump should be used for all rotations + if (sprtemp[frame].rotate == false) + I_Error ("R_InitSprites: Sprite %s frame %c has " + "multip rot=0 lump", spritename, 'A'+frame); + + if (sprtemp[frame].rotate == true) + I_Error ("R_InitSprites: Sprite %s frame %c has rotations " + "and a rot=0 lump", spritename, 'A'+frame); + + sprtemp[frame].rotate = false; + for (r=0 ; r<8 ; r++) + { + sprtemp[frame].lump[r] = lump - firstspritelump; + sprtemp[frame].flip[r] = (byte)flipped; + } + return; + } + + // the lump is only used for one rotation + if (sprtemp[frame].rotate == false) + I_Error ("R_InitSprites: Sprite %s frame %c has rotations " + "and a rot=0 lump", spritename, 'A'+frame); + + sprtemp[frame].rotate = true; + + // make 0 based + rotation--; + if (sprtemp[frame].lump[rotation] != -1) + I_Error ("R_InitSprites: Sprite %s : %c : %c " + "has two lumps mapped to it", + spritename, 'A'+frame, '1'+rotation); + + sprtemp[frame].lump[rotation] = lump - firstspritelump; + sprtemp[frame].flip[rotation] = (byte)flipped; +} + + + + +// +// R_InitSpriteDefs +// Pass a null terminated list of sprite names +// (4 chars exactly) to be used. +// Builds the sprite rotation matrixes to account +// for horizontally flipped sprites. +// Will report an error if the lumps are inconsistant. +// Only called at startup. +// +// Sprite lump names are 4 characters for the actor, +// a letter for the frame, and a number for the rotation. +// A sprite that is flippable will have an additional +// letter/number appended. +// The rotation character can be 0 to signify no rotations. +// +void R_InitSpriteDefs (char** namelist) +{ + char** check; + int i; + int l; + int frame; + int rotation; + int start; + int end; + int patched; + + // count the number of sprite names + check = namelist; + while (*check != NULL) + check++; + + numsprites = check-namelist; + + if (!numsprites) + return; + + sprites = Z_Malloc(numsprites *sizeof(*sprites), PU_STATIC, NULL); + + start = firstspritelump-1; + end = lastspritelump+1; + + // scan all the lump names for each of the names, + // noting the highest frame letter. + // Just compare 4 characters as ints + for (i=0 ; itopdelta != 0xff ; ) + { + // calculate unclipped screen coordinates + // for post + topscreen = sprtopscreen + spryscale*column->topdelta; + bottomscreen = topscreen + spryscale*column->length; + + dc_yl = (topscreen+FRACUNIT-1)>>FRACBITS; + dc_yh = (bottomscreen-1)>>FRACBITS; + + if (dc_yh >= mfloorclip[dc_x]) + dc_yh = mfloorclip[dc_x]-1; + if (dc_yl <= mceilingclip[dc_x]) + dc_yl = mceilingclip[dc_x]+1; + + if (dc_yl <= dc_yh) + { + dc_source = (byte *)column + 3; + dc_texturemid = basetexturemid - (column->topdelta<topdelta; + + // Drawn by either R_DrawColumn + // or (SHADOW) R_DrawFuzzColumn. + colfunc (); + } + column = (column_t *)( (byte *)column + column->length + 4); + } + + dc_texturemid = basetexturemid; +} + + + +// +// R_DrawVisSprite +// mfloorclip and mceilingclip should also be set. +// +void +R_DrawVisSprite +( vissprite_t* vis, + int x1, + int x2 ) +{ + column_t* column; + int texturecolumn; + fixed_t frac; + patch_t* patch; + + + patch = W_CacheLumpNum (vis->patch+firstspritelump, PU_CACHE); + + dc_colormap = vis->colormap; + + if (!dc_colormap) + { + // NULL colormap = shadow draw + colfunc = fuzzcolfunc; + } + else if (vis->mobjflags & MF_TRANSLATION) + { + colfunc = transcolfunc; + dc_translation = translationtables - 256 + + ( (vis->mobjflags & MF_TRANSLATION) >> (MF_TRANSSHIFT-8) ); + } + + dc_iscale = abs(vis->xiscale)>>detailshift; + dc_texturemid = vis->texturemid; + frac = vis->startfrac; + spryscale = vis->scale; + sprtopscreen = centeryfrac - FixedMul(dc_texturemid,spryscale); + + for (dc_x=vis->x1 ; dc_x<=vis->x2 ; dc_x++, frac += vis->xiscale) + { + texturecolumn = frac>>FRACBITS; +#ifdef RANGECHECK + if (texturecolumn < 0 || texturecolumn >= SHORT(patch->width)) + I_Error ("R_DrawSpriteRange: bad texturecolumn"); +#endif + column = (column_t *) ((byte *)patch + + LONG(patch->columnofs[texturecolumn])); + R_DrawMaskedColumn (column); + } + + colfunc = basecolfunc; +} + + + +// +// R_ProjectSprite +// Generates a vissprite for a thing +// if it might be visible. +// +void R_ProjectSprite (mobj_t* thing) +{ + fixed_t tr_x; + fixed_t tr_y; + + fixed_t gxt; + fixed_t gyt; + + fixed_t tx; + fixed_t tz; + + fixed_t xscale; + + int x1; + int x2; + + spritedef_t* sprdef; + spriteframe_t* sprframe; + int lump; + + unsigned rot; + boolean flip; + + int index; + + vissprite_t* vis; + + angle_t ang; + fixed_t iscale; + + // transform the origin point + tr_x = thing->x - viewx; + tr_y = thing->y - viewy; + + gxt = FixedMul(tr_x,viewcos); + gyt = -FixedMul(tr_y,viewsin); + + tz = gxt-gyt; + + // thing is behind view plane? + if (tz < MINZ) + return; + + xscale = FixedDiv(projection, tz); + + gxt = -FixedMul(tr_x,viewsin); + gyt = FixedMul(tr_y,viewcos); + tx = -(gyt+gxt); + + // too far off the side? + if (abs(tx)>(tz<<2)) + return; + + // decide which patch to use for sprite relative to player +#ifdef RANGECHECK + if ((unsigned int) thing->sprite >= (unsigned int) numsprites) + I_Error ("R_ProjectSprite: invalid sprite number %i ", + thing->sprite); +#endif + sprdef = &sprites[thing->sprite]; +#ifdef RANGECHECK + if ( (thing->frame&FF_FRAMEMASK) >= sprdef->numframes ) + I_Error ("R_ProjectSprite: invalid sprite frame %i : %i ", + thing->sprite, thing->frame); +#endif + sprframe = &sprdef->spriteframes[ thing->frame & FF_FRAMEMASK]; + + if (sprframe->rotate) + { + // choose a different rotation based on player view + ang = R_PointToAngle (thing->x, thing->y); + rot = (ang-thing->angle+(unsigned)(ANG45/2)*9)>>29; + lump = sprframe->lump[rot]; + flip = (boolean)sprframe->flip[rot]; + } + else + { + // use single rotation for all views + lump = sprframe->lump[0]; + flip = (boolean)sprframe->flip[0]; + } + + // calculate edges of the shape + tx -= spriteoffset[lump]; + x1 = (centerxfrac + FixedMul (tx,xscale) ) >>FRACBITS; + + // off the right side? + if (x1 > viewwidth) + return; + + tx += spritewidth[lump]; + x2 = ((centerxfrac + FixedMul (tx,xscale) ) >>FRACBITS) - 1; + + // off the left side + if (x2 < 0) + return; + + // store information in a vissprite + vis = R_NewVisSprite (); + vis->mobjflags = thing->flags; + vis->scale = xscale<gx = thing->x; + vis->gy = thing->y; + vis->gz = thing->z; + vis->gzt = thing->z + spritetopoffset[lump]; + vis->texturemid = vis->gzt - viewz; + vis->x1 = x1 < 0 ? 0 : x1; + vis->x2 = x2 >= viewwidth ? viewwidth-1 : x2; + iscale = FixedDiv (FRACUNIT, xscale); + + if (flip) + { + vis->startfrac = spritewidth[lump]-1; + vis->xiscale = -iscale; + } + else + { + vis->startfrac = 0; + vis->xiscale = iscale; + } + + if (vis->x1 > x1) + vis->startfrac += vis->xiscale*(vis->x1-x1); + vis->patch = lump; + + // get light level + if (thing->flags & MF_SHADOW) + { + // shadow draw + vis->colormap = NULL; + } + else if (fixedcolormap) + { + // fixed map + vis->colormap = fixedcolormap; + } + else if (thing->frame & FF_FULLBRIGHT) + { + // full bright + vis->colormap = colormaps; + } + + else + { + // diminished light + index = xscale>>(LIGHTSCALESHIFT-detailshift); + + if (index >= MAXLIGHTSCALE) + index = MAXLIGHTSCALE-1; + + vis->colormap = spritelights[index]; + } +} + + + + +// +// R_AddSprites +// During BSP traversal, this adds sprites by sector. +// +void R_AddSprites (sector_t* sec) +{ + mobj_t* thing; + int lightnum; + + // BSP is traversed by subsector. + // A sector might have been split into several + // subsectors during BSP building. + // Thus we check whether its already added. + if (sec->validcount == validcount) + return; + + // Well, now it will be done. + sec->validcount = validcount; + + lightnum = (sec->lightlevel >> LIGHTSEGSHIFT)+extralight; + + if (lightnum < 0) + spritelights = scalelight[0]; + else if (lightnum >= LIGHTLEVELS) + spritelights = scalelight[LIGHTLEVELS-1]; + else + spritelights = scalelight[lightnum]; + + // Handle all things in sector. + for (thing = sec->thinglist ; thing ; thing = thing->snext) + R_ProjectSprite (thing); +} + + +// +// R_DrawPSprite +// +void R_DrawPSprite (pspdef_t* psp) +{ + fixed_t tx; + int x1; + int x2; + spritedef_t* sprdef; + spriteframe_t* sprframe; + int lump; + boolean flip; + vissprite_t* vis; + vissprite_t avis; + + // decide which patch to use +#ifdef RANGECHECK + if ( (unsigned)psp->state->sprite >= (unsigned int) numsprites) + I_Error ("R_ProjectSprite: invalid sprite number %i ", + psp->state->sprite); +#endif + sprdef = &sprites[psp->state->sprite]; +#ifdef RANGECHECK + if ( (psp->state->frame & FF_FRAMEMASK) >= sprdef->numframes) + I_Error ("R_ProjectSprite: invalid sprite frame %i : %i ", + psp->state->sprite, psp->state->frame); +#endif + sprframe = &sprdef->spriteframes[ psp->state->frame & FF_FRAMEMASK ]; + + lump = sprframe->lump[0]; + flip = (boolean)sprframe->flip[0]; + + // calculate edges of the shape + tx = psp->sx-160*FRACUNIT; + + tx -= spriteoffset[lump]; + x1 = (centerxfrac + FixedMul (tx,pspritescale) ) >>FRACBITS; + + // off the right side + if (x1 > viewwidth) + return; + + tx += spritewidth[lump]; + x2 = ((centerxfrac + FixedMul (tx, pspritescale) ) >>FRACBITS) - 1; + + // off the left side + if (x2 < 0) + return; + + // store information in a vissprite + vis = &avis; + vis->mobjflags = 0; + vis->texturemid = (BASEYCENTER<sy-spritetopoffset[lump]); + vis->x1 = x1 < 0 ? 0 : x1; + vis->x2 = x2 >= viewwidth ? viewwidth-1 : x2; + vis->scale = pspritescale<xiscale = -pspriteiscale; + vis->startfrac = spritewidth[lump]-1; + } + else + { + vis->xiscale = pspriteiscale; + vis->startfrac = 0; + } + + if (vis->x1 > x1) + vis->startfrac += vis->xiscale*(vis->x1-x1); + + vis->patch = lump; + + if (viewplayer->powers[pw_invisibility] > 4*32 + || viewplayer->powers[pw_invisibility] & 8) + { + // shadow draw + vis->colormap = NULL; + } + else if (fixedcolormap) + { + // fixed color + vis->colormap = fixedcolormap; + } + else if (psp->state->frame & FF_FULLBRIGHT) + { + // full bright + vis->colormap = colormaps; + } + else + { + // local light + vis->colormap = spritelights[MAXLIGHTSCALE-1]; + } + + R_DrawVisSprite (vis, vis->x1, vis->x2); +} + + + +// +// R_DrawPlayerSprites +// +void R_DrawPlayerSprites (void) +{ + int i; + int lightnum; + pspdef_t* psp; + + // get light level + lightnum = + (viewplayer->mo->subsector->sector->lightlevel >> LIGHTSEGSHIFT) + +extralight; + + if (lightnum < 0) + spritelights = scalelight[0]; + else if (lightnum >= LIGHTLEVELS) + spritelights = scalelight[LIGHTLEVELS-1]; + else + spritelights = scalelight[lightnum]; + + // clip to screen bounds + mfloorclip = screenheightarray; + mceilingclip = negonearray; + + // add all active psprites + for (i=0, psp=viewplayer->psprites; + istate) + R_DrawPSprite (psp); + } +} + + + + +// +// R_SortVisSprites +// +vissprite_t vsprsortedhead; + + +void R_SortVisSprites (void) +{ + int i; + int count; + vissprite_t* ds; + vissprite_t* best; + vissprite_t unsorted; + fixed_t bestscale; + + count = vissprite_p - vissprites; + + unsorted.next = unsorted.prev = &unsorted; + + if (!count) + return; + + for (ds=vissprites ; dsnext = ds+1; + ds->prev = ds-1; + } + + vissprites[0].prev = &unsorted; + unsorted.next = &vissprites[0]; + (vissprite_p-1)->next = &unsorted; + unsorted.prev = vissprite_p-1; + + // pull the vissprites out by scale + + vsprsortedhead.next = vsprsortedhead.prev = &vsprsortedhead; + for (i=0 ; inext) + { + if (ds->scale < bestscale) + { + bestscale = ds->scale; + best = ds; + } + } + best->next->prev = best->prev; + best->prev->next = best->next; + best->next = &vsprsortedhead; + best->prev = vsprsortedhead.prev; + vsprsortedhead.prev->next = best; + vsprsortedhead.prev = best; + } +} + + + +// +// R_DrawSprite +// +static short clipbot[SCREENWIDTH]; +static short cliptop[SCREENWIDTH]; +void R_DrawSprite (vissprite_t* spr) +{ + drawseg_t* ds; + int x; + int r1; + int r2; + fixed_t scale; + fixed_t lowscale; + int silhouette; + + for (x = spr->x1 ; x<=spr->x2 ; x++) + clipbot[x] = cliptop[x] = -2; + + // Scan drawsegs from end to start for obscuring segs. + // The first drawseg that has a greater scale + // is the clip seg. + for (ds=ds_p-1 ; ds >= drawsegs ; ds--) + { + // determine if the drawseg obscures the sprite + if (ds->x1 > spr->x2 + || ds->x2 < spr->x1 + || (!ds->silhouette + && !ds->maskedtexturecol) ) + { + // does not cover sprite + continue; + } + + r1 = ds->x1 < spr->x1 ? spr->x1 : ds->x1; + r2 = ds->x2 > spr->x2 ? spr->x2 : ds->x2; + + if (ds->scale1 > ds->scale2) + { + lowscale = ds->scale2; + scale = ds->scale1; + } + else + { + lowscale = ds->scale1; + scale = ds->scale2; + } + + if (scale < spr->scale + || ( lowscale < spr->scale + && !R_PointOnSegSide (spr->gx, spr->gy, ds->curline) ) ) + { + // masked mid texture? + if (ds->maskedtexturecol) + R_RenderMaskedSegRange (ds, r1, r2); + // seg is behind sprite + continue; + } + + + // clip this piece of the sprite + silhouette = ds->silhouette; + + if (spr->gz >= ds->bsilheight) + silhouette &= ~SIL_BOTTOM; + + if (spr->gzt <= ds->tsilheight) + silhouette &= ~SIL_TOP; + + if (silhouette == 1) + { + // bottom sil + for (x=r1 ; x<=r2 ; x++) + if (clipbot[x] == -2) + clipbot[x] = ds->sprbottomclip[x]; + } + else if (silhouette == 2) + { + // top sil + for (x=r1 ; x<=r2 ; x++) + if (cliptop[x] == -2) + cliptop[x] = ds->sprtopclip[x]; + } + else if (silhouette == 3) + { + // both + for (x=r1 ; x<=r2 ; x++) + { + if (clipbot[x] == -2) + clipbot[x] = ds->sprbottomclip[x]; + if (cliptop[x] == -2) + cliptop[x] = ds->sprtopclip[x]; + } + } + + } + + // all clipping has been performed, so draw the sprite + + // check for unclipped columns + for (x = spr->x1 ; x<=spr->x2 ; x++) + { + if (clipbot[x] == -2) + clipbot[x] = viewheight; + + if (cliptop[x] == -2) + cliptop[x] = -1; + } + + mfloorclip = clipbot; + mceilingclip = cliptop; + R_DrawVisSprite (spr, spr->x1, spr->x2); +} + + + + +// +// R_DrawMasked +// +void R_DrawMasked (void) +{ + vissprite_t* spr; + drawseg_t* ds; + + R_SortVisSprites (); + + if (vissprite_p > vissprites) + { + // draw all vissprites back to front + for (spr = vsprsortedhead.next ; + spr != &vsprsortedhead ; + spr=spr->next) + { + + R_DrawSprite (spr); + } + } + + // render any remaining masked mid textures + for (ds=ds_p-1 ; ds >= drawsegs ; ds--) + if (ds->maskedtexturecol) + R_RenderMaskedSegRange (ds, ds->x1, ds->x2); + + // draw the psprites on top of everything + // but does not draw on side views + if (!viewangleoffset) + R_DrawPlayerSprites (); +} + + + diff --git a/firmware_p4/components/Applications/doom/r_things.h b/firmware_p4/components/Applications/doom/r_things.h new file mode 100644 index 000000000..256a5ebbe --- /dev/null +++ b/firmware_p4/components/Applications/doom/r_things.h @@ -0,0 +1,65 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Rendering of moving objects, sprites. +// + + +#ifndef __R_THINGS__ +#define __R_THINGS__ + + + +#define MAXVISSPRITES 128 + +extern vissprite_t vissprites[MAXVISSPRITES]; +extern vissprite_t* vissprite_p; +extern vissprite_t vsprsortedhead; + +// Constant arrays used for psprite clipping +// and initializing clipping. +extern short negonearray[SCREENWIDTH]; +extern short screenheightarray[SCREENWIDTH]; + +// vars for R_DrawMaskedColumn +extern short* mfloorclip; +extern short* mceilingclip; +extern fixed_t spryscale; +extern fixed_t sprtopscreen; + +extern fixed_t pspritescale; +extern fixed_t pspriteiscale; + + +void R_DrawMaskedColumn (column_t* column); + + +void R_SortVisSprites (void); + +void R_AddSprites (sector_t* sec); +void R_AddPSprites (void); +void R_DrawSprites (void); +void R_InitSprites (char** namelist); +void R_ClearSprites (void); +void R_DrawMasked (void); + +void +R_ClipVisSprite +( vissprite_t* vis, + int xl, + int xh ); + + +#endif diff --git a/firmware_p4/components/Applications/doom/s_sound.c b/firmware_p4/components/Applications/doom/s_sound.c new file mode 100644 index 000000000..f6d8be138 --- /dev/null +++ b/firmware_p4/components/Applications/doom/s_sound.c @@ -0,0 +1,670 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: none +// + +#include +#include + +#include "i_sound.h" +#include "i_system.h" + +#include "doomfeatures.h" +#include "deh_str.h" + +#include "doomstat.h" +#include "doomtype.h" + +#include "sounds.h" +#include "s_sound.h" + +#include "m_misc.h" +#include "m_random.h" +#include "m_argv.h" + +#include "p_local.h" +#include "w_wad.h" +#include "z_zone.h" + +// when to clip out sounds +// Does not fit the large outdoor areas. + +#define S_CLIPPING_DIST (1200 * FRACUNIT) + +// Distance tp origin when sounds should be maxed out. +// This should relate to movement clipping resolution +// (see BLOCKMAP handling). +// In the source code release: (160*FRACUNIT). Changed back to the +// Vanilla value of 200 (why was this changed?) + +#define S_CLOSE_DIST (200 * FRACUNIT) + +// The range over which sound attenuates + +#define S_ATTENUATOR ((S_CLIPPING_DIST - S_CLOSE_DIST) >> FRACBITS) + +// Stereo separation + +#define S_STEREO_SWING (96 * FRACUNIT) + +#define NORM_PITCH 128 +#define NORM_PRIORITY 64 +#define NORM_SEP 128 + +typedef struct +{ + // sound information (if null, channel avail.) + sfxinfo_t *sfxinfo; + + // origin of sound + mobj_t *origin; + + // handle of the sound being played + int handle; + +} channel_t; + +// The set of channels available + +static channel_t *channels; + +// Maximum volume of a sound effect. +// Internal default is max out of 0-15. + +int sfxVolume = 8; + +// Maximum volume of music. + +int musicVolume = 8; + +// Internal volume level, ranging from 0-127 + +static int snd_SfxVolume; + +// Whether songs are mus_paused + +static boolean mus_paused; + +// Music currently being played + +static musicinfo_t *mus_playing = NULL; + +// Number of channels to use + +int snd_channels = 8; + +// +// Initializes sound stuff, including volume +// Sets channels, SFX and music volume, +// allocates channel buffer, sets S_sfx lookup. +// + +void S_Init(int sfxVolume, int musicVolume) +{ + int i; + + I_PrecacheSounds(S_sfx, NUMSFX); + + S_SetSfxVolume(sfxVolume); + S_SetMusicVolume(musicVolume); + + // Allocating the internal channels for mixing + // (the maximum numer of sounds rendered + // simultaneously) within zone memory. + channels = Z_Malloc(snd_channels*sizeof(channel_t), PU_STATIC, 0); + + // Free all channels for use + for (i=0 ; isfxinfo) + { + // stop the sound playing + + if (I_SoundIsPlaying(c->handle)) + { + I_StopSound(c->handle); + } + + // check to see if other channels are playing the sound + + for (i=0; isfxinfo == channels[i].sfxinfo) + { + break; + } + } + + // degrade usefulness of sound data + + c->sfxinfo->usefulness--; + c->sfxinfo = NULL; + } +} + +// +// Per level startup code. +// Kills playing sounds at start of level, +// determines music if any, changes music. +// + +void S_Start(void) +{ + int cnum; + int mnum; + + // kill all playing sounds at start of level + // (trust me - a good idea) + for (cnum=0 ; cnumpriority >= sfxinfo->priority) + { + break; + } + } + + if (cnum == snd_channels) + { + // FUCK! No lower priority. Sorry, Charlie. + return -1; + } + else + { + // Otherwise, kick out lower priority. + S_StopChannel(cnum); + } + } + + c = &channels[cnum]; + + // channel is decided to be cnum. + c->sfxinfo = sfxinfo; + c->origin = origin; + + return cnum; +} + +// +// Changes volume and stereo-separation variables +// from the norm of a sound effect to be played. +// If the sound is not audible, returns a 0. +// Otherwise, modifies parameters and returns 1. +// + +static int S_AdjustSoundParams(mobj_t *listener, mobj_t *source, + int *vol, int *sep) +{ + fixed_t approx_dist; + fixed_t adx; + fixed_t ady; + angle_t angle; + + // calculate the distance to sound origin + // and clip it if necessary + adx = abs(listener->x - source->x); + ady = abs(listener->y - source->y); + + // From _GG1_ p.428. Appox. eucledian distance fast. + approx_dist = adx + ady - ((adx < ady ? adx : ady)>>1); + + if (gamemap != 8 && approx_dist > S_CLIPPING_DIST) + { + return 0; + } + + // angle of source to listener + angle = R_PointToAngle2(listener->x, + listener->y, + source->x, + source->y); + + if (angle > listener->angle) + { + angle = angle - listener->angle; + } + else + { + angle = angle + (0xffffffff - listener->angle); + } + + angle >>= ANGLETOFINESHIFT; + + // stereo separation + *sep = 128 - (FixedMul(S_STEREO_SWING, finesine[angle]) >> FRACBITS); + + // volume calculation + if (approx_dist < S_CLOSE_DIST) + { + *vol = snd_SfxVolume; + } + else if (gamemap == 8) + { + if (approx_dist > S_CLIPPING_DIST) + { + approx_dist = S_CLIPPING_DIST; + } + + *vol = 15+ ((snd_SfxVolume-15) + *((S_CLIPPING_DIST - approx_dist)>>FRACBITS)) + / S_ATTENUATOR; + } + else + { + // distance effect + *vol = (snd_SfxVolume + * ((S_CLIPPING_DIST - approx_dist)>>FRACBITS)) + / S_ATTENUATOR; + } + + return (*vol > 0); +} + +void S_StartSound(void *origin_p, int sfx_id) +{ + sfxinfo_t *sfx; + mobj_t *origin; + int rc; + int sep; + int cnum; + int volume; + + origin = (mobj_t *) origin_p; + volume = snd_SfxVolume; + + // check for bogus sound # + if (sfx_id < 1 || sfx_id > NUMSFX) + { + I_Error("Bad sfx #: %d", sfx_id); + } + + sfx = &S_sfx[sfx_id]; + + // Initialize sound parameters + if (sfx->link) + { + volume += sfx->volume; + + if (volume < 1) + { + return; + } + + if (volume > snd_SfxVolume) + { + volume = snd_SfxVolume; + } + } + + + // Check to see if it is audible, + // and if not, modify the params + if (origin && origin != players[consoleplayer].mo) + { + rc = S_AdjustSoundParams(players[consoleplayer].mo, + origin, + &volume, + &sep); + + if (origin->x == players[consoleplayer].mo->x + && origin->y == players[consoleplayer].mo->y) + { + sep = NORM_SEP; + } + + if (!rc) + { + return; + } + } + else + { + sep = NORM_SEP; + } + + // kill old sound + S_StopSound(origin); + + // try to find a channel + cnum = S_GetChannel(origin, sfx); + + if (cnum < 0) + { + return; + } + + // increase the usefulness + if (sfx->usefulness++ < 0) + { + sfx->usefulness = 1; + } + + if (sfx->lumpnum < 0) + { + sfx->lumpnum = I_GetSfxLumpNum(sfx); + } + + channels[cnum].handle = I_StartSound(sfx, cnum, volume, sep); +} + +// +// Stop and resume music, during game PAUSE. +// + +void S_PauseSound(void) +{ + if (mus_playing && !mus_paused) + { + I_PauseSong(); + mus_paused = true; + } +} + +void S_ResumeSound(void) +{ + if (mus_playing && mus_paused) + { + I_ResumeSong(); + mus_paused = false; + } +} + +// +// Updates music & sounds +// + +void S_UpdateSounds(mobj_t *listener) +{ + int audible; + int cnum; + int volume; + int sep; + sfxinfo_t* sfx; + channel_t* c; + + I_UpdateSound(); + + for (cnum=0; cnumsfxinfo; + + if (c->sfxinfo) + { + if (I_SoundIsPlaying(c->handle)) + { + // initialize parameters + volume = snd_SfxVolume; + sep = NORM_SEP; + + if (sfx->link) + { + volume += sfx->volume; + if (volume < 1) + { + S_StopChannel(cnum); + continue; + } + else if (volume > snd_SfxVolume) + { + volume = snd_SfxVolume; + } + } + + // check non-local sounds for distance clipping + // or modify their params + if (c->origin && listener != c->origin) + { + audible = S_AdjustSoundParams(listener, + c->origin, + &volume, + &sep); + + if (!audible) + { + S_StopChannel(cnum); + } + else + { + I_UpdateSoundParams(c->handle, volume, sep); + } + } + } + else + { + // if channel is allocated but sound has stopped, + // free it + S_StopChannel(cnum); + } + } + } +} + +void S_SetMusicVolume(int volume) +{ + if (volume < 0 || volume > 127) + { + I_Error("Attempt to set music volume at %d", + volume); + } + + I_SetMusicVolume(volume); +} + +void S_SetSfxVolume(int volume) +{ + if (volume < 0 || volume > 127) + { + I_Error("Attempt to set sfx volume at %d", volume); + } + + snd_SfxVolume = volume; +} + +// +// Starts some music with the music id found in sounds.h. +// + +void S_StartMusic(int m_id) +{ + S_ChangeMusic(m_id, false); +} + +void S_ChangeMusic(int musicnum, int looping) +{ + musicinfo_t *music = NULL; + char namebuf[9]; + void *handle; + + // The Doom IWAD file has two versions of the intro music: d_intro + // and d_introa. The latter is used for OPL playback. + + if (musicnum == mus_intro && (snd_musicdevice == SNDDEVICE_ADLIB + || snd_musicdevice == SNDDEVICE_SB)) + { + musicnum = mus_introa; + } + + if (musicnum <= mus_None || musicnum >= NUMMUSIC) + { + I_Error("Bad music number %d", musicnum); + } + else + { + music = &S_music[musicnum]; + } + + if (mus_playing == music) + { + return; + } + + // shutdown old music + S_StopMusic(); + + // get lumpnum if neccessary + if (!music->lumpnum) + { + M_snprintf(namebuf, sizeof(namebuf), "d_%s", DEH_String(music->name)); + music->lumpnum = W_GetNumForName(namebuf); + } + + music->data = W_CacheLumpNum(music->lumpnum, PU_STATIC); + + handle = I_RegisterSong(music->data, W_LumpLength(music->lumpnum)); + music->handle = handle; + I_PlaySong(handle, looping); + + mus_playing = music; +} + +boolean S_MusicPlaying(void) +{ + return I_MusicIsPlaying(); +} + +void S_StopMusic(void) +{ + if (mus_playing) + { + if (mus_paused) + { + I_ResumeSong(); + } + + I_StopSong(); + I_UnRegisterSong(mus_playing->handle); + W_ReleaseLumpNum(mus_playing->lumpnum); + mus_playing->data = NULL; + mus_playing = NULL; + } +} + diff --git a/firmware_p4/components/Applications/doom/s_sound.h b/firmware_p4/components/Applications/doom/s_sound.h new file mode 100644 index 000000000..bbd100a0c --- /dev/null +++ b/firmware_p4/components/Applications/doom/s_sound.h @@ -0,0 +1,89 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// The not so system specific sound interface. +// + + +#ifndef __S_SOUND__ +#define __S_SOUND__ + +#include "p_mobj.h" +#include "sounds.h" + +// +// Initializes sound stuff, including volume +// Sets channels, SFX and music volume, +// allocates channel buffer, sets S_sfx lookup. +// + +void S_Init(int sfxVolume, int musicVolume); + + +// Shut down sound + +void S_Shutdown(void); + + + +// +// Per level startup code. +// Kills playing sounds at start of level, +// determines music if any, changes music. +// + +void S_Start(void); + +// +// Start sound for thing at +// using from sounds.h +// + +void S_StartSound(void *origin, int sound_id); + +// Stop sound for thing at +void S_StopSound(mobj_t *origin); + + +// Start music using from sounds.h +void S_StartMusic(int music_id); + +// Start music using from sounds.h, +// and set whether looping +void S_ChangeMusic(int music_id, int looping); + +// query if music is playing +boolean S_MusicPlaying(void); + +// Stops the music fer sure. +void S_StopMusic(void); + +// Stop and resume music, during game PAUSE. +void S_PauseSound(void); +void S_ResumeSound(void); + + +// +// Updates music & sounds +// +void S_UpdateSounds(mobj_t *listener); + +void S_SetMusicVolume(int volume); +void S_SetSfxVolume(int volume); + +extern int snd_channels; + +#endif + diff --git a/firmware_p4/components/Applications/doom/sha1.c b/firmware_p4/components/Applications/doom/sha1.c new file mode 100644 index 000000000..06ab40ad8 --- /dev/null +++ b/firmware_p4/components/Applications/doom/sha1.c @@ -0,0 +1,319 @@ +/* sha1.c - SHA1 hash function + * Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. + * + * Please see below for more legal information! + * + * This file is part of GnuPG. + * + * GnuPG 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 3 of the License, or + * (at your option) any later version. + * + * GnuPG 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; if not, see . + */ + + +/* Test vectors: + * + * "abc" + * A999 3E36 4706 816A BA3E 2571 7850 C26C 9CD0 D89D + * + * "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + * 8498 3E44 1C3B D26E BAAE 4AA1 F951 29E5 E546 70F1 + */ + +#include +#include +#include +#include + +#include "i_swap.h" +#include "sha1.h" + +void SHA1_Init(sha1_context_t *hd) +{ + hd->h0 = 0x67452301; + hd->h1 = 0xefcdab89; + hd->h2 = 0x98badcfe; + hd->h3 = 0x10325476; + hd->h4 = 0xc3d2e1f0; + hd->nblocks = 0; + hd->count = 0; +} + + +/**************** + * Transform the message X which consists of 16 32-bit-words + */ +static void Transform(sha1_context_t *hd, byte *data) +{ + uint32_t a,b,c,d,e,tm; + uint32_t x[16]; + + /* get values from the chaining vars */ + a = hd->h0; + b = hd->h1; + c = hd->h2; + d = hd->h3; + e = hd->h4; + +#ifdef SYS_BIG_ENDIAN + memcpy(x, data, 64); +#else + { + int i; + byte *p2; + for(i=0, p2=(byte*)x; i < 16; i++, p2 += 4 ) + { + p2[3] = *data++; + p2[2] = *data++; + p2[1] = *data++; + p2[0] = *data++; + } + } +#endif + + +#define K1 0x5A827999L +#define K2 0x6ED9EBA1L +#define K3 0x8F1BBCDCL +#define K4 0xCA62C1D6L +#define F1(x,y,z) ( z ^ ( x & ( y ^ z ) ) ) +#define F2(x,y,z) ( x ^ y ^ z ) +#define F3(x,y,z) ( ( x & y ) | ( z & ( x | y ) ) ) +#define F4(x,y,z) ( x ^ y ^ z ) + +#define rol(x,n) ( ((x) << (n)) | ((x) >> (32-(n))) ) + +#define M(i) ( tm = x[i&0x0f] ^ x[(i-14)&0x0f] \ + ^ x[(i-8)&0x0f] ^ x[(i-3)&0x0f] \ + , (x[i&0x0f] = rol(tm,1)) ) + +#define R(a,b,c,d,e,f,k,m) do { e += rol( a, 5 ) \ + + f( b, c, d ) \ + + k \ + + m; \ + b = rol( b, 30 ); \ + } while(0) + R( a, b, c, d, e, F1, K1, x[ 0] ); + R( e, a, b, c, d, F1, K1, x[ 1] ); + R( d, e, a, b, c, F1, K1, x[ 2] ); + R( c, d, e, a, b, F1, K1, x[ 3] ); + R( b, c, d, e, a, F1, K1, x[ 4] ); + R( a, b, c, d, e, F1, K1, x[ 5] ); + R( e, a, b, c, d, F1, K1, x[ 6] ); + R( d, e, a, b, c, F1, K1, x[ 7] ); + R( c, d, e, a, b, F1, K1, x[ 8] ); + R( b, c, d, e, a, F1, K1, x[ 9] ); + R( a, b, c, d, e, F1, K1, x[10] ); + R( e, a, b, c, d, F1, K1, x[11] ); + R( d, e, a, b, c, F1, K1, x[12] ); + R( c, d, e, a, b, F1, K1, x[13] ); + R( b, c, d, e, a, F1, K1, x[14] ); + R( a, b, c, d, e, F1, K1, x[15] ); + R( e, a, b, c, d, F1, K1, M(16) ); + R( d, e, a, b, c, F1, K1, M(17) ); + R( c, d, e, a, b, F1, K1, M(18) ); + R( b, c, d, e, a, F1, K1, M(19) ); + R( a, b, c, d, e, F2, K2, M(20) ); + R( e, a, b, c, d, F2, K2, M(21) ); + R( d, e, a, b, c, F2, K2, M(22) ); + R( c, d, e, a, b, F2, K2, M(23) ); + R( b, c, d, e, a, F2, K2, M(24) ); + R( a, b, c, d, e, F2, K2, M(25) ); + R( e, a, b, c, d, F2, K2, M(26) ); + R( d, e, a, b, c, F2, K2, M(27) ); + R( c, d, e, a, b, F2, K2, M(28) ); + R( b, c, d, e, a, F2, K2, M(29) ); + R( a, b, c, d, e, F2, K2, M(30) ); + R( e, a, b, c, d, F2, K2, M(31) ); + R( d, e, a, b, c, F2, K2, M(32) ); + R( c, d, e, a, b, F2, K2, M(33) ); + R( b, c, d, e, a, F2, K2, M(34) ); + R( a, b, c, d, e, F2, K2, M(35) ); + R( e, a, b, c, d, F2, K2, M(36) ); + R( d, e, a, b, c, F2, K2, M(37) ); + R( c, d, e, a, b, F2, K2, M(38) ); + R( b, c, d, e, a, F2, K2, M(39) ); + R( a, b, c, d, e, F3, K3, M(40) ); + R( e, a, b, c, d, F3, K3, M(41) ); + R( d, e, a, b, c, F3, K3, M(42) ); + R( c, d, e, a, b, F3, K3, M(43) ); + R( b, c, d, e, a, F3, K3, M(44) ); + R( a, b, c, d, e, F3, K3, M(45) ); + R( e, a, b, c, d, F3, K3, M(46) ); + R( d, e, a, b, c, F3, K3, M(47) ); + R( c, d, e, a, b, F3, K3, M(48) ); + R( b, c, d, e, a, F3, K3, M(49) ); + R( a, b, c, d, e, F3, K3, M(50) ); + R( e, a, b, c, d, F3, K3, M(51) ); + R( d, e, a, b, c, F3, K3, M(52) ); + R( c, d, e, a, b, F3, K3, M(53) ); + R( b, c, d, e, a, F3, K3, M(54) ); + R( a, b, c, d, e, F3, K3, M(55) ); + R( e, a, b, c, d, F3, K3, M(56) ); + R( d, e, a, b, c, F3, K3, M(57) ); + R( c, d, e, a, b, F3, K3, M(58) ); + R( b, c, d, e, a, F3, K3, M(59) ); + R( a, b, c, d, e, F4, K4, M(60) ); + R( e, a, b, c, d, F4, K4, M(61) ); + R( d, e, a, b, c, F4, K4, M(62) ); + R( c, d, e, a, b, F4, K4, M(63) ); + R( b, c, d, e, a, F4, K4, M(64) ); + R( a, b, c, d, e, F4, K4, M(65) ); + R( e, a, b, c, d, F4, K4, M(66) ); + R( d, e, a, b, c, F4, K4, M(67) ); + R( c, d, e, a, b, F4, K4, M(68) ); + R( b, c, d, e, a, F4, K4, M(69) ); + R( a, b, c, d, e, F4, K4, M(70) ); + R( e, a, b, c, d, F4, K4, M(71) ); + R( d, e, a, b, c, F4, K4, M(72) ); + R( c, d, e, a, b, F4, K4, M(73) ); + R( b, c, d, e, a, F4, K4, M(74) ); + R( a, b, c, d, e, F4, K4, M(75) ); + R( e, a, b, c, d, F4, K4, M(76) ); + R( d, e, a, b, c, F4, K4, M(77) ); + R( c, d, e, a, b, F4, K4, M(78) ); + R( b, c, d, e, a, F4, K4, M(79) ); + + /* update chainig vars */ + hd->h0 += a; + hd->h1 += b; + hd->h2 += c; + hd->h3 += d; + hd->h4 += e; +} + + +/* Update the message digest with the contents + * of INBUF with length INLEN. + */ +void SHA1_Update(sha1_context_t *hd, byte *inbuf, size_t inlen) +{ + if (hd->count == 64) + { + /* flush the buffer */ + Transform(hd, hd->buf); + hd->count = 0; + hd->nblocks++; + } + if (!inbuf) + return; + if (hd->count) + { + for (; inlen && hd->count < 64; inlen--) + hd->buf[hd->count++] = *inbuf++; + SHA1_Update(hd, NULL, 0); + if (!inlen) + return; + } + + while (inlen >= 64) + { + Transform(hd, inbuf); + hd->count = 0; + hd->nblocks++; + inlen -= 64; + inbuf += 64; + } + for (; inlen && hd->count < 64; inlen--) + hd->buf[hd->count++] = *inbuf++; +} + + +/* The routine final terminates the computation and + * returns the digest. + * The handle is prepared for a new cycle, but adding bytes to the + * handle will the destroy the returned buffer. + * Returns: 20 bytes representing the digest. + */ + +void SHA1_Final(sha1_digest_t digest, sha1_context_t *hd) +{ + uint32_t t, msb, lsb; + byte *p; + + SHA1_Update(hd, NULL, 0); /* flush */; + + t = hd->nblocks; + /* multiply by 64 to make a byte count */ + lsb = t << 6; + msb = t >> 26; + /* add the count */ + t = lsb; + if ((lsb += hd->count) < t) + msb++; + /* multiply by 8 to make a bit count */ + t = lsb; + lsb <<= 3; + msb <<= 3; + msb |= t >> 29; + + if (hd->count < 56) + { + /* enough room */ + hd->buf[hd->count++] = 0x80; /* pad */ + while (hd->count < 56) + hd->buf[hd->count++] = 0; /* pad */ + } + else + { + /* need one extra block */ + hd->buf[hd->count++] = 0x80; /* pad character */ + while (hd->count < 64) + hd->buf[hd->count++] = 0; + SHA1_Update(hd, NULL, 0); /* flush */; + memset(hd->buf, 0, 56 ); /* fill next block with zeroes */ + } + /* append the 64 bit count */ + hd->buf[56] = msb >> 24; + hd->buf[57] = msb >> 16; + hd->buf[58] = msb >> 8; + hd->buf[59] = msb ; + hd->buf[60] = lsb >> 24; + hd->buf[61] = lsb >> 16; + hd->buf[62] = lsb >> 8; + hd->buf[63] = lsb ; + Transform(hd, hd->buf); + + p = hd->buf; +#ifdef SYS_BIG_ENDIAN +#define X(a) do { *(uint32_t*)p = hd->h##a ; p += 4; } while(0) +#else /* little endian */ +#define X(a) do { *p++ = hd->h##a >> 24; *p++ = hd->h##a >> 16; \ + *p++ = hd->h##a >> 8; *p++ = hd->h##a; } while(0) +#endif + X(0); + X(1); + X(2); + X(3); + X(4); +#undef X + + memcpy(digest, hd->buf, sizeof(sha1_digest_t)); +} + +void SHA1_UpdateInt32(sha1_context_t *context, unsigned int val) +{ + byte buf[4]; + + buf[0] = (val >> 24) & 0xff; + buf[1] = (val >> 16) & 0xff; + buf[2] = (val >> 8) & 0xff; + buf[3] = val & 0xff; + + SHA1_Update(context, buf, 4); +} + +void SHA1_UpdateString(sha1_context_t *context, char *str) +{ + SHA1_Update(context, (byte *) str, strlen(str) + 1); +} + diff --git a/firmware_p4/components/Applications/doom/sha1.h b/firmware_p4/components/Applications/doom/sha1.h new file mode 100644 index 000000000..249571be3 --- /dev/null +++ b/firmware_p4/components/Applications/doom/sha1.h @@ -0,0 +1,40 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// SHA-1 digest. +// + +#ifndef __SHA1_H__ +#define __SHA1_H__ + +#include "doomtype.h" + +typedef struct sha1_context_s sha1_context_t; +typedef byte sha1_digest_t[20]; + +struct sha1_context_s { + uint32_t h0,h1,h2,h3,h4; + uint32_t nblocks; + byte buf[64]; + int count; +}; + +void SHA1_Init(sha1_context_t *context); +void SHA1_Update(sha1_context_t *context, byte *buf, size_t len); +void SHA1_Final(sha1_digest_t digest, sha1_context_t *context); +void SHA1_UpdateInt32(sha1_context_t *context, unsigned int val); +void SHA1_UpdateString(sha1_context_t *context, char *str); + +#endif /* #ifndef __SHA1_H__ */ + diff --git a/firmware_p4/components/Applications/doom/sounds.c b/firmware_p4/components/Applications/doom/sounds.c new file mode 100644 index 000000000..e976bc844 --- /dev/null +++ b/firmware_p4/components/Applications/doom/sounds.c @@ -0,0 +1,229 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Created by a sound utility. +// Kept as a sample, DOOM2 sounds. +// + + +#include + + +#include "doomtype.h" +#include "sounds.h" + +// +// Information about all the music +// + +#define MUSIC(name) \ + { name, 0, NULL, NULL } + +musicinfo_t S_music[] = +{ + MUSIC(NULL), + MUSIC("e1m1"), + MUSIC("e1m2"), + MUSIC("e1m3"), + MUSIC("e1m4"), + MUSIC("e1m5"), + MUSIC("e1m6"), + MUSIC("e1m7"), + MUSIC("e1m8"), + MUSIC("e1m9"), + MUSIC("e2m1"), + MUSIC("e2m2"), + MUSIC("e2m3"), + MUSIC("e2m4"), + MUSIC("e2m5"), + MUSIC("e2m6"), + MUSIC("e2m7"), + MUSIC("e2m8"), + MUSIC("e2m9"), + MUSIC("e3m1"), + MUSIC("e3m2"), + MUSIC("e3m3"), + MUSIC("e3m4"), + MUSIC("e3m5"), + MUSIC("e3m6"), + MUSIC("e3m7"), + MUSIC("e3m8"), + MUSIC("e3m9"), + MUSIC("inter"), + MUSIC("intro"), + MUSIC("bunny"), + MUSIC("victor"), + MUSIC("introa"), + MUSIC("runnin"), + MUSIC("stalks"), + MUSIC("countd"), + MUSIC("betwee"), + MUSIC("doom"), + MUSIC("the_da"), + MUSIC("shawn"), + MUSIC("ddtblu"), + MUSIC("in_cit"), + MUSIC("dead"), + MUSIC("stlks2"), + MUSIC("theda2"), + MUSIC("doom2"), + MUSIC("ddtbl2"), + MUSIC("runni2"), + MUSIC("dead2"), + MUSIC("stlks3"), + MUSIC("romero"), + MUSIC("shawn2"), + MUSIC("messag"), + MUSIC("count2"), + MUSIC("ddtbl3"), + MUSIC("ampie"), + MUSIC("theda3"), + MUSIC("adrian"), + MUSIC("messg2"), + MUSIC("romer2"), + MUSIC("tense"), + MUSIC("shawn3"), + MUSIC("openin"), + MUSIC("evil"), + MUSIC("ultima"), + MUSIC("read_m"), + MUSIC("dm2ttl"), + MUSIC("dm2int") +}; + + +// +// Information about all the sfx +// + +#define SOUND(name, priority) \ + { NULL, name, priority, NULL, -1, -1, 0, 0, -1, NULL } +#define SOUND_LINK(name, priority, link_id, pitch, volume) \ + { NULL, name, priority, &S_sfx[link_id], pitch, volume, 0, 0, -1, NULL } + +sfxinfo_t S_sfx[] = +{ + // S_sfx[0] needs to be a dummy for odd reasons. + SOUND("none", 0), + SOUND("pistol", 64), + SOUND("shotgn", 64), + SOUND("sgcock", 64), + SOUND("dshtgn", 64), + SOUND("dbopn", 64), + SOUND("dbcls", 64), + SOUND("dbload", 64), + SOUND("plasma", 64), + SOUND("bfg", 64), + SOUND("sawup", 64), + SOUND("sawidl", 118), + SOUND("sawful", 64), + SOUND("sawhit", 64), + SOUND("rlaunc", 64), + SOUND("rxplod", 70), + SOUND("firsht", 70), + SOUND("firxpl", 70), + SOUND("pstart", 100), + SOUND("pstop", 100), + SOUND("doropn", 100), + SOUND("dorcls", 100), + SOUND("stnmov", 119), + SOUND("swtchn", 78), + SOUND("swtchx", 78), + SOUND("plpain", 96), + SOUND("dmpain", 96), + SOUND("popain", 96), + SOUND("vipain", 96), + SOUND("mnpain", 96), + SOUND("pepain", 96), + SOUND("slop", 78), + SOUND("itemup", 78), + SOUND("wpnup", 78), + SOUND("oof", 96), + SOUND("telept", 32), + SOUND("posit1", 98), + SOUND("posit2", 98), + SOUND("posit3", 98), + SOUND("bgsit1", 98), + SOUND("bgsit2", 98), + SOUND("sgtsit", 98), + SOUND("cacsit", 98), + SOUND("brssit", 94), + SOUND("cybsit", 92), + SOUND("spisit", 90), + SOUND("bspsit", 90), + SOUND("kntsit", 90), + SOUND("vilsit", 90), + SOUND("mansit", 90), + SOUND("pesit", 90), + SOUND("sklatk", 70), + SOUND("sgtatk", 70), + SOUND("skepch", 70), + SOUND("vilatk", 70), + SOUND("claw", 70), + SOUND("skeswg", 70), + SOUND("pldeth", 32), + SOUND("pdiehi", 32), + SOUND("podth1", 70), + SOUND("podth2", 70), + SOUND("podth3", 70), + SOUND("bgdth1", 70), + SOUND("bgdth2", 70), + SOUND("sgtdth", 70), + SOUND("cacdth", 70), + SOUND("skldth", 70), + SOUND("brsdth", 32), + SOUND("cybdth", 32), + SOUND("spidth", 32), + SOUND("bspdth", 32), + SOUND("vildth", 32), + SOUND("kntdth", 32), + SOUND("pedth", 32), + SOUND("skedth", 32), + SOUND("posact", 120), + SOUND("bgact", 120), + SOUND("dmact", 120), + SOUND("bspact", 100), + SOUND("bspwlk", 100), + SOUND("vilact", 100), + SOUND("noway", 78), + SOUND("barexp", 60), + SOUND("punch", 64), + SOUND("hoof", 70), + SOUND("metal", 70), + SOUND_LINK("chgun", 64, sfx_pistol, 150, 0), + SOUND("tink", 60), + SOUND("bdopn", 100), + SOUND("bdcls", 100), + SOUND("itmbk", 100), + SOUND("flame", 32), + SOUND("flamst", 32), + SOUND("getpow", 60), + SOUND("bospit", 70), + SOUND("boscub", 70), + SOUND("bossit", 70), + SOUND("bospn", 70), + SOUND("bosdth", 70), + SOUND("manatk", 70), + SOUND("mandth", 70), + SOUND("sssit", 70), + SOUND("ssdth", 70), + SOUND("keenpn", 70), + SOUND("keendt", 70), + SOUND("skeact", 70), + SOUND("skesit", 70), + SOUND("skeatk", 70), + SOUND("radio", 60), +}; + diff --git a/firmware_p4/components/Applications/doom/sounds.h b/firmware_p4/components/Applications/doom/sounds.h new file mode 100644 index 000000000..1e8afc405 --- /dev/null +++ b/firmware_p4/components/Applications/doom/sounds.h @@ -0,0 +1,227 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Created by the sound utility written by Dave Taylor. +// Kept as a sample, DOOM2 sounds. Frozen. +// + +#ifndef __SOUNDS__ +#define __SOUNDS__ + +#include "i_sound.h" + +// the complete set of sound effects +extern sfxinfo_t S_sfx[]; + +// the complete set of music +extern musicinfo_t S_music[]; + +// +// Identifiers for all music in game. +// + +typedef enum +{ + mus_None, + mus_e1m1, + mus_e1m2, + mus_e1m3, + mus_e1m4, + mus_e1m5, + mus_e1m6, + mus_e1m7, + mus_e1m8, + mus_e1m9, + mus_e2m1, + mus_e2m2, + mus_e2m3, + mus_e2m4, + mus_e2m5, + mus_e2m6, + mus_e2m7, + mus_e2m8, + mus_e2m9, + mus_e3m1, + mus_e3m2, + mus_e3m3, + mus_e3m4, + mus_e3m5, + mus_e3m6, + mus_e3m7, + mus_e3m8, + mus_e3m9, + mus_inter, + mus_intro, + mus_bunny, + mus_victor, + mus_introa, + mus_runnin, + mus_stalks, + mus_countd, + mus_betwee, + mus_doom, + mus_the_da, + mus_shawn, + mus_ddtblu, + mus_in_cit, + mus_dead, + mus_stlks2, + mus_theda2, + mus_doom2, + mus_ddtbl2, + mus_runni2, + mus_dead2, + mus_stlks3, + mus_romero, + mus_shawn2, + mus_messag, + mus_count2, + mus_ddtbl3, + mus_ampie, + mus_theda3, + mus_adrian, + mus_messg2, + mus_romer2, + mus_tense, + mus_shawn3, + mus_openin, + mus_evil, + mus_ultima, + mus_read_m, + mus_dm2ttl, + mus_dm2int, + NUMMUSIC +} musicenum_t; + + +// +// Identifiers for all sfx in game. +// + +typedef enum +{ + sfx_None, + sfx_pistol, + sfx_shotgn, + sfx_sgcock, + sfx_dshtgn, + sfx_dbopn, + sfx_dbcls, + sfx_dbload, + sfx_plasma, + sfx_bfg, + sfx_sawup, + sfx_sawidl, + sfx_sawful, + sfx_sawhit, + sfx_rlaunc, + sfx_rxplod, + sfx_firsht, + sfx_firxpl, + sfx_pstart, + sfx_pstop, + sfx_doropn, + sfx_dorcls, + sfx_stnmov, + sfx_swtchn, + sfx_swtchx, + sfx_plpain, + sfx_dmpain, + sfx_popain, + sfx_vipain, + sfx_mnpain, + sfx_pepain, + sfx_slop, + sfx_itemup, + sfx_wpnup, + sfx_oof, + sfx_telept, + sfx_posit1, + sfx_posit2, + sfx_posit3, + sfx_bgsit1, + sfx_bgsit2, + sfx_sgtsit, + sfx_cacsit, + sfx_brssit, + sfx_cybsit, + sfx_spisit, + sfx_bspsit, + sfx_kntsit, + sfx_vilsit, + sfx_mansit, + sfx_pesit, + sfx_sklatk, + sfx_sgtatk, + sfx_skepch, + sfx_vilatk, + sfx_claw, + sfx_skeswg, + sfx_pldeth, + sfx_pdiehi, + sfx_podth1, + sfx_podth2, + sfx_podth3, + sfx_bgdth1, + sfx_bgdth2, + sfx_sgtdth, + sfx_cacdth, + sfx_skldth, + sfx_brsdth, + sfx_cybdth, + sfx_spidth, + sfx_bspdth, + sfx_vildth, + sfx_kntdth, + sfx_pedth, + sfx_skedth, + sfx_posact, + sfx_bgact, + sfx_dmact, + sfx_bspact, + sfx_bspwlk, + sfx_vilact, + sfx_noway, + sfx_barexp, + sfx_punch, + sfx_hoof, + sfx_metal, + sfx_chgun, + sfx_tink, + sfx_bdopn, + sfx_bdcls, + sfx_itmbk, + sfx_flame, + sfx_flamst, + sfx_getpow, + sfx_bospit, + sfx_boscub, + sfx_bossit, + sfx_bospn, + sfx_bosdth, + sfx_manatk, + sfx_mandth, + sfx_sssit, + sfx_ssdth, + sfx_keenpn, + sfx_keendt, + sfx_skeact, + sfx_skesit, + sfx_skeatk, + sfx_radio, + NUMSFX +} sfxenum_t; + +#endif diff --git a/firmware_p4/components/Applications/doom/st_lib.c b/firmware_p4/components/Applications/doom/st_lib.c new file mode 100644 index 000000000..7ce978d44 --- /dev/null +++ b/firmware_p4/components/Applications/doom/st_lib.c @@ -0,0 +1,284 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// The status bar widget code. +// + + +#include +#include + +#include "deh_main.h" +#include "doomdef.h" + +#include "z_zone.h" +#include "v_video.h" + +#include "i_swap.h" +#include "i_system.h" + +#include "w_wad.h" + +#include "st_stuff.h" +#include "st_lib.h" +#include "r_local.h" + + +// in AM_map.c +extern boolean automapactive; + + + + +// +// Hack display negative frags. +// Loads and store the stminus lump. +// +patch_t* sttminus; + +void STlib_init(void) +{ + sttminus = (patch_t *) W_CacheLumpName(DEH_String("STTMINUS"), PU_STATIC); +} + + +// ? +void +STlib_initNum +( st_number_t* n, + int x, + int y, + patch_t** pl, + int* num, + boolean* on, + int width ) +{ + n->x = x; + n->y = y; + n->oldnum = 0; + n->width = width; + n->num = num; + n->on = on; + n->p = pl; +} + + +// +// A fairly efficient way to draw a number +// based on differences from the old number. +// Note: worth the trouble? +// +void +STlib_drawNum +( st_number_t* n, + boolean refresh ) +{ + + int numdigits = n->width; + int num = *n->num; + + int w = SHORT(n->p[0]->width); + int h = SHORT(n->p[0]->height); + int x = n->x; + + int neg; + + n->oldnum = *n->num; + + neg = num < 0; + + if (neg) + { + if (numdigits == 2 && num < -9) + num = -9; + else if (numdigits == 3 && num < -99) + num = -99; + + num = -num; + } + + // clear the area + x = n->x - numdigits*w; + + if (n->y - ST_Y < 0) + I_Error("drawNum: n->y - ST_Y < 0"); + + V_CopyRect(x, n->y - ST_Y, st_backing_screen, w*numdigits, h, x, n->y); + + // if non-number, do not draw it + if (num == 1994) + return; + + x = n->x; + + // in the special case of 0, you draw 0 + if (!num) + V_DrawPatch(x - w, n->y, n->p[ 0 ]); + + // draw the new number + while (num && numdigits--) + { + x -= w; + V_DrawPatch(x, n->y, n->p[ num % 10 ]); + num /= 10; + } + + // draw a minus sign if necessary + if (neg) + V_DrawPatch(x - 8, n->y, sttminus); +} + + +// +void +STlib_updateNum +( st_number_t* n, + boolean refresh ) +{ + if (*n->on) STlib_drawNum(n, refresh); +} + + +// +void +STlib_initPercent +( st_percent_t* p, + int x, + int y, + patch_t** pl, + int* num, + boolean* on, + patch_t* percent ) +{ + STlib_initNum(&p->n, x, y, pl, num, on, 3); + p->p = percent; +} + + + + +void +STlib_updatePercent +( st_percent_t* per, + int refresh ) +{ + if (refresh && *per->n.on) + V_DrawPatch(per->n.x, per->n.y, per->p); + + STlib_updateNum(&per->n, refresh); +} + + + +void +STlib_initMultIcon +( st_multicon_t* i, + int x, + int y, + patch_t** il, + int* inum, + boolean* on ) +{ + i->x = x; + i->y = y; + i->oldinum = -1; + i->inum = inum; + i->on = on; + i->p = il; +} + + + +void +STlib_updateMultIcon +( st_multicon_t* mi, + boolean refresh ) +{ + int w; + int h; + int x; + int y; + + if (*mi->on && (mi->oldinum != *mi->inum || refresh) && (*mi->inum != -1)) + { + if (mi->oldinum != -1) + { + x = mi->x - SHORT(mi->p[mi->oldinum]->leftoffset); + y = mi->y - SHORT(mi->p[mi->oldinum]->topoffset); + w = SHORT(mi->p[mi->oldinum]->width); + h = SHORT(mi->p[mi->oldinum]->height); + + if (y - ST_Y < 0) + I_Error("updateMultIcon: y - ST_Y < 0"); + + V_CopyRect(x, y-ST_Y, st_backing_screen, w, h, x, y); + } + V_DrawPatch(mi->x, mi->y, mi->p[*mi->inum]); + mi->oldinum = *mi->inum; + } +} + + + +void +STlib_initBinIcon +( st_binicon_t* b, + int x, + int y, + patch_t* i, + boolean* val, + boolean* on ) +{ + b->x = x; + b->y = y; + b->oldval = false; + b->val = val; + b->on = on; + b->p = i; +} + + + +void +STlib_updateBinIcon +( st_binicon_t* bi, + boolean refresh ) +{ + int x; + int y; + int w; + int h; + + if (*bi->on + && (bi->oldval != *bi->val || refresh)) + { + x = bi->x - SHORT(bi->p->leftoffset); + y = bi->y - SHORT(bi->p->topoffset); + w = SHORT(bi->p->width); + h = SHORT(bi->p->height); + + if (y - ST_Y < 0) + I_Error("updateBinIcon: y - ST_Y < 0"); + + if (*bi->val) + V_DrawPatch(bi->x, bi->y, bi->p); + else + V_CopyRect(x, y-ST_Y, st_backing_screen, w, h, x, y); + + bi->oldval = *bi->val; + } + +} + diff --git a/firmware_p4/components/Applications/doom/st_lib.h b/firmware_p4/components/Applications/doom/st_lib.h new file mode 100644 index 000000000..3a8f52125 --- /dev/null +++ b/firmware_p4/components/Applications/doom/st_lib.h @@ -0,0 +1,209 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// The status bar widget code. +// + +#ifndef __STLIB__ +#define __STLIB__ + + +// We are referring to patches. +#include "r_defs.h" + +// +// Typedefs of widgets +// + +// Number widget + +typedef struct +{ + // upper right-hand corner + // of the number (right-justified) + int x; + int y; + + // max # of digits in number + int width; + + // last number value + int oldnum; + + // pointer to current value + int* num; + + // pointer to boolean stating + // whether to update number + boolean* on; + + // list of patches for 0-9 + patch_t** p; + + // user data + int data; + +} st_number_t; + + + +// Percent widget ("child" of number widget, +// or, more precisely, contains a number widget.) +typedef struct +{ + // number information + st_number_t n; + + // percent sign graphic + patch_t* p; + +} st_percent_t; + + + +// Multiple Icon widget +typedef struct +{ + // center-justified location of icons + int x; + int y; + + // last icon number + int oldinum; + + // pointer to current icon + int* inum; + + // pointer to boolean stating + // whether to update icon + boolean* on; + + // list of icons + patch_t** p; + + // user data + int data; + +} st_multicon_t; + + + + +// Binary Icon widget + +typedef struct +{ + // center-justified location of icon + int x; + int y; + + // last icon value + boolean oldval; + + // pointer to current icon status + boolean* val; + + // pointer to boolean + // stating whether to update icon + boolean* on; + + + patch_t* p; // icon + int data; // user data + +} st_binicon_t; + + + +// +// Widget creation, access, and update routines +// + +// Initializes widget library. +// More precisely, initialize STMINUS, +// everything else is done somewhere else. +// +void STlib_init(void); + + + +// Number widget routines +void +STlib_initNum +( st_number_t* n, + int x, + int y, + patch_t** pl, + int* num, + boolean* on, + int width ); + +void +STlib_updateNum +( st_number_t* n, + boolean refresh ); + + +// Percent widget routines +void +STlib_initPercent +( st_percent_t* p, + int x, + int y, + patch_t** pl, + int* num, + boolean* on, + patch_t* percent ); + + +void +STlib_updatePercent +( st_percent_t* per, + int refresh ); + + +// Multiple Icon widget routines +void +STlib_initMultIcon +( st_multicon_t* mi, + int x, + int y, + patch_t** il, + int* inum, + boolean* on ); + + +void +STlib_updateMultIcon +( st_multicon_t* mi, + boolean refresh ); + +// Binary Icon widget routines + +void +STlib_initBinIcon +( st_binicon_t* b, + int x, + int y, + patch_t* i, + boolean* val, + boolean* on ); + +void +STlib_updateBinIcon +( st_binicon_t* bi, + boolean refresh ); + +#endif diff --git a/firmware_p4/components/Applications/doom/st_stuff.c b/firmware_p4/components/Applications/doom/st_stuff.c new file mode 100644 index 000000000..e25acccb1 --- /dev/null +++ b/firmware_p4/components/Applications/doom/st_stuff.c @@ -0,0 +1,1416 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Status bar code. +// Does the face/direction indicator animatin. +// Does palette indicators as well (red pain/berserk, bright pickup) +// + + + +#include + +#include "i_system.h" +#include "i_video.h" +#include "z_zone.h" +#include "m_misc.h" +#include "m_random.h" +#include "w_wad.h" + +#include "deh_main.h" +#include "deh_misc.h" +#include "doomdef.h" +#include "doomkeys.h" + +#include "g_game.h" + +#include "st_stuff.h" +#include "st_lib.h" +#include "r_local.h" + +#include "p_local.h" +#include "p_inter.h" + +#include "am_map.h" +#include "m_cheat.h" + +#include "s_sound.h" + +// Needs access to LFB. +#include "v_video.h" + +// State. +#include "doomstat.h" + +// Data. +#include "dstrings.h" +#include "sounds.h" + +// +// STATUS BAR DATA +// + + +// Palette indices. +// For damage/bonus red-/gold-shifts +#define STARTREDPALS 1 +#define STARTBONUSPALS 9 +#define NUMREDPALS 8 +#define NUMBONUSPALS 4 +// Radiation suit, green shift. +#define RADIATIONPAL 13 + +// N/256*100% probability +// that the normal face state will change +#define ST_FACEPROBABILITY 96 + +// For Responder +#define ST_TOGGLECHAT KEY_ENTER + +// Location of status bar +#define ST_X 0 +#define ST_X2 104 + +#define ST_FX 143 +#define ST_FY 169 + +// Should be set to patch width +// for tall numbers later on +#define ST_TALLNUMWIDTH (tallnum[0]->width) + +// Number of status faces. +#define ST_NUMPAINFACES 5 +#define ST_NUMSTRAIGHTFACES 3 +#define ST_NUMTURNFACES 2 +#define ST_NUMSPECIALFACES 3 + +#define ST_FACESTRIDE \ + (ST_NUMSTRAIGHTFACES+ST_NUMTURNFACES+ST_NUMSPECIALFACES) + +#define ST_NUMEXTRAFACES 2 + +#define ST_NUMFACES \ + (ST_FACESTRIDE*ST_NUMPAINFACES+ST_NUMEXTRAFACES) + +#define ST_TURNOFFSET (ST_NUMSTRAIGHTFACES) +#define ST_OUCHOFFSET (ST_TURNOFFSET + ST_NUMTURNFACES) +#define ST_EVILGRINOFFSET (ST_OUCHOFFSET + 1) +#define ST_RAMPAGEOFFSET (ST_EVILGRINOFFSET + 1) +#define ST_GODFACE (ST_NUMPAINFACES*ST_FACESTRIDE) +#define ST_DEADFACE (ST_GODFACE+1) + +#define ST_FACESX 143 +#define ST_FACESY 168 + +#define ST_EVILGRINCOUNT (2*TICRATE) +#define ST_STRAIGHTFACECOUNT (TICRATE/2) +#define ST_TURNCOUNT (1*TICRATE) +#define ST_OUCHCOUNT (1*TICRATE) +#define ST_RAMPAGEDELAY (2*TICRATE) + +#define ST_MUCHPAIN 20 + + +// Location and size of statistics, +// justified according to widget type. +// Problem is, within which space? STbar? Screen? +// Note: this could be read in by a lump. +// Problem is, is the stuff rendered +// into a buffer, +// or into the frame buffer? + +// AMMO number pos. +#define ST_AMMOWIDTH 3 +#define ST_AMMOX 44 +#define ST_AMMOY 171 + +// HEALTH number pos. +#define ST_HEALTHWIDTH 3 +#define ST_HEALTHX 90 +#define ST_HEALTHY 171 + +// Weapon pos. +#define ST_ARMSX 111 +#define ST_ARMSY 172 +#define ST_ARMSBGX 104 +#define ST_ARMSBGY 168 +#define ST_ARMSXSPACE 12 +#define ST_ARMSYSPACE 10 + +// Frags pos. +#define ST_FRAGSX 138 +#define ST_FRAGSY 171 +#define ST_FRAGSWIDTH 2 + +// ARMOR number pos. +#define ST_ARMORWIDTH 3 +#define ST_ARMORX 221 +#define ST_ARMORY 171 + +// Key icon positions. +#define ST_KEY0WIDTH 8 +#define ST_KEY0HEIGHT 5 +#define ST_KEY0X 239 +#define ST_KEY0Y 171 +#define ST_KEY1WIDTH ST_KEY0WIDTH +#define ST_KEY1X 239 +#define ST_KEY1Y 181 +#define ST_KEY2WIDTH ST_KEY0WIDTH +#define ST_KEY2X 239 +#define ST_KEY2Y 191 + +// Ammunition counter. +#define ST_AMMO0WIDTH 3 +#define ST_AMMO0HEIGHT 6 +#define ST_AMMO0X 288 +#define ST_AMMO0Y 173 +#define ST_AMMO1WIDTH ST_AMMO0WIDTH +#define ST_AMMO1X 288 +#define ST_AMMO1Y 179 +#define ST_AMMO2WIDTH ST_AMMO0WIDTH +#define ST_AMMO2X 288 +#define ST_AMMO2Y 191 +#define ST_AMMO3WIDTH ST_AMMO0WIDTH +#define ST_AMMO3X 288 +#define ST_AMMO3Y 185 + +// Indicate maximum ammunition. +// Only needed because backpack exists. +#define ST_MAXAMMO0WIDTH 3 +#define ST_MAXAMMO0HEIGHT 5 +#define ST_MAXAMMO0X 314 +#define ST_MAXAMMO0Y 173 +#define ST_MAXAMMO1WIDTH ST_MAXAMMO0WIDTH +#define ST_MAXAMMO1X 314 +#define ST_MAXAMMO1Y 179 +#define ST_MAXAMMO2WIDTH ST_MAXAMMO0WIDTH +#define ST_MAXAMMO2X 314 +#define ST_MAXAMMO2Y 191 +#define ST_MAXAMMO3WIDTH ST_MAXAMMO0WIDTH +#define ST_MAXAMMO3X 314 +#define ST_MAXAMMO3Y 185 + +// pistol +#define ST_WEAPON0X 110 +#define ST_WEAPON0Y 172 + +// shotgun +#define ST_WEAPON1X 122 +#define ST_WEAPON1Y 172 + +// chain gun +#define ST_WEAPON2X 134 +#define ST_WEAPON2Y 172 + +// missile launcher +#define ST_WEAPON3X 110 +#define ST_WEAPON3Y 181 + +// plasma gun +#define ST_WEAPON4X 122 +#define ST_WEAPON4Y 181 + + // bfg +#define ST_WEAPON5X 134 +#define ST_WEAPON5Y 181 + +// WPNS title +#define ST_WPNSX 109 +#define ST_WPNSY 191 + + // DETH title +#define ST_DETHX 109 +#define ST_DETHY 191 + +//Incoming messages window location +//UNUSED +// #define ST_MSGTEXTX (viewwindowx) +// #define ST_MSGTEXTY (viewwindowy+viewheight-18) +#define ST_MSGTEXTX 0 +#define ST_MSGTEXTY 0 +// Dimensions given in characters. +#define ST_MSGWIDTH 52 +// Or shall I say, in lines? +#define ST_MSGHEIGHT 1 + +#define ST_OUTTEXTX 0 +#define ST_OUTTEXTY 6 + +// Width, in characters again. +#define ST_OUTWIDTH 52 + // Height, in lines. +#define ST_OUTHEIGHT 1 + +#define ST_MAPTITLEX \ + (SCREENWIDTH - ST_MAPWIDTH * ST_CHATFONTWIDTH) + +#define ST_MAPTITLEY 0 +#define ST_MAPHEIGHT 1 + +// graphics are drawn to a backing screen and blitted to the real screen +byte *st_backing_screen; + +// main player in game +static player_t* plyr; + +// ST_Start() has just been called +static boolean st_firsttime; + +// lump number for PLAYPAL +static int lu_palette; + +// used for timing +static unsigned int st_clock; + +// used for making messages go away +static int st_msgcounter=0; + +// used when in chat +static st_chatstateenum_t st_chatstate; + +// whether in automap or first-person +static st_stateenum_t st_gamestate; + +// whether left-side main status bar is active +static boolean st_statusbaron; + +// whether status bar chat is active +static boolean st_chat; + +// value of st_chat before message popped up +static boolean st_oldchat; + +// whether chat window has the cursor on +static boolean st_cursoron; + +// !deathmatch +static boolean st_notdeathmatch; + +// !deathmatch && st_statusbaron +static boolean st_armson; + +// !deathmatch +static boolean st_fragson; + +// main bar left +static patch_t* sbar; + +// 0-9, tall numbers +static patch_t* tallnum[10]; + +// tall % sign +static patch_t* tallpercent; + +// 0-9, short, yellow (,different!) numbers +static patch_t* shortnum[10]; + +// 3 key-cards, 3 skulls +static patch_t* keys[NUMCARDS]; + +// face status patches +static patch_t* faces[ST_NUMFACES]; + +// face background +static patch_t* faceback; + + // main bar right +static patch_t* armsbg; + +// weapon ownership patches +static patch_t* arms[6][2]; + +// ready-weapon widget +static st_number_t w_ready; + + // in deathmatch only, summary of frags stats +static st_number_t w_frags; + +// health widget +static st_percent_t w_health; + +// arms background +static st_binicon_t w_armsbg; + + +// weapon ownership widgets +static st_multicon_t w_arms[6]; + +// face status widget +static st_multicon_t w_faces; + +// keycard widgets +static st_multicon_t w_keyboxes[3]; + +// armor widget +static st_percent_t w_armor; + +// ammo widgets +static st_number_t w_ammo[4]; + +// max ammo widgets +static st_number_t w_maxammo[4]; + + + + // number of frags so far in deathmatch +static int st_fragscount; + +// used to use appopriately pained face +static int st_oldhealth = -1; + +// used for evil grin +static boolean oldweaponsowned[NUMWEAPONS]; + + // count until face changes +static int st_facecount = 0; + +// current face index, used by w_faces +static int st_faceindex = 0; + +// holds key-type for each key box on bar +static int keyboxes[3]; + +// a random number per tick +static int st_randomnumber; + +cheatseq_t cheat_mus = CHEAT("idmus", 2); +cheatseq_t cheat_god = CHEAT("iddqd", 0); +cheatseq_t cheat_ammo = CHEAT("idkfa", 0); +cheatseq_t cheat_ammonokey = CHEAT("idfa", 0); +cheatseq_t cheat_noclip = CHEAT("idspispopd", 0); +cheatseq_t cheat_commercial_noclip = CHEAT("idclip", 0); + +cheatseq_t cheat_powerup[7] = +{ + CHEAT("idbeholdv", 0), + CHEAT("idbeholds", 0), + CHEAT("idbeholdi", 0), + CHEAT("idbeholdr", 0), + CHEAT("idbeholda", 0), + CHEAT("idbeholdl", 0), + CHEAT("idbehold", 0), +}; + +cheatseq_t cheat_choppers = CHEAT("idchoppers", 0); +cheatseq_t cheat_clev = CHEAT("idclev", 2); +cheatseq_t cheat_mypos = CHEAT("idmypos", 0); + + +// +// STATUS BAR CODE +// +void ST_Stop(void); + +void ST_refreshBackground(void) +{ + + if (st_statusbaron) + { + V_UseBuffer(st_backing_screen); + + V_DrawPatch(ST_X, 0, sbar); + + if (netgame) + V_DrawPatch(ST_FX, 0, faceback); + + V_RestoreBuffer(); + + V_CopyRect(ST_X, 0, st_backing_screen, ST_WIDTH, ST_HEIGHT, ST_X, ST_Y); + } + +} + + +// Respond to keyboard input events, +// intercept cheats. +boolean +ST_Responder (event_t* ev) +{ + int i; + + // Filter automap on/off. + if (ev->type == ev_keyup + && ((ev->data1 & 0xffff0000) == AM_MSGHEADER)) + { + switch(ev->data1) + { + case AM_MSGENTERED: + st_gamestate = AutomapState; + st_firsttime = true; + break; + + case AM_MSGEXITED: + // fprintf(stderr, "AM exited\n"); + st_gamestate = FirstPersonState; + break; + } + } + + // if a user keypress... + else if (ev->type == ev_keydown) + { + if (!netgame && gameskill != sk_nightmare) + { + // 'dqd' cheat for toggleable god mode + if (cht_CheckCheat(&cheat_god, ev->data2)) + { + plyr->cheats ^= CF_GODMODE; + if (plyr->cheats & CF_GODMODE) + { + if (plyr->mo) + plyr->mo->health = 100; + + plyr->health = deh_god_mode_health; + plyr->message = DEH_String(STSTR_DQDON); + } + else + plyr->message = DEH_String(STSTR_DQDOFF); + } + // 'fa' cheat for killer fucking arsenal + else if (cht_CheckCheat(&cheat_ammonokey, ev->data2)) + { + plyr->armorpoints = deh_idfa_armor; + plyr->armortype = deh_idfa_armor_class; + + for (i=0;iweaponowned[i] = true; + + for (i=0;iammo[i] = plyr->maxammo[i]; + + plyr->message = DEH_String(STSTR_FAADDED); + } + // 'kfa' cheat for key full ammo + else if (cht_CheckCheat(&cheat_ammo, ev->data2)) + { + plyr->armorpoints = deh_idkfa_armor; + plyr->armortype = deh_idkfa_armor_class; + + for (i=0;iweaponowned[i] = true; + + for (i=0;iammo[i] = plyr->maxammo[i]; + + for (i=0;icards[i] = true; + + plyr->message = DEH_String(STSTR_KFAADDED); + } + // 'mus' cheat for changing music + else if (cht_CheckCheat(&cheat_mus, ev->data2)) + { + + char buf[3]; + int musnum; + + plyr->message = DEH_String(STSTR_MUS); + cht_GetParam(&cheat_mus, buf); + + // Note: The original v1.9 had a bug that tried to play back + // the Doom II music regardless of gamemode. This was fixed + // in the Ultimate Doom executable so that it would work for + // the Doom 1 music as well. + + if (gamemode == commercial || gameversion < exe_ultimate) + { + musnum = mus_runnin + (buf[0]-'0')*10 + buf[1]-'0' - 1; + + if (((buf[0]-'0')*10 + buf[1]-'0') > 35) + plyr->message = DEH_String(STSTR_NOMUS); + else + S_ChangeMusic(musnum, 1); + } + else + { + musnum = mus_e1m1 + (buf[0]-'1')*9 + (buf[1]-'1'); + + if (((buf[0]-'1')*9 + buf[1]-'1') > 31) + plyr->message = DEH_String(STSTR_NOMUS); + else + S_ChangeMusic(musnum, 1); + } + } + else if ( (logical_gamemission == doom + && cht_CheckCheat(&cheat_noclip, ev->data2)) + || (logical_gamemission != doom + && cht_CheckCheat(&cheat_commercial_noclip,ev->data2))) + { + // Noclip cheat. + // For Doom 1, use the idspipsopd cheat; for all others, use + // idclip + + plyr->cheats ^= CF_NOCLIP; + + if (plyr->cheats & CF_NOCLIP) + plyr->message = DEH_String(STSTR_NCON); + else + plyr->message = DEH_String(STSTR_NCOFF); + } + // 'behold?' power-up cheats + for (i=0;i<6;i++) + { + if (cht_CheckCheat(&cheat_powerup[i], ev->data2)) + { + if (!plyr->powers[i]) + P_GivePower( plyr, i); + else if (i!=pw_strength) + plyr->powers[i] = 1; + else + plyr->powers[i] = 0; + + plyr->message = DEH_String(STSTR_BEHOLDX); + } + } + + // 'behold' power-up menu + if (cht_CheckCheat(&cheat_powerup[6], ev->data2)) + { + plyr->message = DEH_String(STSTR_BEHOLD); + } + // 'choppers' invulnerability & chainsaw + else if (cht_CheckCheat(&cheat_choppers, ev->data2)) + { + plyr->weaponowned[wp_chainsaw] = true; + plyr->powers[pw_invulnerability] = true; + plyr->message = DEH_String(STSTR_CHOPPERS); + } + // 'mypos' for player position + else if (cht_CheckCheat(&cheat_mypos, ev->data2)) + { + static char buf[ST_MSGWIDTH]; + M_snprintf(buf, sizeof(buf), "ang=0x%x;x,y=(0x%x,0x%x)", + players[consoleplayer].mo->angle, + players[consoleplayer].mo->x, + players[consoleplayer].mo->y); + plyr->message = buf; + } + } + + // 'clev' change-level cheat + if (!netgame && cht_CheckCheat(&cheat_clev, ev->data2)) + { + char buf[3]; + int epsd; + int map; + + cht_GetParam(&cheat_clev, buf); + + if (gamemode == commercial) + { + epsd = 1; + map = (buf[0] - '0')*10 + buf[1] - '0'; + } + else + { + epsd = buf[0] - '0'; + map = buf[1] - '0'; + } + + // Chex.exe always warps to episode 1. + + if (gameversion == exe_chex) + { + epsd = 1; + } + + // Catch invalid maps. + if (epsd < 1) + return false; + + if (map < 1) + return false; + + // Ohmygod - this is not going to work. + if ((gamemode == retail) + && ((epsd > 4) || (map > 9))) + return false; + + if ((gamemode == registered) + && ((epsd > 3) || (map > 9))) + return false; + + if ((gamemode == shareware) + && ((epsd > 1) || (map > 9))) + return false; + + // The source release has this check as map > 34. However, Vanilla + // Doom allows IDCLEV up to MAP40 even though it normally crashes. + if ((gamemode == commercial) + && (( epsd > 1) || (map > 40))) + return false; + + // So be it. + plyr->message = DEH_String(STSTR_CLEV); + G_DeferedInitNew(gameskill, epsd, map); + } + } + return false; +} + + + +int ST_calcPainOffset(void) +{ + int health; + static int lastcalc; + static int oldhealth = -1; + + health = plyr->health > 100 ? 100 : plyr->health; + + if (health != oldhealth) + { + lastcalc = ST_FACESTRIDE * (((100 - health) * ST_NUMPAINFACES) / 101); + oldhealth = health; + } + return lastcalc; +} + + +// +// This is a not-very-pretty routine which handles +// the face states and their timing. +// the precedence of expressions is: +// dead > evil grin > turned head > straight ahead +// +void ST_updateFaceWidget(void) +{ + int i; + angle_t badguyangle; + angle_t diffang; + static int lastattackdown = -1; + static int priority = 0; + boolean doevilgrin; + + if (priority < 10) + { + // dead + if (!plyr->health) + { + priority = 9; + st_faceindex = ST_DEADFACE; + st_facecount = 1; + } + } + + if (priority < 9) + { + if (plyr->bonuscount) + { + // picking up bonus + doevilgrin = false; + + for (i=0;iweaponowned[i]) + { + doevilgrin = true; + oldweaponsowned[i] = plyr->weaponowned[i]; + } + } + if (doevilgrin) + { + // evil grin if just picked up weapon + priority = 8; + st_facecount = ST_EVILGRINCOUNT; + st_faceindex = ST_calcPainOffset() + ST_EVILGRINOFFSET; + } + } + + } + + if (priority < 8) + { + if (plyr->damagecount + && plyr->attacker + && plyr->attacker != plyr->mo) + { + // being attacked + priority = 7; + + if (plyr->health - st_oldhealth > ST_MUCHPAIN) + { + st_facecount = ST_TURNCOUNT; + st_faceindex = ST_calcPainOffset() + ST_OUCHOFFSET; + } + else + { + badguyangle = R_PointToAngle2(plyr->mo->x, + plyr->mo->y, + plyr->attacker->x, + plyr->attacker->y); + + if (badguyangle > plyr->mo->angle) + { + // whether right or left + diffang = badguyangle - plyr->mo->angle; + i = diffang > ANG180; + } + else + { + // whether left or right + diffang = plyr->mo->angle - badguyangle; + i = diffang <= ANG180; + } // confusing, aint it? + + + st_facecount = ST_TURNCOUNT; + st_faceindex = ST_calcPainOffset(); + + if (diffang < ANG45) + { + // head-on + st_faceindex += ST_RAMPAGEOFFSET; + } + else if (i) + { + // turn face right + st_faceindex += ST_TURNOFFSET; + } + else + { + // turn face left + st_faceindex += ST_TURNOFFSET+1; + } + } + } + } + + if (priority < 7) + { + // getting hurt because of your own damn stupidity + if (plyr->damagecount) + { + if (plyr->health - st_oldhealth > ST_MUCHPAIN) + { + priority = 7; + st_facecount = ST_TURNCOUNT; + st_faceindex = ST_calcPainOffset() + ST_OUCHOFFSET; + } + else + { + priority = 6; + st_facecount = ST_TURNCOUNT; + st_faceindex = ST_calcPainOffset() + ST_RAMPAGEOFFSET; + } + + } + + } + + if (priority < 6) + { + // rapid firing + if (plyr->attackdown) + { + if (lastattackdown==-1) + lastattackdown = ST_RAMPAGEDELAY; + else if (!--lastattackdown) + { + priority = 5; + st_faceindex = ST_calcPainOffset() + ST_RAMPAGEOFFSET; + st_facecount = 1; + lastattackdown = 1; + } + } + else + lastattackdown = -1; + + } + + if (priority < 5) + { + // invulnerability + if ((plyr->cheats & CF_GODMODE) + || plyr->powers[pw_invulnerability]) + { + priority = 4; + + st_faceindex = ST_GODFACE; + st_facecount = 1; + + } + + } + + // look left or look right if the facecount has timed out + if (!st_facecount) + { + st_faceindex = ST_calcPainOffset() + (st_randomnumber % 3); + st_facecount = ST_STRAIGHTFACECOUNT; + priority = 0; + } + + st_facecount--; + +} + +void ST_updateWidgets(void) +{ + static int largeammo = 1994; // means "n/a" + int i; + + // must redirect the pointer if the ready weapon has changed. + // if (w_ready.data != plyr->readyweapon) + // { + if (weaponinfo[plyr->readyweapon].ammo == am_noammo) + w_ready.num = &largeammo; + else + w_ready.num = &plyr->ammo[weaponinfo[plyr->readyweapon].ammo]; + //{ + // static int tic=0; + // static int dir=-1; + // if (!(tic&15)) + // plyr->ammo[weaponinfo[plyr->readyweapon].ammo]+=dir; + // if (plyr->ammo[weaponinfo[plyr->readyweapon].ammo] == -100) + // dir = 1; + // tic++; + // } + w_ready.data = plyr->readyweapon; + + // if (*w_ready.on) + // STlib_updateNum(&w_ready, true); + // refresh weapon change + // } + + // update keycard multiple widgets + for (i=0;i<3;i++) + { + keyboxes[i] = plyr->cards[i] ? i : -1; + + if (plyr->cards[i+3]) + keyboxes[i] = i+3; + } + + // refresh everything if this is him coming back to life + ST_updateFaceWidget(); + + // used by the w_armsbg widget + st_notdeathmatch = !deathmatch; + + // used by w_arms[] widgets + st_armson = st_statusbaron && !deathmatch; + + // used by w_frags widget + st_fragson = deathmatch && st_statusbaron; + st_fragscount = 0; + + for (i=0 ; ifrags[i]; + else + st_fragscount -= plyr->frags[i]; + } + + // get rid of chat window if up because of message + if (!--st_msgcounter) + st_chat = st_oldchat; + +} + +void ST_Ticker (void) +{ + + st_clock++; + st_randomnumber = M_Random(); + ST_updateWidgets(); + st_oldhealth = plyr->health; + +} + +static int st_palette = 0; + +void ST_doPaletteStuff(void) +{ + + int palette; + byte* pal; + int cnt; + int bzc; + + cnt = plyr->damagecount; + + if (plyr->powers[pw_strength]) + { + // slowly fade the berzerk out + bzc = 12 - (plyr->powers[pw_strength]>>6); + + if (bzc > cnt) + cnt = bzc; + } + + if (cnt) + { + palette = (cnt+7)>>3; + + if (palette >= NUMREDPALS) + palette = NUMREDPALS-1; + + palette += STARTREDPALS; + } + + else if (plyr->bonuscount) + { + palette = (plyr->bonuscount+7)>>3; + + if (palette >= NUMBONUSPALS) + palette = NUMBONUSPALS-1; + + palette += STARTBONUSPALS; + } + + else if ( plyr->powers[pw_ironfeet] > 4*32 + || plyr->powers[pw_ironfeet]&8) + palette = RADIATIONPAL; + else + palette = 0; + + // In Chex Quest, the player never sees red. Instead, the + // radiation suit palette is used to tint the screen green, + // as though the player is being covered in goo by an + // attacking flemoid. + + if (gameversion == exe_chex + && palette >= STARTREDPALS && palette < STARTREDPALS + NUMREDPALS) + { + palette = RADIATIONPAL; + } + + if (palette != st_palette) + { + st_palette = palette; + pal = (byte *) W_CacheLumpNum (lu_palette, PU_CACHE)+palette*768; + I_SetPalette (pal); + } + +} + +void ST_drawWidgets(boolean refresh) +{ + int i; + + // used by w_arms[] widgets + st_armson = st_statusbaron && !deathmatch; + + // used by w_frags widget + st_fragson = deathmatch && st_statusbaron; + + STlib_updateNum(&w_ready, refresh); + + for (i=0;i<4;i++) + { + STlib_updateNum(&w_ammo[i], refresh); + STlib_updateNum(&w_maxammo[i], refresh); + } + + STlib_updatePercent(&w_health, refresh); + STlib_updatePercent(&w_armor, refresh); + + STlib_updateBinIcon(&w_armsbg, refresh); + + for (i=0;i<6;i++) + STlib_updateMultIcon(&w_arms[i], refresh); + + STlib_updateMultIcon(&w_faces, refresh); + + for (i=0;i<3;i++) + STlib_updateMultIcon(&w_keyboxes[i], refresh); + + STlib_updateNum(&w_frags, refresh); + +} + +void ST_doRefresh(void) +{ + + st_firsttime = false; + + // draw status bar background to off-screen buff + ST_refreshBackground(); + + // and refresh all widgets + ST_drawWidgets(true); + +} + +void ST_diffDraw(void) +{ + // update all widgets + ST_drawWidgets(false); +} + +void ST_Drawer (boolean fullscreen, boolean refresh) +{ + + st_statusbaron = (!fullscreen) || automapactive; + st_firsttime = st_firsttime || refresh; + + // Do red-/gold-shifts from damage/items + ST_doPaletteStuff(); + + // If just after ST_Start(), refresh all + if (st_firsttime) ST_doRefresh(); + // Otherwise, update as little as possible + else ST_diffDraw(); + +} + +typedef void (*load_callback_t)(char *lumpname, patch_t **variable); + +// Iterates through all graphics to be loaded or unloaded, along with +// the variable they use, invoking the specified callback function. + +static void ST_loadUnloadGraphics(load_callback_t callback) +{ + + int i; + int j; + int facenum; + + char namebuf[9]; + + // Load the numbers, tall and short + for (i=0;i<10;i++) + { + DEH_snprintf(namebuf, 9, "STTNUM%d", i); + callback(namebuf, &tallnum[i]); + + DEH_snprintf(namebuf, 9, "STYSNUM%d", i); + callback(namebuf, &shortnum[i]); + } + + // Load percent key. + //Note: why not load STMINUS here, too? + + callback(DEH_String("STTPRCNT"), &tallpercent); + + // key cards + for (i=0;iweaponowned[i]; + + for (i=0;i<3;i++) + keyboxes[i] = -1; + + STlib_init(); + +} + + + +void ST_createWidgets(void) +{ + + int i; + + // ready weapon ammo + STlib_initNum(&w_ready, + ST_AMMOX, + ST_AMMOY, + tallnum, + &plyr->ammo[weaponinfo[plyr->readyweapon].ammo], + &st_statusbaron, + ST_AMMOWIDTH ); + + // the last weapon type + w_ready.data = plyr->readyweapon; + + // health percentage + STlib_initPercent(&w_health, + ST_HEALTHX, + ST_HEALTHY, + tallnum, + &plyr->health, + &st_statusbaron, + tallpercent); + + // arms background + STlib_initBinIcon(&w_armsbg, + ST_ARMSBGX, + ST_ARMSBGY, + armsbg, + &st_notdeathmatch, + &st_statusbaron); + + // weapons owned + for(i=0;i<6;i++) + { + STlib_initMultIcon(&w_arms[i], + ST_ARMSX+(i%3)*ST_ARMSXSPACE, + ST_ARMSY+(i/3)*ST_ARMSYSPACE, + arms[i], (int *) &plyr->weaponowned[i+1], + &st_armson); + } + + // frags sum + STlib_initNum(&w_frags, + ST_FRAGSX, + ST_FRAGSY, + tallnum, + &st_fragscount, + &st_fragson, + ST_FRAGSWIDTH); + + // faces + STlib_initMultIcon(&w_faces, + ST_FACESX, + ST_FACESY, + faces, + &st_faceindex, + &st_statusbaron); + + // armor percentage - should be colored later + STlib_initPercent(&w_armor, + ST_ARMORX, + ST_ARMORY, + tallnum, + &plyr->armorpoints, + &st_statusbaron, tallpercent); + + // keyboxes 0-2 + STlib_initMultIcon(&w_keyboxes[0], + ST_KEY0X, + ST_KEY0Y, + keys, + &keyboxes[0], + &st_statusbaron); + + STlib_initMultIcon(&w_keyboxes[1], + ST_KEY1X, + ST_KEY1Y, + keys, + &keyboxes[1], + &st_statusbaron); + + STlib_initMultIcon(&w_keyboxes[2], + ST_KEY2X, + ST_KEY2Y, + keys, + &keyboxes[2], + &st_statusbaron); + + // ammo count (all four kinds) + STlib_initNum(&w_ammo[0], + ST_AMMO0X, + ST_AMMO0Y, + shortnum, + &plyr->ammo[0], + &st_statusbaron, + ST_AMMO0WIDTH); + + STlib_initNum(&w_ammo[1], + ST_AMMO1X, + ST_AMMO1Y, + shortnum, + &plyr->ammo[1], + &st_statusbaron, + ST_AMMO1WIDTH); + + STlib_initNum(&w_ammo[2], + ST_AMMO2X, + ST_AMMO2Y, + shortnum, + &plyr->ammo[2], + &st_statusbaron, + ST_AMMO2WIDTH); + + STlib_initNum(&w_ammo[3], + ST_AMMO3X, + ST_AMMO3Y, + shortnum, + &plyr->ammo[3], + &st_statusbaron, + ST_AMMO3WIDTH); + + // max ammo count (all four kinds) + STlib_initNum(&w_maxammo[0], + ST_MAXAMMO0X, + ST_MAXAMMO0Y, + shortnum, + &plyr->maxammo[0], + &st_statusbaron, + ST_MAXAMMO0WIDTH); + + STlib_initNum(&w_maxammo[1], + ST_MAXAMMO1X, + ST_MAXAMMO1Y, + shortnum, + &plyr->maxammo[1], + &st_statusbaron, + ST_MAXAMMO1WIDTH); + + STlib_initNum(&w_maxammo[2], + ST_MAXAMMO2X, + ST_MAXAMMO2Y, + shortnum, + &plyr->maxammo[2], + &st_statusbaron, + ST_MAXAMMO2WIDTH); + + STlib_initNum(&w_maxammo[3], + ST_MAXAMMO3X, + ST_MAXAMMO3Y, + shortnum, + &plyr->maxammo[3], + &st_statusbaron, + ST_MAXAMMO3WIDTH); + +} + +static boolean st_stopped = true; + + +void ST_Start (void) +{ + + if (!st_stopped) + ST_Stop(); + + ST_initData(); + ST_createWidgets(); + st_stopped = false; + +} + +void ST_Stop (void) +{ + if (st_stopped) + return; + + I_SetPalette (W_CacheLumpNum (lu_palette, PU_CACHE)); + + st_stopped = true; +} + +void ST_Init (void) +{ + ST_loadData(); + st_backing_screen = (byte *) Z_Malloc(ST_WIDTH * ST_HEIGHT, PU_STATIC, 0); +} + diff --git a/firmware_p4/components/Applications/doom/st_stuff.h b/firmware_p4/components/Applications/doom/st_stuff.h new file mode 100644 index 000000000..8ed53e41c --- /dev/null +++ b/firmware_p4/components/Applications/doom/st_stuff.h @@ -0,0 +1,89 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Status bar code. +// Does the face/direction indicator animatin. +// Does palette indicators as well (red pain/berserk, bright pickup) +// + +#ifndef __STSTUFF_H__ +#define __STSTUFF_H__ + +#include "doomtype.h" +#include "d_event.h" +#include "m_cheat.h" + +// Size of statusbar. +// Now sensitive for scaling. +#define ST_HEIGHT 32 +#define ST_WIDTH SCREENWIDTH +#define ST_Y (SCREENHEIGHT - ST_HEIGHT) + + +// +// STATUS BAR +// + +// Called by main loop. +boolean ST_Responder (event_t* ev); + +// Called by main loop. +void ST_Ticker (void); + +// Called by main loop. +void ST_Drawer (boolean fullscreen, boolean refresh); + +// Called when the console player is spawned on each level. +void ST_Start (void); + +// Called by startup code. +void ST_Init (void); + + + +// States for status bar code. +typedef enum +{ + AutomapState, + FirstPersonState + +} st_stateenum_t; + + +// States for the chat code. +typedef enum +{ + StartChatState, + WaitDestState, + GetChatState + +} st_chatstateenum_t; + + + +extern byte *st_backing_screen; +extern cheatseq_t cheat_mus; +extern cheatseq_t cheat_god; +extern cheatseq_t cheat_ammo; +extern cheatseq_t cheat_ammonokey; +extern cheatseq_t cheat_noclip; +extern cheatseq_t cheat_commercial_noclip; +extern cheatseq_t cheat_powerup[7]; +extern cheatseq_t cheat_choppers; +extern cheatseq_t cheat_clev; +extern cheatseq_t cheat_mypos; + + +#endif diff --git a/firmware_p4/components/Applications/doom/statdump.c b/firmware_p4/components/Applications/doom/statdump.c new file mode 100644 index 000000000..7afe3f366 --- /dev/null +++ b/firmware_p4/components/Applications/doom/statdump.c @@ -0,0 +1,392 @@ + /* + + Copyright(C) 2005-2014 Simon Howard + + 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 + of the License, 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. + + -- + + Functions for presenting the information captured from the statistics + buffer to a file. + + */ + +#include +#include +#include + +#include "d_player.h" +#include "d_mode.h" +#include "m_argv.h" + +#include "statdump.h" + +/* Par times for E1M1-E1M9. */ +static const int doom1_par_times[] = +{ + 30, 75, 120, 90, 165, 180, 180, 30, 165, +}; + +/* Par times for MAP01-MAP09. */ +static const int doom2_par_times[] = +{ + 30, 90, 120, 120, 90, 150, 120, 120, 270, +}; + +#if ORIGCODE + +/* Player colors. */ +static const char *player_colors[] = +{ + "Green", "Indigo", "Brown", "Red" +}; + +#endif + +// Array of end-of-level statistics that have been captured. + +#define MAX_CAPTURES 32 +static wbstartstruct_t captured_stats[MAX_CAPTURES]; +static int num_captured_stats = 0; + +#if ORIGCODE +static GameMission_t discovered_gamemission = none; +#endif + +#if ORIGCODE + +/* Try to work out whether this is a Doom 1 or Doom 2 game, by looking + * at the episode and map, and the par times. This is used to decide + * how to format the level name. Unfortunately, in some cases it is + * impossible to determine whether this is Doom 1 or Doom 2. */ + +static void DiscoverGamemode(wbstartstruct_t *stats, int num_stats) +{ + int partime; + int level; + int i; + + if (discovered_gamemission != none) + { + return; + } + + for (i=0; i 0) + { + discovered_gamemission = doom; + return; + } + + /* This is episode 1. If this is level 10 or higher, + it must be Doom 2. */ + + if (level >= 9) + { + discovered_gamemission = doom2; + return; + } + + /* Try to work out if this is Doom 1 or Doom 2 by looking + at the par time. */ + + partime = stats[i].partime; + + if (partime == doom1_par_times[level] * TICRATE + && partime != doom2_par_times[level] * TICRATE) + { + discovered_gamemission = doom; + return; + } + + if (partime != doom1_par_times[level] * TICRATE + && partime == doom2_par_times[level] * TICRATE) + { + discovered_gamemission = doom2; + return; + } + } +} + +#endif + +#if ORIGCODE + +/* Returns the number of players active in the given stats buffer. */ + +static int GetNumPlayers(wbstartstruct_t *stats) +{ + int i; + int num_players = 0; + + for (i=0; iplyr[i].in) + { + ++num_players; + } + } + + return num_players; +} + +#endif + +#if ORIGCODE + +static void PrintBanner(FILE *stream) +{ + fprintf(stream, "===========================================\n"); +} + +static void PrintPercentage(FILE *stream, int amount, int total) +{ + if (total == 0) + { + fprintf(stream, "0"); + } + else + { + fprintf(stream, "%i / %i", amount, total); + + // statdump.exe is a 16-bit program, so very occasionally an + // integer overflow can occur when doing this calculation with + // a large value. Therefore, cast to short to give the same + // output. + + fprintf(stream, " (%i%%)", (short) (amount * 100) / total); + } +} + +#endif + +#if ORIGCODE + +/* Display statistics for a single player. */ + +static void PrintPlayerStats(FILE *stream, wbstartstruct_t *stats, + int player_num) +{ + wbplayerstruct_t *player = &stats->plyr[player_num]; + + fprintf(stream, "Player %i (%s):\n", player_num + 1, + player_colors[player_num]); + + /* Kills percentage */ + + fprintf(stream, "\tKills: "); + PrintPercentage(stream, player->skills, stats->maxkills); + fprintf(stream, "\n"); + + /* Items percentage */ + + fprintf(stream, "\tItems: "); + PrintPercentage(stream, player->sitems, stats->maxitems); + fprintf(stream, "\n"); + + /* Secrets percentage */ + + fprintf(stream, "\tSecrets: "); + PrintPercentage(stream, player->ssecret, stats->maxsecret); + fprintf(stream, "\n"); +} + +#endif + +#if ORIGCODE + +/* Frags table for multiplayer games. */ + +static void PrintFragsTable(FILE *stream, wbstartstruct_t *stats) +{ + int x, y; + + fprintf(stream, "Frags:\n"); + + /* Print header */ + + fprintf(stream, "\t\t"); + + for (x=0; xplyr[x].in) + { + continue; + } + + fprintf(stream, "%s\t", player_colors[x]); + } + + fprintf(stream, "\n"); + + fprintf(stream, "\t\t-------------------------------- VICTIMS\n"); + + /* Print table */ + + for (y=0; yplyr[y].in) + { + continue; + } + + fprintf(stream, "\t%s\t|", player_colors[y]); + + for (x=0; xplyr[x].in) + { + continue; + } + + fprintf(stream, "%i\t", stats->plyr[y].frags[x]); + } + + fprintf(stream, "\n"); + } + + fprintf(stream, "\t\t|\n"); + fprintf(stream, "\t KILLERS\n"); +} + +#endif + +#if ORIGCODE + +/* Displays the level name: MAPxy or ExMy, depending on game mode. */ + +static void PrintLevelName(FILE *stream, int episode, int level) +{ + PrintBanner(stream); + + switch (discovered_gamemission) + { + + case doom: + fprintf(stream, "E%iM%i\n", episode + 1, level + 1); + break; + case doom2: + fprintf(stream, "MAP%02i\n", level + 1); + break; + default: + case none: + fprintf(stream, "E%iM%i / MAP%02i\n", + episode + 1, level + 1, level + 1); + break; + } + + PrintBanner(stream); +} + +#endif + +#if ORIGCODE + +/* Print details of a statistics buffer to the given file. */ + +static void PrintStats(FILE *stream, wbstartstruct_t *stats) +{ + int leveltime, partime; + int i; + + PrintLevelName(stream, stats->epsd, stats->last); + fprintf(stream, "\n"); + + leveltime = stats->plyr[0].stime / TICRATE; + partime = stats->partime / TICRATE; + fprintf(stream, "Time: %i:%02i", leveltime / 60, leveltime % 60); + fprintf(stream, " (par: %i:%02i)\n", partime / 60, partime % 60); + fprintf(stream, "\n"); + + for (i=0; iplyr[i].in) + { + PrintPlayerStats(stream, stats, i); + } + } + + if (GetNumPlayers(stats) >= 2) + { + PrintFragsTable(stream, stats); + } + + fprintf(stream, "\n"); +} + +#endif + +void StatCopy(wbstartstruct_t *stats) +{ + if (M_ParmExists("-statdump") && num_captured_stats < MAX_CAPTURES) + { + memcpy(&captured_stats[num_captured_stats], stats, + sizeof(wbstartstruct_t)); + ++num_captured_stats; + } +} + +void StatDump(void) +{ +#if ORIGCODE + FILE *dumpfile; + int i; + + //! + // @category compat + // @arg + // + // Dump statistics information to the specified file on the levels + // that were played. The output from this option matches the output + // from statdump.exe (see ctrlapi.zip in the /idgames archive). + // + + i = M_CheckParmWithArgs("-statdump", 1); + + if (i > 0) + { + printf("Statistics captured for %i level(s)\n", num_captured_stats); + + // We actually know what the real gamemission is, but this has + // to match the output from statdump.exe. + + DiscoverGamemode(captured_stats, num_captured_stats); + + // Allow "-" as output file, for stdout. + + if (strcmp(myargv[i + 1], "-") != 0) + { + dumpfile = fopen(myargv[i + 1], "w"); + } + else + { + dumpfile = NULL; + } + + for (i = 0; i < num_captured_stats; ++i) + { + PrintStats(dumpfile, &captured_stats[i]); + } + + if (dumpfile != NULL) + { + fclose(dumpfile); + } + } +#endif +} + diff --git a/firmware_p4/components/Applications/doom/statdump.h b/firmware_p4/components/Applications/doom/statdump.h new file mode 100644 index 000000000..48db2ad2d --- /dev/null +++ b/firmware_p4/components/Applications/doom/statdump.h @@ -0,0 +1,23 @@ + /* + + Copyright(C) 2005-2014 Simon Howard + + 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 + of the License, 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. + + */ + +#ifndef DOOM_STATDUMP_H +#define DOOM_STATDUMP_H + +void StatCopy(wbstartstruct_t *stats); +void StatDump(void); + +#endif /* #ifndef DOOM_STATDUMP_H */ diff --git a/firmware_p4/components/Applications/doom/tables.c b/firmware_p4/components/Applications/doom/tables.c new file mode 100644 index 000000000..c221e9a04 --- /dev/null +++ b/firmware_p4/components/Applications/doom/tables.c @@ -0,0 +1,2227 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Lookup tables. +// Do not try to look them up :-). +// In the order of appearance: +// +// int finetangent[4096] - Tangens LUT. +// Should work with BAM fairly well (12 of 16bit, +// effectively, by shifting). +// +// int finesine[10240] - Sine lookup. +// Guess what, serves as cosine, too. +// Remarkable thing is, how to use BAMs with this? +// +// int tantoangle[2049] - ArcTan LUT, +// maps tan(angle) to angle fast. Gotta search. +// +// + +#include "tables.h" + +// to get a global angle from cartesian coordinates, the coordinates are +// flipped until they are in the first octant of the coordinate system, then +// the y (<=x) is scaled and divided by x to get a tangent (slope) value +// which is looked up in the tantoangle[] table. The +1 size is to handle +// the case when x==y without additional checking. + +int SlopeDiv(unsigned int num, unsigned int den) +{ + unsigned ans; + + if (den < 512) + { + return SLOPERANGE; + } + else + { + ans = (num << 3) / (den >> 8); + + if (ans <= SLOPERANGE) + { + return ans; + } + else + { + return SLOPERANGE; + } + } +} + +const int finetangent[4096] = +{ + -170910304,-56965752,-34178904,-24413316,-18988036,-15535599,-13145455,-11392683, + -10052327,-8994149,-8137527,-7429880,-6835455,-6329090,-5892567,-5512368, + -5178251,-4882318,-4618375,-4381502,-4167737,-3973855,-3797206,-3635590, + -3487165,-3350381,-3223918,-3106651,-2997613,-2895966,-2800983,-2712030, + -2628549,-2550052,-2476104,-2406322,-2340362,-2277919,-2218719,-2162516, + -2109087,-2058233,-2009771,-1963536,-1919378,-1877161,-1836758,-1798063, + -1760956,-1725348,-1691149,-1658278,-1626658,-1596220,-1566898,-1538632, + -1511367,-1485049,-1459630,-1435065,-1411312,-1388330,-1366084,-1344537, + -1323658,-1303416,-1283783,-1264730,-1246234,-1228269,-1210813,-1193846, + -1177345,-1161294,-1145673,-1130465,-1115654,-1101225,-1087164,-1073455, + -1060087,-1047046,-1034322,-1021901,-1009774,-997931,-986361,-975054, + -964003,-953199,-942633,-932298,-922186,-912289,-902602,-893117, + -883829,-874730,-865817,-857081,-848520,-840127,-831898,-823827, + -815910,-808143,-800521,-793041,-785699,-778490,-771411,-764460, + -757631,-750922,-744331,-737853,-731486,-725227,-719074,-713023, + -707072,-701219,-695462,-689797,-684223,-678737,-673338,-668024, + -662792,-657640,-652568,-647572,-642651,-637803,-633028,-628323, + -623686,-619117,-614613,-610174,-605798,-601483,-597229,-593033, + -588896,-584815,-580789,-576818,-572901,-569035,-565221,-561456, + -557741,-554074,-550455,-546881,-543354,-539870,-536431,-533034, + -529680,-526366,-523094,-519861,-516667,-513512,-510394,-507313, + -504269,-501261,-498287,-495348,-492443,-489571,-486732,-483925, + -481150,-478406,-475692,-473009,-470355,-467730,-465133,-462565, + -460024,-457511,-455024,-452564,-450129,-447720,-445337,-442978, + -440643,-438332,-436045,-433781,-431540,-429321,-427125,-424951, + -422798,-420666,-418555,-416465,-414395,-412344,-410314,-408303, + -406311,-404338,-402384,-400448,-398530,-396630,-394747,-392882, + -391034,-389202,-387387,-385589,-383807,-382040,-380290,-378555, + -376835,-375130,-373440,-371765,-370105,-368459,-366826,-365208, + -363604,-362013,-360436,-358872,-357321,-355783,-354257,-352744, + -351244,-349756,-348280,-346816,-345364,-343924,-342495,-341078, + -339671,-338276,-336892,-335519,-334157,-332805,-331464,-330133, + -328812,-327502,-326201,-324910,-323629,-322358,-321097,-319844, + -318601,-317368,-316143,-314928,-313721,-312524,-311335,-310154, + -308983,-307819,-306664,-305517,-304379,-303248,-302126,-301011, + -299904,-298805,-297714,-296630,-295554,-294485,-293423,-292369, + -291322,-290282,-289249,-288223,-287204,-286192,-285186,-284188, + -283195,-282210,-281231,-280258,-279292,-278332,-277378,-276430, + -275489,-274553,-273624,-272700,-271782,-270871,-269965,-269064, + -268169,-267280,-266397,-265519,-264646,-263779,-262917,-262060, + -261209,-260363,-259522,-258686,-257855,-257029,-256208,-255392, + -254581,-253774,-252973,-252176,-251384,-250596,-249813,-249035, + -248261,-247492,-246727,-245966,-245210,-244458,-243711,-242967, + -242228,-241493,-240763,-240036,-239314,-238595,-237881,-237170, + -236463,-235761,-235062,-234367,-233676,-232988,-232304,-231624, + -230948,-230275,-229606,-228941,-228279,-227621,-226966,-226314, + -225666,-225022,-224381,-223743,-223108,-222477,-221849,-221225, + -220603,-219985,-219370,-218758,-218149,-217544,-216941,-216341, + -215745,-215151,-214561,-213973,-213389,-212807,-212228,-211652, + -211079,-210509,-209941,-209376,-208815,-208255,-207699,-207145, + -206594,-206045,-205500,-204956,-204416,-203878,-203342,-202809, + -202279,-201751,-201226,-200703,-200182,-199664,-199149,-198636, + -198125,-197616,-197110,-196606,-196105,-195606,-195109,-194614, + -194122,-193631,-193143,-192658,-192174,-191693,-191213,-190736, + -190261,-189789,-189318,-188849,-188382,-187918,-187455,-186995, + -186536,-186080,-185625,-185173,-184722,-184274,-183827,-183382, + -182939,-182498,-182059,-181622,-181186,-180753,-180321,-179891, + -179463,-179037,-178612,-178190,-177769,-177349,-176932,-176516, + -176102,-175690,-175279,-174870,-174463,-174057,-173653,-173251, + -172850,-172451,-172053,-171657,-171263,-170870,-170479,-170089, + -169701,-169315,-168930,-168546,-168164,-167784,-167405,-167027, + -166651,-166277,-165904,-165532,-165162,-164793,-164426,-164060, + -163695,-163332,-162970,-162610,-162251,-161893,-161537,-161182, + -160828,-160476,-160125,-159775,-159427,-159079,-158734,-158389, + -158046,-157704,-157363,-157024,-156686,-156349,-156013,-155678, + -155345,-155013,-154682,-154352,-154024,-153697,-153370,-153045, + -152722,-152399,-152077,-151757,-151438,-151120,-150803,-150487, + -150172,-149859,-149546,-149235,-148924,-148615,-148307,-148000, + -147693,-147388,-147084,-146782,-146480,-146179,-145879,-145580, + -145282,-144986,-144690,-144395,-144101,-143808,-143517,-143226, + -142936,-142647,-142359,-142072,-141786,-141501,-141217,-140934, + -140651,-140370,-140090,-139810,-139532,-139254,-138977,-138701, + -138426,-138152,-137879,-137607,-137335,-137065,-136795,-136526, + -136258,-135991,-135725,-135459,-135195,-134931,-134668,-134406, + -134145,-133884,-133625,-133366,-133108,-132851,-132594,-132339, + -132084,-131830,-131576,-131324,-131072,-130821,-130571,-130322, + -130073,-129825,-129578,-129332,-129086,-128841,-128597,-128353, + -128111,-127869,-127627,-127387,-127147,-126908,-126669,-126432, + -126195,-125959,-125723,-125488,-125254,-125020,-124787,-124555, + -124324,-124093,-123863,-123633,-123404,-123176,-122949,-122722, + -122496,-122270,-122045,-121821,-121597,-121374,-121152,-120930, + -120709,-120489,-120269,-120050,-119831,-119613,-119396,-119179, + -118963,-118747,-118532,-118318,-118104,-117891,-117678,-117466, + -117254,-117044,-116833,-116623,-116414,-116206,-115998,-115790, + -115583,-115377,-115171,-114966,-114761,-114557,-114354,-114151, + -113948,-113746,-113545,-113344,-113143,-112944,-112744,-112546, + -112347,-112150,-111952,-111756,-111560,-111364,-111169,-110974, + -110780,-110586,-110393,-110200,-110008,-109817,-109626,-109435, + -109245,-109055,-108866,-108677,-108489,-108301,-108114,-107927, + -107741,-107555,-107369,-107184,-107000,-106816,-106632,-106449, + -106266,-106084,-105902,-105721,-105540,-105360,-105180,-105000, + -104821,-104643,-104465,-104287,-104109,-103933,-103756,-103580, + -103404,-103229,-103054,-102880,-102706,-102533,-102360,-102187, + -102015,-101843,-101671,-101500,-101330,-101159,-100990,-100820, + -100651,-100482,-100314,-100146,-99979,-99812,-99645,-99479, + -99313,-99148,-98982,-98818,-98653,-98489,-98326,-98163, + -98000,-97837,-97675,-97513,-97352,-97191,-97030,-96870, + -96710,-96551,-96391,-96233,-96074,-95916,-95758,-95601, + -95444,-95287,-95131,-94975,-94819,-94664,-94509,-94354, + -94200,-94046,-93892,-93739,-93586,-93434,-93281,-93129, + -92978,-92826,-92675,-92525,-92375,-92225,-92075,-91926, + -91777,-91628,-91480,-91332,-91184,-91036,-90889,-90742, + -90596,-90450,-90304,-90158,-90013,-89868,-89724,-89579, + -89435,-89292,-89148,-89005,-88862,-88720,-88577,-88435, + -88294,-88152,-88011,-87871,-87730,-87590,-87450,-87310, + -87171,-87032,-86893,-86755,-86616,-86479,-86341,-86204, + -86066,-85930,-85793,-85657,-85521,-85385,-85250,-85114, + -84980,-84845,-84710,-84576,-84443,-84309,-84176,-84043, + -83910,-83777,-83645,-83513,-83381,-83250,-83118,-82987, + -82857,-82726,-82596,-82466,-82336,-82207,-82078,-81949, + -81820,-81691,-81563,-81435,-81307,-81180,-81053,-80925, + -80799,-80672,-80546,-80420,-80294,-80168,-80043,-79918, + -79793,-79668,-79544,-79420,-79296,-79172,-79048,-78925, + -78802,-78679,-78557,-78434,-78312,-78190,-78068,-77947, + -77826,-77705,-77584,-77463,-77343,-77223,-77103,-76983, + -76864,-76744,-76625,-76506,-76388,-76269,-76151,-76033, + -75915,-75797,-75680,-75563,-75446,-75329,-75213,-75096, + -74980,-74864,-74748,-74633,-74517,-74402,-74287,-74172, + -74058,-73944,-73829,-73715,-73602,-73488,-73375,-73262, + -73149,-73036,-72923,-72811,-72699,-72587,-72475,-72363, + -72252,-72140,-72029,-71918,-71808,-71697,-71587,-71477, + -71367,-71257,-71147,-71038,-70929,-70820,-70711,-70602, + -70494,-70385,-70277,-70169,-70061,-69954,-69846,-69739, + -69632,-69525,-69418,-69312,-69205,-69099,-68993,-68887, + -68781,-68676,-68570,-68465,-68360,-68255,-68151,-68046, + -67942,-67837,-67733,-67629,-67526,-67422,-67319,-67216, + -67113,-67010,-66907,-66804,-66702,-66600,-66498,-66396, + -66294,-66192,-66091,-65989,-65888,-65787,-65686,-65586, + -65485,-65385,-65285,-65185,-65085,-64985,-64885,-64786, + -64687,-64587,-64488,-64389,-64291,-64192,-64094,-63996, + -63897,-63799,-63702,-63604,-63506,-63409,-63312,-63215, + -63118,-63021,-62924,-62828,-62731,-62635,-62539,-62443, + -62347,-62251,-62156,-62060,-61965,-61870,-61775,-61680, + -61585,-61491,-61396,-61302,-61208,-61114,-61020,-60926, + -60833,-60739,-60646,-60552,-60459,-60366,-60273,-60181, + -60088,-59996,-59903,-59811,-59719,-59627,-59535,-59444, + -59352,-59261,-59169,-59078,-58987,-58896,-58805,-58715, + -58624,-58534,-58443,-58353,-58263,-58173,-58083,-57994, + -57904,-57815,-57725,-57636,-57547,-57458,-57369,-57281, + -57192,-57104,-57015,-56927,-56839,-56751,-56663,-56575, + -56487,-56400,-56312,-56225,-56138,-56051,-55964,-55877, + -55790,-55704,-55617,-55531,-55444,-55358,-55272,-55186, + -55100,-55015,-54929,-54843,-54758,-54673,-54587,-54502, + -54417,-54333,-54248,-54163,-54079,-53994,-53910,-53826, + -53741,-53657,-53574,-53490,-53406,-53322,-53239,-53156, + -53072,-52989,-52906,-52823,-52740,-52657,-52575,-52492, + -52410,-52327,-52245,-52163,-52081,-51999,-51917,-51835, + -51754,-51672,-51591,-51509,-51428,-51347,-51266,-51185, + -51104,-51023,-50942,-50862,-50781,-50701,-50621,-50540, + -50460,-50380,-50300,-50221,-50141,-50061,-49982,-49902, + -49823,-49744,-49664,-49585,-49506,-49427,-49349,-49270, + -49191,-49113,-49034,-48956,-48878,-48799,-48721,-48643, + -48565,-48488,-48410,-48332,-48255,-48177,-48100,-48022, + -47945,-47868,-47791,-47714,-47637,-47560,-47484,-47407, + -47331,-47254,-47178,-47102,-47025,-46949,-46873,-46797, + -46721,-46646,-46570,-46494,-46419,-46343,-46268,-46193, + -46118,-46042,-45967,-45892,-45818,-45743,-45668,-45593, + -45519,-45444,-45370,-45296,-45221,-45147,-45073,-44999, + -44925,-44851,-44778,-44704,-44630,-44557,-44483,-44410, + -44337,-44263,-44190,-44117,-44044,-43971,-43898,-43826, + -43753,-43680,-43608,-43535,-43463,-43390,-43318,-43246, + -43174,-43102,-43030,-42958,-42886,-42814,-42743,-42671, + -42600,-42528,-42457,-42385,-42314,-42243,-42172,-42101, + -42030,-41959,-41888,-41817,-41747,-41676,-41605,-41535, + -41465,-41394,-41324,-41254,-41184,-41113,-41043,-40973, + -40904,-40834,-40764,-40694,-40625,-40555,-40486,-40416, + -40347,-40278,-40208,-40139,-40070,-40001,-39932,-39863, + -39794,-39726,-39657,-39588,-39520,-39451,-39383,-39314, + -39246,-39178,-39110,-39042,-38973,-38905,-38837,-38770, + -38702,-38634,-38566,-38499,-38431,-38364,-38296,-38229, + -38161,-38094,-38027,-37960,-37893,-37826,-37759,-37692, + -37625,-37558,-37491,-37425,-37358,-37291,-37225,-37158, + -37092,-37026,-36959,-36893,-36827,-36761,-36695,-36629, + -36563,-36497,-36431,-36365,-36300,-36234,-36168,-36103, + -36037,-35972,-35907,-35841,-35776,-35711,-35646,-35580, + -35515,-35450,-35385,-35321,-35256,-35191,-35126,-35062, + -34997,-34932,-34868,-34803,-34739,-34675,-34610,-34546, + -34482,-34418,-34354,-34289,-34225,-34162,-34098,-34034, + -33970,-33906,-33843,-33779,-33715,-33652,-33588,-33525, + -33461,-33398,-33335,-33272,-33208,-33145,-33082,-33019, + -32956,-32893,-32830,-32767,-32705,-32642,-32579,-32516, + -32454,-32391,-32329,-32266,-32204,-32141,-32079,-32017, + -31955,-31892,-31830,-31768,-31706,-31644,-31582,-31520, + -31458,-31396,-31335,-31273,-31211,-31150,-31088,-31026, + -30965,-30904,-30842,-30781,-30719,-30658,-30597,-30536, + -30474,-30413,-30352,-30291,-30230,-30169,-30108,-30048, + -29987,-29926,-29865,-29805,-29744,-29683,-29623,-29562, + -29502,-29441,-29381,-29321,-29260,-29200,-29140,-29080, + -29020,-28959,-28899,-28839,-28779,-28719,-28660,-28600, + -28540,-28480,-28420,-28361,-28301,-28241,-28182,-28122, + -28063,-28003,-27944,-27884,-27825,-27766,-27707,-27647, + -27588,-27529,-27470,-27411,-27352,-27293,-27234,-27175, + -27116,-27057,-26998,-26940,-26881,-26822,-26763,-26705, + -26646,-26588,-26529,-26471,-26412,-26354,-26295,-26237, + -26179,-26120,-26062,-26004,-25946,-25888,-25830,-25772, + -25714,-25656,-25598,-25540,-25482,-25424,-25366,-25308, + -25251,-25193,-25135,-25078,-25020,-24962,-24905,-24847, + -24790,-24732,-24675,-24618,-24560,-24503,-24446,-24389, + -24331,-24274,-24217,-24160,-24103,-24046,-23989,-23932, + -23875,-23818,-23761,-23704,-23647,-23591,-23534,-23477, + -23420,-23364,-23307,-23250,-23194,-23137,-23081,-23024, + -22968,-22911,-22855,-22799,-22742,-22686,-22630,-22573, + -22517,-22461,-22405,-22349,-22293,-22237,-22181,-22125, + -22069,-22013,-21957,-21901,-21845,-21789,-21733,-21678, + -21622,-21566,-21510,-21455,-21399,-21343,-21288,-21232, + -21177,-21121,-21066,-21010,-20955,-20900,-20844,-20789, + -20734,-20678,-20623,-20568,-20513,-20457,-20402,-20347, + -20292,-20237,-20182,-20127,-20072,-20017,-19962,-19907, + -19852,-19797,-19742,-19688,-19633,-19578,-19523,-19469, + -19414,-19359,-19305,-19250,-19195,-19141,-19086,-19032, + -18977,-18923,-18868,-18814,-18760,-18705,-18651,-18597, + -18542,-18488,-18434,-18380,-18325,-18271,-18217,-18163, + -18109,-18055,-18001,-17946,-17892,-17838,-17784,-17731, + -17677,-17623,-17569,-17515,-17461,-17407,-17353,-17300, + -17246,-17192,-17138,-17085,-17031,-16977,-16924,-16870, + -16817,-16763,-16710,-16656,-16603,-16549,-16496,-16442, + -16389,-16335,-16282,-16229,-16175,-16122,-16069,-16015, + -15962,-15909,-15856,-15802,-15749,-15696,-15643,-15590, + -15537,-15484,-15431,-15378,-15325,-15272,-15219,-15166, + -15113,-15060,-15007,-14954,-14901,-14848,-14795,-14743, + -14690,-14637,-14584,-14531,-14479,-14426,-14373,-14321, + -14268,-14215,-14163,-14110,-14057,-14005,-13952,-13900, + -13847,-13795,-13742,-13690,-13637,-13585,-13533,-13480, + -13428,-13375,-13323,-13271,-13218,-13166,-13114,-13062, + -13009,-12957,-12905,-12853,-12800,-12748,-12696,-12644, + -12592,-12540,-12488,-12436,-12383,-12331,-12279,-12227, + -12175,-12123,-12071,-12019,-11967,-11916,-11864,-11812, + -11760,-11708,-11656,-11604,-11552,-11501,-11449,-11397, + -11345,-11293,-11242,-11190,-11138,-11086,-11035,-10983, + -10931,-10880,-10828,-10777,-10725,-10673,-10622,-10570, + -10519,-10467,-10415,-10364,-10312,-10261,-10209,-10158, + -10106,-10055,-10004,-9952,-9901,-9849,-9798,-9747, + -9695,-9644,-9592,-9541,-9490,-9438,-9387,-9336, + -9285,-9233,-9182,-9131,-9080,-9028,-8977,-8926, + -8875,-8824,-8772,-8721,-8670,-8619,-8568,-8517, + -8466,-8414,-8363,-8312,-8261,-8210,-8159,-8108, + -8057,-8006,-7955,-7904,-7853,-7802,-7751,-7700, + -7649,-7598,-7547,-7496,-7445,-7395,-7344,-7293, + -7242,-7191,-7140,-7089,-7038,-6988,-6937,-6886, + -6835,-6784,-6733,-6683,-6632,-6581,-6530,-6480, + -6429,-6378,-6327,-6277,-6226,-6175,-6124,-6074, + -6023,-5972,-5922,-5871,-5820,-5770,-5719,-5668, + -5618,-5567,-5517,-5466,-5415,-5365,-5314,-5264, + -5213,-5162,-5112,-5061,-5011,-4960,-4910,-4859, + -4808,-4758,-4707,-4657,-4606,-4556,-4505,-4455, + -4404,-4354,-4303,-4253,-4202,-4152,-4101,-4051, + -4001,-3950,-3900,-3849,-3799,-3748,-3698,-3648, + -3597,-3547,-3496,-3446,-3395,-3345,-3295,-3244, + -3194,-3144,-3093,-3043,-2992,-2942,-2892,-2841, + -2791,-2741,-2690,-2640,-2590,-2539,-2489,-2439, + -2388,-2338,-2288,-2237,-2187,-2137,-2086,-2036, + -1986,-1935,-1885,-1835,-1784,-1734,-1684,-1633, + -1583,-1533,-1483,-1432,-1382,-1332,-1281,-1231, + -1181,-1131,-1080,-1030,-980,-929,-879,-829, + -779,-728,-678,-628,-578,-527,-477,-427, + -376,-326,-276,-226,-175,-125,-75,-25, + 25,75,125,175,226,276,326,376, + 427,477,527,578,628,678,728,779, + 829,879,929,980,1030,1080,1131,1181, + 1231,1281,1332,1382,1432,1483,1533,1583, + 1633,1684,1734,1784,1835,1885,1935,1986, + 2036,2086,2137,2187,2237,2288,2338,2388, + 2439,2489,2539,2590,2640,2690,2741,2791, + 2841,2892,2942,2992,3043,3093,3144,3194, + 3244,3295,3345,3395,3446,3496,3547,3597, + 3648,3698,3748,3799,3849,3900,3950,4001, + 4051,4101,4152,4202,4253,4303,4354,4404, + 4455,4505,4556,4606,4657,4707,4758,4808, + 4859,4910,4960,5011,5061,5112,5162,5213, + 5264,5314,5365,5415,5466,5517,5567,5618, + 5668,5719,5770,5820,5871,5922,5972,6023, + 6074,6124,6175,6226,6277,6327,6378,6429, + 6480,6530,6581,6632,6683,6733,6784,6835, + 6886,6937,6988,7038,7089,7140,7191,7242, + 7293,7344,7395,7445,7496,7547,7598,7649, + 7700,7751,7802,7853,7904,7955,8006,8057, + 8108,8159,8210,8261,8312,8363,8414,8466, + 8517,8568,8619,8670,8721,8772,8824,8875, + 8926,8977,9028,9080,9131,9182,9233,9285, + 9336,9387,9438,9490,9541,9592,9644,9695, + 9747,9798,9849,9901,9952,10004,10055,10106, + 10158,10209,10261,10312,10364,10415,10467,10519, + 10570,10622,10673,10725,10777,10828,10880,10931, + 10983,11035,11086,11138,11190,11242,11293,11345, + 11397,11449,11501,11552,11604,11656,11708,11760, + 11812,11864,11916,11967,12019,12071,12123,12175, + 12227,12279,12331,12383,12436,12488,12540,12592, + 12644,12696,12748,12800,12853,12905,12957,13009, + 13062,13114,13166,13218,13271,13323,13375,13428, + 13480,13533,13585,13637,13690,13742,13795,13847, + 13900,13952,14005,14057,14110,14163,14215,14268, + 14321,14373,14426,14479,14531,14584,14637,14690, + 14743,14795,14848,14901,14954,15007,15060,15113, + 15166,15219,15272,15325,15378,15431,15484,15537, + 15590,15643,15696,15749,15802,15856,15909,15962, + 16015,16069,16122,16175,16229,16282,16335,16389, + 16442,16496,16549,16603,16656,16710,16763,16817, + 16870,16924,16977,17031,17085,17138,17192,17246, + 17300,17353,17407,17461,17515,17569,17623,17677, + 17731,17784,17838,17892,17946,18001,18055,18109, + 18163,18217,18271,18325,18380,18434,18488,18542, + 18597,18651,18705,18760,18814,18868,18923,18977, + 19032,19086,19141,19195,19250,19305,19359,19414, + 19469,19523,19578,19633,19688,19742,19797,19852, + 19907,19962,20017,20072,20127,20182,20237,20292, + 20347,20402,20457,20513,20568,20623,20678,20734, + 20789,20844,20900,20955,21010,21066,21121,21177, + 21232,21288,21343,21399,21455,21510,21566,21622, + 21678,21733,21789,21845,21901,21957,22013,22069, + 22125,22181,22237,22293,22349,22405,22461,22517, + 22573,22630,22686,22742,22799,22855,22911,22968, + 23024,23081,23137,23194,23250,23307,23364,23420, + 23477,23534,23591,23647,23704,23761,23818,23875, + 23932,23989,24046,24103,24160,24217,24274,24331, + 24389,24446,24503,24560,24618,24675,24732,24790, + 24847,24905,24962,25020,25078,25135,25193,25251, + 25308,25366,25424,25482,25540,25598,25656,25714, + 25772,25830,25888,25946,26004,26062,26120,26179, + 26237,26295,26354,26412,26471,26529,26588,26646, + 26705,26763,26822,26881,26940,26998,27057,27116, + 27175,27234,27293,27352,27411,27470,27529,27588, + 27647,27707,27766,27825,27884,27944,28003,28063, + 28122,28182,28241,28301,28361,28420,28480,28540, + 28600,28660,28719,28779,28839,28899,28959,29020, + 29080,29140,29200,29260,29321,29381,29441,29502, + 29562,29623,29683,29744,29805,29865,29926,29987, + 30048,30108,30169,30230,30291,30352,30413,30474, + 30536,30597,30658,30719,30781,30842,30904,30965, + 31026,31088,31150,31211,31273,31335,31396,31458, + 31520,31582,31644,31706,31768,31830,31892,31955, + 32017,32079,32141,32204,32266,32329,32391,32454, + 32516,32579,32642,32705,32767,32830,32893,32956, + 33019,33082,33145,33208,33272,33335,33398,33461, + 33525,33588,33652,33715,33779,33843,33906,33970, + 34034,34098,34162,34225,34289,34354,34418,34482, + 34546,34610,34675,34739,34803,34868,34932,34997, + 35062,35126,35191,35256,35321,35385,35450,35515, + 35580,35646,35711,35776,35841,35907,35972,36037, + 36103,36168,36234,36300,36365,36431,36497,36563, + 36629,36695,36761,36827,36893,36959,37026,37092, + 37158,37225,37291,37358,37425,37491,37558,37625, + 37692,37759,37826,37893,37960,38027,38094,38161, + 38229,38296,38364,38431,38499,38566,38634,38702, + 38770,38837,38905,38973,39042,39110,39178,39246, + 39314,39383,39451,39520,39588,39657,39726,39794, + 39863,39932,40001,40070,40139,40208,40278,40347, + 40416,40486,40555,40625,40694,40764,40834,40904, + 40973,41043,41113,41184,41254,41324,41394,41465, + 41535,41605,41676,41747,41817,41888,41959,42030, + 42101,42172,42243,42314,42385,42457,42528,42600, + 42671,42743,42814,42886,42958,43030,43102,43174, + 43246,43318,43390,43463,43535,43608,43680,43753, + 43826,43898,43971,44044,44117,44190,44263,44337, + 44410,44483,44557,44630,44704,44778,44851,44925, + 44999,45073,45147,45221,45296,45370,45444,45519, + 45593,45668,45743,45818,45892,45967,46042,46118, + 46193,46268,46343,46419,46494,46570,46646,46721, + 46797,46873,46949,47025,47102,47178,47254,47331, + 47407,47484,47560,47637,47714,47791,47868,47945, + 48022,48100,48177,48255,48332,48410,48488,48565, + 48643,48721,48799,48878,48956,49034,49113,49191, + 49270,49349,49427,49506,49585,49664,49744,49823, + 49902,49982,50061,50141,50221,50300,50380,50460, + 50540,50621,50701,50781,50862,50942,51023,51104, + 51185,51266,51347,51428,51509,51591,51672,51754, + 51835,51917,51999,52081,52163,52245,52327,52410, + 52492,52575,52657,52740,52823,52906,52989,53072, + 53156,53239,53322,53406,53490,53574,53657,53741, + 53826,53910,53994,54079,54163,54248,54333,54417, + 54502,54587,54673,54758,54843,54929,55015,55100, + 55186,55272,55358,55444,55531,55617,55704,55790, + 55877,55964,56051,56138,56225,56312,56400,56487, + 56575,56663,56751,56839,56927,57015,57104,57192, + 57281,57369,57458,57547,57636,57725,57815,57904, + 57994,58083,58173,58263,58353,58443,58534,58624, + 58715,58805,58896,58987,59078,59169,59261,59352, + 59444,59535,59627,59719,59811,59903,59996,60088, + 60181,60273,60366,60459,60552,60646,60739,60833, + 60926,61020,61114,61208,61302,61396,61491,61585, + 61680,61775,61870,61965,62060,62156,62251,62347, + 62443,62539,62635,62731,62828,62924,63021,63118, + 63215,63312,63409,63506,63604,63702,63799,63897, + 63996,64094,64192,64291,64389,64488,64587,64687, + 64786,64885,64985,65085,65185,65285,65385,65485, + 65586,65686,65787,65888,65989,66091,66192,66294, + 66396,66498,66600,66702,66804,66907,67010,67113, + 67216,67319,67422,67526,67629,67733,67837,67942, + 68046,68151,68255,68360,68465,68570,68676,68781, + 68887,68993,69099,69205,69312,69418,69525,69632, + 69739,69846,69954,70061,70169,70277,70385,70494, + 70602,70711,70820,70929,71038,71147,71257,71367, + 71477,71587,71697,71808,71918,72029,72140,72252, + 72363,72475,72587,72699,72811,72923,73036,73149, + 73262,73375,73488,73602,73715,73829,73944,74058, + 74172,74287,74402,74517,74633,74748,74864,74980, + 75096,75213,75329,75446,75563,75680,75797,75915, + 76033,76151,76269,76388,76506,76625,76744,76864, + 76983,77103,77223,77343,77463,77584,77705,77826, + 77947,78068,78190,78312,78434,78557,78679,78802, + 78925,79048,79172,79296,79420,79544,79668,79793, + 79918,80043,80168,80294,80420,80546,80672,80799, + 80925,81053,81180,81307,81435,81563,81691,81820, + 81949,82078,82207,82336,82466,82596,82726,82857, + 82987,83118,83250,83381,83513,83645,83777,83910, + 84043,84176,84309,84443,84576,84710,84845,84980, + 85114,85250,85385,85521,85657,85793,85930,86066, + 86204,86341,86479,86616,86755,86893,87032,87171, + 87310,87450,87590,87730,87871,88011,88152,88294, + 88435,88577,88720,88862,89005,89148,89292,89435, + 89579,89724,89868,90013,90158,90304,90450,90596, + 90742,90889,91036,91184,91332,91480,91628,91777, + 91926,92075,92225,92375,92525,92675,92826,92978, + 93129,93281,93434,93586,93739,93892,94046,94200, + 94354,94509,94664,94819,94975,95131,95287,95444, + 95601,95758,95916,96074,96233,96391,96551,96710, + 96870,97030,97191,97352,97513,97675,97837,98000, + 98163,98326,98489,98653,98818,98982,99148,99313, + 99479,99645,99812,99979,100146,100314,100482,100651, + 100820,100990,101159,101330,101500,101671,101843,102015, + 102187,102360,102533,102706,102880,103054,103229,103404, + 103580,103756,103933,104109,104287,104465,104643,104821, + 105000,105180,105360,105540,105721,105902,106084,106266, + 106449,106632,106816,107000,107184,107369,107555,107741, + 107927,108114,108301,108489,108677,108866,109055,109245, + 109435,109626,109817,110008,110200,110393,110586,110780, + 110974,111169,111364,111560,111756,111952,112150,112347, + 112546,112744,112944,113143,113344,113545,113746,113948, + 114151,114354,114557,114761,114966,115171,115377,115583, + 115790,115998,116206,116414,116623,116833,117044,117254, + 117466,117678,117891,118104,118318,118532,118747,118963, + 119179,119396,119613,119831,120050,120269,120489,120709, + 120930,121152,121374,121597,121821,122045,122270,122496, + 122722,122949,123176,123404,123633,123863,124093,124324, + 124555,124787,125020,125254,125488,125723,125959,126195, + 126432,126669,126908,127147,127387,127627,127869,128111, + 128353,128597,128841,129086,129332,129578,129825,130073, + 130322,130571,130821,131072,131324,131576,131830,132084, + 132339,132594,132851,133108,133366,133625,133884,134145, + 134406,134668,134931,135195,135459,135725,135991,136258, + 136526,136795,137065,137335,137607,137879,138152,138426, + 138701,138977,139254,139532,139810,140090,140370,140651, + 140934,141217,141501,141786,142072,142359,142647,142936, + 143226,143517,143808,144101,144395,144690,144986,145282, + 145580,145879,146179,146480,146782,147084,147388,147693, + 148000,148307,148615,148924,149235,149546,149859,150172, + 150487,150803,151120,151438,151757,152077,152399,152722, + 153045,153370,153697,154024,154352,154682,155013,155345, + 155678,156013,156349,156686,157024,157363,157704,158046, + 158389,158734,159079,159427,159775,160125,160476,160828, + 161182,161537,161893,162251,162610,162970,163332,163695, + 164060,164426,164793,165162,165532,165904,166277,166651, + 167027,167405,167784,168164,168546,168930,169315,169701, + 170089,170479,170870,171263,171657,172053,172451,172850, + 173251,173653,174057,174463,174870,175279,175690,176102, + 176516,176932,177349,177769,178190,178612,179037,179463, + 179891,180321,180753,181186,181622,182059,182498,182939, + 183382,183827,184274,184722,185173,185625,186080,186536, + 186995,187455,187918,188382,188849,189318,189789,190261, + 190736,191213,191693,192174,192658,193143,193631,194122, + 194614,195109,195606,196105,196606,197110,197616,198125, + 198636,199149,199664,200182,200703,201226,201751,202279, + 202809,203342,203878,204416,204956,205500,206045,206594, + 207145,207699,208255,208815,209376,209941,210509,211079, + 211652,212228,212807,213389,213973,214561,215151,215745, + 216341,216941,217544,218149,218758,219370,219985,220603, + 221225,221849,222477,223108,223743,224381,225022,225666, + 226314,226966,227621,228279,228941,229606,230275,230948, + 231624,232304,232988,233676,234367,235062,235761,236463, + 237170,237881,238595,239314,240036,240763,241493,242228, + 242967,243711,244458,245210,245966,246727,247492,248261, + 249035,249813,250596,251384,252176,252973,253774,254581, + 255392,256208,257029,257855,258686,259522,260363,261209, + 262060,262917,263779,264646,265519,266397,267280,268169, + 269064,269965,270871,271782,272700,273624,274553,275489, + 276430,277378,278332,279292,280258,281231,282210,283195, + 284188,285186,286192,287204,288223,289249,290282,291322, + 292369,293423,294485,295554,296630,297714,298805,299904, + 301011,302126,303248,304379,305517,306664,307819,308983, + 310154,311335,312524,313721,314928,316143,317368,318601, + 319844,321097,322358,323629,324910,326201,327502,328812, + 330133,331464,332805,334157,335519,336892,338276,339671, + 341078,342495,343924,345364,346816,348280,349756,351244, + 352744,354257,355783,357321,358872,360436,362013,363604, + 365208,366826,368459,370105,371765,373440,375130,376835, + 378555,380290,382040,383807,385589,387387,389202,391034, + 392882,394747,396630,398530,400448,402384,404338,406311, + 408303,410314,412344,414395,416465,418555,420666,422798, + 424951,427125,429321,431540,433781,436045,438332,440643, + 442978,445337,447720,450129,452564,455024,457511,460024, + 462565,465133,467730,470355,473009,475692,478406,481150, + 483925,486732,489571,492443,495348,498287,501261,504269, + 507313,510394,513512,516667,519861,523094,526366,529680, + 533034,536431,539870,543354,546881,550455,554074,557741, + 561456,565221,569035,572901,576818,580789,584815,588896, + 593033,597229,601483,605798,610174,614613,619117,623686, + 628323,633028,637803,642651,647572,652568,657640,662792, + 668024,673338,678737,684223,689797,695462,701219,707072, + 713023,719074,725227,731486,737853,744331,750922,757631, + 764460,771411,778490,785699,793041,800521,808143,815910, + 823827,831898,840127,848520,857081,865817,874730,883829, + 893117,902602,912289,922186,932298,942633,953199,964003, + 975054,986361,997931,1009774,1021901,1034322,1047046,1060087, + 1073455,1087164,1101225,1115654,1130465,1145673,1161294,1177345, + 1193846,1210813,1228269,1246234,1264730,1283783,1303416,1323658, + 1344537,1366084,1388330,1411312,1435065,1459630,1485049,1511367, + 1538632,1566898,1596220,1626658,1658278,1691149,1725348,1760956, + 1798063,1836758,1877161,1919378,1963536,2009771,2058233,2109087, + 2162516,2218719,2277919,2340362,2406322,2476104,2550052,2628549, + 2712030,2800983,2895966,2997613,3106651,3223918,3350381,3487165, + 3635590,3797206,3973855,4167737,4381502,4618375,4882318,5178251, + 5512368,5892567,6329090,6835455,7429880,8137527,8994149,10052327, + 11392683,13145455,15535599,18988036,24413316,34178904,56965752,170910304 +}; + + +const int finesine[10240] = +{ + 25,75,125,175,226,276,326,376, + 427,477,527,578,628,678,728,779, + 829,879,929,980,1030,1080,1130,1181, + 1231,1281,1331,1382,1432,1482,1532,1583, + 1633,1683,1733,1784,1834,1884,1934,1985, + 2035,2085,2135,2186,2236,2286,2336,2387, + 2437,2487,2537,2587,2638,2688,2738,2788, + 2839,2889,2939,2989,3039,3090,3140,3190, + 3240,3291,3341,3391,3441,3491,3541,3592, + 3642,3692,3742,3792,3843,3893,3943,3993, + 4043,4093,4144,4194,4244,4294,4344,4394, + 4445,4495,4545,4595,4645,4695,4745,4796, + 4846,4896,4946,4996,5046,5096,5146,5197, + 5247,5297,5347,5397,5447,5497,5547,5597, + 5647,5697,5748,5798,5848,5898,5948,5998, + 6048,6098,6148,6198,6248,6298,6348,6398, + 6448,6498,6548,6598,6648,6698,6748,6798, + 6848,6898,6948,6998,7048,7098,7148,7198, + 7248,7298,7348,7398,7448,7498,7548,7598, + 7648,7697,7747,7797,7847,7897,7947,7997, + 8047,8097,8147,8196,8246,8296,8346,8396, + 8446,8496,8545,8595,8645,8695,8745,8794, + 8844,8894,8944,8994,9043,9093,9143,9193, + 9243,9292,9342,9392,9442,9491,9541,9591, + 9640,9690,9740,9790,9839,9889,9939,9988, + 10038,10088,10137,10187,10237,10286,10336,10386, + 10435,10485,10534,10584,10634,10683,10733,10782, + 10832,10882,10931,10981,11030,11080,11129,11179, + 11228,11278,11327,11377,11426,11476,11525,11575, + 11624,11674,11723,11773,11822,11872,11921,11970, + 12020,12069,12119,12168,12218,12267,12316,12366, + 12415,12464,12514,12563,12612,12662,12711,12760, + 12810,12859,12908,12957,13007,13056,13105,13154, + 13204,13253,13302,13351,13401,13450,13499,13548, + 13597,13647,13696,13745,13794,13843,13892,13941, + 13990,14040,14089,14138,14187,14236,14285,14334, + 14383,14432,14481,14530,14579,14628,14677,14726, + 14775,14824,14873,14922,14971,15020,15069,15118, + 15167,15215,15264,15313,15362,15411,15460,15509, + 15557,15606,15655,15704,15753,15802,15850,15899, + 15948,15997,16045,16094,16143,16191,16240,16289, + 16338,16386,16435,16484,16532,16581,16629,16678, + 16727,16775,16824,16872,16921,16970,17018,17067, + 17115,17164,17212,17261,17309,17358,17406,17455, + 17503,17551,17600,17648,17697,17745,17793,17842, + 17890,17939,17987,18035,18084,18132,18180,18228, + 18277,18325,18373,18421,18470,18518,18566,18614, + 18663,18711,18759,18807,18855,18903,18951,19000, + 19048,19096,19144,19192,19240,19288,19336,19384, + 19432,19480,19528,19576,19624,19672,19720,19768, + 19816,19864,19912,19959,20007,20055,20103,20151, + 20199,20246,20294,20342,20390,20438,20485,20533, + 20581,20629,20676,20724,20772,20819,20867,20915, + 20962,21010,21057,21105,21153,21200,21248,21295, + 21343,21390,21438,21485,21533,21580,21628,21675, + 21723,21770,21817,21865,21912,21960,22007,22054, + 22102,22149,22196,22243,22291,22338,22385,22433, + 22480,22527,22574,22621,22668,22716,22763,22810, + 22857,22904,22951,22998,23045,23092,23139,23186, + 23233,23280,23327,23374,23421,23468,23515,23562, + 23609,23656,23703,23750,23796,23843,23890,23937, + 23984,24030,24077,24124,24171,24217,24264,24311, + 24357,24404,24451,24497,24544,24591,24637,24684, + 24730,24777,24823,24870,24916,24963,25009,25056, + 25102,25149,25195,25241,25288,25334,25381,25427, + 25473,25520,25566,25612,25658,25705,25751,25797, + 25843,25889,25936,25982,26028,26074,26120,26166, + 26212,26258,26304,26350,26396,26442,26488,26534, + 26580,26626,26672,26718,26764,26810,26856,26902, + 26947,26993,27039,27085,27131,27176,27222,27268, + 27313,27359,27405,27450,27496,27542,27587,27633, + 27678,27724,27770,27815,27861,27906,27952,27997, + 28042,28088,28133,28179,28224,28269,28315,28360, + 28405,28451,28496,28541,28586,28632,28677,28722, + 28767,28812,28858,28903,28948,28993,29038,29083, + 29128,29173,29218,29263,29308,29353,29398,29443, + 29488,29533,29577,29622,29667,29712,29757,29801, + 29846,29891,29936,29980,30025,30070,30114,30159, + 30204,30248,30293,30337,30382,30426,30471,30515, + 30560,30604,30649,30693,30738,30782,30826,30871, + 30915,30959,31004,31048,31092,31136,31181,31225, + 31269,31313,31357,31402,31446,31490,31534,31578, + 31622,31666,31710,31754,31798,31842,31886,31930, + 31974,32017,32061,32105,32149,32193,32236,32280, + 32324,32368,32411,32455,32499,32542,32586,32630, + 32673,32717,32760,32804,32847,32891,32934,32978, + 33021,33065,33108,33151,33195,33238,33281,33325, + 33368,33411,33454,33498,33541,33584,33627,33670, + 33713,33756,33799,33843,33886,33929,33972,34015, + 34057,34100,34143,34186,34229,34272,34315,34358, + 34400,34443,34486,34529,34571,34614,34657,34699, + 34742,34785,34827,34870,34912,34955,34997,35040, + 35082,35125,35167,35210,35252,35294,35337,35379, + 35421,35464,35506,35548,35590,35633,35675,35717, + 35759,35801,35843,35885,35927,35969,36011,36053, + 36095,36137,36179,36221,36263,36305,36347,36388, + 36430,36472,36514,36555,36597,36639,36681,36722, + 36764,36805,36847,36889,36930,36972,37013,37055, + 37096,37137,37179,37220,37262,37303,37344,37386, + 37427,37468,37509,37551,37592,37633,37674,37715, + 37756,37797,37838,37879,37920,37961,38002,38043, + 38084,38125,38166,38207,38248,38288,38329,38370, + 38411,38451,38492,38533,38573,38614,38655,38695, + 38736,38776,38817,38857,38898,38938,38979,39019, + 39059,39100,39140,39180,39221,39261,39301,39341, + 39382,39422,39462,39502,39542,39582,39622,39662, + 39702,39742,39782,39822,39862,39902,39942,39982, + 40021,40061,40101,40141,40180,40220,40260,40300, + 40339,40379,40418,40458,40497,40537,40576,40616, + 40655,40695,40734,40773,40813,40852,40891,40931, + 40970,41009,41048,41087,41127,41166,41205,41244, + 41283,41322,41361,41400,41439,41478,41517,41556, + 41595,41633,41672,41711,41750,41788,41827,41866, + 41904,41943,41982,42020,42059,42097,42136,42174, + 42213,42251,42290,42328,42366,42405,42443,42481, + 42520,42558,42596,42634,42672,42711,42749,42787, + 42825,42863,42901,42939,42977,43015,43053,43091, + 43128,43166,43204,43242,43280,43317,43355,43393, + 43430,43468,43506,43543,43581,43618,43656,43693, + 43731,43768,43806,43843,43880,43918,43955,43992, + 44029,44067,44104,44141,44178,44215,44252,44289, + 44326,44363,44400,44437,44474,44511,44548,44585, + 44622,44659,44695,44732,44769,44806,44842,44879, + 44915,44952,44989,45025,45062,45098,45135,45171, + 45207,45244,45280,45316,45353,45389,45425,45462, + 45498,45534,45570,45606,45642,45678,45714,45750, + 45786,45822,45858,45894,45930,45966,46002,46037, + 46073,46109,46145,46180,46216,46252,46287,46323, + 46358,46394,46429,46465,46500,46536,46571,46606, + 46642,46677,46712,46747,46783,46818,46853,46888, + 46923,46958,46993,47028,47063,47098,47133,47168, + 47203,47238,47273,47308,47342,47377,47412,47446, + 47481,47516,47550,47585,47619,47654,47688,47723, + 47757,47792,47826,47860,47895,47929,47963,47998, + 48032,48066,48100,48134,48168,48202,48237,48271, + 48305,48338,48372,48406,48440,48474,48508,48542, + 48575,48609,48643,48676,48710,48744,48777,48811, + 48844,48878,48911,48945,48978,49012,49045,49078, + 49112,49145,49178,49211,49244,49278,49311,49344, + 49377,49410,49443,49476,49509,49542,49575,49608, + 49640,49673,49706,49739,49771,49804,49837,49869, + 49902,49935,49967,50000,50032,50065,50097,50129, + 50162,50194,50226,50259,50291,50323,50355,50387, + 50420,50452,50484,50516,50548,50580,50612,50644, + 50675,50707,50739,50771,50803,50834,50866,50898, + 50929,50961,50993,51024,51056,51087,51119,51150, + 51182,51213,51244,51276,51307,51338,51369,51401, + 51432,51463,51494,51525,51556,51587,51618,51649, + 51680,51711,51742,51773,51803,51834,51865,51896, + 51926,51957,51988,52018,52049,52079,52110,52140, + 52171,52201,52231,52262,52292,52322,52353,52383, + 52413,52443,52473,52503,52534,52564,52594,52624, + 52653,52683,52713,52743,52773,52803,52832,52862, + 52892,52922,52951,52981,53010,53040,53069,53099, + 53128,53158,53187,53216,53246,53275,53304,53334, + 53363,53392,53421,53450,53479,53508,53537,53566, + 53595,53624,53653,53682,53711,53739,53768,53797, + 53826,53854,53883,53911,53940,53969,53997,54026, + 54054,54082,54111,54139,54167,54196,54224,54252, + 54280,54308,54337,54365,54393,54421,54449,54477, + 54505,54533,54560,54588,54616,54644,54672,54699, + 54727,54755,54782,54810,54837,54865,54892,54920, + 54947,54974,55002,55029,55056,55084,55111,55138, + 55165,55192,55219,55246,55274,55300,55327,55354, + 55381,55408,55435,55462,55489,55515,55542,55569, + 55595,55622,55648,55675,55701,55728,55754,55781, + 55807,55833,55860,55886,55912,55938,55965,55991, + 56017,56043,56069,56095,56121,56147,56173,56199, + 56225,56250,56276,56302,56328,56353,56379,56404, + 56430,56456,56481,56507,56532,56557,56583,56608, + 56633,56659,56684,56709,56734,56760,56785,56810, + 56835,56860,56885,56910,56935,56959,56984,57009, + 57034,57059,57083,57108,57133,57157,57182,57206, + 57231,57255,57280,57304,57329,57353,57377,57402, + 57426,57450,57474,57498,57522,57546,57570,57594, + 57618,57642,57666,57690,57714,57738,57762,57785, + 57809,57833,57856,57880,57903,57927,57950,57974, + 57997,58021,58044,58067,58091,58114,58137,58160, + 58183,58207,58230,58253,58276,58299,58322,58345, + 58367,58390,58413,58436,58459,58481,58504,58527, + 58549,58572,58594,58617,58639,58662,58684,58706, + 58729,58751,58773,58795,58818,58840,58862,58884, + 58906,58928,58950,58972,58994,59016,59038,59059, + 59081,59103,59125,59146,59168,59190,59211,59233, + 59254,59276,59297,59318,59340,59361,59382,59404, + 59425,59446,59467,59488,59509,59530,59551,59572, + 59593,59614,59635,59656,59677,59697,59718,59739, + 59759,59780,59801,59821,59842,59862,59883,59903, + 59923,59944,59964,59984,60004,60025,60045,60065, + 60085,60105,60125,60145,60165,60185,60205,60225, + 60244,60264,60284,60304,60323,60343,60363,60382, + 60402,60421,60441,60460,60479,60499,60518,60537, + 60556,60576,60595,60614,60633,60652,60671,60690, + 60709,60728,60747,60766,60785,60803,60822,60841, + 60859,60878,60897,60915,60934,60952,60971,60989, + 61007,61026,61044,61062,61081,61099,61117,61135, + 61153,61171,61189,61207,61225,61243,61261,61279, + 61297,61314,61332,61350,61367,61385,61403,61420, + 61438,61455,61473,61490,61507,61525,61542,61559, + 61577,61594,61611,61628,61645,61662,61679,61696, + 61713,61730,61747,61764,61780,61797,61814,61831, + 61847,61864,61880,61897,61913,61930,61946,61963, + 61979,61995,62012,62028,62044,62060,62076,62092, + 62108,62125,62141,62156,62172,62188,62204,62220, + 62236,62251,62267,62283,62298,62314,62329,62345, + 62360,62376,62391,62407,62422,62437,62453,62468, + 62483,62498,62513,62528,62543,62558,62573,62588, + 62603,62618,62633,62648,62662,62677,62692,62706, + 62721,62735,62750,62764,62779,62793,62808,62822, + 62836,62850,62865,62879,62893,62907,62921,62935, + 62949,62963,62977,62991,63005,63019,63032,63046, + 63060,63074,63087,63101,63114,63128,63141,63155, + 63168,63182,63195,63208,63221,63235,63248,63261, + 63274,63287,63300,63313,63326,63339,63352,63365, + 63378,63390,63403,63416,63429,63441,63454,63466, + 63479,63491,63504,63516,63528,63541,63553,63565, + 63578,63590,63602,63614,63626,63638,63650,63662, + 63674,63686,63698,63709,63721,63733,63745,63756, + 63768,63779,63791,63803,63814,63825,63837,63848, + 63859,63871,63882,63893,63904,63915,63927,63938, + 63949,63960,63971,63981,63992,64003,64014,64025, + 64035,64046,64057,64067,64078,64088,64099,64109, + 64120,64130,64140,64151,64161,64171,64181,64192, + 64202,64212,64222,64232,64242,64252,64261,64271, + 64281,64291,64301,64310,64320,64330,64339,64349, + 64358,64368,64377,64387,64396,64405,64414,64424, + 64433,64442,64451,64460,64469,64478,64487,64496, + 64505,64514,64523,64532,64540,64549,64558,64566, + 64575,64584,64592,64601,64609,64617,64626,64634, + 64642,64651,64659,64667,64675,64683,64691,64699, + 64707,64715,64723,64731,64739,64747,64754,64762, + 64770,64777,64785,64793,64800,64808,64815,64822, + 64830,64837,64844,64852,64859,64866,64873,64880, + 64887,64895,64902,64908,64915,64922,64929,64936, + 64943,64949,64956,64963,64969,64976,64982,64989, + 64995,65002,65008,65015,65021,65027,65033,65040, + 65046,65052,65058,65064,65070,65076,65082,65088, + 65094,65099,65105,65111,65117,65122,65128,65133, + 65139,65144,65150,65155,65161,65166,65171,65177, + 65182,65187,65192,65197,65202,65207,65212,65217, + 65222,65227,65232,65237,65242,65246,65251,65256, + 65260,65265,65270,65274,65279,65283,65287,65292, + 65296,65300,65305,65309,65313,65317,65321,65325, + 65329,65333,65337,65341,65345,65349,65352,65356, + 65360,65363,65367,65371,65374,65378,65381,65385, + 65388,65391,65395,65398,65401,65404,65408,65411, + 65414,65417,65420,65423,65426,65429,65431,65434, + 65437,65440,65442,65445,65448,65450,65453,65455, + 65458,65460,65463,65465,65467,65470,65472,65474, + 65476,65478,65480,65482,65484,65486,65488,65490, + 65492,65494,65496,65497,65499,65501,65502,65504, + 65505,65507,65508,65510,65511,65513,65514,65515, + 65516,65518,65519,65520,65521,65522,65523,65524, + 65525,65526,65527,65527,65528,65529,65530,65530, + 65531,65531,65532,65532,65533,65533,65534,65534, + 65534,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65534, + 65534,65534,65533,65533,65532,65532,65531,65531, + 65530,65530,65529,65528,65527,65527,65526,65525, + 65524,65523,65522,65521,65520,65519,65518,65516, + 65515,65514,65513,65511,65510,65508,65507,65505, + 65504,65502,65501,65499,65497,65496,65494,65492, + 65490,65488,65486,65484,65482,65480,65478,65476, + 65474,65472,65470,65467,65465,65463,65460,65458, + 65455,65453,65450,65448,65445,65442,65440,65437, + 65434,65431,65429,65426,65423,65420,65417,65414, + 65411,65408,65404,65401,65398,65395,65391,65388, + 65385,65381,65378,65374,65371,65367,65363,65360, + 65356,65352,65349,65345,65341,65337,65333,65329, + 65325,65321,65317,65313,65309,65305,65300,65296, + 65292,65287,65283,65279,65274,65270,65265,65260, + 65256,65251,65246,65242,65237,65232,65227,65222, + 65217,65212,65207,65202,65197,65192,65187,65182, + 65177,65171,65166,65161,65155,65150,65144,65139, + 65133,65128,65122,65117,65111,65105,65099,65094, + 65088,65082,65076,65070,65064,65058,65052,65046, + 65040,65033,65027,65021,65015,65008,65002,64995, + 64989,64982,64976,64969,64963,64956,64949,64943, + 64936,64929,64922,64915,64908,64902,64895,64887, + 64880,64873,64866,64859,64852,64844,64837,64830, + 64822,64815,64808,64800,64793,64785,64777,64770, + 64762,64754,64747,64739,64731,64723,64715,64707, + 64699,64691,64683,64675,64667,64659,64651,64642, + 64634,64626,64617,64609,64600,64592,64584,64575, + 64566,64558,64549,64540,64532,64523,64514,64505, + 64496,64487,64478,64469,64460,64451,64442,64433, + 64424,64414,64405,64396,64387,64377,64368,64358, + 64349,64339,64330,64320,64310,64301,64291,64281, + 64271,64261,64252,64242,64232,64222,64212,64202, + 64192,64181,64171,64161,64151,64140,64130,64120, + 64109,64099,64088,64078,64067,64057,64046,64035, + 64025,64014,64003,63992,63981,63971,63960,63949, + 63938,63927,63915,63904,63893,63882,63871,63859, + 63848,63837,63825,63814,63803,63791,63779,63768, + 63756,63745,63733,63721,63709,63698,63686,63674, + 63662,63650,63638,63626,63614,63602,63590,63578, + 63565,63553,63541,63528,63516,63504,63491,63479, + 63466,63454,63441,63429,63416,63403,63390,63378, + 63365,63352,63339,63326,63313,63300,63287,63274, + 63261,63248,63235,63221,63208,63195,63182,63168, + 63155,63141,63128,63114,63101,63087,63074,63060, + 63046,63032,63019,63005,62991,62977,62963,62949, + 62935,62921,62907,62893,62879,62865,62850,62836, + 62822,62808,62793,62779,62764,62750,62735,62721, + 62706,62692,62677,62662,62648,62633,62618,62603, + 62588,62573,62558,62543,62528,62513,62498,62483, + 62468,62453,62437,62422,62407,62391,62376,62360, + 62345,62329,62314,62298,62283,62267,62251,62236, + 62220,62204,62188,62172,62156,62141,62125,62108, + 62092,62076,62060,62044,62028,62012,61995,61979, + 61963,61946,61930,61913,61897,61880,61864,61847, + 61831,61814,61797,61780,61764,61747,61730,61713, + 61696,61679,61662,61645,61628,61611,61594,61577, + 61559,61542,61525,61507,61490,61473,61455,61438, + 61420,61403,61385,61367,61350,61332,61314,61297, + 61279,61261,61243,61225,61207,61189,61171,61153, + 61135,61117,61099,61081,61062,61044,61026,61007, + 60989,60971,60952,60934,60915,60897,60878,60859, + 60841,60822,60803,60785,60766,60747,60728,60709, + 60690,60671,60652,60633,60614,60595,60576,60556, + 60537,60518,60499,60479,60460,60441,60421,60402, + 60382,60363,60343,60323,60304,60284,60264,60244, + 60225,60205,60185,60165,60145,60125,60105,60085, + 60065,60045,60025,60004,59984,59964,59944,59923, + 59903,59883,59862,59842,59821,59801,59780,59759, + 59739,59718,59697,59677,59656,59635,59614,59593, + 59572,59551,59530,59509,59488,59467,59446,59425, + 59404,59382,59361,59340,59318,59297,59276,59254, + 59233,59211,59190,59168,59146,59125,59103,59081, + 59059,59038,59016,58994,58972,58950,58928,58906, + 58884,58862,58840,58818,58795,58773,58751,58729, + 58706,58684,58662,58639,58617,58594,58572,58549, + 58527,58504,58481,58459,58436,58413,58390,58367, + 58345,58322,58299,58276,58253,58230,58207,58183, + 58160,58137,58114,58091,58067,58044,58021,57997, + 57974,57950,57927,57903,57880,57856,57833,57809, + 57785,57762,57738,57714,57690,57666,57642,57618, + 57594,57570,57546,57522,57498,57474,57450,57426, + 57402,57377,57353,57329,57304,57280,57255,57231, + 57206,57182,57157,57133,57108,57083,57059,57034, + 57009,56984,56959,56935,56910,56885,56860,56835, + 56810,56785,56760,56734,56709,56684,56659,56633, + 56608,56583,56557,56532,56507,56481,56456,56430, + 56404,56379,56353,56328,56302,56276,56250,56225, + 56199,56173,56147,56121,56095,56069,56043,56017, + 55991,55965,55938,55912,55886,55860,55833,55807, + 55781,55754,55728,55701,55675,55648,55622,55595, + 55569,55542,55515,55489,55462,55435,55408,55381, + 55354,55327,55300,55274,55246,55219,55192,55165, + 55138,55111,55084,55056,55029,55002,54974,54947, + 54920,54892,54865,54837,54810,54782,54755,54727, + 54699,54672,54644,54616,54588,54560,54533,54505, + 54477,54449,54421,54393,54365,54337,54308,54280, + 54252,54224,54196,54167,54139,54111,54082,54054, + 54026,53997,53969,53940,53911,53883,53854,53826, + 53797,53768,53739,53711,53682,53653,53624,53595, + 53566,53537,53508,53479,53450,53421,53392,53363, + 53334,53304,53275,53246,53216,53187,53158,53128, + 53099,53069,53040,53010,52981,52951,52922,52892, + 52862,52832,52803,52773,52743,52713,52683,52653, + 52624,52594,52564,52534,52503,52473,52443,52413, + 52383,52353,52322,52292,52262,52231,52201,52171, + 52140,52110,52079,52049,52018,51988,51957,51926, + 51896,51865,51834,51803,51773,51742,51711,51680, + 51649,51618,51587,51556,51525,51494,51463,51432, + 51401,51369,51338,51307,51276,51244,51213,51182, + 51150,51119,51087,51056,51024,50993,50961,50929, + 50898,50866,50834,50803,50771,50739,50707,50675, + 50644,50612,50580,50548,50516,50484,50452,50420, + 50387,50355,50323,50291,50259,50226,50194,50162, + 50129,50097,50065,50032,50000,49967,49935,49902, + 49869,49837,49804,49771,49739,49706,49673,49640, + 49608,49575,49542,49509,49476,49443,49410,49377, + 49344,49311,49278,49244,49211,49178,49145,49112, + 49078,49045,49012,48978,48945,48911,48878,48844, + 48811,48777,48744,48710,48676,48643,48609,48575, + 48542,48508,48474,48440,48406,48372,48338,48304, + 48271,48237,48202,48168,48134,48100,48066,48032, + 47998,47963,47929,47895,47860,47826,47792,47757, + 47723,47688,47654,47619,47585,47550,47516,47481, + 47446,47412,47377,47342,47308,47273,47238,47203, + 47168,47133,47098,47063,47028,46993,46958,46923, + 46888,46853,46818,46783,46747,46712,46677,46642, + 46606,46571,46536,46500,46465,46429,46394,46358, + 46323,46287,46252,46216,46180,46145,46109,46073, + 46037,46002,45966,45930,45894,45858,45822,45786, + 45750,45714,45678,45642,45606,45570,45534,45498, + 45462,45425,45389,45353,45316,45280,45244,45207, + 45171,45135,45098,45062,45025,44989,44952,44915, + 44879,44842,44806,44769,44732,44695,44659,44622, + 44585,44548,44511,44474,44437,44400,44363,44326, + 44289,44252,44215,44178,44141,44104,44067,44029, + 43992,43955,43918,43880,43843,43806,43768,43731, + 43693,43656,43618,43581,43543,43506,43468,43430, + 43393,43355,43317,43280,43242,43204,43166,43128, + 43091,43053,43015,42977,42939,42901,42863,42825, + 42787,42749,42711,42672,42634,42596,42558,42520, + 42481,42443,42405,42366,42328,42290,42251,42213, + 42174,42136,42097,42059,42020,41982,41943,41904, + 41866,41827,41788,41750,41711,41672,41633,41595, + 41556,41517,41478,41439,41400,41361,41322,41283, + 41244,41205,41166,41127,41088,41048,41009,40970, + 40931,40891,40852,40813,40773,40734,40695,40655, + 40616,40576,40537,40497,40458,40418,40379,40339, + 40300,40260,40220,40180,40141,40101,40061,40021, + 39982,39942,39902,39862,39822,39782,39742,39702, + 39662,39622,39582,39542,39502,39462,39422,39382, + 39341,39301,39261,39221,39180,39140,39100,39059, + 39019,38979,38938,38898,38857,38817,38776,38736, + 38695,38655,38614,38573,38533,38492,38451,38411, + 38370,38329,38288,38248,38207,38166,38125,38084, + 38043,38002,37961,37920,37879,37838,37797,37756, + 37715,37674,37633,37592,37551,37509,37468,37427, + 37386,37344,37303,37262,37220,37179,37137,37096, + 37055,37013,36972,36930,36889,36847,36805,36764, + 36722,36681,36639,36597,36556,36514,36472,36430, + 36388,36347,36305,36263,36221,36179,36137,36095, + 36053,36011,35969,35927,35885,35843,35801,35759, + 35717,35675,35633,35590,35548,35506,35464,35421, + 35379,35337,35294,35252,35210,35167,35125,35082, + 35040,34997,34955,34912,34870,34827,34785,34742, + 34699,34657,34614,34571,34529,34486,34443,34400, + 34358,34315,34272,34229,34186,34143,34100,34057, + 34015,33972,33929,33886,33843,33799,33756,33713, + 33670,33627,33584,33541,33498,33454,33411,33368, + 33325,33281,33238,33195,33151,33108,33065,33021, + 32978,32934,32891,32847,32804,32760,32717,32673, + 32630,32586,32542,32499,32455,32411,32368,32324, + 32280,32236,32193,32149,32105,32061,32017,31974, + 31930,31886,31842,31798,31754,31710,31666,31622, + 31578,31534,31490,31446,31402,31357,31313,31269, + 31225,31181,31136,31092,31048,31004,30959,30915, + 30871,30826,30782,30738,30693,30649,30604,30560, + 30515,30471,30426,30382,30337,30293,30248,30204, + 30159,30114,30070,30025,29980,29936,29891,29846, + 29801,29757,29712,29667,29622,29577,29533,29488, + 29443,29398,29353,29308,29263,29218,29173,29128, + 29083,29038,28993,28948,28903,28858,28812,28767, + 28722,28677,28632,28586,28541,28496,28451,28405, + 28360,28315,28269,28224,28179,28133,28088,28042, + 27997,27952,27906,27861,27815,27770,27724,27678, + 27633,27587,27542,27496,27450,27405,27359,27313, + 27268,27222,27176,27131,27085,27039,26993,26947, + 26902,26856,26810,26764,26718,26672,26626,26580, + 26534,26488,26442,26396,26350,26304,26258,26212, + 26166,26120,26074,26028,25982,25936,25889,25843, + 25797,25751,25705,25658,25612,25566,25520,25473, + 25427,25381,25334,25288,25241,25195,25149,25102, + 25056,25009,24963,24916,24870,24823,24777,24730, + 24684,24637,24591,24544,24497,24451,24404,24357, + 24311,24264,24217,24171,24124,24077,24030,23984, + 23937,23890,23843,23796,23750,23703,23656,23609, + 23562,23515,23468,23421,23374,23327,23280,23233, + 23186,23139,23092,23045,22998,22951,22904,22857, + 22810,22763,22716,22668,22621,22574,22527,22480, + 22433,22385,22338,22291,22243,22196,22149,22102, + 22054,22007,21960,21912,21865,21817,21770,21723, + 21675,21628,21580,21533,21485,21438,21390,21343, + 21295,21248,21200,21153,21105,21057,21010,20962, + 20915,20867,20819,20772,20724,20676,20629,20581, + 20533,20485,20438,20390,20342,20294,20246,20199, + 20151,20103,20055,20007,19959,19912,19864,19816, + 19768,19720,19672,19624,19576,19528,19480,19432, + 19384,19336,19288,19240,19192,19144,19096,19048, + 19000,18951,18903,18855,18807,18759,18711,18663, + 18614,18566,18518,18470,18421,18373,18325,18277, + 18228,18180,18132,18084,18035,17987,17939,17890, + 17842,17793,17745,17697,17648,17600,17551,17503, + 17455,17406,17358,17309,17261,17212,17164,17115, + 17067,17018,16970,16921,16872,16824,16775,16727, + 16678,16629,16581,16532,16484,16435,16386,16338, + 16289,16240,16191,16143,16094,16045,15997,15948, + 15899,15850,15802,15753,15704,15655,15606,15557, + 15509,15460,15411,15362,15313,15264,15215,15167, + 15118,15069,15020,14971,14922,14873,14824,14775, + 14726,14677,14628,14579,14530,14481,14432,14383, + 14334,14285,14236,14187,14138,14089,14040,13990, + 13941,13892,13843,13794,13745,13696,13646,13597, + 13548,13499,13450,13401,13351,13302,13253,13204, + 13154,13105,13056,13007,12957,12908,12859,12810, + 12760,12711,12662,12612,12563,12514,12464,12415, + 12366,12316,12267,12218,12168,12119,12069,12020, + 11970,11921,11872,11822,11773,11723,11674,11624, + 11575,11525,11476,11426,11377,11327,11278,11228, + 11179,11129,11080,11030,10981,10931,10882,10832, + 10782,10733,10683,10634,10584,10534,10485,10435, + 10386,10336,10286,10237,10187,10137,10088,10038, + 9988,9939,9889,9839,9790,9740,9690,9640, + 9591,9541,9491,9442,9392,9342,9292,9243, + 9193,9143,9093,9043,8994,8944,8894,8844, + 8794,8745,8695,8645,8595,8545,8496,8446, + 8396,8346,8296,8246,8196,8147,8097,8047, + 7997,7947,7897,7847,7797,7747,7697,7648, + 7598,7548,7498,7448,7398,7348,7298,7248, + 7198,7148,7098,7048,6998,6948,6898,6848, + 6798,6748,6698,6648,6598,6548,6498,6448, + 6398,6348,6298,6248,6198,6148,6098,6048, + 5998,5948,5898,5848,5798,5748,5697,5647, + 5597,5547,5497,5447,5397,5347,5297,5247, + 5197,5146,5096,5046,4996,4946,4896,4846, + 4796,4745,4695,4645,4595,4545,4495,4445, + 4394,4344,4294,4244,4194,4144,4093,4043, + 3993,3943,3893,3843,3792,3742,3692,3642, + 3592,3541,3491,3441,3391,3341,3291,3240, + 3190,3140,3090,3039,2989,2939,2889,2839, + 2788,2738,2688,2638,2587,2537,2487,2437, + 2387,2336,2286,2236,2186,2135,2085,2035, + 1985,1934,1884,1834,1784,1733,1683,1633, + 1583,1532,1482,1432,1382,1331,1281,1231, + 1181,1130,1080,1030,980,929,879,829, + 779,728,678,628,578,527,477,427, + 376,326,276,226,175,125,75,25, + -25,-75,-125,-175,-226,-276,-326,-376, + -427,-477,-527,-578,-628,-678,-728,-779, + -829,-879,-929,-980,-1030,-1080,-1130,-1181, + -1231,-1281,-1331,-1382,-1432,-1482,-1532,-1583, + -1633,-1683,-1733,-1784,-1834,-1884,-1934,-1985, + -2035,-2085,-2135,-2186,-2236,-2286,-2336,-2387, + -2437,-2487,-2537,-2588,-2638,-2688,-2738,-2788, + -2839,-2889,-2939,-2989,-3039,-3090,-3140,-3190, + -3240,-3291,-3341,-3391,-3441,-3491,-3541,-3592, + -3642,-3692,-3742,-3792,-3843,-3893,-3943,-3993, + -4043,-4093,-4144,-4194,-4244,-4294,-4344,-4394, + -4445,-4495,-4545,-4595,-4645,-4695,-4745,-4796, + -4846,-4896,-4946,-4996,-5046,-5096,-5146,-5197, + -5247,-5297,-5347,-5397,-5447,-5497,-5547,-5597, + -5647,-5697,-5748,-5798,-5848,-5898,-5948,-5998, + -6048,-6098,-6148,-6198,-6248,-6298,-6348,-6398, + -6448,-6498,-6548,-6598,-6648,-6698,-6748,-6798, + -6848,-6898,-6948,-6998,-7048,-7098,-7148,-7198, + -7248,-7298,-7348,-7398,-7448,-7498,-7548,-7598, + -7648,-7697,-7747,-7797,-7847,-7897,-7947,-7997, + -8047,-8097,-8147,-8196,-8246,-8296,-8346,-8396, + -8446,-8496,-8545,-8595,-8645,-8695,-8745,-8794, + -8844,-8894,-8944,-8994,-9043,-9093,-9143,-9193, + -9243,-9292,-9342,-9392,-9442,-9491,-9541,-9591, + -9640,-9690,-9740,-9790,-9839,-9889,-9939,-9988, + -10038,-10088,-10137,-10187,-10237,-10286,-10336,-10386, + -10435,-10485,-10534,-10584,-10634,-10683,-10733,-10782, + -10832,-10882,-10931,-10981,-11030,-11080,-11129,-11179, + -11228,-11278,-11327,-11377,-11426,-11476,-11525,-11575, + -11624,-11674,-11723,-11773,-11822,-11872,-11921,-11970, + -12020,-12069,-12119,-12168,-12218,-12267,-12316,-12366, + -12415,-12464,-12514,-12563,-12612,-12662,-12711,-12760, + -12810,-12859,-12908,-12957,-13007,-13056,-13105,-13154, + -13204,-13253,-13302,-13351,-13401,-13450,-13499,-13548, + -13597,-13647,-13696,-13745,-13794,-13843,-13892,-13941, + -13990,-14040,-14089,-14138,-14187,-14236,-14285,-14334, + -14383,-14432,-14481,-14530,-14579,-14628,-14677,-14726, + -14775,-14824,-14873,-14922,-14971,-15020,-15069,-15118, + -15167,-15215,-15264,-15313,-15362,-15411,-15460,-15509, + -15557,-15606,-15655,-15704,-15753,-15802,-15850,-15899, + -15948,-15997,-16045,-16094,-16143,-16191,-16240,-16289, + -16338,-16386,-16435,-16484,-16532,-16581,-16629,-16678, + -16727,-16775,-16824,-16872,-16921,-16970,-17018,-17067, + -17115,-17164,-17212,-17261,-17309,-17358,-17406,-17455, + -17503,-17551,-17600,-17648,-17697,-17745,-17793,-17842, + -17890,-17939,-17987,-18035,-18084,-18132,-18180,-18228, + -18277,-18325,-18373,-18421,-18470,-18518,-18566,-18614, + -18663,-18711,-18759,-18807,-18855,-18903,-18951,-19000, + -19048,-19096,-19144,-19192,-19240,-19288,-19336,-19384, + -19432,-19480,-19528,-19576,-19624,-19672,-19720,-19768, + -19816,-19864,-19912,-19959,-20007,-20055,-20103,-20151, + -20199,-20246,-20294,-20342,-20390,-20438,-20485,-20533, + -20581,-20629,-20676,-20724,-20772,-20819,-20867,-20915, + -20962,-21010,-21057,-21105,-21153,-21200,-21248,-21295, + -21343,-21390,-21438,-21485,-21533,-21580,-21628,-21675, + -21723,-21770,-21817,-21865,-21912,-21960,-22007,-22054, + -22102,-22149,-22196,-22243,-22291,-22338,-22385,-22433, + -22480,-22527,-22574,-22621,-22668,-22716,-22763,-22810, + -22857,-22904,-22951,-22998,-23045,-23092,-23139,-23186, + -23233,-23280,-23327,-23374,-23421,-23468,-23515,-23562, + -23609,-23656,-23703,-23750,-23796,-23843,-23890,-23937, + -23984,-24030,-24077,-24124,-24171,-24217,-24264,-24311, + -24357,-24404,-24451,-24497,-24544,-24591,-24637,-24684, + -24730,-24777,-24823,-24870,-24916,-24963,-25009,-25056, + -25102,-25149,-25195,-25241,-25288,-25334,-25381,-25427, + -25473,-25520,-25566,-25612,-25658,-25705,-25751,-25797, + -25843,-25889,-25936,-25982,-26028,-26074,-26120,-26166, + -26212,-26258,-26304,-26350,-26396,-26442,-26488,-26534, + -26580,-26626,-26672,-26718,-26764,-26810,-26856,-26902, + -26947,-26993,-27039,-27085,-27131,-27176,-27222,-27268, + -27313,-27359,-27405,-27450,-27496,-27542,-27587,-27633, + -27678,-27724,-27770,-27815,-27861,-27906,-27952,-27997, + -28042,-28088,-28133,-28179,-28224,-28269,-28315,-28360, + -28405,-28451,-28496,-28541,-28586,-28632,-28677,-28722, + -28767,-28812,-28858,-28903,-28948,-28993,-29038,-29083, + -29128,-29173,-29218,-29263,-29308,-29353,-29398,-29443, + -29488,-29533,-29577,-29622,-29667,-29712,-29757,-29801, + -29846,-29891,-29936,-29980,-30025,-30070,-30114,-30159, + -30204,-30248,-30293,-30337,-30382,-30426,-30471,-30515, + -30560,-30604,-30649,-30693,-30738,-30782,-30826,-30871, + -30915,-30959,-31004,-31048,-31092,-31136,-31181,-31225, + -31269,-31313,-31357,-31402,-31446,-31490,-31534,-31578, + -31622,-31666,-31710,-31754,-31798,-31842,-31886,-31930, + -31974,-32017,-32061,-32105,-32149,-32193,-32236,-32280, + -32324,-32368,-32411,-32455,-32499,-32542,-32586,-32630, + -32673,-32717,-32760,-32804,-32847,-32891,-32934,-32978, + -33021,-33065,-33108,-33151,-33195,-33238,-33281,-33325, + -33368,-33411,-33454,-33498,-33541,-33584,-33627,-33670, + -33713,-33756,-33799,-33843,-33886,-33929,-33972,-34015, + -34057,-34100,-34143,-34186,-34229,-34272,-34315,-34358, + -34400,-34443,-34486,-34529,-34571,-34614,-34657,-34699, + -34742,-34785,-34827,-34870,-34912,-34955,-34997,-35040, + -35082,-35125,-35167,-35210,-35252,-35294,-35337,-35379, + -35421,-35464,-35506,-35548,-35590,-35633,-35675,-35717, + -35759,-35801,-35843,-35885,-35927,-35969,-36011,-36053, + -36095,-36137,-36179,-36221,-36263,-36305,-36347,-36388, + -36430,-36472,-36514,-36555,-36597,-36639,-36681,-36722, + -36764,-36805,-36847,-36889,-36930,-36972,-37013,-37055, + -37096,-37137,-37179,-37220,-37262,-37303,-37344,-37386, + -37427,-37468,-37509,-37551,-37592,-37633,-37674,-37715, + -37756,-37797,-37838,-37879,-37920,-37961,-38002,-38043, + -38084,-38125,-38166,-38207,-38248,-38288,-38329,-38370, + -38411,-38451,-38492,-38533,-38573,-38614,-38655,-38695, + -38736,-38776,-38817,-38857,-38898,-38938,-38979,-39019, + -39059,-39100,-39140,-39180,-39221,-39261,-39301,-39341, + -39382,-39422,-39462,-39502,-39542,-39582,-39622,-39662, + -39702,-39742,-39782,-39822,-39862,-39902,-39942,-39982, + -40021,-40061,-40101,-40141,-40180,-40220,-40260,-40299, + -40339,-40379,-40418,-40458,-40497,-40537,-40576,-40616, + -40655,-40695,-40734,-40773,-40813,-40852,-40891,-40931, + -40970,-41009,-41048,-41087,-41127,-41166,-41205,-41244, + -41283,-41322,-41361,-41400,-41439,-41478,-41517,-41556, + -41595,-41633,-41672,-41711,-41750,-41788,-41827,-41866, + -41904,-41943,-41982,-42020,-42059,-42097,-42136,-42174, + -42213,-42251,-42290,-42328,-42366,-42405,-42443,-42481, + -42520,-42558,-42596,-42634,-42672,-42711,-42749,-42787, + -42825,-42863,-42901,-42939,-42977,-43015,-43053,-43091, + -43128,-43166,-43204,-43242,-43280,-43317,-43355,-43393, + -43430,-43468,-43506,-43543,-43581,-43618,-43656,-43693, + -43731,-43768,-43806,-43843,-43880,-43918,-43955,-43992, + -44029,-44067,-44104,-44141,-44178,-44215,-44252,-44289, + -44326,-44363,-44400,-44437,-44474,-44511,-44548,-44585, + -44622,-44659,-44695,-44732,-44769,-44806,-44842,-44879, + -44915,-44952,-44989,-45025,-45062,-45098,-45135,-45171, + -45207,-45244,-45280,-45316,-45353,-45389,-45425,-45462, + -45498,-45534,-45570,-45606,-45642,-45678,-45714,-45750, + -45786,-45822,-45858,-45894,-45930,-45966,-46002,-46037, + -46073,-46109,-46145,-46180,-46216,-46252,-46287,-46323, + -46358,-46394,-46429,-46465,-46500,-46536,-46571,-46606, + -46642,-46677,-46712,-46747,-46783,-46818,-46853,-46888, + -46923,-46958,-46993,-47028,-47063,-47098,-47133,-47168, + -47203,-47238,-47273,-47308,-47342,-47377,-47412,-47446, + -47481,-47516,-47550,-47585,-47619,-47654,-47688,-47723, + -47757,-47792,-47826,-47860,-47895,-47929,-47963,-47998, + -48032,-48066,-48100,-48134,-48168,-48202,-48236,-48271, + -48304,-48338,-48372,-48406,-48440,-48474,-48508,-48542, + -48575,-48609,-48643,-48676,-48710,-48744,-48777,-48811, + -48844,-48878,-48911,-48945,-48978,-49012,-49045,-49078, + -49112,-49145,-49178,-49211,-49244,-49278,-49311,-49344, + -49377,-49410,-49443,-49476,-49509,-49542,-49575,-49608, + -49640,-49673,-49706,-49739,-49771,-49804,-49837,-49869, + -49902,-49935,-49967,-50000,-50032,-50065,-50097,-50129, + -50162,-50194,-50226,-50259,-50291,-50323,-50355,-50387, + -50420,-50452,-50484,-50516,-50548,-50580,-50612,-50644, + -50675,-50707,-50739,-50771,-50803,-50834,-50866,-50898, + -50929,-50961,-50993,-51024,-51056,-51087,-51119,-51150, + -51182,-51213,-51244,-51276,-51307,-51338,-51369,-51401, + -51432,-51463,-51494,-51525,-51556,-51587,-51618,-51649, + -51680,-51711,-51742,-51773,-51803,-51834,-51865,-51896, + -51926,-51957,-51988,-52018,-52049,-52079,-52110,-52140, + -52171,-52201,-52231,-52262,-52292,-52322,-52353,-52383, + -52413,-52443,-52473,-52503,-52534,-52564,-52594,-52624, + -52653,-52683,-52713,-52743,-52773,-52803,-52832,-52862, + -52892,-52922,-52951,-52981,-53010,-53040,-53069,-53099, + -53128,-53158,-53187,-53216,-53246,-53275,-53304,-53334, + -53363,-53392,-53421,-53450,-53479,-53508,-53537,-53566, + -53595,-53624,-53653,-53682,-53711,-53739,-53768,-53797, + -53826,-53854,-53883,-53911,-53940,-53969,-53997,-54026, + -54054,-54082,-54111,-54139,-54167,-54196,-54224,-54252, + -54280,-54308,-54337,-54365,-54393,-54421,-54449,-54477, + -54505,-54533,-54560,-54588,-54616,-54644,-54672,-54699, + -54727,-54755,-54782,-54810,-54837,-54865,-54892,-54920, + -54947,-54974,-55002,-55029,-55056,-55084,-55111,-55138, + -55165,-55192,-55219,-55246,-55274,-55300,-55327,-55354, + -55381,-55408,-55435,-55462,-55489,-55515,-55542,-55569, + -55595,-55622,-55648,-55675,-55701,-55728,-55754,-55781, + -55807,-55833,-55860,-55886,-55912,-55938,-55965,-55991, + -56017,-56043,-56069,-56095,-56121,-56147,-56173,-56199, + -56225,-56250,-56276,-56302,-56328,-56353,-56379,-56404, + -56430,-56456,-56481,-56507,-56532,-56557,-56583,-56608, + -56633,-56659,-56684,-56709,-56734,-56760,-56785,-56810, + -56835,-56860,-56885,-56910,-56935,-56959,-56984,-57009, + -57034,-57059,-57083,-57108,-57133,-57157,-57182,-57206, + -57231,-57255,-57280,-57304,-57329,-57353,-57377,-57402, + -57426,-57450,-57474,-57498,-57522,-57546,-57570,-57594, + -57618,-57642,-57666,-57690,-57714,-57738,-57762,-57785, + -57809,-57833,-57856,-57880,-57903,-57927,-57950,-57974, + -57997,-58021,-58044,-58067,-58091,-58114,-58137,-58160, + -58183,-58207,-58230,-58253,-58276,-58299,-58322,-58345, + -58367,-58390,-58413,-58436,-58459,-58481,-58504,-58527, + -58549,-58572,-58594,-58617,-58639,-58662,-58684,-58706, + -58729,-58751,-58773,-58795,-58818,-58840,-58862,-58884, + -58906,-58928,-58950,-58972,-58994,-59016,-59038,-59059, + -59081,-59103,-59125,-59146,-59168,-59190,-59211,-59233, + -59254,-59276,-59297,-59318,-59340,-59361,-59382,-59404, + -59425,-59446,-59467,-59488,-59509,-59530,-59551,-59572, + -59593,-59614,-59635,-59656,-59677,-59697,-59718,-59739, + -59759,-59780,-59801,-59821,-59842,-59862,-59883,-59903, + -59923,-59944,-59964,-59984,-60004,-60025,-60045,-60065, + -60085,-60105,-60125,-60145,-60165,-60185,-60205,-60225, + -60244,-60264,-60284,-60304,-60323,-60343,-60363,-60382, + -60402,-60421,-60441,-60460,-60479,-60499,-60518,-60537, + -60556,-60576,-60595,-60614,-60633,-60652,-60671,-60690, + -60709,-60728,-60747,-60766,-60785,-60803,-60822,-60841, + -60859,-60878,-60897,-60915,-60934,-60952,-60971,-60989, + -61007,-61026,-61044,-61062,-61081,-61099,-61117,-61135, + -61153,-61171,-61189,-61207,-61225,-61243,-61261,-61279, + -61297,-61314,-61332,-61350,-61367,-61385,-61403,-61420, + -61438,-61455,-61473,-61490,-61507,-61525,-61542,-61559, + -61577,-61594,-61611,-61628,-61645,-61662,-61679,-61696, + -61713,-61730,-61747,-61764,-61780,-61797,-61814,-61831, + -61847,-61864,-61880,-61897,-61913,-61930,-61946,-61963, + -61979,-61995,-62012,-62028,-62044,-62060,-62076,-62092, + -62108,-62125,-62141,-62156,-62172,-62188,-62204,-62220, + -62236,-62251,-62267,-62283,-62298,-62314,-62329,-62345, + -62360,-62376,-62391,-62407,-62422,-62437,-62453,-62468, + -62483,-62498,-62513,-62528,-62543,-62558,-62573,-62588, + -62603,-62618,-62633,-62648,-62662,-62677,-62692,-62706, + -62721,-62735,-62750,-62764,-62779,-62793,-62808,-62822, + -62836,-62850,-62865,-62879,-62893,-62907,-62921,-62935, + -62949,-62963,-62977,-62991,-63005,-63019,-63032,-63046, + -63060,-63074,-63087,-63101,-63114,-63128,-63141,-63155, + -63168,-63182,-63195,-63208,-63221,-63235,-63248,-63261, + -63274,-63287,-63300,-63313,-63326,-63339,-63352,-63365, + -63378,-63390,-63403,-63416,-63429,-63441,-63454,-63466, + -63479,-63491,-63504,-63516,-63528,-63541,-63553,-63565, + -63578,-63590,-63602,-63614,-63626,-63638,-63650,-63662, + -63674,-63686,-63698,-63709,-63721,-63733,-63745,-63756, + -63768,-63779,-63791,-63803,-63814,-63825,-63837,-63848, + -63859,-63871,-63882,-63893,-63904,-63915,-63927,-63938, + -63949,-63960,-63971,-63981,-63992,-64003,-64014,-64025, + -64035,-64046,-64057,-64067,-64078,-64088,-64099,-64109, + -64120,-64130,-64140,-64151,-64161,-64171,-64181,-64192, + -64202,-64212,-64222,-64232,-64242,-64252,-64261,-64271, + -64281,-64291,-64301,-64310,-64320,-64330,-64339,-64349, + -64358,-64368,-64377,-64387,-64396,-64405,-64414,-64424, + -64433,-64442,-64451,-64460,-64469,-64478,-64487,-64496, + -64505,-64514,-64523,-64532,-64540,-64549,-64558,-64566, + -64575,-64584,-64592,-64601,-64609,-64617,-64626,-64634, + -64642,-64651,-64659,-64667,-64675,-64683,-64691,-64699, + -64707,-64715,-64723,-64731,-64739,-64747,-64754,-64762, + -64770,-64777,-64785,-64793,-64800,-64808,-64815,-64822, + -64830,-64837,-64844,-64852,-64859,-64866,-64873,-64880, + -64887,-64895,-64902,-64908,-64915,-64922,-64929,-64936, + -64943,-64949,-64956,-64963,-64969,-64976,-64982,-64989, + -64995,-65002,-65008,-65015,-65021,-65027,-65033,-65040, + -65046,-65052,-65058,-65064,-65070,-65076,-65082,-65088, + -65094,-65099,-65105,-65111,-65117,-65122,-65128,-65133, + -65139,-65144,-65150,-65155,-65161,-65166,-65171,-65177, + -65182,-65187,-65192,-65197,-65202,-65207,-65212,-65217, + -65222,-65227,-65232,-65237,-65242,-65246,-65251,-65256, + -65260,-65265,-65270,-65274,-65279,-65283,-65287,-65292, + -65296,-65300,-65305,-65309,-65313,-65317,-65321,-65325, + -65329,-65333,-65337,-65341,-65345,-65349,-65352,-65356, + -65360,-65363,-65367,-65371,-65374,-65378,-65381,-65385, + -65388,-65391,-65395,-65398,-65401,-65404,-65408,-65411, + -65414,-65417,-65420,-65423,-65426,-65429,-65431,-65434, + -65437,-65440,-65442,-65445,-65448,-65450,-65453,-65455, + -65458,-65460,-65463,-65465,-65467,-65470,-65472,-65474, + -65476,-65478,-65480,-65482,-65484,-65486,-65488,-65490, + -65492,-65494,-65496,-65497,-65499,-65501,-65502,-65504, + -65505,-65507,-65508,-65510,-65511,-65513,-65514,-65515, + -65516,-65518,-65519,-65520,-65521,-65522,-65523,-65524, + -65525,-65526,-65527,-65527,-65528,-65529,-65530,-65530, + -65531,-65531,-65532,-65532,-65533,-65533,-65534,-65534, + -65534,-65535,-65535,-65535,-65535,-65535,-65535,-65535, + -65535,-65535,-65535,-65535,-65535,-65535,-65535,-65534, + -65534,-65534,-65533,-65533,-65532,-65532,-65531,-65531, + -65530,-65530,-65529,-65528,-65527,-65527,-65526,-65525, + -65524,-65523,-65522,-65521,-65520,-65519,-65518,-65516, + -65515,-65514,-65513,-65511,-65510,-65508,-65507,-65505, + -65504,-65502,-65501,-65499,-65497,-65496,-65494,-65492, + -65490,-65488,-65486,-65484,-65482,-65480,-65478,-65476, + -65474,-65472,-65470,-65467,-65465,-65463,-65460,-65458, + -65455,-65453,-65450,-65448,-65445,-65442,-65440,-65437, + -65434,-65431,-65429,-65426,-65423,-65420,-65417,-65414, + -65411,-65408,-65404,-65401,-65398,-65395,-65391,-65388, + -65385,-65381,-65378,-65374,-65371,-65367,-65363,-65360, + -65356,-65352,-65349,-65345,-65341,-65337,-65333,-65329, + -65325,-65321,-65317,-65313,-65309,-65305,-65300,-65296, + -65292,-65287,-65283,-65279,-65274,-65270,-65265,-65260, + -65256,-65251,-65246,-65242,-65237,-65232,-65227,-65222, + -65217,-65212,-65207,-65202,-65197,-65192,-65187,-65182, + -65177,-65171,-65166,-65161,-65155,-65150,-65144,-65139, + -65133,-65128,-65122,-65117,-65111,-65105,-65099,-65094, + -65088,-65082,-65076,-65070,-65064,-65058,-65052,-65046, + -65040,-65033,-65027,-65021,-65015,-65008,-65002,-64995, + -64989,-64982,-64976,-64969,-64963,-64956,-64949,-64943, + -64936,-64929,-64922,-64915,-64908,-64902,-64895,-64887, + -64880,-64873,-64866,-64859,-64852,-64844,-64837,-64830, + -64822,-64815,-64808,-64800,-64793,-64785,-64777,-64770, + -64762,-64754,-64747,-64739,-64731,-64723,-64715,-64707, + -64699,-64691,-64683,-64675,-64667,-64659,-64651,-64642, + -64634,-64626,-64617,-64609,-64601,-64592,-64584,-64575, + -64566,-64558,-64549,-64540,-64532,-64523,-64514,-64505, + -64496,-64487,-64478,-64469,-64460,-64451,-64442,-64433, + -64424,-64414,-64405,-64396,-64387,-64377,-64368,-64358, + -64349,-64339,-64330,-64320,-64310,-64301,-64291,-64281, + -64271,-64261,-64252,-64242,-64232,-64222,-64212,-64202, + -64192,-64181,-64171,-64161,-64151,-64140,-64130,-64120, + -64109,-64099,-64088,-64078,-64067,-64057,-64046,-64035, + -64025,-64014,-64003,-63992,-63981,-63971,-63960,-63949, + -63938,-63927,-63915,-63904,-63893,-63882,-63871,-63859, + -63848,-63837,-63825,-63814,-63803,-63791,-63779,-63768, + -63756,-63745,-63733,-63721,-63709,-63698,-63686,-63674, + -63662,-63650,-63638,-63626,-63614,-63602,-63590,-63578, + -63565,-63553,-63541,-63528,-63516,-63504,-63491,-63479, + -63466,-63454,-63441,-63429,-63416,-63403,-63390,-63378, + -63365,-63352,-63339,-63326,-63313,-63300,-63287,-63274, + -63261,-63248,-63235,-63221,-63208,-63195,-63182,-63168, + -63155,-63141,-63128,-63114,-63101,-63087,-63074,-63060, + -63046,-63032,-63019,-63005,-62991,-62977,-62963,-62949, + -62935,-62921,-62907,-62893,-62879,-62865,-62850,-62836, + -62822,-62808,-62793,-62779,-62764,-62750,-62735,-62721, + -62706,-62692,-62677,-62662,-62648,-62633,-62618,-62603, + -62588,-62573,-62558,-62543,-62528,-62513,-62498,-62483, + -62468,-62453,-62437,-62422,-62407,-62391,-62376,-62360, + -62345,-62329,-62314,-62298,-62283,-62267,-62251,-62236, + -62220,-62204,-62188,-62172,-62156,-62141,-62125,-62108, + -62092,-62076,-62060,-62044,-62028,-62012,-61995,-61979, + -61963,-61946,-61930,-61913,-61897,-61880,-61864,-61847, + -61831,-61814,-61797,-61780,-61764,-61747,-61730,-61713, + -61696,-61679,-61662,-61645,-61628,-61611,-61594,-61577, + -61559,-61542,-61525,-61507,-61490,-61473,-61455,-61438, + -61420,-61403,-61385,-61367,-61350,-61332,-61314,-61297, + -61279,-61261,-61243,-61225,-61207,-61189,-61171,-61153, + -61135,-61117,-61099,-61081,-61062,-61044,-61026,-61007, + -60989,-60971,-60952,-60934,-60915,-60897,-60878,-60859, + -60841,-60822,-60803,-60785,-60766,-60747,-60728,-60709, + -60690,-60671,-60652,-60633,-60614,-60595,-60576,-60556, + -60537,-60518,-60499,-60479,-60460,-60441,-60421,-60402, + -60382,-60363,-60343,-60323,-60304,-60284,-60264,-60244, + -60225,-60205,-60185,-60165,-60145,-60125,-60105,-60085, + -60065,-60045,-60025,-60004,-59984,-59964,-59944,-59923, + -59903,-59883,-59862,-59842,-59821,-59801,-59780,-59759, + -59739,-59718,-59697,-59677,-59656,-59635,-59614,-59593, + -59572,-59551,-59530,-59509,-59488,-59467,-59446,-59425, + -59404,-59382,-59361,-59340,-59318,-59297,-59276,-59254, + -59233,-59211,-59189,-59168,-59146,-59125,-59103,-59081, + -59059,-59038,-59016,-58994,-58972,-58950,-58928,-58906, + -58884,-58862,-58840,-58818,-58795,-58773,-58751,-58729, + -58706,-58684,-58662,-58639,-58617,-58594,-58572,-58549, + -58527,-58504,-58481,-58459,-58436,-58413,-58390,-58367, + -58345,-58322,-58299,-58276,-58253,-58230,-58207,-58183, + -58160,-58137,-58114,-58091,-58067,-58044,-58021,-57997, + -57974,-57950,-57927,-57903,-57880,-57856,-57833,-57809, + -57785,-57762,-57738,-57714,-57690,-57666,-57642,-57618, + -57594,-57570,-57546,-57522,-57498,-57474,-57450,-57426, + -57402,-57377,-57353,-57329,-57304,-57280,-57255,-57231, + -57206,-57182,-57157,-57133,-57108,-57083,-57059,-57034, + -57009,-56984,-56959,-56935,-56910,-56885,-56860,-56835, + -56810,-56785,-56760,-56734,-56709,-56684,-56659,-56633, + -56608,-56583,-56557,-56532,-56507,-56481,-56456,-56430, + -56404,-56379,-56353,-56328,-56302,-56276,-56250,-56225, + -56199,-56173,-56147,-56121,-56095,-56069,-56043,-56017, + -55991,-55965,-55938,-55912,-55886,-55860,-55833,-55807, + -55781,-55754,-55728,-55701,-55675,-55648,-55622,-55595, + -55569,-55542,-55515,-55489,-55462,-55435,-55408,-55381, + -55354,-55327,-55300,-55274,-55246,-55219,-55192,-55165, + -55138,-55111,-55084,-55056,-55029,-55002,-54974,-54947, + -54920,-54892,-54865,-54837,-54810,-54782,-54755,-54727, + -54699,-54672,-54644,-54616,-54588,-54560,-54533,-54505, + -54477,-54449,-54421,-54393,-54365,-54337,-54308,-54280, + -54252,-54224,-54196,-54167,-54139,-54111,-54082,-54054, + -54026,-53997,-53969,-53940,-53911,-53883,-53854,-53826, + -53797,-53768,-53739,-53711,-53682,-53653,-53624,-53595, + -53566,-53537,-53508,-53479,-53450,-53421,-53392,-53363, + -53334,-53304,-53275,-53246,-53216,-53187,-53158,-53128, + -53099,-53069,-53040,-53010,-52981,-52951,-52922,-52892, + -52862,-52832,-52803,-52773,-52743,-52713,-52683,-52653, + -52624,-52594,-52564,-52534,-52503,-52473,-52443,-52413, + -52383,-52353,-52322,-52292,-52262,-52231,-52201,-52171, + -52140,-52110,-52079,-52049,-52018,-51988,-51957,-51926, + -51896,-51865,-51834,-51803,-51773,-51742,-51711,-51680, + -51649,-51618,-51587,-51556,-51525,-51494,-51463,-51432, + -51401,-51369,-51338,-51307,-51276,-51244,-51213,-51182, + -51150,-51119,-51087,-51056,-51024,-50993,-50961,-50929, + -50898,-50866,-50834,-50803,-50771,-50739,-50707,-50675, + -50644,-50612,-50580,-50548,-50516,-50484,-50452,-50420, + -50387,-50355,-50323,-50291,-50259,-50226,-50194,-50162, + -50129,-50097,-50065,-50032,-50000,-49967,-49935,-49902, + -49869,-49837,-49804,-49771,-49739,-49706,-49673,-49640, + -49608,-49575,-49542,-49509,-49476,-49443,-49410,-49377, + -49344,-49311,-49278,-49244,-49211,-49178,-49145,-49112, + -49078,-49045,-49012,-48978,-48945,-48911,-48878,-48844, + -48811,-48777,-48744,-48710,-48676,-48643,-48609,-48575, + -48542,-48508,-48474,-48440,-48406,-48372,-48338,-48305, + -48271,-48237,-48202,-48168,-48134,-48100,-48066,-48032, + -47998,-47963,-47929,-47895,-47860,-47826,-47792,-47757, + -47723,-47688,-47654,-47619,-47585,-47550,-47516,-47481, + -47446,-47412,-47377,-47342,-47307,-47273,-47238,-47203, + -47168,-47133,-47098,-47063,-47028,-46993,-46958,-46923, + -46888,-46853,-46818,-46783,-46747,-46712,-46677,-46642, + -46606,-46571,-46536,-46500,-46465,-46429,-46394,-46358, + -46323,-46287,-46251,-46216,-46180,-46145,-46109,-46073, + -46037,-46002,-45966,-45930,-45894,-45858,-45822,-45786, + -45750,-45714,-45678,-45642,-45606,-45570,-45534,-45498, + -45462,-45425,-45389,-45353,-45316,-45280,-45244,-45207, + -45171,-45135,-45098,-45062,-45025,-44989,-44952,-44915, + -44879,-44842,-44806,-44769,-44732,-44695,-44659,-44622, + -44585,-44548,-44511,-44474,-44437,-44400,-44363,-44326, + -44289,-44252,-44215,-44178,-44141,-44104,-44067,-44029, + -43992,-43955,-43918,-43880,-43843,-43806,-43768,-43731, + -43693,-43656,-43618,-43581,-43543,-43506,-43468,-43430, + -43393,-43355,-43317,-43280,-43242,-43204,-43166,-43128, + -43091,-43053,-43015,-42977,-42939,-42901,-42863,-42825, + -42787,-42749,-42711,-42672,-42634,-42596,-42558,-42520, + -42481,-42443,-42405,-42366,-42328,-42290,-42251,-42213, + -42174,-42136,-42097,-42059,-42020,-41982,-41943,-41904, + -41866,-41827,-41788,-41750,-41711,-41672,-41633,-41595, + -41556,-41517,-41478,-41439,-41400,-41361,-41322,-41283, + -41244,-41205,-41166,-41127,-41087,-41048,-41009,-40970, + -40931,-40891,-40852,-40813,-40773,-40734,-40695,-40655, + -40616,-40576,-40537,-40497,-40458,-40418,-40379,-40339, + -40299,-40260,-40220,-40180,-40141,-40101,-40061,-40021, + -39982,-39942,-39902,-39862,-39822,-39782,-39742,-39702, + -39662,-39622,-39582,-39542,-39502,-39462,-39422,-39382, + -39341,-39301,-39261,-39221,-39180,-39140,-39100,-39059, + -39019,-38979,-38938,-38898,-38857,-38817,-38776,-38736, + -38695,-38655,-38614,-38573,-38533,-38492,-38451,-38411, + -38370,-38329,-38288,-38248,-38207,-38166,-38125,-38084, + -38043,-38002,-37961,-37920,-37879,-37838,-37797,-37756, + -37715,-37674,-37633,-37592,-37550,-37509,-37468,-37427, + -37386,-37344,-37303,-37262,-37220,-37179,-37137,-37096, + -37055,-37013,-36972,-36930,-36889,-36847,-36805,-36764, + -36722,-36681,-36639,-36597,-36556,-36514,-36472,-36430, + -36388,-36347,-36305,-36263,-36221,-36179,-36137,-36095, + -36053,-36011,-35969,-35927,-35885,-35843,-35801,-35759, + -35717,-35675,-35633,-35590,-35548,-35506,-35464,-35421, + -35379,-35337,-35294,-35252,-35210,-35167,-35125,-35082, + -35040,-34997,-34955,-34912,-34870,-34827,-34785,-34742, + -34699,-34657,-34614,-34571,-34529,-34486,-34443,-34400, + -34358,-34315,-34272,-34229,-34186,-34143,-34100,-34057, + -34015,-33972,-33929,-33886,-33843,-33799,-33756,-33713, + -33670,-33627,-33584,-33541,-33498,-33454,-33411,-33368, + -33325,-33281,-33238,-33195,-33151,-33108,-33065,-33021, + -32978,-32934,-32891,-32847,-32804,-32760,-32717,-32673, + -32630,-32586,-32542,-32499,-32455,-32411,-32368,-32324, + -32280,-32236,-32193,-32149,-32105,-32061,-32017,-31974, + -31930,-31886,-31842,-31798,-31754,-31710,-31666,-31622, + -31578,-31534,-31490,-31446,-31402,-31357,-31313,-31269, + -31225,-31181,-31136,-31092,-31048,-31004,-30959,-30915, + -30871,-30826,-30782,-30738,-30693,-30649,-30604,-30560, + -30515,-30471,-30426,-30382,-30337,-30293,-30248,-30204, + -30159,-30114,-30070,-30025,-29980,-29936,-29891,-29846, + -29801,-29757,-29712,-29667,-29622,-29577,-29533,-29488, + -29443,-29398,-29353,-29308,-29263,-29218,-29173,-29128, + -29083,-29038,-28993,-28948,-28903,-28858,-28812,-28767, + -28722,-28677,-28632,-28586,-28541,-28496,-28451,-28405, + -28360,-28315,-28269,-28224,-28179,-28133,-28088,-28042, + -27997,-27952,-27906,-27861,-27815,-27770,-27724,-27678, + -27633,-27587,-27542,-27496,-27450,-27405,-27359,-27313, + -27268,-27222,-27176,-27131,-27085,-27039,-26993,-26947, + -26902,-26856,-26810,-26764,-26718,-26672,-26626,-26580, + -26534,-26488,-26442,-26396,-26350,-26304,-26258,-26212, + -26166,-26120,-26074,-26028,-25982,-25936,-25889,-25843, + -25797,-25751,-25705,-25658,-25612,-25566,-25520,-25473, + -25427,-25381,-25334,-25288,-25241,-25195,-25149,-25102, + -25056,-25009,-24963,-24916,-24870,-24823,-24777,-24730, + -24684,-24637,-24591,-24544,-24497,-24451,-24404,-24357, + -24311,-24264,-24217,-24171,-24124,-24077,-24030,-23984, + -23937,-23890,-23843,-23796,-23750,-23703,-23656,-23609, + -23562,-23515,-23468,-23421,-23374,-23327,-23280,-23233, + -23186,-23139,-23092,-23045,-22998,-22951,-22904,-22857, + -22810,-22763,-22716,-22668,-22621,-22574,-22527,-22480, + -22432,-22385,-22338,-22291,-22243,-22196,-22149,-22102, + -22054,-22007,-21960,-21912,-21865,-21817,-21770,-21723, + -21675,-21628,-21580,-21533,-21485,-21438,-21390,-21343, + -21295,-21248,-21200,-21153,-21105,-21057,-21010,-20962, + -20915,-20867,-20819,-20772,-20724,-20676,-20629,-20581, + -20533,-20485,-20438,-20390,-20342,-20294,-20246,-20199, + -20151,-20103,-20055,-20007,-19959,-19912,-19864,-19816, + -19768,-19720,-19672,-19624,-19576,-19528,-19480,-19432, + -19384,-19336,-19288,-19240,-19192,-19144,-19096,-19048, + -19000,-18951,-18903,-18855,-18807,-18759,-18711,-18663, + -18614,-18566,-18518,-18470,-18421,-18373,-18325,-18277, + -18228,-18180,-18132,-18084,-18035,-17987,-17939,-17890, + -17842,-17793,-17745,-17697,-17648,-17600,-17551,-17503, + -17455,-17406,-17358,-17309,-17261,-17212,-17164,-17115, + -17067,-17018,-16970,-16921,-16872,-16824,-16775,-16727, + -16678,-16629,-16581,-16532,-16484,-16435,-16386,-16338, + -16289,-16240,-16191,-16143,-16094,-16045,-15997,-15948, + -15899,-15850,-15802,-15753,-15704,-15655,-15606,-15557, + -15509,-15460,-15411,-15362,-15313,-15264,-15215,-15167, + -15118,-15069,-15020,-14971,-14922,-14873,-14824,-14775, + -14726,-14677,-14628,-14579,-14530,-14481,-14432,-14383, + -14334,-14285,-14236,-14187,-14138,-14089,-14040,-13990, + -13941,-13892,-13843,-13794,-13745,-13696,-13647,-13597, + -13548,-13499,-13450,-13401,-13351,-13302,-13253,-13204, + -13154,-13105,-13056,-13007,-12957,-12908,-12859,-12810, + -12760,-12711,-12662,-12612,-12563,-12514,-12464,-12415, + -12366,-12316,-12267,-12217,-12168,-12119,-12069,-12020, + -11970,-11921,-11872,-11822,-11773,-11723,-11674,-11624, + -11575,-11525,-11476,-11426,-11377,-11327,-11278,-11228, + -11179,-11129,-11080,-11030,-10981,-10931,-10882,-10832, + -10782,-10733,-10683,-10634,-10584,-10534,-10485,-10435, + -10386,-10336,-10286,-10237,-10187,-10137,-10088,-10038, + -9988,-9939,-9889,-9839,-9790,-9740,-9690,-9640, + -9591,-9541,-9491,-9442,-9392,-9342,-9292,-9243, + -9193,-9143,-9093,-9043,-8994,-8944,-8894,-8844, + -8794,-8745,-8695,-8645,-8595,-8545,-8496,-8446, + -8396,-8346,-8296,-8246,-8196,-8147,-8097,-8047, + -7997,-7947,-7897,-7847,-7797,-7747,-7697,-7648, + -7598,-7548,-7498,-7448,-7398,-7348,-7298,-7248, + -7198,-7148,-7098,-7048,-6998,-6948,-6898,-6848, + -6798,-6748,-6698,-6648,-6598,-6548,-6498,-6448, + -6398,-6348,-6298,-6248,-6198,-6148,-6098,-6048, + -5998,-5948,-5898,-5848,-5798,-5747,-5697,-5647, + -5597,-5547,-5497,-5447,-5397,-5347,-5297,-5247, + -5197,-5146,-5096,-5046,-4996,-4946,-4896,-4846, + -4796,-4745,-4695,-4645,-4595,-4545,-4495,-4445, + -4394,-4344,-4294,-4244,-4194,-4144,-4093,-4043, + -3993,-3943,-3893,-3843,-3792,-3742,-3692,-3642, + -3592,-3541,-3491,-3441,-3391,-3341,-3291,-3240, + -3190,-3140,-3090,-3039,-2989,-2939,-2889,-2839, + -2788,-2738,-2688,-2638,-2588,-2537,-2487,-2437, + -2387,-2336,-2286,-2236,-2186,-2135,-2085,-2035, + -1985,-1934,-1884,-1834,-1784,-1733,-1683,-1633, + -1583,-1532,-1482,-1432,-1382,-1331,-1281,-1231, + -1181,-1130,-1080,-1030,-980,-929,-879,-829, + -779,-728,-678,-628,-578,-527,-477,-427, + -376,-326,-276,-226,-175,-125,-75,-25, + 25,75,125,175,226,276,326,376, + 427,477,527,578,628,678,728,779, + 829,879,929,980,1030,1080,1130,1181, + 1231,1281,1331,1382,1432,1482,1532,1583, + 1633,1683,1733,1784,1834,1884,1934,1985, + 2035,2085,2135,2186,2236,2286,2336,2387, + 2437,2487,2537,2587,2638,2688,2738,2788, + 2839,2889,2939,2989,3039,3090,3140,3190, + 3240,3291,3341,3391,3441,3491,3542,3592, + 3642,3692,3742,3792,3843,3893,3943,3993, + 4043,4093,4144,4194,4244,4294,4344,4394, + 4445,4495,4545,4595,4645,4695,4745,4796, + 4846,4896,4946,4996,5046,5096,5146,5197, + 5247,5297,5347,5397,5447,5497,5547,5597, + 5647,5697,5747,5798,5848,5898,5948,5998, + 6048,6098,6148,6198,6248,6298,6348,6398, + 6448,6498,6548,6598,6648,6698,6748,6798, + 6848,6898,6948,6998,7048,7098,7148,7198, + 7248,7298,7348,7398,7448,7498,7548,7598, + 7648,7697,7747,7797,7847,7897,7947,7997, + 8047,8097,8147,8196,8246,8296,8346,8396, + 8446,8496,8545,8595,8645,8695,8745,8794, + 8844,8894,8944,8994,9043,9093,9143,9193, + 9243,9292,9342,9392,9442,9491,9541,9591, + 9640,9690,9740,9790,9839,9889,9939,9988, + 10038,10088,10137,10187,10237,10286,10336,10386, + 10435,10485,10534,10584,10634,10683,10733,10782, + 10832,10882,10931,10981,11030,11080,11129,11179, + 11228,11278,11327,11377,11426,11476,11525,11575, + 11624,11674,11723,11773,11822,11872,11921,11970, + 12020,12069,12119,12168,12218,12267,12316,12366, + 12415,12464,12514,12563,12612,12662,12711,12760, + 12810,12859,12908,12957,13007,13056,13105,13154, + 13204,13253,13302,13351,13401,13450,13499,13548, + 13597,13647,13696,13745,13794,13843,13892,13941, + 13990,14040,14089,14138,14187,14236,14285,14334, + 14383,14432,14481,14530,14579,14628,14677,14726, + 14775,14824,14873,14922,14971,15020,15069,15118, + 15167,15215,15264,15313,15362,15411,15460,15509, + 15557,15606,15655,15704,15753,15802,15850,15899, + 15948,15997,16045,16094,16143,16191,16240,16289, + 16338,16386,16435,16484,16532,16581,16629,16678, + 16727,16775,16824,16872,16921,16970,17018,17067, + 17115,17164,17212,17261,17309,17358,17406,17455, + 17503,17551,17600,17648,17697,17745,17793,17842, + 17890,17939,17987,18035,18084,18132,18180,18228, + 18277,18325,18373,18421,18470,18518,18566,18614, + 18663,18711,18759,18807,18855,18903,18951,19000, + 19048,19096,19144,19192,19240,19288,19336,19384, + 19432,19480,19528,19576,19624,19672,19720,19768, + 19816,19864,19912,19959,20007,20055,20103,20151, + 20199,20246,20294,20342,20390,20438,20485,20533, + 20581,20629,20676,20724,20772,20819,20867,20915, + 20962,21010,21057,21105,21153,21200,21248,21295, + 21343,21390,21438,21485,21533,21580,21628,21675, + 21723,21770,21817,21865,21912,21960,22007,22054, + 22102,22149,22196,22243,22291,22338,22385,22432, + 22480,22527,22574,22621,22668,22716,22763,22810, + 22857,22904,22951,22998,23045,23092,23139,23186, + 23233,23280,23327,23374,23421,23468,23515,23562, + 23609,23656,23703,23750,23796,23843,23890,23937, + 23984,24030,24077,24124,24171,24217,24264,24311, + 24357,24404,24451,24497,24544,24591,24637,24684, + 24730,24777,24823,24870,24916,24963,25009,25056, + 25102,25149,25195,25241,25288,25334,25381,25427, + 25473,25520,25566,25612,25658,25705,25751,25797, + 25843,25889,25936,25982,26028,26074,26120,26166, + 26212,26258,26304,26350,26396,26442,26488,26534, + 26580,26626,26672,26718,26764,26810,26856,26902, + 26947,26993,27039,27085,27131,27176,27222,27268, + 27313,27359,27405,27450,27496,27542,27587,27633, + 27678,27724,27770,27815,27861,27906,27952,27997, + 28042,28088,28133,28179,28224,28269,28315,28360, + 28405,28451,28496,28541,28586,28632,28677,28722, + 28767,28812,28858,28903,28948,28993,29038,29083, + 29128,29173,29218,29263,29308,29353,29398,29443, + 29488,29533,29577,29622,29667,29712,29757,29801, + 29846,29891,29936,29980,30025,30070,30114,30159, + 30204,30248,30293,30337,30382,30427,30471,30516, + 30560,30604,30649,30693,30738,30782,30826,30871, + 30915,30959,31004,31048,31092,31136,31181,31225, + 31269,31313,31357,31402,31446,31490,31534,31578, + 31622,31666,31710,31754,31798,31842,31886,31930, + 31974,32017,32061,32105,32149,32193,32236,32280, + 32324,32368,32411,32455,32499,32542,32586,32630, + 32673,32717,32760,32804,32847,32891,32934,32978, + 33021,33065,33108,33151,33195,33238,33281,33325, + 33368,33411,33454,33498,33541,33584,33627,33670, + 33713,33756,33799,33843,33886,33929,33972,34015, + 34057,34100,34143,34186,34229,34272,34315,34358, + 34400,34443,34486,34529,34571,34614,34657,34699, + 34742,34785,34827,34870,34912,34955,34997,35040, + 35082,35125,35167,35210,35252,35294,35337,35379, + 35421,35464,35506,35548,35590,35633,35675,35717, + 35759,35801,35843,35885,35927,35969,36011,36053, + 36095,36137,36179,36221,36263,36305,36347,36388, + 36430,36472,36514,36556,36597,36639,36681,36722, + 36764,36805,36847,36889,36930,36972,37013,37055, + 37096,37137,37179,37220,37262,37303,37344,37386, + 37427,37468,37509,37551,37592,37633,37674,37715, + 37756,37797,37838,37879,37920,37961,38002,38043, + 38084,38125,38166,38207,38248,38288,38329,38370, + 38411,38451,38492,38533,38573,38614,38655,38695, + 38736,38776,38817,38857,38898,38938,38979,39019, + 39059,39100,39140,39180,39221,39261,39301,39341, + 39382,39422,39462,39502,39542,39582,39622,39662, + 39702,39742,39782,39822,39862,39902,39942,39982, + 40021,40061,40101,40141,40180,40220,40260,40299, + 40339,40379,40418,40458,40497,40537,40576,40616, + 40655,40695,40734,40773,40813,40852,40891,40931, + 40970,41009,41048,41087,41127,41166,41205,41244, + 41283,41322,41361,41400,41439,41478,41517,41556, + 41595,41633,41672,41711,41750,41788,41827,41866, + 41904,41943,41982,42020,42059,42097,42136,42174, + 42213,42251,42290,42328,42366,42405,42443,42481, + 42520,42558,42596,42634,42672,42711,42749,42787, + 42825,42863,42901,42939,42977,43015,43053,43091, + 43128,43166,43204,43242,43280,43317,43355,43393, + 43430,43468,43506,43543,43581,43618,43656,43693, + 43731,43768,43806,43843,43880,43918,43955,43992, + 44029,44067,44104,44141,44178,44215,44252,44289, + 44326,44363,44400,44437,44474,44511,44548,44585, + 44622,44659,44695,44732,44769,44806,44842,44879, + 44915,44952,44989,45025,45062,45098,45135,45171, + 45207,45244,45280,45316,45353,45389,45425,45462, + 45498,45534,45570,45606,45642,45678,45714,45750, + 45786,45822,45858,45894,45930,45966,46002,46037, + 46073,46109,46145,46180,46216,46252,46287,46323, + 46358,46394,46429,46465,46500,46536,46571,46606, + 46642,46677,46712,46747,46783,46818,46853,46888, + 46923,46958,46993,47028,47063,47098,47133,47168, + 47203,47238,47273,47308,47342,47377,47412,47446, + 47481,47516,47550,47585,47619,47654,47688,47723, + 47757,47792,47826,47861,47895,47929,47963,47998, + 48032,48066,48100,48134,48168,48202,48237,48271, + 48305,48338,48372,48406,48440,48474,48508,48542, + 48575,48609,48643,48676,48710,48744,48777,48811, + 48844,48878,48911,48945,48978,49012,49045,49078, + 49112,49145,49178,49211,49244,49278,49311,49344, + 49377,49410,49443,49476,49509,49542,49575,49608, + 49640,49673,49706,49739,49771,49804,49837,49869, + 49902,49935,49967,50000,50032,50064,50097,50129, + 50162,50194,50226,50259,50291,50323,50355,50387, + 50420,50452,50484,50516,50548,50580,50612,50644, + 50675,50707,50739,50771,50803,50834,50866,50898, + 50929,50961,50993,51024,51056,51087,51119,51150, + 51182,51213,51244,51276,51307,51338,51369,51401, + 51432,51463,51494,51525,51556,51587,51618,51649, + 51680,51711,51742,51773,51803,51834,51865,51896, + 51926,51957,51988,52018,52049,52079,52110,52140, + 52171,52201,52231,52262,52292,52322,52353,52383, + 52413,52443,52473,52503,52534,52564,52594,52624, + 52653,52683,52713,52743,52773,52803,52832,52862, + 52892,52922,52951,52981,53010,53040,53069,53099, + 53128,53158,53187,53216,53246,53275,53304,53334, + 53363,53392,53421,53450,53479,53508,53537,53566, + 53595,53624,53653,53682,53711,53739,53768,53797, + 53826,53854,53883,53912,53940,53969,53997,54026, + 54054,54082,54111,54139,54167,54196,54224,54252, + 54280,54309,54337,54365,54393,54421,54449,54477, + 54505,54533,54560,54588,54616,54644,54672,54699, + 54727,54755,54782,54810,54837,54865,54892,54920, + 54947,54974,55002,55029,55056,55084,55111,55138, + 55165,55192,55219,55246,55274,55300,55327,55354, + 55381,55408,55435,55462,55489,55515,55542,55569, + 55595,55622,55648,55675,55701,55728,55754,55781, + 55807,55833,55860,55886,55912,55938,55965,55991, + 56017,56043,56069,56095,56121,56147,56173,56199, + 56225,56250,56276,56302,56328,56353,56379,56404, + 56430,56456,56481,56507,56532,56557,56583,56608, + 56633,56659,56684,56709,56734,56760,56785,56810, + 56835,56860,56885,56910,56935,56959,56984,57009, + 57034,57059,57083,57108,57133,57157,57182,57206, + 57231,57255,57280,57304,57329,57353,57377,57402, + 57426,57450,57474,57498,57522,57546,57570,57594, + 57618,57642,57666,57690,57714,57738,57762,57785, + 57809,57833,57856,57880,57903,57927,57950,57974, + 57997,58021,58044,58067,58091,58114,58137,58160, + 58183,58207,58230,58253,58276,58299,58322,58345, + 58367,58390,58413,58436,58459,58481,58504,58527, + 58549,58572,58594,58617,58639,58662,58684,58706, + 58729,58751,58773,58795,58818,58840,58862,58884, + 58906,58928,58950,58972,58994,59016,59038,59059, + 59081,59103,59125,59146,59168,59190,59211,59233, + 59254,59276,59297,59318,59340,59361,59382,59404, + 59425,59446,59467,59488,59509,59530,59551,59572, + 59593,59614,59635,59656,59677,59697,59718,59739, + 59759,59780,59801,59821,59842,59862,59883,59903, + 59923,59944,59964,59984,60004,60025,60045,60065, + 60085,60105,60125,60145,60165,60185,60205,60225, + 60244,60264,60284,60304,60323,60343,60363,60382, + 60402,60421,60441,60460,60479,60499,60518,60537, + 60556,60576,60595,60614,60633,60652,60671,60690, + 60709,60728,60747,60766,60785,60803,60822,60841, + 60859,60878,60897,60915,60934,60952,60971,60989, + 61007,61026,61044,61062,61081,61099,61117,61135, + 61153,61171,61189,61207,61225,61243,61261,61279, + 61297,61314,61332,61350,61367,61385,61403,61420, + 61438,61455,61473,61490,61507,61525,61542,61559, + 61577,61594,61611,61628,61645,61662,61679,61696, + 61713,61730,61747,61764,61780,61797,61814,61831, + 61847,61864,61880,61897,61913,61930,61946,61963, + 61979,61995,62012,62028,62044,62060,62076,62092, + 62108,62125,62141,62156,62172,62188,62204,62220, + 62236,62251,62267,62283,62298,62314,62329,62345, + 62360,62376,62391,62407,62422,62437,62453,62468, + 62483,62498,62513,62528,62543,62558,62573,62588, + 62603,62618,62633,62648,62662,62677,62692,62706, + 62721,62735,62750,62764,62779,62793,62808,62822, + 62836,62850,62865,62879,62893,62907,62921,62935, + 62949,62963,62977,62991,63005,63019,63032,63046, + 63060,63074,63087,63101,63114,63128,63141,63155, + 63168,63182,63195,63208,63221,63235,63248,63261, + 63274,63287,63300,63313,63326,63339,63352,63365, + 63378,63390,63403,63416,63429,63441,63454,63466, + 63479,63491,63504,63516,63528,63541,63553,63565, + 63578,63590,63602,63614,63626,63638,63650,63662, + 63674,63686,63698,63709,63721,63733,63745,63756, + 63768,63779,63791,63803,63814,63825,63837,63848, + 63859,63871,63882,63893,63904,63915,63927,63938, + 63949,63960,63971,63981,63992,64003,64014,64025, + 64035,64046,64057,64067,64078,64088,64099,64109, + 64120,64130,64140,64151,64161,64171,64181,64192, + 64202,64212,64222,64232,64242,64252,64261,64271, + 64281,64291,64301,64310,64320,64330,64339,64349, + 64358,64368,64377,64387,64396,64405,64414,64424, + 64433,64442,64451,64460,64469,64478,64487,64496, + 64505,64514,64523,64532,64540,64549,64558,64566, + 64575,64584,64592,64600,64609,64617,64626,64634, + 64642,64651,64659,64667,64675,64683,64691,64699, + 64707,64715,64723,64731,64739,64747,64754,64762, + 64770,64777,64785,64793,64800,64808,64815,64822, + 64830,64837,64844,64852,64859,64866,64873,64880, + 64887,64895,64902,64908,64915,64922,64929,64936, + 64943,64949,64956,64963,64969,64976,64982,64989, + 64995,65002,65008,65015,65021,65027,65033,65040, + 65046,65052,65058,65064,65070,65076,65082,65088, + 65094,65099,65105,65111,65117,65122,65128,65133, + 65139,65144,65150,65155,65161,65166,65171,65177, + 65182,65187,65192,65197,65202,65207,65212,65217, + 65222,65227,65232,65237,65242,65246,65251,65256, + 65260,65265,65270,65274,65279,65283,65287,65292, + 65296,65300,65305,65309,65313,65317,65321,65325, + 65329,65333,65337,65341,65345,65349,65352,65356, + 65360,65363,65367,65371,65374,65378,65381,65385, + 65388,65391,65395,65398,65401,65404,65408,65411, + 65414,65417,65420,65423,65426,65429,65431,65434, + 65437,65440,65442,65445,65448,65450,65453,65455, + 65458,65460,65463,65465,65467,65470,65472,65474, + 65476,65478,65480,65482,65484,65486,65488,65490, + 65492,65494,65496,65497,65499,65501,65502,65504, + 65505,65507,65508,65510,65511,65513,65514,65515, + 65516,65518,65519,65520,65521,65522,65523,65524, + 65525,65526,65527,65527,65528,65529,65530,65530, + 65531,65531,65532,65532,65533,65533,65534,65534, + 65534,65535,65535,65535,65535,65535,65535,65535 +}; + +const fixed_t *finecosine = &finesine[FINEANGLES/4]; + +const angle_t tantoangle[2049] = +{ + 0,333772,667544,1001315,1335086,1668857,2002626,2336395, + 2670163,3003929,3337694,3671457,4005219,4338979,4672736,5006492, + 5340245,5673995,6007743,6341488,6675230,7008968,7342704,7676435, + 8010164,8343888,8677609,9011325,9345037,9678744,10012447,10346145, + 10679838,11013526,11347209,11680887,12014558,12348225,12681885,13015539, + 13349187,13682829,14016464,14350092,14683714,15017328,15350936,15684536, + 16018129,16351714,16685291,17018860,17352422,17685974,18019518,18353054, + 18686582,19020100,19353610,19687110,20020600,20354080,20687552,21021014, + 21354466,21687906,22021338,22354758,22688168,23021568,23354956,23688332, + 24021698,24355052,24688396,25021726,25355046,25688352,26021648,26354930, + 26688200,27021456,27354702,27687932,28021150,28354356,28687548,29020724, + 29353888,29687038,30020174,30353296,30686404,31019496,31352574,31685636, + 32018684,32351718,32684734,33017736,33350722,33683692,34016648,34349584, + 34682508,35015412,35348300,35681172,36014028,36346868,36679688,37012492, + 37345276,37678044,38010792,38343524,38676240,39008936,39341612,39674272, + 40006912,40339532,40672132,41004716,41337276,41669820,42002344,42334848, + 42667332,42999796,43332236,43664660,43997060,44329444,44661800,44994140, + 45326456,45658752,45991028,46323280,46655512,46987720,47319908,47652072, + 47984212,48316332,48648428,48980500,49312548,49644576,49976580,50308556, + 50640512,50972444,51304352,51636236,51968096,52299928,52631740,52963524, + 53295284,53627020,53958728,54290412,54622068,54953704,55285308,55616888, + 55948444,56279972,56611472,56942948,57274396,57605816,57937212,58268576, + 58599916,58931228,59262512,59593768,59924992,60256192,60587364,60918508, + 61249620,61580704,61911760,62242788,62573788,62904756,63235692,63566604, + 63897480,64228332,64559148,64889940,65220696,65551424,65882120,66212788, + 66543420,66874024,67204600,67535136,67865648,68196120,68526568,68856984, + 69187360,69517712,69848024,70178304,70508560,70838776,71168960,71499112, + 71829224,72159312,72489360,72819376,73149360,73479304,73809216,74139096, + 74468936,74798744,75128520,75458264,75787968,76117632,76447264,76776864, + 77106424,77435952,77765440,78094888,78424304,78753688,79083032,79412336, + 79741608,80070840,80400032,80729192,81058312,81387392,81716432,82045440, + 82374408,82703336,83032224,83361080,83689896,84018664,84347400,84676096, + 85004760,85333376,85661952,85990488,86318984,86647448,86975864,87304240, + 87632576,87960872,88289128,88617344,88945520,89273648,89601736,89929792, + 90257792,90585760,90913688,91241568,91569408,91897200,92224960,92552672, + 92880336,93207968,93535552,93863088,94190584,94518040,94845448,95172816, + 95500136,95827416,96154648,96481832,96808976,97136080,97463136,97790144, + 98117112,98444032,98770904,99097736,99424520,99751256,100077944,100404592, + 100731192,101057744,101384248,101710712,102037128,102363488,102689808,103016080, + 103342312,103668488,103994616,104320696,104646736,104972720,105298656,105624552, + 105950392,106276184,106601928,106927624,107253272,107578872,107904416,108229920, + 108555368,108880768,109206120,109531416,109856664,110181872,110507016,110832120, + 111157168,111482168,111807112,112132008,112456856,112781648,113106392,113431080, + 113755720,114080312,114404848,114729328,115053760,115378136,115702464,116026744, + 116350960,116675128,116999248,117323312,117647320,117971272,118295176,118619024, + 118942816,119266560,119590248,119913880,120237456,120560984,120884456,121207864, + 121531224,121854528,122177784,122500976,122824112,123147200,123470224,123793200, + 124116120,124438976,124761784,125084528,125407224,125729856,126052432,126374960, + 126697424,127019832,127342184,127664472,127986712,128308888,128631008,128953072, + 129275080,129597024,129918912,130240744,130562520,130884232,131205888,131527480, + 131849016,132170496,132491912,132813272,133134576,133455816,133776992,134098120, + 134419184,134740176,135061120,135382000,135702816,136023584,136344272,136664912, + 136985488,137306016,137626464,137946864,138267184,138587456,138907664,139227808, + 139547904,139867920,140187888,140507776,140827616,141147392,141467104,141786752, + 142106336,142425856,142745312,143064720,143384048,143703312,144022512,144341664, + 144660736,144979744,145298704,145617584,145936400,146255168,146573856,146892480, + 147211040,147529536,147847968,148166336,148484640,148802880,149121056,149439152, + 149757200,150075168,150393072,150710912,151028688,151346400,151664048,151981616, + 152299136,152616576,152933952,153251264,153568496,153885680,154202784,154519824, + 154836784,155153696,155470528,155787296,156104000,156420624,156737200,157053696, + 157370112,157686480,158002768,158318976,158635136,158951216,159267232,159583168, + 159899040,160214848,160530592,160846256,161161840,161477376,161792832,162108208, + 162423520,162738768,163053952,163369040,163684080,163999040,164313936,164628752, + 164943504,165258176,165572784,165887312,166201776,166516160,166830480,167144736, + 167458912,167773008,168087040,168400992,168714880,169028688,169342432,169656096, + 169969696,170283216,170596672,170910032,171223344,171536576,171849728,172162800, + 172475808,172788736,173101600,173414384,173727104,174039728,174352288,174664784, + 174977200,175289536,175601792,175913984,176226096,176538144,176850096,177161984, + 177473792,177785536,178097200,178408784,178720288,179031728,179343088,179654368, + 179965568,180276704,180587744,180898720,181209616,181520448,181831184,182141856, + 182452448,182762960,183073408,183383760,183694048,184004240,184314368,184624416, + 184934400,185244288,185554096,185863840,186173504,186483072,186792576,187102000, + 187411344,187720608,188029808,188338912,188647936,188956896,189265760,189574560, + 189883264,190191904,190500448,190808928,191117312,191425632,191733872,192042016, + 192350096,192658096,192966000,193273840,193581584,193889264,194196848,194504352, + 194811792,195119136,195426400,195733584,196040688,196347712,196654656,196961520, + 197268304,197574992,197881616,198188144,198494592,198800960,199107248,199413456, + 199719584,200025616,200331584,200637456,200943248,201248960,201554576,201860128, + 202165584,202470960,202776256,203081456,203386592,203691632,203996592,204301472, + 204606256,204910976,205215600,205520144,205824592,206128960,206433248,206737456, + 207041584,207345616,207649568,207953424,208257216,208560912,208864512,209168048, + 209471488,209774832,210078112,210381296,210684384,210987408,211290336,211593184, + 211895936,212198608,212501184,212803680,213106096,213408432,213710672,214012816, + 214314880,214616864,214918768,215220576,215522288,215823920,216125472,216426928, + 216728304,217029584,217330784,217631904,217932928,218233856,218534704,218835472, + 219136144,219436720,219737216,220037632,220337952,220638192,220938336,221238384, + 221538352,221838240,222138032,222437728,222737344,223036880,223336304,223635664, + 223934912,224234096,224533168,224832160,225131072,225429872,225728608,226027232, + 226325776,226624240,226922608,227220880,227519056,227817152,228115168,228413088, + 228710912,229008640,229306288,229603840,229901312,230198688,230495968,230793152, + 231090256,231387280,231684192,231981024,232277760,232574416,232870960,233167440, + 233463808,233760096,234056288,234352384,234648384,234944304,235240128,235535872, + 235831504,236127056,236422512,236717888,237013152,237308336,237603424,237898416, + 238193328,238488144,238782864,239077488,239372016,239666464,239960816,240255072, + 240549232,240843312,241137280,241431168,241724960,242018656,242312256,242605776, + 242899200,243192512,243485744,243778896,244071936,244364880,244657744,244950496, + 245243168,245535744,245828224,246120608,246412912,246705104,246997216,247289216, + 247581136,247872960,248164688,248456320,248747856,249039296,249330640,249621904, + 249913056,250204128,250495088,250785968,251076736,251367424,251658016,251948512, + 252238912,252529200,252819408,253109520,253399536,253689456,253979280,254269008, + 254558640,254848176,255137632,255426976,255716224,256005376,256294432,256583392, + 256872256,257161024,257449696,257738272,258026752,258315136,258603424,258891600, + 259179696,259467696,259755600,260043392,260331104,260618704,260906224,261193632, + 261480960,261768176,262055296,262342320,262629248,262916080,263202816,263489456, + 263776000,264062432,264348784,264635024,264921168,265207216,265493168,265779024, + 266064784,266350448,266636000,266921472,267206832,267492096,267777264,268062336, + 268347312,268632192,268916960,269201632,269486208,269770688,270055072,270339360, + 270623552,270907616,271191616,271475488,271759296,272042976,272326560,272610048, + 272893440,273176736,273459936,273743040,274026048,274308928,274591744,274874432, + 275157024,275439520,275721920,276004224,276286432,276568512,276850528,277132416, + 277414240,277695936,277977536,278259040,278540448,278821728,279102944,279384032, + 279665056,279945952,280226752,280507456,280788064,281068544,281348960,281629248, + 281909472,282189568,282469568,282749440,283029248,283308960,283588544,283868032, + 284147424,284426720,284705920,284985024,285264000,285542912,285821696,286100384, + 286378976,286657440,286935840,287214112,287492320,287770400,288048384,288326240, + 288604032,288881696,289159264,289436768,289714112,289991392,290268576,290545632, + 290822592,291099456,291376224,291652896,291929440,292205888,292482272,292758528, + 293034656,293310720,293586656,293862496,294138240,294413888,294689440,294964864, + 295240192,295515424,295790560,296065600,296340512,296615360,296890080,297164704, + 297439200,297713632,297987936,298262144,298536256,298810240,299084160,299357952, + 299631648,299905248,300178720,300452128,300725408,300998592,301271680,301544640, + 301817536,302090304,302362976,302635520,302908000,303180352,303452608,303724768, + 303996800,304268768,304540608,304812320,305083968,305355520,305626944,305898272, + 306169472,306440608,306711616,306982528,307253344,307524064,307794656,308065152, + 308335552,308605856,308876032,309146112,309416096,309685984,309955744,310225408, + 310494976,310764448,311033824,311303072,311572224,311841280,312110208,312379040, + 312647776,312916416,313184960,313453376,313721696,313989920,314258016,314526016, + 314793920,315061728,315329408,315597024,315864512,316131872,316399168,316666336, + 316933408,317200384,317467232,317733984,318000640,318267200,318533632,318799968, + 319066208,319332352,319598368,319864288,320130112,320395808,320661408,320926912, + 321192320,321457632,321722816,321987904,322252864,322517760,322782528,323047200, + 323311744,323576192,323840544,324104800,324368928,324632992,324896928,325160736, + 325424448,325688096,325951584,326215008,326478304,326741504,327004608,327267584, + 327530464,327793248,328055904,328318496,328580960,328843296,329105568,329367712, + 329629760,329891680,330153536,330415264,330676864,330938400,331199808,331461120, + 331722304,331983392,332244384,332505280,332766048,333026752,333287296,333547776, + 333808128,334068384,334328544,334588576,334848512,335108352,335368064,335627712, + 335887200,336146624,336405920,336665120,336924224,337183200,337442112,337700864, + 337959552,338218112,338476576,338734944,338993184,339251328,339509376,339767296, + 340025120,340282848,340540480,340797984,341055392,341312704,341569888,341826976, + 342083968,342340832,342597600,342854272,343110848,343367296,343623648,343879904, + 344136032,344392064,344648000,344903808,345159520,345415136,345670656,345926048, + 346181344,346436512,346691616,346946592,347201440,347456224,347710880,347965440, + 348219872,348474208,348728448,348982592,349236608,349490528,349744320,349998048, + 350251648,350505152,350758528,351011808,351264992,351518048,351771040,352023872, + 352276640,352529280,352781824,353034272,353286592,353538816,353790944,354042944, + 354294880,354546656,354798368,355049952,355301440,355552800,355804096,356055264, + 356306304,356557280,356808128,357058848,357309504,357560032,357810464,358060768, + 358311008,358561088,358811104,359060992,359310784,359560480,359810048,360059520, + 360308896,360558144,360807296,361056352,361305312,361554144,361802880,362051488, + 362300032,362548448,362796736,363044960,363293056,363541024,363788928,364036704, + 364284384,364531936,364779392,365026752,365274016,365521152,365768192,366015136, + 366261952,366508672,366755296,367001792,367248192,367494496,367740704,367986784, + 368232768,368478656,368724416,368970080,369215648,369461088,369706432,369951680, + 370196800,370441824,370686752,370931584,371176288,371420896,371665408,371909792, + 372154080,372398272,372642336,372886304,373130176,373373952,373617600,373861152, + 374104608,374347936,374591168,374834304,375077312,375320224,375563040,375805760, + 376048352,376290848,376533248,376775520,377017696,377259776,377501728,377743584, + 377985344,378227008,378468544,378709984,378951328,379192544,379433664,379674688, + 379915584,380156416,380397088,380637696,380878176,381118560,381358848,381599040, + 381839104,382079072,382318912,382558656,382798304,383037856,383277280,383516640, + 383755840,383994976,384233984,384472896,384711712,384950400,385188992,385427488, + 385665888,385904160,386142336,386380384,386618368,386856224,387093984,387331616, + 387569152,387806592,388043936,388281152,388518272,388755296,388992224,389229024, + 389465728,389702336,389938816,390175200,390411488,390647680,390883744,391119712, + 391355584,391591328,391826976,392062528,392297984,392533312,392768544,393003680, + 393238720,393473632,393708448,393943168,394177760,394412256,394646656,394880960, + 395115136,395349216,395583200,395817088,396050848,396284512,396518080,396751520, + 396984864,397218112,397451264,397684288,397917248,398150080,398382784,398615424, + 398847936,399080320,399312640,399544832,399776928,400008928,400240832,400472608, + 400704288,400935872,401167328,401398720,401629984,401861120,402092192,402323136, + 402553984,402784736,403015360,403245888,403476320,403706656,403936896,404167008, + 404397024,404626944,404856736,405086432,405316032,405545536,405774912,406004224, + 406233408,406462464,406691456,406920320,407149088,407377760,407606336,407834784, + 408063136,408291392,408519520,408747584,408975520,409203360,409431072,409658720, + 409886240,410113664,410340992,410568192,410795296,411022304,411249216,411476032, + 411702720,411929312,412155808,412382176,412608480,412834656,413060736,413286720, + 413512576,413738336,413964000,414189568,414415040,414640384,414865632,415090784, + 415315840,415540800,415765632,415990368,416215008,416439552,416663968,416888288, + 417112512,417336640,417560672,417784576,418008384,418232096,418455712,418679200, + 418902624,419125920,419349120,419572192,419795200,420018080,420240864,420463552, + 420686144,420908608,421130976,421353280,421575424,421797504,422019488,422241344, + 422463104,422684768,422906336,423127776,423349120,423570400,423791520,424012576, + 424233536,424454368,424675104,424895744,425116288,425336736,425557056,425777280, + 425997408,426217440,426437376,426657184,426876928,427096544,427316064,427535488, + 427754784,427974016,428193120,428412128,428631040,428849856,429068544,429287168, + 429505664,429724064,429942368,430160576,430378656,430596672,430814560,431032352, + 431250048,431467616,431685120,431902496,432119808,432336992,432554080,432771040, + 432987936,433204736,433421408,433637984,433854464,434070848,434287104,434503296, + 434719360,434935360,435151232,435367008,435582656,435798240,436013696,436229088, + 436444352,436659520,436874592,437089568,437304416,437519200,437733856,437948416, + 438162880,438377248,438591520,438805696,439019744,439233728,439447584,439661344, + 439875008,440088576,440302048,440515392,440728672,440941824,441154880,441367872, + 441580736,441793472,442006144,442218720,442431168,442643552,442855808,443067968, + 443280032,443492000,443703872,443915648,444127296,444338880,444550336,444761696, + 444972992,445184160,445395232,445606176,445817056,446027840,446238496,446449088, + 446659552,446869920,447080192,447290400,447500448,447710432,447920320,448130112, + 448339776,448549376,448758848,448968224,449177536,449386720,449595808,449804800, + 450013664,450222464,450431168,450639776,450848256,451056640,451264960,451473152, + 451681248,451889248,452097152,452304960,452512672,452720288,452927808,453135232, + 453342528,453549760,453756864,453963904,454170816,454377632,454584384,454791008, + 454997536,455203968,455410304,455616544,455822688,456028704,456234656,456440512, + 456646240,456851904,457057472,457262912,457468256,457673536,457878688,458083744, + 458288736,458493600,458698368,458903040,459107616,459312096,459516480,459720768, + 459924960,460129056,460333056,460536960,460740736,460944448,461148064,461351584, + 461554976,461758304,461961536,462164640,462367680,462570592,462773440,462976160, + 463178816,463381344,463583776,463786144,463988384,464190560,464392608,464594560, + 464796448,464998208,465199872,465401472,465602944,465804320,466005600,466206816, + 466407904,466608896,466809824,467010624,467211328,467411936,467612480,467812896, + 468013216,468213440,468413600,468613632,468813568,469013440,469213184,469412832, + 469612416,469811872,470011232,470210528,470409696,470608800,470807776,471006688, + 471205472,471404192,471602784,471801312,471999712,472198048,472396288,472594400, + 472792448,472990400,473188256,473385984,473583648,473781216,473978688,474176064, + 474373344,474570528,474767616,474964608,475161504,475358336,475555040,475751648, + 475948192,476144608,476340928,476537184,476733312,476929376,477125344,477321184, + 477516960,477712640,477908224,478103712,478299104,478494400,478689600,478884704, + 479079744,479274656,479469504,479664224,479858880,480053408,480247872,480442240, + 480636512,480830656,481024736,481218752,481412640,481606432,481800128,481993760, + 482187264,482380704,482574016,482767264,482960416,483153472,483346432,483539296, + 483732064,483924768,484117344,484309856,484502240,484694560,484886784,485078912, + 485270944,485462880,485654720,485846464,486038144,486229696,486421184,486612576, + 486803840,486995040,487186176,487377184,487568096,487758912,487949664,488140320, + 488330880,488521312,488711712,488901984,489092160,489282240,489472256,489662176, + 489851968,490041696,490231328,490420896,490610336,490799712,490988960,491178144, + 491367232,491556224,491745120,491933920,492122656,492311264,492499808,492688256, + 492876608,493064864,493253056,493441120,493629120,493817024,494004832,494192544, + 494380160,494567712,494755136,494942496,495129760,495316928,495504000,495691008, + 495877888,496064704,496251424,496438048,496624608,496811040,496997408,497183680, + 497369856,497555936,497741920,497927840,498113632,498299360,498484992,498670560, + 498856000,499041376,499226656,499411840,499596928,499781920,499966848,500151680, + 500336416,500521056,500705600,500890080,501074464,501258752,501442944,501627040, + 501811072,501995008,502178848,502362592,502546240,502729824,502913312,503096704, + 503280000,503463232,503646368,503829408,504012352,504195200,504377984,504560672, + 504743264,504925760,505108192,505290496,505472736,505654912,505836960,506018944, + 506200832,506382624,506564320,506745952,506927488,507108928,507290272,507471552, + 507652736,507833824,508014816,508195744,508376576,508557312,508737952,508918528, + 509099008,509279392,509459680,509639904,509820032,510000064,510180000,510359872, + 510539648,510719328,510898944,511078432,511257856,511437216,511616448,511795616, + 511974688,512153664,512332576,512511392,512690112,512868768,513047296,513225792, + 513404160,513582432,513760640,513938784,514116800,514294752,514472608,514650368, + 514828064,515005664,515183168,515360608,515537952,515715200,515892352,516069440, + 516246432,516423328,516600160,516776896,516953536,517130112,517306592,517482976, + 517659264,517835488,518011616,518187680,518363648,518539520,518715296,518891008, + 519066624,519242144,519417600,519592960,519768256,519943424,520118528,520293568, + 520468480,520643328,520818112,520992800,521167392,521341888,521516320,521690656, + 521864896,522039072,522213152,522387168,522561056,522734912,522908640,523082304, + 523255872,523429376,523602784,523776096,523949312,524122464,524295552,524468512, + 524641440,524814240,524986976,525159616,525332192,525504640,525677056,525849344, + 526021568,526193728,526365792,526537760,526709632,526881440,527053152,527224800, + 527396352,527567840,527739200,527910528,528081728,528252864,528423936,528594880, + 528765760,528936576,529107296,529277920,529448480,529618944,529789344,529959648, + 530129856,530300000,530470048,530640000,530809888,530979712,531149440,531319072, + 531488608,531658080,531827488,531996800,532166016,532335168,532504224,532673184, + 532842080,533010912,533179616,533348288,533516832,533685312,533853728,534022048, + 534190272,534358432,534526496,534694496,534862400,535030240,535197984,535365632, + 535533216,535700704,535868128,536035456,536202720,536369888,536536992,536704000, + 536870912 +}; + +// Now where did these came from? +const byte gammatable[5][256] = +{ + { + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16, + 17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32, + 33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48, + 49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64, + 65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80, + 81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96, + 97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112, + 113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128, + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 + }, + + { + 2,4,5,7,8,10,11,12,14,15,16,18,19,20,21,23, + 24,25,26,27,29,30,31,32,33,34,36,37,38,39,40,41, + 42,44,45,46,47,48,49,50,51,52,54,55,56,57,58,59, + 60,61,62,63,64,65,66,67,69,70,71,72,73,74,75,76, + 77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92, + 93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108, + 109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124, + 125,126,127,128,129,129,130,131,132,133,134,135,136,137,138,139, + 140,141,142,143,144,145,146,147,148,148,149,150,151,152,153,154, + 155,156,157,158,159,160,161,162,163,163,164,165,166,167,168,169, + 170,171,172,173,174,175,175,176,177,178,179,180,181,182,183,184, + 185,186,186,187,188,189,190,191,192,193,194,195,196,196,197,198, + 199,200,201,202,203,204,205,205,206,207,208,209,210,211,212,213, + 214,214,215,216,217,218,219,220,221,222,222,223,224,225,226,227, + 228,229,230,230,231,232,233,234,235,236,237,237,238,239,240,241, + 242,243,244,245,245,246,247,248,249,250,251,252,252,253,254,255 + }, + + { + 4,7,9,11,13,15,17,19,21,22,24,26,27,29,30,32, + 33,35,36,38,39,40,42,43,45,46,47,48,50,51,52,54, + 55,56,57,59,60,61,62,63,65,66,67,68,69,70,72,73, + 74,75,76,77,78,79,80,82,83,84,85,86,87,88,89,90, + 91,92,93,94,95,96,97,98,100,101,102,103,104,105,106,107, + 108,109,110,111,112,113,114,114,115,116,117,118,119,120,121,122, + 123,124,125,126,127,128,129,130,131,132,133,133,134,135,136,137, + 138,139,140,141,142,143,144,144,145,146,147,148,149,150,151,152, + 153,153,154,155,156,157,158,159,160,160,161,162,163,164,165,166, + 166,167,168,169,170,171,172,172,173,174,175,176,177,178,178,179, + 180,181,182,183,183,184,185,186,187,188,188,189,190,191,192,193, + 193,194,195,196,197,197,198,199,200,201,201,202,203,204,205,206, + 206,207,208,209,210,210,211,212,213,213,214,215,216,217,217,218, + 219,220,221,221,222,223,224,224,225,226,227,228,228,229,230,231, + 231,232,233,234,235,235,236,237,238,238,239,240,241,241,242,243, + 244,244,245,246,247,247,248,249,250,251,251,252,253,254,254,255 + }, + + { + 8,12,16,19,22,24,27,29,31,34,36,38,40,41,43,45, + 47,49,50,52,53,55,57,58,60,61,63,64,65,67,68,70, + 71,72,74,75,76,77,79,80,81,82,84,85,86,87,88,90, + 91,92,93,94,95,96,98,99,100,101,102,103,104,105,106,107, + 108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123, + 124,125,126,127,128,129,130,131,132,133,134,135,135,136,137,138, + 139,140,141,142,143,143,144,145,146,147,148,149,150,150,151,152, + 153,154,155,155,156,157,158,159,160,160,161,162,163,164,165,165, + 166,167,168,169,169,170,171,172,173,173,174,175,176,176,177,178, + 179,180,180,181,182,183,183,184,185,186,186,187,188,189,189,190, + 191,192,192,193,194,195,195,196,197,197,198,199,200,200,201,202, + 202,203,204,205,205,206,207,207,208,209,210,210,211,212,212,213, + 214,214,215,216,216,217,218,219,219,220,221,221,222,223,223,224, + 225,225,226,227,227,228,229,229,230,231,231,232,233,233,234,235, + 235,236,237,237,238,238,239,240,240,241,242,242,243,244,244,245, + 246,246,247,247,248,249,249,250,251,251,252,253,253,254,254,255 + }, + + + { + 16,23,28,32,36,39,42,45,48,50,53,55,57,60,62,64, + 66,68,69,71,73,75,76,78,80,81,83,84,86,87,89,90, + 92,93,94,96,97,98,100,101,102,103,105,106,107,108,109,110, + 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,128, + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, + 143,144,145,146,147,148,149,150,150,151,152,153,154,155,155,156, + 157,158,159,159,160,161,162,163,163,164,165,166,166,167,168,169, + 169,170,171,172,172,173,174,175,175,176,177,177,178,179,180,180, + 181,182,182,183,184,184,185,186,187,187,188,189,189,190,191,191, + 192,193,193,194,195,195,196,196,197,198,198,199,200,200,201,202, + 202,203,203,204,205,205,206,207,207,208,208,209,210,210,211,211, + 212,213,213,214,214,215,216,216,217,217,218,219,219,220,220,221, + 221,222,223,223,224,224,225,225,226,227,227,228,228,229,229,230, + 230,231,232,232,233,233,234,234,235,235,236,236,237,237,238,239, + 239,240,240,241,241,242,242,243,243,244,244,245,245,246,246,247, + 247,248,248,249,249,250,250,251,251,252,252,253,254,254,255,255 + } +}; + diff --git a/firmware_p4/components/Applications/doom/tables.h b/firmware_p4/components/Applications/doom/tables.h new file mode 100644 index 000000000..495fd5349 --- /dev/null +++ b/firmware_p4/components/Applications/doom/tables.h @@ -0,0 +1,96 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Lookup tables. +// Do not try to look them up :-). +// In the order of appearance: +// +// int finetangent[4096] - Tangens LUT. +// Should work with BAM fairly well (12 of 16bit, +// effectively, by shifting). +// +// int finesine[10240] - Sine lookup. +// Guess what, serves as cosine, too. +// Remarkable thing is, how to use BAMs with this? +// +// int tantoangle[2049] - ArcTan LUT, +// maps tan(angle) to angle fast. Gotta search. +// + + +#ifndef __TABLES__ +#define __TABLES__ + +#include "doomtype.h" + +#include "m_fixed.h" + +#define FINEANGLES 8192 +#define FINEMASK (FINEANGLES-1) + + +// 0x100000000 to 0x2000 +#define ANGLETOFINESHIFT 19 + +// Effective size is 10240. +extern const fixed_t finesine[5*FINEANGLES/4]; + +// Re-use data, is just PI/2 pahse shift. +extern const fixed_t *finecosine; + + +// Effective size is 4096. +extern const fixed_t finetangent[FINEANGLES/2]; + +// Gamma correction tables. +extern const byte gammatable[5][256]; + +// Binary Angle Measument, BAM. + +#define ANG45 0x20000000 +#define ANG90 0x40000000 +#define ANG180 0x80000000 +#define ANG270 0xc0000000 +#define ANG_MAX 0xffffffff + +#define ANG1 (ANG45 / 45) +#define ANG60 (ANG180 / 3) + +// Heretic code uses this definition as though it represents one +// degree, but it is not! This is actually ~1.40 degrees. + +#define ANG1_X 0x01000000 + +#define SLOPERANGE 2048 +#define SLOPEBITS 11 +#define DBITS (FRACBITS-SLOPEBITS) + +typedef unsigned angle_t; + + +// Effective size is 2049; +// The +1 size is to handle the case when x==y +// without additional checking. +extern const angle_t tantoangle[SLOPERANGE+1]; + + +// Utility function, +// called by R_PointToAngle. +int SlopeDiv(unsigned int num, unsigned int den); + + +#endif + diff --git a/firmware_p4/components/Applications/doom/v_patch.h b/firmware_p4/components/Applications/doom/v_patch.h new file mode 100644 index 000000000..687dca10e --- /dev/null +++ b/firmware_p4/components/Applications/doom/v_patch.h @@ -0,0 +1,50 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Refresh/rendering module, shared data struct definitions. +// + + +#ifndef V_PATCH_H +#define V_PATCH_H + +// Patches. +// A patch holds one or more columns. +// Patches are used for sprites and all masked pictures, +// and we compose textures from the TEXTURE1/2 lists +// of patches. + +typedef struct +{ + short width; // bounding box size + short height; + short leftoffset; // pixels to the left of origin + short topoffset; // pixels below the origin + int columnofs[8]; // only [width] used + // the [0] is &columnofs[width] +} PACKEDATTR patch_t; + +// posts are runs of non masked source pixels +typedef struct +{ + byte topdelta; // -1 is the last post in a column + byte length; // length data bytes follows +} PACKEDATTR post_t; + +// column_t is a list of 0 or more post_t, (byte)-1 terminated +typedef post_t column_t; + +#endif + diff --git a/firmware_p4/components/Applications/doom/v_video.c b/firmware_p4/components/Applications/doom/v_video.c new file mode 100644 index 000000000..6db28aa5f --- /dev/null +++ b/firmware_p4/components/Applications/doom/v_video.c @@ -0,0 +1,932 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Gamma correction LUT stuff. +// Functions to draw patches (by post) directly to screen. +// Functions to blit a block to the screen. +// + +#include +#include +#include + +#include "i_system.h" + +#include "doomtype.h" + +#include "deh_str.h" +#include "i_swap.h" +#include "i_video.h" +#include "m_bbox.h" +#include "m_misc.h" +#include "v_video.h" +#include "w_wad.h" +#include "z_zone.h" + +#include "config.h" +#ifdef HAVE_LIBPNG +#include +#endif + +// TODO: There are separate RANGECHECK defines for different games, but this +// is common code. Fix this. +#define RANGECHECK + +// Blending table used for fuzzpatch, etc. +// Only used in Heretic/Hexen + +byte *tinttable = NULL; + +// villsa [STRIFE] Blending table used for Strife +byte *xlatab = NULL; + +// The screen buffer that the v_video.c code draws to. + +static byte *dest_screen = NULL; + +int dirtybox[4]; + +// haleyjd 08/28/10: clipping callback function for patches. +// This is needed for Chocolate Strife, which clips patches to the screen. +static vpatchclipfunc_t patchclip_callback = NULL; + +// +// V_MarkRect +// +void V_MarkRect(int x, int y, int width, int height) +{ + // If we are temporarily using an alternate screen, do not + // affect the update box. + + if (dest_screen == I_VideoBuffer) + { + M_AddToBox (dirtybox, x, y); + M_AddToBox (dirtybox, x + width-1, y + height-1); + } +} + + +// +// V_CopyRect +// +void V_CopyRect(int srcx, int srcy, byte *source, + int width, int height, + int destx, int desty) +{ + byte *src; + byte *dest; + +#ifdef RANGECHECK + if (srcx < 0 + || srcx + width > SCREENWIDTH + || srcy < 0 + || srcy + height > SCREENHEIGHT + || destx < 0 + || destx + width > SCREENWIDTH + || desty < 0 + || desty + height > SCREENHEIGHT) + { + I_Error ("Bad V_CopyRect"); + } +#endif + + V_MarkRect(destx, desty, width, height); + + src = source + SCREENWIDTH * srcy + srcx; + dest = dest_screen + SCREENWIDTH * desty + destx; + + for ( ; height>0 ; height--) + { + memcpy(dest, src, width); + src += SCREENWIDTH; + dest += SCREENWIDTH; + } +} + +// +// V_SetPatchClipCallback +// +// haleyjd 08/28/10: Added for Strife support. +// By calling this function, you can setup runtime error checking for patch +// clipping. Strife never caused errors by drawing patches partway off-screen. +// Some versions of vanilla DOOM also behaved differently than the default +// implementation, so this could possibly be extended to those as well for +// accurate emulation. +// +void V_SetPatchClipCallback(vpatchclipfunc_t func) +{ + patchclip_callback = func; +} + +// +// V_DrawPatch +// Masks a column based masked pic to the screen. +// + +void V_DrawPatch(int x, int y, patch_t *patch) +{ + int count; + int col; + column_t *column; + byte *desttop; + byte *dest; + byte *source; + int w; + + y -= SHORT(patch->topoffset); + x -= SHORT(patch->leftoffset); + + // haleyjd 08/28/10: Strife needs silent error checking here. + if(patchclip_callback) + { + if(!patchclip_callback(patch, x, y)) + return; + } + +#ifdef RANGECHECK + if (x < 0 + || x + SHORT(patch->width) > SCREENWIDTH + || y < 0 + || y + SHORT(patch->height) > SCREENHEIGHT) + { + I_Error("Bad V_DrawPatch x=%i y=%i patch.width=%i patch.height=%i topoffset=%i leftoffset=%i", x, y, patch->width, patch->height, patch->topoffset, patch->leftoffset); + } +#endif + + V_MarkRect(x, y, SHORT(patch->width), SHORT(patch->height)); + + col = 0; + desttop = dest_screen + y * SCREENWIDTH + x; + + w = SHORT(patch->width); + + for ( ; colcolumnofs[col])); + + // step through the posts in a column + while (column->topdelta != 0xff) + { + source = (byte *)column + 3; + dest = desttop + column->topdelta*SCREENWIDTH; + count = column->length; + + while (count--) + { + *dest = *source++; + dest += SCREENWIDTH; + } + column = (column_t *)((byte *)column + column->length + 4); + } + } +} + +// +// V_DrawPatchFlipped +// Masks a column based masked pic to the screen. +// Flips horizontally, e.g. to mirror face. +// + +void V_DrawPatchFlipped(int x, int y, patch_t *patch) +{ + int count; + int col; + column_t *column; + byte *desttop; + byte *dest; + byte *source; + int w; + + y -= SHORT(patch->topoffset); + x -= SHORT(patch->leftoffset); + + // haleyjd 08/28/10: Strife needs silent error checking here. + if(patchclip_callback) + { + if(!patchclip_callback(patch, x, y)) + return; + } + +#ifdef RANGECHECK + if (x < 0 + || x + SHORT(patch->width) > SCREENWIDTH + || y < 0 + || y + SHORT(patch->height) > SCREENHEIGHT) + { + I_Error("Bad V_DrawPatchFlipped"); + } +#endif + + V_MarkRect (x, y, SHORT(patch->width), SHORT(patch->height)); + + col = 0; + desttop = dest_screen + y * SCREENWIDTH + x; + + w = SHORT(patch->width); + + for ( ; colcolumnofs[w-1-col])); + + // step through the posts in a column + while (column->topdelta != 0xff ) + { + source = (byte *)column + 3; + dest = desttop + column->topdelta*SCREENWIDTH; + count = column->length; + + while (count--) + { + *dest = *source++; + dest += SCREENWIDTH; + } + column = (column_t *)((byte *)column + column->length + 4); + } + } +} + + + +// +// V_DrawPatchDirect +// Draws directly to the screen on the pc. +// + +void V_DrawPatchDirect(int x, int y, patch_t *patch) +{ + V_DrawPatch(x, y, patch); +} + +// +// V_DrawTLPatch +// +// Masks a column based translucent masked pic to the screen. +// + +void V_DrawTLPatch(int x, int y, patch_t * patch) +{ + int count, col; + column_t *column; + byte *desttop, *dest, *source; + int w; + + y -= SHORT(patch->topoffset); + x -= SHORT(patch->leftoffset); + + if (x < 0 + || x + SHORT(patch->width) > SCREENWIDTH + || y < 0 + || y + SHORT(patch->height) > SCREENHEIGHT) + { + I_Error("Bad V_DrawTLPatch"); + } + + col = 0; + desttop = dest_screen + y * SCREENWIDTH + x; + + w = SHORT(patch->width); + for (; col < w; x++, col++, desttop++) + { + column = (column_t *) ((byte *) patch + LONG(patch->columnofs[col])); + + // step through the posts in a column + + while (column->topdelta != 0xff) + { + source = (byte *) column + 3; + dest = desttop + column->topdelta * SCREENWIDTH; + count = column->length; + + while (count--) + { + *dest = tinttable[((*dest) << 8) + *source++]; + dest += SCREENWIDTH; + } + column = (column_t *) ((byte *) column + column->length + 4); + } + } +} + +// +// V_DrawXlaPatch +// +// villsa [STRIFE] Masks a column based translucent masked pic to the screen. +// + +void V_DrawXlaPatch(int x, int y, patch_t * patch) +{ + int count, col; + column_t *column; + byte *desttop, *dest, *source; + int w; + + y -= SHORT(patch->topoffset); + x -= SHORT(patch->leftoffset); + + if(patchclip_callback) + { + if(!patchclip_callback(patch, x, y)) + return; + } + + col = 0; + desttop = dest_screen + y * SCREENWIDTH + x; + + w = SHORT(patch->width); + for(; col < w; x++, col++, desttop++) + { + column = (column_t *) ((byte *) patch + LONG(patch->columnofs[col])); + + // step through the posts in a column + + while(column->topdelta != 0xff) + { + source = (byte *) column + 3; + dest = desttop + column->topdelta * SCREENWIDTH; + count = column->length; + + while(count--) + { + *dest = xlatab[*dest + ((*source) << 8)]; + source++; + dest += SCREENWIDTH; + } + column = (column_t *) ((byte *) column + column->length + 4); + } + } +} + +// +// V_DrawAltTLPatch +// +// Masks a column based translucent masked pic to the screen. +// + +void V_DrawAltTLPatch(int x, int y, patch_t * patch) +{ + int count, col; + column_t *column; + byte *desttop, *dest, *source; + int w; + + y -= SHORT(patch->topoffset); + x -= SHORT(patch->leftoffset); + + if (x < 0 + || x + SHORT(patch->width) > SCREENWIDTH + || y < 0 + || y + SHORT(patch->height) > SCREENHEIGHT) + { + I_Error("Bad V_DrawAltTLPatch"); + } + + col = 0; + desttop = dest_screen + y * SCREENWIDTH + x; + + w = SHORT(patch->width); + for (; col < w; x++, col++, desttop++) + { + column = (column_t *) ((byte *) patch + LONG(patch->columnofs[col])); + + // step through the posts in a column + + while (column->topdelta != 0xff) + { + source = (byte *) column + 3; + dest = desttop + column->topdelta * SCREENWIDTH; + count = column->length; + + while (count--) + { + *dest = tinttable[((*dest) << 8) + *source++]; + dest += SCREENWIDTH; + } + column = (column_t *) ((byte *) column + column->length + 4); + } + } +} + +// +// V_DrawShadowedPatch +// +// Masks a column based masked pic to the screen. +// + +void V_DrawShadowedPatch(int x, int y, patch_t *patch) +{ + int count, col; + column_t *column; + byte *desttop, *dest, *source; + byte *desttop2, *dest2; + int w; + + y -= SHORT(patch->topoffset); + x -= SHORT(patch->leftoffset); + + if (x < 0 + || x + SHORT(patch->width) > SCREENWIDTH + || y < 0 + || y + SHORT(patch->height) > SCREENHEIGHT) + { + I_Error("Bad V_DrawShadowedPatch"); + } + + col = 0; + desttop = dest_screen + y * SCREENWIDTH + x; + desttop2 = dest_screen + (y + 2) * SCREENWIDTH + x + 2; + + w = SHORT(patch->width); + for (; col < w; x++, col++, desttop++, desttop2++) + { + column = (column_t *) ((byte *) patch + LONG(patch->columnofs[col])); + + // step through the posts in a column + + while (column->topdelta != 0xff) + { + source = (byte *) column + 3; + dest = desttop + column->topdelta * SCREENWIDTH; + dest2 = desttop2 + column->topdelta * SCREENWIDTH; + count = column->length; + + while (count--) + { + *dest2 = tinttable[((*dest2) << 8)]; + dest2 += SCREENWIDTH; + *dest = *source++; + dest += SCREENWIDTH; + + } + column = (column_t *) ((byte *) column + column->length + 4); + } + } +} + +// +// Load tint table from TINTTAB lump. +// + +void V_LoadTintTable(void) +{ + tinttable = W_CacheLumpName("TINTTAB", PU_STATIC); +} + +// +// V_LoadXlaTable +// +// villsa [STRIFE] Load xla table from XLATAB lump. +// + +void V_LoadXlaTable(void) +{ + xlatab = W_CacheLumpName("XLATAB", PU_STATIC); +} + +// +// V_DrawBlock +// Draw a linear block of pixels into the view buffer. +// + +void V_DrawBlock(int x, int y, int width, int height, byte *src) +{ + byte *dest; + +#ifdef RANGECHECK + if (x < 0 + || x + width >SCREENWIDTH + || y < 0 + || y + height > SCREENHEIGHT) + { + I_Error ("Bad V_DrawBlock"); + } +#endif + + V_MarkRect (x, y, width, height); + + dest = dest_screen + y * SCREENWIDTH + x; + + while (height--) + { + memcpy (dest, src, width); + src += width; + dest += SCREENWIDTH; + } +} + +void V_DrawFilledBox(int x, int y, int w, int h, int c) +{ + uint8_t *buf, *buf1; + int x1, y1; + + buf = I_VideoBuffer + SCREENWIDTH * y + x; + + for (y1 = 0; y1 < h; ++y1) + { + buf1 = buf; + + for (x1 = 0; x1 < w; ++x1) + { + *buf1++ = c; + } + + buf += SCREENWIDTH; + } +} + +void V_DrawHorizLine(int x, int y, int w, int c) +{ + uint8_t *buf; + int x1; + + buf = I_VideoBuffer + SCREENWIDTH * y + x; + + for (x1 = 0; x1 < w; ++x1) + { + *buf++ = c; + } +} + +void V_DrawVertLine(int x, int y, int h, int c) +{ + uint8_t *buf; + int y1; + + buf = I_VideoBuffer + SCREENWIDTH * y + x; + + for (y1 = 0; y1 < h; ++y1) + { + *buf = c; + buf += SCREENWIDTH; + } +} + +void V_DrawBox(int x, int y, int w, int h, int c) +{ + V_DrawHorizLine(x, y, w, c); + V_DrawHorizLine(x, y+h-1, w, c); + V_DrawVertLine(x, y, h, c); + V_DrawVertLine(x+w-1, y, h, c); +} + +// +// Draw a "raw" screen (lump containing raw data to blit directly +// to the screen) +// + +void V_DrawRawScreen(byte *raw) +{ + memcpy(dest_screen, raw, SCREENWIDTH * SCREENHEIGHT); +} + +// +// V_Init +// +void V_Init (void) +{ + // no-op! + // There used to be separate screens that could be drawn to; these are + // now handled in the upper layers. +} + +// Set the buffer that the code draws to. + +void V_UseBuffer(byte *buffer) +{ + dest_screen = buffer; +} + +// Restore screen buffer to the i_video screen buffer. + +void V_RestoreBuffer(void) +{ + dest_screen = I_VideoBuffer; +} + +// +// SCREEN SHOTS +// + +typedef struct +{ + char manufacturer; + char version; + char encoding; + char bits_per_pixel; + + unsigned short xmin; + unsigned short ymin; + unsigned short xmax; + unsigned short ymax; + + unsigned short hres; + unsigned short vres; + + unsigned char palette[48]; + + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + + char filler[58]; + unsigned char data; // unbounded +} PACKEDATTR pcx_t; + + +// +// WritePCXfile +// + +void WritePCXfile(char *filename, byte *data, + int width, int height, + byte *palette) +{ + int i; + int length; + pcx_t* pcx; + byte* pack; + + pcx = Z_Malloc (width*height*2+1000, PU_STATIC, NULL); + + pcx->manufacturer = 0x0a; // PCX id + pcx->version = 5; // 256 color + pcx->encoding = 1; // uncompressed + pcx->bits_per_pixel = 8; // 256 color + pcx->xmin = 0; + pcx->ymin = 0; + pcx->xmax = SHORT(width-1); + pcx->ymax = SHORT(height-1); + pcx->hres = SHORT(width); + pcx->vres = SHORT(height); + memset (pcx->palette,0,sizeof(pcx->palette)); + pcx->color_planes = 1; // chunky image + pcx->bytes_per_line = SHORT(width); + pcx->palette_type = SHORT(2); // not a grey scale + memset (pcx->filler,0,sizeof(pcx->filler)); + + // pack the image + pack = &pcx->data; + + for (i=0 ; i MOUSE_SPEED_BOX_WIDTH - 1) + { + linelen = MOUSE_SPEED_BOX_WIDTH - 1; + } + + V_DrawHorizLine(box_x + 1, box_y + 4, MOUSE_SPEED_BOX_WIDTH - 2, black); + + if (linelen < redline_x) + { + V_DrawHorizLine(box_x + 1, box_y + MOUSE_SPEED_BOX_HEIGHT / 2, + linelen, white); + } + else + { + V_DrawHorizLine(box_x + 1, box_y + MOUSE_SPEED_BOX_HEIGHT / 2, + redline_x, white); + V_DrawHorizLine(box_x + redline_x, box_y + MOUSE_SPEED_BOX_HEIGHT / 2, + linelen - redline_x, yellow); + } + + // Draw red line + + V_DrawVertLine(box_x + redline_x, box_y + 1, + MOUSE_SPEED_BOX_HEIGHT - 2, red); +} + diff --git a/firmware_p4/components/Applications/doom/v_video.h b/firmware_p4/components/Applications/doom/v_video.h new file mode 100644 index 000000000..a970c7193 --- /dev/null +++ b/firmware_p4/components/Applications/doom/v_video.h @@ -0,0 +1,108 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Gamma correction LUT. +// Functions to draw patches (by post) directly to screen. +// Functions to blit a block to the screen. +// + + +#ifndef __V_VIDEO__ +#define __V_VIDEO__ + +#include "doomtype.h" + +// Needed because we are refering to patches. +#include "v_patch.h" + +// +// VIDEO +// + +#define CENTERY (SCREENHEIGHT/2) + + +extern int dirtybox[4]; + +extern byte *tinttable; + +// haleyjd 08/28/10: implemented for Strife support +// haleyjd 08/28/10: Patch clipping callback, implemented to support Choco +// Strife. +typedef boolean (*vpatchclipfunc_t)(patch_t *, int, int); +void V_SetPatchClipCallback(vpatchclipfunc_t func); + + +// Allocates buffer screens, call before R_Init. +void V_Init (void); + +// Draw a block from the specified source screen to the screen. + +void V_CopyRect(int srcx, int srcy, byte *source, + int width, int height, + int destx, int desty); + +void V_DrawPatch(int x, int y, patch_t *patch); +void V_DrawPatchFlipped(int x, int y, patch_t *patch); +void V_DrawTLPatch(int x, int y, patch_t *patch); +void V_DrawAltTLPatch(int x, int y, patch_t * patch); +void V_DrawShadowedPatch(int x, int y, patch_t *patch); +void V_DrawXlaPatch(int x, int y, patch_t * patch); // villsa [STRIFE] +void V_DrawPatchDirect(int x, int y, patch_t *patch); + +// Draw a linear block of pixels into the view buffer. + +void V_DrawBlock(int x, int y, int width, int height, byte *src); + +void V_MarkRect(int x, int y, int width, int height); + +void V_DrawFilledBox(int x, int y, int w, int h, int c); +void V_DrawHorizLine(int x, int y, int w, int c); +void V_DrawVertLine(int x, int y, int h, int c); +void V_DrawBox(int x, int y, int w, int h, int c); + +// Draw a raw screen lump + +void V_DrawRawScreen(byte *raw); + +// Temporarily switch to using a different buffer to draw graphics, etc. + +void V_UseBuffer(byte *buffer); + +// Return to using the normal screen buffer to draw graphics. + +void V_RestoreBuffer(void); + +// Save a screenshot of the current screen to a file, named in the +// format described in the string passed to the function, eg. +// "DOOM%02i.pcx" + +void V_ScreenShot(char *format); + +// Load the lookup table for translucency calculations from the TINTTAB +// lump. + +void V_LoadTintTable(void); + +// villsa [STRIFE] +// Load the lookup table for translucency calculations from the XLATAB +// lump. + +void V_LoadXlaTable(void); + +void V_DrawMouseSpeedBox(int speed); + +#endif + diff --git a/firmware_p4/components/Applications/doom/w_checksum.c b/firmware_p4/components/Applications/doom/w_checksum.c new file mode 100644 index 000000000..5933fdf9a --- /dev/null +++ b/firmware_p4/components/Applications/doom/w_checksum.c @@ -0,0 +1,87 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Generate a checksum of the WAD directory. +// + +#include +#include +#include + +#include "m_misc.h" +#include "sha1.h" +#include "w_checksum.h" +#include "w_wad.h" + +static wad_file_t **open_wadfiles = NULL; +static int num_open_wadfiles = 0; + +static int GetFileNumber(wad_file_t *handle) +{ + int i; + int result; + + for (i=0; iname, sizeof(buf)); + SHA1_UpdateString(sha1_context, buf); + SHA1_UpdateInt32(sha1_context, GetFileNumber(lump->wad_file)); + SHA1_UpdateInt32(sha1_context, lump->position); + SHA1_UpdateInt32(sha1_context, lump->size); +} + +void W_Checksum(sha1_digest_t digest) +{ + sha1_context_t sha1_context; + unsigned int i; + + SHA1_Init(&sha1_context); + + num_open_wadfiles = 0; + + // Go through each entry in the WAD directory, adding information + // about each entry to the SHA1 hash. + + for (i=0; i + +#include "config.h" + +#include "doomtype.h" +#include "m_argv.h" + +#include "w_file.h" + +extern wad_file_class_t stdc_wad_file; + +/* +#ifdef _WIN32 +extern wad_file_class_t win32_wad_file; +#endif +*/ + +#ifdef HAVE_MMAP +extern wad_file_class_t posix_wad_file; +#endif + +static wad_file_class_t *wad_file_classes[] = +{ +/* +#ifdef _WIN32 + &win32_wad_file, +#endif +*/ +#ifdef HAVE_MMAP + &posix_wad_file, +#endif + &stdc_wad_file, +}; + +wad_file_t *W_OpenFile(char *path) +{ + wad_file_t *result; + int i; + + //! + // Use the OS's virtual memory subsystem to map WAD files + // directly into memory. + // + + if (!M_CheckParm("-mmap")) + { + return stdc_wad_file.OpenFile(path); + } + + // Try all classes in order until we find one that works + + result = NULL; + + for (i = 0; i < arrlen(wad_file_classes); ++i) + { + result = wad_file_classes[i]->OpenFile(path); + + if (result != NULL) + { + break; + } + } + + return result; +} + +void W_CloseFile(wad_file_t *wad) +{ + wad->file_class->CloseFile(wad); +} + +size_t W_Read(wad_file_t *wad, unsigned int offset, + void *buffer, size_t buffer_len) +{ + return wad->file_class->Read(wad, offset, buffer, buffer_len); +} + diff --git a/firmware_p4/components/Applications/doom/w_file.h b/firmware_p4/components/Applications/doom/w_file.h new file mode 100644 index 000000000..f57781436 --- /dev/null +++ b/firmware_p4/components/Applications/doom/w_file.h @@ -0,0 +1,78 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// WAD I/O functions. +// + + +#ifndef __W_FILE__ +#define __W_FILE__ + +#include +#include "doomtype.h" + +typedef struct _wad_file_s wad_file_t; + +typedef struct +{ + // Open a file for reading. + + wad_file_t *(*OpenFile)(char *path); + + // Close the specified file. + + void (*CloseFile)(wad_file_t *file); + + // Read data from the specified position in the file into the + // provided buffer. Returns the number of bytes read. + + size_t (*Read)(wad_file_t *file, unsigned int offset, + void *buffer, size_t buffer_len); + +} wad_file_class_t; + +struct _wad_file_s +{ + // Class of this file. + + wad_file_class_t *file_class; + + // If this is NULL, the file cannot be mapped into memory. If this + // is non-NULL, it is a pointer to the mapped file. + + byte *mapped; + + // Length of the file, in bytes. + + unsigned int length; +}; + +// Open the specified file. Returns a pointer to a new wad_file_t +// handle for the WAD file, or NULL if it could not be opened. + +wad_file_t *W_OpenFile(char *path); + +// Close the specified WAD file. + +void W_CloseFile(wad_file_t *wad); + +// Read data from the specified file into the provided buffer. The +// data is read from the specified offset from the start of the file. +// Returns the number of bytes read. + +size_t W_Read(wad_file_t *wad, unsigned int offset, + void *buffer, size_t buffer_len); + +#endif /* #ifndef __W_FILE__ */ diff --git a/firmware_p4/components/Applications/doom/w_file_stdc.c b/firmware_p4/components/Applications/doom/w_file_stdc.c new file mode 100644 index 000000000..829e96037 --- /dev/null +++ b/firmware_p4/components/Applications/doom/w_file_stdc.c @@ -0,0 +1,96 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// WAD I/O functions. +// + +#include + +#include "m_misc.h" +#include "w_file.h" +#include "z_zone.h" + +typedef struct +{ + wad_file_t wad; + FILE *fstream; +} stdc_wad_file_t; + +extern wad_file_class_t stdc_wad_file; + +static wad_file_t *W_StdC_OpenFile(char *path) +{ + stdc_wad_file_t *result; + FILE *fstream; + + fstream = fopen(path, "rb"); + + if (fstream == NULL) + { + return NULL; + } + + // Create a new stdc_wad_file_t to hold the file handle. + + result = Z_Malloc(sizeof(stdc_wad_file_t), PU_STATIC, 0); + result->wad.file_class = &stdc_wad_file; + result->wad.mapped = NULL; + result->wad.length = M_FileLength(fstream); + result->fstream = fstream; + + return &result->wad; +} + +static void W_StdC_CloseFile(wad_file_t *wad) +{ + stdc_wad_file_t *stdc_wad; + + stdc_wad = (stdc_wad_file_t *) wad; + + fclose(stdc_wad->fstream); + Z_Free(stdc_wad); +} + +// Read data from the specified position in the file into the +// provided buffer. Returns the number of bytes read. + +size_t W_StdC_Read(wad_file_t *wad, unsigned int offset, + void *buffer, size_t buffer_len) +{ + stdc_wad_file_t *stdc_wad; + size_t result; + + stdc_wad = (stdc_wad_file_t *) wad; + + // Jump to the specified position in the file. + + fseek(stdc_wad->fstream, offset, SEEK_SET); + + // Read into the buffer. + + result = fread(buffer, 1, buffer_len, stdc_wad->fstream); + + return result; +} + + +wad_file_class_t stdc_wad_file = +{ + W_StdC_OpenFile, + W_StdC_CloseFile, + W_StdC_Read, +}; + + diff --git a/firmware_p4/components/Applications/doom/w_main.c b/firmware_p4/components/Applications/doom/w_main.c new file mode 100644 index 000000000..115f08116 --- /dev/null +++ b/firmware_p4/components/Applications/doom/w_main.c @@ -0,0 +1,198 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Common code to parse command line, identifying WAD files to load. +// + +#include "doomfeatures.h" +#include "d_iwad.h" +#include "m_argv.h" +#include "w_main.h" +#include "w_merge.h" +#include "w_wad.h" +#include "z_zone.h" + +// Parse the command line, merging WAD files that are sppecified. +// Returns true if at least one file was added. + +boolean W_ParseCommandLine(void) +{ + boolean modifiedgame = false; + int p; + +#ifdef FEATURE_WAD_MERGE + + // Merged PWADs are loaded first, because they are supposed to be + // modified IWADs. + + //! + // @arg + // @category mod + // + // Simulates the behavior of deutex's -merge option, merging a PWAD + // into the main IWAD. Multiple files may be specified. + // + + p = M_CheckParmWithArgs("-merge", 1); + + if (p > 0) + { + for (p = p + 1; p + // @category mod + // + // Simulates the behavior of NWT's -merge option. Multiple files + // may be specified. + + p = M_CheckParmWithArgs("-nwtmerge", 1); + + if (p > 0) + { + for (p = p + 1; p + // @category mod + // + // Simulates the behavior of NWT's -af option, merging flats into + // the main IWAD directory. Multiple files may be specified. + // + + p = M_CheckParmWithArgs("-af", 1); + + if (p > 0) + { + for (p = p + 1; p + // @category mod + // + // Simulates the behavior of NWT's -as option, merging sprites + // into the main IWAD directory. Multiple files may be specified. + // + + p = M_CheckParmWithArgs("-as", 1); + + if (p > 0) + { + for (p = p + 1; p + // @category mod + // + // Equivalent to "-af -as ". + // + + p = M_CheckParmWithArgs("-aa", 1); + + if (p > 0) + { + for (p = p + 1; p + // @vanilla + // + // Load the specified PWAD files. + // + + p = M_CheckParmWithArgs ("-file", 1); + if (p) + { + // the parms after p are wadfile/lump names, + // until end of parms or another - preceded parm + modifiedgame = true; // homebrew levels + while (++p != myargc && myargv[p][0] != '-') + { + char *filename; + + filename = D_TryFindWADByName(myargv[p]); + + printf(" adding %s\n", filename); + W_AddFile(filename); + } + } + +// W_PrintDirectory(); + + return modifiedgame; +} + diff --git a/firmware_p4/components/Applications/doom/w_main.h b/firmware_p4/components/Applications/doom/w_main.h new file mode 100644 index 000000000..2e39efc36 --- /dev/null +++ b/firmware_p4/components/Applications/doom/w_main.h @@ -0,0 +1,24 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Common code to parse command line, identifying WAD files to load. +// + +#ifndef W_MAIN_H +#define W_MAIN_H + +boolean W_ParseCommandLine(void); + +#endif /* #ifndef W_MAIN_H */ + diff --git a/firmware_p4/components/Applications/doom/w_merge.h b/firmware_p4/components/Applications/doom/w_merge.h new file mode 100644 index 000000000..c8ecc69f4 --- /dev/null +++ b/firmware_p4/components/Applications/doom/w_merge.h @@ -0,0 +1,44 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Handles merging of PWADs, similar to deutex's -merge option +// +// Ideally this should work exactly the same as in deutex, but trying to +// read the deutex source code made my brain hurt. +// + +#ifndef W_MERGE_H +#define W_MERGE_H + +#define W_NWT_MERGE_SPRITES 0x1 +#define W_NWT_MERGE_FLATS 0x2 + +// Add a new WAD and merge it into the main directory + +void W_MergeFile(char *filename); + +// NWT-style merging + +void W_NWTMergeFile(char *filename, int flags); + +// Acts the same as NWT's "-merge" option. + +void W_NWTDashMerge(char *filename); + +// Debug function that prints the WAD directory. + +void W_PrintDirectory(void); + +#endif /* #ifndef W_MERGE_H */ + diff --git a/firmware_p4/components/Applications/doom/w_wad.c b/firmware_p4/components/Applications/doom/w_wad.c new file mode 100644 index 000000000..124804639 --- /dev/null +++ b/firmware_p4/components/Applications/doom/w_wad.c @@ -0,0 +1,612 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Handles WAD file header, directory, lump I/O. +// + + + + +#include +#include +#include +#include + +#include "doomtype.h" + +#include "config.h" +#include "d_iwad.h" +#include "i_swap.h" +#include "i_system.h" +#include "i_video.h" +#include "m_misc.h" +#include "z_zone.h" + +#include "w_wad.h" + +typedef struct +{ + // Should be "IWAD" or "PWAD". + char identification[4]; + int numlumps; + int infotableofs; +} PACKEDATTR wadinfo_t; + + +typedef struct +{ + int filepos; + int size; + char name[8]; +} PACKEDATTR filelump_t; + +// +// GLOBALS +// + +// Location of each lump on disk. + +lumpinfo_t *lumpinfo; +unsigned int numlumps = 0; + +// Hash table for fast lookups + +static lumpinfo_t **lumphash; + +// Hash function used for lump names. + +unsigned int W_LumpNameHash(const char *s) +{ + // This is the djb2 string hash function, modded to work on strings + // that have a maximum length of 8. + + unsigned int result = 5381; + unsigned int i; + + for (i=0; i < 8 && s[i] != '\0'; ++i) + { + result = ((result << 5) ^ result ) ^ toupper((int)s[i]); + } + + return result; +} + +// Increase the size of the lumpinfo[] array to the specified size. +static void ExtendLumpInfo(int newnumlumps) +{ + lumpinfo_t *newlumpinfo; + unsigned int i; + + newlumpinfo = calloc(newnumlumps, sizeof(lumpinfo_t)); + + if (newlumpinfo == NULL) + { + I_Error ("Couldn't realloc lumpinfo"); + } + + // Copy over lumpinfo_t structures from the old array. If any of + // these lumps have been cached, we need to update the user + // pointers to the new location. + for (i = 0; i < numlumps && i < newnumlumps; ++i) + { + memcpy(&newlumpinfo[i], &lumpinfo[i], sizeof(lumpinfo_t)); + + if (newlumpinfo[i].cache != NULL) + { + Z_ChangeUser(newlumpinfo[i].cache, &newlumpinfo[i].cache); + } + + // We shouldn't be generating a hash table until after all WADs have + // been loaded, but just in case... + if (lumpinfo[i].next != NULL) + { + int nextlumpnum = lumpinfo[i].next - lumpinfo; + newlumpinfo[i].next = &newlumpinfo[nextlumpnum]; + } + } + + // All done. + free(lumpinfo); + lumpinfo = newlumpinfo; + numlumps = newnumlumps; +} + +// +// LUMP BASED ROUTINES. +// + +// +// W_AddFile +// All files are optional, but at least one file must be +// found (PWAD, if all required lumps are present). +// Files with a .wad extension are wadlink files +// with multiple lumps. +// Other files are single lumps with the base filename +// for the lump name. + +wad_file_t *W_AddFile (char *filename) +{ + wadinfo_t header; + lumpinfo_t *lump_p; + unsigned int i; + wad_file_t *wad_file; + int length; + int startlump; + filelump_t *fileinfo; + filelump_t *filerover; + int newnumlumps; + + // open the file and add to directory + + wad_file = W_OpenFile(filename); + + if (wad_file == NULL) + { + printf (" couldn't open %s\n", filename); + return NULL; + } + + newnumlumps = numlumps; + + if (strcasecmp(filename+strlen(filename)-3 , "wad" ) ) + { + // single lump file + + // fraggle: Swap the filepos and size here. The WAD directory + // parsing code expects a little-endian directory, so will swap + // them back. Effectively we're constructing a "fake WAD directory" + // here, as it would appear on disk. + + fileinfo = Z_Malloc(sizeof(filelump_t), PU_STATIC, 0); + fileinfo->filepos = LONG(0); + fileinfo->size = LONG(wad_file->length); + + // Name the lump after the base of the filename (without the + // extension). + + M_ExtractFileBase (filename, fileinfo->name); + newnumlumps++; + } + else + { + // WAD file + W_Read(wad_file, 0, &header, sizeof(header)); + + if (strncmp(header.identification,"IWAD",4)) + { + // Homebrew levels? + if (strncmp(header.identification,"PWAD",4)) + { + I_Error ("Wad file %s doesn't have IWAD " + "or PWAD id\n", filename); + } + + // ???modifiedgame = true; + } + + header.numlumps = LONG(header.numlumps); + header.infotableofs = LONG(header.infotableofs); + length = header.numlumps*sizeof(filelump_t); + fileinfo = Z_Malloc(length, PU_STATIC, 0); + + W_Read(wad_file, header.infotableofs, fileinfo, length); + newnumlumps += header.numlumps; + } + + // Increase size of numlumps array to accomodate the new file. + startlump = numlumps; + ExtendLumpInfo(newnumlumps); + + lump_p = &lumpinfo[startlump]; + + filerover = fileinfo; + + for (i=startlump; iwad_file = wad_file; + lump_p->position = LONG(filerover->filepos); + lump_p->size = LONG(filerover->size); + lump_p->cache = NULL; + strncpy(lump_p->name, filerover->name, 8); + + ++lump_p; + ++filerover; + } + + Z_Free(fileinfo); + + if (lumphash != NULL) + { + Z_Free(lumphash); + lumphash = NULL; + } + + return wad_file; +} + + + +// +// W_NumLumps +// +int W_NumLumps (void) +{ + return numlumps; +} + + + +// +// W_CheckNumForName +// Returns -1 if name not found. +// + +int W_CheckNumForName (char* name) +{ + lumpinfo_t *lump_p; + int i; + + // Do we have a hash table yet? + + if (lumphash != NULL) + { + int hash; + + // We do! Excellent. + + hash = W_LumpNameHash(name) % numlumps; + + for (lump_p = lumphash[hash]; lump_p != NULL; lump_p = lump_p->next) + { + if (!strncasecmp(lump_p->name, name, 8)) + { + return lump_p - lumpinfo; + } + } + } + else + { + // We don't have a hash table generate yet. Linear search :-( + // + // scan backwards so patch lump files take precedence + + for (i=numlumps-1; i >= 0; --i) + { + if (!strncasecmp(lumpinfo[i].name, name, 8)) + { + return i; + } + } + } + + // TFB. Not found. + + return -1; +} + + + + +// +// W_GetNumForName +// Calls W_CheckNumForName, but bombs out if not found. +// +int W_GetNumForName (char* name) +{ + int i; + + i = W_CheckNumForName (name); + + if (i < 0) + { + I_Error ("W_GetNumForName: %s not found!", name); + } + + return i; +} + + +// +// W_LumpLength +// Returns the buffer size needed to load the given lump. +// +int W_LumpLength (unsigned int lump) +{ + if (lump >= numlumps) + { + I_Error ("W_LumpLength: %i >= numlumps", lump); + } + + return lumpinfo[lump].size; +} + + + +// +// W_ReadLump +// Loads the lump into the given buffer, +// which must be >= W_LumpLength(). +// +void W_ReadLump(unsigned int lump, void *dest) +{ + int c; + lumpinfo_t *l; + + if (lump >= numlumps) + { + I_Error ("W_ReadLump: %i >= numlumps", lump); + } + + l = lumpinfo+lump; + + I_BeginRead (); + + c = W_Read(l->wad_file, l->position, dest, l->size); + + if (c < l->size) + { + I_Error ("W_ReadLump: only read %i of %i on lump %i", + c, l->size, lump); + } + + I_EndRead (); +} + + + + +// +// W_CacheLumpNum +// +// Load a lump into memory and return a pointer to a buffer containing +// the lump data. +// +// 'tag' is the type of zone memory buffer to allocate for the lump +// (usually PU_STATIC or PU_CACHE). If the lump is loaded as +// PU_STATIC, it should be released back using W_ReleaseLumpNum +// when no longer needed (do not use Z_ChangeTag). +// + +void *W_CacheLumpNum(int lumpnum, int tag) +{ + byte *result; + lumpinfo_t *lump; + + if ((unsigned)lumpnum >= numlumps) + { + I_Error ("W_CacheLumpNum: %i >= numlumps", lumpnum); + } + + lump = &lumpinfo[lumpnum]; + + // Get the pointer to return. If the lump is in a memory-mapped + // file, we can just return a pointer to within the memory-mapped + // region. If the lump is in an ordinary file, we may already + // have it cached; otherwise, load it into memory. + + if (lump->wad_file->mapped != NULL) + { + // Memory mapped file, return from the mmapped region. + + result = lump->wad_file->mapped + lump->position; + } + else if (lump->cache != NULL) + { + // Already cached, so just switch the zone tag. + + result = lump->cache; + Z_ChangeTag(lump->cache, tag); + } + else + { + // Not yet loaded, so load it now + + lump->cache = Z_Malloc(W_LumpLength(lumpnum), tag, &lump->cache); + W_ReadLump (lumpnum, lump->cache); + result = lump->cache; + } + + return result; +} + + + +// +// W_CacheLumpName +// +void *W_CacheLumpName(char *name, int tag) +{ + return W_CacheLumpNum(W_GetNumForName(name), tag); +} + +// +// Release a lump back to the cache, so that it can be reused later +// without having to read from disk again, or alternatively, discarded +// if we run out of memory. +// +// Back in Vanilla Doom, this was just done using Z_ChangeTag +// directly, but now that we have WAD mmap, things are a bit more +// complicated ... +// + +void W_ReleaseLumpNum(int lumpnum) +{ + lumpinfo_t *lump; + + if ((unsigned)lumpnum >= numlumps) + { + I_Error ("W_ReleaseLumpNum: %i >= numlumps", lumpnum); + } + + lump = &lumpinfo[lumpnum]; + + if (lump->wad_file->mapped != NULL) + { + // Memory-mapped file, so nothing needs to be done here. + } + else + { + Z_ChangeTag(lump->cache, PU_CACHE); + } +} + +void W_ReleaseLumpName(char *name) +{ + W_ReleaseLumpNum(W_GetNumForName(name)); +} + +#if 0 + +// +// W_Profile +// +int info[2500][10]; +int profilecount; + +void W_Profile (void) +{ + int i; + memblock_t* block; + void* ptr; + char ch; + FILE* f; + int j; + char name[9]; + + + for (i=0 ; itag < PU_PURGELEVEL) + ch = 'S'; + else + ch = 'P'; + } + info[i][profilecount] = ch; + } + profilecount++; +#if ORIGCODE + f = fopen ("waddump.txt","w"); + name[8] = 0; + + for (i=0 ; i 0) + { + lumphash = Z_Malloc(sizeof(lumpinfo_t *) * numlumps, PU_STATIC, NULL); + memset(lumphash, 0, sizeof(lumpinfo_t *) * numlumps); + + for (i=0; i= 0) + { + I_Error("\nYou are trying to use a %s IWAD file with " + "the %s%s binary.\nThis isn't going to work.\n" + "You probably want to use the %s%s binary.", + D_SuggestGameName(unique_lumps[i].mission, + indetermined), + PROGRAM_PREFIX, + D_GameMissionString(mission), + PROGRAM_PREFIX, + D_GameMissionString(unique_lumps[i].mission)); + } + } + } +} + diff --git a/firmware_p4/components/Applications/doom/w_wad.h b/firmware_p4/components/Applications/doom/w_wad.h new file mode 100644 index 000000000..718957499 --- /dev/null +++ b/firmware_p4/components/Applications/doom/w_wad.h @@ -0,0 +1,78 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// WAD I/O functions. +// + + +#ifndef __W_WAD__ +#define __W_WAD__ + +#include + +#include "doomtype.h" +#include "d_mode.h" + +#include "w_file.h" + + +// +// TYPES +// + +// +// WADFILE I/O related stuff. +// + +typedef struct lumpinfo_s lumpinfo_t; + +struct lumpinfo_s +{ + char name[8]; + wad_file_t *wad_file; + int position; + int size; + void *cache; + + // Used for hash table lookups + + lumpinfo_t *next; +}; + + +extern lumpinfo_t *lumpinfo; +extern unsigned int numlumps; + +wad_file_t *W_AddFile (char *filename); + +int W_CheckNumForName (char* name); +int W_GetNumForName (char* name); + +int W_LumpLength (unsigned int lump); +void W_ReadLump (unsigned int lump, void *dest); + +void* W_CacheLumpNum (int lump, int tag); +void* W_CacheLumpName (char* name, int tag); + +void W_GenerateHashTable(void); + +extern unsigned int W_LumpNameHash(const char *s); + +void W_ReleaseLumpNum(int lump); +void W_ReleaseLumpName(char *name); + +void W_CheckCorrectIWAD(GameMission_t mission); + +#endif diff --git a/firmware_p4/components/Applications/doom/wi_stuff.c b/firmware_p4/components/Applications/doom/wi_stuff.c new file mode 100644 index 000000000..ddb9a66c1 --- /dev/null +++ b/firmware_p4/components/Applications/doom/wi_stuff.c @@ -0,0 +1,1829 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Intermission screens. +// + + +#include + +#include "z_zone.h" + +#include "m_misc.h" +#include "m_random.h" + +#include "deh_main.h" +#include "i_swap.h" +#include "i_system.h" + +#include "w_wad.h" + +#include "g_game.h" + +#include "r_local.h" +#include "s_sound.h" + +#include "doomstat.h" + +// Data. +#include "sounds.h" + +// Needs access to LFB. +#include "v_video.h" + +#include "wi_stuff.h" + +// +// Data needed to add patches to full screen intermission pics. +// Patches are statistics messages, and animations. +// Loads of by-pixel layout and placement, offsets etc. +// + + +// +// Different vetween registered DOOM (1994) and +// Ultimate DOOM - Final edition (retail, 1995?). +// This is supposedly ignored for commercial +// release (aka DOOM II), which had 34 maps +// in one episode. So there. +#define NUMEPISODES 4 +#define NUMMAPS 9 + + +// in tics +//U #define PAUSELEN (TICRATE*2) +//U #define SCORESTEP 100 +//U #define ANIMPERIOD 32 +// pixel distance from "(YOU)" to "PLAYER N" +//U #define STARDIST 10 +//U #define WK 1 + + +// GLOBAL LOCATIONS +#define WI_TITLEY 2 +#define WI_SPACINGY 33 + +// SINGPLE-PLAYER STUFF +#define SP_STATSX 50 +#define SP_STATSY 50 + +#define SP_TIMEX 16 +#define SP_TIMEY (SCREENHEIGHT-32) + + +// NET GAME STUFF +#define NG_STATSY 50 +#define NG_STATSX (32 + SHORT(star->width)/2 + 32*!dofrags) + +#define NG_SPACINGX 64 + + +// DEATHMATCH STUFF +#define DM_MATRIXX 42 +#define DM_MATRIXY 68 + +#define DM_SPACINGX 40 + +#define DM_TOTALSX 269 + +#define DM_KILLERSX 10 +#define DM_KILLERSY 100 +#define DM_VICTIMSX 5 +#define DM_VICTIMSY 50 + + + + +typedef enum +{ + ANIM_ALWAYS, + ANIM_RANDOM, + ANIM_LEVEL + +} animenum_t; + +typedef struct +{ + int x; + int y; + +} point_t; + + +// +// Animation. +// There is another anim_t used in p_spec. +// +typedef struct +{ + animenum_t type; + + // period in tics between animations + int period; + + // number of animation frames + int nanims; + + // location of animation + point_t loc; + + // ALWAYS: n/a, + // RANDOM: period deviation (<256), + // LEVEL: level + int data1; + + // ALWAYS: n/a, + // RANDOM: random base period, + // LEVEL: n/a + int data2; + + // actual graphics for frames of animations + patch_t* p[3]; + + // following must be initialized to zero before use! + + // next value of bcnt (used in conjunction with period) + int nexttic; + + // last drawn animation frame + int lastdrawn; + + // next frame number to animate + int ctr; + + // used by RANDOM and LEVEL when animating + int state; + +} anim_t; + + +static point_t lnodes[NUMEPISODES][NUMMAPS] = +{ + // Episode 0 World Map + { + { 185, 164 }, // location of level 0 (CJ) + { 148, 143 }, // location of level 1 (CJ) + { 69, 122 }, // location of level 2 (CJ) + { 209, 102 }, // location of level 3 (CJ) + { 116, 89 }, // location of level 4 (CJ) + { 166, 55 }, // location of level 5 (CJ) + { 71, 56 }, // location of level 6 (CJ) + { 135, 29 }, // location of level 7 (CJ) + { 71, 24 } // location of level 8 (CJ) + }, + + // Episode 1 World Map should go here + { + { 254, 25 }, // location of level 0 (CJ) + { 97, 50 }, // location of level 1 (CJ) + { 188, 64 }, // location of level 2 (CJ) + { 128, 78 }, // location of level 3 (CJ) + { 214, 92 }, // location of level 4 (CJ) + { 133, 130 }, // location of level 5 (CJ) + { 208, 136 }, // location of level 6 (CJ) + { 148, 140 }, // location of level 7 (CJ) + { 235, 158 } // location of level 8 (CJ) + }, + + // Episode 2 World Map should go here + { + { 156, 168 }, // location of level 0 (CJ) + { 48, 154 }, // location of level 1 (CJ) + { 174, 95 }, // location of level 2 (CJ) + { 265, 75 }, // location of level 3 (CJ) + { 130, 48 }, // location of level 4 (CJ) + { 279, 23 }, // location of level 5 (CJ) + { 198, 48 }, // location of level 6 (CJ) + { 140, 25 }, // location of level 7 (CJ) + { 281, 136 } // location of level 8 (CJ) + } + +}; + + +// +// Animation locations for episode 0 (1). +// Using patches saves a lot of space, +// as they replace 320x200 full screen frames. +// + +#define ANIM(type, period, nanims, x, y, nexttic) \ + { (type), (period), (nanims), { (x), (y) }, (nexttic), \ + 0, { NULL, NULL, NULL }, 0, 0, 0, 0 } + + +static anim_t epsd0animinfo[] = +{ + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 224, 104, 0), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 184, 160, 0), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 112, 136, 0), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 72, 112, 0), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 88, 96, 0), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 64, 48, 0), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 192, 40, 0), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 136, 16, 0), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 80, 16, 0), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 64, 24, 0), +}; + +static anim_t epsd1animinfo[] = +{ + ANIM(ANIM_LEVEL, TICRATE/3, 1, 128, 136, 1), + ANIM(ANIM_LEVEL, TICRATE/3, 1, 128, 136, 2), + ANIM(ANIM_LEVEL, TICRATE/3, 1, 128, 136, 3), + ANIM(ANIM_LEVEL, TICRATE/3, 1, 128, 136, 4), + ANIM(ANIM_LEVEL, TICRATE/3, 1, 128, 136, 5), + ANIM(ANIM_LEVEL, TICRATE/3, 1, 128, 136, 6), + ANIM(ANIM_LEVEL, TICRATE/3, 1, 128, 136, 7), + ANIM(ANIM_LEVEL, TICRATE/3, 3, 192, 144, 8), + ANIM(ANIM_LEVEL, TICRATE/3, 1, 128, 136, 8), +}; + +static anim_t epsd2animinfo[] = +{ + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 104, 168, 0), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 40, 136, 0), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 160, 96, 0), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 104, 80, 0), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 120, 32, 0), + ANIM(ANIM_ALWAYS, TICRATE/4, 3, 40, 0, 0), +}; + +static int NUMANIMS[NUMEPISODES] = +{ + arrlen(epsd0animinfo), + arrlen(epsd1animinfo), + arrlen(epsd2animinfo), +}; + +static anim_t *anims[NUMEPISODES] = +{ + epsd0animinfo, + epsd1animinfo, + epsd2animinfo +}; + + +// +// GENERAL DATA +// + +// +// Locally used stuff. +// + +// States for single-player +#define SP_KILLS 0 +#define SP_ITEMS 2 +#define SP_SECRET 4 +#define SP_FRAGS 6 +#define SP_TIME 8 +#define SP_PAR ST_TIME + +#define SP_PAUSE 1 + +// in seconds +#define SHOWNEXTLOCDELAY 4 +//#define SHOWLASTLOCDELAY SHOWNEXTLOCDELAY + + +// used to accelerate or skip a stage +static int acceleratestage; + +// wbs->pnum +static int me; + + // specifies current state +static stateenum_t state; + +// contains information passed into intermission +static wbstartstruct_t* wbs; + +static wbplayerstruct_t* plrs; // wbs->plyr[] + +// used for general timing +static int cnt; + +// used for timing of background animation +static int bcnt; + +// signals to refresh everything for one frame +static int firstrefresh; + +static int cnt_kills[MAXPLAYERS]; +static int cnt_items[MAXPLAYERS]; +static int cnt_secret[MAXPLAYERS]; +static int cnt_time; +static int cnt_par; +static int cnt_pause; + +// # of commercial levels +static int NUMCMAPS; + + +// +// GRAPHICS +// + +// You Are Here graphic +static patch_t* yah[3] = { NULL, NULL, NULL }; + +// splat +static patch_t* splat[2] = { NULL, NULL }; + +// %, : graphics +static patch_t* percent; +static patch_t* colon; + +// 0-9 graphic +static patch_t* num[10]; + +// minus sign +static patch_t* wiminus; + +// "Finished!" graphics +static patch_t* finished; + +// "Entering" graphic +static patch_t* entering; + +// "secret" +static patch_t* sp_secret; + + // "Kills", "Scrt", "Items", "Frags" +static patch_t* kills; +static patch_t* secret; +static patch_t* items; +static patch_t* frags; + +// Time sucks. +static patch_t* timepatch; +static patch_t* par; +static patch_t* sucks; + +// "killers", "victims" +static patch_t* killers; +static patch_t* victims; + +// "Total", your face, your dead face +static patch_t* total; +static patch_t* star; +static patch_t* bstar; + +// "red P[1..MAXPLAYERS]" +static patch_t* p[MAXPLAYERS]; + +// "gray P[1..MAXPLAYERS]" +static patch_t* bp[MAXPLAYERS]; + + // Name graphics of each level (centered) +static patch_t** lnames; + +// Buffer storing the backdrop +static patch_t *background; + +// +// CODE +// + +// slam background +void WI_slamBackground(void) +{ + V_DrawPatch(0, 0, background); +} + +// The ticker is used to detect keys +// because of timing issues in netgames. +boolean WI_Responder(event_t* ev) +{ + return false; +} + + +// Draws " Finished!" +void WI_drawLF(void) +{ + int y = WI_TITLEY; + + if (gamemode != commercial || wbs->last < NUMCMAPS) + { + // draw + V_DrawPatch((SCREENWIDTH - SHORT(lnames[wbs->last]->width))/2, + y, lnames[wbs->last]); + + // draw "Finished!" + y += (5*SHORT(lnames[wbs->last]->height))/4; + + V_DrawPatch((SCREENWIDTH - SHORT(finished->width)) / 2, y, finished); + } + else if (wbs->last == NUMCMAPS) + { + // MAP33 - nothing is displayed! + } + else if (wbs->last > NUMCMAPS) + { + // > MAP33. Doom bombs out here with a Bad V_DrawPatch error. + // I'm pretty sure that doom2.exe is just reading into random + // bits of memory at this point, but let's try to be accurate + // anyway. This deliberately triggers a V_DrawPatch error. + + patch_t tmp = { SCREENWIDTH, SCREENHEIGHT, 1, 1, + { 0, 0, 0, 0, 0, 0, 0, 0 } }; + + V_DrawPatch(0, y, &tmp); + } +} + + + +// Draws "Entering " +void WI_drawEL(void) +{ + int y = WI_TITLEY; + + // draw "Entering" + V_DrawPatch((SCREENWIDTH - SHORT(entering->width))/2, + y, + entering); + + // draw level + y += (5*SHORT(lnames[wbs->next]->height))/4; + + V_DrawPatch((SCREENWIDTH - SHORT(lnames[wbs->next]->width))/2, + y, + lnames[wbs->next]); + +} + +void +WI_drawOnLnode +( int n, + patch_t* c[] ) +{ + + int i; + int left; + int top; + int right; + int bottom; + boolean fits = false; + + i = 0; + do + { + left = lnodes[wbs->epsd][n].x - SHORT(c[i]->leftoffset); + top = lnodes[wbs->epsd][n].y - SHORT(c[i]->topoffset); + right = left + SHORT(c[i]->width); + bottom = top + SHORT(c[i]->height); + + if (left >= 0 + && right < SCREENWIDTH + && top >= 0 + && bottom < SCREENHEIGHT) + { + fits = true; + } + else + { + i++; + } + } while (!fits && i!=2 && c[i] != NULL); + + if (fits && i<2) + { + V_DrawPatch(lnodes[wbs->epsd][n].x, + lnodes[wbs->epsd][n].y, + c[i]); + } + else + { + // DEBUG + printf("Could not place patch on level %d", n+1); + } +} + + + +void WI_initAnimatedBack(void) +{ + int i; + anim_t* a; + + if (gamemode == commercial) + return; + + if (wbs->epsd > 2) + return; + + for (i=0;iepsd];i++) + { + a = &anims[wbs->epsd][i]; + + // init variables + a->ctr = -1; + + // specify the next time to draw it + if (a->type == ANIM_ALWAYS) + a->nexttic = bcnt + 1 + (M_Random()%a->period); + else if (a->type == ANIM_RANDOM) + a->nexttic = bcnt + 1 + a->data2+(M_Random()%a->data1); + else if (a->type == ANIM_LEVEL) + a->nexttic = bcnt + 1; + } + +} + +void WI_updateAnimatedBack(void) +{ + int i; + anim_t* a; + + if (gamemode == commercial) + return; + + if (wbs->epsd > 2) + return; + + for (i=0;iepsd];i++) + { + a = &anims[wbs->epsd][i]; + + if (bcnt == a->nexttic) + { + switch (a->type) + { + case ANIM_ALWAYS: + if (++a->ctr >= a->nanims) a->ctr = 0; + a->nexttic = bcnt + a->period; + break; + + case ANIM_RANDOM: + a->ctr++; + if (a->ctr == a->nanims) + { + a->ctr = -1; + a->nexttic = bcnt+a->data2+(M_Random()%a->data1); + } + else a->nexttic = bcnt + a->period; + break; + + case ANIM_LEVEL: + // gawd-awful hack for level anims + if (!(state == StatCount && i == 7) + && wbs->next == a->data1) + { + a->ctr++; + if (a->ctr == a->nanims) a->ctr--; + a->nexttic = bcnt + a->period; + } + break; + } + } + + } + +} + +void WI_drawAnimatedBack(void) +{ + int i; + anim_t* a; + + if (gamemode == commercial) + return; + + if (wbs->epsd > 2) + return; + + for (i=0 ; iepsd] ; i++) + { + a = &anims[wbs->epsd][i]; + + if (a->ctr >= 0) + V_DrawPatch(a->loc.x, a->loc.y, a->p[a->ctr]); + } + +} + +// +// Draws a number. +// If digits > 0, then use that many digits minimum, +// otherwise only use as many as necessary. +// Returns new x position. +// + +int +WI_drawNum +( int x, + int y, + int n, + int digits ) +{ + + int fontwidth = SHORT(num[0]->width); + int neg; + int temp; + + if (digits < 0) + { + if (!n) + { + // make variable-length zeros 1 digit long + digits = 1; + } + else + { + // figure out # of digits in # + digits = 0; + temp = n; + + while (temp) + { + temp /= 10; + digits++; + } + } + } + + neg = n < 0; + if (neg) + n = -n; + + // if non-number, do not draw it + if (n == 1994) + return 0; + + // draw the new number + while (digits--) + { + x -= fontwidth; + V_DrawPatch(x, y, num[ n % 10 ]); + n /= 10; + } + + // draw a minus sign if necessary + if (neg) + V_DrawPatch(x-=8, y, wiminus); + + return x; + +} + +void +WI_drawPercent +( int x, + int y, + int p ) +{ + if (p < 0) + return; + + V_DrawPatch(x, y, percent); + WI_drawNum(x, y, p, -1); +} + + + +// +// Display level completion time and par, +// or "sucks" message if overflow. +// +void +WI_drawTime +( int x, + int y, + int t ) +{ + + int div; + int n; + + if (t<0) + return; + + if (t <= 61*59) + { + div = 1; + + do + { + n = (t / div) % 60; + x = WI_drawNum(x, y, n, 2) - SHORT(colon->width); + div *= 60; + + // draw + if (div==60 || t / div) + V_DrawPatch(x, y, colon); + + } while (t / div); + } + else + { + // "sucks" + V_DrawPatch(x - SHORT(sucks->width), y, sucks); + } +} + + +void WI_End(void) +{ + void WI_unloadData(void); + WI_unloadData(); +} + +void WI_initNoState(void) +{ + state = NoState; + acceleratestage = 0; + cnt = 10; +} + +void WI_updateNoState(void) { + + WI_updateAnimatedBack(); + + if (!--cnt) + { + // Don't call WI_End yet. G_WorldDone doesnt immediately + // change gamestate, so WI_Drawer is still going to get + // run until that happens. If we do that after WI_End + // (which unloads all the graphics), we're in trouble. + //WI_End(); + G_WorldDone(); + } + +} + +static boolean snl_pointeron = false; + + +void WI_initShowNextLoc(void) +{ + state = ShowNextLoc; + acceleratestage = 0; + cnt = SHOWNEXTLOCDELAY * TICRATE; + + WI_initAnimatedBack(); +} + +void WI_updateShowNextLoc(void) +{ + WI_updateAnimatedBack(); + + if (!--cnt || acceleratestage) + WI_initNoState(); + else + snl_pointeron = (cnt & 31) < 20; +} + +void WI_drawShowNextLoc(void) +{ + + int i; + int last; + + WI_slamBackground(); + + // draw animated background + WI_drawAnimatedBack(); + + if ( gamemode != commercial) + { + if (wbs->epsd > 2) + { + WI_drawEL(); + return; + } + + last = (wbs->last == 8) ? wbs->next - 1 : wbs->last; + + // draw a splat on taken cities. + for (i=0 ; i<=last ; i++) + WI_drawOnLnode(i, splat); + + // splat the secret level? + if (wbs->didsecret) + WI_drawOnLnode(8, splat); + + // draw flashing ptr + if (snl_pointeron) + WI_drawOnLnode(wbs->next, yah); + } + + // draws which level you are entering.. + if ( (gamemode != commercial) + || wbs->next != 30) + WI_drawEL(); + +} + +void WI_drawNoState(void) +{ + snl_pointeron = true; + WI_drawShowNextLoc(); +} + +int WI_fragSum(int playernum) +{ + int i; + int frags = 0; + + for (i=0 ; i 99) + dm_frags[i][j] = 99; + + if (dm_frags[i][j] < -99) + dm_frags[i][j] = -99; + + stillticking = true; + } + } + dm_totals[i] = WI_fragSum(i); + + if (dm_totals[i] > 99) + dm_totals[i] = 99; + + if (dm_totals[i] < -99) + dm_totals[i] = -99; + } + + } + if (!stillticking) + { + S_StartSound(0, sfx_barexp); + dm_state++; + } + + } + else if (dm_state == 4) + { + if (acceleratestage) + { + S_StartSound(0, sfx_slop); + + if ( gamemode == commercial) + WI_initNoState(); + else + WI_initShowNextLoc(); + } + } + else if (dm_state & 1) + { + if (!--cnt_pause) + { + dm_state++; + cnt_pause = TICRATE; + } + } +} + + + +void WI_drawDeathmatchStats(void) +{ + + int i; + int j; + int x; + int y; + int w; + + WI_slamBackground(); + + // draw animated background + WI_drawAnimatedBack(); + WI_drawLF(); + + // draw stat titles (top line) + V_DrawPatch(DM_TOTALSX-SHORT(total->width)/2, + DM_MATRIXY-WI_SPACINGY+10, + total); + + V_DrawPatch(DM_KILLERSX, DM_KILLERSY, killers); + V_DrawPatch(DM_VICTIMSX, DM_VICTIMSY, victims); + + // draw P? + x = DM_MATRIXX + DM_SPACINGX; + y = DM_MATRIXY; + + for (i=0 ; iwidth)/2, + DM_MATRIXY - WI_SPACINGY, + p[i]); + + V_DrawPatch(DM_MATRIXX-SHORT(p[i]->width)/2, + y, + p[i]); + + if (i == me) + { + V_DrawPatch(x-SHORT(p[i]->width)/2, + DM_MATRIXY - WI_SPACINGY, + bstar); + + V_DrawPatch(DM_MATRIXX-SHORT(p[i]->width)/2, + y, + star); + } + } + else + { + // V_DrawPatch(x-SHORT(bp[i]->width)/2, + // DM_MATRIXY - WI_SPACINGY, bp[i]); + // V_DrawPatch(DM_MATRIXX-SHORT(bp[i]->width)/2, + // y, bp[i]); + } + x += DM_SPACINGX; + y += WI_SPACINGY; + } + + // draw stats + y = DM_MATRIXY+10; + w = SHORT(num[0]->width); + + for (i=0 ; imaxkills; + cnt_items[i] = (plrs[i].sitems * 100) / wbs->maxitems; + cnt_secret[i] = (plrs[i].ssecret * 100) / wbs->maxsecret; + + if (dofrags) + cnt_frags[i] = WI_fragSum(i); + } + S_StartSound(0, sfx_barexp); + ng_state = 10; + } + + if (ng_state == 2) + { + if (!(bcnt&3)) + S_StartSound(0, sfx_pistol); + + stillticking = false; + + for (i=0 ; i= (plrs[i].skills * 100) / wbs->maxkills) + cnt_kills[i] = (plrs[i].skills * 100) / wbs->maxkills; + else + stillticking = true; + } + + if (!stillticking) + { + S_StartSound(0, sfx_barexp); + ng_state++; + } + } + else if (ng_state == 4) + { + if (!(bcnt&3)) + S_StartSound(0, sfx_pistol); + + stillticking = false; + + for (i=0 ; i= (plrs[i].sitems * 100) / wbs->maxitems) + cnt_items[i] = (plrs[i].sitems * 100) / wbs->maxitems; + else + stillticking = true; + } + if (!stillticking) + { + S_StartSound(0, sfx_barexp); + ng_state++; + } + } + else if (ng_state == 6) + { + if (!(bcnt&3)) + S_StartSound(0, sfx_pistol); + + stillticking = false; + + for (i=0 ; i= (plrs[i].ssecret * 100) / wbs->maxsecret) + cnt_secret[i] = (plrs[i].ssecret * 100) / wbs->maxsecret; + else + stillticking = true; + } + + if (!stillticking) + { + S_StartSound(0, sfx_barexp); + ng_state += 1 + 2*!dofrags; + } + } + else if (ng_state == 8) + { + if (!(bcnt&3)) + S_StartSound(0, sfx_pistol); + + stillticking = false; + + for (i=0 ; i= (fsum = WI_fragSum(i))) + cnt_frags[i] = fsum; + else + stillticking = true; + } + + if (!stillticking) + { + S_StartSound(0, sfx_pldeth); + ng_state++; + } + } + else if (ng_state == 10) + { + if (acceleratestage) + { + S_StartSound(0, sfx_sgcock); + if ( gamemode == commercial ) + WI_initNoState(); + else + WI_initShowNextLoc(); + } + } + else if (ng_state & 1) + { + if (!--cnt_pause) + { + ng_state++; + cnt_pause = TICRATE; + } + } +} + + + +void WI_drawNetgameStats(void) +{ + int i; + int x; + int y; + int pwidth = SHORT(percent->width); + + WI_slamBackground(); + + // draw animated background + WI_drawAnimatedBack(); + + WI_drawLF(); + + // draw stat titles (top line) + V_DrawPatch(NG_STATSX+NG_SPACINGX-SHORT(kills->width), + NG_STATSY, kills); + + V_DrawPatch(NG_STATSX+2*NG_SPACINGX-SHORT(items->width), + NG_STATSY, items); + + V_DrawPatch(NG_STATSX+3*NG_SPACINGX-SHORT(secret->width), + NG_STATSY, secret); + + if (dofrags) + V_DrawPatch(NG_STATSX+4*NG_SPACINGX-SHORT(frags->width), + NG_STATSY, frags); + + // draw stats + y = NG_STATSY + SHORT(kills->height); + + for (i=0 ; iwidth), y, p[i]); + + if (i == me) + V_DrawPatch(x-SHORT(p[i]->width), y, star); + + x += NG_SPACINGX; + WI_drawPercent(x-pwidth, y+10, cnt_kills[i]); x += NG_SPACINGX; + WI_drawPercent(x-pwidth, y+10, cnt_items[i]); x += NG_SPACINGX; + WI_drawPercent(x-pwidth, y+10, cnt_secret[i]); x += NG_SPACINGX; + + if (dofrags) + WI_drawNum(x, y+10, cnt_frags[i], -1); + + y += WI_SPACINGY; + } + +} + +static int sp_state; + +void WI_initStats(void) +{ + state = StatCount; + acceleratestage = 0; + sp_state = 1; + cnt_kills[0] = cnt_items[0] = cnt_secret[0] = -1; + cnt_time = cnt_par = -1; + cnt_pause = TICRATE; + + WI_initAnimatedBack(); +} + +void WI_updateStats(void) +{ + + WI_updateAnimatedBack(); + + if (acceleratestage && sp_state != 10) + { + acceleratestage = 0; + cnt_kills[0] = (plrs[me].skills * 100) / wbs->maxkills; + cnt_items[0] = (plrs[me].sitems * 100) / wbs->maxitems; + cnt_secret[0] = (plrs[me].ssecret * 100) / wbs->maxsecret; + cnt_time = plrs[me].stime / TICRATE; + cnt_par = wbs->partime / TICRATE; + S_StartSound(0, sfx_barexp); + sp_state = 10; + } + + if (sp_state == 2) + { + cnt_kills[0] += 2; + + if (!(bcnt&3)) + S_StartSound(0, sfx_pistol); + + if (cnt_kills[0] >= (plrs[me].skills * 100) / wbs->maxkills) + { + cnt_kills[0] = (plrs[me].skills * 100) / wbs->maxkills; + S_StartSound(0, sfx_barexp); + sp_state++; + } + } + else if (sp_state == 4) + { + cnt_items[0] += 2; + + if (!(bcnt&3)) + S_StartSound(0, sfx_pistol); + + if (cnt_items[0] >= (plrs[me].sitems * 100) / wbs->maxitems) + { + cnt_items[0] = (plrs[me].sitems * 100) / wbs->maxitems; + S_StartSound(0, sfx_barexp); + sp_state++; + } + } + else if (sp_state == 6) + { + cnt_secret[0] += 2; + + if (!(bcnt&3)) + S_StartSound(0, sfx_pistol); + + if (cnt_secret[0] >= (plrs[me].ssecret * 100) / wbs->maxsecret) + { + cnt_secret[0] = (plrs[me].ssecret * 100) / wbs->maxsecret; + S_StartSound(0, sfx_barexp); + sp_state++; + } + } + + else if (sp_state == 8) + { + if (!(bcnt&3)) + S_StartSound(0, sfx_pistol); + + cnt_time += 3; + + if (cnt_time >= plrs[me].stime / TICRATE) + cnt_time = plrs[me].stime / TICRATE; + + cnt_par += 3; + + if (cnt_par >= wbs->partime / TICRATE) + { + cnt_par = wbs->partime / TICRATE; + + if (cnt_time >= plrs[me].stime / TICRATE) + { + S_StartSound(0, sfx_barexp); + sp_state++; + } + } + } + else if (sp_state == 10) + { + if (acceleratestage) + { + S_StartSound(0, sfx_sgcock); + + if (gamemode == commercial) + WI_initNoState(); + else + WI_initShowNextLoc(); + } + } + else if (sp_state & 1) + { + if (!--cnt_pause) + { + sp_state++; + cnt_pause = TICRATE; + } + } + +} + +void WI_drawStats(void) +{ + // line height + int lh; + + lh = (3*SHORT(num[0]->height))/2; + + WI_slamBackground(); + + // draw animated background + WI_drawAnimatedBack(); + + WI_drawLF(); + + V_DrawPatch(SP_STATSX, SP_STATSY, kills); + WI_drawPercent(SCREENWIDTH - SP_STATSX, SP_STATSY, cnt_kills[0]); + + V_DrawPatch(SP_STATSX, SP_STATSY+lh, items); + WI_drawPercent(SCREENWIDTH - SP_STATSX, SP_STATSY+lh, cnt_items[0]); + + V_DrawPatch(SP_STATSX, SP_STATSY+2*lh, sp_secret); + WI_drawPercent(SCREENWIDTH - SP_STATSX, SP_STATSY+2*lh, cnt_secret[0]); + + V_DrawPatch(SP_TIMEX, SP_TIMEY, timepatch); + WI_drawTime(SCREENWIDTH/2 - SP_TIMEX, SP_TIMEY, cnt_time); + + if (wbs->epsd < 3) + { + V_DrawPatch(SCREENWIDTH/2 + SP_TIMEX, SP_TIMEY, par); + WI_drawTime(SCREENWIDTH - SP_TIMEX, SP_TIMEY, cnt_par); + } + +} + +void WI_checkForAccelerate(void) +{ + int i; + player_t *player; + + // check for button presses to skip delays + for (i=0, player = players ; icmd.buttons & BT_ATTACK) + { + if (!player->attackdown) + acceleratestage = 1; + player->attackdown = true; + } + else + player->attackdown = false; + if (player->cmd.buttons & BT_USE) + { + if (!player->usedown) + acceleratestage = 1; + player->usedown = true; + } + else + player->usedown = false; + } + } +} + + + +// Updates stuff each tick +void WI_Ticker(void) +{ + // counter for general background animation + bcnt++; + + if (bcnt == 1) + { + // intermission music + if ( gamemode == commercial ) + S_ChangeMusic(mus_dm2int, true); + else + S_ChangeMusic(mus_inter, true); + } + + WI_checkForAccelerate(); + + switch (state) + { + case StatCount: + if (deathmatch) WI_updateDeathmatchStats(); + else if (netgame) WI_updateNetgameStats(); + else WI_updateStats(); + break; + + case ShowNextLoc: + WI_updateShowNextLoc(); + break; + + case NoState: + WI_updateNoState(); + break; + } + +} + +typedef void (*load_callback_t)(char *lumpname, patch_t **variable); + +// Common load/unload function. Iterates over all the graphics +// lumps to be loaded/unloaded into memory. + +static void WI_loadUnloadData(load_callback_t callback) +{ + int i, j; + char name[9]; + anim_t *a; + + if (gamemode == commercial) + { + for (i=0 ; iepsd, i); + callback(name, &lnames[i]); + } + + // you are here + callback(DEH_String("WIURH0"), &yah[0]); + + // you are here (alt.) + callback(DEH_String("WIURH1"), &yah[1]); + + // splat + callback(DEH_String("WISPLAT"), &splat[0]); + + if (wbs->epsd < 3) + { + for (j=0;jepsd];j++) + { + a = &anims[wbs->epsd][j]; + for (i=0;inanims;i++) + { + // MONDO HACK! + if (wbs->epsd != 1 || j != 8) + { + // animations + DEH_snprintf(name, 9, "WIA%d%.2d%.2d", wbs->epsd, j, i); + callback(name, &a->p[i]); + } + else + { + // HACK ALERT! + a->p[i] = anims[1][4].p[i]; + } + } + } + } + } + + // More hacks on minus sign. + callback(DEH_String("WIMINUS"), &wiminus); + + for (i=0;i<10;i++) + { + // numbers 0-9 + DEH_snprintf(name, 9, "WINUM%d", i); + callback(name, &num[i]); + } + + // percent sign + callback(DEH_String("WIPCNT"), &percent); + + // "finished" + callback(DEH_String("WIF"), &finished); + + // "entering" + callback(DEH_String("WIENTER"), &entering); + + // "kills" + callback(DEH_String("WIOSTK"), &kills); + + // "scrt" + callback(DEH_String("WIOSTS"), &secret); + + // "secret" + callback(DEH_String("WISCRT2"), &sp_secret); + + // french wad uses WIOBJ (?) + if (W_CheckNumForName(DEH_String("WIOBJ")) >= 0) + { + // "items" + if (netgame && !deathmatch) + callback(DEH_String("WIOBJ"), &items); + else + callback(DEH_String("WIOSTI"), &items); + } else { + callback(DEH_String("WIOSTI"), &items); + } + + // "frgs" + callback(DEH_String("WIFRGS"), &frags); + + // ":" + callback(DEH_String("WICOLON"), &colon); + + // "time" + callback(DEH_String("WITIME"), &timepatch); + + // "sucks" + callback(DEH_String("WISUCKS"), &sucks); + + // "par" + callback(DEH_String("WIPAR"), &par); + + // "killers" (vertical) + callback(DEH_String("WIKILRS"), &killers); + + // "victims" (horiz) + callback(DEH_String("WIVCTMS"), &victims); + + // "total" + callback(DEH_String("WIMSTT"), &total); + + for (i=0 ; iepsd == 3) + { + M_StringCopy(name, DEH_String("INTERPIC"), sizeof(name)); + } + else + { + DEH_snprintf(name, sizeof(name), "WIMAP%d", wbs->epsd); + } + + // Draw backdrop and save to a temporary buffer + + callback(name, &background); +} + +static void WI_loadCallback(char *name, patch_t **variable) +{ + *variable = W_CacheLumpName(name, PU_STATIC); +} + +void WI_loadData(void) +{ + if (gamemode == commercial) + { + NUMCMAPS = 32; + lnames = (patch_t **) Z_Malloc(sizeof(patch_t*) * NUMCMAPS, + PU_STATIC, NULL); + } + else + { + lnames = (patch_t **) Z_Malloc(sizeof(patch_t*) * NUMMAPS, + PU_STATIC, NULL); + } + + WI_loadUnloadData(WI_loadCallback); + + // These two graphics are special cased because we're sharing + // them with the status bar code + + // your face + star = W_CacheLumpName(DEH_String("STFST01"), PU_STATIC); + + // dead face + bstar = W_CacheLumpName(DEH_String("STFDEAD0"), PU_STATIC); +} + +static void WI_unloadCallback(char *name, patch_t **variable) +{ + W_ReleaseLumpName(name); + *variable = NULL; +} + +void WI_unloadData(void) +{ + WI_loadUnloadData(WI_unloadCallback); + + // We do not free these lumps as they are shared with the status + // bar code. + + // W_ReleaseLumpName("STFST01"); + // W_ReleaseLumpName("STFDEAD0"); +} + +void WI_Drawer (void) +{ + switch (state) + { + case StatCount: + if (deathmatch) + WI_drawDeathmatchStats(); + else if (netgame) + WI_drawNetgameStats(); + else + WI_drawStats(); + break; + + case ShowNextLoc: + WI_drawShowNextLoc(); + break; + + case NoState: + WI_drawNoState(); + break; + } +} + + +void WI_initVariables(wbstartstruct_t* wbstartstruct) +{ + + wbs = wbstartstruct; + +#ifdef RANGECHECKING + if (gamemode != commercial) + { + if ( gamemode == retail ) + RNGCHECK(wbs->epsd, 0, 3); + else + RNGCHECK(wbs->epsd, 0, 2); + } + else + { + RNGCHECK(wbs->last, 0, 8); + RNGCHECK(wbs->next, 0, 8); + } + RNGCHECK(wbs->pnum, 0, MAXPLAYERS); + RNGCHECK(wbs->pnum, 0, MAXPLAYERS); +#endif + + acceleratestage = 0; + cnt = bcnt = 0; + firstrefresh = 1; + me = wbs->pnum; + plrs = wbs->plyr; + + if (!wbs->maxkills) + wbs->maxkills = 1; + + if (!wbs->maxitems) + wbs->maxitems = 1; + + if (!wbs->maxsecret) + wbs->maxsecret = 1; + + if ( gamemode != retail ) + if (wbs->epsd > 2) + wbs->epsd -= 3; +} + +void WI_Start(wbstartstruct_t* wbstartstruct) +{ + WI_initVariables(wbstartstruct); + WI_loadData(); + + if (deathmatch) + WI_initDeathmatchStats(); + else if (netgame) + WI_initNetgameStats(); + else + WI_initStats(); +} diff --git a/firmware_p4/components/Applications/doom/wi_stuff.h b/firmware_p4/components/Applications/doom/wi_stuff.h new file mode 100644 index 000000000..296571f73 --- /dev/null +++ b/firmware_p4/components/Applications/doom/wi_stuff.h @@ -0,0 +1,48 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Intermission. +// + +#ifndef __WI_STUFF__ +#define __WI_STUFF__ + +//#include "v_video.h" + +#include "doomdef.h" + +// States for the intermission + +typedef enum +{ + NoState = -1, + StatCount, + ShowNextLoc, +} stateenum_t; + +// Called by main loop, animate the intermission. +void WI_Ticker (void); + +// Called by main loop, +// draws the intermission directly into the screen buffer. +void WI_Drawer (void); + +// Setup for an intermission screen. +void WI_Start(wbstartstruct_t* wbstartstruct); + +// Shut down the intermission screen +void WI_End(void); + +#endif diff --git a/firmware_p4/components/Applications/doom/z_zone.c b/firmware_p4/components/Applications/doom/z_zone.c new file mode 100644 index 000000000..16da22b90 --- /dev/null +++ b/firmware_p4/components/Applications/doom/z_zone.c @@ -0,0 +1,488 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Zone Memory Allocation. Neat. +// + + +#include "z_zone.h" +#include "i_system.h" +#include "doomtype.h" + + +// +// ZONE MEMORY ALLOCATION +// +// There is never any space between memblocks, +// and there will never be two contiguous free memblocks. +// The rover can be left pointing at a non-empty block. +// +// It is of no value to free a cachable block, +// because it will get overwritten automatically if needed. +// + +#define MEM_ALIGN sizeof(void *) +#define ZONEID 0x1d4a11 + +typedef struct memblock_s +{ + int size; // including the header and possibly tiny fragments + void** user; + int tag; // PU_FREE if this is free + int id; // should be ZONEID + struct memblock_s* next; + struct memblock_s* prev; +} memblock_t; + + +typedef struct +{ + // total bytes malloced, including header + int size; + + // start / end cap for linked list + memblock_t blocklist; + + memblock_t* rover; + +} memzone_t; + + + +memzone_t* mainzone; + + + +// +// Z_ClearZone +// +void Z_ClearZone (memzone_t* zone) +{ + memblock_t* block; + + // set the entire zone to one free block + zone->blocklist.next = + zone->blocklist.prev = + block = (memblock_t *)( (byte *)zone + sizeof(memzone_t) ); + + zone->blocklist.user = (void *)zone; + zone->blocklist.tag = PU_STATIC; + zone->rover = block; + + block->prev = block->next = &zone->blocklist; + + // a free block. + block->tag = PU_FREE; + + block->size = zone->size - sizeof(memzone_t); +} + + + +// +// Z_Init +// +void Z_Init (void) +{ + memblock_t* block; + int size; + + mainzone = (memzone_t *)I_ZoneBase (&size); + mainzone->size = size; + + // set the entire zone to one free block + mainzone->blocklist.next = + mainzone->blocklist.prev = + block = (memblock_t *)( (byte *)mainzone + sizeof(memzone_t) ); + + mainzone->blocklist.user = (void *)mainzone; + mainzone->blocklist.tag = PU_STATIC; + mainzone->rover = block; + + block->prev = block->next = &mainzone->blocklist; + + // free block + block->tag = PU_FREE; + + block->size = mainzone->size - sizeof(memzone_t); +} + + +// +// Z_Free +// +void Z_Free (void* ptr) +{ + memblock_t* block; + memblock_t* other; + + block = (memblock_t *) ( (byte *)ptr - sizeof(memblock_t)); + + if (block->id != ZONEID) + I_Error ("Z_Free: freed a pointer without ZONEID"); + + if (block->tag != PU_FREE && block->user != NULL) + { + // clear the user's mark + *block->user = 0; + } + + // mark as free + block->tag = PU_FREE; + block->user = NULL; + block->id = 0; + + other = block->prev; + + if (other->tag == PU_FREE) + { + // merge with previous free block + other->size += block->size; + other->next = block->next; + other->next->prev = other; + + if (block == mainzone->rover) + mainzone->rover = other; + + block = other; + } + + other = block->next; + if (other->tag == PU_FREE) + { + // merge the next free block onto the end + block->size += other->size; + block->next = other->next; + block->next->prev = block; + + if (other == mainzone->rover) + mainzone->rover = block; + } +} + + + +// +// Z_Malloc +// You can pass a NULL user if the tag is < PU_PURGELEVEL. +// +#define MINFRAGMENT 64 + + +void* +Z_Malloc +( int size, + int tag, + void* user ) +{ + int extra; + memblock_t* start; + memblock_t* rover; + memblock_t* newblock; + memblock_t* base; + void *result; + + size = (size + MEM_ALIGN - 1) & ~(MEM_ALIGN - 1); + + // scan through the block list, + // looking for the first free block + // of sufficient size, + // throwing out any purgable blocks along the way. + + // account for size of block header + size += sizeof(memblock_t); + + // if there is a free block behind the rover, + // back up over them + base = mainzone->rover; + + if (base->prev->tag == PU_FREE) + base = base->prev; + + rover = base; + start = base->prev; + + do + { + if (rover == start) + { + // scanned all the way around the list + I_Error ("Z_Malloc: failed on allocation of %i bytes", size); + } + + if (rover->tag != PU_FREE) + { + if (rover->tag < PU_PURGELEVEL) + { + // hit a block that can't be purged, + // so move base past it + base = rover = rover->next; + } + else + { + // free the rover block (adding the size to base) + + // the rover can be the base block + base = base->prev; + Z_Free ((byte *)rover+sizeof(memblock_t)); + base = base->next; + rover = base->next; + } + } + else + { + rover = rover->next; + } + + } while (base->tag != PU_FREE || base->size < size); + + + // found a block big enough + extra = base->size - size; + + if (extra > MINFRAGMENT) + { + // there will be a free fragment after the allocated block + newblock = (memblock_t *) ((byte *)base + size ); + newblock->size = extra; + + newblock->tag = PU_FREE; + newblock->user = NULL; + newblock->prev = base; + newblock->next = base->next; + newblock->next->prev = newblock; + + base->next = newblock; + base->size = size; + } + + if (user == NULL && tag >= PU_PURGELEVEL) + I_Error ("Z_Malloc: an owner is required for purgable blocks"); + + base->user = user; + base->tag = tag; + + result = (void *) ((byte *)base + sizeof(memblock_t)); + + if (base->user) + { + *base->user = result; + } + + // next allocation will start looking here + mainzone->rover = base->next; + + base->id = ZONEID; + + return result; +} + + + +// +// Z_FreeTags +// +void +Z_FreeTags +( int lowtag, + int hightag ) +{ + memblock_t* block; + memblock_t* next; + + for (block = mainzone->blocklist.next ; + block != &mainzone->blocklist ; + block = next) + { + // get link before freeing + next = block->next; + + // free block? + if (block->tag == PU_FREE) + continue; + + if (block->tag >= lowtag && block->tag <= hightag) + Z_Free ( (byte *)block+sizeof(memblock_t)); + } +} + + + +// +// Z_DumpHeap +// Note: TFileDumpHeap( stdout ) ? +// +void +Z_DumpHeap +( int lowtag, + int hightag ) +{ + memblock_t* block; + + printf ("zone size: %i location: %p\n", + mainzone->size,mainzone); + + printf ("tag range: %i to %i\n", + lowtag, hightag); + + for (block = mainzone->blocklist.next ; ; block = block->next) + { + if (block->tag >= lowtag && block->tag <= hightag) + printf ("block:%p size:%7i user:%p tag:%3i\n", + block, block->size, block->user, block->tag); + + if (block->next == &mainzone->blocklist) + { + // all blocks have been hit + break; + } + + if ( (byte *)block + block->size != (byte *)block->next) + printf ("ERROR: block size does not touch the next block\n"); + + if ( block->next->prev != block) + printf ("ERROR: next block doesn't have proper back link\n"); + + if (block->tag == PU_FREE && block->next->tag == PU_FREE) + printf ("ERROR: two consecutive free blocks\n"); + } +} + + +// +// Z_FileDumpHeap +// +void Z_FileDumpHeap (FILE* f) +{ + memblock_t* block; + + fprintf (f,"zone size: %i location: %p\n",mainzone->size,mainzone); + + for (block = mainzone->blocklist.next ; ; block = block->next) + { + fprintf (f,"block:%p size:%7i user:%p tag:%3i\n", + block, block->size, block->user, block->tag); + + if (block->next == &mainzone->blocklist) + { + // all blocks have been hit + break; + } + + if ( (byte *)block + block->size != (byte *)block->next) + fprintf (f,"ERROR: block size does not touch the next block\n"); + + if ( block->next->prev != block) + fprintf (f,"ERROR: next block doesn't have proper back link\n"); + + if (block->tag == PU_FREE && block->next->tag == PU_FREE) + fprintf (f,"ERROR: two consecutive free blocks\n"); + } +} + + + +// +// Z_CheckHeap +// +void Z_CheckHeap (void) +{ + memblock_t* block; + + for (block = mainzone->blocklist.next ; ; block = block->next) + { + if (block->next == &mainzone->blocklist) + { + // all blocks have been hit + break; + } + + if ( (byte *)block + block->size != (byte *)block->next) + I_Error ("Z_CheckHeap: block size does not touch the next block\n"); + + if ( block->next->prev != block) + I_Error ("Z_CheckHeap: next block doesn't have proper back link\n"); + + if (block->tag == PU_FREE && block->next->tag == PU_FREE) + I_Error ("Z_CheckHeap: two consecutive free blocks\n"); + } +} + + + + +// +// Z_ChangeTag +// +void Z_ChangeTag2(void *ptr, int tag, char *file, int line) +{ + memblock_t* block; + + block = (memblock_t *) ((byte *)ptr - sizeof(memblock_t)); + + if (block->id != ZONEID) + I_Error("%s:%i: Z_ChangeTag: block without a ZONEID!", + file, line); + + if (tag >= PU_PURGELEVEL && block->user == NULL) + I_Error("%s:%i: Z_ChangeTag: an owner is required " + "for purgable blocks", file, line); + + block->tag = tag; +} + +void Z_ChangeUser(void *ptr, void **user) +{ + memblock_t* block; + + block = (memblock_t *) ((byte *)ptr - sizeof(memblock_t)); + + if (block->id != ZONEID) + { + I_Error("Z_ChangeUser: Tried to change user for invalid block!"); + } + + block->user = user; + *user = ptr; +} + + + +// +// Z_FreeMemory +// +int Z_FreeMemory (void) +{ + memblock_t* block; + int free; + + free = 0; + + for (block = mainzone->blocklist.next ; + block != &mainzone->blocklist; + block = block->next) + { + if (block->tag == PU_FREE || block->tag >= PU_PURGELEVEL) + free += block->size; + } + + return free; +} + +unsigned int Z_ZoneSize(void) +{ + return mainzone->size; +} + diff --git a/firmware_p4/components/Applications/doom/z_zone.h b/firmware_p4/components/Applications/doom/z_zone.h new file mode 100644 index 000000000..526f30d30 --- /dev/null +++ b/firmware_p4/components/Applications/doom/z_zone.h @@ -0,0 +1,73 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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 +// of the License, 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. +// +// DESCRIPTION: +// Zone Memory Allocation, perhaps NeXT ObjectiveC inspired. +// Remark: this was the only stuff that, according +// to John Carmack, might have been useful for +// Quake. +// + + + +#ifndef __Z_ZONE__ +#define __Z_ZONE__ + +#include + +// +// ZONE MEMORY +// PU - purge tags. + +enum +{ + PU_STATIC = 1, // static entire execution time + PU_SOUND, // static while playing + PU_MUSIC, // static while playing + PU_FREE, // a free block + PU_LEVEL, // static until level exited + PU_LEVSPEC, // a special thinker in a level + + // Tags >= PU_PURGELEVEL are purgable whenever needed. + + PU_PURGELEVEL, + PU_CACHE, + + // Total number of different tag types + + PU_NUM_TAGS +}; + + +void Z_Init (void); +void* Z_Malloc (int size, int tag, void *ptr); +void Z_Free (void *ptr); +void Z_FreeTags (int lowtag, int hightag); +void Z_DumpHeap (int lowtag, int hightag); +void Z_FileDumpHeap (FILE *f); +void Z_CheckHeap (void); +void Z_ChangeTag2 (void *ptr, int tag, char *file, int line); +void Z_ChangeUser(void *ptr, void **user); +int Z_FreeMemory (void); +unsigned int Z_ZoneSize(void); + +// +// This is used to get the local FILE:LINE info from CPP +// prior to really call the function in question. +// +#define Z_ChangeTag(p,t) \ + Z_ChangeTag2((p), (t), __FILE__, __LINE__) + + +#endif diff --git a/firmware_p4/components/Applications/gameboy/CMakeLists.txt b/firmware_p4/components/Applications/gameboy/CMakeLists.txt new file mode 100644 index 000000000..ed3d549d0 --- /dev/null +++ b/firmware_p4/components/Applications/gameboy/CMakeLists.txt @@ -0,0 +1,33 @@ +# Copyright (c) 2025 HIGH CODE LLC +# +# TentacleOS 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 3 of the License, or +# (at your option) any later version. +# +# TentacleOS 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 TentacleOS. If not, see . + +# Game Boy (DMG) emulator — Peanut-GB core + HighBoy platform layer. +file(GLOB GB_SRCS "*.c") # gb_highboy.c + minigb_apu.c +idf_component_register( + SRCS ${GB_SRCS} + INCLUDE_DIRS "include" + PRIV_INCLUDE_DIRS "." # peanut_gb.h (single-header core) + REQUIRES Drivers Service lvgl esp_lcd esp_timer esp_system driver +) + +# Sound: enable Peanut-GB audio hooks (gb_highboy.c) and the minigb_apu 16-bit +# sample format (both gb_highboy.c and minigb_apu.c must see the format macro). +target_compile_definitions(${COMPONENT_LIB} PRIVATE + ENABLE_SOUND=1 + MINIGB_APU_AUDIO_FORMAT_S16SYS +) + +# Peanut-GB is performance-critical and warns a lot; optimize + silence. +target_compile_options(${COMPONENT_LIB} PRIVATE -O2 -w) diff --git a/firmware_p4/components/Applications/gameboy/README.md b/firmware_p4/components/Applications/gameboy/README.md new file mode 100644 index 000000000..f76616af1 --- /dev/null +++ b/firmware_p4/components/Applications/gameboy/README.md @@ -0,0 +1,7 @@ +# Game Boy Emulator Application + +Documentation for this component lives in the project docs hub (single source of truth): + +- [docs/gameboy/README.md](../../../../docs/gameboy/README.md) + +This file is only a pointer: edit the docs there to keep things from drifting. diff --git a/firmware_p4/components/Applications/gameboy/gb_highboy.c b/firmware_p4/components/Applications/gameboy/gb_highboy.c new file mode 100644 index 000000000..e5441748a --- /dev/null +++ b/firmware_p4/components/Applications/gameboy/gb_highboy.c @@ -0,0 +1,517 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#include "gb_highboy.h" + +#include +#include +#include +#include +#include + +#include "esp_heap_caps.h" +#include "esp_lcd_panel_ops.h" +#include "esp_log.h" +#include "esp_system.h" +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/idf_additions.h" +#include "freertos/task.h" + +#include "audio_i2s.h" +#include "buttons_gpio.h" +#include "lvgl_glue.h" +#include "st7789.h" +#include "storage_init.h" +#include "sys_prio.h" + +void ui_render_beat_kick(void); + +#define GB_OPEN_BUS 0xFF + +#define ENABLE_LCD 1 +#include "minigb_apu.h" +static struct minigb_apu_ctx *s_apu = NULL; +uint8_t audio_read(const uint16_t addr) { + return s_apu ? minigb_apu_audio_read(s_apu, addr) : GB_OPEN_BUS; +} +void audio_write(const uint16_t addr, const uint8_t val) { + if (s_apu) + minigb_apu_audio_write(s_apu, addr, val); +} +#include "peanut_gb.h" + +static const char *TAG = "GB"; + +#define GB_JOYPAD_IDLE 0xFF + +#define GB_DST_W 320 +#define GB_DST_H 240 +#define GB_X_OFF ((320 - GB_DST_W) / 2) +#define GB_Y_OFF ((240 - GB_DST_H) / 2) +#define GB_STRIP_ROWS 24 + +#define GB_ROM_PATH_LEN 300 +#define GB_MAIN_STACK 32768 +#define GB_AUDIO_STACK 4096 + +#define GB_STARTUP_DELAY_MS 150 +#define GB_EXIT_HOLD_MS 1200 +#define GB_AUTOSAVE_MS 3000 +#define GB_FLUSH_WAIT_MS 100 +#define GB_DMA_DRAIN_MS 30 +#define GB_HALT_DELAY_MS 2000 +#define GB_AUDIO_POLL_MS 10 +#define GB_AUDIO_STOP_TRIES 100 + +#define GB_FRAME_US (1000000 / 60) +#define GB_RESYNC_US 250000 + +static const uint32_t GB_DMG_PALETTE[] = {0xE0F8D0, 0x88C070, 0x346856, 0x081820}; +#define GB_DMG_PALETTE_COUNT (sizeof(GB_DMG_PALETTE) / sizeof(GB_DMG_PALETTE[0])) + +static struct gb_s *s_gb; +static uint8_t *s_rom; +static size_t s_rom_size; +static uint8_t *s_cram; +static size_t s_cram_size; +static uint8_t *s_shade; +static uint16_t *s_strip; +static uint16_t s_pal[GB_DMG_PALETTE_COUNT]; +static uint8_t s_sx[GB_DST_W], s_sy[GB_DST_H]; + +static char s_rompath[GB_ROM_PATH_LEN]; +static char s_savepath[GB_ROM_PATH_LEN]; +static bool s_has_save = false; +static volatile bool s_cram_dirty = false; +static int64_t s_cram_dirty_ms = 0; + +static volatile bool s_exit_req = false; +static volatile bool s_finished = false; +static volatile bool s_audio_run = true; +static volatile bool s_audio_done = false; + +bool highboy_gb_finished(void) { + return s_finished; +} + +static uint8_t rom_read(struct gb_s *gb, const uint_fast32_t addr) { + (void)gb; + return addr < s_rom_size ? s_rom[addr] : GB_OPEN_BUS; +} + +static uint8_t cram_read(struct gb_s *gb, const uint_fast32_t addr) { + (void)gb; + return (s_cram && addr < s_cram_size) ? s_cram[addr] : GB_OPEN_BUS; +} + +static void cram_write(struct gb_s *gb, const uint_fast32_t addr, const uint8_t v) { + (void)gb; + if (s_cram && addr < s_cram_size) { + s_cram[addr] = v; + s_cram_dirty = true; + s_cram_dirty_ms = esp_timer_get_time() / 1000; + } +} + +static void gb_err(struct gb_s *gb, const enum gb_error_e e, const uint16_t addr) { + (void)gb; + ESP_LOGE(TAG, "gb_error %d @ 0x%04X", (int)e, addr); +} + +static void lcd_line(struct gb_s *gb, const uint8_t *pixels, const uint_fast8_t line) { + (void)gb; + if (line >= LCD_HEIGHT) + return; + uint8_t *dst = s_shade + (size_t)line * LCD_WIDTH; + for (int x = 0; x < LCD_WIDTH; x++) + dst[x] = pixels[x] & LCD_COLOUR; +} + +static inline uint16_t to565be(uint32_t rgb) { + uint8_t r = (rgb >> 16) & 0xFF, g = (rgb >> 8) & 0xFF, b = rgb & 0xFF; + uint16_t c = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); + return (uint16_t)((c >> 8) | (c << 8)); +} + +static bool find_gb_rom(char *out, size_t outsz) { + const char *dirs[] = {"/sdcard", "/sdcard/gb", "/sdcard/roms", NULL}; + for (int d = 0; dirs[d]; d++) { + DIR *dir = opendir(dirs[d]); + if (!dir) + continue; + struct dirent *e; + while ((e = readdir(dir)) != NULL) { + size_t n = strlen(e->d_name); + bool gb = (n >= 3 && strcasecmp(e->d_name + n - 3, ".gb") == 0); + bool gbc = (n >= 4 && strcasecmp(e->d_name + n - 4, ".gbc") == 0); + if (gb || gbc) { + snprintf(out, outsz, "%s/%s", dirs[d], e->d_name); + closedir(dir); + return true; + } + } + closedir(dir); + } + return false; +} + +static void derive_savepath(void) { + strncpy(s_savepath, s_rompath, sizeof(s_savepath) - 1); + s_savepath[sizeof(s_savepath) - 1] = '\0'; + char *dot = strrchr(s_savepath, '.'); + char *slash = strrchr(s_savepath, '/'); + if (dot && (!slash || dot > slash)) + *dot = '\0'; + strncat(s_savepath, ".sav", sizeof(s_savepath) - strlen(s_savepath) - 1); +} + +static void load_cram(void) { + if (!s_has_save || !s_cram) + return; + FILE *f = fopen(s_savepath, "rb"); + if (!f) { + ESP_LOGW(TAG, "no save file yet (%s)", s_savepath); + return; + } + fseek(f, 0, SEEK_END); + long sz = ftell(f); + fseek(f, 0, SEEK_SET); + if ((size_t)sz == s_cram_size) { + size_t rd = fread(s_cram, 1, s_cram_size, f); + ESP_LOGW(TAG, "save loaded (%u B) from %s", (unsigned)rd, s_savepath); + } else { + ESP_LOGW(TAG, "save size mismatch (%ld vs %u) - ignoring", sz, (unsigned)s_cram_size); + } + fclose(f); +} + +static void save_cram(void) { + if (!s_has_save || !s_cram || s_cram_size <= 1) + return; + FILE *f = fopen(s_savepath, "wb"); + if (!f) { + ESP_LOGE(TAG, "save open failed (%s)", s_savepath); + return; + } + size_t w = fwrite(s_cram, 1, s_cram_size, f); + fclose(f); + s_cram_dirty = false; + ESP_LOGW(TAG, "save written (%u B) -> %s", (unsigned)w, s_savepath); +} + +static void poll_input(void) { + bool up = up_button_is_down(), dn = down_button_is_down(); + bool l = left_button_is_down(), r = right_button_is_down(); + bool ok = ok_button_is_down(), bk = back_button_is_down(); + uint8_t jp = GB_JOYPAD_IDLE; + if (r) + jp &= ~JOYPAD_UP; + if (l) + jp &= ~JOYPAD_DOWN; + if (up) + jp &= ~JOYPAD_LEFT; + if (dn) + jp &= ~JOYPAD_RIGHT; + + static int64_t bk_since = 0; + if (ok && bk) { + jp &= ~JOYPAD_START; + bk_since = 0; + } else { + if (ok) + jp &= ~JOYPAD_A; + if (bk) { + jp &= ~JOYPAD_B; + int64_t now = esp_timer_get_time() / 1000; + if (bk_since == 0) + bk_since = now; + else if (now - bk_since >= GB_EXIT_HOLD_MS) { + s_exit_req = true; + } + } else { + bk_since = 0; + } + } + s_gb->direct.joypad = jp; +} + +static void blit_frame(void) { + for (int y0 = 0; y0 < GB_DST_H; y0 += GB_STRIP_ROWS) { + int rows = (y0 + GB_STRIP_ROWS <= GB_DST_H) ? GB_STRIP_ROWS : (GB_DST_H - y0); + for (int j = 0; j < rows; j++) { + const uint8_t *srow = s_shade + (size_t)s_sy[y0 + j] * LCD_WIDTH; + uint16_t *orow = s_strip + (size_t)j * GB_DST_W; + for (int x = 0; x < GB_DST_W; x++) + orow[x] = s_pal[srow[s_sx[x]]]; + } + esp_lcd_panel_draw_bitmap( + panel_handle, GB_X_OFF, GB_Y_OFF + y0, GB_X_OFF + GB_DST_W, GB_Y_OFF + y0 + rows, s_strip); + lvgl_glue_wait_flush(GB_FLUSH_WAIT_MS); + } +} + +static void panel_clear_black(void) { + memset(s_strip, 0, (size_t)GB_DST_W * GB_STRIP_ROWS * sizeof(uint16_t)); + for (int y = 0; y < GB_DST_H; y += GB_STRIP_ROWS) { + int rows = (y + GB_STRIP_ROWS <= GB_DST_H) ? GB_STRIP_ROWS : (GB_DST_H - y); + esp_lcd_panel_draw_bitmap(panel_handle, 0, y, GB_DST_W, y + rows, s_strip); + lvgl_glue_wait_flush(GB_FLUSH_WAIT_MS); + } + vTaskDelay(pdMS_TO_TICKS(GB_DMA_DRAIN_MS)); +} + +static void gb_audio_task(void *arg) { + (void)arg; + if (audio_i2s_stream_start(AUDIO_SAMPLE_RATE) != ESP_OK) { + ESP_LOGE(TAG, "audio stream start failed - no sound"); + s_audio_done = true; + vTaskDeleteWithCaps(NULL); + return; + } + int16_t *st = heap_caps_malloc(AUDIO_SAMPLES_TOTAL * sizeof(int16_t), MALLOC_CAP_SPIRAM); + int16_t *mo = heap_caps_malloc(AUDIO_SAMPLES * sizeof(int16_t), MALLOC_CAP_SPIRAM); + if (st == NULL || mo == NULL) { + ESP_LOGE(TAG, "audio buf alloc failed"); + free(st); + free(mo); + s_audio_done = true; + vTaskDeleteWithCaps(NULL); + return; + } + ESP_LOGW(TAG, "audio task running @ %d Hz", AUDIO_SAMPLE_RATE); + while (s_audio_run) { + if (s_apu == NULL) { + vTaskDelay(pdMS_TO_TICKS(GB_AUDIO_POLL_MS)); + continue; + } + minigb_apu_audio_callback(s_apu, st); + for (unsigned i = 0; i < AUDIO_SAMPLES; i++) + mo[i] = (int16_t)(((int)st[2 * i] + (int)st[2 * i + 1]) >> 1); + audio_i2s_stream_write(mo, AUDIO_SAMPLES); + } + free(st); + free(mo); + s_audio_done = true; + vTaskDeleteWithCaps(NULL); +} + +static void gb_main_task(void *arg) { + (void)arg; + ESP_LOGW(TAG, + "gb_main_task: enter (free int=%u psram=%u)", + (unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL), + (unsigned)heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); + vTaskDelay(pdMS_TO_TICKS(GB_STARTUP_DELAY_MS)); + + if (!storage_is_mounted()) + storage_init(); + if (!storage_is_mounted()) { + ESP_LOGE(TAG, "SD not mounted. Halting."); + for (;;) + vTaskDelay(pdMS_TO_TICKS(GB_HALT_DELAY_MS)); + } + + if (s_rompath[0] == '\0' && !find_gb_rom(s_rompath, sizeof(s_rompath))) { + ESP_LOGE(TAG, "No .gb/.gbc ROM found on SD."); + for (;;) + vTaskDelay(pdMS_TO_TICKS(GB_HALT_DELAY_MS)); + } + ESP_LOGW(TAG, "loading ROM: %s", s_rompath); + FILE *f = fopen(s_rompath, "rb"); + if (f == NULL) { + ESP_LOGE(TAG, "fopen failed"); + for (;;) + vTaskDelay(pdMS_TO_TICKS(GB_HALT_DELAY_MS)); + } + fseek(f, 0, SEEK_END); + s_rom_size = ftell(f); + fseek(f, 0, SEEK_SET); + s_rom = heap_caps_malloc(s_rom_size, MALLOC_CAP_SPIRAM); + if (s_rom == NULL) { + ESP_LOGE(TAG, "ROM alloc %u failed", (unsigned)s_rom_size); + for (;;) + vTaskDelay(pdMS_TO_TICKS(GB_HALT_DELAY_MS)); + } + size_t rd = fread(s_rom, 1, s_rom_size, f); + fclose(f); + ESP_LOGW(TAG, "ROM %u bytes read (%u)", (unsigned)rd, (unsigned)s_rom_size); + + s_gb = heap_caps_malloc(sizeof(struct gb_s), MALLOC_CAP_SPIRAM); + s_shade = heap_caps_malloc((size_t)LCD_WIDTH * LCD_HEIGHT, MALLOC_CAP_SPIRAM); + s_strip = heap_caps_malloc((size_t)GB_DST_W * GB_STRIP_ROWS * sizeof(uint16_t), + MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); + if (s_gb == NULL || s_shade == NULL || s_strip == NULL) { + ESP_LOGE(TAG, + "buffer alloc failed (int free=%u) - returning to launcher", + (unsigned)heap_caps_get_free_size(MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL)); + vTaskDelay(pdMS_TO_TICKS(50)); + esp_restart(); + } + memset(s_shade, 0, (size_t)LCD_WIDTH * LCD_HEIGHT); + + enum gb_init_error_e ie = gb_init(s_gb, rom_read, cram_read, cram_write, gb_err, NULL); + ESP_LOGW(TAG, "gb_init -> %d", (int)ie); + if (ie != GB_INIT_NO_ERROR) { + ESP_LOGE(TAG, "gb_init failed (%d) - unsupported cart?", (int)ie); + for (;;) + vTaskDelay(pdMS_TO_TICKS(GB_HALT_DELAY_MS)); + } + + size_t ram = 0; + gb_get_save_size_s(s_gb, &ram); + s_has_save = (ram > 0); + s_cram_size = ram ? ram : 1; + s_cram = heap_caps_malloc(s_cram_size, MALLOC_CAP_SPIRAM); + if (s_cram) + memset(s_cram, 0, s_cram_size); + gb_init_lcd(s_gb, lcd_line); + + derive_savepath(); + load_cram(); + s_cram_dirty = false; + + s_apu = heap_caps_malloc(sizeof(struct minigb_apu_ctx), MALLOC_CAP_SPIRAM); + if (s_apu) { + minigb_apu_audio_init(s_apu); + xTaskCreatePinnedToCoreWithCaps(gb_audio_task, + "gb_audio", + GB_AUDIO_STACK, + NULL, + SYS_PRIO_SERVICE_HI, + NULL, + SYS_CORE_RADIO, + MALLOC_CAP_SPIRAM); + } else { + ESP_LOGW(TAG, "APU alloc failed - running without sound"); + s_audio_done = true; + } + + for (size_t i = 0; i < GB_DMG_PALETTE_COUNT; i++) + s_pal[i] = to565be(GB_DMG_PALETTE[i]); + for (int x = 0; x < GB_DST_W; x++) + s_sx[x] = (uint8_t)(x * LCD_WIDTH / GB_DST_W); + for (int y = 0; y < GB_DST_H; y++) + s_sy[y] = (uint8_t)(y * LCD_HEIGHT / GB_DST_H); + + lvgl_glue_lock(-1); + lvgl_glue_direct_begin(); + esp_lcd_panel_swap_xy(panel_handle, true); + esp_lcd_panel_mirror(panel_handle, true, false); + panel_clear_black(); + ESP_LOGW(TAG, "running. D-pad, OK=A, BACK=B, OK+BACK=START, hold BACK ~1.2s=exit."); + + int64_t start = esp_timer_get_time(); + uint64_t frame = 0; + int64_t last_blit = 0; + for (;;) { + poll_input(); + if (s_exit_req) + break; + + int64_t now = esp_timer_get_time(); + int64_t due = start + (int64_t)frame * GB_FRAME_US; + bool behind = (now > due + GB_FRAME_US); + bool want_blit = !behind && (now - last_blit >= 2 * GB_FRAME_US); + s_gb->direct.frame_skip = !want_blit; + + gb_run_frame(s_gb); + frame++; + + if (want_blit) { + blit_frame(); + last_blit = esp_timer_get_time(); + } + ui_render_beat_kick(); + + if (s_cram_dirty) { + int64_t ms = esp_timer_get_time() / 1000; + if (ms - s_cram_dirty_ms >= GB_AUTOSAVE_MS) + save_cram(); + } + + now = esp_timer_get_time(); + due = start + (int64_t)frame * GB_FRAME_US; + if (due > now) { + int64_t d_ms = (due - now) / 1000; + if (d_ms >= 1) + vTaskDelay(pdMS_TO_TICKS(d_ms)); + } else if (now - due > GB_RESYNC_US) { + start = now - (int64_t)frame * GB_FRAME_US; + } + } + + ESP_LOGW(TAG, "exit requested -> save + teardown"); + save_cram(); + + s_audio_run = false; + for (int i = 0; i < GB_AUDIO_STOP_TRIES && !s_audio_done; i++) + vTaskDelay(pdMS_TO_TICKS(GB_AUDIO_POLL_MS)); + audio_i2s_stream_stop(); + + vTaskDelay(pdMS_TO_TICKS(GB_DMA_DRAIN_MS)); + + if (s_apu) { + free(s_apu); + s_apu = NULL; + } + if (s_cram) { + free(s_cram); + s_cram = NULL; + } + if (s_strip) { + free(s_strip); + s_strip = NULL; + } + if (s_shade) { + free(s_shade); + s_shade = NULL; + } + if (s_gb) { + free(s_gb); + s_gb = NULL; + } + if (s_rom) { + free(s_rom); + s_rom = NULL; + } + + lvgl_glue_direct_end(); + s_finished = true; + lvgl_glue_unlock(); + vTaskDeleteWithCaps(NULL); +} + +void highboy_gb_start(const char *rompath) { + s_exit_req = false; + s_finished = false; + s_audio_run = true; + s_audio_done = false; + if (rompath && rompath[0]) { + strncpy(s_rompath, rompath, sizeof(s_rompath) - 1); + s_rompath[sizeof(s_rompath) - 1] = '\0'; + } else { + s_rompath[0] = '\0'; + } + BaseType_t ok = xTaskCreatePinnedToCoreWithCaps(gb_main_task, + "gameboy", + GB_MAIN_STACK, + NULL, + SYS_PRIO_SERVICE_HI, + NULL, + SYS_CORE_UI, + MALLOC_CAP_SPIRAM); + ESP_LOGW(TAG, "highboy_gb_start: task create -> %s", ok == pdPASS ? "OK" : "FAILED"); +} diff --git a/firmware_p4/components/Applications/gameboy/include/gb_highboy.h b/firmware_p4/components/Applications/gameboy/include/gb_highboy.h new file mode 100644 index 000000000..6166af2cf --- /dev/null +++ b/firmware_p4/components/Applications/gameboy/include/gb_highboy.h @@ -0,0 +1,56 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#ifndef GB_HIGHBOY_H +#define GB_HIGHBOY_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Launch the Game Boy (DMG) emulator (Peanut-GB core) in its own task. + * + * The emulator takes over the ST7789 (landscape, stretched to full screen) and + * runs until the user holds BACK for ~1.2 s. Battery-backed cartridge RAM is + * persisted to .sav next to the ROM on the SD card. + * + * On exit the task tears itself down (stops audio, frees its buffers, releases + * the LVGL lock) and reports highboy_gb_finished(); the ROM picker then restores + * the panel orientation and returns to the games menu without a firmware reboot. + * + * @param rompath Full SD path of the .gb/.gbc ROM to run, or NULL/"" to + * auto-discover the first ROM on the SD card. + */ +void highboy_gb_start(const char *rompath); + +/** + * @brief Whether the emulator task has fully torn down. + * + * Polled by the ROM picker's navigation timer (LVGL task) so it can restore the + * panel and switch to the games menu once the emulator has released the panel. + * Reset to false by the next highboy_gb_start(). + * + * @return true once teardown is complete, false while the emulator is running. + */ +bool highboy_gb_finished(void); + +#ifdef __cplusplus +} +#endif + +#endif // GB_HIGHBOY_H diff --git a/firmware_p4/components/Applications/gameboy/minigb_apu.c b/firmware_p4/components/Applications/gameboy/minigb_apu.c new file mode 100644 index 000000000..cbe5c983b --- /dev/null +++ b/firmware_p4/components/Applications/gameboy/minigb_apu.c @@ -0,0 +1,518 @@ +/** + * Game Boy APU emulator. + * Copyright (c) 2019 Mahyar Koshkouei + * Copyright (c) 2017 Alex Baines + * minigb_apu is released under the terms of the MIT license. + * + * minigb_apu emulates the audio processing unit (APU) of the Game Boy. This + * project is based on MiniGBS by Alex Baines: https://github.com/baines/MiniGBS + */ + +#include +#include +#include + +#include "minigb_apu.h" + +#define DMG_CLOCK_FREQ_U ((unsigned)DMG_CLOCK_FREQ) +#define AUDIO_NSAMPLES (AUDIO_SAMPLES_TOTAL) + +#define MAX(a, b) (a > b ? a : b) +#define MIN(a, b) (a <= b ? a : b) + +/* Factor in which values are multiplied to compensate for fixed-point + * arithmetic. Some hard-coded values in this project must be recreated. */ +#ifndef FREQ_INC_MULT +#define FREQ_INC_MULT 105 +#endif +/* Handles time keeping for sound generation. + * FREQ_INC_REF must be equal to, or larger than AUDIO_SAMPLE_RATE in order + * to avoid a division by zero error. + * Using a square of 2 simplifies calculations. */ +#define FREQ_INC_REF (AUDIO_SAMPLE_RATE * FREQ_INC_MULT) + +#define MAX_CHAN_VOLUME 15 + +static void set_note_freq(struct chan *c) { + /* Lowest expected value of freq is 64. */ + uint32_t freq = (DMG_CLOCK_FREQ_U / 4) / (2048 - c->freq); + c->freq_inc = freq * (uint32_t)(FREQ_INC_REF / AUDIO_SAMPLE_RATE); +} + +static void chan_enable(struct minigb_apu_ctx *ctx, const uint_fast8_t i, const bool enable) { + uint8_t val; + + ctx->chans[i].enabled = enable; + val = (ctx->audio_mem[0xFF26 - AUDIO_ADDR_COMPENSATION] & 0x80) | (ctx->chans[3].enabled << 3) | + (ctx->chans[2].enabled << 2) | (ctx->chans[1].enabled << 1) | (ctx->chans[0].enabled << 0); + + ctx->audio_mem[0xFF26 - AUDIO_ADDR_COMPENSATION] = val; +} + +static void update_env(struct chan *c) { + c->env.counter += c->env.inc; + + while (c->env.counter > FREQ_INC_REF) { + if (c->env.step) { + c->volume += c->env.up ? 1 : -1; + if (c->volume == 0 || c->volume == MAX_CHAN_VOLUME) { + c->env.inc = 0; + } + c->volume = MAX(0, MIN(MAX_CHAN_VOLUME, c->volume)); + } + c->env.counter -= FREQ_INC_REF; + } +} + +static void update_len(struct minigb_apu_ctx *ctx, struct chan *c) { + if (!c->len.enabled) + return; + + c->len.counter += c->len.inc; + if (c->len.counter > FREQ_INC_REF) { + chan_enable(ctx, c - ctx->chans, 0); + c->len.counter = 0; + } +} + +static bool update_freq(struct chan *c, uint32_t *pos) { + uint32_t inc = c->freq_inc - *pos; + c->freq_counter += inc; + + if (c->freq_counter > FREQ_INC_REF) { + *pos = c->freq_inc - (c->freq_counter - FREQ_INC_REF); + c->freq_counter = 0; + return true; + } else { + *pos = c->freq_inc; + return false; + } +} + +static void update_sweep(struct chan *c) { + c->sweep.counter += c->sweep.inc; + + while (c->sweep.counter > FREQ_INC_REF) { + if (c->sweep.shift) { + uint16_t inc = (c->sweep.freq >> c->sweep.shift); + if (c->sweep.down) + inc *= -1; + + c->freq = c->sweep.freq + inc; + if (c->freq > 2047) { + c->enabled = 0; + } else { + set_note_freq(c); + c->sweep.freq = c->freq; + } + } else if (c->sweep.rate) { + c->enabled = 0; + } + c->sweep.counter -= FREQ_INC_REF; + } +} + +static void update_square(struct minigb_apu_ctx *ctx, audio_sample_t *samples, const bool ch2) { + struct chan *c = &ctx->chans[ch2]; + + if (!c->powered || !c->enabled) + return; + + set_note_freq(c); + + for (uint_fast16_t i = 0; i < AUDIO_NSAMPLES; i += 2) { + update_len(ctx, c); + if (!c->enabled) + return; + + update_env(c); + if (!c->volume) + continue; + + if (!ch2) + update_sweep(c); + + uint32_t pos = 0; + uint32_t prev_pos = 0; + int32_t sample = 0; + + while (update_freq(c, &pos)) { + c->square.duty_counter = (c->square.duty_counter + 1) & 7; + sample += ((pos - prev_pos) / c->freq_inc) * c->val; + c->val = (c->square.duty & (1 << c->square.duty_counter)) ? VOL_INIT_MAX / MAX_CHAN_VOLUME + : VOL_INIT_MIN / MAX_CHAN_VOLUME; + prev_pos = pos; + } + + sample += c->val; + sample *= c->volume; + sample /= 4; + + samples[i + 0] += sample * c->on_left * ctx->vol_l; + samples[i + 1] += sample * c->on_right * ctx->vol_r; + } +} + +static uint8_t +wave_sample(struct minigb_apu_ctx *ctx, const unsigned int pos, const unsigned int volume) { + uint8_t sample; + + sample = ctx->audio_mem[(0xFF30 + pos / 2) - AUDIO_ADDR_COMPENSATION]; + if (pos & 1) { + sample &= 0xF; + } else { + sample >>= 4; + } + return volume ? (sample >> (volume - 1)) : 0; +} + +static void update_wave(struct minigb_apu_ctx *ctx, audio_sample_t *samples) { + struct chan *c = &ctx->chans[2]; + + if (!c->powered || !c->enabled || !c->volume) + return; + + set_note_freq(c); + c->freq_inc *= 2; + + for (uint_fast16_t i = 0; i < AUDIO_NSAMPLES; i += 2) { + update_len(ctx, c); + if (!c->enabled) + return; + + uint32_t pos = 0; + uint32_t prev_pos = 0; + audio_sample_t sample = 0; + + c->wave.sample = wave_sample(ctx, c->val, c->volume); + + while (update_freq(c, &pos)) { + c->val = (c->val + 1) & 31; + sample += ((pos - prev_pos) / c->freq_inc) * ((audio_sample_t)c->wave.sample - 8) * + (AUDIO_SAMPLE_MAX / 64); + c->wave.sample = wave_sample(ctx, c->val, c->volume); + prev_pos = pos; + } + + sample += ((audio_sample_t)c->wave.sample - 8) * (audio_sample_t)(AUDIO_SAMPLE_MAX / 64); + { + /* First element is unused. */ + audio_sample_t div[] = {AUDIO_SAMPLE_MAX, 1, 2, 4}; + sample = sample / (div[c->volume]); + } + + sample /= 4; + samples[i + 0] += sample * c->on_left * ctx->vol_l; + samples[i + 1] += sample * c->on_right * ctx->vol_r; + } +} + +static void update_noise(struct minigb_apu_ctx *ctx, audio_sample_t *samples) { + struct chan *c = &ctx->chans[3]; + + if (c->freq >= 14) + c->enabled = 0; + + if (!c->powered || !c->enabled) + return; + + { + const uint32_t lfsr_div_lut[] = {8, 16, 32, 48, 64, 80, 96, 112}; + uint32_t freq; + + freq = DMG_CLOCK_FREQ_U / (lfsr_div_lut[c->noise.lfsr_div] << c->freq); + c->freq_inc = freq * (uint32_t)(FREQ_INC_REF / AUDIO_SAMPLE_RATE); + } + + for (uint_fast16_t i = 0; i < AUDIO_NSAMPLES; i += 2) { + update_len(ctx, c); + if (!c->enabled) + return; + + update_env(c); + if (!c->volume) + continue; + + uint32_t pos = 0; + uint32_t prev_pos = 0; + int32_t sample = 0; + + while (update_freq(c, &pos)) { + c->noise.lfsr_reg = (c->noise.lfsr_reg << 1) | (c->val >= VOL_INIT_MAX / MAX_CHAN_VOLUME); + + if (c->noise.lfsr_wide) { + c->val = !(((c->noise.lfsr_reg >> 14) & 1) ^ ((c->noise.lfsr_reg >> 13) & 1)) + ? VOL_INIT_MAX / MAX_CHAN_VOLUME + : VOL_INIT_MIN / MAX_CHAN_VOLUME; + } else { + c->val = !(((c->noise.lfsr_reg >> 6) & 1) ^ ((c->noise.lfsr_reg >> 5) & 1)) + ? VOL_INIT_MAX / MAX_CHAN_VOLUME + : VOL_INIT_MIN / MAX_CHAN_VOLUME; + } + + sample += ((pos - prev_pos) / c->freq_inc) * c->val; + prev_pos = pos; + } + + sample += c->val; + sample *= c->volume; + sample /= 4; + + samples[i + 0] += sample * c->on_left * ctx->vol_l; + samples[i + 1] += sample * c->on_right * ctx->vol_r; + } +} + +/** + * SDL2 style audio callback function. + */ +void minigb_apu_audio_callback(struct minigb_apu_ctx *ctx, audio_sample_t *stream) { + memset(stream, 0, AUDIO_SAMPLES_TOTAL * sizeof(audio_sample_t)); + update_square(ctx, stream, 0); + update_square(ctx, stream, 1); + update_wave(ctx, stream); + update_noise(ctx, stream); +} + +static void chan_trigger(struct minigb_apu_ctx *ctx, uint_fast8_t i) { + struct chan *c = &ctx->chans[i]; + + chan_enable(ctx, i, 1); + c->volume = c->volume_init; + + // volume envelope + { + /* LUT created in Julia with: + * `(FREQ_INC_MULT * 64)./vcat(8, 1:7)` + * Must be recreated when FREQ_INC_MULT modified. + */ + const uint32_t inc_lut[8] = { +#if FREQ_INC_MULT == 16 + 128, 1024, 512, 341, 256, 205, 171, 146 +#elif FREQ_INC_MULT == 64 + 512, 4096, 2048, 1365, 1024, 819, 683, 585 +#elif FREQ_INC_MULT == 105 + /* Multiples of 105 provide integer values. */ + 840, + 6720, + 3360, + 2240, + 1680, + 1344, + 1120, + 960 +#else +#error "LUT not calculated for this value of FREQ_INC_MULT" +#endif + }; + uint8_t val; + + val = ctx->audio_mem[(0xFF12 + (i * 5)) - AUDIO_ADDR_COMPENSATION]; + + c->env.step = val & 0x7; + c->env.up = val & 0x8; + c->env.inc = inc_lut[c->env.step]; + c->env.counter = 0; + } + + // freq sweep + if (i == 0) { + uint8_t val = ctx->audio_mem[0xFF10 - AUDIO_ADDR_COMPENSATION]; + + c->sweep.freq = c->freq; + c->sweep.rate = (val >> 4) & 0x07; + c->sweep.down = (val & 0x08); + c->sweep.shift = (val & 0x07); + c->sweep.inc = + c->sweep.rate ? ((128u * FREQ_INC_REF) / (c->sweep.rate * AUDIO_SAMPLE_RATE)) : 0; + c->sweep.counter = FREQ_INC_REF; + } + + int len_max = 64; + + if (i == 2) { // wave + len_max = 256; + c->val = 0; + } else if (i == 3) { // noise + c->noise.lfsr_reg = 0xFFFF; + c->val = VOL_INIT_MIN / MAX_CHAN_VOLUME; + } + + c->len.inc = (256u * FREQ_INC_REF) / (AUDIO_SAMPLE_RATE * (len_max - c->len.load)); + c->len.counter = 0; +} + +/** + * Read audio register. + * \param addr Address of audio register. Must be 0xFF10 <= addr <= 0xFF3F. + * This is not checked in this function. + * \return Byte at address. + */ +uint8_t minigb_apu_audio_read(struct minigb_apu_ctx *ctx, const uint16_t addr) { + static const uint8_t ortab[] = {0x80, 0x3f, 0x00, 0xff, 0xbf, 0xff, 0x3f, 0x00, 0xff, 0xbf, + 0x7f, 0xff, 0x9f, 0xff, 0xbf, 0xff, 0xff, 0x00, 0x00, 0xbf, + 0x00, 0x00, 0x70, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + return ctx->audio_mem[addr - AUDIO_ADDR_COMPENSATION] | ortab[addr - AUDIO_ADDR_COMPENSATION]; +} + +/** + * Write audio register. + * \param addr Address of audio register. Must be 0xFF10 <= addr <= 0xFF3F. + * This is not checked in this function. + * \param val Byte to write at address. + */ +void minigb_apu_audio_write(struct minigb_apu_ctx *ctx, const uint16_t addr, const uint8_t val) { + /* Find sound channel corresponding to register address. */ + uint_fast8_t i; + + if (addr == 0xFF26) { + ctx->audio_mem[addr - AUDIO_ADDR_COMPENSATION] = val & 0x80; + /* On APU power off, clear all registers apart from wave + * RAM. */ + if ((val & 0x80) == 0) { + memset(ctx->audio_mem, 0x00, 0xFF26 - AUDIO_ADDR_COMPENSATION); + ctx->chans[0].enabled = false; + ctx->chans[1].enabled = false; + ctx->chans[2].enabled = false; + ctx->chans[3].enabled = false; + } + + return; + } + + /* Ignore register writes if APU powered off. */ + if (ctx->audio_mem[0xFF26 - AUDIO_ADDR_COMPENSATION] == 0x00) + return; + + ctx->audio_mem[addr - AUDIO_ADDR_COMPENSATION] = val; + i = (addr - AUDIO_ADDR_COMPENSATION) / 5; + + switch (addr) { + case 0xFF12: + case 0xFF17: + case 0xFF21: { + ctx->chans[i].volume_init = val >> 4; + ctx->chans[i].powered = (val >> 3) != 0; + + // "zombie mode" stuff, needed for Prehistorik Man and probably + // others + if (ctx->chans[i].powered && ctx->chans[i].enabled) { + if ((ctx->chans[i].env.step == 0 && ctx->chans[i].env.inc != 0)) { + if (val & 0x08) { + ctx->chans[i].volume++; + } else { + ctx->chans[i].volume += 2; + } + } else { + ctx->chans[i].volume = 16 - ctx->chans[i].volume; + } + + ctx->chans[i].volume &= 0x0F; + ctx->chans[i].env.step = val & 0x07; + } + } break; + + case 0xFF1C: + ctx->chans[i].volume = ctx->chans[i].volume_init = (val >> 5) & 0x03; + break; + + case 0xFF11: + case 0xFF16: + case 0xFF20: { + const uint8_t duty_lookup[] = {0x10, 0x30, 0x3C, 0xCF}; + ctx->chans[i].len.load = val & 0x3f; + ctx->chans[i].square.duty = duty_lookup[val >> 6]; + break; + } + + case 0xFF1B: + ctx->chans[i].len.load = val; + break; + + case 0xFF13: + case 0xFF18: + case 0xFF1D: + ctx->chans[i].freq &= 0xFF00; + ctx->chans[i].freq |= val; + break; + + case 0xFF1A: + ctx->chans[i].powered = (val & 0x80) != 0; + chan_enable(ctx, i, val & 0x80); + break; + + case 0xFF14: + case 0xFF19: + case 0xFF1E: + ctx->chans[i].freq &= 0x00FF; + ctx->chans[i].freq |= ((val & 0x07) << 8); + /* Intentional fall-through. */ + case 0xFF23: + ctx->chans[i].len.enabled = val & 0x40; + if (val & 0x80) + chan_trigger(ctx, i); + + break; + + case 0xFF22: + ctx->chans[3].freq = val >> 4; + ctx->chans[3].noise.lfsr_wide = !(val & 0x08); + ctx->chans[3].noise.lfsr_div = val & 0x07; + break; + + case 0xFF24: { + ctx->vol_l = ((val >> 4) & 0x07); + ctx->vol_r = (val & 0x07); + break; + } + + case 0xFF25: + for (uint_fast8_t j = 0; j < 4; j++) { + ctx->chans[j].on_left = (val >> (4 + j)) & 1; + ctx->chans[j].on_right = (val >> j) & 1; + } + break; + } +} + +void minigb_apu_audio_init(struct minigb_apu_ctx *ctx) { + /* Initialise channels and samples. */ + memset(ctx->chans, 0, sizeof(ctx->chans)); + ctx->chans[0].val = ctx->chans[1].val = -1; + + /* Initialise IO registers. */ + { + const uint8_t regs_init[] = {0x80, 0xBF, 0xF3, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, + 0xFF, 0x3F, 0x7F, 0xFF, 0x9F, 0xFF, 0x3F, 0xFF, + 0xFF, 0x00, 0x00, 0x3F, 0x77, 0xF3, 0xF1}; + + for (uint_fast8_t i = 0; i < sizeof(regs_init); ++i) + minigb_apu_audio_write(ctx, 0xFF10 + i, regs_init[i]); + } + + /* Initialise Wave Pattern RAM. */ + { + const uint8_t wave_init[] = {0xac, + 0xdd, + 0xda, + 0x48, + 0x36, + 0x02, + 0xcf, + 0x16, + 0x2c, + 0x04, + 0xe5, + 0x2c, + 0xac, + 0xdd, + 0xda, + 0x48}; + + for (uint_fast8_t i = 0; i < sizeof(wave_init); ++i) + minigb_apu_audio_write(ctx, 0xFF30 + i, wave_init[i]); + } +} diff --git a/firmware_p4/components/Applications/gameboy/minigb_apu.h b/firmware_p4/components/Applications/gameboy/minigb_apu.h new file mode 100644 index 000000000..e6f49a893 --- /dev/null +++ b/firmware_p4/components/Applications/gameboy/minigb_apu.h @@ -0,0 +1,147 @@ +/** + * minigb_apu is released under the terms listed within the LICENSE file. + * + * minigb_apu emulates the audio processing unit (APU) of the Game Boy. This + * project is based on MiniGBS by Alex Baines: https://github.com/baines/MiniGBS + */ + +#pragma once + +#include + +#ifndef AUDIO_SAMPLE_RATE +#define AUDIO_SAMPLE_RATE 32768 +#endif + +/* The audio output format is in platform native endian. */ +#if defined(MINIGB_APU_AUDIO_FORMAT_S16SYS) +typedef int16_t audio_sample_t; +#define AUDIO_SAMPLE_MAX INT16_MAX +#define AUDIO_SAMPLE_MIN INT16_MIN +#define VOL_INIT_MAX (AUDIO_SAMPLE_MAX / 8) +#define VOL_INIT_MIN (AUDIO_SAMPLE_MIN / 8) +#elif defined(MINIGB_APU_AUDIO_FORMAT_S32SYS) +typedef int32_t audio_sample_t; +#define AUDIO_SAMPLE_MAX INT32_MAX +#define AUDIO_SAMPLE_MIN INT32_MIN +#define VOL_INIT_MAX (INT32_MAX / 8) +#define VOL_INIT_MIN (INT32_MIN / 8) +#else +#error MiniGB APU: Invalid or unsupported audio format selected +#endif + +#define DMG_CLOCK_FREQ 4194304.0 +#define SCREEN_REFRESH_CYCLES 70224.0 +#define VERTICAL_SYNC (DMG_CLOCK_FREQ / SCREEN_REFRESH_CYCLES) + +/* Number of audio samples in each channel. */ +#define AUDIO_SAMPLES ((unsigned)(AUDIO_SAMPLE_RATE / VERTICAL_SYNC)) +/* Number of audio channels. The audio output is in interleaved stereo format.*/ +#define AUDIO_CHANNELS 2 +/* Number of audio samples output in each audio_callback call. */ +#define AUDIO_SAMPLES_TOTAL (AUDIO_SAMPLES * 2) + +#define AUDIO_MEM_SIZE (0xFF3F - 0xFF10 + 1) +#define AUDIO_ADDR_COMPENSATION 0xFF10 + +struct chan_len_ctr { + uint8_t load; + uint8_t enabled; + uint32_t counter; + uint32_t inc; +}; + +struct chan_vol_env { + uint8_t step; + uint8_t up; + uint32_t counter; + uint32_t inc; +}; + +struct chan_freq_sweep { + uint8_t rate; + uint8_t shift; + uint8_t down; + uint16_t freq; + uint32_t counter; + uint32_t inc; +}; + +struct chan { + uint8_t enabled; + uint8_t powered; + uint8_t on_left; + uint8_t on_right; + + uint8_t volume; + uint8_t volume_init; + + uint16_t freq; + uint32_t freq_counter; + uint32_t freq_inc; + + int32_t val; + + struct chan_len_ctr len; + struct chan_vol_env env; + struct chan_freq_sweep sweep; + + union { + struct { + uint8_t duty; + uint8_t duty_counter; + } square; + struct { + uint16_t lfsr_reg; + uint8_t lfsr_wide; + uint8_t lfsr_div; + } noise; + struct { + uint8_t sample; + } wave; + }; +}; + +struct minigb_apu_ctx { + struct chan chans[4]; + int32_t vol_l, vol_r; + + /** + * Memory holding audio registers between 0xFF10 and 0xFF3F inclusive. + */ + uint8_t audio_mem[AUDIO_MEM_SIZE]; +}; + +/** + * Fill allocated buffer "stream" with AUDIO_SAMPLES_TOTAL number of 16-bit + * signed samples (native endian order) in stereo interleaved format. + * Each call corresponds to the time taken for each VSYNC in the Game Boy. + * + * \param ctx Library context. Must be initialised with audio_init(). + * \param stream Allocated pointer to store audio samples. Must be at least + * AUDIO_SAMPLES_TOTAL in size. + */ +void minigb_apu_audio_callback(struct minigb_apu_ctx *ctx, audio_sample_t *stream); + +/** + * Read audio register at given address "addr". + * \param ctx Library context. Must be initialised with audio_init(). + * \param addr Address of registers to read. Must be within 0xFF10 and 0xFF3F, + * inclusive. + */ +uint8_t minigb_apu_audio_read(struct minigb_apu_ctx *ctx, const uint16_t addr); + +/** + * Write "val" to audio register at given address "addr". + * \param ctx Library context. Must be initialised with audio_init(). + * \param addr Address of registers to read. Must be within 0xFF10 and 0xFF3F, + * inclusive. + * \param val Value to write to address. + */ +void minigb_apu_audio_write(struct minigb_apu_ctx *ctx, const uint16_t addr, const uint8_t val); + +/** + * Initialise audio driver. + * \param ctx Library context. + */ +void minigb_apu_audio_init(struct minigb_apu_ctx *ctx); diff --git a/firmware_p4/components/Applications/gameboy/peanut_gb.h b/firmware_p4/components/Applications/gameboy/peanut_gb.h new file mode 100644 index 000000000..459172613 --- /dev/null +++ b/firmware_p4/components/Applications/gameboy/peanut_gb.h @@ -0,0 +1,3804 @@ +/** + * MIT License + * + * Copyright (c) 2018-2023 Mahyar Koshkouei + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Please note that at least two parts of source code within this project was + * taken from the SameBoy project at https://github.com/LIJI32/SameBoy/ which at + * the time of this writing is released under the MIT License. Occurrences of + * this code is marked as being taken from SameBoy with a comment. + * SameBoy, and code marked as being taken from SameBoy, + * is Copyright (c) 2015-2019 Lior Halphon. + */ + +#ifndef PEANUT_GB_H +#define PEANUT_GB_H + +#if defined(__has_include) +#if __has_include("version.all") +#include "version.all" /* Version information */ +#endif +#else +/* Stub __has_include for later. */ +#define __has_include(x) 0 +#endif + +#include /* Required for abort */ +#include /* Required for bool types */ +#include /* Required for int types */ +#include /* Required for memset */ +#include /* Required for tm struct */ + +/** + * If PEANUT_GB_IS_LITTLE_ENDIAN is positive, then Peanut-GB will be configured + * for a little endian platform. If 0, then big endian. + */ +#if !defined(PEANUT_GB_IS_LITTLE_ENDIAN) +/* If endian is not defined, then attempt to detect it. */ +#if defined(__BYTE_ORDER__) +#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ +/* Building for a big endian platform. */ +#define PEANUT_GB_IS_LITTLE_ENDIAN 0 +#else +#define PEANUT_GB_IS_LITTLE_ENDIAN 1 +#endif /* __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ */ +#elif defined(_WIN32) +/* We assume that Windows is always little endian by default. */ +#define PEANUT_GB_IS_LITTLE_ENDIAN 1 +#elif !defined(PEANUT_GB_IS_LITTLE_ENDIAN) +#error "Could not detect target platform endian. Please define PEANUT_GB_IS_LITTLE_ENDIAN" +#endif +#endif /* !defined(PEANUT_GB_IS_LITTLE_ENDIAN) */ + +#if PEANUT_GB_IS_LITTLE_ENDIAN == 0 +#error "Peanut-GB only supports little endian targets" +/* This is because the logic has been written with assumption of little + * endian byte order. */ +#endif + +/** Definitions for compile-time setting of features. **/ +/** + * Sound support must be provided by an external library. When audio_read() and + * audio_write() functions are provided, define ENABLE_SOUND to a non-zero value + * before including peanut_gb.h in order for these functions to be used. + */ +#ifndef ENABLE_SOUND +#define ENABLE_SOUND 0 +#endif + +/* Enable LCD drawing. On by default. May be turned off for testing purposes. */ +#ifndef ENABLE_LCD +#define ENABLE_LCD 1 +#endif + +/* Enable 16 bit colour palette. If disabled, only four colour shades are set in + * pixel data. */ +#ifndef PEANUT_GB_12_COLOUR +#define PEANUT_GB_12_COLOUR 1 +#endif + +/* Adds more code to improve LCD rendering accuracy. */ +#ifndef PEANUT_GB_HIGH_LCD_ACCURACY +#define PEANUT_GB_HIGH_LCD_ACCURACY 1 +#endif + +/* Use intrinsic functions. This may produce smaller and faster code. */ +#ifndef PEANUT_GB_USE_INTRINSICS +#define PEANUT_GB_USE_INTRINSICS 1 +#endif + +/* Only include function prototypes. At least one file must *not* have this + * defined. */ +// #define PEANUT_GB_HEADER_ONLY + +/** Internal source code. **/ +/* Interrupt masks */ +#define VBLANK_INTR 0x01 +#define LCDC_INTR 0x02 +#define TIMER_INTR 0x04 +#define SERIAL_INTR 0x08 +#define CONTROL_INTR 0x10 +#define ANY_INTR 0x1F + +/* Memory section sizes for DMG */ +#define WRAM_SIZE 0x2000 +#define VRAM_SIZE 0x2000 +#define HRAM_IO_SIZE 0x0100 +#define OAM_SIZE 0x00A0 + +/* Memory addresses */ +#define ROM_0_ADDR 0x0000 +#define ROM_N_ADDR 0x4000 +#define VRAM_ADDR 0x8000 +#define CART_RAM_ADDR 0xA000 +#define WRAM_0_ADDR 0xC000 +#define WRAM_1_ADDR 0xD000 +#define ECHO_ADDR 0xE000 +#define OAM_ADDR 0xFE00 +#define UNUSED_ADDR 0xFEA0 +#define IO_ADDR 0xFF00 +#define HRAM_ADDR 0xFF80 +#define INTR_EN_ADDR 0xFFFF + +/* Cart section sizes */ +#define ROM_BANK_SIZE 0x4000 +#define WRAM_BANK_SIZE 0x1000 +#define CRAM_BANK_SIZE 0x2000 +#define VRAM_BANK_SIZE 0x2000 + +/* DIV Register is incremented at rate of 16384Hz. + * 4194304 / 16384 = 256 clock cycles for one increment. */ +#define DIV_CYCLES 256 + +/* Serial clock locked to 8192Hz on DMG. + * 4194304 / (8192 / 8) = 4096 clock cycles for sending 1 byte. */ +#define SERIAL_CYCLES 4096 + +/* Calculating VSYNC. */ +#define DMG_CLOCK_FREQ 4194304.0 +#define SCREEN_REFRESH_CYCLES 70224.0 +#define VERTICAL_SYNC (DMG_CLOCK_FREQ / SCREEN_REFRESH_CYCLES) + +/* Real Time Clock is locked to 1Hz. */ +#define RTC_CYCLES ((uint_fast32_t)DMG_CLOCK_FREQ) + +/* SERIAL SC register masks. */ +#define SERIAL_SC_TX_START 0x80 +#define SERIAL_SC_CLOCK_SRC 0x01 + +/* STAT register masks */ +#define STAT_LYC_INTR 0x40 +#define STAT_MODE_2_INTR 0x20 +#define STAT_MODE_1_INTR 0x10 +#define STAT_MODE_0_INTR 0x08 +#define STAT_LYC_COINC 0x04 +#define STAT_MODE 0x03 +#define STAT_USER_BITS 0xF8 + +/* LCDC control masks */ +#define LCDC_ENABLE 0x80 +#define LCDC_WINDOW_MAP 0x40 +#define LCDC_WINDOW_ENABLE 0x20 +#define LCDC_TILE_SELECT 0x10 +#define LCDC_BG_MAP 0x08 +#define LCDC_OBJ_SIZE 0x04 +#define LCDC_OBJ_ENABLE 0x02 +#define LCDC_BG_ENABLE 0x01 + +/** LCD characteristics **/ +/* There are 154 scanlines. LY < 154. */ +#define LCD_VERT_LINES 154 +#define LCD_WIDTH 160 +#define LCD_HEIGHT 144 +/* PPU cycles through modes every 456 cycles. */ +#define LCD_LINE_CYCLES 456 +#define LCD_MODE0_HBLANK_MAX_DRUATION 204 +#define LCD_MODE0_HBLANK_MIN_DRUATION 87 +#define LCD_MODE2_OAM_SCAN_DURATION 80 +#define LCD_MODE3_LCD_DRAW_MIN_DURATION 172 +#define LCD_MODE3_LCD_DRAW_MAX_DURATION 289 +#define LCD_MODE1_VBLANK_DURATION (LCD_LINE_CYCLES * (LCD_VERT_LINES - LCD_HEIGHT)) +#define LCD_FRAME_CYCLES (LCD_LINE_CYCLES * LCD_VERT_LINES) +/* The following assumes that Hblank starts on cycle 0. */ +/* Mode 2 (OAM Scan) starts on cycle 204 (although this is dependent on the + * duration of Mode 3 (LCD Draw). */ +#define LCD_MODE_2_CYCLES LCD_MODE0_HBLANK_MAX_DRUATION +/* Mode 3 starts on cycle 284. */ +#define LCD_MODE_3_CYCLES (LCD_MODE_2_CYCLES + LCD_MODE2_OAM_SCAN_DURATION) +/* Mode 0 starts on cycle 376. */ +#define LCD_MODE_0_CYCLES (LCD_MODE_3_CYCLES + LCD_MODE3_LCD_DRAW_MIN_DURATION) + +#define LCD_MODE2_OAM_SCAN_START 0 +#define LCD_MODE2_OAM_SCAN_END (LCD_MODE2_OAM_SCAN_DURATION) +#define LCD_MODE3_LCD_DRAW_END (LCD_MODE2_OAM_SCAN_END + LCD_MODE3_LCD_DRAW_MIN_DURATION) +#define LCD_MODE0_HBLANK_END (LCD_MODE3_LCD_DRAW_END + LCD_MODE0_HBLANK_MAX_DRUATION) +#if LCD_MODE0_HBLANK_END != LCD_LINE_CYCLES +#error "LCD length not equal" +#endif + +/* VRAM Locations */ +#define VRAM_TILES_1 (0x8000 - VRAM_ADDR) +#define VRAM_TILES_2 (0x8800 - VRAM_ADDR) +#define VRAM_BMAP_1 (0x9800 - VRAM_ADDR) +#define VRAM_BMAP_2 (0x9C00 - VRAM_ADDR) +#define VRAM_TILES_3 (0x8000 - VRAM_ADDR + VRAM_BANK_SIZE) +#define VRAM_TILES_4 (0x8800 - VRAM_ADDR + VRAM_BANK_SIZE) + +/* Interrupt jump addresses */ +#define VBLANK_INTR_ADDR 0x0040 +#define LCDC_INTR_ADDR 0x0048 +#define TIMER_INTR_ADDR 0x0050 +#define SERIAL_INTR_ADDR 0x0058 +#define CONTROL_INTR_ADDR 0x0060 + +/* SPRITE controls */ +#define NUM_SPRITES 0x28 +#define MAX_SPRITES_LINE 0x0A +#define OBJ_PRIORITY 0x80 +#define OBJ_FLIP_Y 0x40 +#define OBJ_FLIP_X 0x20 +#define OBJ_PALETTE 0x10 + +/* Joypad buttons */ +#define JOYPAD_A 0x01 +#define JOYPAD_B 0x02 +#define JOYPAD_SELECT 0x04 +#define JOYPAD_START 0x08 +#define JOYPAD_RIGHT 0x10 +#define JOYPAD_LEFT 0x20 +#define JOYPAD_UP 0x40 +#define JOYPAD_DOWN 0x80 + +#define ROM_HEADER_CHECKSUM_LOC 0x014D + +/* Local macros. */ +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#define PEANUT_GB_ARRAYSIZE(array) (sizeof(array) / sizeof(array[0])) + +/** Allow setting deprecated functions and variables. */ +#if (defined(__GNUC__) && __GNUC__ >= 6) || (defined(__clang__) && __clang_major__ >= 4) +#define PGB_DEPRECATED(msg) __attribute__((deprecated(msg))) +#else +#define PGB_DEPRECATED(msg) +#endif + +#if !defined(__has_builtin) +/* Stub __has_builtin if it isn't available. */ +#define __has_builtin(x) 0 +#endif + +/* The PGB_UNREACHABLE() macro tells the compiler that the code path will never + * be reached, allowing for further optimisation. */ +#if !defined(PGB_UNREACHABLE) +#if __has_builtin(__builtin_unreachable) +#define PGB_UNREACHABLE() __builtin_unreachable() +#elif defined(_MSC_VER) && _MSC_VER >= 1200 +#/* __assume is not available before VC6. */ +#define PGB_UNREACHABLE() __assume(0) +#else +#define PGB_UNREACHABLE() abort() +#endif +#endif /* !defined(PGB_UNREACHABLE) */ + +#if !defined(PGB_UNLIKELY) +#if __has_builtin(__builtin_expect) +#define PGB_UNLIKELY(expr) __builtin_expect(!!(expr), 0) +#else +#define PGB_UNLIKELY(expr) (expr) +#endif +#endif /* !defined(PGB_UNLIKELY) */ +#if !defined(PGB_LIKELY) +#if __has_builtin(__builtin_expect) +#define PGB_LIKELY(expr) __builtin_expect(!!(expr), 1) +#else +#define PGB_LIKELY(expr) (expr) +#endif +#endif /* !defined(PGB_LIKELY) */ + +#if PEANUT_GB_USE_INTRINSICS +/* If using MSVC, only enable intrinsics for x86 platforms*/ +#if defined(_MSC_VER) && \ + __has_include("intrin.h") && (defined(_M_IX86_FP) || defined(_M_AMD64) || defined(_M_X64)) +/* Define intrinsic functions for MSVC. */ +#include +#define PGB_INTRIN_SBC(x, y, cin, res) _subborrow_u8(cin, x, y, &res) +#define PGB_INTRIN_ADC(x, y, cin, res) _addcarry_u8(cin, x, y, &res) +#endif /* MSVC */ + +/* Check for intrinsic functions in GCC and Clang. */ +#if __has_builtin(__builtin_sub_overflow) +#define PGB_INTRIN_SBC(x, y, cin, res) __builtin_sub_overflow(x, y + cin, &res) +#define PGB_INTRIN_ADC(x, y, cin, res) __builtin_add_overflow(x, y + cin, &res) +#endif +#endif /* PEANUT_GB_USE_INTRINSICS */ + +#if defined(PGB_INTRIN_SBC) +#define PGB_INSTR_SBC_R8(r, cin) \ + { \ + uint8_t temp; \ + gb->cpu_reg.f.f_bits.c = PGB_INTRIN_SBC(gb->cpu_reg.a, r, cin, temp); \ + gb->cpu_reg.f.f_bits.h = ((gb->cpu_reg.a ^ r ^ temp) & 0x10) > 0; \ + gb->cpu_reg.f.f_bits.n = 1; \ + gb->cpu_reg.f.f_bits.z = (temp == 0x00); \ + gb->cpu_reg.a = temp; \ + } + +#define PGB_INSTR_CP_R8(r) \ + { \ + uint8_t temp; \ + gb->cpu_reg.f.f_bits.c = PGB_INTRIN_SBC(gb->cpu_reg.a, r, 0, temp); \ + gb->cpu_reg.f.f_bits.h = ((gb->cpu_reg.a ^ r ^ temp) & 0x10) > 0; \ + gb->cpu_reg.f.f_bits.n = 1; \ + gb->cpu_reg.f.f_bits.z = (temp == 0x00); \ + } +#else +#define PGB_INSTR_SBC_R8(r, cin) \ + { \ + uint16_t temp = gb->cpu_reg.a - (r + cin); \ + gb->cpu_reg.f.f_bits.c = (temp & 0xFF00) ? 1 : 0; \ + gb->cpu_reg.f.f_bits.h = ((gb->cpu_reg.a ^ r ^ temp) & 0x10) > 0; \ + gb->cpu_reg.f.f_bits.n = 1; \ + gb->cpu_reg.f.f_bits.z = ((temp & 0xFF) == 0x00); \ + gb->cpu_reg.a = (temp & 0xFF); \ + } + +#define PGB_INSTR_CP_R8(r) \ + { \ + uint16_t temp = gb->cpu_reg.a - r; \ + gb->cpu_reg.f.f_bits.c = (temp & 0xFF00) ? 1 : 0; \ + gb->cpu_reg.f.f_bits.h = ((gb->cpu_reg.a ^ r ^ temp) & 0x10) > 0; \ + gb->cpu_reg.f.f_bits.n = 1; \ + gb->cpu_reg.f.f_bits.z = ((temp & 0xFF) == 0x00); \ + } +#endif /* PGB_INTRIN_SBC */ + +#if defined(PGB_INTRIN_ADC) +#define PGB_INSTR_ADC_R8(r, cin) \ + { \ + uint8_t temp; \ + gb->cpu_reg.f.f_bits.c = PGB_INTRIN_ADC(gb->cpu_reg.a, r, cin, temp); \ + gb->cpu_reg.f.f_bits.h = ((gb->cpu_reg.a ^ r ^ temp) & 0x10) > 0; \ + gb->cpu_reg.f.f_bits.n = 0; \ + gb->cpu_reg.f.f_bits.z = (temp == 0x00); \ + gb->cpu_reg.a = temp; \ + } +#else +#define PGB_INSTR_ADC_R8(r, cin) \ + { \ + uint16_t temp = gb->cpu_reg.a + r + cin; \ + gb->cpu_reg.f.f_bits.c = (temp & 0xFF00) ? 1 : 0; \ + gb->cpu_reg.f.f_bits.h = ((gb->cpu_reg.a ^ r ^ temp) & 0x10) > 0; \ + gb->cpu_reg.f.f_bits.n = 0; \ + gb->cpu_reg.f.f_bits.z = ((temp & 0xFF) == 0x00); \ + gb->cpu_reg.a = (temp & 0xFF); \ + } +#endif /* PGB_INTRIN_ADC */ + +#define PGB_INSTR_INC_R8(r) \ + r++; \ + gb->cpu_reg.f.f_bits.h = ((r & 0x0F) == 0x00); \ + gb->cpu_reg.f.f_bits.n = 0; \ + gb->cpu_reg.f.f_bits.z = (r == 0x00) + +#define PGB_INSTR_DEC_R8(r) \ + r--; \ + gb->cpu_reg.f.f_bits.h = ((r & 0x0F) == 0x0F); \ + gb->cpu_reg.f.f_bits.n = 1; \ + gb->cpu_reg.f.f_bits.z = (r == 0x00) + +#define PGB_INSTR_XOR_R8(r) \ + gb->cpu_reg.a ^= r; \ + gb->cpu_reg.f.reg = 0; \ + gb->cpu_reg.f.f_bits.z = (gb->cpu_reg.a == 0x00) + +#define PGB_INSTR_OR_R8(r) \ + gb->cpu_reg.a |= r; \ + gb->cpu_reg.f.reg = 0; \ + gb->cpu_reg.f.f_bits.z = (gb->cpu_reg.a == 0x00) + +#define PGB_INSTR_AND_R8(r) \ + gb->cpu_reg.a &= r; \ + gb->cpu_reg.f.reg = 0; \ + gb->cpu_reg.f.f_bits.z = (gb->cpu_reg.a == 0x00); \ + gb->cpu_reg.f.f_bits.h = 1 + +#if PEANUT_GB_IS_LITTLE_ENDIAN +#define PEANUT_GB_GET_LSB16(x) (x & 0xFF) +#define PEANUT_GB_GET_MSB16(x) (x >> 8) +#define PEANUT_GB_GET_MSN16(x) (x >> 12) +#define PEANUT_GB_U8_TO_U16(h, l) ((l) | ((h) << 8)) +#else +#define PEANUT_GB_GET_LSB16(x) (x >> 8) +#define PEANUT_GB_GET_MSB16(x) (x & 0xFF) +#define PEANUT_GB_GET_MSN16(x) ((x & 0xF0) >> 4) +#define PEANUT_GB_U8_TO_U16(h, l) ((h) | ((l) << 8)) +#endif + +struct cpu_registers_s { +/* Change register order if big endian. + * Macro receives registers in little endian order. */ +#if PEANUT_GB_IS_LITTLE_ENDIAN +#define PEANUT_GB_LE_REG(x, y) x, y +#else +#define PEANUT_GB_LE_REG(x, y) y, x +#endif + /* Define specific bits of Flag register. */ + union { + struct { + uint8_t : 4; /* Unused. */ + uint8_t c : 1; /* Carry flag. */ + uint8_t h : 1; /* Half carry flag. */ + uint8_t n : 1; /* Add/sub flag. */ + uint8_t z : 1; /* Zero flag. */ + } f_bits; + uint8_t reg; + } f; + uint8_t a; + + union { + struct { + uint8_t PEANUT_GB_LE_REG(c, b); + } bytes; + uint16_t reg; + } bc; + + union { + struct { + uint8_t PEANUT_GB_LE_REG(e, d); + } bytes; + uint16_t reg; + } de; + + union { + struct { + uint8_t PEANUT_GB_LE_REG(l, h); + } bytes; + uint16_t reg; + } hl; + + /* Stack pointer */ + union { + struct { + uint8_t PEANUT_GB_LE_REG(p, s); + } bytes; + uint16_t reg; + } sp; + + /* Program counter */ + union { + struct { + uint8_t PEANUT_GB_LE_REG(c, p); + } bytes; + uint16_t reg; + } pc; +#undef PEANUT_GB_LE_REG +}; + +struct count_s { + uint_fast16_t lcd_count; /* LCD Timing */ + uint_fast16_t div_count; /* Divider Register Counter */ + uint_fast16_t tima_count; /* Timer Counter */ + uint_fast16_t serial_count; /* Serial Counter */ + uint_fast32_t rtc_count; /* RTC Counter */ + uint_fast32_t lcd_off_count; /* Cycles LCD has been disabled */ +}; + +#if ENABLE_LCD +/* Bit mask for the shade of pixel to display */ +#define LCD_COLOUR 0x03 + +#if PEANUT_GB_12_COLOUR +/** + * Bit mask for whether a pixel is OBJ0, OBJ1, or BG. Each may have a different + * palette when playing a DMG game on CGB. + */ +#define LCD_PALETTE_OBJ 0x10 +#define LCD_PALETTE_BG 0x20 +/** + * Bit mask for the two bits listed above. + * LCD_PALETTE_ALL == 0b00 --> OBJ0 + * LCD_PALETTE_ALL == 0b01 --> OBJ1 + * LCD_PALETTE_ALL == 0b10 --> BG + * LCD_PALETTE_ALL == 0b11 --> NOT POSSIBLE + */ +#define LCD_PALETTE_ALL 0x30 +#endif +#endif + +/** + * Errors that may occur during emulation. + */ +enum gb_error_e { + GB_UNKNOWN_ERROR = 0, + GB_INVALID_OPCODE = 1, + GB_INVALID_READ = 2, + GB_INVALID_WRITE = 3, + + /* GB_HALT_FOREVER is deprecated and will no longer be issued as an + * error by Peanut-GB. */ + GB_HALT_FOREVER PGB_DEPRECATED("Error no longer issued by Peanut-GB") = 4, + + GB_INVALID_MAX = 5 +}; + +/** + * Errors that may occur during library initialisation. + */ +enum gb_init_error_e { + GB_INIT_NO_ERROR = 0, + GB_INIT_CARTRIDGE_UNSUPPORTED, + GB_INIT_INVALID_CHECKSUM, + + GB_INIT_INVALID_MAX +}; + +/** + * Return codes for serial receive function, mainly for clarity. + */ +enum gb_serial_rx_ret_e { GB_SERIAL_RX_SUCCESS = 0, GB_SERIAL_RX_NO_CONNECTION = 1 }; + +union cart_rtc { + struct { + uint8_t sec; + uint8_t min; + uint8_t hour; + uint8_t yday; + uint8_t high; + } reg; + uint8_t bytes[5]; +}; + +/** + * Emulator context. + * + * Only values within the `direct` struct may be modified directly by the + * front-end implementation. Other variables must not be modified. + */ +struct gb_s { + /** + * Return byte from ROM at given address. + * + * \param gb_s emulator context + * \param addr address + * \return byte at address in ROM + */ + uint8_t (*gb_rom_read)(struct gb_s *, const uint_fast32_t addr); + + /** + * Return byte from cart RAM at given address. + * + * \param gb_s emulator context + * \param addr address + * \return byte at address in RAM + */ + uint8_t (*gb_cart_ram_read)(struct gb_s *, const uint_fast32_t addr); + + /** + * Write byte to cart RAM at given address. + * + * \param gb_s emulator context + * \param addr address + * \param val value to write to address in RAM + */ + void (*gb_cart_ram_write)(struct gb_s *, const uint_fast32_t addr, const uint8_t val); + + /** + * Notify front-end of error. + * + * \param gb_s emulator context + * \param gb_error_e error code + * \param addr address of where error occurred + */ + void (*gb_error)(struct gb_s *, const enum gb_error_e, const uint16_t addr); + + /* Transmit one byte and return the received byte. */ + void (*gb_serial_tx)(struct gb_s *, const uint8_t tx); + enum gb_serial_rx_ret_e (*gb_serial_rx)(struct gb_s *, uint8_t *rx); + + /* Read byte from boot ROM at given address. */ + uint8_t (*gb_bootrom_read)(struct gb_s *, const uint_fast16_t addr); + + struct { + bool gb_halt : 1; + bool gb_ime : 1; + /* gb_frame is set when 0.016742706298828125 seconds have + * passed. It is likely that a new frame has been drawn since + * then, but it is possible that the LCD was switched off and + * nothing was drawn. */ + bool gb_frame : 1; + bool lcd_blank : 1; + /* Set if MBC3O cart is used. */ + bool cart_is_mbc3O : 1; + }; + + /* Cartridge information: + * Memory Bank Controller (MBC) type. */ + int8_t mbc; + /* Whether the MBC has internal RAM. */ + uint8_t cart_ram; + /* Number of ROM banks in cartridge. */ + uint16_t num_rom_banks_mask; + /* Number of RAM banks in cartridge. Ignore for MBC2. */ + uint8_t num_ram_banks; + + uint16_t selected_rom_bank; + /* WRAM and VRAM bank selection not available. */ + uint8_t cart_ram_bank; + uint8_t enable_cart_ram; + /* Cartridge ROM/RAM mode select. */ + uint8_t cart_mode_select; + + union cart_rtc rtc_latched, rtc_real; + + struct cpu_registers_s cpu_reg; + // struct gb_registers_s gb_reg; + struct count_s counter; + + /* TODO: Allow implementation to allocate WRAM, VRAM and Frame Buffer. */ + uint8_t wram[WRAM_SIZE]; + uint8_t vram[VRAM_SIZE]; + uint8_t oam[OAM_SIZE]; + uint8_t hram_io[HRAM_IO_SIZE]; + + struct { + /** + * Draw line on screen. + * + * \param gb_s emulator context + * \param pixels The 160 pixels to draw. + * Bits 1-0 are the colour to draw. + * Bits 5-4 are the palette, where: + * OBJ0 = 0b00, + * OBJ1 = 0b01, + * BG = 0b10 + * Other bits are undefined. + * Bits 5-4 are only required by front-ends + * which want to use a different colour for + * different object palettes. This is what + * the Game Boy Color (CGB) does to DMG + * games. + * \param line Line to draw pixels on. This is + * guaranteed to be between 0-144 inclusive. + */ + void (*lcd_draw_line)(struct gb_s *gb, const uint8_t *pixels, const uint_fast8_t line); + + /* Palettes */ + uint8_t bg_palette[4]; + uint8_t sp_palette[8]; + + uint8_t window_clear; + uint8_t WY; + + /* Only support 30fps frame skip. */ + bool frame_skip_count : 1; + bool interlace_count : 1; + } display; + + /** + * Variables that may be modified directly by the front-end. + * This method seems to be easier and possibly less overhead than + * calling a function to modify these variables each time. + * + * None of this is thread-safe. + */ + struct { + /* Set to enable interlacing. Interlacing will start immediately + * (at the next line drawing). + */ + bool interlace : 1; + bool frame_skip : 1; + + union { + struct { + /* Using this bitfield is deprecated due to + * portability concerns. It is recommended to + * use the JOYPAD_* defines instead. + */ + bool a : 1; + bool b : 1; + bool select : 1; + bool start : 1; + bool right : 1; + bool left : 1; + bool up : 1; + bool down : 1; + } joypad_bits; + uint8_t joypad; + }; + + /* Implementation defined data. Set to NULL if not required. */ + void *priv; + } direct; +}; + +#ifndef PEANUT_GB_HEADER_ONLY + +#define IO_JOYP 0x00 +#define IO_SB 0x01 +#define IO_SC 0x02 +#define IO_DIV 0x04 +#define IO_TIMA 0x05 +#define IO_TMA 0x06 +#define IO_TAC 0x07 +#define IO_IF 0x0F +#define IO_LCDC 0x40 +#define IO_STAT 0x41 +#define IO_SCY 0x42 +#define IO_SCX 0x43 +#define IO_LY 0x44 +#define IO_LYC 0x45 +#define IO_DMA 0x46 +#define IO_BGP 0x47 +#define IO_OBP0 0x48 +#define IO_OBP1 0x49 +#define IO_WY 0x4A +#define IO_WX 0x4B +#define IO_BOOT 0x50 +#define IO_IE 0xFF + +#define IO_TAC_RATE_MASK 0x3 +#define IO_TAC_ENABLE_MASK 0x4 + +/* LCD Mode defines. */ +#define IO_STAT_MODE_HBLANK 0 +#define IO_STAT_MODE_VBLANK 1 +#define IO_STAT_MODE_OAM_SCAN 2 +#define IO_STAT_MODE_LCD_DRAW 3 +#define IO_STAT_MODE_VBLANK_OR_TRANSFER_MASK 0x1 + +/** + * Internal function used to read bytes. + * addr is host platform endian. + */ +uint8_t __gb_read(struct gb_s *gb, uint16_t addr) { + switch (PEANUT_GB_GET_MSN16(addr)) { + case 0x0: + /* IO_BOOT is only set to 1 if gb->gb_bootrom_read was not NULL + * on reset. */ + if (gb->hram_io[IO_BOOT] == 0 && addr < 0x0100) { + return gb->gb_bootrom_read(gb, addr); + } + + /* Fallthrough */ + case 0x1: + case 0x2: + case 0x3: + return gb->gb_rom_read(gb, addr); + + case 0x4: + case 0x5: + case 0x6: + case 0x7: + if (gb->mbc == 1 && gb->cart_mode_select) + return gb->gb_rom_read(gb, addr + ((gb->selected_rom_bank & 0x1F) - 1) * ROM_BANK_SIZE); + else + return gb->gb_rom_read(gb, addr + (gb->selected_rom_bank - 1) * ROM_BANK_SIZE); + + case 0x8: + case 0x9: + return gb->vram[addr - VRAM_ADDR]; + + case 0xA: + case 0xB: + if (gb->mbc == 3 && gb->cart_ram_bank >= 0x08) { + return gb->rtc_latched.bytes[gb->cart_ram_bank - 0x08]; + } else if (gb->cart_ram && gb->enable_cart_ram) { + if (gb->mbc == 2) { + /* Only 9 bits are available in address. */ + addr &= 0x1FF; + return gb->gb_cart_ram_read(gb, addr); + } else if ((gb->cart_mode_select || gb->mbc != 1) && + gb->cart_ram_bank < gb->num_ram_banks) { + return gb->gb_cart_ram_read(gb, + addr - CART_RAM_ADDR + (gb->cart_ram_bank * CRAM_BANK_SIZE)); + } else + return gb->gb_cart_ram_read(gb, addr - CART_RAM_ADDR); + } + + return 0xFF; + + case 0xC: + case 0xD: + return gb->wram[addr - WRAM_0_ADDR]; + + case 0xE: + return gb->wram[addr - ECHO_ADDR]; + + case 0xF: + if (addr < OAM_ADDR) + return gb->wram[addr - ECHO_ADDR]; + + if (addr < UNUSED_ADDR) + return gb->oam[addr - OAM_ADDR]; + + /* Unusable memory area. Reading from this area returns 0xFF.*/ + if (addr < IO_ADDR) + return 0xFF; + + /* APU registers. */ + if ((addr >= 0xFF10) && (addr <= 0xFF3F)) { +#if ENABLE_SOUND + return audio_read(addr); +#else + static const uint8_t ortab[] = {0x80, 0x3f, 0x00, 0xff, 0xbf, 0xff, 0x3f, 0x00, 0xff, 0xbf, + 0x7f, 0xff, 0x9f, 0xff, 0xbf, 0xff, 0xff, 0x00, 0x00, 0xbf, + 0x00, 0x00, 0x70, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + return gb->hram_io[addr - IO_ADDR] | ortab[addr - IO_ADDR]; +#endif + } + + /* HRAM */ + if (addr >= IO_ADDR) + return gb->hram_io[addr - IO_ADDR]; + } + + /* Return address that caused read error. */ + (gb->gb_error)(gb, GB_INVALID_READ, addr); + PGB_UNREACHABLE(); +} + +/** + * Internal function used to write bytes. + */ +void __gb_write(struct gb_s *gb, uint_fast16_t addr, uint8_t val) { + switch (PEANUT_GB_GET_MSN16(addr)) { + case 0x0: + case 0x1: + /* Set RAM enable bit. MBC2 is handled in fall-through. */ + if (gb->mbc > 0 && gb->mbc != 2) { + if (gb->cart_ram) + gb->enable_cart_ram = ((val & 0x0F) == 0x0A); + return; + } + + /* Intentional fall through. */ + case 0x2: + if (gb->mbc == 5) { + gb->selected_rom_bank = (gb->selected_rom_bank & 0x100) | val; + gb->selected_rom_bank = gb->selected_rom_bank & gb->num_rom_banks_mask; + return; + } + + /* Intentional fall through. */ + case 0x3: + if (gb->mbc == 1) { + // selected_rom_bank = val & 0x7; + gb->selected_rom_bank = (val & 0x1F) | (gb->selected_rom_bank & 0x60); + + if ((gb->selected_rom_bank & 0x1F) == 0x00) + gb->selected_rom_bank++; + } else if (gb->mbc == 2) { + /* If bit 8 is 1, then set ROM bank number. */ + if (addr & 0x100) { + gb->selected_rom_bank = val & 0x0F; + /* Setting ROM bank to 0, sets it to 1. */ + if (!gb->selected_rom_bank) + gb->selected_rom_bank++; + } + /* Otherwise set whether RAM is enabled or not. */ + else { + gb->enable_cart_ram = ((val & 0x0F) == 0x0A); + return; + } + } else if (gb->mbc == 3) { + gb->selected_rom_bank = val; + if (!gb->cart_is_mbc3O) + gb->selected_rom_bank = val & 0x7F; + + if (!gb->selected_rom_bank) + gb->selected_rom_bank++; + } else if (gb->mbc == 5) + gb->selected_rom_bank = (val & 0x01) << 8 | (gb->selected_rom_bank & 0xFF); + + gb->selected_rom_bank = gb->selected_rom_bank & gb->num_rom_banks_mask; + return; + + case 0x4: + case 0x5: + if (gb->mbc == 1) { + gb->cart_ram_bank = (val & 3); + gb->selected_rom_bank = ((val & 3) << 5) | (gb->selected_rom_bank & 0x1F); + gb->selected_rom_bank = gb->selected_rom_bank & gb->num_rom_banks_mask; + } else if (gb->mbc == 3) { + gb->cart_ram_bank = val; + /* If not using MBC3, only the first 4 cart RAM banks are useable. + * If cart RAM bank 0x8-0xC are selected, then the corresponding + * RTC register is selected instead of cart RAM. */ + if (!gb->cart_is_mbc3O && gb->cart_ram_bank < 0x8) + gb->cart_ram_bank &= 0x3; + } + + else if (gb->mbc == 5) + gb->cart_ram_bank = (val & 0x0F); + + return; + + case 0x6: + case 0x7: + val &= 1; + if (gb->mbc == 3 && val && gb->cart_mode_select == 0) + memcpy(&gb->rtc_latched.bytes, &gb->rtc_real.bytes, sizeof(gb->rtc_latched.bytes)); + + /* Set banking mode select. */ + gb->cart_mode_select = val; + return; + + case 0x8: + case 0x9: + gb->vram[addr - VRAM_ADDR] = val; + return; + + case 0xA: + case 0xB: + if (gb->mbc == 3 && gb->cart_ram_bank >= 0x08) { + const uint8_t rtc_reg_mask[5] = {0x3F, 0x3F, 0x1F, 0xFF, 0xC1}; + uint8_t reg = gb->cart_ram_bank - 0x08; + // if(reg == 0) gb->counter.rtc_count = 0; + + gb->rtc_real.bytes[reg] = val & rtc_reg_mask[reg]; + } + /* Do not write to RAM if unavailable or disabled. */ + else if (gb->cart_ram && gb->enable_cart_ram) { + if (gb->mbc == 2) { + /* Only 9 bits are available in address. */ + addr &= 0x1FF; + /* Data is only 4 bits wide in MBC2 RAM. */ + val &= 0x0F; + /* Upper nibble is set to high. */ + val |= 0xF0; + gb->gb_cart_ram_write(gb, addr, val); + } + /* If cart has RAM, use this. If MBC1, only the first + * RAM bank can be written to if the advanced banking + * mode is selected. */ + else if (((gb->mbc == 1 && gb->cart_mode_select) || gb->mbc != 1) && + gb->cart_ram_bank < gb->num_ram_banks) { + gb->gb_cart_ram_write( + gb, addr - CART_RAM_ADDR + (gb->cart_ram_bank * CRAM_BANK_SIZE), val); + } else if (gb->num_ram_banks) + gb->gb_cart_ram_write(gb, addr - CART_RAM_ADDR, val); + } + + return; + + case 0xC: + gb->wram[addr - WRAM_0_ADDR] = val; + return; + + case 0xD: + gb->wram[addr - WRAM_1_ADDR + WRAM_BANK_SIZE] = val; + return; + + case 0xE: + gb->wram[addr - ECHO_ADDR] = val; + return; + + case 0xF: + if (addr < OAM_ADDR) { + gb->wram[addr - ECHO_ADDR] = val; + return; + } + + if (addr < UNUSED_ADDR) { + gb->oam[addr - OAM_ADDR] = val; + return; + } + + /* Unusable memory area. */ + if (addr < IO_ADDR) + return; + + if (HRAM_ADDR <= addr && addr < INTR_EN_ADDR) { + gb->hram_io[addr - IO_ADDR] = val; + return; + } + + if ((addr >= 0xFF10) && (addr <= 0xFF3F)) { +#if ENABLE_SOUND + audio_write(addr, val); +#else + gb->hram_io[addr - IO_ADDR] = val; +#endif + return; + } + + /* IO and Interrupts. */ + switch (PEANUT_GB_GET_LSB16(addr)) { + /* Joypad */ + case 0x00: + /* Only bits 5 and 4 are R/W. + * The lower bits are overwritten later, and the two most + * significant bits are unused. */ + gb->hram_io[IO_JOYP] = val; + + /* Direction keys selected */ + if ((gb->hram_io[IO_JOYP] & 0x10) == 0) + gb->hram_io[IO_JOYP] |= (gb->direct.joypad >> 4); + /* Button keys selected */ + else + gb->hram_io[IO_JOYP] |= (gb->direct.joypad & 0x0F); + + return; + + /* Serial */ + case 0x01: + gb->hram_io[IO_SB] = val; + return; + + case 0x02: + gb->hram_io[IO_SC] = val; + return; + + /* Timer Registers */ + case 0x04: + gb->hram_io[IO_DIV] = 0x00; + return; + + case 0x05: + gb->hram_io[IO_TIMA] = val; + return; + + case 0x06: + gb->hram_io[IO_TMA] = val; + return; + + case 0x07: + gb->hram_io[IO_TAC] = val; + return; + + /* Interrupt Flag Register */ + case 0x0F: + gb->hram_io[IO_IF] = (val | 0xE0); + return; + + /* LCD Registers */ + case 0x40: { + uint8_t lcd_enabled; + + /* Check if LCD is already enabled. */ + lcd_enabled = (gb->hram_io[IO_LCDC] & LCDC_ENABLE); + + gb->hram_io[IO_LCDC] = val; + + /* Check if LCD is going to be switched on. */ + if (!lcd_enabled && (val & LCDC_ENABLE)) { + gb->lcd_blank = true; + } + /* Check if LCD is being switched off. */ + else if (lcd_enabled && !(val & LCDC_ENABLE)) { + /* Peanut-GB will happily turn off LCD outside + * of VBLANK even though this damages real + * hardware. */ + + /* Set LCD to Mode 0. */ + gb->hram_io[IO_STAT] = (gb->hram_io[IO_STAT] & ~STAT_MODE) | IO_STAT_MODE_HBLANK; + /* LY fixed to 0 when LCD turned off. */ + gb->hram_io[IO_LY] = 0; + /* Keep track of lcd_count to correctly track + * passing time. */ + gb->counter.lcd_off_count += gb->counter.lcd_count; + /* Reset LCD timer, since the LCD starts from + * the beginning on power on. */ + gb->counter.lcd_count = 0; + } + return; + } + + case 0x41: + gb->hram_io[IO_STAT] = (val & STAT_USER_BITS) | (gb->hram_io[IO_STAT] & STAT_MODE) | 0x80; + return; + + case 0x42: + gb->hram_io[IO_SCY] = val; + return; + + case 0x43: + gb->hram_io[IO_SCX] = val; + return; + + /* LY (0xFF44) is read only. */ + case 0x45: + gb->hram_io[IO_LYC] = val; + return; + + /* DMA Register */ + case 0x46: { + uint16_t dma_addr; + uint16_t i; + + dma_addr = (uint_fast16_t)val << 8; + gb->hram_io[IO_DMA] = val; + + for (i = 0; i < OAM_SIZE; i++) { + gb->oam[i] = __gb_read(gb, dma_addr + i); + } + + return; + } + + /* DMG Palette Registers */ + case 0x47: + gb->hram_io[IO_BGP] = val; + gb->display.bg_palette[0] = (gb->hram_io[IO_BGP] & 0x03); + gb->display.bg_palette[1] = (gb->hram_io[IO_BGP] >> 2) & 0x03; + gb->display.bg_palette[2] = (gb->hram_io[IO_BGP] >> 4) & 0x03; + gb->display.bg_palette[3] = (gb->hram_io[IO_BGP] >> 6) & 0x03; + return; + + case 0x48: + gb->hram_io[IO_OBP0] = val; + gb->display.sp_palette[0] = (gb->hram_io[IO_OBP0] & 0x03); + gb->display.sp_palette[1] = (gb->hram_io[IO_OBP0] >> 2) & 0x03; + gb->display.sp_palette[2] = (gb->hram_io[IO_OBP0] >> 4) & 0x03; + gb->display.sp_palette[3] = (gb->hram_io[IO_OBP0] >> 6) & 0x03; + return; + + case 0x49: + gb->hram_io[IO_OBP1] = val; + gb->display.sp_palette[4] = (gb->hram_io[IO_OBP1] & 0x03); + gb->display.sp_palette[5] = (gb->hram_io[IO_OBP1] >> 2) & 0x03; + gb->display.sp_palette[6] = (gb->hram_io[IO_OBP1] >> 4) & 0x03; + gb->display.sp_palette[7] = (gb->hram_io[IO_OBP1] >> 6) & 0x03; + return; + + /* Window Position Registers */ + case 0x4A: + gb->hram_io[IO_WY] = val; + return; + + case 0x4B: + gb->hram_io[IO_WX] = val; + return; + + /* Turn off boot ROM */ + case 0x50: + gb->hram_io[IO_BOOT] = 0x01; + return; + + /* Interrupt Enable Register */ + case 0xFF: + gb->hram_io[IO_IE] = val; + return; + } + } + + /* Invalid writes are ignored. */ + return; +} + +uint8_t __gb_execute_cb(struct gb_s *gb) { + uint8_t inst_cycles; + uint8_t cbop = __gb_read(gb, gb->cpu_reg.pc.reg++); + uint8_t r = (cbop & 0x7); + uint8_t b = (cbop >> 3) & 0x7; + uint8_t d = (cbop >> 3) & 0x1; + uint8_t val; + uint8_t writeback = 1; + + inst_cycles = 8; + /* Add an additional 8 cycles to these sets of instructions. */ + switch (cbop & 0xC7) { + case 0x06: + case 0x86: + case 0xC6: + inst_cycles += 8; + break; + case 0x46: + inst_cycles += 4; + break; + } + + switch (r) { + case 0: + val = gb->cpu_reg.bc.bytes.b; + break; + + case 1: + val = gb->cpu_reg.bc.bytes.c; + break; + + case 2: + val = gb->cpu_reg.de.bytes.d; + break; + + case 3: + val = gb->cpu_reg.de.bytes.e; + break; + + case 4: + val = gb->cpu_reg.hl.bytes.h; + break; + + case 5: + val = gb->cpu_reg.hl.bytes.l; + break; + + case 6: + val = __gb_read(gb, gb->cpu_reg.hl.reg); + break; + + /* Only values 0-7 are possible here, so we make the final case + * default to satisfy -Wmaybe-uninitialized warning. */ + default: + val = gb->cpu_reg.a; + break; + } + + switch (cbop >> 6) { + case 0x0: + cbop = (cbop >> 4) & 0x3; + + switch (cbop) { + case 0x0: /* RdC R */ + case 0x1: /* Rd R */ + if (d) /* RRC R / RR R */ + { + uint8_t temp = val; + val = (val >> 1); + val |= cbop ? (gb->cpu_reg.f.f_bits.c << 7) : (temp << 7); + gb->cpu_reg.f.reg = 0; + gb->cpu_reg.f.f_bits.z = (val == 0x00); + gb->cpu_reg.f.f_bits.c = (temp & 0x01); + } else /* RLC R / RL R */ + { + uint8_t temp = val; + val = (val << 1); + val |= cbop ? gb->cpu_reg.f.f_bits.c : (temp >> 7); + gb->cpu_reg.f.reg = 0; + gb->cpu_reg.f.f_bits.z = (val == 0x00); + gb->cpu_reg.f.f_bits.c = (temp >> 7); + } + + break; + + case 0x2: + if (d) /* SRA R */ + { + gb->cpu_reg.f.reg = 0; + gb->cpu_reg.f.f_bits.c = val & 0x01; + val = (val >> 1) | (val & 0x80); + gb->cpu_reg.f.f_bits.z = (val == 0x00); + } else /* SLA R */ + { + gb->cpu_reg.f.reg = 0; + gb->cpu_reg.f.f_bits.c = (val >> 7); + val = val << 1; + gb->cpu_reg.f.f_bits.z = (val == 0x00); + } + + break; + + case 0x3: + if (d) /* SRL R */ + { + gb->cpu_reg.f.reg = 0; + gb->cpu_reg.f.f_bits.c = val & 0x01; + val = val >> 1; + gb->cpu_reg.f.f_bits.z = (val == 0x00); + } else /* SWAP R */ + { + uint8_t temp = (val >> 4) & 0x0F; + temp |= (val << 4) & 0xF0; + val = temp; + gb->cpu_reg.f.reg = 0; + gb->cpu_reg.f.f_bits.z = (val == 0x00); + } + + break; + } + + break; + + case 0x1: /* BIT B, R */ + gb->cpu_reg.f.f_bits.z = !((val >> b) & 0x1); + gb->cpu_reg.f.f_bits.n = 0; + gb->cpu_reg.f.f_bits.h = 1; + writeback = 0; + break; + + case 0x2: /* RES B, R */ + val &= (0xFE << b) | (0xFF >> (8 - b)); + break; + + case 0x3: /* SET B, R */ + val |= (0x1 << b); + break; + } + + if (writeback) { + switch (r) { + case 0: + gb->cpu_reg.bc.bytes.b = val; + break; + + case 1: + gb->cpu_reg.bc.bytes.c = val; + break; + + case 2: + gb->cpu_reg.de.bytes.d = val; + break; + + case 3: + gb->cpu_reg.de.bytes.e = val; + break; + + case 4: + gb->cpu_reg.hl.bytes.h = val; + break; + + case 5: + gb->cpu_reg.hl.bytes.l = val; + break; + + case 6: + __gb_write(gb, gb->cpu_reg.hl.reg, val); + break; + + case 7: + gb->cpu_reg.a = val; + break; + } + } + return inst_cycles; +} + +#if ENABLE_LCD +struct sprite_data { + uint8_t sprite_number; + uint8_t x; +}; + +#if PEANUT_GB_HIGH_LCD_ACCURACY +static int compare_sprites(const struct sprite_data *const sd1, + const struct sprite_data *const sd2) { + int x_res; + + x_res = (int)sd1->x - (int)sd2->x; + if (x_res != 0) + return x_res; + + return (int)sd1->sprite_number - (int)sd2->sprite_number; +} +#endif + +void __gb_draw_line(struct gb_s *gb) { + uint8_t pixels[160] = {0}; + + /* If LCD not initialised by front-end, don't render anything. */ + if (gb->display.lcd_draw_line == NULL) + return; + + if (gb->direct.frame_skip && !gb->display.frame_skip_count) + return; + + /* If interlaced mode is activated, check if we need to draw the current + * line. */ + if (gb->direct.interlace) { + if ((!gb->display.interlace_count && (gb->hram_io[IO_LY] & 1) == 0) || + (gb->display.interlace_count && (gb->hram_io[IO_LY] & 1) == 1)) { + /* Compensate for missing window draw if required. */ + if (gb->hram_io[IO_LCDC] & LCDC_WINDOW_ENABLE && gb->hram_io[IO_LY] >= gb->display.WY && + gb->hram_io[IO_WX] <= 166) + gb->display.window_clear++; + + return; + } + } + + /* If background is enabled, draw it. */ + if (gb->hram_io[IO_LCDC] & LCDC_BG_ENABLE) { + uint8_t bg_y, disp_x, bg_x, idx, py, px, t1, t2; + uint16_t bg_map, tile; + + /* Calculate current background line to draw. Constant because + * this function draws only this one line each time it is + * called. */ + bg_y = gb->hram_io[IO_LY] + gb->hram_io[IO_SCY]; + + /* Get selected background map address for first tile + * corresponding to current line. + * 0x20 (32) is the width of a background tile, and the bit + * shift is to calculate the address. */ + bg_map = + ((gb->hram_io[IO_LCDC] & LCDC_BG_MAP) ? VRAM_BMAP_2 : VRAM_BMAP_1) + (bg_y >> 3) * 0x20; + + /* The displays (what the player sees) X coordinate, drawn right + * to left. */ + disp_x = LCD_WIDTH - 1; + + /* The X coordinate to begin drawing the background at. */ + bg_x = disp_x + gb->hram_io[IO_SCX]; + + /* Get tile index for current background tile. */ + idx = gb->vram[bg_map + (bg_x >> 3)]; + /* Y coordinate of tile pixel to draw. */ + py = (bg_y & 0x07); + /* X coordinate of tile pixel to draw. */ + px = 7 - (bg_x & 0x07); + + /* Select addressing mode. */ + if (gb->hram_io[IO_LCDC] & LCDC_TILE_SELECT) + tile = VRAM_TILES_1 + idx * 0x10; + else + tile = VRAM_TILES_2 + ((idx + 0x80) % 0x100) * 0x10; + + tile += 2 * py; + + /* fetch first tile */ + t1 = gb->vram[tile] >> px; + t2 = gb->vram[tile + 1] >> px; + + for (; disp_x != 0xFF; disp_x--) { + uint8_t c; + + if (px == 8) { + /* fetch next tile */ + px = 0; + bg_x = disp_x + gb->hram_io[IO_SCX]; + idx = gb->vram[bg_map + (bg_x >> 3)]; + + if (gb->hram_io[IO_LCDC] & LCDC_TILE_SELECT) + tile = VRAM_TILES_1 + idx * 0x10; + else + tile = VRAM_TILES_2 + ((idx + 0x80) % 0x100) * 0x10; + + tile += 2 * py; + t1 = gb->vram[tile]; + t2 = gb->vram[tile + 1]; + } + + /* copy background */ + c = (t1 & 0x1) | ((t2 & 0x1) << 1); + pixels[disp_x] = gb->display.bg_palette[c]; +#if PEANUT_GB_12_COLOUR + pixels[disp_x] |= LCD_PALETTE_BG; +#endif + t1 = t1 >> 1; + t2 = t2 >> 1; + px++; + } + } + + /* draw window */ + if (gb->hram_io[IO_LCDC] & LCDC_WINDOW_ENABLE && gb->hram_io[IO_LY] >= gb->display.WY && + gb->hram_io[IO_WX] <= 166) { + uint16_t win_line, tile; + uint8_t disp_x, win_x, py, px, idx, t1, t2, end; + + /* Calculate Window Map Address. */ + win_line = (gb->hram_io[IO_LCDC] & LCDC_WINDOW_MAP) ? VRAM_BMAP_2 : VRAM_BMAP_1; + win_line += (gb->display.window_clear >> 3) * 0x20; + + disp_x = LCD_WIDTH - 1; + win_x = disp_x - gb->hram_io[IO_WX] + 7; + + // look up tile + py = gb->display.window_clear & 0x07; + px = 7 - (win_x & 0x07); + idx = gb->vram[win_line + (win_x >> 3)]; + + if (gb->hram_io[IO_LCDC] & LCDC_TILE_SELECT) + tile = VRAM_TILES_1 + idx * 0x10; + else + tile = VRAM_TILES_2 + ((idx + 0x80) % 0x100) * 0x10; + + tile += 2 * py; + + // fetch first tile + t1 = gb->vram[tile] >> px; + t2 = gb->vram[tile + 1] >> px; + + // loop & copy window + end = (gb->hram_io[IO_WX] < 7 ? 0 : gb->hram_io[IO_WX] - 7) - 1; + + for (; disp_x != end; disp_x--) { + uint8_t c; + + if (px == 8) { + // fetch next tile + px = 0; + win_x = disp_x - gb->hram_io[IO_WX] + 7; + idx = gb->vram[win_line + (win_x >> 3)]; + + if (gb->hram_io[IO_LCDC] & LCDC_TILE_SELECT) + tile = VRAM_TILES_1 + idx * 0x10; + else + tile = VRAM_TILES_2 + ((idx + 0x80) % 0x100) * 0x10; + + tile += 2 * py; + t1 = gb->vram[tile]; + t2 = gb->vram[tile + 1]; + } + + // copy window + c = (t1 & 0x1) | ((t2 & 0x1) << 1); + pixels[disp_x] = gb->display.bg_palette[c]; +#if PEANUT_GB_12_COLOUR + pixels[disp_x] |= LCD_PALETTE_BG; +#endif + t1 = t1 >> 1; + t2 = t2 >> 1; + px++; + } + + gb->display.window_clear++; // advance window line + } + + // draw sprites + if (gb->hram_io[IO_LCDC] & LCDC_OBJ_ENABLE) { + uint8_t sprite_number; +#if PEANUT_GB_HIGH_LCD_ACCURACY + uint8_t number_of_sprites = 0; + + struct sprite_data sprites_to_render[MAX_SPRITES_LINE]; + + /* Record number of sprites on the line being rendered, limited + * to the maximum number sprites that the Game Boy is able to + * render on each line (10 sprites). */ + for (sprite_number = 0; sprite_number < NUM_SPRITES; sprite_number++) { + /* Sprite Y position. */ + uint8_t OY = gb->oam[4 * sprite_number + 0]; + /* Sprite X position. */ + uint8_t OX = gb->oam[4 * sprite_number + 1]; + + /* If sprite isn't on this line, continue. */ + if (gb->hram_io[IO_LY] + (gb->hram_io[IO_LCDC] & LCDC_OBJ_SIZE ? 0 : 8) >= OY || + gb->hram_io[IO_LY] + 16 < OY) + continue; + + struct sprite_data current; + + current.sprite_number = sprite_number; + current.x = OX; + + uint8_t place; + for (place = number_of_sprites; place != 0; place--) { + if (compare_sprites(&sprites_to_render[place - 1], ¤t) < 0) + break; + } + if (place >= MAX_SPRITES_LINE) + continue; + for (uint8_t i = number_of_sprites; i > place; --i) { + sprites_to_render[i] = sprites_to_render[i - 1]; + } + if (number_of_sprites < MAX_SPRITES_LINE) + number_of_sprites++; + sprites_to_render[place] = current; + } +#endif + + /* Render each sprite, from low priority to high priority. */ +#if PEANUT_GB_HIGH_LCD_ACCURACY + /* Render the top ten prioritised sprites on this scanline. */ + for (sprite_number = number_of_sprites - 1; sprite_number != 0xFF; sprite_number--) { + uint8_t s = sprites_to_render[sprite_number].sprite_number; +#else + for (sprite_number = NUM_SPRITES - 1; sprite_number != 0xFF; sprite_number--) { + uint8_t s = sprite_number; +#endif + uint8_t py, t1, t2, dir, start, end, shift, disp_x; + /* Sprite Y position. */ + uint8_t OY = gb->oam[4 * s + 0]; + /* Sprite X position. */ + uint8_t OX = gb->oam[4 * s + 1]; + /* Sprite Tile/Pattern Number. */ + uint8_t OT = gb->oam[4 * s + 2] & (gb->hram_io[IO_LCDC] & LCDC_OBJ_SIZE ? 0xFE : 0xFF); + /* Additional attributes. */ + uint8_t OF = gb->oam[4 * s + 3]; + +#if !PEANUT_GB_HIGH_LCD_ACCURACY + /* If sprite isn't on this line, continue. */ + if (gb->hram_io[IO_LY] + (gb->hram_io[IO_LCDC] & LCDC_OBJ_SIZE ? 0 : 8) >= OY || + gb->hram_io[IO_LY] + 16 < OY) + continue; +#endif + + /* Continue if sprite not visible. */ + if (OX == 0 || OX >= 168) + continue; + + // y flip + py = gb->hram_io[IO_LY] - OY + 16; + + if (OF & OBJ_FLIP_Y) + py = (gb->hram_io[IO_LCDC] & LCDC_OBJ_SIZE ? 15 : 7) - py; + + // fetch the tile + t1 = gb->vram[VRAM_TILES_1 + OT * 0x10 + 2 * py]; + t2 = gb->vram[VRAM_TILES_1 + OT * 0x10 + 2 * py + 1]; + + // handle x flip + if (OF & OBJ_FLIP_X) { + dir = 1; + start = (OX < 8 ? 0 : OX - 8); + end = MIN(OX, LCD_WIDTH); + shift = 8 - OX + start; + } else { + dir = (uint8_t)-1; + start = MIN(OX, LCD_WIDTH) - 1; + end = (OX < 8 ? 0 : OX - 8) - 1; + shift = OX - (start + 1); + } + + // copy tile + t1 >>= shift; + t2 >>= shift; + + /* TODO: Put for loop within the to if statements + * because the BG priority bit will be the same for + * all the pixels in the tile. */ + for (disp_x = start; disp_x != end; disp_x += dir) { + uint8_t c = (t1 & 0x1) | ((t2 & 0x1) << 1); + // check transparency / sprite overlap / background overlap + + if (c && !(OF & OBJ_PRIORITY && !((pixels[disp_x] & 0x3) == gb->display.bg_palette[0]))) { + /* Set pixel colour. */ + pixels[disp_x] = + (OF & OBJ_PALETTE) ? gb->display.sp_palette[c + 4] : gb->display.sp_palette[c]; +#if PEANUT_GB_12_COLOUR + /* Set pixel palette (OBJ0 or OBJ1). */ + pixels[disp_x] |= (OF & OBJ_PALETTE); +#endif + } + + t1 = t1 >> 1; + t2 = t2 >> 1; + } + } + } + + gb->display.lcd_draw_line(gb, pixels, gb->hram_io[IO_LY]); +} +#endif + +/** + * Internal function used to step the CPU. + */ +void __gb_step_cpu(struct gb_s *gb) { + uint8_t opcode; + uint_fast16_t inst_cycles; + static const uint8_t op_cycles[0x100] = { + /* *INDENT-OFF* */ + /*0 1 2 3 4 5 6 7 8 9 A B C D E F */ + 4, 12, 8, 8, 4, 4, 8, 4, 20, + 8, 8, 8, 4, 4, 8, 4, /* 0x00 */ + 4, 12, 8, 8, 4, 4, 8, 4, 12, + 8, 8, 8, 4, 4, 8, 4, /* 0x10 */ + 8, 12, 8, 8, 4, 4, 8, 4, 8, + 8, 8, 8, 4, 4, 8, 4, /* 0x20 */ + 8, 12, 8, 8, 12, 12, 12, 4, 8, + 8, 8, 8, 4, 4, 8, 4, /* 0x30 */ + 4, 4, 4, 4, 4, 4, 8, 4, 4, + 4, 4, 4, 4, 4, 8, 4, /* 0x40 */ + 4, 4, 4, 4, 4, 4, 8, 4, 4, + 4, 4, 4, 4, 4, 8, 4, /* 0x50 */ + 4, 4, 4, 4, 4, 4, 8, 4, 4, + 4, 4, 4, 4, 4, 8, 4, /* 0x60 */ + 8, 8, 8, 8, 8, 8, 4, 8, 4, + 4, 4, 4, 4, 4, 8, 4, /* 0x70 */ + 4, 4, 4, 4, 4, 4, 8, 4, 4, + 4, 4, 4, 4, 4, 8, 4, /* 0x80 */ + 4, 4, 4, 4, 4, 4, 8, 4, 4, + 4, 4, 4, 4, 4, 8, 4, /* 0x90 */ + 4, 4, 4, 4, 4, 4, 8, 4, 4, + 4, 4, 4, 4, 4, 8, 4, /* 0xA0 */ + 4, 4, 4, 4, 4, 4, 8, 4, 4, + 4, 4, 4, 4, 4, 8, 4, /* 0xB0 */ + 8, 12, 12, 16, 12, 16, 8, 16, 8, + 16, 12, 8, 12, 24, 8, 16, /* 0xC0 */ + 8, 12, 12, 0, 12, 16, 8, 16, 8, + 16, 12, 0, 12, 0, 8, 16, /* 0xD0 */ + 12, 12, 8, 0, 0, 16, 8, 16, 16, + 4, 16, 0, 0, 0, 8, 16, /* 0xE0 */ + 12, 12, 8, 4, 0, 16, 8, 16, 12, + 8, 16, 4, 0, 0, 8, 16 /* 0xF0 */ + /* *INDENT-ON* */ + }; + static const uint_fast16_t TAC_CYCLES[4] = {1024, 16, 64, 256}; + + /* Handle interrupts */ + /* If gb_halt is positive, then an interrupt must have occurred by the + * time we reach here, because on HALT, we jump to the next interrupt + * immediately. */ + while (gb->gb_halt || (gb->gb_ime && gb->hram_io[IO_IF] & gb->hram_io[IO_IE] & ANY_INTR)) { + gb->gb_halt = false; + + if (!gb->gb_ime) + break; + + /* Disable interrupts */ + gb->gb_ime = false; + + /* Push Program Counter */ + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p); + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c); + + /* Call interrupt handler if required. */ + if (gb->hram_io[IO_IF] & gb->hram_io[IO_IE] & VBLANK_INTR) { + gb->cpu_reg.pc.reg = VBLANK_INTR_ADDR; + gb->hram_io[IO_IF] ^= VBLANK_INTR; + } else if (gb->hram_io[IO_IF] & gb->hram_io[IO_IE] & LCDC_INTR) { + gb->cpu_reg.pc.reg = LCDC_INTR_ADDR; + gb->hram_io[IO_IF] ^= LCDC_INTR; + } else if (gb->hram_io[IO_IF] & gb->hram_io[IO_IE] & TIMER_INTR) { + gb->cpu_reg.pc.reg = TIMER_INTR_ADDR; + gb->hram_io[IO_IF] ^= TIMER_INTR; + } else if (gb->hram_io[IO_IF] & gb->hram_io[IO_IE] & SERIAL_INTR) { + gb->cpu_reg.pc.reg = SERIAL_INTR_ADDR; + gb->hram_io[IO_IF] ^= SERIAL_INTR; + } else if (gb->hram_io[IO_IF] & gb->hram_io[IO_IE] & CONTROL_INTR) { + gb->cpu_reg.pc.reg = CONTROL_INTR_ADDR; + gb->hram_io[IO_IF] ^= CONTROL_INTR; + } + + break; + } + + /* Obtain opcode */ + opcode = __gb_read(gb, gb->cpu_reg.pc.reg++); + inst_cycles = op_cycles[opcode]; + + /* Execute opcode */ + switch (opcode) { + case 0x00: /* NOP */ + break; + + case 0x01: /* LD BC, imm */ + gb->cpu_reg.bc.bytes.c = __gb_read(gb, gb->cpu_reg.pc.reg++); + gb->cpu_reg.bc.bytes.b = __gb_read(gb, gb->cpu_reg.pc.reg++); + break; + + case 0x02: /* LD (BC), A */ + __gb_write(gb, gb->cpu_reg.bc.reg, gb->cpu_reg.a); + break; + + case 0x03: /* INC BC */ + gb->cpu_reg.bc.reg++; + break; + + case 0x04: /* INC B */ + PGB_INSTR_INC_R8(gb->cpu_reg.bc.bytes.b); + break; + + case 0x05: /* DEC B */ + PGB_INSTR_DEC_R8(gb->cpu_reg.bc.bytes.b); + break; + + case 0x06: /* LD B, imm */ + gb->cpu_reg.bc.bytes.b = __gb_read(gb, gb->cpu_reg.pc.reg++); + break; + + case 0x07: /* RLCA */ + gb->cpu_reg.a = (gb->cpu_reg.a << 1) | (gb->cpu_reg.a >> 7); + gb->cpu_reg.f.reg = 0; + gb->cpu_reg.f.f_bits.c = (gb->cpu_reg.a & 0x01); + break; + + case 0x08: /* LD (imm), SP */ + { + uint8_t h, l; + uint16_t temp; + l = __gb_read(gb, gb->cpu_reg.pc.reg++); + h = __gb_read(gb, gb->cpu_reg.pc.reg++); + temp = PEANUT_GB_U8_TO_U16(h, l); + __gb_write(gb, temp++, gb->cpu_reg.sp.bytes.p); + __gb_write(gb, temp, gb->cpu_reg.sp.bytes.s); + break; + } + + case 0x09: /* ADD HL, BC */ + { + uint_fast32_t temp = gb->cpu_reg.hl.reg + gb->cpu_reg.bc.reg; + gb->cpu_reg.f.f_bits.n = 0; + gb->cpu_reg.f.f_bits.h = (temp ^ gb->cpu_reg.hl.reg ^ gb->cpu_reg.bc.reg) & 0x1000 ? 1 : 0; + gb->cpu_reg.f.f_bits.c = (temp & 0xFFFF0000) ? 1 : 0; + gb->cpu_reg.hl.reg = (temp & 0x0000FFFF); + break; + } + + case 0x0A: /* LD A, (BC) */ + gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.bc.reg); + break; + + case 0x0B: /* DEC BC */ + gb->cpu_reg.bc.reg--; + break; + + case 0x0C: /* INC C */ + PGB_INSTR_INC_R8(gb->cpu_reg.bc.bytes.c); + break; + + case 0x0D: /* DEC C */ + PGB_INSTR_DEC_R8(gb->cpu_reg.bc.bytes.c); + break; + + case 0x0E: /* LD C, imm */ + gb->cpu_reg.bc.bytes.c = __gb_read(gb, gb->cpu_reg.pc.reg++); + break; + + case 0x0F: /* RRCA */ + gb->cpu_reg.f.reg = 0; + gb->cpu_reg.f.f_bits.c = gb->cpu_reg.a & 0x01; + gb->cpu_reg.a = (gb->cpu_reg.a >> 1) | (gb->cpu_reg.a << 7); + break; + + case 0x10: /* STOP */ + // gb->gb_halt = true; + break; + + case 0x11: /* LD DE, imm */ + gb->cpu_reg.de.bytes.e = __gb_read(gb, gb->cpu_reg.pc.reg++); + gb->cpu_reg.de.bytes.d = __gb_read(gb, gb->cpu_reg.pc.reg++); + break; + + case 0x12: /* LD (DE), A */ + __gb_write(gb, gb->cpu_reg.de.reg, gb->cpu_reg.a); + break; + + case 0x13: /* INC DE */ + gb->cpu_reg.de.reg++; + break; + + case 0x14: /* INC D */ + PGB_INSTR_INC_R8(gb->cpu_reg.de.bytes.d); + break; + + case 0x15: /* DEC D */ + PGB_INSTR_DEC_R8(gb->cpu_reg.de.bytes.d); + break; + + case 0x16: /* LD D, imm */ + gb->cpu_reg.de.bytes.d = __gb_read(gb, gb->cpu_reg.pc.reg++); + break; + + case 0x17: /* RLA */ + { + uint8_t temp = gb->cpu_reg.a; + gb->cpu_reg.a = (gb->cpu_reg.a << 1) | gb->cpu_reg.f.f_bits.c; + gb->cpu_reg.f.reg = 0; + gb->cpu_reg.f.f_bits.c = (temp >> 7) & 0x01; + break; + } + + case 0x18: /* JR imm */ + { + int8_t temp = (int8_t)__gb_read(gb, gb->cpu_reg.pc.reg++); + gb->cpu_reg.pc.reg += temp; + break; + } + + case 0x19: /* ADD HL, DE */ + { + uint_fast32_t temp = gb->cpu_reg.hl.reg + gb->cpu_reg.de.reg; + gb->cpu_reg.f.f_bits.n = 0; + gb->cpu_reg.f.f_bits.h = (temp ^ gb->cpu_reg.hl.reg ^ gb->cpu_reg.de.reg) & 0x1000 ? 1 : 0; + gb->cpu_reg.f.f_bits.c = (temp & 0xFFFF0000) ? 1 : 0; + gb->cpu_reg.hl.reg = (temp & 0x0000FFFF); + break; + } + + case 0x1A: /* LD A, (DE) */ + gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.de.reg); + break; + + case 0x1B: /* DEC DE */ + gb->cpu_reg.de.reg--; + break; + + case 0x1C: /* INC E */ + PGB_INSTR_INC_R8(gb->cpu_reg.de.bytes.e); + break; + + case 0x1D: /* DEC E */ + PGB_INSTR_DEC_R8(gb->cpu_reg.de.bytes.e); + break; + + case 0x1E: /* LD E, imm */ + gb->cpu_reg.de.bytes.e = __gb_read(gb, gb->cpu_reg.pc.reg++); + break; + + case 0x1F: /* RRA */ + { + uint8_t temp = gb->cpu_reg.a; + gb->cpu_reg.a = gb->cpu_reg.a >> 1 | (gb->cpu_reg.f.f_bits.c << 7); + gb->cpu_reg.f.reg = 0; + gb->cpu_reg.f.f_bits.c = temp & 0x1; + break; + } + + case 0x20: /* JR NZ, imm */ + if (!gb->cpu_reg.f.f_bits.z) { + int8_t temp = (int8_t)__gb_read(gb, gb->cpu_reg.pc.reg++); + gb->cpu_reg.pc.reg += temp; + inst_cycles += 4; + } else + gb->cpu_reg.pc.reg++; + + break; + + case 0x21: /* LD HL, imm */ + gb->cpu_reg.hl.bytes.l = __gb_read(gb, gb->cpu_reg.pc.reg++); + gb->cpu_reg.hl.bytes.h = __gb_read(gb, gb->cpu_reg.pc.reg++); + break; + + case 0x22: /* LDI (HL), A */ + __gb_write(gb, gb->cpu_reg.hl.reg, gb->cpu_reg.a); + gb->cpu_reg.hl.reg++; + break; + + case 0x23: /* INC HL */ + gb->cpu_reg.hl.reg++; + break; + + case 0x24: /* INC H */ + PGB_INSTR_INC_R8(gb->cpu_reg.hl.bytes.h); + break; + + case 0x25: /* DEC H */ + PGB_INSTR_DEC_R8(gb->cpu_reg.hl.bytes.h); + break; + + case 0x26: /* LD H, imm */ + gb->cpu_reg.hl.bytes.h = __gb_read(gb, gb->cpu_reg.pc.reg++); + break; + + case 0x27: /* DAA */ + { + /* The following is from SameBoy. MIT License. */ + int16_t a = gb->cpu_reg.a; + + if (gb->cpu_reg.f.f_bits.n) { + if (gb->cpu_reg.f.f_bits.h) + a = (a - 0x06) & 0xFF; + + if (gb->cpu_reg.f.f_bits.c) + a -= 0x60; + } else { + if (gb->cpu_reg.f.f_bits.h || (a & 0x0F) > 9) + a += 0x06; + + if (gb->cpu_reg.f.f_bits.c || a > 0x9F) + a += 0x60; + } + + if ((a & 0x100) == 0x100) + gb->cpu_reg.f.f_bits.c = 1; + + gb->cpu_reg.a = a; + gb->cpu_reg.f.f_bits.z = (gb->cpu_reg.a == 0); + gb->cpu_reg.f.f_bits.h = 0; + + break; + } + + case 0x28: /* JR Z, imm */ + if (gb->cpu_reg.f.f_bits.z) { + int8_t temp = (int8_t)__gb_read(gb, gb->cpu_reg.pc.reg++); + gb->cpu_reg.pc.reg += temp; + inst_cycles += 4; + } else + gb->cpu_reg.pc.reg++; + + break; + + case 0x29: /* ADD HL, HL */ + { + gb->cpu_reg.f.f_bits.c = (gb->cpu_reg.hl.reg & 0x8000) > 0; + gb->cpu_reg.hl.reg <<= 1; + gb->cpu_reg.f.f_bits.n = 0; + gb->cpu_reg.f.f_bits.h = (gb->cpu_reg.hl.reg & 0x1000) > 0; + break; + } + + case 0x2A: /* LD A, (HL+) */ + gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.hl.reg++); + break; + + case 0x2B: /* DEC HL */ + gb->cpu_reg.hl.reg--; + break; + + case 0x2C: /* INC L */ + PGB_INSTR_INC_R8(gb->cpu_reg.hl.bytes.l); + break; + + case 0x2D: /* DEC L */ + PGB_INSTR_DEC_R8(gb->cpu_reg.hl.bytes.l); + break; + + case 0x2E: /* LD L, imm */ + gb->cpu_reg.hl.bytes.l = __gb_read(gb, gb->cpu_reg.pc.reg++); + break; + + case 0x2F: /* CPL */ + gb->cpu_reg.a = ~gb->cpu_reg.a; + gb->cpu_reg.f.f_bits.n = 1; + gb->cpu_reg.f.f_bits.h = 1; + break; + + case 0x30: /* JR NC, imm */ + if (!gb->cpu_reg.f.f_bits.c) { + int8_t temp = (int8_t)__gb_read(gb, gb->cpu_reg.pc.reg++); + gb->cpu_reg.pc.reg += temp; + inst_cycles += 4; + } else + gb->cpu_reg.pc.reg++; + + break; + + case 0x31: /* LD SP, imm */ + gb->cpu_reg.sp.bytes.p = __gb_read(gb, gb->cpu_reg.pc.reg++); + gb->cpu_reg.sp.bytes.s = __gb_read(gb, gb->cpu_reg.pc.reg++); + break; + + case 0x32: /* LD (HL), A */ + __gb_write(gb, gb->cpu_reg.hl.reg, gb->cpu_reg.a); + gb->cpu_reg.hl.reg--; + break; + + case 0x33: /* INC SP */ + gb->cpu_reg.sp.reg++; + break; + + case 0x34: /* INC (HL) */ + { + uint8_t temp = __gb_read(gb, gb->cpu_reg.hl.reg); + PGB_INSTR_INC_R8(temp); + __gb_write(gb, gb->cpu_reg.hl.reg, temp); + break; + } + + case 0x35: /* DEC (HL) */ + { + uint8_t temp = __gb_read(gb, gb->cpu_reg.hl.reg); + PGB_INSTR_DEC_R8(temp); + __gb_write(gb, gb->cpu_reg.hl.reg, temp); + break; + } + + case 0x36: /* LD (HL), imm */ + __gb_write(gb, gb->cpu_reg.hl.reg, __gb_read(gb, gb->cpu_reg.pc.reg++)); + break; + + case 0x37: /* SCF */ + gb->cpu_reg.f.f_bits.n = 0; + gb->cpu_reg.f.f_bits.h = 0; + gb->cpu_reg.f.f_bits.c = 1; + break; + + case 0x38: /* JR C, imm */ + if (gb->cpu_reg.f.f_bits.c) { + int8_t temp = (int8_t)__gb_read(gb, gb->cpu_reg.pc.reg++); + gb->cpu_reg.pc.reg += temp; + inst_cycles += 4; + } else + gb->cpu_reg.pc.reg++; + + break; + + case 0x39: /* ADD HL, SP */ + { + uint_fast32_t temp = gb->cpu_reg.hl.reg + gb->cpu_reg.sp.reg; + gb->cpu_reg.f.f_bits.n = 0; + gb->cpu_reg.f.f_bits.h = + ((gb->cpu_reg.hl.reg & 0xFFF) + (gb->cpu_reg.sp.reg & 0xFFF)) & 0x1000 ? 1 : 0; + gb->cpu_reg.f.f_bits.c = temp & 0x10000 ? 1 : 0; + gb->cpu_reg.hl.reg = (uint16_t)temp; + break; + } + + case 0x3A: /* LD A, (HL) */ + gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.hl.reg--); + break; + + case 0x3B: /* DEC SP */ + gb->cpu_reg.sp.reg--; + break; + + case 0x3C: /* INC A */ + PGB_INSTR_INC_R8(gb->cpu_reg.a); + break; + + case 0x3D: /* DEC A */ + PGB_INSTR_DEC_R8(gb->cpu_reg.a); + break; + + case 0x3E: /* LD A, imm */ + gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.pc.reg++); + break; + + case 0x3F: /* CCF */ + gb->cpu_reg.f.f_bits.n = 0; + gb->cpu_reg.f.f_bits.h = 0; + gb->cpu_reg.f.f_bits.c = ~gb->cpu_reg.f.f_bits.c; + break; + + case 0x40: /* LD B, B */ + break; + + case 0x41: /* LD B, C */ + gb->cpu_reg.bc.bytes.b = gb->cpu_reg.bc.bytes.c; + break; + + case 0x42: /* LD B, D */ + gb->cpu_reg.bc.bytes.b = gb->cpu_reg.de.bytes.d; + break; + + case 0x43: /* LD B, E */ + gb->cpu_reg.bc.bytes.b = gb->cpu_reg.de.bytes.e; + break; + + case 0x44: /* LD B, H */ + gb->cpu_reg.bc.bytes.b = gb->cpu_reg.hl.bytes.h; + break; + + case 0x45: /* LD B, L */ + gb->cpu_reg.bc.bytes.b = gb->cpu_reg.hl.bytes.l; + break; + + case 0x46: /* LD B, (HL) */ + gb->cpu_reg.bc.bytes.b = __gb_read(gb, gb->cpu_reg.hl.reg); + break; + + case 0x47: /* LD B, A */ + gb->cpu_reg.bc.bytes.b = gb->cpu_reg.a; + break; + + case 0x48: /* LD C, B */ + gb->cpu_reg.bc.bytes.c = gb->cpu_reg.bc.bytes.b; + break; + + case 0x49: /* LD C, C */ + break; + + case 0x4A: /* LD C, D */ + gb->cpu_reg.bc.bytes.c = gb->cpu_reg.de.bytes.d; + break; + + case 0x4B: /* LD C, E */ + gb->cpu_reg.bc.bytes.c = gb->cpu_reg.de.bytes.e; + break; + + case 0x4C: /* LD C, H */ + gb->cpu_reg.bc.bytes.c = gb->cpu_reg.hl.bytes.h; + break; + + case 0x4D: /* LD C, L */ + gb->cpu_reg.bc.bytes.c = gb->cpu_reg.hl.bytes.l; + break; + + case 0x4E: /* LD C, (HL) */ + gb->cpu_reg.bc.bytes.c = __gb_read(gb, gb->cpu_reg.hl.reg); + break; + + case 0x4F: /* LD C, A */ + gb->cpu_reg.bc.bytes.c = gb->cpu_reg.a; + break; + + case 0x50: /* LD D, B */ + gb->cpu_reg.de.bytes.d = gb->cpu_reg.bc.bytes.b; + break; + + case 0x51: /* LD D, C */ + gb->cpu_reg.de.bytes.d = gb->cpu_reg.bc.bytes.c; + break; + + case 0x52: /* LD D, D */ + break; + + case 0x53: /* LD D, E */ + gb->cpu_reg.de.bytes.d = gb->cpu_reg.de.bytes.e; + break; + + case 0x54: /* LD D, H */ + gb->cpu_reg.de.bytes.d = gb->cpu_reg.hl.bytes.h; + break; + + case 0x55: /* LD D, L */ + gb->cpu_reg.de.bytes.d = gb->cpu_reg.hl.bytes.l; + break; + + case 0x56: /* LD D, (HL) */ + gb->cpu_reg.de.bytes.d = __gb_read(gb, gb->cpu_reg.hl.reg); + break; + + case 0x57: /* LD D, A */ + gb->cpu_reg.de.bytes.d = gb->cpu_reg.a; + break; + + case 0x58: /* LD E, B */ + gb->cpu_reg.de.bytes.e = gb->cpu_reg.bc.bytes.b; + break; + + case 0x59: /* LD E, C */ + gb->cpu_reg.de.bytes.e = gb->cpu_reg.bc.bytes.c; + break; + + case 0x5A: /* LD E, D */ + gb->cpu_reg.de.bytes.e = gb->cpu_reg.de.bytes.d; + break; + + case 0x5B: /* LD E, E */ + break; + + case 0x5C: /* LD E, H */ + gb->cpu_reg.de.bytes.e = gb->cpu_reg.hl.bytes.h; + break; + + case 0x5D: /* LD E, L */ + gb->cpu_reg.de.bytes.e = gb->cpu_reg.hl.bytes.l; + break; + + case 0x5E: /* LD E, (HL) */ + gb->cpu_reg.de.bytes.e = __gb_read(gb, gb->cpu_reg.hl.reg); + break; + + case 0x5F: /* LD E, A */ + gb->cpu_reg.de.bytes.e = gb->cpu_reg.a; + break; + + case 0x60: /* LD H, B */ + gb->cpu_reg.hl.bytes.h = gb->cpu_reg.bc.bytes.b; + break; + + case 0x61: /* LD H, C */ + gb->cpu_reg.hl.bytes.h = gb->cpu_reg.bc.bytes.c; + break; + + case 0x62: /* LD H, D */ + gb->cpu_reg.hl.bytes.h = gb->cpu_reg.de.bytes.d; + break; + + case 0x63: /* LD H, E */ + gb->cpu_reg.hl.bytes.h = gb->cpu_reg.de.bytes.e; + break; + + case 0x64: /* LD H, H */ + break; + + case 0x65: /* LD H, L */ + gb->cpu_reg.hl.bytes.h = gb->cpu_reg.hl.bytes.l; + break; + + case 0x66: /* LD H, (HL) */ + gb->cpu_reg.hl.bytes.h = __gb_read(gb, gb->cpu_reg.hl.reg); + break; + + case 0x67: /* LD H, A */ + gb->cpu_reg.hl.bytes.h = gb->cpu_reg.a; + break; + + case 0x68: /* LD L, B */ + gb->cpu_reg.hl.bytes.l = gb->cpu_reg.bc.bytes.b; + break; + + case 0x69: /* LD L, C */ + gb->cpu_reg.hl.bytes.l = gb->cpu_reg.bc.bytes.c; + break; + + case 0x6A: /* LD L, D */ + gb->cpu_reg.hl.bytes.l = gb->cpu_reg.de.bytes.d; + break; + + case 0x6B: /* LD L, E */ + gb->cpu_reg.hl.bytes.l = gb->cpu_reg.de.bytes.e; + break; + + case 0x6C: /* LD L, H */ + gb->cpu_reg.hl.bytes.l = gb->cpu_reg.hl.bytes.h; + break; + + case 0x6D: /* LD L, L */ + break; + + case 0x6E: /* LD L, (HL) */ + gb->cpu_reg.hl.bytes.l = __gb_read(gb, gb->cpu_reg.hl.reg); + break; + + case 0x6F: /* LD L, A */ + gb->cpu_reg.hl.bytes.l = gb->cpu_reg.a; + break; + + case 0x70: /* LD (HL), B */ + __gb_write(gb, gb->cpu_reg.hl.reg, gb->cpu_reg.bc.bytes.b); + break; + + case 0x71: /* LD (HL), C */ + __gb_write(gb, gb->cpu_reg.hl.reg, gb->cpu_reg.bc.bytes.c); + break; + + case 0x72: /* LD (HL), D */ + __gb_write(gb, gb->cpu_reg.hl.reg, gb->cpu_reg.de.bytes.d); + break; + + case 0x73: /* LD (HL), E */ + __gb_write(gb, gb->cpu_reg.hl.reg, gb->cpu_reg.de.bytes.e); + break; + + case 0x74: /* LD (HL), H */ + __gb_write(gb, gb->cpu_reg.hl.reg, gb->cpu_reg.hl.bytes.h); + break; + + case 0x75: /* LD (HL), L */ + __gb_write(gb, gb->cpu_reg.hl.reg, gb->cpu_reg.hl.bytes.l); + break; + + case 0x76: /* HALT */ + { + int_fast16_t halt_cycles = INT_FAST16_MAX; + + /* TODO: Emulate HALT bug? */ + gb->gb_halt = true; + + if (gb->hram_io[IO_SC] & SERIAL_SC_TX_START) { + int serial_cycles = SERIAL_CYCLES - gb->counter.serial_count; + + if (serial_cycles < halt_cycles) + halt_cycles = serial_cycles; + } + + if (gb->hram_io[IO_TAC] & IO_TAC_ENABLE_MASK) { + int tac_cycles = + TAC_CYCLES[gb->hram_io[IO_TAC] & IO_TAC_RATE_MASK] - gb->counter.tima_count; + + if (tac_cycles < halt_cycles) + halt_cycles = tac_cycles; + } + + if ((gb->hram_io[IO_LCDC] & LCDC_ENABLE)) { + int lcd_cycles; + + /* If LCD is in HBlank, calculate the number of cycles + * until the end of HBlank and the start of mode 2 or + * mode 1. */ + if ((gb->hram_io[IO_STAT] & STAT_MODE) == IO_STAT_MODE_HBLANK) { + lcd_cycles = LCD_MODE0_HBLANK_MAX_DRUATION - gb->counter.lcd_count; + } else if ((gb->hram_io[IO_STAT] & STAT_MODE) == IO_STAT_MODE_OAM_SCAN) { + lcd_cycles = LCD_MODE3_LCD_DRAW_MIN_DURATION - gb->counter.lcd_count; + } else if ((gb->hram_io[IO_STAT] & STAT_MODE) == IO_STAT_MODE_LCD_DRAW) { + lcd_cycles = LCD_MODE0_HBLANK_MAX_DRUATION - gb->counter.lcd_count; + } else { + /* VBlank */ + lcd_cycles = LCD_LINE_CYCLES - gb->counter.lcd_count; + } + + if (lcd_cycles < halt_cycles) + halt_cycles = lcd_cycles; + } + + /* Some halt cycles may already be very high, so make sure we + * don't underflow here. */ + if (halt_cycles <= 0) + halt_cycles = 4; + + inst_cycles = (uint_fast16_t)halt_cycles; + break; + } + + case 0x77: /* LD (HL), A */ + __gb_write(gb, gb->cpu_reg.hl.reg, gb->cpu_reg.a); + break; + + case 0x78: /* LD A, B */ + gb->cpu_reg.a = gb->cpu_reg.bc.bytes.b; + break; + + case 0x79: /* LD A, C */ + gb->cpu_reg.a = gb->cpu_reg.bc.bytes.c; + break; + + case 0x7A: /* LD A, D */ + gb->cpu_reg.a = gb->cpu_reg.de.bytes.d; + break; + + case 0x7B: /* LD A, E */ + gb->cpu_reg.a = gb->cpu_reg.de.bytes.e; + break; + + case 0x7C: /* LD A, H */ + gb->cpu_reg.a = gb->cpu_reg.hl.bytes.h; + break; + + case 0x7D: /* LD A, L */ + gb->cpu_reg.a = gb->cpu_reg.hl.bytes.l; + break; + + case 0x7E: /* LD A, (HL) */ + gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.hl.reg); + break; + + case 0x7F: /* LD A, A */ + break; + + case 0x80: /* ADD A, B */ + PGB_INSTR_ADC_R8(gb->cpu_reg.bc.bytes.b, 0); + break; + + case 0x81: /* ADD A, C */ + PGB_INSTR_ADC_R8(gb->cpu_reg.bc.bytes.c, 0); + break; + + case 0x82: /* ADD A, D */ + PGB_INSTR_ADC_R8(gb->cpu_reg.de.bytes.d, 0); + break; + + case 0x83: /* ADD A, E */ + PGB_INSTR_ADC_R8(gb->cpu_reg.de.bytes.e, 0); + break; + + case 0x84: /* ADD A, H */ + PGB_INSTR_ADC_R8(gb->cpu_reg.hl.bytes.h, 0); + break; + + case 0x85: /* ADD A, L */ + PGB_INSTR_ADC_R8(gb->cpu_reg.hl.bytes.l, 0); + break; + + case 0x86: /* ADD A, (HL) */ + PGB_INSTR_ADC_R8(__gb_read(gb, gb->cpu_reg.hl.reg), 0); + break; + + case 0x87: /* ADD A, A */ + PGB_INSTR_ADC_R8(gb->cpu_reg.a, 0); + break; + + case 0x88: /* ADC A, B */ + PGB_INSTR_ADC_R8(gb->cpu_reg.bc.bytes.b, gb->cpu_reg.f.f_bits.c); + break; + + case 0x89: /* ADC A, C */ + PGB_INSTR_ADC_R8(gb->cpu_reg.bc.bytes.c, gb->cpu_reg.f.f_bits.c); + break; + + case 0x8A: /* ADC A, D */ + PGB_INSTR_ADC_R8(gb->cpu_reg.de.bytes.d, gb->cpu_reg.f.f_bits.c); + break; + + case 0x8B: /* ADC A, E */ + PGB_INSTR_ADC_R8(gb->cpu_reg.de.bytes.e, gb->cpu_reg.f.f_bits.c); + break; + + case 0x8C: /* ADC A, H */ + PGB_INSTR_ADC_R8(gb->cpu_reg.hl.bytes.h, gb->cpu_reg.f.f_bits.c); + break; + + case 0x8D: /* ADC A, L */ + PGB_INSTR_ADC_R8(gb->cpu_reg.hl.bytes.l, gb->cpu_reg.f.f_bits.c); + break; + + case 0x8E: /* ADC A, (HL) */ + PGB_INSTR_ADC_R8(__gb_read(gb, gb->cpu_reg.hl.reg), gb->cpu_reg.f.f_bits.c); + break; + + case 0x8F: /* ADC A, A */ + PGB_INSTR_ADC_R8(gb->cpu_reg.a, gb->cpu_reg.f.f_bits.c); + break; + + case 0x90: /* SUB B */ + PGB_INSTR_SBC_R8(gb->cpu_reg.bc.bytes.b, 0); + break; + + case 0x91: /* SUB C */ + PGB_INSTR_SBC_R8(gb->cpu_reg.bc.bytes.c, 0); + break; + + case 0x92: /* SUB D */ + PGB_INSTR_SBC_R8(gb->cpu_reg.de.bytes.d, 0); + break; + + case 0x93: /* SUB E */ + PGB_INSTR_SBC_R8(gb->cpu_reg.de.bytes.e, 0); + break; + + case 0x94: /* SUB H */ + PGB_INSTR_SBC_R8(gb->cpu_reg.hl.bytes.h, 0); + break; + + case 0x95: /* SUB L */ + PGB_INSTR_SBC_R8(gb->cpu_reg.hl.bytes.l, 0); + break; + + case 0x96: /* SUB (HL) */ + PGB_INSTR_SBC_R8(__gb_read(gb, gb->cpu_reg.hl.reg), 0); + break; + + case 0x97: /* SUB A */ + gb->cpu_reg.a = 0; + gb->cpu_reg.f.reg = 0; + gb->cpu_reg.f.f_bits.z = 1; + gb->cpu_reg.f.f_bits.n = 1; + break; + + case 0x98: /* SBC A, B */ + PGB_INSTR_SBC_R8(gb->cpu_reg.bc.bytes.b, gb->cpu_reg.f.f_bits.c); + break; + + case 0x99: /* SBC A, C */ + PGB_INSTR_SBC_R8(gb->cpu_reg.bc.bytes.c, gb->cpu_reg.f.f_bits.c); + break; + + case 0x9A: /* SBC A, D */ + PGB_INSTR_SBC_R8(gb->cpu_reg.de.bytes.d, gb->cpu_reg.f.f_bits.c); + break; + + case 0x9B: /* SBC A, E */ + PGB_INSTR_SBC_R8(gb->cpu_reg.de.bytes.e, gb->cpu_reg.f.f_bits.c); + break; + + case 0x9C: /* SBC A, H */ + PGB_INSTR_SBC_R8(gb->cpu_reg.hl.bytes.h, gb->cpu_reg.f.f_bits.c); + break; + + case 0x9D: /* SBC A, L */ + PGB_INSTR_SBC_R8(gb->cpu_reg.hl.bytes.l, gb->cpu_reg.f.f_bits.c); + break; + + case 0x9E: /* SBC A, (HL) */ + PGB_INSTR_SBC_R8(__gb_read(gb, gb->cpu_reg.hl.reg), gb->cpu_reg.f.f_bits.c); + break; + + case 0x9F: /* SBC A, A */ + gb->cpu_reg.a = gb->cpu_reg.f.f_bits.c ? 0xFF : 0x00; + gb->cpu_reg.f.f_bits.z = !gb->cpu_reg.f.f_bits.c; + gb->cpu_reg.f.f_bits.n = 1; + gb->cpu_reg.f.f_bits.h = gb->cpu_reg.f.f_bits.c; + break; + + case 0xA0: /* AND B */ + PGB_INSTR_AND_R8(gb->cpu_reg.bc.bytes.b); + break; + + case 0xA1: /* AND C */ + PGB_INSTR_AND_R8(gb->cpu_reg.bc.bytes.c); + break; + + case 0xA2: /* AND D */ + PGB_INSTR_AND_R8(gb->cpu_reg.de.bytes.d); + break; + + case 0xA3: /* AND E */ + PGB_INSTR_AND_R8(gb->cpu_reg.de.bytes.e); + break; + + case 0xA4: /* AND H */ + PGB_INSTR_AND_R8(gb->cpu_reg.hl.bytes.h); + break; + + case 0xA5: /* AND L */ + PGB_INSTR_AND_R8(gb->cpu_reg.hl.bytes.l); + break; + + case 0xA6: /* AND (HL) */ + PGB_INSTR_AND_R8(__gb_read(gb, gb->cpu_reg.hl.reg)); + break; + + case 0xA7: /* AND A */ + PGB_INSTR_AND_R8(gb->cpu_reg.a); + break; + + case 0xA8: /* XOR B */ + PGB_INSTR_XOR_R8(gb->cpu_reg.bc.bytes.b); + break; + + case 0xA9: /* XOR C */ + PGB_INSTR_XOR_R8(gb->cpu_reg.bc.bytes.c); + break; + + case 0xAA: /* XOR D */ + PGB_INSTR_XOR_R8(gb->cpu_reg.de.bytes.d); + break; + + case 0xAB: /* XOR E */ + PGB_INSTR_XOR_R8(gb->cpu_reg.de.bytes.e); + break; + + case 0xAC: /* XOR H */ + PGB_INSTR_XOR_R8(gb->cpu_reg.hl.bytes.h); + break; + + case 0xAD: /* XOR L */ + PGB_INSTR_XOR_R8(gb->cpu_reg.hl.bytes.l); + break; + + case 0xAE: /* XOR (HL) */ + PGB_INSTR_XOR_R8(__gb_read(gb, gb->cpu_reg.hl.reg)); + break; + + case 0xAF: /* XOR A */ + PGB_INSTR_XOR_R8(gb->cpu_reg.a); + break; + + case 0xB0: /* OR B */ + PGB_INSTR_OR_R8(gb->cpu_reg.bc.bytes.b); + break; + + case 0xB1: /* OR C */ + PGB_INSTR_OR_R8(gb->cpu_reg.bc.bytes.c); + break; + + case 0xB2: /* OR D */ + PGB_INSTR_OR_R8(gb->cpu_reg.de.bytes.d); + break; + + case 0xB3: /* OR E */ + PGB_INSTR_OR_R8(gb->cpu_reg.de.bytes.e); + break; + + case 0xB4: /* OR H */ + PGB_INSTR_OR_R8(gb->cpu_reg.hl.bytes.h); + break; + + case 0xB5: /* OR L */ + PGB_INSTR_OR_R8(gb->cpu_reg.hl.bytes.l); + break; + + case 0xB6: /* OR (HL) */ + PGB_INSTR_OR_R8(__gb_read(gb, gb->cpu_reg.hl.reg)); + break; + + case 0xB7: /* OR A */ + PGB_INSTR_OR_R8(gb->cpu_reg.a); + break; + + case 0xB8: /* CP B */ + PGB_INSTR_CP_R8(gb->cpu_reg.bc.bytes.b); + break; + + case 0xB9: /* CP C */ + PGB_INSTR_CP_R8(gb->cpu_reg.bc.bytes.c); + break; + + case 0xBA: /* CP D */ + PGB_INSTR_CP_R8(gb->cpu_reg.de.bytes.d); + break; + + case 0xBB: /* CP E */ + PGB_INSTR_CP_R8(gb->cpu_reg.de.bytes.e); + break; + + case 0xBC: /* CP H */ + PGB_INSTR_CP_R8(gb->cpu_reg.hl.bytes.h); + break; + + case 0xBD: /* CP L */ + PGB_INSTR_CP_R8(gb->cpu_reg.hl.bytes.l); + break; + + case 0xBE: /* CP (HL) */ + PGB_INSTR_CP_R8(__gb_read(gb, gb->cpu_reg.hl.reg)); + break; + + case 0xBF: /* CP A */ + gb->cpu_reg.f.reg = 0; + gb->cpu_reg.f.f_bits.z = 1; + gb->cpu_reg.f.f_bits.n = 1; + break; + + case 0xC0: /* RET NZ */ + if (!gb->cpu_reg.f.f_bits.z) { + gb->cpu_reg.pc.bytes.c = __gb_read(gb, gb->cpu_reg.sp.reg++); + gb->cpu_reg.pc.bytes.p = __gb_read(gb, gb->cpu_reg.sp.reg++); + inst_cycles += 12; + } + + break; + + case 0xC1: /* POP BC */ + gb->cpu_reg.bc.bytes.c = __gb_read(gb, gb->cpu_reg.sp.reg++); + gb->cpu_reg.bc.bytes.b = __gb_read(gb, gb->cpu_reg.sp.reg++); + break; + + case 0xC2: /* JP NZ, imm */ + if (!gb->cpu_reg.f.f_bits.z) { + uint8_t p, c; + c = __gb_read(gb, gb->cpu_reg.pc.reg++); + p = __gb_read(gb, gb->cpu_reg.pc.reg); + gb->cpu_reg.pc.bytes.c = c; + gb->cpu_reg.pc.bytes.p = p; + inst_cycles += 4; + } else + gb->cpu_reg.pc.reg += 2; + + break; + + case 0xC3: /* JP imm */ + { + uint8_t p, c; + c = __gb_read(gb, gb->cpu_reg.pc.reg++); + p = __gb_read(gb, gb->cpu_reg.pc.reg); + gb->cpu_reg.pc.bytes.c = c; + gb->cpu_reg.pc.bytes.p = p; + break; + } + + case 0xC4: /* CALL NZ imm */ + if (!gb->cpu_reg.f.f_bits.z) { + uint8_t p, c; + c = __gb_read(gb, gb->cpu_reg.pc.reg++); + p = __gb_read(gb, gb->cpu_reg.pc.reg++); + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p); + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c); + gb->cpu_reg.pc.bytes.c = c; + gb->cpu_reg.pc.bytes.p = p; + inst_cycles += 12; + } else + gb->cpu_reg.pc.reg += 2; + + break; + + case 0xC5: /* PUSH BC */ + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.bc.bytes.b); + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.bc.bytes.c); + break; + + case 0xC6: /* ADD A, imm */ + { + uint8_t val = __gb_read(gb, gb->cpu_reg.pc.reg++); + PGB_INSTR_ADC_R8(val, 0); + break; + } + + case 0xC7: /* RST 0x0000 */ + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p); + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c); + gb->cpu_reg.pc.reg = 0x0000; + break; + + case 0xC8: /* RET Z */ + if (gb->cpu_reg.f.f_bits.z) { + gb->cpu_reg.pc.bytes.c = __gb_read(gb, gb->cpu_reg.sp.reg++); + gb->cpu_reg.pc.bytes.p = __gb_read(gb, gb->cpu_reg.sp.reg++); + inst_cycles += 12; + } + break; + + case 0xC9: /* RET */ + { + gb->cpu_reg.pc.bytes.c = __gb_read(gb, gb->cpu_reg.sp.reg++); + gb->cpu_reg.pc.bytes.p = __gb_read(gb, gb->cpu_reg.sp.reg++); + break; + } + + case 0xCA: /* JP Z, imm */ + if (gb->cpu_reg.f.f_bits.z) { + uint8_t p, c; + c = __gb_read(gb, gb->cpu_reg.pc.reg++); + p = __gb_read(gb, gb->cpu_reg.pc.reg); + gb->cpu_reg.pc.bytes.c = c; + gb->cpu_reg.pc.bytes.p = p; + inst_cycles += 4; + } else + gb->cpu_reg.pc.reg += 2; + + break; + + case 0xCB: /* CB INST */ + inst_cycles = __gb_execute_cb(gb); + break; + + case 0xCC: /* CALL Z, imm */ + if (gb->cpu_reg.f.f_bits.z) { + uint8_t p, c; + c = __gb_read(gb, gb->cpu_reg.pc.reg++); + p = __gb_read(gb, gb->cpu_reg.pc.reg++); + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p); + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c); + gb->cpu_reg.pc.bytes.c = c; + gb->cpu_reg.pc.bytes.p = p; + inst_cycles += 12; + } else + gb->cpu_reg.pc.reg += 2; + + break; + + case 0xCD: /* CALL imm */ + { + uint8_t p, c; + c = __gb_read(gb, gb->cpu_reg.pc.reg++); + p = __gb_read(gb, gb->cpu_reg.pc.reg++); + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p); + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c); + gb->cpu_reg.pc.bytes.c = c; + gb->cpu_reg.pc.bytes.p = p; + } break; + + case 0xCE: /* ADC A, imm */ + { + uint8_t val = __gb_read(gb, gb->cpu_reg.pc.reg++); + PGB_INSTR_ADC_R8(val, gb->cpu_reg.f.f_bits.c); + break; + } + + case 0xCF: /* RST 0x0008 */ + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p); + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c); + gb->cpu_reg.pc.reg = 0x0008; + break; + + case 0xD0: /* RET NC */ + if (!gb->cpu_reg.f.f_bits.c) { + gb->cpu_reg.pc.bytes.c = __gb_read(gb, gb->cpu_reg.sp.reg++); + gb->cpu_reg.pc.bytes.p = __gb_read(gb, gb->cpu_reg.sp.reg++); + inst_cycles += 12; + } + + break; + + case 0xD1: /* POP DE */ + gb->cpu_reg.de.bytes.e = __gb_read(gb, gb->cpu_reg.sp.reg++); + gb->cpu_reg.de.bytes.d = __gb_read(gb, gb->cpu_reg.sp.reg++); + break; + + case 0xD2: /* JP NC, imm */ + if (!gb->cpu_reg.f.f_bits.c) { + uint8_t p, c; + c = __gb_read(gb, gb->cpu_reg.pc.reg++); + p = __gb_read(gb, gb->cpu_reg.pc.reg); + gb->cpu_reg.pc.bytes.c = c; + gb->cpu_reg.pc.bytes.p = p; + inst_cycles += 4; + } else + gb->cpu_reg.pc.reg += 2; + + break; + + case 0xD4: /* CALL NC, imm */ + if (!gb->cpu_reg.f.f_bits.c) { + uint8_t p, c; + c = __gb_read(gb, gb->cpu_reg.pc.reg++); + p = __gb_read(gb, gb->cpu_reg.pc.reg++); + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p); + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c); + gb->cpu_reg.pc.bytes.c = c; + gb->cpu_reg.pc.bytes.p = p; + inst_cycles += 12; + } else + gb->cpu_reg.pc.reg += 2; + + break; + + case 0xD5: /* PUSH DE */ + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.de.bytes.d); + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.de.bytes.e); + break; + + case 0xD6: /* SUB imm */ + { + uint8_t val = __gb_read(gb, gb->cpu_reg.pc.reg++); + uint16_t temp = gb->cpu_reg.a - val; + gb->cpu_reg.f.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f.f_bits.n = 1; + gb->cpu_reg.f.f_bits.h = (gb->cpu_reg.a ^ val ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); + break; + } + + case 0xD7: /* RST 0x0010 */ + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p); + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c); + gb->cpu_reg.pc.reg = 0x0010; + break; + + case 0xD8: /* RET C */ + if (gb->cpu_reg.f.f_bits.c) { + gb->cpu_reg.pc.bytes.c = __gb_read(gb, gb->cpu_reg.sp.reg++); + gb->cpu_reg.pc.bytes.p = __gb_read(gb, gb->cpu_reg.sp.reg++); + inst_cycles += 12; + } + + break; + + case 0xD9: /* RETI */ + { + gb->cpu_reg.pc.bytes.c = __gb_read(gb, gb->cpu_reg.sp.reg++); + gb->cpu_reg.pc.bytes.p = __gb_read(gb, gb->cpu_reg.sp.reg++); + gb->gb_ime = true; + } break; + + case 0xDA: /* JP C, imm */ + if (gb->cpu_reg.f.f_bits.c) { + uint8_t p, c; + c = __gb_read(gb, gb->cpu_reg.pc.reg++); + p = __gb_read(gb, gb->cpu_reg.pc.reg); + gb->cpu_reg.pc.bytes.c = c; + gb->cpu_reg.pc.bytes.p = p; + inst_cycles += 4; + } else + gb->cpu_reg.pc.reg += 2; + + break; + + case 0xDC: /* CALL C, imm */ + if (gb->cpu_reg.f.f_bits.c) { + uint8_t p, c; + c = __gb_read(gb, gb->cpu_reg.pc.reg++); + p = __gb_read(gb, gb->cpu_reg.pc.reg++); + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p); + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c); + gb->cpu_reg.pc.bytes.c = c; + gb->cpu_reg.pc.bytes.p = p; + inst_cycles += 12; + } else + gb->cpu_reg.pc.reg += 2; + + break; + + case 0xDE: /* SBC A, imm */ + { + uint8_t val = __gb_read(gb, gb->cpu_reg.pc.reg++); + PGB_INSTR_SBC_R8(val, gb->cpu_reg.f.f_bits.c); + break; + } + + case 0xDF: /* RST 0x0018 */ + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p); + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c); + gb->cpu_reg.pc.reg = 0x0018; + break; + + case 0xE0: /* LD (0xFF00+imm), A */ + __gb_write(gb, 0xFF00 | __gb_read(gb, gb->cpu_reg.pc.reg++), gb->cpu_reg.a); + break; + + case 0xE1: /* POP HL */ + gb->cpu_reg.hl.bytes.l = __gb_read(gb, gb->cpu_reg.sp.reg++); + gb->cpu_reg.hl.bytes.h = __gb_read(gb, gb->cpu_reg.sp.reg++); + break; + + case 0xE2: /* LD (C), A */ + __gb_write(gb, 0xFF00 | gb->cpu_reg.bc.bytes.c, gb->cpu_reg.a); + break; + + case 0xE5: /* PUSH HL */ + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.hl.bytes.h); + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.hl.bytes.l); + break; + + case 0xE6: /* AND imm */ + { + uint8_t temp = __gb_read(gb, gb->cpu_reg.pc.reg++); + PGB_INSTR_AND_R8(temp); + break; + } + + case 0xE7: /* RST 0x0020 */ + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p); + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c); + gb->cpu_reg.pc.reg = 0x0020; + break; + + case 0xE8: /* ADD SP, imm */ + { + int8_t offset = (int8_t)__gb_read(gb, gb->cpu_reg.pc.reg++); + gb->cpu_reg.f.reg = 0; + gb->cpu_reg.f.f_bits.h = ((gb->cpu_reg.sp.reg & 0xF) + (offset & 0xF) > 0xF) ? 1 : 0; + gb->cpu_reg.f.f_bits.c = ((gb->cpu_reg.sp.reg & 0xFF) + (offset & 0xFF) > 0xFF); + gb->cpu_reg.sp.reg += offset; + break; + } + + case 0xE9: /* JP (HL) */ + gb->cpu_reg.pc.reg = gb->cpu_reg.hl.reg; + break; + + case 0xEA: /* LD (imm), A */ + { + uint8_t h, l; + uint16_t addr; + l = __gb_read(gb, gb->cpu_reg.pc.reg++); + h = __gb_read(gb, gb->cpu_reg.pc.reg++); + addr = PEANUT_GB_U8_TO_U16(h, l); + __gb_write(gb, addr, gb->cpu_reg.a); + break; + } + + case 0xEE: /* XOR imm */ + PGB_INSTR_XOR_R8(__gb_read(gb, gb->cpu_reg.pc.reg++)); + break; + + case 0xEF: /* RST 0x0028 */ + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p); + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c); + gb->cpu_reg.pc.reg = 0x0028; + break; + + case 0xF0: /* LD A, (0xFF00+imm) */ + gb->cpu_reg.a = __gb_read(gb, 0xFF00 | __gb_read(gb, gb->cpu_reg.pc.reg++)); + break; + + case 0xF1: /* POP AF */ + { + uint8_t temp_8 = __gb_read(gb, gb->cpu_reg.sp.reg++); + gb->cpu_reg.f.f_bits.z = (temp_8 >> 7) & 1; + gb->cpu_reg.f.f_bits.n = (temp_8 >> 6) & 1; + gb->cpu_reg.f.f_bits.h = (temp_8 >> 5) & 1; + gb->cpu_reg.f.f_bits.c = (temp_8 >> 4) & 1; + gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.sp.reg++); + break; + } + + case 0xF2: /* LD A, (C) */ + gb->cpu_reg.a = __gb_read(gb, 0xFF00 | gb->cpu_reg.bc.bytes.c); + break; + + case 0xF3: /* DI */ + gb->gb_ime = false; + break; + + case 0xF5: /* PUSH AF */ + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.a); + __gb_write(gb, + --gb->cpu_reg.sp.reg, + gb->cpu_reg.f.f_bits.z << 7 | gb->cpu_reg.f.f_bits.n << 6 | + gb->cpu_reg.f.f_bits.h << 5 | gb->cpu_reg.f.f_bits.c << 4); + break; + + case 0xF6: /* OR imm */ + PGB_INSTR_OR_R8(__gb_read(gb, gb->cpu_reg.pc.reg++)); + break; + + case 0xF7: /* PUSH AF */ + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p); + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c); + gb->cpu_reg.pc.reg = 0x0030; + break; + + case 0xF8: /* LD HL, SP+/-imm */ + { + /* Taken from SameBoy, which is released under MIT Licence. */ + int8_t offset = (int8_t)__gb_read(gb, gb->cpu_reg.pc.reg++); + gb->cpu_reg.hl.reg = gb->cpu_reg.sp.reg + offset; + gb->cpu_reg.f.reg = 0; + gb->cpu_reg.f.f_bits.h = ((gb->cpu_reg.sp.reg & 0xF) + (offset & 0xF) > 0xF) ? 1 : 0; + gb->cpu_reg.f.f_bits.c = ((gb->cpu_reg.sp.reg & 0xFF) + (offset & 0xFF) > 0xFF) ? 1 : 0; + break; + } + + case 0xF9: /* LD SP, HL */ + gb->cpu_reg.sp.reg = gb->cpu_reg.hl.reg; + break; + + case 0xFA: /* LD A, (imm) */ + { + uint8_t h, l; + uint16_t addr; + l = __gb_read(gb, gb->cpu_reg.pc.reg++); + h = __gb_read(gb, gb->cpu_reg.pc.reg++); + addr = PEANUT_GB_U8_TO_U16(h, l); + gb->cpu_reg.a = __gb_read(gb, addr); + break; + } + + case 0xFB: /* EI */ + gb->gb_ime = true; + break; + + case 0xFE: /* CP imm */ + { + uint8_t val = __gb_read(gb, gb->cpu_reg.pc.reg++); + PGB_INSTR_CP_R8(val); + break; + } + + case 0xFF: /* RST 0x0038 */ + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p); + __gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c); + gb->cpu_reg.pc.reg = 0x0038; + break; + + default: + /* Return address where invalid opcode that was read. */ + (gb->gb_error)(gb, GB_INVALID_OPCODE, gb->cpu_reg.pc.reg - 1); + PGB_UNREACHABLE(); + } + + do { + /* DIV register timing */ + gb->counter.div_count += inst_cycles; + while (gb->counter.div_count >= DIV_CYCLES) { + gb->hram_io[IO_DIV]++; + gb->counter.div_count -= DIV_CYCLES; + } + + /* Check for RTC tick. */ + if (gb->mbc == 3 && (gb->rtc_real.reg.high & 0x40) == 0) { + gb->counter.rtc_count += inst_cycles; + while (PGB_UNLIKELY(gb->counter.rtc_count >= RTC_CYCLES)) { + gb->counter.rtc_count -= RTC_CYCLES; + + /* Detect invalid rollover. */ + if (PGB_UNLIKELY(gb->rtc_real.reg.sec == 63)) { + gb->rtc_real.reg.sec = 0; + continue; + } + + if (++gb->rtc_real.reg.sec != 60) + continue; + + gb->rtc_real.reg.sec = 0; + if (gb->rtc_real.reg.min == 63) { + gb->rtc_real.reg.min = 0; + continue; + } + if (++gb->rtc_real.reg.min != 60) + continue; + + gb->rtc_real.reg.min = 0; + if (gb->rtc_real.reg.hour == 31) { + gb->rtc_real.reg.hour = 0; + continue; + } + if (++gb->rtc_real.reg.hour != 24) + continue; + + gb->rtc_real.reg.hour = 0; + if (++gb->rtc_real.reg.yday != 0) + continue; + + if (gb->rtc_real.reg.high & 1) /* Bit 8 of days*/ + gb->rtc_real.reg.high |= 0x80; /* Overflow bit */ + + gb->rtc_real.reg.high ^= 1; + } + } + + /* Check serial transmission. */ + if (gb->hram_io[IO_SC] & SERIAL_SC_TX_START) { + /* If new transfer, call TX function. */ + if (gb->counter.serial_count == 0 && gb->gb_serial_tx != NULL) + (gb->gb_serial_tx)(gb, gb->hram_io[IO_SB]); + + gb->counter.serial_count += inst_cycles; + + /* If it's time to receive byte, call RX function. */ + if (gb->counter.serial_count >= SERIAL_CYCLES) { + /* If RX can be done, do it. */ + /* If RX failed, do not change SB if using external + * clock, or set to 0xFF if using internal clock. */ + uint8_t rx; + + if (gb->gb_serial_rx != NULL && (gb->gb_serial_rx(gb, &rx) == GB_SERIAL_RX_SUCCESS)) { + gb->hram_io[IO_SB] = rx; + + /* Inform game of serial TX/RX completion. */ + gb->hram_io[IO_SC] &= 0x01; + gb->hram_io[IO_IF] |= SERIAL_INTR; + } else if (gb->hram_io[IO_SC] & SERIAL_SC_CLOCK_SRC) { + /* If using internal clock, and console is not + * attached to any external peripheral, shifted + * bits are replaced with logic 1. */ + gb->hram_io[IO_SB] = 0xFF; + + /* Inform game of serial TX/RX completion. */ + gb->hram_io[IO_SC] &= 0x01; + gb->hram_io[IO_IF] |= SERIAL_INTR; + } else { + /* If using external clock, and console is not + * attached to any external peripheral, bits are + * not shifted, so SB is not modified. */ + } + + gb->counter.serial_count = 0; + } + } + + /* TIMA register timing */ + /* TODO: Change tac_enable to struct of TAC timer control bits. */ + if (gb->hram_io[IO_TAC] & IO_TAC_ENABLE_MASK) { + gb->counter.tima_count += inst_cycles; + + while (gb->counter.tima_count >= TAC_CYCLES[gb->hram_io[IO_TAC] & IO_TAC_RATE_MASK]) { + gb->counter.tima_count -= TAC_CYCLES[gb->hram_io[IO_TAC] & IO_TAC_RATE_MASK]; + + if (++gb->hram_io[IO_TIMA] == 0) { + gb->hram_io[IO_IF] |= TIMER_INTR; + /* On overflow, set TMA to TIMA. */ + gb->hram_io[IO_TIMA] = gb->hram_io[IO_TMA]; + } + } + } + + /* If LCD is off, don't update LCD state or increase the LCD + * ticks. Instead, keep track of the amount of time that is + * being passed. */ + if (!(gb->hram_io[IO_LCDC] & LCDC_ENABLE)) { + gb->counter.lcd_off_count += inst_cycles; + if (gb->counter.lcd_off_count >= LCD_FRAME_CYCLES) { + gb->counter.lcd_off_count -= LCD_FRAME_CYCLES; + gb->gb_frame = true; + } + continue; + } + + /* LCD Timing */ + gb->counter.lcd_count += inst_cycles; + + /* New Scanline. HBlank -> VBlank or OAM Scan */ + if (gb->counter.lcd_count >= LCD_LINE_CYCLES) { + gb->counter.lcd_count -= LCD_LINE_CYCLES; + + /* Next line */ + gb->hram_io[IO_LY] = gb->hram_io[IO_LY] + 1; + if (gb->hram_io[IO_LY] == LCD_VERT_LINES) + gb->hram_io[IO_LY] = 0; + + /* LYC Update */ + if (gb->hram_io[IO_LY] == gb->hram_io[IO_LYC]) { + gb->hram_io[IO_STAT] |= STAT_LYC_COINC; + + if (gb->hram_io[IO_STAT] & STAT_LYC_INTR) + gb->hram_io[IO_IF] |= LCDC_INTR; + } else + gb->hram_io[IO_STAT] &= 0xFB; + + /* Check if LCD should be in Mode 1 (VBLANK) state */ + if (gb->hram_io[IO_LY] == LCD_HEIGHT) { + gb->hram_io[IO_STAT] = (gb->hram_io[IO_STAT] & ~STAT_MODE) | IO_STAT_MODE_VBLANK; + gb->gb_frame = true; + gb->hram_io[IO_IF] |= VBLANK_INTR; + gb->lcd_blank = false; + + if (gb->hram_io[IO_STAT] & STAT_MODE_1_INTR) + gb->hram_io[IO_IF] |= LCDC_INTR; + +#if ENABLE_LCD + /* If frame skip is activated, check if we need to draw + * the frame or skip it. */ + if (gb->direct.frame_skip) { + gb->display.frame_skip_count = !gb->display.frame_skip_count; + } + + /* If interlaced is activated, change which lines get + * updated. Also, only update lines on frames that are + * actually drawn when frame skip is enabled. */ + if (gb->direct.interlace && (!gb->direct.frame_skip || gb->display.frame_skip_count)) { + gb->display.interlace_count = !gb->display.interlace_count; + } +#endif + /* If halted forever, then return on VBLANK. */ + if (gb->gb_halt && !gb->hram_io[IO_IE]) + break; + } + /* Start of normal Line (not in VBLANK) */ + else if (gb->hram_io[IO_LY] < LCD_HEIGHT) { + if (gb->hram_io[IO_LY] == 0) { + /* Clear Screen */ + gb->display.WY = gb->hram_io[IO_WY]; + gb->display.window_clear = 0; + } + + /* OAM Search occurs at the start of the line. */ + gb->hram_io[IO_STAT] = (gb->hram_io[IO_STAT] & ~STAT_MODE) | IO_STAT_MODE_OAM_SCAN; + gb->counter.lcd_count = 0; + + if (gb->hram_io[IO_STAT] & STAT_MODE_2_INTR) + gb->hram_io[IO_IF] |= LCDC_INTR; + + /* If halted immediately jump to next LCD mode. + * From OAM Search to LCD Draw. */ + // if(gb->counter.lcd_count < LCD_MODE2_OAM_SCAN_END) + // inst_cycles = LCD_MODE2_OAM_SCAN_END - gb->counter.lcd_count; + inst_cycles = LCD_MODE2_OAM_SCAN_DURATION; + } + } + /* Go from Mode 3 (LCD Draw) to Mode 0 (HBLANK). */ + else if ((gb->hram_io[IO_STAT] & STAT_MODE) == IO_STAT_MODE_LCD_DRAW && + gb->counter.lcd_count >= LCD_MODE3_LCD_DRAW_END) { + gb->hram_io[IO_STAT] = (gb->hram_io[IO_STAT] & ~STAT_MODE) | IO_STAT_MODE_HBLANK; + + if (gb->hram_io[IO_STAT] & STAT_MODE_0_INTR) + gb->hram_io[IO_IF] |= LCDC_INTR; + + /* If halted immediately, jump from OAM Scan to LCD Draw. */ + if (gb->counter.lcd_count < LCD_MODE0_HBLANK_MAX_DRUATION) + inst_cycles = LCD_MODE0_HBLANK_MAX_DRUATION - gb->counter.lcd_count; + } + /* Go from Mode 2 (OAM Scan) to Mode 3 (LCD Draw). */ + else if ((gb->hram_io[IO_STAT] & STAT_MODE) == IO_STAT_MODE_OAM_SCAN && + gb->counter.lcd_count >= LCD_MODE2_OAM_SCAN_END) { + gb->hram_io[IO_STAT] = (gb->hram_io[IO_STAT] & ~STAT_MODE) | IO_STAT_MODE_LCD_DRAW; +#if ENABLE_LCD + if (!gb->lcd_blank) + __gb_draw_line(gb); +#endif + /* If halted immediately jump to next LCD mode. */ + if (gb->counter.lcd_count < LCD_MODE3_LCD_DRAW_MIN_DURATION) + inst_cycles = LCD_MODE3_LCD_DRAW_MIN_DURATION - gb->counter.lcd_count; + } + } while (gb->gb_halt && (gb->hram_io[IO_IF] & gb->hram_io[IO_IE]) == 0); + /* If halted, loop until an interrupt occurs. */ +} + +void gb_run_frame(struct gb_s *gb) { + gb->gb_frame = false; + + while (!gb->gb_frame) + __gb_step_cpu(gb); +} + +int gb_get_save_size_s(struct gb_s *gb, size_t *ram_size) { + const uint_fast16_t ram_size_location = 0x0149; + const uint_fast32_t ram_sizes[] = {/* 0, 2KiB, 8KiB, 32KiB, 128KiB, 64KiB */ + 0x00, + 0x800, + 0x2000, + 0x8000, + 0x20000, + 0x10000}; + uint8_t ram_size_code = gb->gb_rom_read(gb, ram_size_location); + + /* MBC2 always has 512 half-bytes of cart RAM. + * This assumes that only the lower nibble of each byte is used; the + * nibbles are not packed. */ + if (gb->mbc == 2) { + *ram_size = 0x200; + return 0; + } + + /* Return -1 on invalid or unsupported RAM size. */ + if (ram_size_code >= PEANUT_GB_ARRAYSIZE(ram_sizes)) + return -1; + + *ram_size = ram_sizes[ram_size_code]; + return 0; +} + +PGB_DEPRECATED("Does not return error code. Use gb_get_save_size_s instead.") +uint_fast32_t gb_get_save_size(struct gb_s *gb) { + const uint_fast16_t ram_size_location = 0x0149; + const uint_fast32_t ram_sizes[] = {/* 0, 2KiB, 8KiB, 32KiB, 128KiB, 64KiB */ + 0x00, + 0x800, + 0x2000, + 0x8000, + 0x20000, + 0x10000}; + uint8_t ram_size_code = gb->gb_rom_read(gb, ram_size_location); + + /* MBC2 always has 512 half-bytes of cart RAM. + * This assumes that only the lower nibble of each byte is used; the + * nibbles are not packed. */ + if (gb->mbc == 2) + return 0x200; + + /* Return 0 on invalid or unsupported RAM size. */ + if (ram_size_code >= PEANUT_GB_ARRAYSIZE(ram_sizes)) + return 0; + + return ram_sizes[ram_size_code]; +} + +void gb_init_serial(struct gb_s *gb, + void (*gb_serial_tx)(struct gb_s *, const uint8_t), + enum gb_serial_rx_ret_e (*gb_serial_rx)(struct gb_s *, uint8_t *)) { + gb->gb_serial_tx = gb_serial_tx; + gb->gb_serial_rx = gb_serial_rx; +} + +uint8_t gb_colour_hash(struct gb_s *gb) { +#define ROM_TITLE_START_ADDR 0x0134 +#define ROM_TITLE_END_ADDR 0x0143 + + uint8_t x = 0; + uint16_t i; + + for (i = ROM_TITLE_START_ADDR; i <= ROM_TITLE_END_ADDR; i++) + x += gb->gb_rom_read(gb, i); + + return x; +} + +/** + * Resets the context, and initialises startup values for a DMG console. + */ +void gb_reset(struct gb_s *gb) { + gb->gb_halt = false; + gb->gb_ime = true; + + /* Initialise MBC values. */ + gb->selected_rom_bank = 1; + gb->cart_ram_bank = 0; + gb->enable_cart_ram = 0; + gb->cart_mode_select = 0; + + /* Use values as though the boot ROM was already executed. */ + if (gb->gb_bootrom_read == NULL) { + uint8_t hdr_chk; + hdr_chk = gb->gb_rom_read(gb, ROM_HEADER_CHECKSUM_LOC) != 0; + + gb->cpu_reg.a = 0x01; + gb->cpu_reg.f.f_bits.z = 1; + gb->cpu_reg.f.f_bits.n = 0; + gb->cpu_reg.f.f_bits.h = hdr_chk; + gb->cpu_reg.f.f_bits.c = hdr_chk; + gb->cpu_reg.bc.reg = 0x0013; + gb->cpu_reg.de.reg = 0x00D8; + gb->cpu_reg.hl.reg = 0x014D; + gb->cpu_reg.sp.reg = 0xFFFE; + gb->cpu_reg.pc.reg = 0x0100; + + gb->hram_io[IO_DIV] = 0xAB; + gb->hram_io[IO_LCDC] = 0x91; + gb->hram_io[IO_STAT] = 0x85; + gb->hram_io[IO_BOOT] = 0x01; + + __gb_write(gb, 0xFF26, 0xF1); + + memset(gb->vram, 0x00, VRAM_SIZE); + } else { + /* Set value as though the console was just switched on. + * CPU registers are uninitialised. */ + gb->cpu_reg.pc.reg = 0x0000; + gb->hram_io[IO_DIV] = 0x00; + gb->hram_io[IO_LCDC] = 0x00; + gb->hram_io[IO_STAT] = 0x84; + gb->hram_io[IO_BOOT] = 0x00; + } + + gb->counter.lcd_count = 0; + gb->counter.div_count = 0; + gb->counter.tima_count = 0; + gb->counter.serial_count = 0; + gb->counter.rtc_count = 0; + gb->counter.lcd_off_count = 0; + + gb->direct.joypad = 0xFF; + gb->hram_io[IO_JOYP] = 0xCF; + gb->hram_io[IO_SB] = 0x00; + gb->hram_io[IO_SC] = 0x7E; + /* DIV */ + gb->hram_io[IO_TIMA] = 0x00; + gb->hram_io[IO_TMA] = 0x00; + gb->hram_io[IO_TAC] = 0xF8; + gb->hram_io[IO_IF] = 0xE1; + + /* LCDC */ + /* STAT */ + gb->hram_io[IO_SCY] = 0x00; + gb->hram_io[IO_SCX] = 0x00; + gb->hram_io[IO_LY] = 0x00; + gb->hram_io[IO_LYC] = 0x00; + __gb_write(gb, 0xFF47, 0xFC); // BGP + __gb_write(gb, 0xFF48, 0xFF); // OBJP0 + __gb_write(gb, 0xFF49, 0xFF); // OBJP1 + gb->hram_io[IO_WY] = 0x00; + gb->hram_io[IO_WX] = 0x00; + gb->hram_io[IO_IE] = 0x00; + gb->hram_io[IO_IF] = 0xE1; +} + +enum gb_init_error_e +gb_init(struct gb_s *gb, + uint8_t (*gb_rom_read)(struct gb_s *, const uint_fast32_t), + uint8_t (*gb_cart_ram_read)(struct gb_s *, const uint_fast32_t), + void (*gb_cart_ram_write)(struct gb_s *, const uint_fast32_t, const uint8_t), + void (*gb_error)(struct gb_s *, const enum gb_error_e, const uint16_t), + void *priv) { + const uint16_t mbc_location = 0x0147; + const uint16_t bank_count_location = 0x0148; + const uint16_t ram_size_location = 0x0149; + /** + * Table for cartridge type (MBC). -1 if invalid. + * TODO: MMM01 is untested. + * TODO: MBC6 is untested. + * TODO: MBC7 is unsupported. + * TODO: POCKET CAMERA is unsupported. + * TODO: BANDAI TAMA5 is unsupported. + * TODO: HuC3 is unsupported. + * TODO: HuC1 is unsupported. + **/ + const int8_t cart_mbc[] = {0, 1, 1, 1, -1, 2, 2, -1, 0, 0, -1, 0, 0, 0, -1, 3, + 3, 3, 3, 3, -1, -1, -1, -1, -1, 5, 5, 5, 5, 5, 5, -1}; + /* Whether cart has RAM. */ + const uint8_t cart_ram[] = {0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0}; + /* How large the ROM is in banks of 16 KiB. */ + const uint16_t num_rom_banks_mask[] = {2, 4, 8, 16, 32, 64, 128, 256, 512}; + /* How large the cart RAM is in banks of 8 KiB. Code $01 is unused, but + * some early homebrew ROMs supposedly may use this value. */ + const uint8_t num_ram_banks[] = {0, 1, 1, 4, 16, 8}; + + gb->gb_rom_read = gb_rom_read; + gb->gb_cart_ram_read = gb_cart_ram_read; + gb->gb_cart_ram_write = gb_cart_ram_write; + gb->gb_error = gb_error; + gb->direct.priv = priv; + + /* Initialise serial transfer function to NULL. If the front-end does + * not provide serial support, Peanut-GB will emulate no cable connected + * automatically. */ + gb->gb_serial_tx = NULL; + gb->gb_serial_rx = NULL; + + gb->gb_bootrom_read = NULL; + + /* Check valid ROM using checksum value. */ + { + uint8_t x = 0; + uint16_t i; + + for (i = 0x0134; i <= 0x014C; i++) + x = x - gb->gb_rom_read(gb, i) - 1; + + if (x != gb->gb_rom_read(gb, ROM_HEADER_CHECKSUM_LOC)) + return GB_INIT_INVALID_CHECKSUM; + } + + /* Check if cartridge type is supported, and set MBC type. */ + { + const uint8_t mbc_value = gb->gb_rom_read(gb, mbc_location); + + if (mbc_value > sizeof(cart_mbc) - 1 || (gb->mbc = cart_mbc[mbc_value]) == -1) + return GB_INIT_CARTRIDGE_UNSUPPORTED; + } + + gb->num_rom_banks_mask = num_rom_banks_mask[gb->gb_rom_read(gb, bank_count_location)] - 1; + gb->cart_ram = cart_ram[gb->gb_rom_read(gb, mbc_location)]; + gb->num_ram_banks = num_ram_banks[gb->gb_rom_read(gb, ram_size_location)]; + + /* If the ROM says that it support RAM, but has 0 RAM banks, then + * disable RAM reads from the cartridge. */ + if (gb->cart_ram == 0 || gb->num_ram_banks == 0) { + gb->cart_ram = 0; + gb->num_ram_banks = 0; + } + + /* If MBC3 and number of ROM or RAM banks are larger than 128 or 8, + * respectively, then select MBC3O mode. */ + if (gb->mbc == 3) + gb->cart_is_mbc3O = gb->num_rom_banks_mask > 128 || gb->num_ram_banks > 4; + + /* Note that MBC2 will appear to have no RAM banks, but it actually + * always has 512 half-bytes of RAM. Hence, gb->num_ram_banks must be + * ignored for MBC2. */ + + gb->lcd_blank = false; + gb->display.lcd_draw_line = NULL; + + gb_reset(gb); + + return GB_INIT_NO_ERROR; +} + +const char *gb_get_rom_name(struct gb_s *gb, char *title_str) { + uint_fast16_t title_loc = 0x134; + /* End of title may be 0x13E for newer games. */ + const uint_fast16_t title_end = 0x143; + const char *title_start = title_str; + + for (; title_loc <= title_end; title_loc++) { + const char title_char = gb->gb_rom_read(gb, title_loc); + + if (title_char >= ' ' && title_char <= '_') { + *title_str = title_char; + title_str++; + } else + break; + } + + *title_str = '\0'; + return title_start; +} + +#if ENABLE_LCD +void gb_init_lcd(struct gb_s *gb, + void (*lcd_draw_line)(struct gb_s *gb, + const uint8_t *pixels, + const uint_fast8_t line)) { + gb->display.lcd_draw_line = lcd_draw_line; + + gb->direct.interlace = false; + gb->display.interlace_count = false; + gb->direct.frame_skip = false; + gb->display.frame_skip_count = false; + + gb->display.window_clear = 0; + gb->display.WY = 0; + + return; +} +#endif + +void gb_set_bootrom(struct gb_s *gb, + uint8_t (*gb_bootrom_read)(struct gb_s *, const uint_fast16_t)) { + gb->gb_bootrom_read = gb_bootrom_read; +} + +/** + * Deprecated. Will be removed in the next major version. + */ +PGB_DEPRECATED("RTC is now ticked internally; this function has no effect") +void gb_tick_rtc(struct gb_s *gb) { + (void)gb; + return; +} + +void gb_set_rtc(struct gb_s *gb, const struct tm *const time) { + gb->rtc_real.bytes[0] = time->tm_sec; + gb->rtc_real.bytes[1] = time->tm_min; + gb->rtc_real.bytes[2] = time->tm_hour; + gb->rtc_real.bytes[3] = time->tm_yday & 0xFF; /* Low 8 bits of day counter. */ + gb->rtc_real.bytes[4] = time->tm_yday >> 8; /* High 1 bit of day counter. */ +} +#endif // PEANUT_GB_HEADER_ONLY + +/** Function prototypes: Required functions **/ +/** + * Initialises the emulator context to a known state. Call this before calling + * any other peanut-gb function. + * To reset the emulator, you can call gb_reset() instead. + * + * \param gb Allocated emulator context. Must not be NULL. + * \param gb_rom_read Pointer to function that reads ROM data. ROM banking is + * already handled by Peanut-GB. Must not be NULL. + * \param gb_cart_ram_read Pointer to function that reads Cart RAM. Must not be + * NULL. + * \param gb_cart_ram_write Pointer to function to writes to Cart RAM. Must not + * be NULL. + * \param gb_error Pointer to function that is called when an unrecoverable + * error occurs. Must not be NULL. Returning from this + * function is undefined and will result in SIGABRT. + * \param priv Private data that is stored within the emulator context. Set to + * NULL if unused. + * \returns 0 on success or an enum that describes the error. + */ +enum gb_init_error_e +gb_init(struct gb_s *gb, + uint8_t (*gb_rom_read)(struct gb_s *, const uint_fast32_t), + uint8_t (*gb_cart_ram_read)(struct gb_s *, const uint_fast32_t), + void (*gb_cart_ram_write)(struct gb_s *, const uint_fast32_t, const uint8_t), + void (*gb_error)(struct gb_s *, const enum gb_error_e, const uint16_t), + void *priv); + +/** + * Executes the emulator and runs for the duration of time equal to one frame. + * + * \param An initialised emulator context. Must not be NULL. + */ +void gb_run_frame(struct gb_s *gb); + +/** + * Internal function used to step the CPU. Used mainly for testing. + * Use gb_run_frame() instead. + * + * \param An initialised emulator context. Must not be NULL. + */ +void __gb_step_cpu(struct gb_s *gb); + +/** Function prototypes: Optional Functions **/ +/** + * Reset the emulator, like turning the Game Boy off and on again. + * This function can be called at any time. + * + * \param An initialised emulator context. Must not be NULL. + */ +void gb_reset(struct gb_s *gb); + +/** + * Initialises the display context of the emulator. Only available when + * ENABLE_LCD is defined to a non-zero value. + * The pixel data sent to lcd_draw_line comes with both shade and layer data. + * The first two least significant bits are the shade data (black, dark, light, + * white). Bits 4 and 5 are layer data (OBJ0, OBJ1, BG), which can be used to + * add more colours to the game in the same way that the Game Boy Color does to + * older Game Boy games. + * This function can be called at any time. + * + * \param gb An initialised emulator context. Must not be NULL. + * \param lcd_draw_line Pointer to function that draws the 2-bit pixel data on the line + * "line". Must not be NULL. + */ +#if ENABLE_LCD +void gb_init_lcd(struct gb_s *gb, + void (*lcd_draw_line)(struct gb_s *gb, + const uint8_t *pixels, + const uint_fast8_t line)); +#endif + +/** + * Initialises the serial connection of the emulator. This function is optional, + * and if not called, the emulator will assume that no link cable is connected + * to the game. + * + * \param gb An initialised emulator context. Must not be NULL. + * \param gb_serial_tx Pointer to function that transmits a byte of data over + * the serial connection. Must not be NULL. + * \param gb_serial_rx Pointer to function that receives a byte of data over the + * serial connection. If no byte is received, + * return GB_SERIAL_RX_NO_CONNECTION. Must not be NULL. + */ +void gb_init_serial(struct gb_s *gb, + void (*gb_serial_tx)(struct gb_s *, const uint8_t), + enum gb_serial_rx_ret_e (*gb_serial_rx)(struct gb_s *, uint8_t *)); + +/** + * Obtains the save size of the game (size of the Cart RAM). Required by the + * frontend to allocate enough memory for the Cart RAM. + * + * \param gb An initialised emulator context. Must not be NULL. + * \param ram_size Pointer to size_t variable that will be set to the size of + * the Cart RAM in bytes. Must not be NULL. + * If the Cart RAM is not battery backed, this will be set to 0. + * If the Cart RAM size is invalid or unknown, this will not be + * set. + * \returns 0 on success, or -1 if the RAM size is invalid or unknown. + */ +int gb_get_save_size_s(struct gb_s *gb, size_t *ram_size); + +/** + * Deprecated. Use gb_get_save_size_s() instead. + * Obtains the save size of the game (size of the Cart RAM). Required by the + * frontend to allocate enough memory for the Cart RAM. + * + * \param gb An initialised emulator context. Must not be NULL. + * \returns Size of the Cart RAM in bytes. 0 if Cartridge has not battery + * backed RAM. + * 0 is also returned on invalid or unknown RAM size. + */ +uint_fast32_t gb_get_save_size(struct gb_s *gb); + +/** + * Calculates and returns a hash of the game header in the same way the Game + * Boy Color does for colourising old Game Boy games. The frontend can use this + * hash to automatically set a colour palette. + * + * \param gb An initialised emulator context. Must not be NULL. + * \returns Hash of the game header. + */ +uint8_t gb_colour_hash(struct gb_s *gb); + +/** + * Returns the title of ROM. + * + * \param gb An initialised emulator context. Must not be NULL. + * \param title_str Allocated string at least 16 characters. + * \returns Pointer to start of string, null terminated. + */ +const char *gb_get_rom_name(struct gb_s *gb, char *title_str); + +/** + * Deprecated. Will be removed in the next major version. + * RTC is ticked internally and this function has no effect. + */ +void gb_tick_rtc(struct gb_s *gb); + +/** + * Set initial values in RTC. + * Should be called after gb_init(). + * + * \param gb An initialised emulator context. Must not be NULL. + * \param time Time structure with date and time. + */ +void gb_set_rtc(struct gb_s *gb, const struct tm *const time); + +/** + * Use boot ROM on reset. gb_reset() must be called for this to take affect. + * \param gb An initialised emulator context. Must not be NULL. + * \param gb_bootrom_read Function pointer to read boot ROM binary. + */ +void gb_set_bootrom(struct gb_s *gb, + uint8_t (*gb_bootrom_read)(struct gb_s *, const uint_fast16_t)); + +/* Undefine CPU Flag helper functions. */ +#undef PEANUT_GB_CPUFLAG_MASK_CARRY +#undef PEANUT_GB_CPUFLAG_MASK_HALFC +#undef PEANUT_GB_CPUFLAG_MASK_ARITH +#undef PEANUT_GB_CPUFLAG_MASK_ZERO +#undef PEANUT_GB_CPUFLAG_BIT_CARRY +#undef PEANUT_GB_CPUFLAG_BIT_HALFC +#undef PEANUT_GB_CPUFLAG_BIT_ARITH +#undef PEANUT_GB_CPUFLAG_BIT_ZERO +#undef PGB_SET_CARRY +#undef PGB_SET_HALFC +#undef PGB_SET_ARITH +#undef PGB_SET_ZERO +#undef PGB_GET_CARRY +#undef PGB_GET_HALFC +#undef PGB_GET_ARITH +#undef PGB_GET_ZERO +#endif // PEANUT_GB_H diff --git a/firmware_p4/components/Applications/nfc/README.md b/firmware_p4/components/Applications/nfc/README.md new file mode 100644 index 000000000..1e4fb31e7 --- /dev/null +++ b/firmware_p4/components/Applications/nfc/README.md @@ -0,0 +1,7 @@ +# NFC Application + +Documentation for this component lives in the project docs hub (single source of truth): + +- [docs/nfc/README.md](../../../../docs/nfc/README.md) + +This file is only a pointer: edit the docs there to keep things from drifting. diff --git a/firmware_p4/components/Applications/nfc/nfc_reader.c b/firmware_p4/components/Applications/nfc/nfc_reader.c index f09963a28..1b53a45a2 100644 --- a/firmware_p4/components/Applications/nfc/nfc_reader.c +++ b/firmware_p4/components/Applications/nfc/nfc_reader.c @@ -14,6 +14,8 @@ // along with TentacleOS. If not, see . #include "nfc_reader.h" +#include "esp_attr.h" + #include #include @@ -441,7 +443,7 @@ void mf_classic_read_full(nfc_iso14443a_data_t *card) { mf_classic_key_t nested_src_key; mf_key_type_t nested_src_type = MF_KEY_A; - static sector_result_t results[40]; + EXT_RAM_BSS_ATTR static sector_result_t results[40]; memset(results, 0, sizeof(results)); for (int sect = 0; sect < nsect; sect++) { diff --git a/firmware_p4/components/Applications/nfc/protocols/mifare/mf_key_cache.c b/firmware_p4/components/Applications/nfc/protocols/mifare/mf_key_cache.c index 5ac526d28..11adf3b2c 100644 --- a/firmware_p4/components/Applications/nfc/protocols/mifare/mf_key_cache.c +++ b/firmware_p4/components/Applications/nfc/protocols/mifare/mf_key_cache.c @@ -15,6 +15,8 @@ #include "mf_key_cache.h" +#include "esp_attr.h" + #include #include "esp_log.h" @@ -30,7 +32,7 @@ static const char *TAG = "NFC_MF_KEY_CACHE"; #define NVS_ENTRY_NAME_SIZE 16 #define MF_KEY_SIZE 6 -static mf_key_cache_entry_t s_cache[MF_KEY_CACHE_MAX_CARDS]; +EXT_RAM_BSS_ATTR static mf_key_cache_entry_t s_cache[MF_KEY_CACHE_MAX_CARDS]; static int s_count = 0; static int find_entry(const uint8_t *uid, uint8_t uid_len) { diff --git a/firmware_p4/components/Applications/ui/assets_manager.c b/firmware_p4/components/Applications/ui/assets_manager.c index 695539ca5..abc37f9b1 100644 --- a/firmware_p4/components/Applications/ui/assets_manager.c +++ b/firmware_p4/components/Applications/ui/assets_manager.c @@ -30,8 +30,6 @@ static const char *TAG = "ASSETS_MANAGER"; -#define ARGB8888_BYTES_PER_PIXEL 4 - typedef struct __attribute__((packed)) { uint32_t magic_cf; uint16_t w; @@ -104,7 +102,7 @@ static lv_result_t asset_decoder_open(lv_image_decoder_t *decoder, lv_image_deco const uint32_t w = node->dsc.header.w; const uint32_t h = node->dsc.header.h; - lv_draw_buf_t *buf = lv_draw_buf_create(w, h, LV_COLOR_FORMAT_ARGB8888, LV_STRIDE_AUTO); + lv_draw_buf_t *buf = lv_draw_buf_create(w, h, node->dsc.header.cf, LV_STRIDE_AUTO); if (buf == NULL) { ESP_LOGE(TAG, "draw buf alloc failed for %s (%lux%lu)", @@ -122,16 +120,15 @@ static lv_result_t asset_decoder_open(lv_image_decoder_t *decoder, lv_image_deco return LV_RESULT_INVALID; } - const uint32_t row_bytes = w * ARGB8888_BYTES_PER_PIXEL; - bool ok = true; - for (uint32_t y = 0; y < h && ok; y++) { - uint8_t *row = buf->data + (size_t)y * buf->header.stride; - ok = fread(row, 1, row_bytes, f) == row_bytes; - } + size_t got = fread(buf->data, 1, buf->data_size, f); fclose(f); - if (!ok) { - ESP_LOGE(TAG, "pixel read failed for %s", node->path); + if (got != buf->data_size) { + ESP_LOGE(TAG, + "pixel read failed for %s (%u/%u)", + node->path, + (unsigned)got, + (unsigned)buf->data_size); lv_draw_buf_destroy(buf); return LV_RESULT_INVALID; } @@ -229,13 +226,21 @@ lv_image_dsc_t *assets_get(const char *path) { return NULL; } + lv_color_format_t cf = LV_COLOR_FORMAT_ARGB8888; + if ((hdr.magic_cf & 0xFF) == LV_IMAGE_HEADER_MAGIC) + cf = (lv_color_format_t)((hdr.magic_cf >> 8) & 0xFF); + uint32_t stride = lv_draw_buf_width_to_stride(hdr.w, cf); + uint32_t data_size = stride * hdr.h; + if (cf == LV_COLOR_FORMAT_RGB565A8) + data_size += (stride / 2) * hdr.h; + node->dsc.header.magic = LV_IMAGE_HEADER_MAGIC; - node->dsc.header.cf = LV_COLOR_FORMAT_ARGB8888; + node->dsc.header.cf = cf; node->dsc.header.w = hdr.w; node->dsc.header.h = hdr.h; - node->dsc.header.stride = hdr.w * ARGB8888_BYTES_PER_PIXEL; + node->dsc.header.stride = stride; node->dsc.header.flags = 0; - node->dsc.data_size = (uint32_t)hdr.w * hdr.h * ARGB8888_BYTES_PER_PIXEL; + node->dsc.data_size = data_size; node->dsc.data = (const uint8_t *)node->path; node->next = s_assets_head; diff --git a/firmware_p4/components/Applications/ui/components/capture_result/capture_result_ui.c b/firmware_p4/components/Applications/ui/components/capture_result/capture_result_ui.c index 27b5cae63..5bea94807 100644 --- a/firmware_p4/components/Applications/ui/components/capture_result/capture_result_ui.c +++ b/firmware_p4/components/Applications/ui/components/capture_result/capture_result_ui.c @@ -21,7 +21,6 @@ #include "ui_chrome.h" #include "ui_theme.h" -#define COL_DIM 0x8A8594 #define COL_RAISE 0x170A28 #define CR_TOP UI_CHROME_HEADER_H @@ -58,7 +57,7 @@ static void style_row(capture_result_t *cr, int i, bool sel) { lv_obj_set_style_shadow_color(cr->rows[i], cr->accent, 0); lv_obj_set_style_shadow_spread(cr->rows[i], sel ? -3 : 0, 0); if (!(cr->saved && i == CAP_ACT_SAVE)) - lv_obj_set_style_text_color(cr->icons[i], sel ? cr->accent : lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(cr->icons[i], sel ? cr->accent : current_theme.text_secondary, 0); } static void refresh(capture_result_t *cr) { @@ -127,7 +126,7 @@ static void make_card(capture_result_t *cr, lv_obj_t *root, const capture_result lv_label_set_long_mode(sub, LV_LABEL_LONG_DOT); lv_label_set_text(sub, cfg->card_sub); lv_obj_set_style_text_font(sub, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(sub, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(sub, current_theme.text_secondary, 0); } if (cfg->card_value) { lv_obj_t *val = lv_label_create(col); diff --git a/firmware_p4/components/Applications/ui/components/chrome/ui_chrome.c b/firmware_p4/components/Applications/ui/components/chrome/ui_chrome.c index 60691f7f1..d64678909 100644 --- a/firmware_p4/components/Applications/ui/components/chrome/ui_chrome.c +++ b/firmware_p4/components/Applications/ui/components/chrome/ui_chrome.c @@ -20,6 +20,7 @@ #include "st7789.h" #include "header_ui.h" +#include "ui_metrics.h" #include "ui_theme.h" // Whether the next chrome header shows the breadcrumb letreiro. Set per screen by @@ -72,7 +73,7 @@ lv_obj_t *ui_chrome_light_title(lv_obj_t *container, const char *title) { lv_obj_set_style_text_opa(lbl, LV_OPA_80, 0); lv_obj_set_style_text_font(lbl, &lv_font_montserrat_12, 0); lv_label_set_long_mode(lbl, LV_LABEL_LONG_DOT); - lv_obj_set_width(lbl, LCD_H_RES - 24); + lv_obj_set_width(lbl, ui_screen_w() - 24); lv_obj_align(lbl, LV_ALIGN_BOTTOM_LEFT, 12, -1); return lbl; } @@ -81,7 +82,7 @@ lv_obj_t *ui_chrome_light_title(lv_obj_t *container, const char *title) { // still starts at UI_CHROME_HEADER_H and no per-screen layout changes are needed. static lv_obj_t *make_header_container(lv_obj_t *parent) { lv_obj_t *hdr = lv_obj_create(parent); - lv_obj_set_size(hdr, LCD_H_RES, UI_CHROME_HEADER_H); + lv_obj_set_size(hdr, lv_pct(100), UI_CHROME_HEADER_H); lv_obj_align(hdr, LV_ALIGN_TOP_LEFT, 0, 0); lv_obj_remove_flag(hdr, LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(hdr, LV_OBJ_FLAG_CLICKABLE); @@ -114,7 +115,7 @@ lv_obj_t *ui_chrome_header_overlay(lv_obj_t *parent, const char *title, const ch lv_obj_t *ui_chrome_footer(lv_obj_t *parent, const char *hint) { lv_obj_t *ft = lv_obj_create(parent); - lv_obj_set_size(ft, LCD_H_RES, UI_CHROME_FOOTER_H); + lv_obj_set_size(ft, lv_pct(100), UI_CHROME_FOOTER_H); lv_obj_align(ft, LV_ALIGN_BOTTOM_LEFT, 0, 0); lv_obj_remove_flag(ft, LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(ft, LV_OBJ_FLAG_CLICKABLE); diff --git a/firmware_p4/components/Applications/ui/components/dropdown/dropdown_ui.c b/firmware_p4/components/Applications/ui/components/dropdown/dropdown_ui.c index 4d8c6c950..ef2151b72 100644 --- a/firmware_p4/components/Applications/ui/components/dropdown/dropdown_ui.c +++ b/firmware_p4/components/Applications/ui/components/dropdown/dropdown_ui.c @@ -37,6 +37,7 @@ #include "tos_storage_paths.h" #include "tutorial_ui.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #include "vfs_sdcard.h" #include "wifi_service.h" @@ -361,8 +362,8 @@ static void slide_btn_timer_cb(lv_timer_t *timer) { slide_btn_timer = NULL; return; } - bool up = up_button_is_down(), down = down_button_is_down(); - bool left = left_button_is_down(), right = right_button_is_down(); + bool up = ui_nav_pressed(INPUT_BTN_UP), down = ui_nav_pressed(INPUT_BTN_DOWN); + bool left = ui_nav_pressed(INPUT_BTN_LEFT), right = ui_nav_pressed(INPUT_BTN_RIGHT); bool ok = ok_button_is_down(), back = back_button_is_down(); uint32_t nowt = lv_tick_get(); @@ -795,8 +796,8 @@ void dropdown_ui_create(lv_obj_t *parent) { lv_obj_update_layout(slide_panel); s_panel_h = lv_obj_get_height(slide_panel); - if (s_panel_h < 40 || s_panel_h > LCD_V_RES) - s_panel_h = (LCD_V_RES * 85) / 100; + if (s_panel_h < 40 || s_panel_h > ui_screen_h()) + s_panel_h = (ui_screen_h() * 85) / 100; lv_obj_set_y(slide_panel, -s_panel_h); if (slide_btn_timer == NULL) diff --git a/firmware_p4/components/Applications/ui/components/header/header_ui.c b/firmware_p4/components/Applications/ui/components/header/header_ui.c index 57ab4543d..2e2b93ffc 100644 --- a/firmware_p4/components/Applications/ui/components/header/header_ui.c +++ b/firmware_p4/components/Applications/ui/components/header/header_ui.c @@ -37,6 +37,7 @@ #include "pin_def.h" #include "sys_time.h" #include "ui_feedback.h" +#include "ui_manager.h" #include "ui_theme.h" #include "vfs_config.h" #include "vfs_core.h" @@ -262,7 +263,7 @@ static void sd_cd_task(void *arg) { if (present) { if (sd_try_mount()) { - lv_async_call(sd_apply_mounted, (void *)(intptr_t)(boot || force)); + ui_async_call(sd_apply_mounted, (void *)(intptr_t)(boot || force)); } else if (vfs_sdcard_is_mounted()) { vfs_sdcard_deinit(); } @@ -271,7 +272,7 @@ static void sd_cd_task(void *arg) { if (vfs_sdcard_is_mounted()) { vfs_sdcard_deinit(); } - lv_async_call(sd_apply_removed, NULL); + ui_async_call(sd_apply_removed, NULL); } boot = false; diff --git a/firmware_p4/components/Applications/ui/components/keyboard/keyboard_ui.c b/firmware_p4/components/Applications/ui/components/keyboard/keyboard_ui.c index da8c9301e..f5adc4b73 100644 --- a/firmware_p4/components/Applications/ui/components/keyboard/keyboard_ui.c +++ b/firmware_p4/components/Applications/ui/components/keyboard/keyboard_ui.c @@ -199,7 +199,7 @@ void keyboard_close(void) { lv_group_set_editing(main_group, false); lv_group_remove_all_objs(main_group); } - lv_obj_del(kb_screen); + lv_obj_del_async(kb_screen); kb_screen = NULL; kb_obj = NULL; kb_ta = NULL; diff --git a/firmware_p4/components/Applications/ui/components/menu_component/menu_component_ui.c b/firmware_p4/components/Applications/ui/components/menu_component/menu_component_ui.c index 4aea79cad..ac1218a3c 100644 --- a/firmware_p4/components/Applications/ui/components/menu_component/menu_component_ui.c +++ b/firmware_p4/components/Applications/ui/components/menu_component/menu_component_ui.c @@ -21,6 +21,7 @@ #include "assets_manager.h" #include "ui_chrome.h" #include "ui_feedback.h" +#include "ui_metrics.h" #include "ui_theme.h" #define HEADER_BG current_theme.bg_secondary @@ -139,7 +140,7 @@ menu_component_create(lv_obj_t *parent, const char *title, const char *title_ico (void)title_icon_path; // the shared status header carries its own icons now m.screen = lv_obj_create(parent); - lv_obj_set_size(m.screen, LCD_H_RES, LCD_V_RES); + lv_obj_set_size(m.screen, lv_pct(100), lv_pct(100)); lv_obj_align(m.screen, LV_ALIGN_TOP_LEFT, 0, 0); lv_obj_remove_flag(m.screen, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_style_bg_color(m.screen, current_theme.screen_base, 0); @@ -153,7 +154,7 @@ menu_component_create(lv_obj_t *parent, const char *title, const char *title_ico // alone). Kept in title_bar (transparent) so callers that fade_in(title_bar) // still animate the whole area. m.title_bar = lv_obj_create(m.screen); - lv_obj_set_size(m.title_bar, LCD_H_RES, HEADER_H); + lv_obj_set_size(m.title_bar, lv_pct(100), HEADER_H); lv_obj_align(m.title_bar, LV_ALIGN_TOP_LEFT, 0, 0); lv_obj_remove_flag(m.title_bar, LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(m.title_bar, LV_OBJ_FLAG_CLICKABLE); @@ -163,12 +164,12 @@ menu_component_create(lv_obj_t *parent, const char *title, const char *title_ico lv_obj_set_style_radius(m.title_bar, 0, 0); m.title_label = ui_chrome_light_title(m.title_bar, title); - int items_h = LCD_V_RES - ITEMS_Y - FOOTER_H - 4; + int items_h = ui_screen_h() - ITEMS_Y - FOOTER_H - 4; if (items_h < ITEM_H) items_h = ITEM_H; m.items_cont = lv_obj_create(m.screen); - lv_obj_set_size(m.items_cont, LCD_H_RES - LEFT_MARGIN - RIGHT_GUTTER, items_h); + lv_obj_set_size(m.items_cont, ui_screen_w() - LEFT_MARGIN - RIGHT_GUTTER, items_h); lv_obj_align(m.items_cont, LV_ALIGN_TOP_LEFT, LEFT_MARGIN, ITEMS_Y); lv_obj_set_style_bg_opa(m.items_cont, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(m.items_cont, 0, 0); @@ -178,7 +179,7 @@ menu_component_create(lv_obj_t *parent, const char *title, const char *title_ico lv_obj_set_scrollbar_mode(m.items_cont, LV_SCROLLBAR_MODE_OFF); lv_obj_set_scroll_snap_y(m.items_cont, LV_SCROLL_SNAP_NONE); - int track_x = LCD_H_RES - OUTER_BORDER - 9; + int track_x = ui_screen_w() - OUTER_BORDER - 9; m.track_y_start = ITEMS_Y + 8; m.track_h = items_h - 16; if (m.track_h < 0) @@ -210,7 +211,7 @@ menu_component_create(lv_obj_t *parent, const char *title, const char *title_ico lv_obj_move_foreground(m.scroll_bar); m.footer = lv_obj_create(m.screen); - lv_obj_set_size(m.footer, LCD_H_RES, FOOTER_H); + lv_obj_set_size(m.footer, lv_pct(100), FOOTER_H); lv_obj_align(m.footer, LV_ALIGN_BOTTOM_LEFT, 0, 0); lv_obj_remove_flag(m.footer, LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(m.footer, LV_OBJ_FLAG_CLICKABLE); diff --git a/firmware_p4/components/Applications/ui/components/message_box/msgbox_ui.c b/firmware_p4/components/Applications/ui/components/message_box/msgbox_ui.c index 2249fdf98..9a58a33f6 100644 --- a/firmware_p4/components/Applications/ui/components/message_box/msgbox_ui.c +++ b/firmware_p4/components/Applications/ui/components/message_box/msgbox_ui.c @@ -19,9 +19,10 @@ #include "assets_manager.h" #include "buttons_gpio.h" +#include "ui_metrics.h" #include "ui_theme.h" -#define MSGBOX_H ((LCD_V_RES * 45) / 100) +#define MSGBOX_H ((ui_screen_h() * 45) / 100) #define ANIM_TIME 300 #define BORDER_COLOR current_theme.border_accent #define GRAD_TOP current_theme.border_interface @@ -103,7 +104,7 @@ static void do_close(bool confirm) { lv_anim_t a; lv_anim_init(&a); lv_anim_set_var(&a, closing); - lv_anim_set_values(&a, lv_obj_get_y(closing), LCD_V_RES); + lv_anim_set_values(&a, lv_obj_get_y(closing), ui_screen_h()); lv_anim_set_duration(&a, ANIM_TIME); lv_anim_set_path_cb(&a, lv_anim_path_ease_in_out); lv_anim_set_exec_cb(&a, slide_anim_cb); @@ -208,8 +209,8 @@ void msgbox_open( lv_obj_t *scr = lv_screen_active(); panel = lv_obj_create(scr); - lv_obj_set_size(panel, LCD_H_RES, MSGBOX_H); - lv_obj_set_pos(panel, 0, LCD_V_RES); + lv_obj_set_size(panel, ui_screen_w(), MSGBOX_H); + lv_obj_set_pos(panel, 0, ui_screen_h()); lv_obj_remove_flag(panel, LV_OBJ_FLAG_SCROLLABLE); lv_obj_add_event_cb(panel, panel_deleted_cb, LV_EVENT_DELETE, NULL); @@ -250,7 +251,7 @@ void msgbox_open( lv_obj_set_style_text_font(msg_lbl, &lv_font_montserrat_12, 0); lv_obj_set_style_text_align(msg_lbl, LV_TEXT_ALIGN_CENTER, 0); lv_label_set_long_mode(msg_lbl, LV_LABEL_LONG_WRAP); - lv_obj_set_width(msg_lbl, LCD_H_RES - 40); + lv_obj_set_width(msg_lbl, ui_screen_w() - 40); lv_obj_t *btn_row = lv_obj_create(content); lv_obj_set_size(btn_row, LV_SIZE_CONTENT, LV_SIZE_CONTENT); @@ -279,11 +280,11 @@ void msgbox_open( update_btn_selection(); - int target_y = LCD_V_RES - MSGBOX_H; + int target_y = ui_screen_h() - MSGBOX_H; lv_anim_t a; lv_anim_init(&a); lv_anim_set_var(&a, panel); - lv_anim_set_values(&a, LCD_V_RES, target_y); + lv_anim_set_values(&a, ui_screen_h(), target_y); lv_anim_set_duration(&a, ANIM_TIME); lv_anim_set_path_cb(&a, lv_anim_path_ease_in_out); lv_anim_set_exec_cb(&a, slide_anim_cb); @@ -328,7 +329,7 @@ void msgbox_open_sd_info(const char *name, const char *size, const char *free, c lv_obj_t *scr = lv_screen_active(); panel = lv_obj_create(scr); lv_obj_set_size(panel, SD_MODAL_W, SD_MODAL_H); - lv_obj_set_pos(panel, (LCD_H_RES - SD_MODAL_W) / 2, LCD_V_RES); + lv_obj_set_pos(panel, (ui_screen_w() - SD_MODAL_W) / 2, ui_screen_h()); lv_obj_remove_flag(panel, LV_OBJ_FLAG_SCROLLABLE); lv_obj_add_event_cb(panel, panel_deleted_cb, LV_EVENT_DELETE, NULL); @@ -392,11 +393,11 @@ void msgbox_open_sd_info(const char *name, const char *size, const char *free, c btn_sel = 0; update_btn_selection(); - int target_y = (LCD_V_RES - SD_MODAL_H) / 2; + int target_y = (ui_screen_h() - SD_MODAL_H) / 2; lv_anim_t a; lv_anim_init(&a); lv_anim_set_var(&a, panel); - lv_anim_set_values(&a, LCD_V_RES, target_y); + lv_anim_set_values(&a, ui_screen_h(), target_y); lv_anim_set_duration(&a, ANIM_TIME); lv_anim_set_path_cb(&a, lv_anim_path_ease_in_out); lv_anim_set_exec_cb(&a, slide_anim_cb); @@ -421,7 +422,7 @@ void msgbox_open_info(const char *icon_path, lv_obj_t *scr = lv_screen_active(); panel = lv_obj_create(scr); lv_obj_set_size(panel, SD_MODAL_W, SD_MODAL_H); - lv_obj_set_pos(panel, (LCD_H_RES - SD_MODAL_W) / 2, LCD_V_RES); + lv_obj_set_pos(panel, (ui_screen_w() - SD_MODAL_W) / 2, ui_screen_h()); lv_obj_remove_flag(panel, LV_OBJ_FLAG_SCROLLABLE); lv_obj_add_event_cb(panel, panel_deleted_cb, LV_EVENT_DELETE, NULL); @@ -483,11 +484,11 @@ void msgbox_open_info(const char *icon_path, btn_sel = 0; update_btn_selection(); - int target_y = (LCD_V_RES - SD_MODAL_H) / 2; + int target_y = (ui_screen_h() - SD_MODAL_H) / 2; lv_anim_t a; lv_anim_init(&a); lv_anim_set_var(&a, panel); - lv_anim_set_values(&a, LCD_V_RES, target_y); + lv_anim_set_values(&a, ui_screen_h(), target_y); lv_anim_set_duration(&a, ANIM_TIME); lv_anim_set_path_cb(&a, lv_anim_path_ease_in_out); lv_anim_set_exec_cb(&a, slide_anim_cb); diff --git a/firmware_p4/components/Applications/ui/components/tutorial/screen_tips.c b/firmware_p4/components/Applications/ui/components/tutorial/screen_tips.c index 82dcab0d0..f06c0c328 100644 --- a/firmware_p4/components/Applications/ui/components/tutorial/screen_tips.c +++ b/firmware_p4/components/Applications/ui/components/tutorial/screen_tips.c @@ -30,8 +30,8 @@ #define TIP_NVS_KEY_SKIP "skip" #define TIP_WORDS ((SCREEN_COUNT + 31) / 32) -#define TIP_SCRIM_OPA 232 // how dark the screen behind gets -#define TIP_ARM_MS 500 // ignore input while the intro plays +#define TIP_SCRIM_OPA 232 +#define TIP_ARM_MS 500 #define TIP_TEXT_W 182 #define TIP_ART "/assets/img/image.bin" @@ -108,7 +108,8 @@ static const tip_entry_t TIPS[] = { {SCREEN_NFC_MENU, "NFC", - "The whole 13.56 MHz NFC world: read tags, write, emulate and store your cards."}, + "The whole 13.56 MHz NFC world: read tags, write, emulate and store your cards. Heads-up: " + "mocked for now due to a hardware issue."}, {SCREEN_CARD_EMU, "CARD EMULATION", "Build a card from scratch or pick a saved one, and the High Boy broadcasts it as the real " @@ -144,7 +145,8 @@ static const tip_entry_t TIPS[] = { {SCREEN_SUBGHZ_MENU, "SUB-GHZ", - "The Sub-GHz radio: capture, analyze and replay remote signals on 433, 868 and 315 MHz."}, + "The Sub-GHz radio: capture, analyze and replay remote signals on 433, 868 and 315 MHz. " + "Heads-up: mocked for now due to a hardware issue."}, {SCREEN_SUBGHZ_BRUTE, "CODE BRUTE FORCE", "Brute Force fires thousands of codes across a range until one pops the gate - no key " @@ -155,8 +157,8 @@ static const tip_entry_t TIPS[] = { "rate and key."}, {SCREEN_RFID_MENU, "RFID 125 kHz", - "RFID reads LF 125 kHz tags: read, emulate, add one by hand, even clone a whole access " - "badge."}, + "RFID reads LF 125 kHz tags: read, emulate, add one by hand, even clone a whole access badge. " + "Heads-up: mocked for now due to a hardware issue."}, {SCREEN_SUBGHZ_CONFIG, "RADIO CONFIG", "For the tough ones, Radio Config tunes modulation, bandwidth, data rate and preset before " @@ -184,7 +186,7 @@ static const tip_entry_t TIPS[] = { {SCREEN_LORA_CHAT, "LORA MESH", "Step into the LoRa mesh: pick MeshCore or Meshtastic, see the nodes on the map and chat " - "off-grid."}, + "off-grid. Heads-up: mocked for now due to a hardware issue."}, {SCREEN_LORA_SECURE_DM, "ENCRYPTED DM", "Direct messages with per-contact X25519 keys. Compare fingerprints to be sure who is on the " @@ -288,6 +290,7 @@ static uint32_t s_seen[TIP_WORDS]; static lv_obj_t *s_scrim = NULL; static lv_obj_t *s_prev_focus = NULL; static uint32_t s_open_tick = 0; +static bool s_armed = false; static const tip_entry_t *tip_for(screen_id_t screen) { for (int i = 0; i < TIP_COUNT; i++) { @@ -368,7 +371,7 @@ static void dismiss(void) { static void scrim_key_cb(lv_event_t *e) { if (lv_event_get_code(e) != LV_EVENT_KEY) return; - if (lv_tick_get() - s_open_tick < TIP_ARM_MS) + if (!s_armed || lv_tick_get() - s_open_tick < TIP_ARM_MS) return; uint32_t key = lv_event_get_key(e); if (key == LV_KEY_ENTER || key == LV_KEY_ESC || key == LV_KEY_RIGHT) @@ -378,9 +381,13 @@ static void scrim_key_cb(lv_event_t *e) { void screen_tips_handle_input(const input_event_t *ev) { if (!s_active || ev == NULL) return; + if (ev->action == INPUT_ACTION_RELEASE) { + s_armed = true; + return; + } if (ev->action != INPUT_ACTION_PRESS) return; - if (lv_tick_get() - s_open_tick < TIP_ARM_MS) + if (!s_armed || lv_tick_get() - s_open_tick < TIP_ARM_MS) return; if (ev->button == INPUT_BTN_OK || ev->button == INPUT_BTN_BACK || ev->button == INPUT_BTN_RIGHT) dismiss(); @@ -396,7 +403,6 @@ static void tip_ty_cb(void *o, int32_t v) { lv_obj_set_style_translate_y((lv_obj_t *)o, v, 0); } -// Fade an element in from transparent, after `delay` ms. static void tip_fade(lv_obj_t *o, uint32_t delay) { lv_obj_set_style_opa(o, LV_OPA_TRANSP, 0); lv_anim_t a; @@ -410,7 +416,6 @@ static void tip_fade(lv_obj_t *o, uint32_t delay) { lv_anim_start(&a); } -// Gentle continuous float for the mascot. static void tip_bob(lv_obj_t *o) { lv_anim_t a; lv_anim_init(&a); @@ -431,14 +436,13 @@ static void build_overlay(const tip_entry_t *entry) { lv_obj_remove_flag(scrim, LV_OBJ_FLAG_SCROLLABLE); lv_obj_add_flag(scrim, LV_OBJ_FLAG_CLICKABLE); lv_obj_set_style_bg_color(scrim, lv_color_black(), 0); - lv_obj_set_style_bg_opa(scrim, LV_OPA_TRANSP, 0); // fades in — the screen darkens + lv_obj_set_style_bg_opa(scrim, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(scrim, 0, 0); lv_obj_set_style_radius(scrim, 0, 0); lv_obj_set_style_pad_all(scrim, 0, 0); lv_obj_add_event_cb(scrim, scrim_key_cb, LV_EVENT_KEY, NULL); s_scrim = scrim; - // The screen behind darkens first. lv_anim_t a; lv_anim_init(&a); lv_anim_set_var(&a, scrim); @@ -448,7 +452,6 @@ static void build_overlay(const tip_entry_t *entry) { lv_anim_set_path_cb(&a, lv_anim_path_ease_out); lv_anim_start(&a); - // No boxed window: octobit on top, the explanation below it, centered on the scrim. lv_obj_t *col = lv_obj_create(scrim); lv_obj_remove_style_all(col); lv_obj_remove_flag(col, LV_OBJ_FLAG_SCROLLABLE); @@ -463,18 +466,36 @@ static void build_overlay(const tip_entry_t *entry) { if (dsc != NULL) { lv_obj_t *img = lv_image_create(col); lv_image_set_src(img, dsc); - lv_image_set_scale(img, 168); // ~66% so it leaves room for the text + lv_image_set_scale(img, 168); tip_fade(img, 130); tip_bob(img); } - lv_obj_t *title = lv_label_create(col); + lv_obj_t *card = lv_obj_create(col); + lv_obj_remove_style_all(card); + lv_obj_remove_flag(card, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_width(card, LV_SIZE_CONTENT); + lv_obj_set_height(card, LV_SIZE_CONTENT); + lv_obj_set_flex_flow(card, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(card, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_style_pad_hor(card, 18, 0); + lv_obj_set_style_pad_ver(card, 14, 0); + lv_obj_set_style_pad_row(card, 8, 0); + lv_obj_set_style_bg_opa(card, LV_OPA_COVER, 0); + lv_obj_set_style_bg_color(card, current_theme.bg_primary, 0); + lv_obj_set_style_radius(card, 16, 0); + lv_obj_set_style_border_width(card, 1, 0); + lv_obj_set_style_border_color(card, current_theme.border_accent, 0); + lv_obj_set_style_border_opa(card, 110, 0); + tip_fade(card, 200); + + lv_obj_t *title = lv_label_create(card); lv_label_set_text(title, entry->title); lv_obj_set_style_text_font(title, &lv_font_montserrat_16, 0); lv_obj_set_style_text_color(title, current_theme.border_accent, 0); tip_fade(title, 300); - lv_obj_t *tip = lv_label_create(col); + lv_obj_t *tip = lv_label_create(card); lv_label_set_long_mode(tip, LV_LABEL_LONG_WRAP); lv_obj_set_width(tip, TIP_TEXT_W); lv_label_set_text(tip, entry->tip); @@ -483,7 +504,7 @@ static void build_overlay(const tip_entry_t *entry) { lv_obj_set_style_text_align(tip, LV_TEXT_ALIGN_CENTER, 0); tip_fade(tip, 380); - lv_obj_t *hint = lv_label_create(col); + lv_obj_t *hint = lv_label_create(card); lv_label_set_text(hint, LV_SYMBOL_OK " OK"); lv_obj_set_style_text_font(hint, &lv_font_montserrat_12, 0); lv_obj_set_style_text_color(hint, current_theme.border_accent, 0); @@ -491,6 +512,7 @@ static void build_overlay(const tip_entry_t *entry) { tip_fade(hint, 460); s_open_tick = lv_tick_get(); + s_armed = false; s_active = true; hijack_input(); } diff --git a/firmware_p4/components/Applications/ui/components/tutorial/tutorial_ui.c b/firmware_p4/components/Applications/ui/components/tutorial/tutorial_ui.c index 7b4393f46..1ca96051e 100644 --- a/firmware_p4/components/Applications/ui/components/tutorial/tutorial_ui.c +++ b/firmware_p4/components/Applications/ui/components/tutorial/tutorial_ui.c @@ -28,10 +28,13 @@ #include "host_link_sec.h" #include "storage_assets.h" #include "storage_init.h" +#include + #include "sys_time.h" #include "tos_config.h" #include "tos_storage_paths.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" static const char *TAG = "TUTORIAL"; @@ -46,10 +49,10 @@ static const char *const WIZ_THEME_NAMES[] = {"default", "cyber_blue"}; #define WIZ_MARGIN 16 #define WIZ_STEP_Y 6 #define WIZ_PROG_Y 24 -#define WIZ_PROG_W (LCD_H_RES - 2 * WIZ_MARGIN) +#define WIZ_PROG_W (ui_screen_w() - 2 * WIZ_MARGIN) #define WIZ_PROG_H 4 #define WIZ_CONTENT_Y 36 -#define WIZ_CONTENT_H 252 +#define WIZ_CONTENT_H (ui_screen_h() - WIZ_CONTENT_Y - 32) #define WIZ_FOOT_Y -8 #define WIZ_GAP 9 #define WIZ_TEXT_W 206 @@ -58,19 +61,17 @@ static const char *const WIZ_THEME_NAMES[] = {"default", "cyber_blue"}; #define WIZ_SUB_OPA 180 #define WIZ_SWATCH 24 -// Animation timing (slow + cinematic; affordable now that PSRAM keeps frames resident). #define FADE_OUT_MS 180 #define FADE_IN_MS 320 #define PROG_MS 460 -#define STAGGER_MS 110 // gap between elements fading in one after another -#define MASCOT_FADE 420 // octobit fade-in -#define MASCOT_ENTER 640 // when the text starts fading in (after octobit is established) -#define BOB_MS 1800 // octobit float period (slow, gentle) +#define STAGGER_MS 110 +#define MASCOT_FADE 420 +#define MASCOT_ENTER 640 +#define BOB_MS 1800 #define BOB_PX 6 -#define WOBBLE_MS 560 // arrow idle horizontal swing period +#define WOBBLE_MS 560 #define WOBBLE_PX 5 -// Vertical chooser geometry. #define CH_ROW_H 30 #define CH_ARROW_W 22 #define CH_LIST_W 190 @@ -79,7 +80,7 @@ static const char *const WIZ_THEME_NAMES[] = {"default", "cyber_blue"}; extern lv_group_t *main_group; static bool s_active = false; -static bool s_busy = false; // mid page-transition: swallow input +static bool s_busy = false; static int s_page = 0; static int s_pending = 0; static uint32_t s_open_tick = 0; @@ -89,9 +90,8 @@ static lv_obj_t *s_content = NULL; static lv_obj_t *s_prog_fill = NULL; static lv_obj_t *s_step = NULL; static lv_obj_t *s_foot = NULL; -static lv_obj_t *s_mascot = NULL; // octobit on the current page (NULL if none) +static lv_obj_t *s_mascot = NULL; -// Active chooser (rebuilt per page; count==0 means the page is not a chooser). static lv_obj_t *s_ch_rows[CH_MAX] = {NULL}; static lv_obj_t *s_ch_arrow = NULL; static int s_ch_count = 0; @@ -101,8 +101,6 @@ static int *s_ch_selp = NULL; static int s_lang_sel = 0; static int s_theme_sel = 0; -// ---- small animation helpers ------------------------------------------------ - static void anim_opa_cb(void *obj, int32_t v) { lv_obj_set_style_opa((lv_obj_t *)obj, (lv_opa_t)v, 0); } @@ -134,8 +132,6 @@ static void fade(lv_obj_t *o, lv_anim_start(&a); } -// ---- reusable page widgets -------------------------------------------------- - static lv_obj_t *wiz_heading(lv_obj_t *p, const char *text) { lv_obj_t *l = lv_label_create(p); lv_label_set_text(l, text); @@ -199,7 +195,6 @@ static void wiz_chip(lv_obj_t *p, const char *text, lv_color_t accent) { lv_obj_set_style_pad_ver(k, 4, 0); } -// A small octobit that gently bobs, added to the top of a guide page's content. static void wiz_mascot(lv_obj_t *p, int zoom) { lv_image_dsc_t *dsc = assets_get(WIZ_ART_ASSET); if (dsc == NULL) @@ -208,8 +203,7 @@ static void wiz_mascot(lv_obj_t *p, int zoom) { lv_image_set_src(img, dsc); if (zoom != 256) lv_image_set_scale(img, zoom); - s_mascot = img; // build_page_now choreographs its entrance (fade + glide in) - // Gentle continuous float (translate-y; independent of the entrance translate-x). + s_mascot = img; lv_anim_t a; lv_anim_init(&a); lv_anim_set_var(&a, img); @@ -222,8 +216,6 @@ static void wiz_mascot(lv_obj_t *p, int zoom) { lv_anim_start(&a); } -// ---- animated vertical chooser (the "setinha" selector) --------------------- - static void ch_restyle(void) { for (int i = 0; i < s_ch_count; i++) { lv_obj_t *row = s_ch_rows[i]; @@ -240,8 +232,6 @@ static void ch_restyle(void) { } } -// Vertical alignment is INSTANT — the arrow snaps to the selected row; the only -// motion it keeps is the idle horizontal swing (started in build_chooser). static void ch_arrow_to(int idx) { if (!s_ch_arrow) return; @@ -297,7 +287,6 @@ build_chooser(lv_obj_t *c, const char **items, const uint32_t *colors, int count ch_restyle(); ch_arrow_to(s_ch_sel); - // Idle swing so it reads as "point here, use UP/DOWN". lv_anim_t a; lv_anim_init(&a); lv_anim_set_var(&a, s_ch_arrow); @@ -321,8 +310,6 @@ static void chooser_move(int delta) { ch_arrow_to(n); } -// ---- pages ------------------------------------------------------------------ - static void page_language(lv_obj_t *c) { wiz_heading(c, "Language"); wiz_sub(c, "Pick your language. UP / DOWN to choose."); @@ -330,27 +317,126 @@ static void page_language(lv_obj_t *c) { build_chooser(c, langs, NULL, 4, &s_lang_sel); } +static struct tm s_dt; +static int s_dt_field = 0; +static bool s_dt_active = false; +static lv_obj_t *s_dt_lbl[5] = {0}; + +static int dt_days_in_month(int year, int mon0) { + static const int base[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + if (mon0 == 1) { + bool leap = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); + return leap ? 29 : 28; + } + return base[mon0]; +} + +static void dt_refresh(void) { + char b[8]; + snprintf(b, sizeof(b), "%04d", s_dt.tm_year + 1900); + if (s_dt_lbl[0]) + lv_label_set_text(s_dt_lbl[0], b); + snprintf(b, sizeof(b), "%02d", s_dt.tm_mon + 1); + if (s_dt_lbl[1]) + lv_label_set_text(s_dt_lbl[1], b); + snprintf(b, sizeof(b), "%02d", s_dt.tm_mday); + if (s_dt_lbl[2]) + lv_label_set_text(s_dt_lbl[2], b); + snprintf(b, sizeof(b), "%02d", s_dt.tm_hour); + if (s_dt_lbl[3]) + lv_label_set_text(s_dt_lbl[3], b); + snprintf(b, sizeof(b), "%02d", s_dt.tm_min); + if (s_dt_lbl[4]) + lv_label_set_text(s_dt_lbl[4], b); + for (int i = 0; i < 5; i++) + if (s_dt_lbl[i]) + lv_obj_set_style_text_color( + s_dt_lbl[i], i == s_dt_field ? current_theme.border_accent : current_theme.text_main, 0); +} + +static void dt_adjust(int d) { + switch (s_dt_field) { + case 0: { + int y = s_dt.tm_year + 1900 + d; + if (y < 2020) + y = 2099; + if (y > 2099) + y = 2020; + s_dt.tm_year = y - 1900; + break; + } + case 1: + s_dt.tm_mon = (s_dt.tm_mon + d + 12) % 12; + break; + case 2: { + int dim = dt_days_in_month(s_dt.tm_year + 1900, s_dt.tm_mon); + s_dt.tm_mday += d; + if (s_dt.tm_mday < 1) + s_dt.tm_mday = dim; + if (s_dt.tm_mday > dim) + s_dt.tm_mday = 1; + break; + } + case 3: + s_dt.tm_hour = (s_dt.tm_hour + d + 24) % 24; + break; + case 4: + s_dt.tm_min = (s_dt.tm_min + d + 60) % 60; + break; + default: + break; + } + int dim = dt_days_in_month(s_dt.tm_year + 1900, s_dt.tm_mon); + if (s_dt.tm_mday > dim) + s_dt.tm_mday = dim; + dt_refresh(); +} + +static void dt_save(void) { + struct tm t = s_dt; + t.tm_sec = 0; + t.tm_isdst = 0; + time_t epoch = mktime(&t); + if (epoch != (time_t)-1) + sys_time_set(epoch, SYS_TIME_SOURCE_MANUAL); +} + static void page_datetime(lv_obj_t *c) { wiz_heading(c, "Date & Time"); - lv_obj_t *clk = lv_label_create(c); - char clkbuf[16]; - if (!sys_time_format(clkbuf, sizeof(clkbuf), "%H:%M")) - snprintf(clkbuf, sizeof(clkbuf), "--:--"); - lv_label_set_text(clk, clkbuf); - lv_obj_set_style_text_font(clk, &lv_font_montserrat_16, 0); - lv_obj_set_style_text_color(clk, current_theme.border_accent, 0); - wiz_sub(c, "High Boy timestamps every capture and log. Sync the clock from the companion app."); - lv_obj_t *pill = lv_label_create(c); - lv_label_set_text(pill, LV_SYMBOL_REFRESH " Sync from companion"); - lv_obj_set_style_text_font(pill, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(pill, current_theme.border_accent, 0); - lv_obj_set_style_bg_opa(pill, LV_OPA_COVER, 0); - lv_obj_set_style_bg_color(pill, current_theme.bg_secondary, 0); - lv_obj_set_style_border_width(pill, 1, 0); - lv_obj_set_style_border_color(pill, current_theme.border_accent, 0); - lv_obj_set_style_radius(pill, 8, 0); - lv_obj_set_style_pad_hor(pill, 9, 0); - lv_obj_set_style_pad_ver(pill, 4, 0); + wiz_sub(c, "High Boy timestamps every capture and log. Set the clock now."); + + time_t now = sys_time_now(); + localtime_r(&now, &s_dt); + s_dt_field = 0; + + lv_obj_t *row = lv_obj_create(c); + lv_obj_remove_style_all(row); + lv_obj_remove_flag(row, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_size(row, LV_SIZE_CONTENT, LV_SIZE_CONTENT); + lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(row, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_style_pad_column(row, 3, 0); + + static const char *const SEP[5] = {NULL, "-", "-", " ", ":"}; + for (int i = 0; i < 5; i++) { + if (SEP[i]) { + lv_obj_t *sep = lv_label_create(row); + lv_label_set_text(sep, SEP[i]); + lv_obj_set_style_text_font(sep, &lv_font_montserrat_16, 0); + lv_obj_set_style_text_color(sep, current_theme.text_secondary, 0); + } + s_dt_lbl[i] = lv_label_create(row); + lv_obj_set_style_text_font(s_dt_lbl[i], &lv_font_montserrat_16, 0); + } + dt_refresh(); + s_dt_active = true; + + lv_obj_t *hint = lv_label_create(c); + lv_label_set_text(hint, + LV_SYMBOL_UP LV_SYMBOL_DOWN " edit " LV_SYMBOL_LEFT LV_SYMBOL_RIGHT + " field " LV_SYMBOL_OK " set"); + lv_obj_set_style_text_font(hint, &lv_font_montserrat_12, 0); + lv_obj_set_style_text_color(hint, current_theme.text_secondary, 0); } static void wiz_status_row(lv_obj_t *list, const char *name, const char *value) { @@ -387,7 +473,7 @@ static void page_storage(lv_obj_t *c) { static void page_companion(lv_obj_t *c) { wiz_heading(c, "Companion App"); - wiz_sub(c, "Pair the phone app for time sync, file transfer and remote control."); + wiz_sub(c, "Pair the phone app for file transfer and remote control."); lv_obj_t *box = lv_label_create(c); char psk[HOST_LINK_PSK_HEX_SIZE]; if (host_link_sec_get_psk_hex(psk, sizeof(psk)) == ESP_OK) { @@ -492,6 +578,9 @@ static void page_ready(lv_obj_t *c) { wiz_mascot(c, 256); wiz_heading(c, "You're all set!"); wiz_sub(c, "Tips will nudge you as you go. Let's dive in."); + wiz_sub(c, + "The High Boy is an early prototype: a few tools are still simulated while the " + "hardware matures. You're getting a first look at something that's growing fast."); } typedef void (*wiz_build_fn)(lv_obj_t *); @@ -499,12 +588,12 @@ typedef void (*wiz_build_fn)(lv_obj_t *); typedef struct { wiz_build_fn build; const char *foot; - bool mascot; // keep the persistent guide visible from here on + bool mascot; } wiz_page_t; static const wiz_page_t PAGES[] = { {page_language, "UP/DOWN Pick OK Next", false}, - {page_datetime, "OK Next BACK Back", false}, + {page_datetime, "OK Set BACK Back", false}, {page_storage, "OK Next BACK Back", false}, {page_companion, "OK Next BACK Back", false}, {page_terms, "OK Accept BACK Back", false}, @@ -526,17 +615,19 @@ static void arm_done(lv_anim_t *a) { } static void build_page_now(int idx) { - s_busy = true; // cleared by arm_done() when the fade-in finishes + s_busy = true; s_page = idx; s_ch_count = 0; s_ch_arrow = NULL; - s_mascot = NULL; // set by wiz_mascot() during the build if this page has one + s_mascot = NULL; + s_dt_active = false; + for (int i = 0; i < 5; i++) + s_dt_lbl[i] = NULL; lv_obj_clean(s_content); PAGES[idx].build(s_content); lv_label_set_text(s_foot, PAGES[idx].foot); lv_label_set_text_fmt(s_step, "%d / %d", idx + 1, PAGE_COUNT); - // Progress bar eases to the new fraction. lv_anim_t a; lv_anim_init(&a); lv_anim_set_var(&a, s_prog_fill); @@ -546,13 +637,10 @@ static void build_page_now(int idx) { lv_anim_set_path_cb(&a, lv_anim_path_ease_out); lv_anim_start(&a); - // Compose the page in on the dark canvas: octobit fades in and glides from the - // side first, THEN the text fades in below it, element by element. lv_obj_set_style_opa(s_content, LV_OPA_COVER, 0); uint32_t base = 0; if (s_mascot) { - // Octobit simply fades in and floats in place (no slide); the text waits for it. fade(s_mascot, LV_OPA_TRANSP, LV_OPA_COVER, MASCOT_FADE, 0, lv_anim_path_ease_out, NULL); base = MASCOT_ENTER; } @@ -573,7 +661,6 @@ static void build_page_now(int idx) { step++; } - // Arm input once the whole sequence has settled (a no-op timing anim). uint32_t settle = base + (step > 0 ? (uint32_t)(step - 1) * STAGGER_MS : 0) + FADE_IN_MS; fade(s_content, LV_OPA_COVER, LV_OPA_COVER, settle, 0, lv_anim_path_linear, arm_done); } @@ -641,7 +728,43 @@ static void key_cb(lv_event_t *e) { return; uint32_t key = lv_event_get_key(e); - // On a chooser page UP/DOWN move the selection (arrow slides to it). + if (s_dt_active) { + if (key == LV_KEY_UP) { + dt_adjust(+1); + return; + } + if (key == LV_KEY_DOWN) { + dt_adjust(-1); + return; + } + if (key == LV_KEY_RIGHT) { + s_dt_field = (s_dt_field + 1) % 5; + dt_refresh(); + return; + } + if (key == LV_KEY_LEFT) { + s_dt_field = (s_dt_field + 4) % 5; + dt_refresh(); + return; + } + if (key == LV_KEY_ENTER) { + dt_save(); + s_dt_active = false; + if (s_page + 1 < PAGE_COUNT) + go_page(s_page + 1); + else + finish(); + return; + } + if (key == LV_KEY_ESC) { + s_dt_active = false; + if (s_page > 0) + go_page(s_page - 1); + return; + } + return; + } + if (s_ch_count > 0) { if (key == LV_KEY_UP) { chooser_move(-1); @@ -704,7 +827,7 @@ void tutorial_start(void) { s_content = lv_obj_create(root); lv_obj_remove_style_all(s_content); lv_obj_remove_flag(s_content, LV_OBJ_FLAG_SCROLLABLE); - lv_obj_set_size(s_content, LCD_H_RES, WIZ_CONTENT_H); + lv_obj_set_size(s_content, ui_screen_w(), WIZ_CONTENT_H); lv_obj_align(s_content, LV_ALIGN_TOP_MID, 0, WIZ_CONTENT_Y); lv_obj_set_flex_flow(s_content, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_align( diff --git a/firmware_p4/components/Applications/ui/include/ui_liveness.h b/firmware_p4/components/Applications/ui/include/ui_liveness.h index 8515bb18d..6c45614f6 100644 --- a/firmware_p4/components/Applications/ui/include/ui_liveness.h +++ b/firmware_p4/components/Applications/ui/include/ui_liveness.h @@ -37,6 +37,17 @@ extern "C" { */ uint32_t ui_render_beat(void); +/** + * @brief Advance the render heartbeat from a full-screen takeover renderer. + * + * A full-screen app that takes the panel and drives it directly (e.g. the Game + * Boy emulator, which holds the LVGL lock and blits itself) prevents the internal + * lv_timer from bumping the beat. Such an app must call this once per rendered + * frame so the system monitor sees the display is still alive. If the app truly + * hangs, the beat stalls and the monitor performs its normal controlled restart. + */ +void ui_render_beat_kick(void); + #ifdef __cplusplus } #endif diff --git a/firmware_p4/components/Applications/ui/include/ui_manager.h b/firmware_p4/components/Applications/ui/include/ui_manager.h index 632decc78..c45f88784 100644 --- a/firmware_p4/components/Applications/ui/include/ui_manager.h +++ b/firmware_p4/components/Applications/ui/include/ui_manager.h @@ -86,11 +86,18 @@ typedef enum { SCREEN_IR_BURST, SCREEN_OCTOBIT_STATUS, SCREEN_DEV_MENU, + SCREEN_GAMES_MENU, + SCREEN_GAME_SNAKE, + SCREEN_GAME_BREAKOUT, + SCREEN_GAME_GB, + SCREEN_GAME_DOOM, SCREEN_GPIO, SCREEN_HAPTIC, SCREEN_SPEAKER, SCREEN_MIC_REC, SCREEN_WAV_PLAYER, + SCREEN_MP4_PLAYER, + SCREEN_MP3_PLAYER, SCREEN_PLAYER, SCREEN_SUBGHZ_MENU, SCREEN_SUBGHZ_READ, @@ -164,6 +171,8 @@ typedef enum { SCREEN_SD_HEALTH, SCREEN_USB_MOUSE, SCREEN_TIME, + SCREEN_IMAGE_VIEWER, + SCREEN_USB_STORAGE, SCREEN_COUNT } screen_id_t; @@ -186,6 +195,17 @@ bool ui_acquire(void); /** @brief Release the UI mutex. */ void ui_release(void); +/** + * @brief Schedule an lv_async_call safely from any thread. + * + * lv_async_call() creates an lv_timer under the hood, mutating LVGL's global + * timer list. Called from a non-LVGL thread (radio/bridge worker tasks) it races + * lv_timer_handler on the render thread. This wrapper takes the (recursive) UI + * mutex first, so it is safe to call from any context; it silently drops the + * call only if the lock cannot be taken within the UI timeout. + */ +void ui_async_call(lv_async_cb_t cb, void *user_data); + /** @brief Switch to a new screen by identifier. */ void ui_switch_screen(screen_id_t new_screen); @@ -210,6 +230,19 @@ void ui_screen_load_owned(lv_obj_t **slot, lv_obj_t *scr); /** @brief Returns the currently active screen id. */ screen_id_t ui_current_screen(void); +/** + * @brief Rebuild the active screen so it re-lays-out at the current logical + * resolution. Call after a rotation change to reflow it immediately. + */ +void ui_relayout_current_screen(void); + +/** + * @brief Is the given LOGICAL direction button currently held? Accounts for the + * landscape d-pad rotation. Use in components that poll button levels + * directly (dropdown, modals) so they navigate like the rotated screens. + */ +bool ui_nav_pressed(input_button_t logical); + /** * @brief Whether a screen shows the global chrome (status bar + dropdown). * diff --git a/firmware_p4/components/Applications/ui/include/ui_metrics.h b/firmware_p4/components/Applications/ui/include/ui_metrics.h new file mode 100644 index 000000000..1c83784a7 --- /dev/null +++ b/firmware_p4/components/Applications/ui/include/ui_metrics.h @@ -0,0 +1,41 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#ifndef UI_METRICS_H +#define UI_METRICS_H + +#include "lvgl.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Live logical screen size. Unlike the LCD_H_RES / LCD_V_RES panel constants +// (fixed 240 x 320), these follow lv_display_set_rotation: in landscape (270) +// they return 320 x 240. Use them for layout that must survive rotation; the +// panel constants stay for DMA buffer sizing only. +static inline int32_t ui_screen_w(void) { + return lv_display_get_horizontal_resolution(NULL); +} + +static inline int32_t ui_screen_h(void) { + return lv_display_get_vertical_resolution(NULL); +} + +#ifdef __cplusplus +} +#endif + +#endif // UI_METRICS_H diff --git a/firmware_p4/components/Applications/ui/include/ui_semantic.h b/firmware_p4/components/Applications/ui/include/ui_semantic.h new file mode 100644 index 000000000..43fc2eff5 --- /dev/null +++ b/firmware_p4/components/Applications/ui/include/ui_semantic.h @@ -0,0 +1,56 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#ifndef UI_SEMANTIC_H +#define UI_SEMANTIC_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @file ui_semantic.h + * @brief Semantic UI colors — meaning-bound, not theme-bound. + * + * These express a fixed meaning ("this is good", "this is dangerous") that must + * read the same in every theme, so they are deliberately hardcoded rather than + * pulled from current_theme. Use them with lv_color_hex(UI_COL_*). + * + * Rule of thumb: + * - Structural color (background, border, primary/secondary text): use current_theme.*. + * - Meaning color (success/danger/warning): use one of these. + * - Screen-specific art (game sprites, data-series palette): keep it local; it is neither. + */ + +/** @brief Good / connected / on / signal-present (green). */ +#define UI_COL_SUCCESS 0x00E676 + +/** @brief Bad / threat / stop / failure (red). */ +#define UI_COL_DANGER 0xFF5252 + +/** @brief Caution / pending / degraded (amber). */ +#define UI_COL_WARNING 0xFFB300 + +/** @brief Max-contrast text/fill, absolute by intent. */ +#define UI_COL_WHITE 0xFFFFFF + +/** @brief Absolute black, absolute by intent. */ +#define UI_COL_BLACK 0x000000 + +#ifdef __cplusplus +} +#endif + +#endif // UI_SEMANTIC_H diff --git a/firmware_p4/components/Applications/ui/include/ui_theme.h b/firmware_p4/components/Applications/ui/include/ui_theme.h index cd6b499ea..50909ca38 100644 --- a/firmware_p4/components/Applications/ui/include/ui_theme.h +++ b/firmware_p4/components/Applications/ui/include/ui_theme.h @@ -34,6 +34,7 @@ typedef struct { lv_color_t border_interface; lv_color_t border_inactive; lv_color_t text_main; + lv_color_t text_secondary; lv_color_t screen_base; lv_color_t protocol_nfc; diff --git a/firmware_p4/components/Applications/ui/screens/audio/id3_meta.c b/firmware_p4/components/Applications/ui/screens/audio/id3_meta.c new file mode 100644 index 000000000..e10b8d9d1 --- /dev/null +++ b/firmware_p4/components/Applications/ui/screens/audio/id3_meta.c @@ -0,0 +1,249 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#include "id3_meta.h" + +#include +#include +#include + +#define ID3_MAX_TAG (2u * 1024u * 1024u) +#define ID3_MAX_COVER (4u * 1024u * 1024u) + +static uint32_t syncsafe32(const uint8_t *p) { + return ((uint32_t)(p[0] & 0x7f) << 21) | ((uint32_t)(p[1] & 0x7f) << 14) | + ((uint32_t)(p[2] & 0x7f) << 7) | (uint32_t)(p[3] & 0x7f); +} +static uint32_t be32(const uint8_t *p) { + return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3]; +} +static uint32_t be24(const uint8_t *p) { + return ((uint32_t)p[0] << 16) | ((uint32_t)p[1] << 8) | (uint32_t)p[2]; +} + +static void copy_text(char *dst, size_t dstcap, const uint8_t *data, uint32_t len) { + if (dstcap == 0 || len < 1) + return; + uint8_t enc = data[0]; + const uint8_t *s = data + 1; + uint32_t n = len - 1; + size_t o = 0; + if (enc == 1 || enc == 2) { + for (uint32_t i = 0; i + 1 <= n && o < dstcap - 1; i += 2) { + uint8_t lo = s[i]; + uint8_t hi = (i + 1 < n) ? s[i + 1] : 0; + if (lo == 0xFF || lo == 0xFE) + continue; + if (hi == 0 && lo >= 0x20 && lo < 0x7F) + dst[o++] = (char)lo; + } + } else { + for (uint32_t i = 0; i < n && o < dstcap - 1; i++) { + if (s[i] == 0) + break; + dst[o++] = (char)s[i]; + } + } + dst[o] = '\0'; +} + +static void +handle_pic(const uint8_t *frame_data, uint32_t len, long file_base, bool v22, id3_meta_t *out) { + if (len < 4) + return; + uint8_t enc = frame_data[0]; + uint32_t i = 1; + id3_cover_fmt_t fmt = ID3_COVER_JPEG; + + if (v22) { + if (len < 1 + 3 + 1) + return; + if (memcmp(frame_data + 1, "PNG", 3) == 0) + fmt = ID3_COVER_PNG; + else + fmt = ID3_COVER_JPEG; + i = 1 + 3; + } else { + uint32_t m = i; + while (m < len && frame_data[m] != 0) + m++; + if (m >= len) + return; + if ((m - i) >= 9 && memcmp(frame_data + i, "image/png", 9) == 0) + fmt = ID3_COVER_PNG; + else + fmt = ID3_COVER_JPEG; + i = m + 1; + } + + if (i >= len) + return; + i += 1; + + if (enc == 1 || enc == 2) { + while (i + 1 < len && !(frame_data[i] == 0 && frame_data[i + 1] == 0)) + i += 2; + i += 2; + } else { + while (i < len && frame_data[i] != 0) + i++; + i += 1; + } + if (i >= len) + return; + + out->cover_fmt = fmt; + out->cover_off = file_base + (long)i; + out->cover_size = len - i; +} + +bool id3_meta_parse(const char *path, id3_meta_t *out) { + if (path == NULL || out == NULL) + return false; + memset(out, 0, sizeof(*out)); + out->cover_fmt = ID3_COVER_NONE; + + FILE *f = fopen(path, "rb"); + if (f == NULL) + return false; + + uint8_t h[10]; + if (fread(h, 1, 10, f) != 10) { + fclose(f); + return false; + } + if (memcmp(h, "ID3", 3) != 0 || h[3] == 0xFF || h[4] == 0xFF) { + fclose(f); + out->audio_off = 0; + return true; + } + + int major = h[3]; + uint8_t flags = h[5]; + uint32_t tag_size = syncsafe32(h + 6); + out->audio_off = 10 + (long)tag_size + ((flags & 0x10) ? 10 : 0); + + if (tag_size == 0 || tag_size > ID3_MAX_TAG) { + fclose(f); + return true; + } + + uint8_t *buf = malloc(tag_size); + if (buf == NULL) { + fclose(f); + return true; + } + if (fread(buf, 1, tag_size, f) != tag_size) { + free(buf); + fclose(f); + return true; + } + fclose(f); + + uint32_t p = 0; + if (flags & 0x40) { + if (major == 4) { + if (tag_size >= 4) + p += syncsafe32(buf); + } else if (major == 3) { + if (tag_size >= 4) + p += 4 + be32(buf); + } + if (p >= tag_size) { + free(buf); + return true; + } + } + + bool v22 = (major == 2); + uint32_t id_len = v22 ? 3 : 4; + uint32_t sz_len = v22 ? 3 : 4; + uint32_t hdr_len = v22 ? 6 : 10; + + while (p + hdr_len <= tag_size) { + const uint8_t *id = buf + p; + if (id[0] == 0) + break; + + uint32_t fsize; + if (v22) + fsize = be24(buf + p + id_len); + else if (major == 4) + fsize = syncsafe32(buf + p + id_len); + else + fsize = be32(buf + p + id_len); + + uint32_t data_off = p + hdr_len; + if (fsize == 0 || data_off + fsize > tag_size) + break; + const uint8_t *data = buf + data_off; + long file_base = 10 + (long)data_off; + + if (v22) { + if (memcmp(id, "TT2", 3) == 0) + copy_text(out->title, sizeof(out->title), data, fsize); + else if (memcmp(id, "TP1", 3) == 0) + copy_text(out->artist, sizeof(out->artist), data, fsize); + else if (memcmp(id, "TAL", 3) == 0) + copy_text(out->album, sizeof(out->album), data, fsize); + else if (memcmp(id, "TYE", 3) == 0) + copy_text(out->year, sizeof(out->year), data, fsize); + else if (memcmp(id, "PIC", 3) == 0) + handle_pic(data, fsize, file_base, true, out); + } else { + if (memcmp(id, "TIT2", 4) == 0) + copy_text(out->title, sizeof(out->title), data, fsize); + else if (memcmp(id, "TPE1", 4) == 0) + copy_text(out->artist, sizeof(out->artist), data, fsize); + else if (memcmp(id, "TALB", 4) == 0) + copy_text(out->album, sizeof(out->album), data, fsize); + else if (memcmp(id, "TYER", 4) == 0 || memcmp(id, "TDRC", 4) == 0) + copy_text(out->year, sizeof(out->year), data, fsize); + else if (memcmp(id, "APIC", 4) == 0) + handle_pic(data, fsize, file_base, false, out); + } + + p = data_off + fsize; + (void)sz_len; + } + + free(buf); + return true; +} + +uint8_t *id3_meta_read_cover(const char *path, const id3_meta_t *m, uint32_t *out_size) { + if (path == NULL || m == NULL || m->cover_fmt == ID3_COVER_NONE || m->cover_size == 0) + return NULL; + if (m->cover_size > ID3_MAX_COVER) + return NULL; + + FILE *f = fopen(path, "rb"); + if (f == NULL) + return NULL; + uint8_t *buf = malloc(m->cover_size); + if (buf == NULL) { + fclose(f); + return NULL; + } + if (fseek(f, m->cover_off, SEEK_SET) != 0 || fread(buf, 1, m->cover_size, f) != m->cover_size) { + free(buf); + fclose(f); + return NULL; + } + fclose(f); + if (out_size) + *out_size = m->cover_size; + return buf; +} diff --git a/firmware_p4/components/Applications/ui/screens/audio/include/id3_meta.h b/firmware_p4/components/Applications/ui/screens/audio/include/id3_meta.h new file mode 100644 index 000000000..ce86e044d --- /dev/null +++ b/firmware_p4/components/Applications/ui/screens/audio/include/id3_meta.h @@ -0,0 +1,70 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#ifndef ID3_META_H +#define ID3_META_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** + * @brief Embedded cover-art image format. + */ +typedef enum { + ID3_COVER_NONE = 0, + ID3_COVER_JPEG, + ID3_COVER_PNG, +} id3_cover_fmt_t; + +/** + * @brief Parsed ID3v2 tag. Strings are always NUL-terminated (empty if absent). + */ +typedef struct { + char title[64]; /**< TIT2 / TT2 */ + char artist[64]; /**< TPE1 / TP1 */ + char album[64]; /**< TALB / TAL */ + char year[8]; /**< TYER / TDRC / TYE */ + + id3_cover_fmt_t + cover_fmt; /**< Embedded cover-art format (APIC / PIC). Read with id3_meta_read_cover(). */ + long cover_off; /**< Absolute file offset of the image bytes (0 if none). */ + uint32_t cover_size; /**< Size of the image blob in bytes. */ + + long audio_off; /**< File offset where audio begins, right after the ID3v2 tag; a decoder should + fseek here before searching for the first MP3 frame sync word. */ +} id3_meta_t; + +/** + * @brief Parse the ID3v2 tag at the head of an .mp3 file. Reads only the tag + * (bounded), never the audio. Self-contained, no library. On a file with + * no ID3v2 tag it still succeeds with empty strings and audio_off = 0. + */ +bool id3_meta_parse(const char *path, id3_meta_t *out); + +/** + * @brief Read the embedded cover-art blob into a freshly malloc'd buffer that + * the caller must free(). NULL if there is no cover or on error. + */ +uint8_t *id3_meta_read_cover(const char *path, const id3_meta_t *m, uint32_t *out_size); + +#ifdef __cplusplus +} +#endif + +#endif // ID3_META_H diff --git a/firmware_p4/components/Applications/ui/screens/audio/include/mp3_player_ui.h b/firmware_p4/components/Applications/ui/screens/audio/include/mp3_player_ui.h new file mode 100644 index 000000000..1d02f02b9 --- /dev/null +++ b/firmware_p4/components/Applications/ui/screens/audio/include/mp3_player_ui.h @@ -0,0 +1,52 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#ifndef MP3_PLAYER_UI_H +#define MP3_PLAYER_UI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Set the .mp3 file to play. Call BEFORE switching to SCREEN_MP3_PLAYER + * (e.g. from the Files screen). The string is copied. + */ +void ui_mp3_player_set_path(const char *path); + +/** @brief Set the screen to return to on BACK (as a screen_id_t value). */ +void ui_mp3_player_set_return(int screen); + +/** + * @brief Bind to the Player library at index @p index (playlist mode) so + * prev/next and auto-advance navigate the shared wav+mp3 list. Call AFTER + * ui_mp3_player_set_path() (which resets to standalone) and before opening. + */ +void ui_mp3_player_set_index(int index); + +/** @brief Open the MP3 player: parses ID3 + cover, shows them, starts playback. */ +void ui_mp3_player_open(void); + +/** + * @brief Stop playback and release the I2S channel, waiting for the decode task + * to exit. Registered as the screen close hook for clean navigation/handoff. + */ +void ui_mp3_player_stop(void); + +#ifdef __cplusplus +} +#endif + +#endif // MP3_PLAYER_UI_H diff --git a/firmware_p4/components/Applications/ui/screens/audio/include/wav_library_ui.h b/firmware_p4/components/Applications/ui/screens/audio/include/wav_library_ui.h index 098bbb1eb..832558b64 100644 --- a/firmware_p4/components/Applications/ui/screens/audio/include/wav_library_ui.h +++ b/firmware_p4/components/Applications/ui/screens/audio/include/wav_library_ui.h @@ -20,9 +20,22 @@ extern "C" { #endif -/** @brief Open the audio player library: lists every .wav on the SD card. */ +#include + +/** @brief Open the audio player library: lists every .wav / .mp3 on the SD card. */ void ui_wav_library_open(void); +/** @brief True if track @p i is an .mp3 (else it is a .wav). */ +bool ui_wav_library_is_mp3(int i); + +/** + * @brief Play library track @p i in the correct player (wav or mp3), wrapping + * the index. Used by both players for prev/next and auto-advance so + * navigation flows seamlessly across a mixed wav+mp3 list. @p return_screen + * is a screen_id_t value the opened player returns to on BACK. + */ +void ui_player_play_index(int i, int return_screen); + /** @brief Number of .wav tracks found on the last scan. */ int ui_wav_library_count(void); diff --git a/firmware_p4/components/Applications/ui/screens/audio/include/wav_player_ui.h b/firmware_p4/components/Applications/ui/screens/audio/include/wav_player_ui.h index b3597e339..f6e42a33e 100644 --- a/firmware_p4/components/Applications/ui/screens/audio/include/wav_player_ui.h +++ b/firmware_p4/components/Applications/ui/screens/audio/include/wav_player_ui.h @@ -46,6 +46,13 @@ void ui_wav_player_set_return(int screen); /** @brief Open the WAV player screen and start streaming the set path. */ void ui_wav_player_open(void); +/** + * @brief Stop playback and release the I2S channel, waiting for the decode task + * to exit. Registered as the screen close hook so navigating away (or + * handing off to the MP3 player) tears down cleanly with no audio clash. + */ +void ui_wav_player_stop(void); + #ifdef __cplusplus } #endif diff --git a/firmware_p4/components/Applications/ui/screens/audio/micrec_ui.c b/firmware_p4/components/Applications/ui/screens/audio/micrec_ui.c index 6ec63be10..d99ff24e2 100644 --- a/firmware_p4/components/Applications/ui/screens/audio/micrec_ui.c +++ b/firmware_p4/components/Applications/ui/screens/audio/micrec_ui.c @@ -377,7 +377,7 @@ static void op_done_cb(void *unused) { static void finish(const char *done_text) { strncpy(s_done_text, done_text, sizeof(s_done_text) - 1); s_done_text[sizeof(s_done_text) - 1] = '\0'; - lv_async_call(op_done_cb, NULL); + ui_async_call(op_done_cb, NULL); } static void mic_level_cb(int peak, int rms, void *ctx) { diff --git a/firmware_p4/components/Applications/ui/screens/audio/mp3_player_ui.c b/firmware_p4/components/Applications/ui/screens/audio/mp3_player_ui.c new file mode 100644 index 000000000..7ca509308 --- /dev/null +++ b/firmware_p4/components/Applications/ui/screens/audio/mp3_player_ui.c @@ -0,0 +1,676 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#ifndef MP3_AUDIO_DECODE +#define MP3_AUDIO_DECODE 1 +#endif + +#include "mp3_player_ui.h" + +#include +#include +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "sys_prio.h" + +#include "esp_log.h" +#include "st7789.h" + +#include "audio_i2s.h" +#include "tos_config.h" +#include "tos_storage_paths.h" +#include "id3_meta.h" +#include "media_thumb.h" +#include "wav_library_ui.h" +#include "ui_chrome.h" +#include "ui_feedback.h" +#include "ui_manager.h" +#include "ui_metrics.h" +#include "ui_theme.h" + +#if MP3_AUDIO_DECODE +#include "mp3dec.h" +#endif + +#define PATH_MAX_LEN 256 +#define REFRESH_TIMER_MS 60 +#define N_BARS 12 +#define BAR_W 9 +#define SPEC_H 58 +#define COVER_BOX 118 +#define READBUF_SZ 4096 +#define MP3_MAXFRAME 2304 +#define VOL_STEP 10 +#define VOL_DEFAULT 80 +#define ACC1 0x7A52D6 +#define ACC2 0xB89AFF + +#define PLAYER_TASK_STACK 12288 +#define PLAYER_TASK_PRIO SYS_PRIO_SERVICE_HI + +#define MP3_DEFAULT_RATE_HZ 44100 +#define MP3_SILENCE_SAMPLES 256 +#define MP3_STOP_WAIT_ITERS 80 +#define MP3_STOP_POLL_MS 10 + +static const char *TAG = "MP3_PLAYER"; + +static char s_path[PATH_MAX_LEN]; +static screen_id_t s_return = SCREEN_FILES; +static int s_index = -1; + +static lv_obj_t *s_screen = NULL; +static lv_obj_t *s_bars[N_BARS]; +static lv_obj_t *s_pfill = NULL; +static lv_obj_t *s_t_cur = NULL; +static lv_obj_t *s_t_tot = NULL; +static lv_obj_t *s_play_ic = NULL; +static lv_obj_t *s_fmt = NULL; +static lv_timer_t *s_refresh_timer = NULL; + +static media_thumb_t s_thumb; +static bool s_thumb_valid = false; +static uint8_t *s_png_blob = NULL; +static lv_image_dsc_t s_png_dsc; +static bool s_png_valid = false; +static id3_meta_t s_meta; + +static TaskHandle_t s_task = NULL; +static volatile bool s_task_run = false; +static volatile bool s_stop_req = false; +static volatile bool s_exit_req = false; +static volatile bool s_playing = false; +static volatile bool s_err = false; +static volatile bool s_finished = false; +static volatile int s_pos_sec = 0; +static volatile int s_total_sec = 0; +static volatile int s_level[N_BARS]; +static int s_vol = VOL_DEFAULT; +static bool s_vol_dirty = false; + +static void free_thumb(void) { + if (s_thumb_valid) { + media_thumb_free(&s_thumb); + s_thumb_valid = false; + } + if (s_png_blob != NULL) { + free(s_png_blob); + s_png_blob = NULL; + } + s_png_valid = false; +} + +static void load_cover(void) { + free_thumb(); + ESP_LOGI(TAG, + "ID3 cover fmt=%d size=%u (1=JPEG 2=PNG 0=none)", + (int)s_meta.cover_fmt, + (unsigned)s_meta.cover_size); + if (s_meta.cover_fmt == ID3_COVER_NONE || s_meta.cover_size == 0) + return; + uint32_t blob_len = 0; + uint8_t *blob = id3_meta_read_cover(s_path, &s_meta, &blob_len); + if (blob == NULL) + return; + if (s_meta.cover_fmt == ID3_COVER_JPEG) { + s_thumb_valid = media_thumb_decode_jpeg(blob, blob_len, &s_thumb); + ESP_LOGI(TAG, "JPEG cover decode %s", s_thumb_valid ? "OK" : "FAILED"); + free(blob); + } else { + s_png_blob = blob; + memset(&s_png_dsc, 0, sizeof(s_png_dsc)); + s_png_dsc.header.magic = LV_IMAGE_HEADER_MAGIC; + s_png_dsc.header.cf = LV_COLOR_FORMAT_RAW; + s_png_dsc.data = s_png_blob; + s_png_dsc.data_size = blob_len; + s_png_valid = true; + ESP_LOGI(TAG, "PNG cover queued (%u bytes)", (unsigned)blob_len); + } +} + +static void fmt_time(char *out, size_t n, int sec) { + if (sec < 0) + sec = 0; + snprintf(out, n, "%d:%02d", sec / 60, sec % 60); +} + +#if MP3_AUDIO_DECODE +static void compute_bars(const int16_t *mono, int n) { + if (n <= 0) + return; + for (int b = 0; b < N_BARS; b++) { + int a = (b * n) / N_BARS; + int z = ((b + 1) * n) / N_BARS; + if (z <= a) { + s_level[b] = 0; + continue; + } + uint64_t sumsq = 0; + for (int i = a; i < z; i++) { + int32_t v = mono[i]; + sumsq += (uint64_t)(v * v); + } + int rms = (int)sqrt((double)(sumsq / (z - a))); + int pct = (rms * 130) / 32768; + if (pct > 100) + pct = 100; + s_level[b] = pct; + } +} +#endif + +static void exit_cb(void *p) { + (void)p; + ui_switch_screen(s_return); +} + +static void player_task(void *arg) { + (void)arg; +#if MP3_AUDIO_DECODE + FILE *f = fopen(s_path, "rb"); + HMP3Decoder dec = NULL; + uint8_t *readbuf = malloc(READBUF_SZ); + int16_t *pcm = malloc(MP3_MAXFRAME * sizeof(int16_t)); + int16_t *mono = malloc((MP3_MAXFRAME / 2 + 4) * sizeof(int16_t)); + if (f != NULL) + dec = MP3InitDecoder(); + if (f == NULL || dec == NULL || readbuf == NULL || pcm == NULL || mono == NULL) { + if (dec) + MP3FreeDecoder(dec); + free(readbuf); + free(pcm); + free(mono); + if (f) + fclose(f); + s_err = true; + s_task_run = false; + s_task = NULL; + vTaskDelete(NULL); + return; + } + + long audio_off = s_meta.audio_off > 0 ? s_meta.audio_off : 0; + fseek(f, 0, SEEK_END); + long fsize = ftell(f); + fseek(f, audio_off, SEEK_SET); + + uint8_t *rp = readbuf; + int left = 0; + bool started = false; + bool eof = false; + uint32_t rate = MP3_DEFAULT_RATE_HZ; + long samples_played = 0; + + while (!s_stop_req) { + if (!s_playing) { + if (started) { + memset(mono, 0, MP3_SILENCE_SAMPLES * sizeof(int16_t)); + audio_i2s_stream_write(mono, MP3_SILENCE_SAMPLES); + } else { + vTaskDelay(pdMS_TO_TICKS(20)); + } + continue; + } + + if (left < 2 * 512) { + if (rp != readbuf && left > 0) + memmove(readbuf, rp, left); + rp = readbuf; + size_t got = fread(readbuf + left, 1, READBUF_SZ - left, f); + left += (int)got; + if (got == 0) + eof = true; + if (eof && left < 4) + break; + } + + int off = MP3FindSyncWord(rp, left); + if (off < 0) { + if (eof) + break; + left = 0; + continue; + } + rp += off; + left -= off; + + int err = MP3Decode(dec, &rp, &left, pcm, 0); + if (err != 0) { + if (err == ERR_MP3_INDATA_UNDERFLOW || err == ERR_MP3_MAINDATA_UNDERFLOW) { + if (eof) + break; + if (left > 0) { + memmove(readbuf, rp, left); + rp = readbuf; + } + continue; + } + if (left > 0) { + rp++; + left--; + } + continue; + } + + MP3FrameInfo fi; + MP3GetLastFrameInfo(dec, &fi); + if (fi.samprate <= 0 || fi.nChans <= 0 || fi.outputSamps <= 0) + continue; + + if (!started) { + rate = (uint32_t)fi.samprate; + if (audio_i2s_stream_start(rate) != ESP_OK) { + s_err = true; + break; + } + started = true; + if (fi.bitrate > 0) + s_total_sec = (int)(((int64_t)(fsize - audio_off) * 8) / fi.bitrate); + } + + int frames = fi.outputSamps / fi.nChans; + if (frames > MP3_MAXFRAME / 2) + frames = MP3_MAXFRAME / 2; + if (fi.nChans == 2) { + for (int i = 0; i < frames; i++) { + int l = pcm[2 * i]; + int r = pcm[2 * i + 1]; + mono[i] = (int16_t)((l + r) / 2); + } + } else { + for (int i = 0; i < frames; i++) + mono[i] = pcm[i]; + } + compute_bars(mono, frames); + audio_i2s_stream_write(mono, frames); + samples_played += frames; + s_pos_sec = (int)(samples_played / (rate > 0 ? rate : MP3_DEFAULT_RATE_HZ)); + } + + if (started) + audio_i2s_stream_stop(); + MP3FreeDecoder(dec); + free(readbuf); + free(pcm); + free(mono); + fclose(f); +#else + s_err = true; +#endif + + for (int i = 0; i < N_BARS; i++) + s_level[i] = 0; + if (!s_stop_req) + s_finished = true; + s_playing = false; + s_task_run = false; + s_task = NULL; + if (s_exit_req) { + s_exit_req = false; + ui_async_call(exit_cb, NULL); + } + vTaskDelete(NULL); +} + +static void start_playback(void) { + if (s_task_run) + return; + s_stop_req = false; + s_finished = false; + s_err = false; + s_pos_sec = 0; + s_playing = true; + for (int i = 0; i < N_BARS; i++) + s_level[i] = 0; + s_task_run = true; + if (xTaskCreatePinnedToCore(player_task, + "mp3_play", + PLAYER_TASK_STACK, + NULL, + PLAYER_TASK_PRIO, + &s_task, + SYS_CORE_UI) != pdPASS) { + s_task_run = false; + s_err = true; + } +} + +static void mp3_input(const input_event_t *ev, void *ctx) { + (void)ctx; + if (ev->action != INPUT_ACTION_PRESS && ev->action != INPUT_ACTION_REPEAT) + return; + const bool press = (ev->action == INPUT_ACTION_PRESS); + + switch (ev->button) { + case INPUT_BTN_BACK: + if (press) + ui_switch_screen(s_return); + break; + case INPUT_BTN_RIGHT: + if (press && s_index >= 0) + ui_player_play_index(s_index + 1, s_return); + break; + case INPUT_BTN_LEFT: + if (press && s_index >= 0) + ui_player_play_index(s_index - 1, s_return); + break; + case INPUT_BTN_OK: + if (press) { + if (!s_task_run || s_finished) + start_playback(); + else + s_playing = !s_playing; + ui_feedback(UI_FB_SELECT); + } + break; + case INPUT_BTN_DOWN: + s_vol = (s_vol > VOL_STEP) ? s_vol - VOL_STEP : 0; + s_vol_dirty = true; + audio_i2s_set_volume((uint8_t)s_vol); + ui_feedback(UI_FB_NAV); + break; + case INPUT_BTN_UP: + s_vol = (s_vol + VOL_STEP < 100) ? s_vol + VOL_STEP : 100; + s_vol_dirty = true; + audio_i2s_set_volume((uint8_t)s_vol); + ui_feedback(UI_FB_NAV); + break; + default: + break; + } +} + +static void refresh_cb(lv_timer_t *t) { + if (lv_screen_active() != s_screen) { + lv_timer_delete(t); + s_refresh_timer = NULL; + return; + } + if (s_finished) { + s_finished = false; + if (s_index >= 0) + ui_player_play_index(s_index + 1, s_return); + else + start_playback(); + return; + } + for (int i = 0; i < N_BARS; i++) { + if (s_bars[i]) + lv_obj_set_height(s_bars[i], lv_pct(s_level[i] < 3 ? 3 : s_level[i])); + } + int tot = s_total_sec > 0 ? s_total_sec : 1; + int pct = (s_pos_sec * 100) / tot; + if (pct > 100) + pct = 100; + if (s_pfill) + lv_obj_set_width(s_pfill, lv_pct(pct)); + char b[12]; + fmt_time(b, sizeof(b), s_pos_sec); + lv_label_set_text(s_t_cur, b); + fmt_time(b, sizeof(b), s_total_sec); + lv_label_set_text(s_t_tot, b); + if (s_play_ic) + lv_label_set_text(s_play_ic, (s_playing && !s_finished) ? LV_SYMBOL_PAUSE : LV_SYMBOL_PLAY); + if (s_fmt && s_err) { + lv_label_set_text(s_fmt, MP3_AUDIO_DECODE ? "decode error" : "playback: enable esp-audio-libs"); + lv_obj_set_style_text_color(s_fmt, lv_color_hex(0xFF5252), 0); + } +} + +void ui_mp3_player_set_path(const char *path) { + s_index = -1; + if (path == NULL) { + s_path[0] = '\0'; + return; + } + strncpy(s_path, path, sizeof(s_path) - 1); + s_path[sizeof(s_path) - 1] = '\0'; +} + +void ui_mp3_player_set_return(int screen) { + s_return = (screen_id_t)screen; +} + +void ui_mp3_player_set_index(int index) { + s_index = index; +} + +void ui_mp3_player_stop(void) { + if (s_task_run) { + s_stop_req = true; + for (int i = 0; i < MP3_STOP_WAIT_ITERS && s_task_run; i++) + vTaskDelay(pdMS_TO_TICKS(MP3_STOP_POLL_MS)); + } + if (s_vol_dirty) { + g_config_system.volume = s_vol; + tos_config_save(TOS_PATH_CONFIG_SYSTEM, "system"); + s_vol_dirty = false; + } +} + +static lv_obj_t * +mk_label(lv_obj_t *parent, const char *txt, const lv_font_t *font, lv_color_t col, int width) { + lv_obj_t *l = lv_label_create(parent); + lv_label_set_text(l, txt); + lv_obj_set_style_text_font(l, font, 0); + lv_obj_set_style_text_color(l, col, 0); + if (width > 0) { + lv_obj_set_width(l, width); + lv_label_set_long_mode(l, LV_LABEL_LONG_DOT); + lv_obj_set_style_text_align(l, LV_TEXT_ALIGN_CENTER, 0); + } + return l; +} + +static lv_obj_t *transport_btn(lv_obj_t *parent, const char *sym, bool primary) { + lv_obj_t *b = lv_obj_create(parent); + int d = primary ? 46 : 32; + lv_obj_set_size(b, d, d); + lv_obj_remove_flag(b, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_style_radius(b, LV_RADIUS_CIRCLE, 0); + lv_obj_set_style_border_width(b, 0, 0); + lv_obj_set_style_pad_all(b, 0, 0); + if (primary) { + lv_obj_set_style_bg_color(b, lv_color_hex(ACC2), 0); + lv_obj_set_style_bg_grad_color(b, lv_color_hex(ACC1), 0); + lv_obj_set_style_bg_grad_dir(b, LV_GRAD_DIR_VER, 0); + lv_obj_set_style_bg_opa(b, LV_OPA_COVER, 0); + lv_obj_set_style_shadow_width(b, 14, 0); + lv_obj_set_style_shadow_color(b, lv_color_hex(ACC1), 0); + lv_obj_set_style_shadow_opa(b, LV_OPA_40, 0); + } else { + lv_obj_set_style_bg_opa(b, LV_OPA_TRANSP, 0); + lv_obj_set_style_shadow_width(b, 0, 0); + } + lv_obj_t *l = lv_label_create(b); + lv_label_set_text(l, sym); + lv_obj_set_style_text_font(l, &lv_font_montserrat_14, 0); + lv_obj_set_style_text_color( + l, primary ? lv_color_hex(0x0A0220) : current_theme.text_secondary, 0); + lv_obj_center(l); + return l; +} + +void ui_mp3_player_open(void) { + if (s_screen != NULL) { + lv_obj_del(s_screen); + s_screen = NULL; + } + + if (s_index >= 0) { + const char *p = ui_wav_library_path(s_index); + if (p != NULL) { + strncpy(s_path, p, sizeof(s_path) - 1); + s_path[sizeof(s_path) - 1] = '\0'; + ui_wav_library_set_selected(s_index); + } else { + s_index = -1; + } + } + + memset(&s_meta, 0, sizeof(s_meta)); + id3_meta_parse(s_path, &s_meta); + load_cover(); + + s_screen = lv_obj_create(NULL); + lv_obj_set_style_bg_color(s_screen, current_theme.screen_base, 0); + lv_obj_set_style_bg_opa(s_screen, LV_OPA_COVER, 0); + lv_obj_remove_flag(s_screen, LV_OBJ_FLAG_SCROLLABLE); + + ui_chrome_header(s_screen, "PLAYING", "/assets/icons/music_note.bin"); + ui_chrome_footer(s_screen, LV_SYMBOL_UP LV_SYMBOL_DOWN " vol OK play BACK exit"); + + lv_obj_t *body = lv_obj_create(s_screen); + lv_obj_remove_style_all(body); + lv_obj_set_size(body, 224, ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); + lv_obj_align(body, LV_ALIGN_TOP_MID, 0, UI_CHROME_HEADER_H); + lv_obj_remove_flag(body, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_flex_flow(body, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align( + body, LV_FLEX_ALIGN_SPACE_EVENLY, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_style_pad_hor(body, 6, 0); + lv_obj_set_style_pad_ver(body, 6, 0); + + lv_obj_t *cover = lv_obj_create(body); + lv_obj_remove_style_all(cover); + lv_obj_set_size(cover, COVER_BOX, COVER_BOX); + lv_obj_remove_flag(cover, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_style_radius(cover, 14, 0); + lv_obj_set_style_clip_corner(cover, true, 0); + lv_obj_set_style_bg_color(cover, current_theme.bg_secondary, 0); + lv_obj_set_style_bg_opa(cover, LV_OPA_COVER, 0); + lv_obj_set_style_border_width(cover, 1, 0); + lv_obj_set_style_border_color(cover, current_theme.border_inactive, 0); + lv_obj_set_style_shadow_width(cover, 26, 0); + lv_obj_set_style_shadow_color(cover, lv_color_hex(ACC1), 0); + lv_obj_set_style_shadow_opa(cover, LV_OPA_40, 0); + const lv_image_dsc_t *cover_src = NULL; + int cw = 0, ch = 0; + if (s_thumb_valid && s_thumb.w > 0 && s_thumb.h > 0) { + cover_src = &s_thumb.dsc; + cw = s_thumb.w; + ch = s_thumb.h; + } else if (s_png_valid) { + cover_src = &s_png_dsc; + lv_image_header_t hdr = {0}; + if (lv_image_decoder_get_info(&s_png_dsc, &hdr) == LV_RESULT_OK) { + cw = hdr.w; + ch = hdr.h; + } + } + if (cover_src != NULL) { + lv_obj_t *img = lv_image_create(cover); + lv_image_set_src(img, cover_src); + lv_obj_center(img); + int longest = cw > ch ? cw : ch; + if (longest > 0) { + int zoom = (COVER_BOX * 256) / longest; + if (zoom < 1) + zoom = 1; + lv_image_set_scale(img, (uint16_t)zoom); + } + } else { + lv_obj_t *ph = lv_label_create(cover); + lv_label_set_text(ph, LV_SYMBOL_AUDIO); + lv_obj_set_style_text_font(ph, &lv_font_montserrat_14, 0); + lv_obj_set_style_text_color(ph, current_theme.text_secondary, 0); + lv_obj_center(ph); + } + + const char *slash = strrchr(s_path, '/'); + const char *fallback = s_path[0] ? (slash ? slash + 1 : s_path) : "no file"; + lv_obj_t *fname = lv_label_create(body); + lv_label_set_text(fname, s_meta.title[0] ? s_meta.title : fallback); + lv_label_set_long_mode(fname, LV_LABEL_LONG_DOT); + lv_obj_set_width(fname, 204); + lv_obj_set_style_text_align(fname, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_style_text_font(fname, &lv_font_montserrat_14, 0); + lv_obj_set_style_text_color(fname, current_theme.text_main, 0); + + char info[128] = ""; + if (s_meta.artist[0]) + snprintf(info, sizeof(info), "%s", s_meta.artist); + if (s_meta.album[0]) { + size_t k = strlen(info); + snprintf(info + k, sizeof(info) - k, "%s%s", info[0] ? " | " : "", s_meta.album); + } + if (s_meta.year[0]) { + size_t k = strlen(info); + snprintf(info + k, sizeof(info) - k, "%s%s", info[0] ? " | " : "", s_meta.year); + } + s_fmt = lv_label_create(body); + lv_label_set_text(s_fmt, info[0] ? info : "MP3"); + lv_label_set_long_mode(s_fmt, LV_LABEL_LONG_DOT); + lv_obj_set_width(s_fmt, 204); + lv_obj_set_style_text_align(s_fmt, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_style_text_font(s_fmt, &lv_font_montserrat_12, 0); + lv_obj_set_style_text_color(s_fmt, lv_color_hex(0x00E5D0), 0); + + lv_obj_t *prog = lv_obj_create(body); + lv_obj_remove_style_all(prog); + lv_obj_set_size(prog, 200, LV_SIZE_CONTENT); + lv_obj_remove_flag(prog, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_flex_flow(prog, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(prog, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_style_pad_row(prog, 3, 0); + lv_obj_t *track = lv_obj_create(prog); + lv_obj_remove_style_all(track); + lv_obj_set_size(track, 200, 5); + lv_obj_set_style_radius(track, 3, 0); + lv_obj_set_style_bg_color(track, current_theme.border_inactive, 0); + lv_obj_set_style_bg_opa(track, LV_OPA_COVER, 0); + s_pfill = lv_obj_create(track); + lv_obj_remove_style_all(s_pfill); + lv_obj_set_height(s_pfill, lv_pct(100)); + lv_obj_set_width(s_pfill, lv_pct(0)); + lv_obj_align(s_pfill, LV_ALIGN_LEFT_MID, 0, 0); + lv_obj_set_style_radius(s_pfill, 3, 0); + lv_obj_set_style_bg_color(s_pfill, lv_color_hex(ACC1), 0); + lv_obj_set_style_bg_grad_color(s_pfill, lv_color_hex(ACC2), 0); + lv_obj_set_style_bg_grad_dir(s_pfill, LV_GRAD_DIR_HOR, 0); + lv_obj_set_style_bg_opa(s_pfill, LV_OPA_COVER, 0); + lv_obj_t *trow = lv_obj_create(prog); + lv_obj_remove_style_all(trow); + lv_obj_set_size(trow, 200, LV_SIZE_CONTENT); + lv_obj_set_flex_flow(trow, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align( + trow, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + s_t_cur = mk_label(trow, "0:00", &lv_font_montserrat_12, current_theme.text_main, 0); + s_t_tot = mk_label(trow, "0:00", &lv_font_montserrat_12, current_theme.text_main, 0); + + lv_obj_t *ctrls = lv_obj_create(body); + lv_obj_remove_style_all(ctrls); + lv_obj_set_size(ctrls, 180, 50); + lv_obj_remove_flag(ctrls, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_flex_flow(ctrls, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(ctrls, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_style_pad_column(ctrls, 20, 0); + transport_btn(ctrls, LV_SYMBOL_PREV, false); + s_play_ic = transport_btn(ctrls, LV_SYMBOL_PAUSE, true); + transport_btn(ctrls, LV_SYMBOL_NEXT, false); + + s_vol = g_config_system.volume; + s_vol_dirty = false; + audio_i2s_set_volume((uint8_t)s_vol); + ui_input_set_screen_handler(mp3_input, NULL); + if (s_refresh_timer == NULL) + s_refresh_timer = lv_timer_create(refresh_cb, REFRESH_TIMER_MS, NULL); + + start_playback(); + ui_screen_load_owned(&s_screen, s_screen); +} diff --git a/firmware_p4/components/Applications/ui/screens/audio/speaker_ui.c b/firmware_p4/components/Applications/ui/screens/audio/speaker_ui.c index 255145147..55737343d 100644 --- a/firmware_p4/components/Applications/ui/screens/audio/speaker_ui.c +++ b/firmware_p4/components/Applications/ui/screens/audio/speaker_ui.c @@ -477,7 +477,7 @@ static void rebuild_async(void *p) { static void cycle_category(int dir) { s_category = (s_category + dir + NUM_CATS) % NUM_CATS; - lv_async_call(rebuild_async, NULL); + ui_async_call(rebuild_async, NULL); } static void speaker_input(const input_event_t *ev, void *ctx) { diff --git a/firmware_p4/components/Applications/ui/screens/audio/spectrum_ui.c b/firmware_p4/components/Applications/ui/screens/audio/spectrum_ui.c index d408475e3..171147a36 100644 --- a/firmware_p4/components/Applications/ui/screens/audio/spectrum_ui.c +++ b/firmware_p4/components/Applications/ui/screens/audio/spectrum_ui.c @@ -29,6 +29,7 @@ #include "audio_i2s.h" #include "ui_chrome.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" static const char *TAG = "SPECTRUM_UI"; @@ -269,7 +270,7 @@ void ui_spectrum_open(void) { int band_top = UI_CHROME_HEADER_H; int plot_top = band_top + PLOT_TOP_OFS; - int plot_h = LCD_V_RES - UI_CHROME_FOOTER_H - AXIS_H - PLOT_BOTTOM_PAD - plot_top; + int plot_h = ui_screen_h() - UI_CHROME_FOOTER_H - AXIS_H - PLOT_BOTTOM_PAD - plot_top; if (plot_h < PLOT_MIN_H) plot_h = PLOT_MIN_H; diff --git a/firmware_p4/components/Applications/ui/screens/audio/wav_library_ui.c b/firmware_p4/components/Applications/ui/screens/audio/wav_library_ui.c index 74320062a..590b86155 100644 --- a/firmware_p4/components/Applications/ui/screens/audio/wav_library_ui.c +++ b/firmware_p4/components/Applications/ui/screens/audio/wav_library_ui.c @@ -15,6 +15,8 @@ #include "wav_library_ui.h" +#include "esp_attr.h" + #include #include #include @@ -25,11 +27,14 @@ #include "assets_manager.h" #include "ui_chrome.h" #include "ui_feedback.h" +#include "mp3_player_ui.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #include "wav_player_ui.h" #define SDCARD_ROOT "/sdcard" +#define ASSETS_ROOT "/assets" #define MAX_TRACKS 64 #define PATH_LEN 192 #define NAME_LEN 56 @@ -38,13 +43,12 @@ #define ROW_ICON "/assets/icons/music_note.bin" #define EMPTY_ICON "/assets/icons/sd_card.bin" -#define EMPTY_MSG "No .wav on SD card" +#define EMPTY_MSG "No music on SD card" #define EMPTY_SUB "Add tracks and reopen" #define CARD_W 200 #define CARD_H 132 #define BADGE_PX 56 #define ICON_PX 34 -#define DIM_COLOR 0x8A8594 #define LIST_PAD_SIDE 8 #define LIST_PAD_ROW 6 @@ -84,16 +88,21 @@ static int s_eq_phase = 0; static const uint8_t EQ_PAT[8] = {3, 7, 11, 15, 16, 12, 8, 4}; -static char s_paths[MAX_TRACKS][PATH_LEN]; -static char s_names[MAX_TRACKS][NAME_LEN]; +EXT_RAM_BSS_ATTR static char s_paths[MAX_TRACKS][PATH_LEN]; +EXT_RAM_BSS_ATTR static char s_names[MAX_TRACKS][NAME_LEN]; static int s_count = 0; static int s_sel = 0; static bool s_resume = false; static bool s_empty = false; -static bool is_wav(const char *name) { +static bool is_playable(const char *name) { const char *dot = strrchr(name, '.'); - return dot != NULL && strcasecmp(dot, ".wav") == 0; + return dot != NULL && (strcasecmp(dot, ".wav") == 0 || strcasecmp(dot, ".mp3") == 0); +} + +static bool name_is_mp3(const char *name) { + const char *dot = strrchr(name, '.'); + return dot != NULL && strcasecmp(dot, ".mp3") == 0; } static void scan_dir(const char *dir, int depth) { @@ -115,7 +124,7 @@ static void scan_dir(const char *dir, int depth) { strlcat(full, dn, sizeof(full)); if (ent->d_type == DT_DIR) { scan_dir(full, depth + 1); - } else if (is_wav(dn)) { + } else if (is_playable(dn)) { strncpy(s_paths[s_count], full, PATH_LEN - 1); s_paths[s_count][PATH_LEN - 1] = '\0'; strncpy(s_names[s_count], dn, NAME_LEN - 1); @@ -129,6 +138,7 @@ static void scan_dir(const char *dir, int depth) { static void scan_wavs(void) { s_count = 0; scan_dir(SDCARD_ROOT, 0); + scan_dir(ASSETS_ROOT, 0); } int ui_wav_library_count(void) { @@ -152,6 +162,31 @@ void ui_wav_library_set_selected(int i) { s_sel = i; } +bool ui_wav_library_is_mp3(int i) { + if (i < 0 || i >= s_count) + return false; + return name_is_mp3(s_paths[i]); +} + +void ui_player_play_index(int i, int return_screen) { + if (s_count <= 0) + return; + i = ((i % s_count) + s_count) % s_count; + s_sel = i; + s_resume = true; + if (name_is_mp3(s_paths[i])) { + ui_mp3_player_set_path(s_paths[i]); + ui_mp3_player_set_index(i); + ui_mp3_player_set_return(return_screen); + ui_switch_screen(SCREEN_MP3_PLAYER); + } else { + ui_wav_player_set_path(s_paths[i]); + ui_wav_player_set_index(i); + ui_wav_player_set_return(return_screen); + ui_switch_screen(SCREEN_WAV_PLAYER); + } +} + static void track_duration(int i, char *out, size_t n) { int total = DUR_BASE_S + i * DUR_STEP_S; snprintf(out, n, "%d:%02d", total / 60, total % 60); @@ -201,7 +236,7 @@ static void build_empty_card(void) { lv_obj_t *sub = lv_label_create(card); lv_label_set_text(sub, EMPTY_SUB); lv_obj_set_style_text_font(sub, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(sub, lv_color_hex(DIM_COLOR), 0); + lv_obj_set_style_text_color(sub, current_theme.text_secondary, 0); } static void split_name(const char *full, char *base, size_t bn, char *ext, size_t en) { @@ -274,8 +309,8 @@ static void style_row(int i, bool sel) { lv_obj_set_style_border_color(row, current_theme.border_inactive, 0); lv_obj_set_style_shadow_width(row, 0, 0); lv_obj_set_style_shadow_opa(row, LV_OPA_TRANSP, 0); - lv_obj_set_style_text_color(s_row_name[i], lv_color_hex(DIM_COLOR), 0); - lv_obj_set_style_text_color(s_row_val[i], lv_color_hex(DIM_COLOR), 0); + lv_obj_set_style_text_color(s_row_name[i], current_theme.text_secondary, 0); + lv_obj_set_style_text_color(s_row_val[i], current_theme.text_secondary, 0); } } @@ -293,7 +328,7 @@ static void build_list(void) { lv_obj_t *cont = lv_obj_create(s_screen); s_list = cont; - lv_obj_set_size(cont, lv_pct(100), LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); + lv_obj_set_size(cont, lv_pct(100), ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); lv_obj_align(cont, LV_ALIGN_TOP_MID, 0, UI_CHROME_HEADER_H); lv_obj_set_style_bg_opa(cont, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(cont, 0, 0); @@ -401,7 +436,7 @@ static void build_list(void) { lv_obj_t *extl = lv_label_create(col); lv_label_set_text(extl, ext); lv_obj_set_style_text_font(extl, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(extl, lv_color_hex(DIM_COLOR), 0); + lv_obj_set_style_text_color(extl, current_theme.text_secondary, 0); char dur[DUR_BUF]; track_duration(i, dur, sizeof(dur)); @@ -418,16 +453,10 @@ static void build_list(void) { } static void play_selected(void) { - if (s_count <= 0) - return; - if (s_sel < 0 || s_sel >= s_count) + if (s_count <= 0 || s_sel < 0 || s_sel >= s_count) return; - s_resume = true; ui_feedback(UI_FB_SELECT); - ui_wav_player_set_path(s_paths[s_sel]); - ui_wav_player_set_index(s_sel); - ui_wav_player_set_return(SCREEN_PLAYER); - ui_switch_screen(SCREEN_WAV_PLAYER); + ui_player_play_index(s_sel, SCREEN_PLAYER); } static void wav_library_input(const input_event_t *ev, void *ctx) { diff --git a/firmware_p4/components/Applications/ui/screens/audio/wav_player_ui.c b/firmware_p4/components/Applications/ui/screens/audio/wav_player_ui.c index 275166a44..1240722d5 100644 --- a/firmware_p4/components/Applications/ui/screens/audio/wav_player_ui.c +++ b/firmware_p4/components/Applications/ui/screens/audio/wav_player_ui.c @@ -27,9 +27,12 @@ #include "st7789.h" #include "audio_i2s.h" +#include "tos_config.h" +#include "tos_storage_paths.h" #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #include "wav_library_ui.h" @@ -92,6 +95,7 @@ static volatile int s_i_rate = 0; static volatile int s_i_ch = 0; static volatile int s_i_bits = 0; static int s_vol = VOL_DEFAULT; +static bool s_vol_dirty = false; static uint32_t rd_u32(const uint8_t *p) { return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); @@ -264,7 +268,7 @@ static void player_task(void *arg) { s_task = NULL; if (s_exit_req) { s_exit_req = false; - lv_async_call(exit_to_files_cb, NULL); + ui_async_call(exit_to_files_cb, NULL); } vTaskDelete(NULL); } @@ -338,27 +342,6 @@ static void refresh_track_labels(void) { } } -static void play_index(int i) { - if (s_index >= 0) { - int n = ui_wav_library_count(); - if (n > 0) { - if (i < 0) - i = n - 1; - if (i >= n) - i = 0; - s_index = i; - const char *p = ui_wav_library_path(i); - if (p != NULL) { - strncpy(s_path, p, sizeof(s_path) - 1); - s_path[sizeof(s_path) - 1] = '\0'; - } - ui_wav_library_set_selected(i); - } - } - refresh_track_labels(); - start_playback(); -} - static void go_relative(int dir) { int n = pl_count(); if (n <= 1 || s_index < 0) { @@ -366,7 +349,7 @@ static void go_relative(int dir) { start_playback(); return; } - play_index((s_index + dir + n) % n); + ui_player_play_index(s_index + dir, s_return); } static void fmt_time(char *out, size_t n, int sec) { @@ -432,15 +415,8 @@ static void wav_player_input(const input_event_t *ev, void *ctx) { switch (ev->button) { case INPUT_BTN_BACK: - if (press) { - s_pending_play = false; - if (s_task_run) { - s_stop_req = true; - s_exit_req = true; - } else { - ui_switch_screen(s_return); - } - } + if (press) + ui_switch_screen(s_return); break; case INPUT_BTN_OK: if (press) { @@ -480,6 +456,7 @@ static void wav_player_input(const input_event_t *ev, void *ctx) { case INPUT_BTN_DOWN: if (nav) { s_vol = (s_vol > VOL_STEP) ? s_vol - VOL_STEP : 0; + s_vol_dirty = true; audio_i2s_set_volume((uint8_t)s_vol); ui_feedback(UI_FB_NAV); } @@ -487,6 +464,7 @@ static void wav_player_input(const input_event_t *ev, void *ctx) { case INPUT_BTN_UP: if (nav) { s_vol = (s_vol + VOL_STEP < 100) ? s_vol + VOL_STEP : 100; + s_vol_dirty = true; audio_i2s_set_volume((uint8_t)s_vol); ui_feedback(UI_FB_NAV); } @@ -540,6 +518,20 @@ void ui_wav_player_set_index(int index) { s_index = index; } +void ui_wav_player_stop(void) { + s_pending_play = false; + if (s_task_run) { + s_stop_req = true; + for (int i = 0; i < 80 && s_task_run; i++) + vTaskDelay(pdMS_TO_TICKS(10)); + } + if (s_vol_dirty) { + g_config_system.volume = s_vol; + tos_config_save(TOS_PATH_CONFIG_SYSTEM, "system"); + s_vol_dirty = false; + } +} + void ui_wav_player_open(void) { if (s_screen != NULL) { lv_obj_del(s_screen); @@ -568,7 +560,7 @@ void ui_wav_player_open(void) { lv_obj_t *body = lv_obj_create(s_screen); lv_obj_remove_style_all(body); - lv_obj_set_size(body, 216, LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); + lv_obj_set_size(body, 216, ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); lv_obj_align(body, LV_ALIGN_TOP_MID, 0, UI_CHROME_HEADER_H); lv_obj_remove_flag(body, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_flex_flow(body, LV_FLEX_FLOW_COLUMN); @@ -699,6 +691,8 @@ void ui_wav_player_open(void) { refresh_track_labels(); + s_vol = g_config_system.volume; + s_vol_dirty = false; audio_i2s_set_volume((uint8_t)s_vol); start_playback(); diff --git a/firmware_p4/components/Applications/ui/screens/badusb/badusb_menu_ui.c b/firmware_p4/components/Applications/ui/screens/badusb/badusb_menu_ui.c index 671f25ed2..fc2d72eb0 100644 --- a/firmware_p4/components/Applications/ui/screens/badusb/badusb_menu_ui.c +++ b/firmware_p4/components/Applications/ui/screens/badusb/badusb_menu_ui.c @@ -40,6 +40,7 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_semantic.h" #include "ui_theme.h" #include "waves_ui.h" @@ -48,7 +49,6 @@ static const char *TAG = "BADUSB_UI"; #define TERM_GREEN 0x00E676 #define TERM_DIM_GREEN 0x1F7A52 #define DARK_PANEL_COLOR 0x05090A -#define SIG_GREEN 0x00E676 #define ERR_RED 0xFF5252 #define WARN_AMBER 0xFFB300 @@ -135,12 +135,13 @@ static const char *TAG = "BADUSB_UI"; #define PAY_BODY_BUF_LEN 200 #define PREVIEW_MAX_LINES 4 -#define BADUSB_SCRIPT_DIR TOS_PATH_BADUSB -#define BADUSB_ASSET_DIR FLASH_STORAGE_BADUSB -#define ASSETS_PREFIX_LEN (sizeof(FLASH_MOUNT "/") - 1) -#define MAX_PAYLOADS 24 -#define PL_PATH_LEN 192 -#define PL_NAME_LEN 56 +#define BADUSB_SCRIPT_DIR TOS_PATH_BADUSB +#define BADUSB_ASSET_DIR FLASH_STORAGE_BADUSB +#define BADUSB_SCAN_MAX_DEPTH 3 +#define ASSETS_PREFIX_LEN (sizeof(FLASH_MOUNT "/") - 1) +#define MAX_PAYLOADS 24 +#define PL_PATH_LEN 192 +#define PL_NAME_LEN 56 #define POLL_MS 80 @@ -212,7 +213,7 @@ static view_t s_view = VIEW_LIST; static int s_payload_sel = 0; static int s_layout_active = 0; -static char s_pl_path[MAX_PAYLOADS][PL_PATH_LEN]; +EXT_RAM_BSS_ATTR static char s_pl_path[MAX_PAYLOADS][PL_PATH_LEN]; static char s_pl_name[MAX_PAYLOADS][PL_NAME_LEN]; static bool s_pl_is_asset[MAX_PAYLOADS]; static int s_pl_count = 0; @@ -279,21 +280,31 @@ static bool is_ducky_script(const char *name) { strcasecmp(dot, ".duck") == 0 || strcasecmp(dot, ".ducky") == 0; } -static void scan_dir_into(const char *dir, bool is_asset) { +// Recurses into subfolders so payloads organised in /badusb// still +// show. Depth is bounded and subdirs are visited while their parent DIR is open, +// so at most BADUSB_SCAN_MAX_DEPTH handles are held (under VFS_MAX_FILES). +static void scan_dir_into_depth(const char *dir, bool is_asset, int depth) { DIR *d = opendir(dir); if (d == NULL) { - ESP_LOGW(TAG, "No script dir: %s", dir); + if (depth == 0) + ESP_LOGW(TAG, "No script dir: %s", dir); return; } struct dirent *ent; while ((ent = readdir(d)) != NULL && s_pl_count < MAX_PAYLOADS) { if (ent->d_name[0] == '.') continue; - if (ent->d_type == DT_DIR) + if (strlen(dir) + 1 + strlen(ent->d_name) >= PL_PATH_LEN) continue; - if (!is_ducky_script(ent->d_name)) + if (ent->d_type == DT_DIR) { + if (depth + 1 < BADUSB_SCAN_MAX_DEPTH) { + char sub[PL_PATH_LEN]; + snprintf(sub, sizeof(sub), "%s/%s", dir, ent->d_name); + scan_dir_into_depth(sub, is_asset, depth + 1); + } continue; - if (strlen(dir) + 1 + strlen(ent->d_name) >= PL_PATH_LEN) + } + if (!is_ducky_script(ent->d_name)) continue; strlcpy(s_pl_path[s_pl_count], dir, PL_PATH_LEN); strlcat(s_pl_path[s_pl_count], "/", PL_PATH_LEN); @@ -305,6 +316,10 @@ static void scan_dir_into(const char *dir, bool is_asset) { closedir(d); } +static void scan_dir_into(const char *dir, bool is_asset) { + scan_dir_into_depth(dir, is_asset, 0); +} + static void scan_payloads(void) { s_pl_count = 0; scan_dir_into(BADUSB_SCRIPT_DIR, false); @@ -899,7 +914,7 @@ static void build_layout(void) { for (int i = 0; i < LAYOUT_COUNT; i++) { menu_component_add_item(&s_menu, "/assets/icons/keyboard.bin", LAYOUTS[i].label); if (i == s_layout_active) - menu_component_set_item_label_color(&s_menu, i, lv_color_hex(SIG_GREEN)); + menu_component_set_item_label_color(&s_menu, i, lv_color_hex(UI_COL_SUCCESS)); } menu_component_select(&s_menu, s_layout_active); fade_in(s_menu.items_cont, FADE_MS); @@ -916,7 +931,7 @@ static void status_row(lv_obj_t *panel, int index, const badusb_status_row_t *ro lv_obj_t *val = lv_label_create(panel); lv_label_set_text(val, row->value); lv_obj_set_style_text_color( - val, row->is_accent ? lv_color_hex(SIG_GREEN) : current_theme.text_main, 0); + val, row->is_accent ? lv_color_hex(UI_COL_SUCCESS) : current_theme.text_main, 0); lv_obj_set_style_text_font(val, &lv_font_montserrat_12, 0); lv_obj_align(val, LV_ALIGN_TOP_RIGHT, -INFO_LABEL_X, INFO_FIRST_ROW_Y + index * INFO_ROW_GAP); } @@ -1121,7 +1136,8 @@ static void input_view_layout(const input_event_t *ev, bool press, bool nav) { if (sel >= 0 && sel < LAYOUT_COUNT && sel != s_layout_active) { menu_component_set_item_label_color(&s_menu, s_layout_active, current_theme.text_main); s_layout_active = sel; - menu_component_set_item_label_color(&s_menu, s_layout_active, lv_color_hex(SIG_GREEN)); + menu_component_set_item_label_color( + &s_menu, s_layout_active, lv_color_hex(UI_COL_SUCCESS)); ESP_LOGI(TAG, "layout set: %s", LAYOUTS[s_layout_active].label); } } diff --git a/firmware_p4/components/Applications/ui/screens/badusb/usb_mouse_ui.c b/firmware_p4/components/Applications/ui/screens/badusb/usb_mouse_ui.c index e88a7e520..80af76448 100644 --- a/firmware_p4/components/Applications/ui/screens/badusb/usb_mouse_ui.c +++ b/firmware_p4/components/Applications/ui/screens/badusb/usb_mouse_ui.c @@ -20,6 +20,7 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_semantic.h" #include "ui_theme.h" #define MOVE_TICK_MS 50 @@ -34,9 +35,7 @@ #define MX 8 #define CONTENT_W (240 - 2 * MX) -#define COL_DIM 0x8A8594 -#define COL_SUCCESS 0x00E676 -#define COL_ACC2 0xB89AFF +#define COL_ACC2 0xB89AFF #define STATUS_Y 48 #define STATUS_H 18 @@ -103,7 +102,7 @@ static void refresh_chip(void) { } if (s_chip_lbl) lv_obj_set_style_text_color( - s_chip_lbl, s_jiggle ? current_theme.screen_base : lv_color_hex(COL_DIM), 0); + s_chip_lbl, s_jiggle ? current_theme.screen_base : current_theme.text_secondary, 0); } static void build_status(void) { @@ -113,7 +112,7 @@ static void build_status(void) { lv_obj_align(dot, LV_ALIGN_TOP_LEFT, MX, STATUS_Y + 5); lv_obj_set_style_radius(dot, LV_RADIUS_CIRCLE, 0); lv_obj_set_style_border_width(dot, 0, 0); - lv_obj_set_style_bg_color(dot, lv_color_hex(COL_SUCCESS), 0); + lv_obj_set_style_bg_color(dot, lv_color_hex(UI_COL_SUCCESS), 0); lv_obj_set_style_bg_opa(dot, LV_OPA_COVER, 0); lv_obj_t *lbl = lv_label_create(s_screen); @@ -176,7 +175,7 @@ static void build_trackpad(void) { lv_obj_t *tag = lv_label_create(pad); lv_label_set_text(tag, "trackpad"); lv_obj_set_style_text_font(tag, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(tag, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(tag, current_theme.text_secondary, 0); lv_obj_align(tag, LV_ALIGN_BOTTOM_LEFT, 6, -4); } @@ -190,7 +189,7 @@ static void build_rail(void) { lv_obj_set_style_pad_hor(rail, 0, 0); lv_obj_set_style_bg_color(rail, current_theme.bg_primary, 0); lv_obj_set_style_bg_opa(rail, LV_OPA_COVER, 0); - lv_obj_set_style_border_color(rail, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_border_color(rail, current_theme.text_secondary, 0); lv_obj_set_style_border_opa(rail, LV_OPA_40, 0); lv_obj_set_style_border_width(rail, 1, 0); lv_obj_set_flex_flow(rail, LV_FLEX_FLOW_COLUMN); @@ -205,7 +204,7 @@ static void build_rail(void) { lv_obj_t *scr = lv_label_create(rail); lv_label_set_text(scr, "SCR"); lv_obj_set_style_text_font(scr, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(scr, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(scr, current_theme.text_secondary, 0); lv_obj_t *down = lv_label_create(rail); lv_label_set_text(down, LV_SYMBOL_DOWN); @@ -236,7 +235,7 @@ static void build_click(int x, const char *text, bool selected) { lv_label_set_text(lbl, text); lv_obj_set_style_text_font(lbl, &lv_font_montserrat_14, 0); lv_obj_set_style_text_color( - lbl, selected ? current_theme.border_accent : lv_color_hex(COL_DIM), 0); + lbl, selected ? current_theme.border_accent : current_theme.text_secondary, 0); lv_obj_center(lbl); } diff --git a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_beacon_ui.c b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_beacon_ui.c index fe21279c3..93bd8e362 100644 --- a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_beacon_ui.c +++ b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_beacon_ui.c @@ -22,18 +22,17 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #define SPAM_TICK_MS 120 #define BEACON_CYCLE_MS 400 #define BODY_W 240 -#define BODY_H 256 +#define BODY_H LV_MIN(256, ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) #define CARD_W 160 #define CARD_H 54 -#define COL_DIM 0x8A8594 - typedef struct { const char *kind; const char *uuid; @@ -119,7 +118,7 @@ void ui_beacon_spam_open(void) { ui_chrome_footer(s_screen, "BACK Stop"); lv_obj_t *body = lv_obj_create(s_screen); - lv_obj_set_size(body, BODY_W, BODY_H); + lv_obj_set_size(body, lv_pct(100), ui_screen_h() - UI_CHROME_HEADER_H); lv_obj_align(body, LV_ALIGN_TOP_LEFT, 0, UI_CHROME_HEADER_H); lv_obj_remove_flag(body, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_style_bg_opa(body, LV_OPA_TRANSP, 0); @@ -136,7 +135,7 @@ void ui_beacon_spam_open(void) { lv_obj_t *type = lv_label_create(body); lv_label_set_text(type, "Type: Mixed"); - lv_obj_set_style_text_color(type, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(type, current_theme.text_secondary, 0); lv_obj_set_style_text_font(type, &lv_font_montserrat_12, 0); lv_obj_set_style_text_align(type, LV_TEXT_ALIGN_CENTER, 0); lv_obj_align(type, LV_ALIGN_TOP_MID, 0, 78); @@ -153,10 +152,13 @@ void ui_beacon_spam_open(void) { s_uuid_label = lv_label_create(body); lv_label_set_text_fmt(s_uuid_label, "%s %s", BEACONS[0].kind, BEACONS[0].uuid); - lv_obj_set_style_text_color(s_uuid_label, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s_uuid_label, current_theme.text_secondary, 0); lv_obj_set_style_text_font(s_uuid_label, &lv_font_montserrat_12, 0); lv_obj_set_style_text_align(s_uuid_label, LV_TEXT_ALIGN_CENTER, 0); - lv_obj_align(s_uuid_label, LV_ALIGN_TOP_MID, 0, 166); + lv_obj_align(s_uuid_label, + LV_ALIGN_TOP_MID, + 0, + LV_MIN(166, ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H - 14)); fade_in(header, 200); fade_in(status, 200); diff --git a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_companion_ui.c b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_companion_ui.c index 4f34daa43..5211a8dee 100644 --- a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_companion_ui.c +++ b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_companion_ui.c @@ -24,6 +24,8 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" +#include "ui_semantic.h" #include "ui_theme.h" #include "waves_ui.h" @@ -32,9 +34,6 @@ static const char *TAG = "BLE_COMPANION_UI"; #define CONN_POLL_MS 500 #define COMPANION_ICON "/assets/icons/app_shortcut.bin" -#define SIG_GREEN 0x00E676 -#define COL_DIM 0x8A8594 - static lv_obj_t *s_screen = NULL; static lv_obj_t *s_body = NULL; static lv_obj_t *s_footer = NULL; @@ -93,7 +92,7 @@ static void pop_in(lv_obj_t *obj, int target_px, uint32_t ms) { static lv_obj_t *make_body(lv_obj_t *parent) { lv_obj_t *b = lv_obj_create(parent); lv_obj_remove_flag(b, LV_OBJ_FLAG_SCROLLABLE); - lv_obj_set_size(b, LCD_H_RES, LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); + lv_obj_set_size(b, lv_pct(100), ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); lv_obj_align(b, LV_ALIGN_TOP_LEFT, 0, UI_CHROME_HEADER_H); lv_obj_set_style_bg_opa(b, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(b, 0, 0); @@ -113,7 +112,7 @@ static void build_pairing(void) { lv_obj_t *caption = lv_label_create(s_body); lv_label_set_text(caption, "Pairing key (PSK):"); - lv_obj_set_style_text_color(caption, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(caption, current_theme.text_secondary, 0); lv_obj_set_style_text_font(caption, &lv_font_montserrat_12, 0); lv_obj_align(caption, LV_ALIGN_CENTER, 0, 78); @@ -154,7 +153,7 @@ static void show_success(void) { lv_obj_t *seal = lv_obj_create(seal_slot); lv_obj_remove_flag(seal, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_style_radius(seal, LV_RADIUS_CIRCLE, 0); - lv_obj_set_style_bg_color(seal, lv_color_hex(SIG_GREEN), 0); + lv_obj_set_style_bg_color(seal, lv_color_hex(UI_COL_SUCCESS), 0); lv_obj_set_style_bg_opa(seal, LV_OPA_COVER, 0); lv_obj_set_style_border_width(seal, 0, 0); lv_obj_set_style_pad_all(seal, 0, 0); @@ -169,7 +168,7 @@ static void show_success(void) { lv_obj_t *status = lv_label_create(s_body); lv_label_set_text(status, "Companion linked!"); - lv_obj_set_style_text_color(status, lv_color_hex(SIG_GREEN), 0); + lv_obj_set_style_text_color(status, lv_color_hex(UI_COL_SUCCESS), 0); lv_obj_set_style_text_font(status, &lv_font_montserrat_14, 0); lv_obj_set_style_text_align(status, LV_TEXT_ALIGN_CENTER, 0); lv_obj_align(status, LV_ALIGN_CENTER, 0, 42); @@ -188,7 +187,7 @@ static void show_success(void) { lv_obj_t *ver = lv_label_create(badge); lv_label_set_text(ver, "v1.2 - Companion v1.0"); - lv_obj_set_style_text_color(ver, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(ver, current_theme.text_secondary, 0); lv_obj_set_style_text_font(ver, &lv_font_montserrat_12, 0); pop_in(seal, 30, 360); diff --git a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_exposure_ui.c b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_exposure_ui.c index 4a71dbb3e..e0bc67e2d 100644 --- a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_exposure_ui.c +++ b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_exposure_ui.c @@ -25,6 +25,7 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" static const char *TAG = "BLE_EXPOSURE_UI"; @@ -36,16 +37,13 @@ static const char *TAG = "BLE_EXPOSURE_UI"; #define ROW_LEN 36 #define LIST_BUF_LEN (MAX_ADV * ROW_LEN) -#define COL_DIM 0x8A8594 -#define COL_SUCCESS 0x00E676 - #define EXPO_ICON "/assets/icons/broadcast_on_personal.bin" #define COUNT_CARD_W 190 #define COUNT_CARD_H 34 #define COUNT_CARD_Y 50 #define LIST_PANEL_W 224 -#define LIST_PANEL_H 190 +#define LIST_PANEL_H LV_MIN(190, ui_screen_h() - LIST_PANEL_Y - UI_CHROME_FOOTER_H) #define LIST_PANEL_Y 94 #define LIST_PAD 8 diff --git a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_flood_ui.c b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_flood_ui.c index 14e094895..deaca7656 100644 --- a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_flood_ui.c +++ b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_flood_ui.c @@ -30,6 +30,7 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" static const char *TAG = "BLE_FLOOD_UI"; @@ -42,8 +43,6 @@ static const char *TAG = "BLE_FLOOD_UI"; #define BLE_ADDR_LEN 6 -#define COL_DIM 0x8A8594 - #define CARD_W 172 #define CARD_H 54 @@ -150,7 +149,7 @@ void ui_ble_flood_open(void) { lv_obj_t *body = lv_obj_create(s_screen); lv_obj_remove_flag(body, LV_OBJ_FLAG_SCROLLABLE); - lv_obj_set_size(body, LCD_H_RES, LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); + lv_obj_set_size(body, lv_pct(100), ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); lv_obj_align(body, LV_ALIGN_TOP_LEFT, 0, UI_CHROME_HEADER_H); lv_obj_set_style_bg_opa(body, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(body, 0, 0); @@ -164,7 +163,7 @@ void ui_ble_flood_open(void) { s_target_label = lv_label_create(body); lv_label_set_text(s_target_label, ""); - lv_obj_set_style_text_color(s_target_label, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s_target_label, current_theme.text_secondary, 0); lv_obj_set_style_text_font(s_target_label, &lv_font_montserrat_12, 0); lv_obj_align(s_target_label, LV_ALIGN_TOP_MID, 0, 38); @@ -185,7 +184,7 @@ void ui_ble_flood_open(void) { s_rate_label = lv_label_create(body); lv_label_set_text(s_rate_label, ""); - lv_obj_set_style_text_color(s_rate_label, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s_rate_label, current_theme.text_secondary, 0); lv_obj_set_style_text_font(s_rate_label, &lv_font_montserrat_12, 0); lv_obj_align(s_rate_label, LV_ALIGN_TOP_MID, 0, 178); diff --git a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_keyboard_ui.c b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_keyboard_ui.c index 03ffa2214..4254ae67d 100644 --- a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_keyboard_ui.c +++ b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_keyboard_ui.c @@ -26,6 +26,8 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" +#include "ui_semantic.h" #include "ui_theme.h" #include "waves_ui.h" @@ -41,9 +43,6 @@ static const char *TAG = "BLE_KEYBOARD_UI"; #define KB_ICON "/assets/icons/keyboard.bin" -#define SIG_GREEN 0x00E676 -#define COL_DIM 0x8A8594 - #define CONSOLE_W 212 #define CONSOLE_H 150 #define TYPED_MAX 160 @@ -93,7 +92,7 @@ static lv_obj_t *lit_panel(lv_obj_t *parent, int w, int h) { static lv_obj_t *make_body(lv_obj_t *parent) { lv_obj_t *b = lv_obj_create(parent); lv_obj_remove_flag(b, LV_OBJ_FLAG_SCROLLABLE); - lv_obj_set_size(b, LCD_H_RES, LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); + lv_obj_set_size(b, lv_pct(100), ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); lv_obj_align(b, LV_ALIGN_TOP_LEFT, 0, UI_CHROME_HEADER_H); lv_obj_set_style_bg_opa(b, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(b, 0, 0); @@ -113,7 +112,7 @@ static void build_pairing(void) { lv_obj_t *hint = lv_label_create(s_body); lv_label_set_text(hint, "Accept on Target-PC"); - lv_obj_set_style_text_color(hint, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(hint, current_theme.text_secondary, 0); lv_obj_set_style_text_font(hint, &lv_font_montserrat_12, 0); lv_obj_set_style_text_align(hint, LV_TEXT_ALIGN_CENTER, 0); lv_obj_align(hint, LV_ALIGN_CENTER, 0, 82); @@ -140,7 +139,7 @@ static void build_console(void) { lv_obj_t *status = lv_label_create(s_body); lv_label_set_text(status, LV_SYMBOL_OK " Connected"); - lv_obj_set_style_text_color(status, lv_color_hex(SIG_GREEN), 0); + lv_obj_set_style_text_color(status, lv_color_hex(UI_COL_SUCCESS), 0); lv_obj_set_style_text_font(status, &lv_font_montserrat_14, 0); lv_obj_set_style_text_align(status, LV_TEXT_ALIGN_CENTER, 0); lv_obj_align(status, LV_ALIGN_TOP_MID, 0, 10); @@ -151,7 +150,7 @@ static void build_console(void) { s_term_label = lv_label_create(card); lv_label_set_long_mode(s_term_label, LV_LABEL_LONG_WRAP); lv_obj_set_width(s_term_label, CONSOLE_W - 20); - lv_obj_set_style_text_color(s_term_label, lv_color_hex(SIG_GREEN), 0); + lv_obj_set_style_text_color(s_term_label, lv_color_hex(UI_COL_SUCCESS), 0); lv_obj_set_style_text_font(s_term_label, &lv_font_montserrat_14, 0); lv_obj_align(s_term_label, LV_ALIGN_TOP_LEFT, 0, 0); update_terminal(); diff --git a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_mouse_ui.c b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_mouse_ui.c index dde30f53e..725f46d66 100644 --- a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_mouse_ui.c +++ b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_mouse_ui.c @@ -24,6 +24,8 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" +#include "ui_semantic.h" #include "ui_theme.h" #include "waves_ui.h" @@ -34,7 +36,6 @@ static const char *TAG = "BLE_MOUSE_UI"; #define FOUND_DWELL_US 600000 #define DOT_CYCLE_US 350000 -#define SIG_GREEN 0x00E676 #define COL_RAISE 0x170A28 static void opa_cb(void *var, int32_t v) { @@ -85,7 +86,7 @@ static void pair_reveal(void) { s_pair_waves = waves_create(s_pair_screen, LV_ALIGN_CENTER, 0, -24, LV_SYMBOL_OK, NULL); lv_label_set_text(s_pair_status, "Connected!"); - lv_obj_set_style_text_color(s_pair_status, lv_color_hex(SIG_GREEN), 0); + lv_obj_set_style_text_color(s_pair_status, lv_color_hex(UI_COL_SUCCESS), 0); ui_feedback(UI_FB_EMULATE); } @@ -294,7 +295,7 @@ void ui_ble_mouse_open(void) { lv_obj_t *body = lv_obj_create(s_mouse_screen); lv_obj_remove_flag(body, LV_OBJ_FLAG_SCROLLABLE); - lv_obj_set_size(body, LCD_H_RES, LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); + lv_obj_set_size(body, lv_pct(100), ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); lv_obj_align(body, LV_ALIGN_TOP_LEFT, 0, UI_CHROME_HEADER_H); lv_obj_set_style_bg_opa(body, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(body, 0, 0); diff --git a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_radio_ui.c b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_radio_ui.c index 87d941dba..32f9794a9 100644 --- a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_radio_ui.c +++ b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_radio_ui.c @@ -25,6 +25,7 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #define RADIO_ICON "/assets/icons/bluetooth.bin" @@ -32,15 +33,14 @@ #define BLE_ADDR_LEN 6 #define MX 10 -#define CONTENT_W (LCD_H_RES - 2 * MX) +#define CONTENT_W (ui_screen_w() - 2 * MX) #define CARD_Y 50 #define CARD_H 58 -#define ROWS_Y 124 +#define ROWS_Y LV_MIN(124, ui_screen_h() - UI_CHROME_FOOTER_H - (R_COUNT - 1) * ROW_STEP - ROW_H) #define ROW_H 42 #define ROW_GAP 8 #define ROW_STEP (ROW_H + ROW_GAP) -#define COL_DIM 0x8A8594 #define COL_RAISE 0x170A28 enum { @@ -104,7 +104,7 @@ static void update_values(void) { static void refresh_selection(void) { const lv_color_t accent = current_theme.border_accent; - const lv_color_t dim = lv_color_hex(COL_DIM); + const lv_color_t dim = current_theme.text_secondary; for (int i = 0; i < R_COUNT; i++) { bool sel = (i == s_sel); lv_obj_set_style_border_color(s_row[i], sel ? accent : current_theme.border_inactive, 0); diff --git a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_scan_ui.c b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_scan_ui.c index 9b908888c..9aa4bcc24 100644 --- a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_scan_ui.c +++ b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_scan_ui.c @@ -32,6 +32,7 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #include "waves_ui.h" @@ -40,7 +41,6 @@ static const char *TAG = "BLE_SCAN_UI"; #define STATUS_TICK_MS 50 #define DOT_CYCLE_MS 350 #define SCAN_RESULT_COLOR_HEX 0x00E676 -#define COL_DIM 0x8A8594 #define BLE_MAX_DEVS 12 #define SCAN_POLL_MS 150 #define SCAN_TIMEOUT_MS 15000 @@ -51,11 +51,11 @@ static const char *TAG = "BLE_SCAN_UI"; #define HERO_CARD_W 200 #define HERO_CARD_H 108 -#define RADAR_BOX 190 -#define RADAR_TOP_Y (UI_CHROME_HEADER_H + 4) -#define RADAR_RING_1 28 -#define RADAR_RING_2 55 -#define RADAR_RING_3 82 +#define RADAR_BOX LV_MIN(190, ui_screen_h() - RADAR_TOP_Y - (CHIP_H + 10) - UI_CHROME_FOOTER_H) +#define RADAR_TOP_Y (UI_CHROME_HEADER_H + 4) +#define RADAR_RING_1 28 +#define RADAR_RING_2 55 +#define RADAR_RING_3 82 #define RADAR_BLIP_RMIN 14 #define RADAR_BLIP_RMAX 72 #define RADAR_BLIP_SZ 10 @@ -227,7 +227,7 @@ static void build_empty_hero(const char *title, const char *sub) { lv_obj_t *s = lv_label_create(card); lv_label_set_text(s, sub); lv_obj_set_style_text_font(s, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(s, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s, current_theme.text_secondary, 0); lv_obj_set_style_text_align(s, LV_TEXT_ALIGN_CENTER, 0); ui_chrome_footer(s_screen, "RIGHT Rescan BACK Back"); @@ -313,7 +313,7 @@ static void build_radar(void) { s_chip_meta = lv_label_create(s_chip); lv_obj_set_style_text_font(s_chip_meta, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(s_chip_meta, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s_chip_meta, current_theme.text_secondary, 0); lv_obj_align(s_chip_meta, LV_ALIGN_BOTTOM_LEFT, 0, 0); s_chip_rssi = lv_label_create(s_chip); @@ -433,7 +433,7 @@ static void ble_scan_task(void *arg) { s_dev_count = 0; s_scan_state = SCAN_FAIL; s_scanning = false; - lv_async_call(scan_done_cb, NULL); + ui_async_call(scan_done_cb, NULL); vTaskDelete(NULL); return; } @@ -455,7 +455,7 @@ static void ble_scan_task(void *arg) { } s_scanning = false; - lv_async_call(scan_done_cb, NULL); + ui_async_call(scan_done_cb, NULL); vTaskDelete(NULL); } diff --git a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_skimmer_ui.c b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_skimmer_ui.c index 9f38f562a..59b31e8e3 100644 --- a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_skimmer_ui.c +++ b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_skimmer_ui.c @@ -25,6 +25,7 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #include "waves_ui.h" @@ -37,13 +38,12 @@ static const char *TAG = "BLE_SKIMMER_UI"; #define DETAIL_LEN 48 #define SUSPECTS_SHOWN 3 -#define COL_DIM 0x8A8594 #define COL_THREAT 0xFFB300 #define SKIM_ICON "/assets/icons/warning.bin" #define BODY_W 240 -#define BODY_H 256 +#define BODY_H LV_MIN(256, ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) #define BODY_PAD 10 #define BODY_GAP 8 #define CARD_W 220 @@ -74,7 +74,7 @@ static void stop_detector(void) { static lv_obj_t *body_container(void) { lv_obj_t *body = lv_obj_create(s_screen); - lv_obj_set_size(body, BODY_W, BODY_H); + lv_obj_set_size(body, lv_pct(100), ui_screen_h() - UI_CHROME_HEADER_H); lv_obj_align(body, LV_ALIGN_TOP_MID, 0, UI_CHROME_HEADER_H); lv_obj_remove_flag(body, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_style_bg_opa(body, LV_OPA_TRANSP, 0); @@ -119,7 +119,7 @@ static void make_suspect_card(lv_obj_t *parent, const char *name, const char *ma char buf[DETAIL_LEN]; snprintf(buf, sizeof(buf), "%s %d dBm", mac, rssi); lv_label_set_text(detail, buf); - lv_obj_set_style_text_color(detail, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(detail, current_theme.text_secondary, 0); lv_obj_set_style_text_font(detail, &lv_font_montserrat_12, 0); lv_obj_align(detail, LV_ALIGN_TOP_LEFT, 0, DETAIL_Y); } @@ -141,7 +141,7 @@ static void build_scanning_view(void) { lv_obj_t *sub = lv_label_create(s_screen); lv_label_set_text(sub, "Looking for HC / RNBT chips"); - lv_obj_set_style_text_color(sub, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(sub, current_theme.text_secondary, 0); lv_obj_set_style_text_font(sub, &lv_font_montserrat_12, 0); lv_obj_set_style_text_align(sub, LV_TEXT_ALIGN_CENTER, 0); lv_obj_align(sub, LV_ALIGN_CENTER, 0, SUB_Y_OFS); diff --git a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_sniffer_ui.c b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_sniffer_ui.c index bc1fc6336..979c68f31 100644 --- a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_sniffer_ui.c +++ b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_sniffer_ui.c @@ -26,6 +26,7 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" static const char *TAG = "BLE_SNIFFER_UI"; @@ -36,8 +37,6 @@ static const char *TAG = "BLE_SNIFFER_UI"; #define ROW_LEN 40 #define FEED_BUF_LEN (MAX_ROWS * ROW_LEN) -#define COL_DIM 0x8A8594 - #define SNIFF_ICON "/assets/icons/monitoring.bin" #define STATUS_Y 48 @@ -45,7 +44,7 @@ static const char *TAG = "BLE_SNIFFER_UI"; #define FRAMES_CARD_H 34 #define FRAMES_CARD_Y 70 #define HEX_PANEL_W 224 -#define HEX_PANEL_H 150 +#define HEX_PANEL_H LV_MIN(150, ui_screen_h() - HEX_PANEL_Y - UI_CHROME_FOOTER_H) #define HEX_PANEL_Y 112 #define HEX_PANEL_PAD 8 diff --git a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_spam_names_ui.c b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_spam_names_ui.c index bd77163ef..96789334a 100644 --- a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_spam_names_ui.c +++ b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_spam_names_ui.c @@ -121,7 +121,7 @@ static void on_kb_submit(const char *text, void *ud) { ui_feedback(UI_FB_WRITE); notify(NOTIFY_SAVED, "Name added"); } - lv_async_call(rebuild_async, NULL); + ui_async_call(rebuild_async, NULL); } static void on_delete_confirm(bool confirm) { @@ -134,7 +134,7 @@ static void on_delete_confirm(bool confirm) { notify(NOTIFY_INFO, "Name deleted"); } s_del_index = -1; - lv_async_call(rebuild_async, NULL); + ui_async_call(rebuild_async, NULL); } static void ble_spam_names_input(const input_event_t *ev, void *ctx) { diff --git a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_spam_ui.c b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_spam_ui.c index 4b7050658..9eb834ded 100644 --- a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_spam_ui.c +++ b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_spam_ui.c @@ -28,12 +28,12 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" static const char *TAG = "BLE_SPAM_UI"; #define RUN_TICK_MS 250 -#define COL_DIM 0x8A8594 #define SPAM_ICON "/assets/icons/broadcast_on_personal.bin" #define SPAM_GRID_PAD 8 @@ -116,7 +116,7 @@ static lv_obj_t *build_spam_card( lv_obj_t *d = lv_label_create(c); lv_label_set_text(d, effect); lv_obj_set_style_text_font(d, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(d, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(d, current_theme.text_secondary, 0); return c; } @@ -161,7 +161,7 @@ void ui_ble_spam_select_open(void) { lv_obj_t *grid = lv_obj_create(s_select_screen); lv_obj_remove_flag(grid, LV_OBJ_FLAG_SCROLLABLE); - lv_obj_set_size(grid, LCD_H_RES, LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); + lv_obj_set_size(grid, lv_pct(100), ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); lv_obj_align(grid, LV_ALIGN_TOP_MID, 0, UI_CHROME_HEADER_H); lv_obj_set_style_bg_opa(grid, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(grid, 0, 0); @@ -273,7 +273,7 @@ void ui_ble_spam_open(void) { lv_obj_t *body = lv_obj_create(s_run_screen); lv_obj_remove_flag(body, LV_OBJ_FLAG_SCROLLABLE); - lv_obj_set_size(body, LCD_H_RES, LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); + lv_obj_set_size(body, lv_pct(100), ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); lv_obj_align(body, LV_ALIGN_TOP_LEFT, 0, UI_CHROME_HEADER_H); lv_obj_set_style_bg_opa(body, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(body, 0, 0); @@ -288,7 +288,7 @@ void ui_ble_spam_open(void) { const canned_spam_type_t *mt = spam_get_attack_type(s_spam_mode); lv_obj_t *mode = lv_label_create(body); lv_label_set_text_fmt(mode, "Mode: %s", (mt != NULL && mt->name) ? mt->name : "?"); - lv_obj_set_style_text_color(mode, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(mode, current_theme.text_secondary, 0); lv_obj_set_style_text_font(mode, &lv_font_montserrat_12, 0); lv_obj_align(mode, LV_ALIGN_TOP_MID, 0, 34); @@ -306,7 +306,7 @@ void ui_ble_spam_open(void) { s_run_rate_label = lv_label_create(body); lv_label_set_text(s_run_rate_label, "Broadcasting"); - lv_obj_set_style_text_color(s_run_rate_label, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s_run_rate_label, current_theme.text_secondary, 0); lv_obj_set_style_text_font(s_run_rate_label, &lv_font_montserrat_12, 0); lv_obj_align(s_run_rate_label, LV_ALIGN_TOP_MID, 0, 180); diff --git a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_track_device_ui.c b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_track_device_ui.c index 3e528882f..06068d66b 100644 --- a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_track_device_ui.c +++ b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_track_device_ui.c @@ -26,6 +26,7 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" static const char *TAG = "BLE_TRACK_DEV"; @@ -39,7 +40,7 @@ static const char *TAG = "BLE_TRACK_DEV"; #define TRACK_ICON "/assets/icons/bluetooth_searching.bin" -#define ARC_SIZE 140 +#define ARC_SIZE LV_MIN(140, ui_screen_h() - (ARC_TOP_Y + 20) - UI_CHROME_FOOTER_H - 20) #define ARC_WIDTH 15 #define ARC_ROTATION 270 #define ARC_TOP_Y 50 @@ -52,7 +53,6 @@ static const char *TAG = "BLE_TRACK_DEV"; #define COL_COLD 0x2A6FDB #define COL_WARM 0xFFB300 #define COL_HOT 0x00E676 -#define COL_DIM 0x8A8594 typedef enum { PHASE_SCAN, PHASE_TRACK, PHASE_NOTGT } track_phase_t; @@ -118,7 +118,7 @@ static void apply_reading(int trend) { lv_obj_set_style_text_color(s_caption, lv_color_hex(COL_COLD), 0); } else { lv_label_set_text(s_caption, "Hold steady"); - lv_obj_set_style_text_color(s_caption, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s_caption, current_theme.text_secondary, 0); } } } @@ -179,12 +179,12 @@ void ui_ble_track_device_open(void) { lv_obj_t *tip = lv_label_create(s_screen); lv_label_set_text(tip, "Move around to home in"); lv_obj_set_style_text_font(tip, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(tip, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(tip, current_theme.text_secondary, 0); lv_obj_align(tip, LV_ALIGN_BOTTOM_MID, 0, -(UI_CHROME_FOOTER_H + 14)); if (s_caption != NULL) { lv_label_set_text(s_caption, "Finding devices..."); - lv_obj_set_style_text_color(s_caption, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s_caption, current_theme.text_secondary, 0); } lv_obj_fade_in(s_screen, 240, 0); diff --git a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_tracker_ui.c b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_tracker_ui.c index 36bb03ad0..fdf577bf3 100644 --- a/firmware_p4/components/Applications/ui/screens/bluetooth/ble_tracker_ui.c +++ b/firmware_p4/components/Applications/ui/screens/bluetooth/ble_tracker_ui.c @@ -25,6 +25,7 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #include "waves_ui.h" @@ -36,13 +37,12 @@ static const char *TAG = "BLE_TRACKER_UI"; #define DETAIL_LEN 48 #define TRACKERS_SHOWN 3 -#define COL_DIM 0x8A8594 #define COL_ALERT 0xFF5252 #define TRACK_ICON "/assets/icons/troubleshoot.bin" #define BODY_W 240 -#define BODY_H 256 +#define BODY_H LV_MIN(256, ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) #define BODY_PAD 10 #define BODY_GAP 8 #define CARD_W 220 @@ -80,7 +80,7 @@ static void clear_screen_children(void) { static lv_obj_t *body_container(void) { lv_obj_t *body = lv_obj_create(s_screen); - lv_obj_set_size(body, BODY_W, BODY_H); + lv_obj_set_size(body, lv_pct(100), ui_screen_h() - UI_CHROME_HEADER_H); lv_obj_align(body, LV_ALIGN_TOP_MID, 0, UI_CHROME_HEADER_H); lv_obj_remove_flag(body, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_style_bg_opa(body, LV_OPA_TRANSP, 0); @@ -125,7 +125,7 @@ static void make_tracker_card(lv_obj_t *parent, const char *type, const char *ma char buf[DETAIL_LEN]; snprintf(buf, sizeof(buf), "%s %d dBm", mac, rssi); lv_label_set_text(detail, buf); - lv_obj_set_style_text_color(detail, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(detail, current_theme.text_secondary, 0); lv_obj_set_style_text_font(detail, &lv_font_montserrat_12, 0); lv_obj_align(detail, LV_ALIGN_TOP_LEFT, 0, DETAIL_Y); } @@ -147,7 +147,7 @@ static void build_scanning_view(void) { lv_obj_t *sub = lv_label_create(s_screen); lv_label_set_text(sub, "Hold still while we listen"); - lv_obj_set_style_text_color(sub, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(sub, current_theme.text_secondary, 0); lv_obj_set_style_text_font(sub, &lv_font_montserrat_12, 0); lv_obj_set_style_text_align(sub, LV_TEXT_ALIGN_CENTER, 0); lv_obj_align(sub, LV_ALIGN_CENTER, 0, SUB_Y_OFS); diff --git a/firmware_p4/components/Applications/ui/screens/boot_report/boot_map_ui.c b/firmware_p4/components/Applications/ui/screens/boot_report/boot_map_ui.c index 13200c22c..5277c586d 100644 --- a/firmware_p4/components/Applications/ui/screens/boot_report/boot_map_ui.c +++ b/firmware_p4/components/Applications/ui/screens/boot_report/boot_map_ui.c @@ -19,13 +19,13 @@ #include "boot_report.h" #include "ui_manager.h" +#include "ui_semantic.h" #include "ui_theme.h" #define TITLE "BOOT MAP" #define ICON "/assets/icons/troubleshoot.bin" #define HINT "BACK exit" -#define OK_COLOR 0x00E676 #define FAIL_COLOR 0xFF5252 #define SKIP_COLOR 0x8A8594 @@ -54,7 +54,7 @@ static void add_row(lv_obj_t *list, const boot_stage_t *st) { uint32_t color; if (st->result == ESP_OK) { state = "OK"; - color = OK_COLOR; + color = UI_COL_SUCCESS; } else if (st->result == ESP_ERR_NOT_FOUND) { state = "skip"; color = SKIP_COLOR; diff --git a/firmware_p4/components/Applications/ui/screens/connect_wifi/connect_wifi_ui.c b/firmware_p4/components/Applications/ui/screens/connect_wifi/connect_wifi_ui.c index 4ca554a85..aeb9e925c 100644 --- a/firmware_p4/components/Applications/ui/screens/connect_wifi/connect_wifi_ui.c +++ b/firmware_p4/components/Applications/ui/screens/connect_wifi/connect_wifi_ui.c @@ -30,6 +30,7 @@ #include "msgbox_ui.h" #include "ui_chrome.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #include "waves_ui.h" @@ -52,9 +53,8 @@ static const char *TAG = "CONNECT_WIFI_UI"; #define COLOR_OPEN_HEX 0x00E676 #define COLOR_LOCK_HEX 0xF5B13D -#define COLOR_DIM_HEX 0x8A8594 -#define NET_BODY_H (LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) +#define NET_BODY_H (ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) #define NET_ROW_H 46 #define NET_ROW_GAP 6 #define NET_SIDE_PAD 8 @@ -159,7 +159,7 @@ static void wifi_connect_task(void *arg) { s_connect_ip[2] = 1; s_connect_ip[3] = 42; s_connecting = false; - lv_async_call(connect_done_cb, NULL); + ui_async_call(connect_done_cb, NULL); vTaskDelete(NULL); } @@ -248,7 +248,7 @@ static void build_join_list(void) { ui_chrome_header(s_screen, "Networks", "/assets/icons/wifi_find.bin"); lv_obj_t *col = lv_obj_create(s_screen); - lv_obj_set_size(col, LCD_H_RES, NET_BODY_H); + lv_obj_set_size(col, ui_screen_w(), NET_BODY_H); lv_obj_align(col, LV_ALIGN_TOP_MID, 0, UI_CHROME_HEADER_H); lv_obj_set_style_bg_opa(col, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(col, 0, 0); @@ -280,7 +280,7 @@ static void build_join_list(void) { lv_obj_t *sec = lv_label_create(card); lv_label_set_text_fmt(sec, "%s ch %d", s_aps[i].open ? "Open" : "WPA2", s_aps[i].channel); - lv_obj_set_style_text_color(sec, lv_color_hex(COLOR_DIM_HEX), 0); + lv_obj_set_style_text_color(sec, current_theme.text_secondary, 0); lv_obj_set_style_text_font(sec, &lv_font_montserrat_12, 0); lv_obj_align(sec, LV_ALIGN_BOTTOM_LEFT, NET_TEXT_X, -6); @@ -372,7 +372,7 @@ static void wifi_scan_task(void *arg) { s_ap_count = count; s_scan_state = SCAN_DONE; s_scanning = false; - lv_async_call(scan_done_cb, NULL); + ui_async_call(scan_done_cb, NULL); vTaskDelete(NULL); } diff --git a/firmware_p4/components/Applications/ui/screens/connection_settings/connection_settings_ui.c b/firmware_p4/components/Applications/ui/screens/connection_settings/connection_settings_ui.c index 7b2c60c94..069ab56bd 100644 --- a/firmware_p4/components/Applications/ui/screens/connection_settings/connection_settings_ui.c +++ b/firmware_p4/components/Applications/ui/screens/connection_settings/connection_settings_ui.c @@ -15,39 +15,21 @@ #include "connection_settings_ui.h" -#include "esp_log.h" -#include "esp_timer.h" - #include "host_link.h" #include "lv_port_indev.h" #include "menu_component_ui.h" -#include "msgbox_ui.h" #include "notify_ui.h" #include "power_manager.h" #include "tusb_desc.h" #include "ui_manager.h" #include "ui_theme.h" -#include "wifi_service.h" - -static const char *TAG = "CONN_UI"; -#define IDX_WIFI 0 -#define IDX_NETWORKS 1 -#define IDX_USB_NATIVE 2 -#define WIFI_LOADING_TIMER_INTERVAL_MS 100 -#define WIFI_LOADING_MIN_US 1500000 -#define WIFI_LOADING_MAX_US 5000000 -#define MSGBOX_DEBOUNCE_US 300000 +#define IDX_USB_NATIVE 0 static lv_obj_t *s_screen_conn = NULL; static menu_component_t s_menu; -static lv_timer_t *s_wifi_loading_timer = NULL; -static int64_t s_wifi_loading_start_time = 0; -static int64_t s_msgbox_open_time = 0; static bool s_usb_no_sleep_held = false; -static void wifi_loading_timer_cb(lv_timer_t *timer); -static void show_wifi_loading(void); static void connection_settings_input(const input_event_t *ev, void *ctx); void ui_connection_settings_open(void) { @@ -56,17 +38,12 @@ void ui_connection_settings_open(void) { s_screen_conn = NULL; } - bool is_wifi_active = wifi_service_is_active(); - s_screen_conn = lv_obj_create(NULL); lv_obj_set_style_bg_color(s_screen_conn, current_theme.screen_base, 0); lv_obj_set_style_bg_opa(s_screen_conn, LV_OPA_COVER, 0); lv_obj_remove_flag(s_screen_conn, LV_OBJ_FLAG_SCROLLABLE); s_menu = menu_component_create(s_screen_conn, "CONNECTION", "/assets/icons/hub.bin"); - menu_component_add_toggle(&s_menu, "/assets/icons/wifi.bin", "WI-FI", is_wifi_active); - menu_component_add_item(&s_menu, "/assets/icons/wifi_find.bin", "NETWORKS"); - // USB-C data mux: OFF = UART bridge (serial/flash, default), ON = native P4 USB. menu_component_add_toggle(&s_menu, "/assets/icons/usb.bin", "USB NATIVE", usb_mux_is_native()); ui_input_set_screen_handler(connection_settings_input, NULL); @@ -74,42 +51,8 @@ void ui_connection_settings_open(void) { ui_screen_load_owned(&s_screen_conn, s_screen_conn); } -static void wifi_loading_timer_cb(lv_timer_t *timer) { - int64_t elapsed = esp_timer_get_time() - s_wifi_loading_start_time; - bool is_ready = (wifi_service_is_active() && elapsed >= WIFI_LOADING_MIN_US) || - elapsed >= WIFI_LOADING_MAX_US; - if (!is_ready) - return; - - lv_timer_del(timer); - s_wifi_loading_timer = NULL; - msgbox_close(); - notify(NOTIFY_INFO, "Wi-Fi on"); -} - -static void show_wifi_loading(void) { - if (s_wifi_loading_timer != NULL) - lv_timer_del(s_wifi_loading_timer); - - s_wifi_loading_start_time = esp_timer_get_time(); - msgbox_open(LV_SYMBOL_WIFI, "LIGANDO WIFI...", NULL, NULL, NULL); - s_wifi_loading_timer = - lv_timer_create(wifi_loading_timer_cb, WIFI_LOADING_TIMER_INTERVAL_MS, NULL); -} - static void conn_toggle(int sel) { - if (sel == IDX_WIFI) { - menu_component_toggle_item(&s_menu, IDX_WIFI); - bool is_new_state = menu_component_get_toggle(&s_menu, IDX_WIFI); - wifi_service_set_enabled(is_new_state); - - if (is_new_state) { - show_wifi_loading(); - } else { - msgbox_close(); - notify(NOTIFY_INFO, "Wi-Fi off"); - } - } else if (sel == IDX_USB_NATIVE) { + if (sel == IDX_USB_NATIVE) { menu_component_toggle_item(&s_menu, IDX_USB_NATIVE); bool native = menu_component_get_toggle(&s_menu, IDX_USB_NATIVE); usb_mux_set_native(native); @@ -147,19 +90,10 @@ static void connection_settings_input(const input_event_t *ev, void *ctx) { ui_switch_screen(SCREEN_SETTINGS); break; case INPUT_BTN_LEFT: - if (press) - conn_toggle(sel); - break; case INPUT_BTN_RIGHT: - if (press) { - conn_toggle(sel); - if (sel == IDX_NETWORKS) - ui_switch_screen(SCREEN_CONNECT_WIFI); - } - break; case INPUT_BTN_OK: - if (press && sel == IDX_NETWORKS) - ui_switch_screen(SCREEN_CONNECT_WIFI); + if (press) + conn_toggle(sel); break; default: break; diff --git a/firmware_p4/components/Applications/ui/screens/dev/dev_console_ui.c b/firmware_p4/components/Applications/ui/screens/dev/dev_console_ui.c index cdfe436ba..4b00a3971 100644 --- a/firmware_p4/components/Applications/ui/screens/dev/dev_console_ui.c +++ b/firmware_p4/components/Applications/ui/screens/dev/dev_console_ui.c @@ -29,6 +29,7 @@ #include "terminal_ui.h" #include "ui_chrome.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #include "wifi_service.h" @@ -45,15 +46,15 @@ #define BODY_BOTTOM_PAD 4 #define RIGHT_GUTTER 16 #define BODY_Y (UI_CHROME_HEADER_H + BODY_TOP_PAD) -#define TERM_W (LCD_H_RES - BODY_X - RIGHT_GUTTER) -#define TERM_H (LCD_V_RES - BODY_Y - UI_CHROME_FOOTER_H - BODY_BOTTOM_PAD) +#define TERM_W (ui_screen_w() - BODY_X - RIGHT_GUTTER) +#define TERM_H (ui_screen_h() - BODY_Y - UI_CHROME_FOOTER_H - BODY_BOTTOM_PAD) -#define SCROLL_TRACK_X 227 +#define SCROLL_TRACK_X (ui_screen_w() - 13) #define SCROLL_TRACK_Y 54 -#define SCROLL_TRACK_LEN 232 +#define SCROLL_TRACK_LEN (ui_screen_h() - SCROLL_TRACK_Y - 34) #define SCROLL_LINE_W 3 #define SCROLL_DASH 4 -#define SCROLL_THUMB_X 223 +#define SCROLL_THUMB_X (ui_screen_w() - 17) #define SCROLL_THUMB_H 45 #define LINE_STEP 24 @@ -262,7 +263,11 @@ static void build_screen(void) { lv_obj_set_style_bg_opa(s_term, LV_OPA_TRANSP, LV_PART_CURSOR); lv_obj_set_style_border_width(s_term, 0, LV_PART_CURSOR); - static lv_point_precise_t track_pts[2] = {{0, 0}, {0, SCROLL_TRACK_LEN}}; + static lv_point_precise_t track_pts[2]; + track_pts[0].x = 0; + track_pts[0].y = 0; + track_pts[1].x = 0; + track_pts[1].y = SCROLL_TRACK_LEN; lv_obj_t *track = lv_line_create(s_screen); lv_line_set_points(track, track_pts, 2); lv_obj_set_pos(track, SCROLL_TRACK_X, SCROLL_TRACK_Y); diff --git a/firmware_p4/components/Applications/ui/screens/dev/dev_diag_ui.c b/firmware_p4/components/Applications/ui/screens/dev/dev_diag_ui.c index e3ed8949d..60a1be184 100644 --- a/firmware_p4/components/Applications/ui/screens/dev/dev_diag_ui.c +++ b/firmware_p4/components/Applications/ui/screens/dev/dev_diag_ui.c @@ -25,6 +25,7 @@ #include "sys_metrics.h" #include "ui_chrome.h" #include "ui_manager.h" +#include "ui_semantic.h" #include "ui_theme.h" #define TICK_MS 260 @@ -33,10 +34,8 @@ #define ICON "/assets/icons/troubleshoot.bin" #define FOOTER_HINT LV_SYMBOL_LEFT " BACK exit" -#define OK_COLOR 0x00E676 #define WARN_COLOR 0xFFC23D #define CYAN_COLOR 0x37E0A8 -#define DIM_COLOR 0x8A8594 #define GRID_COLOR 0x241F31 #define PANEL_PAD 8 @@ -95,7 +94,7 @@ make_value_label(lv_obj_t *panel, const char *title, uint32_t color, const char lv_obj_t *t = lv_label_create(head); lv_label_set_text(t, title); - lv_obj_set_style_text_color(t, lv_color_hex(DIM_COLOR), 0); + lv_obj_set_style_text_color(t, current_theme.text_secondary, 0); lv_obj_set_style_text_font(t, &lv_font_montserrat_12, 0); lv_obj_t *v = lv_label_create(head); @@ -145,7 +144,7 @@ static lv_obj_t *make_stat(lv_obj_t *row, const char *key, const char *val, uint lv_obj_t *k = lv_label_create(c); lv_label_set_text(k, key); - lv_obj_set_style_text_color(k, lv_color_hex(DIM_COLOR), 0); + lv_obj_set_style_text_color(k, current_theme.text_secondary, 0); lv_obj_set_style_text_font(k, &lv_font_montserrat_12, 0); lv_obj_t *v = lv_label_create(c); @@ -193,7 +192,7 @@ static void tick_cb(lv_timer_t *t) { if (battery_service_get(&b) && b.valid) { snprintf(buf, sizeof(buf), "%d%%", b.soc); lv_obj_set_style_text_color( - s_batt_val, lv_color_hex(b.soc <= BATT_LOW_PCT ? WARN_COLOR : OK_COLOR), 0); + s_batt_val, lv_color_hex(b.soc <= BATT_LOW_PCT ? WARN_COLOR : UI_COL_SUCCESS), 0); } else { snprintf(buf, sizeof(buf), "--"); } @@ -217,7 +216,7 @@ static void tick_cb(lv_timer_t *t) { if (s_c5_val != NULL) { bool alive = spi_bridge_is_alive(); lv_label_set_text(s_c5_val, alive ? "OK" : "OFF"); - lv_obj_set_style_text_color(s_c5_val, lv_color_hex(alive ? OK_COLOR : WARN_COLOR), 0); + lv_obj_set_style_text_color(s_c5_val, lv_color_hex(alive ? UI_COL_SUCCESS : WARN_COLOR), 0); } } @@ -289,9 +288,9 @@ static void build_screen(void) { lv_obj_set_flex_flow(stats, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align( stats, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); - s_batt_val = make_stat(stats, "Batt", "--", OK_COLOR); + s_batt_val = make_stat(stats, "Batt", "--", UI_COL_SUCCESS); s_temp_val = make_stat(stats, "Temp", "--", CYAN_COLOR); - s_c5_val = make_stat(stats, "C5", "--", DIM_COLOR); + s_c5_val = make_stat(stats, "C5", "--", 0x8A8594); // TODO: not themed (raw hex arg) ui_input_set_screen_handler(dev_diag_input, NULL); diff --git a/firmware_p4/components/Applications/ui/screens/dev/scripts_ui.c b/firmware_p4/components/Applications/ui/screens/dev/scripts_ui.c index fd156812f..baaf4d118 100644 --- a/firmware_p4/components/Applications/ui/screens/dev/scripts_ui.c +++ b/firmware_p4/components/Applications/ui/screens/dev/scripts_ui.c @@ -30,6 +30,8 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" +#include "ui_semantic.h" #include "ui_theme.h" static const char *TAG = "SCRIPTS_UI"; @@ -40,9 +42,7 @@ static const char *TAG = "SCRIPTS_UI"; #define TERM_GREEN 0x00E676 #define TERM_DIM_GREEN 0x1F7A52 #define DARK_PANEL_COLOR 0x05090A -#define DIM_COLOR 0x8A8594 #define DANGER_COLOR 0xFF5252 -#define SUCCESS_COLOR 0x00E676 #define HEADER_TITLE "SCRIPTS" #define HEADER_ICON "/assets/icons/description.bin" @@ -56,8 +56,8 @@ static const char *TAG = "SCRIPTS_UI"; #define LIST_X 6 #define LIST_Y 46 #define LIST_GUTTER 16 -#define LIST_W (LCD_H_RES - LIST_X - LIST_GUTTER) -#define LIST_BODY_H (LCD_V_RES - LIST_Y - UI_CHROME_FOOTER_H - 4) +#define LIST_W (ui_screen_w() - LIST_X - LIST_GUTTER) +#define LIST_BODY_H (ui_screen_h() - LIST_Y - UI_CHROME_FOOTER_H - 4) #define ROW_H 26 #define ROW_GAP 4 #define ROW_RADIUS 8 @@ -70,12 +70,12 @@ static const char *TAG = "SCRIPTS_UI"; #define BADGE_PAD_VER 1 #define BADGE_TINT_OPA LV_OPA_20 -#define SCROLL_TRACK_X 227 +#define SCROLL_TRACK_X (ui_screen_w() - 13) #define SCROLL_TRACK_Y 54 -#define SCROLL_TRACK_LEN 232 +#define SCROLL_TRACK_LEN (ui_screen_h() - SCROLL_TRACK_Y - 34) #define SCROLL_TRACK_WIDTH 3 #define SCROLL_DASH 4 -#define SCROLL_THUMB_X 223 +#define SCROLL_THUMB_X (ui_screen_w() - 17) #define SCROLL_THUMB_FALLBACK_H 45 #define SCROLL_THUMB_SRC "/assets/icons/drag_indicator.bin" @@ -340,7 +340,7 @@ static bool script_needs_permission(const script_t *s) { static void build_badge(lv_obj_t *parent, cap_t c) { bool avail = CAPS[c].available; - lv_color_t color = avail ? current_theme.border_accent : lv_color_hex(DIM_COLOR); + lv_color_t color = avail ? current_theme.border_accent : current_theme.text_secondary; lv_obj_t *badge = lv_obj_create(parent); lv_obj_remove_flag(badge, LV_OBJ_FLAG_SCROLLABLE); @@ -431,7 +431,7 @@ static void build_empty(void) { lv_obj_t *t2 = lv_label_create(card); lv_label_set_text(t2, "Copy .js to /apps/scripts"); lv_obj_set_style_text_font(t2, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(t2, lv_color_hex(DIM_COLOR), 0); + lv_obj_set_style_text_color(t2, current_theme.text_secondary, 0); fade_in(card, FADE_MS); } @@ -490,13 +490,17 @@ static void build_browser(void) { lv_obj_t *chev = lv_label_create(row); lv_label_set_text(chev, CHEVRON_TEXT); - lv_obj_set_style_text_color(chev, lv_color_hex(DIM_COLOR), 0); + lv_obj_set_style_text_color(chev, current_theme.text_secondary, 0); lv_obj_set_style_text_font(chev, &lv_font_montserrat_12, 0); s_rows[i] = row; } - static lv_point_precise_t scroll_pts[2] = {{0, 0}, {0, SCROLL_TRACK_LEN}}; + static lv_point_precise_t scroll_pts[2]; + scroll_pts[0].x = 0; + scroll_pts[0].y = 0; + scroll_pts[1].x = 0; + scroll_pts[1].y = SCROLL_TRACK_LEN; lv_obj_t *track = lv_line_create(s_screen); lv_line_set_points(track, scroll_pts, 2); lv_obj_set_pos(track, SCROLL_TRACK_X, SCROLL_TRACK_Y); @@ -553,11 +557,15 @@ static void build_terminal(void) { lv_label_set_text(s_pct_lbl, PCT_TEXT " 0%"); lv_obj_set_style_text_color(s_pct_lbl, lv_color_hex(TERM_GREEN), 0); lv_obj_set_style_text_font(s_pct_lbl, &lv_font_montserrat_12, 0); - lv_obj_align(s_pct_lbl, LV_ALIGN_TOP_MID, 0, PCT_Y); + lv_obj_align( + s_pct_lbl, LV_ALIGN_TOP_MID, 0, LV_MIN(PCT_Y, ui_screen_h() - UI_CHROME_FOOTER_H - 56)); s_progress = lv_bar_create(s_screen); lv_obj_set_size(s_progress, PROGRESS_W, PROGRESS_H); - lv_obj_align(s_progress, LV_ALIGN_TOP_MID, 0, PROGRESS_Y); + lv_obj_align(s_progress, + LV_ALIGN_TOP_MID, + 0, + LV_MIN(PCT_Y, ui_screen_h() - UI_CHROME_FOOTER_H - 56) + (PROGRESS_Y - PCT_Y)); lv_bar_set_range(s_progress, 0, 100); lv_bar_set_value(s_progress, 0, LV_ANIM_OFF); lv_obj_set_style_bg_color(s_progress, lv_color_hex(PROGRESS_TRACK_COLOR), LV_PART_MAIN); @@ -610,7 +618,7 @@ static void show_done(void) { char buf[80]; snprintf(buf, sizeof(buf), LV_SYMBOL_OK " %s", s->result); lv_label_set_text(result, buf); - lv_obj_set_style_text_color(result, lv_color_hex(SUCCESS_COLOR), 0); + lv_obj_set_style_text_color(result, lv_color_hex(UI_COL_SUCCESS), 0); ui_feedback(UI_FB_WRITE); ESP_LOGI(TAG, "mock script done: %s", s->name); } @@ -618,7 +626,10 @@ static void show_done(void) { lv_label_set_long_mode(result, LV_LABEL_LONG_WRAP); lv_obj_set_style_text_align(result, LV_TEXT_ALIGN_CENTER, 0); lv_obj_set_style_text_font(result, &lv_font_montserrat_14, 0); - lv_obj_align(result, LV_ALIGN_TOP_MID, 0, RESULT_Y); + lv_obj_align(result, + LV_ALIGN_TOP_MID, + 0, + LV_MIN(PCT_Y, ui_screen_h() - UI_CHROME_FOOTER_H - 56) + (RESULT_Y - PCT_Y)); fade_in(result, FADE_MS); if (s_footer != NULL) diff --git a/firmware_p4/components/Applications/ui/screens/files/files_ui.c b/firmware_p4/components/Applications/ui/screens/files/files_ui.c index 733ea7313..b3839b340 100644 --- a/firmware_p4/components/Applications/ui/screens/files/files_ui.c +++ b/firmware_p4/components/Applications/ui/screens/files/files_ui.c @@ -30,6 +30,10 @@ #include "storage_assets.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" +#include "image_viewer_ui.h" +#include "mp3_player_ui.h" +#include "mp4_player_ui.h" #include "wav_player_ui.h" #include "text_viewer_ui.h" #include "ui_theme.h" @@ -50,7 +54,7 @@ static const char *TAG = "FILES_UI"; #define TILE_H 64 #define GLABEL_H 28 -#define ROW_CONTENT_W (LCD_H_RES - 16) +#define ROW_CONTENT_W (ui_screen_w() - 16) #define ROW_NAME_X 36 #define ROW_NAME_W_FILE (ROW_CONTENT_W - ROW_NAME_X - 52) #define ROW_NAME_W_DIR (ROW_CONTENT_W - ROW_NAME_X - 24) @@ -60,20 +64,19 @@ static const char *TAG = "FILES_UI"; #define SCROLL_STEP 36 #define COL_RAISE 0x170A28 -#define COL_DIM 0x8A8594 #define COL_DIRNM 0xF0E6FF -#define MAX_ENTRIES 96 -#define MAX_DEPTH 10 -#define MAX_PATH 256 -#define MAX_NAME 64 -#define PREVIEW_MAX 2048 -#define GUTTER_MAX 512 -#define FULL_PATH (MAX_PATH + MAX_NAME) -#define ROW_POOL LIST_VIS -#define GRID_COLS 2 -#define GRID_ROWS 3 -#define GRID_POOL (GRID_COLS * GRID_ROWS) +#define MAX_ENTRIES 96 +#define MAX_DEPTH 10 +#define MAX_PATH 256 +#define MAX_NAME 64 +#define PREVIEW_MAX 2048 +#define GUTTER_MAX 512 +#define FULL_PATH (MAX_PATH + MAX_NAME) +#define ROW_POOL LIST_VIS +#define GRID_COLS_MAX 4 +#define GRID_ROWS_MAX 3 +#define GRID_POOL (GRID_COLS_MAX * GRID_ROWS_MAX) #define ASSETS_ROOT "/assets" #define SDCARD_ROOT "/sdcard" @@ -91,8 +94,9 @@ typedef enum { VIEW_LIST = 0, VIEW_GRID } view_t; static const char *const VOL_LABELS[] = {"Assets", "SD Card"}; static const char *const VOL_PATHS[] = {ASSETS_ROOT, SDCARD_ROOT}; -#define VOL_COUNT 2 -#define VOL_SD 1 +#define VOL_COUNT 2 +#define VOL_SD 1 +#define ENTRY_USB_MSC 99 static const char *ICON_OF[] = { [FT_DIR] = "/assets/icons/folder.bin", @@ -161,6 +165,9 @@ static lv_obj_t *s_screen = NULL; static lv_timer_t *s_timer = NULL; static view_t s_view = VIEW_LIST; +static int s_grid_cols = 2; +static int s_grid_rows = 3; + static lv_obj_t *s_row[ROW_POOL]; static lv_obj_t *s_row_icon[ROW_POOL]; static lv_obj_t *s_row_name[ROW_POOL]; @@ -235,6 +242,8 @@ static uint8_t type_from_ext(const char *name) { static const char *entry_icon(int idx) { if (s_depth == 0) { + if (s_vol_idx[idx] == ENTRY_USB_MSC) + return "/assets/icons/usb.bin"; return (s_vol_idx[idx] == VOL_SD) ? "/assets/icons/sd_card.bin" : "/assets/icons/folder.bin"; } return ICON_OF[s_entries[idx].type]; @@ -242,6 +251,8 @@ static const char *entry_icon(int idx) { static lv_color_t entry_color(int idx) { if (s_depth == 0) { + if (s_vol_idx[idx] == ENTRY_USB_MSC) + return lv_color_hex(0x00E676); return (s_vol_idx[idx] == VOL_SD) ? lv_color_hex(0x00BCD4) : lv_color_hex(0xFFC400); } return color_of(s_entries[idx].type); @@ -277,6 +288,15 @@ static void scan_dir(void) { s_vol_idx[s_count] = k; s_count++; } + if (vfs_sdcard_is_mounted() && s_count < MAX_ENTRIES) { + entry_t *e = &s_entries[s_count]; + snprintf(e->name, sizeof(e->name), "USB Storage"); + e->is_dir = true; + e->size = 0; + e->type = FT_DIR; + s_vol_idx[s_count] = ENTRY_USB_MSC; + s_count++; + } return; } @@ -351,10 +371,14 @@ static void fill_peek(int idx) { lv_obj_set_style_border_color(s_pk_tag, c, 0); if (e->is_dir) { - lv_label_set_text(s_pk_meta, s_depth == 0 ? "Storage volume" : "Folder"); - if (s_depth == 0) { + if (s_depth == 0 && s_vol_idx[idx] == ENTRY_USB_MSC) { + lv_label_set_text(s_pk_meta, "USB drive"); + lv_label_set_text(s_pk_snip, "Share SD with a PC"); + } else if (s_depth == 0) { + lv_label_set_text(s_pk_meta, "Storage volume"); lv_label_set_text(s_pk_snip, s_vol_idx[idx] == VOL_SD ? "SD card" : "Internal flash"); } else { + lv_label_set_text(s_pk_meta, "Folder"); lv_label_set_text(s_pk_snip, ""); } } else { @@ -414,7 +438,7 @@ static void populate_row(int j) { char sz[16]; fmt_size(sz, sizeof(sz), e->size); lv_label_set_text(s_row_right[j], sz); - lv_obj_set_style_text_color(s_row_right[j], lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s_row_right[j], current_theme.text_secondary, 0); } style_item(row, entry_color(idx), idx == s_sel); } @@ -422,7 +446,7 @@ static void populate_row(int j) { static void populate_tile(int j) { int idx = s_top + j; lv_obj_t *tile = s_tile[j]; - if (idx >= s_count) { + if (j >= s_grid_cols * s_grid_rows || idx >= s_count) { hide_obj(tile); return; } @@ -481,18 +505,18 @@ static void refresh_selection(void) { } fill_peek(s_sel); } else { - int row = s_sel / GRID_COLS; - int toprow = s_top / GRID_COLS; + int row = s_sel / s_grid_cols; + int toprow = s_top / s_grid_cols; if (row < toprow) { toprow = row; } - if (row >= toprow + GRID_ROWS) { - toprow = row - GRID_ROWS + 1; + if (row >= toprow + s_grid_rows) { + toprow = row - s_grid_rows + 1; } if (toprow < 0) { toprow = 0; } - s_top = toprow * GRID_COLS; + s_top = toprow * s_grid_cols; for (int j = 0; j < GRID_POOL; j++) { populate_tile(j); } @@ -522,7 +546,7 @@ static void build_header(void) { lv_obj_t *hdr = lv_obj_create(s_screen); lv_obj_remove_flag(hdr, LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(hdr, LV_OBJ_FLAG_CLICKABLE); - lv_obj_set_size(hdr, LCD_H_RES, HEADER_H); + lv_obj_set_size(hdr, ui_screen_w(), HEADER_H); lv_obj_align(hdr, LV_ALIGN_TOP_LEFT, 0, 0); lv_obj_set_style_radius(hdr, 0, 0); lv_obj_set_style_bg_color(hdr, current_theme.bg_secondary, 0); @@ -566,7 +590,7 @@ static void build_pathbar(void) { lv_obj_t *bar = lv_obj_create(s_screen); lv_obj_remove_flag(bar, LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(bar, LV_OBJ_FLAG_CLICKABLE); - lv_obj_set_size(bar, LCD_H_RES, PATH_H); + lv_obj_set_size(bar, ui_screen_w(), PATH_H); lv_obj_align(bar, LV_ALIGN_TOP_LEFT, 0, HEADER_H); lv_obj_set_style_radius(bar, 0, 0); lv_obj_set_style_bg_color(bar, lv_color_hex(0x0A0710), 0); @@ -581,7 +605,7 @@ static void build_pathbar(void) { lv_label_set_text(lbl, s_depth == 0 ? "root" : s_cwd); lv_label_set_long_mode(lbl, LV_LABEL_LONG_DOT); lv_obj_set_style_text_font(lbl, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(lbl, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(lbl, current_theme.text_secondary, 0); lv_obj_set_flex_grow(lbl, 1); lv_obj_t *dots = plain(bar); @@ -637,12 +661,12 @@ static lv_obj_t *make_row_pool(lv_obj_t *parent, int j) { } static void build_list(void) { - int list_h = LCD_V_RES - CONTENT_Y - FOOTER_H - PEEK_H - 8; + int list_h = ui_screen_h() - CONTENT_Y - FOOTER_H - PEEK_H - 8; lv_obj_t *wrap = lv_obj_create(s_screen); lv_obj_remove_flag(wrap, LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(wrap, LV_OBJ_FLAG_CLICKABLE); - lv_obj_set_size(wrap, LCD_H_RES, list_h); + lv_obj_set_size(wrap, ui_screen_w(), list_h); lv_obj_align(wrap, LV_ALIGN_TOP_LEFT, 0, CONTENT_Y); lv_obj_set_style_bg_opa(wrap, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(wrap, 0, 0); @@ -682,12 +706,17 @@ static lv_obj_t *make_tile_pool(lv_obj_t *parent, int j) { } static void build_grid(void) { - int grid_h = LCD_V_RES - CONTENT_Y - FOOTER_H - GLABEL_H; + int grid_h = ui_screen_h() - CONTENT_Y - FOOTER_H - GLABEL_H; + + s_grid_cols = (ui_screen_w() - 16 + 6) / (TILE_W + 6); + s_grid_cols = LV_CLAMP(1, s_grid_cols, GRID_COLS_MAX); + s_grid_rows = (grid_h - 16 + 6) / (TILE_H + 6); + s_grid_rows = LV_CLAMP(1, s_grid_rows, GRID_ROWS_MAX); lv_obj_t *g = lv_obj_create(s_screen); lv_obj_remove_flag(g, LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(g, LV_OBJ_FLAG_CLICKABLE); - lv_obj_set_size(g, LCD_H_RES, grid_h); + lv_obj_set_size(g, ui_screen_w(), grid_h); lv_obj_align(g, LV_ALIGN_TOP_LEFT, 0, CONTENT_Y); lv_obj_set_style_bg_opa(g, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(g, 0, 0); @@ -705,7 +734,7 @@ static void build_grid(void) { lv_obj_t *gl = lv_obj_create(s_screen); lv_obj_remove_flag(gl, LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(gl, LV_OBJ_FLAG_CLICKABLE); - lv_obj_set_size(gl, LCD_H_RES, GLABEL_H); + lv_obj_set_size(gl, ui_screen_w(), GLABEL_H); lv_obj_align(gl, LV_ALIGN_BOTTOM_LEFT, 0, -FOOTER_H); lv_obj_set_style_radius(gl, 0, 0); lv_obj_set_style_bg_opa(gl, LV_OPA_TRANSP, 0); @@ -734,7 +763,7 @@ static void build_peek(void) { lv_obj_t *pk = lv_obj_create(s_screen); lv_obj_remove_flag(pk, LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(pk, LV_OBJ_FLAG_CLICKABLE); - lv_obj_set_size(pk, LCD_H_RES - 16, PEEK_H); + lv_obj_set_size(pk, ui_screen_w() - 16, PEEK_H); lv_obj_align(pk, LV_ALIGN_BOTTOM_MID, 0, -(FOOTER_H + 4)); lv_obj_set_style_radius(pk, 11, 0); lv_obj_set_style_bg_color(pk, lv_color_hex(COL_RAISE), 0); @@ -775,7 +804,7 @@ static void build_peek(void) { lv_label_set_long_mode(s_pk_meta, LV_LABEL_LONG_DOT); lv_obj_set_width(s_pk_meta, lv_pct(100)); lv_obj_set_style_text_font(s_pk_meta, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(s_pk_meta, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s_pk_meta, current_theme.text_secondary, 0); s_pk_snip = lv_label_create(pk); lv_label_set_long_mode(s_pk_snip, LV_LABEL_LONG_DOT); @@ -789,7 +818,7 @@ static void build_footer(const char *hint) { lv_obj_t *ft = lv_obj_create(s_screen); lv_obj_remove_flag(ft, LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(ft, LV_OBJ_FLAG_CLICKABLE); - lv_obj_set_size(ft, LCD_H_RES, FOOTER_H); + lv_obj_set_size(ft, ui_screen_w(), FOOTER_H); lv_obj_align(ft, LV_ALIGN_BOTTOM_LEFT, 0, 0); lv_obj_set_style_radius(ft, 0, 0); lv_obj_set_style_bg_color(ft, current_theme.bg_secondary, 0); @@ -822,7 +851,6 @@ static void load_preview(const char *path) { total = (long)n; } - // Classify: a NUL byte or lots of non-printables means it's a binary file. int nonprint = 0; bool binary = false; for (size_t i = 0; i < n; i++) { @@ -840,7 +868,6 @@ static void load_preview(const char *path) { } if (!binary) { - // Text: keep it, replacing any stray control byte with a dot. s_vbuf[n] = '\0'; for (size_t i = 0; i < n; i++) { unsigned char c = (unsigned char)s_vbuf[i]; @@ -854,7 +881,6 @@ static void load_preview(const char *path) { return; } - // Binary: give it a real hex preview (offset + bytes) so it still opens. unsigned char raw[256]; size_t hn = n < sizeof(raw) ? n : sizeof(raw); for (size_t i = 0; i < hn; i++) { @@ -879,9 +905,6 @@ static void build_viewer(void) { snprintf(full, sizeof(full), "%s/%s", s_cwd, e->name); load_preview(full); - // Use the shared text_viewer component (wrapped text, line/byte meta, scroll - // bar). It builds a full-screen viewer under s_screen; keep its scroll area in - // s_vbody so the nav timer's UP/DOWN can scroll it. s_tv = text_viewer_create(s_screen, e->name); text_viewer_set_text(&s_tv, s_vbuf); s_vbody = s_tv.text_area; @@ -952,11 +975,11 @@ static void move_grid(int dx, int dy) { s--; } if (dy > 0) { - int t = s + GRID_COLS; + int t = s + s_grid_cols; s = (t < s_count) ? t : s_count - 1; } if (dy < 0) { - int t = s - GRID_COLS; + int t = s - s_grid_cols; if (t >= 0) { s = t; } @@ -992,6 +1015,11 @@ static void do_enter(void) { return; } if (s_depth == 0) { + if (s_vol_idx[s_sel] == ENTRY_USB_MSC) { + ui_feedback(UI_FB_SELECT); + ui_switch_screen(SCREEN_USB_STORAGE); + return; + } enter_dir(VOL_PATHS[s_vol_idx[s_sel]], true); return; } @@ -1011,6 +1039,38 @@ static void do_enter(void) { ui_switch_screen(SCREEN_WAV_PLAYER); return; } + if (dot != NULL && (strcasecmp(dot, ".mp4") == 0 || strcasecmp(dot, ".m4v") == 0 || + strcasecmp(dot, ".mov") == 0)) { + char full[FULL_PATH]; + snprintf(full, sizeof(full), "%s/%s", s_cwd, e->name); + ui_feedback(UI_FB_SELECT); + s_resume = true; + ui_mp4_player_set_path(full); + ui_mp4_player_set_return(SCREEN_FILES); + ui_switch_screen(SCREEN_MP4_PLAYER); + return; + } + if (dot != NULL && strcasecmp(dot, ".mp3") == 0) { + char full[FULL_PATH]; + snprintf(full, sizeof(full), "%s/%s", s_cwd, e->name); + ui_feedback(UI_FB_SELECT); + s_resume = true; + ui_mp3_player_set_path(full); + ui_mp3_player_set_return(SCREEN_FILES); + ui_switch_screen(SCREEN_MP3_PLAYER); + return; + } + if (dot != NULL && (strcasecmp(dot, ".jpg") == 0 || strcasecmp(dot, ".jpeg") == 0 || + strcasecmp(dot, ".png") == 0 || strcasecmp(dot, ".gif") == 0)) { + char full[FULL_PATH]; + snprintf(full, sizeof(full), "%s/%s", s_cwd, e->name); + ui_feedback(UI_FB_SELECT); + s_resume = true; + ui_image_viewer_set_path(full); + ui_image_viewer_set_return(SCREEN_FILES); + ui_switch_screen(SCREEN_IMAGE_VIEWER); + return; + } s_in_viewer = true; ui_feedback(UI_FB_SELECT); build_screen(); @@ -1089,8 +1149,6 @@ static void files_input(const input_event_t *ev, void *ctx) { const bool nav = press || (ev->action == INPUT_ACTION_REPEAT); if (s_in_viewer) { - // _bounded clamps the scroll to the content — plain lv_obj_scroll_by() ignores - // the content bounds (and SCROLL_ELASTIC/MOMENTUM), which let it scroll forever. switch (ev->button) { case INPUT_BTN_DOWN: if (nav && s_vbody) diff --git a/firmware_p4/components/Applications/ui/screens/files/include/usb_storage_ui.h b/firmware_p4/components/Applications/ui/screens/files/include/usb_storage_ui.h new file mode 100644 index 000000000..71c32b401 --- /dev/null +++ b/firmware_p4/components/Applications/ui/screens/files/include/usb_storage_ui.h @@ -0,0 +1,38 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#ifndef USB_STORAGE_UI_H +#define USB_STORAGE_UI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Open the USB Storage screen: exposes the microSD to a host PC as a USB + * drive. Reached from the Files root. BACK ejects and returns to Files + * WITHOUT rebooting. + */ +void ui_usb_storage_open(void); + +/** @brief Screen close hook: reboots only if it is torn down while the card is + * still exposed to the host (defensive; normal exit needs no reboot). */ +void ui_usb_storage_stop(void); + +#ifdef __cplusplus +} +#endif + +#endif // USB_STORAGE_UI_H diff --git a/firmware_p4/components/Applications/ui/screens/files/usb_storage_ui.c b/firmware_p4/components/Applications/ui/screens/files/usb_storage_ui.c new file mode 100644 index 000000000..cbcb3ca73 --- /dev/null +++ b/firmware_p4/components/Applications/ui/screens/files/usb_storage_ui.c @@ -0,0 +1,204 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#include "usb_storage_ui.h" + +#include "esp_log.h" +#include "esp_system.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "lvgl.h" +#include "st7789.h" + +#include "assets_manager.h" +#include "sys_prio.h" +#include "ui_feedback.h" +#include "ui_manager.h" +#include "ui_metrics.h" +#include "ui_theme.h" +#include "usb_msc.h" + +static const char *TAG = "USB_STORAGE_UI"; + +#define VIEW_W ui_screen_w() +#define VIEW_H ui_screen_h() + +#define COL_READY lv_color_hex(0x00BCD4) +#define COL_CONNECTED lv_color_hex(0x00E676) +#define COL_ERROR lv_color_hex(0xFF5252) + +#define USB_MSC_TASK_STACK 8192 +#define REFRESH_PERIOD_MS 200 + +static lv_obj_t *s_screen = NULL; +static lv_obj_t *s_status = NULL; +static lv_obj_t *s_hint = NULL; +static lv_timer_t *s_timer = NULL; +static volatile bool s_exit_requested = false; + +static void enter_task(void *a) { + (void)a; + usb_msc_enter(); + vTaskDelete(NULL); +} +static void exit_task(void *a) { + (void)a; + usb_msc_exit(); + vTaskDelete(NULL); +} + +static void set_texts(const char *status, lv_color_t col, const char *hint) { + if (s_status) { + lv_label_set_text(s_status, status); + lv_obj_set_style_text_color(s_status, col, 0); + } + if (s_hint) + lv_label_set_text(s_hint, hint); +} + +static void refresh_cb(lv_timer_t *t) { + if (lv_screen_active() != s_screen) { + lv_timer_delete(t); + s_timer = NULL; + return; + } + switch (usb_msc_get_state()) { + case USB_MSC_ACTIVE: + if (usb_msc_host_connected()) + set_texts("Connected", COL_CONNECTED, "Copy your files, then press BACK to eject."); + else + set_texts("Ready", COL_READY, "Open the drive on your PC.\nBACK ejects and returns."); + break; + case USB_MSC_ERROR: + set_texts("Couldn't start USB storage", COL_ERROR, "Press BACK to return."); + break; + case USB_MSC_EXITING: + set_texts("Ejecting...", current_theme.text_secondary, "Restoring the card."); + break; + case USB_MSC_IDLE: + if (s_exit_requested) + ui_switch_screen(SCREEN_FILES); + else + set_texts("Starting...", current_theme.text_secondary, "Preparing the USB drive."); + break; + case USB_MSC_ENTERING: + default: + set_texts("Starting...", current_theme.text_secondary, "Preparing the USB drive."); + break; + } +} + +static void usb_storage_input(const input_event_t *ev, void *ctx) { + (void)ctx; + if (ev->action != INPUT_ACTION_PRESS || ev->button != INPUT_BTN_BACK) + return; + switch (usb_msc_get_state()) { + case USB_MSC_ACTIVE: + s_exit_requested = true; + set_texts("Ejecting...", current_theme.text_secondary, "Restoring the card."); + ui_feedback(UI_FB_SELECT); + xTaskCreatePinnedToCore(exit_task, + "usb_msc_x", + USB_MSC_TASK_STACK, + NULL, + SYS_PRIO_SERVICE_HI, + NULL, + SYS_CORE_RADIO); + break; + case USB_MSC_ERROR: + case USB_MSC_IDLE: + ui_switch_screen(SCREEN_FILES); + break; + default: + break; + } +} + +void ui_usb_storage_open(void) { + if (s_screen != NULL) { + lv_obj_del(s_screen); + s_screen = NULL; + } + s_exit_requested = false; + + s_screen = lv_obj_create(NULL); + lv_obj_remove_flag(s_screen, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_style_bg_color(s_screen, current_theme.bg_primary, 0); + lv_obj_set_style_bg_opa(s_screen, LV_OPA_COVER, 0); + lv_obj_set_style_pad_all(s_screen, 0, 0); + lv_obj_set_style_border_width(s_screen, 0, 0); + + lv_obj_t *card = lv_obj_create(s_screen); + lv_obj_remove_flag(card, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_size(card, VIEW_W - 36, 236); + lv_obj_center(card); + lv_obj_set_style_bg_color(card, current_theme.bg_secondary, 0); + lv_obj_set_style_bg_opa(card, LV_OPA_COVER, 0); + lv_obj_set_style_radius(card, 18, 0); + lv_obj_set_style_border_width(card, 1, 0); + lv_obj_set_style_border_color(card, current_theme.border_accent, 0); + lv_obj_set_style_shadow_width(card, 24, 0); + lv_obj_set_style_shadow_opa(card, LV_OPA_30, 0); + lv_obj_set_style_shadow_color(card, lv_color_black(), 0); + lv_obj_set_style_pad_all(card, 16, 0); + lv_obj_set_flex_flow(card, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(card, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_style_pad_row(card, 10, 0); + + lv_image_dsc_t *ic = assets_get("/assets/icons/usb.bin"); + if (ic != NULL) { + lv_obj_t *img = lv_image_create(card); + lv_image_set_src(img, ic); + lv_image_set_scale(img, 384); + } + + lv_obj_t *title = lv_label_create(card); + lv_label_set_text(title, "USB Storage"); + lv_obj_set_style_text_font(title, &lv_font_montserrat_16, 0); + lv_obj_set_style_text_color(title, current_theme.text_main, 0); + + s_status = lv_label_create(card); + lv_obj_set_width(s_status, VIEW_W - 80); + lv_label_set_long_mode(s_status, LV_LABEL_LONG_WRAP); + lv_obj_set_style_text_align(s_status, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_style_text_font(s_status, &lv_font_montserrat_14, 0); + lv_label_set_text(s_status, "Starting..."); + lv_obj_set_style_text_color(s_status, current_theme.text_secondary, 0); + + s_hint = lv_label_create(card); + lv_obj_set_width(s_hint, VIEW_W - 72); + lv_label_set_long_mode(s_hint, LV_LABEL_LONG_WRAP); + lv_obj_set_style_text_align(s_hint, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_style_text_font(s_hint, &lv_font_montserrat_12, 0); + lv_label_set_text(s_hint, "Preparing the USB drive."); + lv_obj_set_style_text_color(s_hint, current_theme.text_secondary, 0); + + ui_input_set_screen_handler(usb_storage_input, NULL); + s_timer = lv_timer_create(refresh_cb, REFRESH_PERIOD_MS, NULL); + ui_screen_load_owned(&s_screen, s_screen); + + xTaskCreatePinnedToCore( + enter_task, "usb_msc_e", USB_MSC_TASK_STACK, NULL, SYS_PRIO_SERVICE_HI, NULL, SYS_CORE_RADIO); +} + +void ui_usb_storage_stop(void) { + s_status = NULL; + s_hint = NULL; + if (usb_msc_get_state() == USB_MSC_ACTIVE) { + ESP_LOGW(TAG, "closed while active - rebooting to restore /sdcard"); + esp_restart(); + } +} diff --git a/firmware_p4/components/Applications/ui/screens/games/doom_real_ui.c b/firmware_p4/components/Applications/ui/screens/games/doom_real_ui.c new file mode 100644 index 000000000..1cd07f527 --- /dev/null +++ b/firmware_p4/components/Applications/ui/screens/games/doom_real_ui.c @@ -0,0 +1,39 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#include "doom_real_ui.h" + +#include "ui_manager.h" // lvgl.h + ui_screen_load() +#include "ui_liveness.h" // ui_render_beat_kick() +#include "doom_highboy.h" // highboy_doom_start() (doom component) + +void ui_doom_real_open(void) { + lv_obj_t *scr = lv_obj_create(NULL); + lv_obj_set_style_bg_color(scr, lv_color_black(), 0); + lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0); + lv_obj_remove_flag(scr, LV_OBJ_FLAG_SCROLLABLE); + + lv_obj_t *lbl = lv_label_create(scr); + lv_label_set_text(lbl, "DOOM\nloading /sdcard/doom/doom1.wad ..."); + lv_obj_set_style_text_color(lbl, lv_color_hex(0xC8C8C8), 0); + lv_obj_set_style_text_align(lbl, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_center(lbl); + + ui_screen_load(scr); + + // The DOOM task waits ~150ms for this screen to settle, then enters direct + // draw mode and owns the panel. Quitting DOOM reboots, so no teardown here. + highboy_doom_start(ui_render_beat_kick); +} diff --git a/firmware_p4/components/Applications/ui/screens/games/games_menu_ui.c b/firmware_p4/components/Applications/ui/screens/games/games_menu_ui.c index cf243bc62..3d3b5d2ab 100644 --- a/firmware_p4/components/Applications/ui/screens/games/games_menu_ui.c +++ b/firmware_p4/components/Applications/ui/screens/games/games_menu_ui.c @@ -40,12 +40,10 @@ typedef struct { } game_t; static const game_t GAMES[] = { - {"Octo Pet", "/assets/icons/game_pet.bin", 0x834EC6, false, SCREEN_GAME_OCTOPET}, - {"Octo Flap", "/assets/icons/game_flap.bin", 0x00E5D0, true, SCREEN_GAME_FLAPPY}, {"Snake", "/assets/icons/game_snake.bin", 0x00E676, true, SCREEN_GAME_SNAKE}, {"Breakout", "/assets/icons/game_brk.bin", 0xFFB020, true, SCREEN_GAME_BREAKOUT}, - {"Motion / Level", "/assets/icons/sensors.bin", 0x22D3EE, true, SCREEN_MOTION}, - {"Coming Soon", "/assets/icons/timer.bin", 0x5E12A0, false, TARGET_STUB}, + {"Game Boy", "/assets/icons/game_gb.bin", 0x9BBC0F, true, SCREEN_GAME_GB}, + {"DOOM", "/assets/icons/timer.bin", 0xC81E1E, false, SCREEN_GAME_DOOM}, }; #define GAME_COUNT ((int)(sizeof(GAMES) / sizeof(GAMES[0]))) diff --git a/firmware_p4/components/Applications/ui/screens/games/gb_ui.c b/firmware_p4/components/Applications/ui/screens/games/gb_ui.c new file mode 100644 index 000000000..e541723f8 --- /dev/null +++ b/firmware_p4/components/Applications/ui/screens/games/gb_ui.c @@ -0,0 +1,207 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#include "gb_ui.h" + +#include +#include +#include + +#include "esp_attr.h" + +#include "lvgl.h" + +#include "buttons_gpio.h" +#include "gb_highboy.h" +#include "st7789.h" +#include "storage_init.h" +#include "ui_manager.h" +#include "ui_theme.h" + +#define MAX_ROMS 64 +#define NAV_MS 80 +#define SD_ROOT "/sdcard" +#define MAX_DEPTH 3 +#define ROM_PATH_LEN 300 +#define ROM_NAME_LEN 256 +#define EXT_GB_LEN 3 +#define EXT_GBC_LEN 4 +#define COLOR_NO_ROMS 0xDD4444 +#define LIST_TOP_OFFSET 26 +#define LIST_HEIGHT_TRIM 60 + +EXT_RAM_BSS_ATTR static char s_paths[MAX_ROMS][ROM_PATH_LEN]; +EXT_RAM_BSS_ATTR static char s_names[MAX_ROMS][ROM_NAME_LEN]; +static int s_count = 0; +static int s_sel = 0; +static bool s_launched = false; + +static lv_obj_t *s_scr = NULL; +static lv_obj_t *s_list = NULL; +static lv_obj_t *s_items[MAX_ROMS]; +static lv_timer_t *s_nav = NULL; +static bool s_up_last, s_dn_last, s_ok_last, s_bk_last, s_lf_last; + +static bool is_rom(const char *name) { + size_t n = strlen(name); + return (n >= EXT_GB_LEN && strcasecmp(name + n - EXT_GB_LEN, ".gb") == 0) || + (n >= EXT_GBC_LEN && strcasecmp(name + n - EXT_GBC_LEN, ".gbc") == 0); +} + +static void scan_dir(const char *dir, int depth) { + if (depth > MAX_DEPTH || s_count >= MAX_ROMS) + return; + DIR *d = opendir(dir); + if (d == NULL) + return; + struct dirent *e; + while ((e = readdir(d)) != NULL && s_count < MAX_ROMS) { + const char *dn = e->d_name; + if (dn[0] == '.') + continue; + if (strlen(dir) + 1 + strlen(dn) >= sizeof(s_paths[0])) + continue; + char full[sizeof(s_paths[0])]; + strlcpy(full, dir, sizeof(full)); + strlcat(full, "/", sizeof(full)); + strlcat(full, dn, sizeof(full)); + if (e->d_type == DT_DIR) { + scan_dir(full, depth + 1); + } else if (is_rom(dn)) { + strlcpy(s_paths[s_count], full, sizeof(s_paths[0])); + strlcpy(s_names[s_count], dn, sizeof(s_names[0])); + s_count++; + } + } + closedir(d); +} + +static void scan_roms(void) { + s_count = 0; + scan_dir(SD_ROOT, 0); +} + +static void draw_sel(void) { + for (int i = 0; i < s_count; i++) { + bool sel = (i == s_sel); + lv_label_set_text_fmt(s_items[i], "%s %s", sel ? ">" : " ", s_names[i]); + lv_obj_set_style_text_color( + s_items[i], sel ? ui_theme_get_accent() : current_theme.text_main, 0); + } + if (s_count > 0) + lv_obj_scroll_to_view(s_items[s_sel], LV_ANIM_OFF); +} + +static void nav_tick(lv_timer_t *t) { + if (lv_screen_active() != s_scr) { + lv_timer_delete(t); + s_nav = NULL; + return; + } + if (s_launched) { + if (highboy_gb_finished()) { + s_launched = false; + lcd_set_rotation(lcd_get_rotation()); + ui_switch_screen(SCREEN_GAMES_MENU); + } + return; + } + if (ui_input_is_locked()) + return; + + bool up = ui_btn_up(), dn = ui_btn_down(), lf = ui_btn_left(); + bool ok = ok_button_is_down(), bk = back_button_is_down(); + + if (s_count > 0) { + if (dn && !s_dn_last) { + s_sel = (s_sel + 1) % s_count; + draw_sel(); + } + if (up && !s_up_last) { + s_sel = (s_sel - 1 + s_count) % s_count; + draw_sel(); + } + if (ok && !s_ok_last) { + s_launched = true; + highboy_gb_start(s_paths[s_sel]); + } + } + if ((bk && !s_bk_last) || (lf && !s_lf_last)) + ui_switch_screen(SCREEN_GAMES_MENU); + + s_up_last = up; + s_dn_last = dn; + s_ok_last = ok; + s_bk_last = bk; + s_lf_last = lf; +} + +void ui_gb_open(void) { + s_sel = 0; + s_launched = false; + s_up_last = s_dn_last = s_ok_last = s_bk_last = s_lf_last = false; + s_nav = NULL; + + if (!storage_is_mounted()) + storage_init(); + scan_roms(); + + s_scr = lv_obj_create(NULL); + lv_obj_set_style_bg_color(s_scr, current_theme.screen_base, 0); + lv_obj_set_style_bg_opa(s_scr, LV_OPA_COVER, 0); + lv_obj_remove_flag(s_scr, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_style_pad_all(s_scr, 6, 0); + + lv_obj_t *title = lv_label_create(s_scr); + lv_label_set_text(title, "GAME BOY - ROMs"); + lv_obj_set_style_text_color(title, current_theme.text_main, 0); + lv_obj_set_style_text_font(title, &lv_font_montserrat_16, 0); + lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 0); + + if (s_count == 0) { + lv_obj_t *m = lv_label_create(s_scr); + lv_label_set_text( + m, "No .gb / .gbc ROMs found.\n\nCopy games anywhere on the\nSD card and reopen."); + lv_obj_set_style_text_color(m, lv_color_hex(COLOR_NO_ROMS), 0); + lv_obj_set_style_text_align(m, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_center(m); + } else { + int vres = lv_display_get_vertical_resolution(NULL); + s_list = lv_obj_create(s_scr); + lv_obj_set_style_bg_opa(s_list, LV_OPA_TRANSP, 0); + lv_obj_set_style_border_width(s_list, 0, 0); + lv_obj_set_style_pad_all(s_list, 2, 0); + lv_obj_set_size(s_list, LV_PCT(100), vres - LIST_HEIGHT_TRIM); + lv_obj_align(s_list, LV_ALIGN_TOP_MID, 0, LIST_TOP_OFFSET); + lv_obj_set_flex_flow(s_list, LV_FLEX_FLOW_COLUMN); + lv_obj_set_scroll_dir(s_list, LV_DIR_VER); + for (int i = 0; i < s_count; i++) { + s_items[i] = lv_label_create(s_list); + lv_obj_set_style_text_font(s_items[i], &lv_font_montserrat_14, 0); + lv_obj_set_width(s_items[i], LV_PCT(100)); + lv_label_set_long_mode(s_items[i], LV_LABEL_LONG_DOT); + } + draw_sel(); + } + + lv_obj_t *hint = lv_label_create(s_scr); + lv_label_set_text(hint, s_count > 0 ? "UP/DN pick OK play BACK exit" : "BACK exit"); + lv_obj_set_style_text_color(hint, current_theme.text_main, 0); + lv_obj_set_style_text_font(hint, &lv_font_montserrat_12, 0); + lv_obj_align(hint, LV_ALIGN_BOTTOM_MID, 0, 0); + + s_nav = lv_timer_create(nav_tick, NAV_MS, NULL); + ui_screen_load(s_scr); +} diff --git a/firmware_p4/components/Applications/ui/screens/games/imu_monitor_ui.c b/firmware_p4/components/Applications/ui/screens/games/imu_monitor_ui.c index ecc20a9b9..2ad5d19d1 100644 --- a/firmware_p4/components/Applications/ui/screens/games/imu_monitor_ui.c +++ b/firmware_p4/components/Applications/ui/screens/games/imu_monitor_ui.c @@ -32,7 +32,6 @@ #define MX 8 #define CONTENT_W (240 - 2 * MX) -#define COL_DIM 0x8A8594 #define COL_CYAN 0x37E0A8 #define COL_WARN 0xFFC23D #define COL_ACC2 0xB89AFF @@ -217,7 +216,8 @@ static void build_scope_card(void) { fill_traces(); - lv_obj_t *xval = make_mono_label(card, "X -0.01", COL_DIM, 0, VAL_ROW_Y); + lv_obj_t *xval = + make_mono_label(card, "X -0.01", 0x8A8594, 0, VAL_ROW_Y); // TODO: not themed (raw hex arg) lv_obj_set_style_text_color(xval, current_theme.border_accent, 0); make_mono_label(card, "Y +0.05", COL_CYAN, 80, VAL_ROW_Y); make_mono_label(card, "Z +1.00", COL_WARN, 158, VAL_ROW_Y); @@ -233,7 +233,7 @@ static void build_gyro_card(void) { lv_obj_t *lbl = lv_label_create(card); lv_label_set_text(lbl, GYRO_ROWS[i].label); lv_obj_set_style_text_font(lbl, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(lbl, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(lbl, current_theme.text_secondary, 0); lv_obj_align(lbl, LV_ALIGN_TOP_LEFT, 0, row_y); lv_obj_t *track = lv_obj_create(card); @@ -244,7 +244,7 @@ static void build_gyro_card(void) { lv_obj_set_style_pad_all(track, 0, 0); lv_obj_set_style_bg_color(track, current_theme.bg_primary, 0); lv_obj_set_style_bg_opa(track, LV_OPA_COVER, 0); - lv_obj_set_style_border_color(track, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_border_color(track, current_theme.text_secondary, 0); lv_obj_set_style_border_opa(track, LV_OPA_40, 0); lv_obj_set_style_border_width(track, 1, 0); lv_obj_set_style_clip_corner(track, true, 0); diff --git a/firmware_p4/components/Applications/ui/screens/games/include/doom_real_ui.h b/firmware_p4/components/Applications/ui/screens/games/include/doom_real_ui.h new file mode 100644 index 000000000..03744aac7 --- /dev/null +++ b/firmware_p4/components/Applications/ui/screens/games/include/doom_real_ui.h @@ -0,0 +1,32 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#ifndef DOOM_REAL_UI_H +#define DOOM_REAL_UI_H + +#ifdef __cplusplus +extern "C" { +#endif + +// Open official DOOM (doomgeneric): shows a black loading screen, then hands the +// ST7789 to the DOOM task (see components/doom). Quitting DOOM reboots the +// device (hold OK+BACK ~2s), so this open fn never returns control to the UI. +void ui_doom_real_open(void); + +#ifdef __cplusplus +} +#endif + +#endif // DOOM_REAL_UI_H diff --git a/firmware_p4/components/Applications/ui/screens/games/include/gb_ui.h b/firmware_p4/components/Applications/ui/screens/games/include/gb_ui.h new file mode 100644 index 000000000..46cb939d7 --- /dev/null +++ b/firmware_p4/components/Applications/ui/screens/games/include/gb_ui.h @@ -0,0 +1,36 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#ifndef GB_UI_H +#define GB_UI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Open the Game Boy ROM picker screen. + * + * Recursively lists every .gb/.gbc ROM on the SD card and lets the user choose + * one; OK hands the chosen ROM to the emulator (components/gameboy), which takes + * over the display. Returns to the games menu on BACK or when the emulator exits. + */ +void ui_gb_open(void); + +#ifdef __cplusplus +} +#endif + +#endif // GB_UI_H diff --git a/firmware_p4/components/Applications/ui/screens/games/motion_ui.c b/firmware_p4/components/Applications/ui/screens/games/motion_ui.c index f642ab5bb..da1b309b9 100644 --- a/firmware_p4/components/Applications/ui/screens/games/motion_ui.c +++ b/firmware_p4/components/Applications/ui/screens/games/motion_ui.c @@ -37,7 +37,6 @@ #define DECAY 0.94f #define COL_LEVEL 0x00E676 -#define COL_DIM 0x8A8594 #define HDR_ICON "/assets/icons/sensors.bin" #define HDR_TITLE "BUBBLE LEVEL" @@ -89,7 +88,7 @@ static void build_level(void) { lv_obj_center(ring); lv_obj_set_style_radius(ring, LV_RADIUS_CIRCLE, 0); lv_obj_set_style_bg_opa(ring, LV_OPA_TRANSP, 0); - lv_obj_set_style_border_color(ring, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_border_color(ring, current_theme.text_secondary, 0); lv_obj_set_style_border_width(ring, 2, 0); lv_obj_set_style_pad_all(ring, 0, 0); @@ -109,7 +108,7 @@ static void build_level(void) { s_status = lv_label_create(s_screen); lv_label_set_text(s_status, "TILTED"); lv_obj_set_style_text_font(s_status, &lv_font_montserrat_14, 0); - lv_obj_set_style_text_color(s_status, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s_status, current_theme.text_secondary, 0); lv_obj_align(s_status, LV_ALIGN_BOTTOM_MID, 0, -52); s_read = lv_label_create(s_screen); @@ -144,7 +143,7 @@ static void update_physics(void) { if (s_status) { lv_label_set_text(s_status, level ? "LEVEL" : "TILTED"); lv_obj_set_style_text_color( - s_status, level ? lv_color_hex(COL_LEVEL) : lv_color_hex(COL_DIM), 0); + s_status, level ? lv_color_hex(COL_LEVEL) : current_theme.text_secondary, 0); } if (s_read) lv_label_set_text_fmt(s_read, "Pitch %d deg Roll %d deg", (int)s_pitch, (int)s_roll); diff --git a/firmware_p4/components/Applications/ui/screens/home/home_ui.c b/firmware_p4/components/Applications/ui/screens/home/home_ui.c index 53dcfe3bd..038d1ff8a 100644 --- a/firmware_p4/components/Applications/ui/screens/home/home_ui.c +++ b/firmware_p4/components/Applications/ui/screens/home/home_ui.c @@ -30,20 +30,21 @@ #include "menu_ui.h" #include "sys_time.h" #include "ui_feedback.h" +#include "ui_metrics.h" #include "ui_manager.h" #include "ui_theme.h" static const char *TAG = "HOME_UI"; #define HOME_HEADER_HEIGHT_PCT 9 -#define HOME_HEADER_HEIGHT ((LCD_V_RES * HOME_HEADER_HEIGHT_PCT) / 100) +#define HOME_HEADER_HEIGHT ((ui_screen_h() * HOME_HEADER_HEIGHT_PCT) / 100) #define HOME_ART_ASSET "/assets/img/image.bin" #define HOME_DATE_FONT "A:assets/fonts/Inter.bin" #define HOME_PAD 10 #define HOME_ROW_GAP 8 -#define HOME_INNER_W (LCD_H_RES - 2 * HOME_PAD) +#define HOME_INNER_W (ui_screen_w() - 2 * HOME_PAD) #define HOME_ART_MAX_W 210 #define HOME_ART_MAX_H 132 #define HOME_FLOAT_AMP 5 @@ -328,16 +329,17 @@ void ui_home_open(void) { header_ui_create(s_screen_home); lv_obj_t *content = lv_obj_create(s_screen_home); - lv_obj_set_size(content, LCD_H_RES, LCD_V_RES - HOME_HEADER_HEIGHT); - lv_obj_align(content, LV_ALIGN_TOP_MID, 0, HOME_HEADER_HEIGHT); + int32_t header_h = HOME_HEADER_HEIGHT; + lv_obj_set_size(content, ui_screen_w(), ui_screen_h() - header_h); + lv_obj_align(content, LV_ALIGN_TOP_MID, 0, header_h); lv_obj_remove_flag(content, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_style_bg_opa(content, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(content, 0, 0); lv_obj_set_style_pad_left(content, HOME_PAD, 0); lv_obj_set_style_pad_right(content, HOME_PAD, 0); - lv_obj_set_style_pad_top(content, 8, 0); - lv_obj_set_style_pad_bottom(content, HOME_PAD, 0); - lv_obj_set_style_pad_row(content, HOME_ROW_GAP, 0); + lv_obj_set_style_pad_top(content, 4, 0); + lv_obj_set_style_pad_bottom(content, HOME_PAD + 18, 0); + lv_obj_set_style_pad_row(content, HOME_ROW_GAP - 4, 0); lv_obj_set_flex_flow(content, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_align(content, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); @@ -345,6 +347,13 @@ void ui_home_open(void) { build_octobit(content); build_favorites(content); + lv_obj_t *hint = lv_label_create(s_screen_home); + lv_label_set_text(hint, LV_SYMBOL_DOWN " Apps " LV_SYMBOL_UP " hold: Config"); + lv_obj_set_style_text_font(hint, &lv_font_montserrat_12, 0); + lv_obj_set_style_text_color(hint, current_theme.text_secondary, 0); + lv_obj_set_style_bg_opa(hint, LV_OPA_TRANSP, 0); + lv_obj_align(hint, LV_ALIGN_BOTTOM_MID, 0, -3); + lv_obj_add_event_cb(s_screen_home, home_event_cb, LV_EVENT_KEY, NULL); if (main_group != NULL) { diff --git a/firmware_p4/components/Applications/ui/screens/images/image_viewer_ui.c b/firmware_p4/components/Applications/ui/screens/images/image_viewer_ui.c new file mode 100644 index 000000000..e529703dd --- /dev/null +++ b/firmware_p4/components/Applications/ui/screens/images/image_viewer_ui.c @@ -0,0 +1,433 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#include "image_viewer_ui.h" + +#include "esp_attr.h" + +#include +#include +#include +#include +#include + +#include "esp_heap_caps.h" +#include "esp_log.h" + +#include "lvgl.h" +#include "st7789.h" +#include "ui_feedback.h" +#include "ui_manager.h" +#include "ui_metrics.h" + +static const char *TAG = "IMG_VIEWER"; + +#define VIEW_W ui_screen_w() +#define VIEW_H ui_screen_h() +#define HDR_H 26 +#define FTR_H 24 +#define IMG_AREA_H (VIEW_H - HDR_H - FTR_H) +#define MAX_IMAGES 256 +#define PATH_LEN 288 +#define FOOTER_TXT_LEN 48 +#define LVGL_PATH_PREFIX_MAX 4 + +typedef enum { IMG_NONE, IMG_JPEG, IMG_PNG, IMG_GIF } img_fmt_t; + +static char s_path[PATH_LEN]; +static screen_id_t s_return = SCREEN_FILES; + +static lv_obj_t *s_screen = NULL; +static lv_obj_t *s_area = NULL; +static lv_obj_t *s_hdr = NULL; +static lv_obj_t *s_ftr = NULL; + +static uint8_t *s_blob = NULL; +static lv_image_dsc_t s_raw_dsc; + +static lv_obj_t *s_loading = NULL; +static lv_timer_t *s_decode_timer = NULL; + +EXT_RAM_BSS_ATTR static char s_list[MAX_IMAGES][PATH_LEN]; +static int s_count = 0; +static int s_idx = 0; + +static img_fmt_t fmt_of(const char *name) { + const char *dot = strrchr(name, '.'); + if (dot == NULL) + return IMG_NONE; + if (strcasecmp(dot, ".jpg") == 0 || strcasecmp(dot, ".jpeg") == 0) + return IMG_JPEG; + if (strcasecmp(dot, ".png") == 0) + return IMG_PNG; + if (strcasecmp(dot, ".gif") == 0) + return IMG_GIF; + return IMG_NONE; +} + +static bool is_image(const char *name) { + return fmt_of(name) != IMG_NONE; +} + +static void free_media(void) { + if (s_blob != NULL) { + free(s_blob); + s_blob = NULL; + } + memset(&s_raw_dsc, 0, sizeof(s_raw_dsc)); +} + +static uint8_t *read_file(const char *path, size_t *out_len) { + FILE *f = fopen(path, "rb"); + if (f == NULL) + return NULL; + fseek(f, 0, SEEK_END); + long sz = ftell(f); + fseek(f, 0, SEEK_SET); + if (sz <= 0) { + fclose(f); + return NULL; + } + uint8_t *buf = heap_caps_malloc((size_t)sz, MALLOC_CAP_SPIRAM); + if (buf == NULL) { + fclose(f); + ESP_LOGE(TAG, "OOM reading %s (%ld bytes)", path, sz); + return NULL; + } + size_t rd = fread(buf, 1, (size_t)sz, f); + fclose(f); + if (rd != (size_t)sz) { + free(buf); + return NULL; + } + *out_len = (size_t)sz; + return buf; +} + +static void scan_folder(void) { + s_count = 0; + s_idx = 0; + + char dir[PATH_LEN]; + strncpy(dir, s_path, sizeof(dir) - 1); + dir[sizeof(dir) - 1] = '\0'; + const char *base = s_path; + char *slash = strrchr(dir, '/'); + if (slash != NULL) { + *slash = '\0'; + base = slash + 1; + } else { + strcpy(dir, "."); + } + + DIR *d = opendir(dir); + if (d != NULL) { + struct dirent *ent; + while ((ent = readdir(d)) != NULL && s_count < MAX_IMAGES) { + if (ent->d_name[0] == '.') + continue; + if (ent->d_type == DT_DIR) + continue; + if (!is_image(ent->d_name)) + continue; + if (strlen(dir) + 1 + strlen(ent->d_name) >= PATH_LEN) + continue; + strlcpy(s_list[s_count], dir, PATH_LEN); + strlcat(s_list[s_count], "/", PATH_LEN); + strlcat(s_list[s_count], ent->d_name, PATH_LEN); + if (strcmp(ent->d_name, base) == 0) + s_idx = s_count; + s_count++; + } + closedir(d); + } + + if (s_count == 0) { + strncpy(s_list[0], s_path, PATH_LEN - 1); + s_list[0][PATH_LEN - 1] = '\0'; + s_count = 1; + s_idx = 0; + } +} + +static void fit_image(lv_obj_t *w, int32_t iw, int32_t ih) { + lv_obj_set_size(w, VIEW_W, IMG_AREA_H); + lv_image_set_inner_align(w, LV_IMAGE_ALIGN_CENTER); + + bool oversize = (iw > 0 && ih > 0 && (iw > VIEW_W || ih > IMG_AREA_H)); + if (oversize) { + int32_t sw = (int32_t)((int64_t)VIEW_W * LV_SCALE_NONE / iw); + int32_t sh = (int32_t)((int64_t)IMG_AREA_H * LV_SCALE_NONE / ih); + int32_t s = sw < sh ? sw : sh; + lv_image_set_scale(w, (uint32_t)(s < 1 ? 1 : s)); + } else { + lv_image_set_scale(w, LV_SCALE_NONE); + } + lv_image_set_antialias(w, oversize); + lv_obj_center(w); +} + +static void decode_now(lv_timer_t *t) { + (void)t; + s_decode_timer = NULL; + if (s_area == NULL || s_count <= 0) + return; + + const char *path = s_list[s_idx]; + const char *name = strrchr(path, '/'); + name = (name != NULL) ? name + 1 : path; + + img_fmt_t fmt = fmt_of(name); + size_t len = 0; + lv_obj_t *w = NULL; + int32_t iw = 0, ih = 0; + + if (fmt == IMG_JPEG) { + char lp[PATH_LEN + LVGL_PATH_PREFIX_MAX]; + strlcpy(lp, "A:", sizeof(lp)); + strlcat(lp, (path[0] == '/') ? path + 1 : path, sizeof(lp)); + lv_image_header_t h; + memset(&h, 0, sizeof(h)); + lv_result_t r = lv_image_decoder_get_info(lp, &h); + ESP_LOGI(TAG, + "JPEG '%s': src='%s' get_info=%s %dx%d cf=%d", + name, + lp, + r == LV_RESULT_OK ? "OK" : "FAIL", + (int)h.w, + (int)h.h, + (int)h.cf); + if (r == LV_RESULT_OK) { + iw = h.w; + ih = h.h; + } + w = lv_image_create(s_area); + lv_image_set_src(w, lp); + } else if (fmt == IMG_PNG) { + s_blob = read_file(path, &len); + ESP_LOGI(TAG, "PNG '%s': read=%u bytes", name, (unsigned)len); + if (s_blob != NULL) { + memset(&s_raw_dsc, 0, sizeof(s_raw_dsc)); + s_raw_dsc.header.magic = LV_IMAGE_HEADER_MAGIC; + s_raw_dsc.header.cf = LV_COLOR_FORMAT_RAW; + s_raw_dsc.data = s_blob; + s_raw_dsc.data_size = (uint32_t)len; + lv_image_header_t h; + memset(&h, 0, sizeof(h)); + lv_result_t r = lv_image_decoder_get_info(&s_raw_dsc, &h); + ESP_LOGI(TAG, + "PNG '%s': get_info=%s %dx%d cf=%d", + name, + r == LV_RESULT_OK ? "OK" : "FAIL", + (int)h.w, + (int)h.h, + (int)h.cf); + if (r == LV_RESULT_OK) { + iw = h.w; + ih = h.h; + } + w = lv_image_create(s_area); + lv_image_set_src(w, &s_raw_dsc); + } + } else if (fmt == IMG_GIF) { +#if LV_USE_GIF + s_blob = read_file(path, &len); + if (s_blob != NULL) { + if (len >= 10) { + iw = s_blob[6] | (s_blob[7] << 8); + ih = s_blob[8] | (s_blob[9] << 8); + } + ESP_LOGI(TAG, "GIF '%s': %u bytes, %dx%d", name, (unsigned)len, (int)iw, (int)ih); + memset(&s_raw_dsc, 0, sizeof(s_raw_dsc)); + s_raw_dsc.header.magic = LV_IMAGE_HEADER_MAGIC; + s_raw_dsc.header.cf = LV_COLOR_FORMAT_RAW; + s_raw_dsc.data = s_blob; + s_raw_dsc.data_size = (uint32_t)len; + w = lv_gif_create(s_area); + lv_gif_set_color_format(w, LV_COLOR_FORMAT_RGB565); + lv_gif_set_src(w, &s_raw_dsc); + } +#endif + } + + if (s_loading != NULL) { + lv_obj_del(s_loading); + s_loading = NULL; + } + + if (w == NULL) { + lv_obj_t *ph = lv_label_create(s_area); + lv_label_set_text(ph, LV_SYMBOL_WARNING " cannot display"); + lv_obj_set_style_text_color(ph, lv_color_hex(0xFF5252), 0); + lv_obj_set_style_text_font(ph, &lv_font_montserrat_14, 0); + lv_obj_set_style_text_align(ph, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_center(ph); + return; + } + + fit_image(w, iw, ih); +} + +static void load_current(void) { + if (s_decode_timer != NULL) { + lv_timer_del(s_decode_timer); + s_decode_timer = NULL; + } + if (s_area == NULL) + return; + lv_obj_clean(s_area); + s_loading = NULL; + free_media(); + + if (s_count <= 0) + return; + + const char *path = s_list[s_idx]; + const char *name = strrchr(path, '/'); + name = (name != NULL) ? name + 1 : path; + + if (s_hdr != NULL) + lv_label_set_text(s_hdr, name); + if (s_ftr != NULL) { + char f[FOOTER_TXT_LEN]; + snprintf(f, sizeof(f), "%d/%d " LV_SYMBOL_LEFT " " LV_SYMBOL_RIGHT, s_idx + 1, s_count); + lv_label_set_text(s_ftr, f); + } + + s_loading = lv_label_create(s_area); + lv_label_set_text(s_loading, LV_SYMBOL_REFRESH " Loading..."); + lv_obj_set_style_text_color(s_loading, lv_color_hex(0x8A8594), 0); + lv_obj_set_style_text_font(s_loading, &lv_font_montserrat_14, 0); + lv_obj_center(s_loading); + + s_decode_timer = lv_timer_create(decode_now, 40, NULL); + lv_timer_set_repeat_count(s_decode_timer, 1); +} + +static void go_relative(int dir) { + if (s_count <= 1) + return; + s_idx = (s_idx + dir + s_count) % s_count; + load_current(); + ui_feedback(UI_FB_NAV); +} + +static void image_viewer_input(const input_event_t *ev, void *ctx) { + (void)ctx; + if (ev->action != INPUT_ACTION_PRESS && ev->action != INPUT_ACTION_REPEAT) + return; + const bool press = (ev->action == INPUT_ACTION_PRESS); + switch (ev->button) { + case INPUT_BTN_BACK: + if (press) + ui_switch_screen(s_return); + break; + case INPUT_BTN_RIGHT: + case INPUT_BTN_DOWN: + go_relative(+1); + break; + case INPUT_BTN_LEFT: + case INPUT_BTN_UP: + go_relative(-1); + break; + default: + break; + } +} + +void ui_image_viewer_set_path(const char *path) { + if (path == NULL) { + s_path[0] = '\0'; + return; + } + strncpy(s_path, path, sizeof(s_path) - 1); + s_path[sizeof(s_path) - 1] = '\0'; +} + +void ui_image_viewer_set_return(int screen) { + s_return = (screen_id_t)screen; +} + +void ui_image_viewer_stop(void) { + if (s_decode_timer != NULL) { + lv_timer_del(s_decode_timer); + s_decode_timer = NULL; + } + if (s_area != NULL) + lv_obj_clean(s_area); + free_media(); + s_count = 0; + s_loading = NULL; + s_area = NULL; + s_hdr = NULL; + s_ftr = NULL; +} + +static void on_screen_delete(lv_event_t *e) { + (void)e; + if (s_decode_timer != NULL) { + lv_timer_del(s_decode_timer); + s_decode_timer = NULL; + } + s_loading = NULL; + s_area = NULL; + s_hdr = NULL; + s_ftr = NULL; + free_media(); +} + +void ui_image_viewer_open(void) { + if (s_screen != NULL) { + lv_obj_del(s_screen); + s_screen = NULL; + } + + s_screen = lv_obj_create(NULL); + lv_obj_add_event_cb(s_screen, on_screen_delete, LV_EVENT_DELETE, NULL); + lv_obj_remove_flag(s_screen, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_style_bg_color(s_screen, lv_color_black(), 0); + lv_obj_set_style_bg_opa(s_screen, LV_OPA_COVER, 0); + lv_obj_set_style_pad_all(s_screen, 0, 0); + lv_obj_set_style_border_width(s_screen, 0, 0); + + s_hdr = lv_label_create(s_screen); + lv_obj_set_width(s_hdr, VIEW_W - 16); + lv_label_set_long_mode(s_hdr, LV_LABEL_LONG_DOT); + lv_obj_set_style_text_align(s_hdr, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_style_text_font(s_hdr, &lv_font_montserrat_14, 0); + lv_obj_set_style_text_color(s_hdr, lv_color_hex(0xFFFFFF), 0); + lv_obj_align(s_hdr, LV_ALIGN_TOP_MID, 0, 5); + + s_area = lv_obj_create(s_screen); + lv_obj_remove_flag(s_area, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_size(s_area, VIEW_W, IMG_AREA_H); + lv_obj_align(s_area, LV_ALIGN_TOP_MID, 0, HDR_H); + lv_obj_set_style_bg_color(s_area, lv_color_black(), 0); + lv_obj_set_style_bg_opa(s_area, LV_OPA_COVER, 0); + lv_obj_set_style_pad_all(s_area, 0, 0); + lv_obj_set_style_border_width(s_area, 0, 0); + lv_obj_set_style_radius(s_area, 0, 0); + + s_ftr = lv_label_create(s_screen); + lv_obj_set_style_text_font(s_ftr, &lv_font_montserrat_12, 0); + lv_obj_set_style_text_color(s_ftr, lv_color_hex(0x9AA0A6), 0); + lv_obj_align(s_ftr, LV_ALIGN_BOTTOM_MID, 0, -5); + + ui_input_set_screen_handler(image_viewer_input, NULL); + ui_screen_load_owned(&s_screen, s_screen); + + scan_folder(); + load_current(); +} diff --git a/firmware_p4/components/Applications/ui/screens/images/include/image_viewer_ui.h b/firmware_p4/components/Applications/ui/screens/images/include/image_viewer_ui.h new file mode 100644 index 000000000..61276c7e1 --- /dev/null +++ b/firmware_p4/components/Applications/ui/screens/images/include/image_viewer_ui.h @@ -0,0 +1,57 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#ifndef IMAGE_VIEWER_UI_H +#define IMAGE_VIEWER_UI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Set the image/GIF file to view. Call BEFORE switching to + * SCREEN_IMAGE_VIEWER (e.g. from the Files screen). The string is copied. + * On open the viewer also scans that file's folder for sibling images so + * LEFT/RIGHT flip through them. + * + * @param path Absolute path to the image/GIF file to display. + */ +void ui_image_viewer_set_path(const char *path); + +/** + * @brief Set the screen to return to on BACK. + * + * @param screen Screen to load on BACK, as a screen_id_t value. + */ +void ui_image_viewer_set_return(int screen); + +/** + * @brief Open the immersive viewer: fills the screen with the image (JPEG via + * the LVGL software decoder, PNG via lodepng, animated GIF via lv_gif), + * with a slim filename header and a position/hint footer. + */ +void ui_image_viewer_open(void); + +/** + * @brief Free the decode buffers held for the current image. Registered as the + * screen close hook so navigating away releases PSRAM cleanly. + */ +void ui_image_viewer_stop(void); + +#ifdef __cplusplus +} +#endif + +#endif // IMAGE_VIEWER_UI_H diff --git a/firmware_p4/components/Applications/ui/screens/infrared/ir_burst_ui.c b/firmware_p4/components/Applications/ui/screens/infrared/ir_burst_ui.c index 33218c523..53f2523e7 100644 --- a/firmware_p4/components/Applications/ui/screens/infrared/ir_burst_ui.c +++ b/firmware_p4/components/Applications/ui/screens/infrared/ir_burst_ui.c @@ -15,6 +15,8 @@ #include "ir_burst_ui.h" +#include "esp_attr.h" + #include #include "esp_log.h" @@ -26,12 +28,11 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_semantic.h" #include "ui_theme.h" static const char *TAG = "IR_BURST_UI"; -#define SIG_GREEN 0x00E676 -#define COL_DIM 0x8A8594 #define BAR_TRACK 0x202028 #define IR_BURST_ICON "/assets/icons/bolt.bin" @@ -71,7 +72,7 @@ static lv_obj_t *s_log_label = NULL; static lv_obj_t *s_footer = NULL; static int s_sent = 0; -static ir_store_entry_t s_entries[IR_STORE_MAX_ENTRIES]; +EXT_RAM_BSS_ATTR static ir_store_entry_t s_entries[IR_STORE_MAX_ENTRIES]; static int s_total = 0; static void ir_burst_input(const input_event_t *ev, void *ctx); @@ -157,7 +158,7 @@ void ui_ir_burst_open(void) { s_count_label = lv_label_create(s_screen); lv_label_set_text_fmt(s_count_label, "0 / %d files", s_total); - lv_obj_set_style_text_color(s_count_label, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s_count_label, current_theme.text_secondary, 0); lv_obj_set_style_text_font(s_count_label, &lv_font_montserrat_12, 0); lv_obj_set_style_text_align(s_count_label, LV_TEXT_ALIGN_CENTER, 0); lv_obj_align(s_count_label, LV_ALIGN_TOP_MID, 0, COUNT_Y); @@ -189,7 +190,7 @@ void ui_ir_burst_open(void) { s_log_label = lv_label_create(s_screen); lv_label_set_text(s_log_label, s_total > 0 ? "" : "Capture signals in Learn first"); - lv_obj_set_style_text_color(s_log_label, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s_log_label, current_theme.text_secondary, 0); lv_obj_set_style_text_font(s_log_label, &lv_font_montserrat_12, 0); lv_obj_set_style_text_align(s_log_label, LV_TEXT_ALIGN_CENTER, 0); lv_obj_align(s_log_label, LV_ALIGN_BOTTOM_MID, 0, LOG_Y); @@ -237,7 +238,7 @@ static void burst_tick_cb(lv_timer_t *timer) { if (s_sent >= s_total) { lv_label_set_text(s_status_label, STATUS_DONE); - lv_obj_set_style_text_color(s_status_label, lv_color_hex(SIG_GREEN), 0); + lv_obj_set_style_text_color(s_status_label, lv_color_hex(UI_COL_SUCCESS), 0); lv_label_set_text_fmt(s_count_label, "%d / %d files", s_total, s_total); if (s_footer) ui_chrome_footer_set_text(s_footer, HINT_DONE); diff --git a/firmware_p4/components/Applications/ui/screens/infrared/ir_controller_ui.c b/firmware_p4/components/Applications/ui/screens/infrared/ir_controller_ui.c index f3c18bb98..026e3b513 100644 --- a/firmware_p4/components/Applications/ui/screens/infrared/ir_controller_ui.c +++ b/firmware_p4/components/Applications/ui/screens/infrared/ir_controller_ui.c @@ -25,17 +25,16 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" static const char *TAG = "IR_CTRL_UI"; -#define COL_DIM 0x8A8594 - #define MAX_BTNS 16 #define KEYPAD_W 232 -#define KEYPAD_H 250 #define KEYPAD_Y 44 +#define KEYPAD_H LV_MIN(250, ui_screen_h() - KEYPAD_Y - UI_CHROME_FOOTER_H) #define FLASH_MS 150 @@ -63,7 +62,7 @@ static const char *TAG = "IR_CTRL_UI"; typedef struct { const char *text; int dx, dy, w, h; - const char *sig; // universal-remote signal name in the .ir file, or NULL if none + const char *sig; } rc_btn_t; typedef struct { @@ -101,7 +100,6 @@ static const rc_btn_t SOUND_BTNS[] = { {"MODE", 58, 194, 58, 34, NULL}, }; -// AC uses the state panel (build_ac_panel), not these buttons — sig stays NULL. static const rc_btn_t AC_BTNS[] = { {LV_SYMBOL_POWER, -58, 14, 58, 34, NULL}, {"MODE", 58, 14, 58, 34, NULL}, @@ -149,8 +147,6 @@ static int s_ac_fan = AC_FAN_DEFAULT; static void ir_controller_input(const input_event_t *ev, void *ctx); -// --- Universal remote: send codes sourced from Flipper .ir files on the SD --- - static const char *const UNIVERSAL_FILE[] = { [IR_DEV_TV] = "/sdcard/ir/tv.ir", [IR_DEV_SOUND] = "/sdcard/ir/audio.ir", @@ -168,9 +164,6 @@ static void load_universal(ir_device_t dev) { lv_timer_delete(s_send_timer); s_send_timer = NULL; } - // Free unconditionally: a prior ir_store_load() can partially allocate signals[] - // and then fail (leaving s_uni_loaded false but memory live). ir_file_free is safe - // on a zeroed file, so this covers both the loaded and partial-failure cases. ir_file_free(&s_uni); s_uni_loaded = false; ir_file_init(&s_uni); @@ -190,8 +183,6 @@ static int uni_count(const char *name) { return c; } -// Transmit one matching code per tick — a universal button can map to many -// brand codes, and sending them all inline would stall the UI. static void uni_send_tick(lv_timer_t *t) { if (lv_screen_active() != s_screen) { lv_timer_delete(t); @@ -225,18 +216,17 @@ static void uni_send(const char *name) { s_send_timer = lv_timer_create(uni_send_tick, 90, NULL); } -// Map the AC panel state onto the universal ac.ir signal names. static const char *ac_universal_name(void) { if (!s_ac_power) return "Off"; bool hi = s_ac_temp >= 24; switch (s_ac_mode) { case 0: - return hi ? "Cool_hi" : "Cool_lo"; // Cool + return hi ? "Cool_hi" : "Cool_lo"; case 1: - return hi ? "Heat_hi" : "Heat_lo"; // Heat + return hi ? "Heat_hi" : "Heat_lo"; case 2: - return "Dh"; // Fan / dehumidify + return "Dh"; default: return hi ? "Cool_hi" : "Cool_lo"; } @@ -267,7 +257,7 @@ static void apply_focus_style(lv_obj_t *btn, bool focused) { lv_obj_set_style_shadow_width(btn, 0, 0); lv_obj_set_style_shadow_opa(btn, LV_OPA_TRANSP, 0); if (lbl) - lv_obj_set_style_text_color(lbl, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(lbl, current_theme.text_secondary, 0); } } @@ -300,7 +290,7 @@ static void ac_apply_row_style(lv_obj_t *row, bool focused) { lv_obj_set_style_shadow_width(row, 0, 0); lv_obj_set_style_shadow_opa(row, LV_OPA_TRANSP, 0); if (name) - lv_obj_set_style_text_color(name, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(name, current_theme.text_secondary, 0); } } @@ -313,7 +303,7 @@ static void ac_update_values(void) { lv_label_set_text(s_ac_vals[AC_F_FAN], AC_FANS[s_ac_fan]); lv_color_t active = current_theme.border_accent; - lv_color_t idle = lv_color_hex(COL_DIM); + lv_color_t idle = current_theme.text_secondary; lv_color_t body = s_ac_power ? active : idle; lv_obj_set_style_text_color( s_ac_vals[AC_F_POWER], s_ac_power ? lv_color_hex(AC_ON_COLOR) : idle, 0); diff --git a/firmware_p4/components/Applications/ui/screens/infrared/ir_raw_ui.c b/firmware_p4/components/Applications/ui/screens/infrared/ir_raw_ui.c index 7823fcc0c..acd28d731 100644 --- a/firmware_p4/components/Applications/ui/screens/infrared/ir_raw_ui.c +++ b/firmware_p4/components/Applications/ui/screens/infrared/ir_raw_ui.c @@ -26,6 +26,8 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" +#include "ui_semantic.h" #include "ui_theme.h" #define HDR_TITLE "RAW SIGNAL" @@ -33,7 +35,7 @@ #define FOOTER "OK replay BACK exit" #define MX 8 -#define CONTENT_W (LCD_H_RES - 2 * MX) +#define CONTENT_W (ui_screen_w() - 2 * MX) #define CARD1_Y 50 #define CARD1_H 76 @@ -53,7 +55,7 @@ #define G_VAL_Y 22 #define G_RADIUS 8 -#define CAR_LBL_Y (GRID_Y + G_TILE_H + 8) +#define CAR_LBL_Y LV_MIN(GRID_Y + G_TILE_H + 8, ui_screen_h() - UI_CHROME_FOOTER_H - 66) #define CHIP_Y (CAR_LBL_Y + 20) #define CHIP_H 24 #define CHIP_GAP 6 @@ -61,9 +63,6 @@ #define CHIP_RAD 8 #define KV_Y (CHIP_Y + CHIP_H + 8) -#define COL_DIM 0x8A8594 -#define COL_OK 0x00E676 - #define CARRIER_COUNT 4 #define CHIP_TXT_LEN 10 @@ -100,7 +99,6 @@ static int s_sel = 2; static rmt_symbol_word_t s_raw[IR_MAX_SYMBOLS]; static size_t s_raw_count = 0; -// Pull the last frame the RMT RX captured and derive real scope stats from it. static void compute_stats(void) { s_raw_count = 0; if (ir_rx_init() == ESP_OK) @@ -143,7 +141,7 @@ static void build_scope_card(void) { lv_obj_t *hdr = lv_label_create(card); lv_label_set_text(hdr, "PULSE TRAIN"); lv_obj_set_style_text_font(hdr, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(hdr, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(hdr, current_theme.text_secondary, 0); lv_obj_align(hdr, LV_ALIGN_TOP_LEFT, CARD_PAD_X, CARD_HDR_Y); lv_obj_t *cap_grp = lv_obj_create(card); @@ -163,13 +161,15 @@ static void build_scope_card(void) { lv_obj_set_style_radius(dot, LV_RADIUS_CIRCLE, 0); lv_obj_set_style_border_width(dot, 0, 0); lv_obj_set_style_pad_all(dot, 0, 0); - lv_obj_set_style_bg_color(dot, lv_color_hex(has ? COL_OK : COL_DIM), 0); + lv_obj_set_style_bg_color( + dot, (has ? lv_color_hex(UI_COL_SUCCESS) : current_theme.text_secondary), 0); lv_obj_set_style_bg_opa(dot, LV_OPA_COVER, 0); lv_obj_t *cap_lbl = lv_label_create(cap_grp); lv_label_set_text(cap_lbl, has ? "CAPTURED" : "EMPTY"); lv_obj_set_style_text_font(cap_lbl, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(cap_lbl, lv_color_hex(has ? COL_OK : COL_DIM), 0); + lv_obj_set_style_text_color( + cap_lbl, (has ? lv_color_hex(UI_COL_SUCCESS) : current_theme.text_secondary), 0); lv_obj_t *scope = lv_line_create(card); lv_line_set_points(scope, PULSE_PTS, PULSE_PT_COUNT); @@ -196,7 +196,7 @@ static void build_stat_tile(int i) { lv_obj_t *cap = lv_label_create(tile); lv_label_set_text(cap, STATS[i].cap); lv_obj_set_style_text_font(cap, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(cap, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(cap, current_theme.text_secondary, 0); lv_obj_align(cap, LV_ALIGN_TOP_LEFT, G_TILE_PAD, G_CAP_Y); lv_obj_t *grp = lv_obj_create(tile); @@ -219,7 +219,7 @@ static void build_stat_tile(int i) { lv_obj_t *unit = lv_label_create(grp); lv_label_set_text(unit, STATS[i].unit); lv_obj_set_style_text_font(unit, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(unit, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(unit, current_theme.text_secondary, 0); } } @@ -237,7 +237,7 @@ static void style_chip(int i, bool sel) { lv_obj_set_style_bg_color(s_chip[i], current_theme.bg_primary, 0); lv_obj_set_style_bg_opa(s_chip[i], LV_OPA_COVER, 0); lv_obj_set_style_border_color(s_chip[i], current_theme.border_inactive, 0); - lv_obj_set_style_text_color(s_chip_lbl[i], lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s_chip_lbl[i], current_theme.text_secondary, 0); } } @@ -245,7 +245,7 @@ static void build_carrier(void) { lv_obj_t *lbl = lv_label_create(s_screen); lv_label_set_text(lbl, "CARRIER FREQUENCY"); lv_obj_set_style_text_font(lbl, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(lbl, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(lbl, current_theme.text_secondary, 0); lv_obj_align(lbl, LV_ALIGN_TOP_LEFT, MX, CAR_LBL_Y); lv_obj_t *row = lv_obj_create(s_screen); @@ -283,7 +283,7 @@ static void build_saved_row(void) { lv_obj_t *tag = lv_label_create(s_screen); lv_label_set_text(tag, "Last capture"); lv_obj_set_style_text_font(tag, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(tag, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(tag, current_theme.text_secondary, 0); lv_obj_align(tag, LV_ALIGN_TOP_LEFT, MX, KV_Y); lv_obj_t *name = lv_label_create(s_screen); diff --git a/firmware_p4/components/Applications/ui/screens/infrared/ir_receive_ui.c b/firmware_p4/components/Applications/ui/screens/infrared/ir_receive_ui.c index 4eaa9e5b3..7ee04ef1c 100644 --- a/firmware_p4/components/Applications/ui/screens/infrared/ir_receive_ui.c +++ b/firmware_p4/components/Applications/ui/screens/infrared/ir_receive_ui.c @@ -27,13 +27,12 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_semantic.h" #include "ui_theme.h" #include "waves_ui.h" static const char *TAG = "IR_RX_UI"; -#define SIG_GREEN 0x00E676 - #define HEADER_TITLE_Y 10 #define HEADER_RULE_Y 32 #define HEADER_RULE_W 70 @@ -119,7 +118,7 @@ static void set_status(const char *text, bool success) { return; lv_label_set_text(s_status_label, text); lv_obj_set_style_text_color( - s_status_label, success ? lv_color_hex(SIG_GREEN) : current_theme.text_main, 0); + s_status_label, success ? lv_color_hex(UI_COL_SUCCESS) : current_theme.text_main, 0); } static void set_hint(const char *text) { diff --git a/firmware_p4/components/Applications/ui/screens/infrared/ir_saved_ui.c b/firmware_p4/components/Applications/ui/screens/infrared/ir_saved_ui.c index 0a37c8654..3f18aa823 100644 --- a/firmware_p4/components/Applications/ui/screens/infrared/ir_saved_ui.c +++ b/firmware_p4/components/Applications/ui/screens/infrared/ir_saved_ui.c @@ -15,6 +15,8 @@ #include "ir_saved_ui.h" +#include "esp_attr.h" + #include #include @@ -32,6 +34,7 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" static const char *TAG = "IR_SAVED_UI"; @@ -43,7 +46,6 @@ static const char *TAG = "IR_SAVED_UI"; #define IR_SIGNAL_ICON "/assets/icons/settings_remote.bin" #define IR_CARRIER "38 kHz" -#define FILES_COL_DIM 0x8A8594 #define IRC_LEFT 6 #define IRC_GUTTER 16 #define IRC_TOP_Y 46 @@ -53,9 +55,9 @@ static const char *TAG = "IR_SAVED_UI"; #define IRC_CARD_RADIUS 12 #define IRC_CARD_PAD 10 #define IRC_GLOW_W 14 -#define IRC_TRACK_X 227 +#define IRC_TRACK_X (ui_screen_w() - 13) #define IRC_TRACK_Y 54 -#define IRC_TRACK_LEN 232 +#define IRC_TRACK_LEN LV_MIN(232, ui_screen_h() - UI_CHROME_FOOTER_H - IRC_TRACK_Y) #define IRC_THUMB_H 45 #define IRC_THUMB_ICON "/assets/icons/drag_indicator.bin" @@ -74,14 +76,12 @@ static int s_proto = 0; static int s_file = 0; static bool s_saved = false; -// Real data: every .ir under /sdcard/ir, the distinct protocols across them, -// and the files filtered to the currently-open protocol. -static ir_store_entry_t s_all[IR_STORE_MAX_ENTRIES]; +EXT_RAM_BSS_ATTR static ir_store_entry_t s_all[IR_STORE_MAX_ENTRIES]; static int s_all_count = 0; static char s_protos[MAX_PROTOS][16]; static int s_proto_count = 0; static char s_proto_name[16] = {0}; -static ir_store_entry_t s_files[MAX_FILES]; +EXT_RAM_BSS_ATTR static ir_store_entry_t s_files[MAX_FILES]; static int s_file_count = 0; static lv_obj_t *s_file_list = NULL; @@ -93,7 +93,6 @@ static lv_obj_t *s_file_thumb = NULL; static void build_screen(void); static void ir_saved_input(const input_event_t *ev, void *ctx); -// Re-scan /sdcard/ir and rebuild the distinct-protocol bucket list. static void reload_all(void) { s_all_count = ir_store_list(s_all, IR_STORE_MAX_ENTRIES); if (s_all_count < 0) @@ -115,7 +114,6 @@ static void reload_all(void) { } } -// Filter the flat file list down to the files of one protocol. static void filter_for_proto(const char *proto) { s_file_count = 0; for (int i = 0; i < s_all_count && s_file_count < MAX_FILES; i++) { @@ -128,7 +126,6 @@ static void filter_for_proto(const char *proto) { s_file = 0; } -// Load the selected file and format its first signal's command for display. static void selected_value(char *buf, size_t cap) { buf[0] = '\0'; ir_file_t f; @@ -178,7 +175,7 @@ static void on_rename_submit(const char *text, void *ud) { filter_for_proto(s_proto_name); if (s_file_count == 0) s_level = LEVEL_PROTOCOLS; - lv_async_call(rebuild_async, NULL); + ui_async_call(rebuild_async, NULL); } static void on_delete_confirm(bool confirm) { @@ -190,7 +187,7 @@ static void on_delete_confirm(bool confirm) { reload_all(); filter_for_proto(s_proto_name); s_level = (s_file_count > 0) ? LEVEL_FILES : LEVEL_PROTOCOLS; - lv_async_call(rebuild_async, NULL); + ui_async_call(rebuild_async, NULL); } static void move_file_thumb(void) { @@ -223,7 +220,7 @@ static void style_file_row(int i, bool sel) { lv_obj_set_style_border_opa(card, LV_OPA_60, 0); lv_obj_set_style_shadow_width(card, 0, 0); lv_obj_set_style_shadow_opa(card, LV_OPA_TRANSP, 0); - lv_obj_set_style_text_color(s_file_values[i], lv_color_hex(FILES_COL_DIM), 0); + lv_obj_set_style_text_color(s_file_values[i], current_theme.text_secondary, 0); } } @@ -247,8 +244,9 @@ static void build_files_list(void) { lv_obj_t *cont = lv_obj_create(s_screen); s_file_list = cont; - lv_obj_set_size( - cont, LCD_H_RES - IRC_LEFT - IRC_GUTTER, LCD_V_RES - IRC_TOP_Y - UI_CHROME_FOOTER_H - 4); + lv_obj_set_size(cont, + ui_screen_w() - IRC_LEFT - IRC_GUTTER, + ui_screen_h() - IRC_TOP_Y - UI_CHROME_FOOTER_H - 4); lv_obj_align(cont, LV_ALIGN_TOP_LEFT, IRC_LEFT, IRC_TOP_Y); lv_obj_set_style_bg_opa(cont, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(cont, 0, 0); @@ -300,7 +298,7 @@ static void build_files_list(void) { lv_label_set_long_mode(proto, LV_LABEL_LONG_DOT); lv_label_set_text(proto, s_files[i].proto); lv_obj_set_style_text_font(proto, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(proto, lv_color_hex(FILES_COL_DIM), 0); + lv_obj_set_style_text_color(proto, current_theme.text_secondary, 0); lv_obj_align(proto, LV_ALIGN_TOP_LEFT, 0, 20); lv_obj_t *pulse = lv_line_create(card); diff --git a/firmware_p4/components/Applications/ui/screens/infrared/ir_send_ui.c b/firmware_p4/components/Applications/ui/screens/infrared/ir_send_ui.c index 644ccab18..2d6d3f928 100644 --- a/firmware_p4/components/Applications/ui/screens/infrared/ir_send_ui.c +++ b/firmware_p4/components/Applications/ui/screens/infrared/ir_send_ui.c @@ -15,6 +15,8 @@ #include "ir_send_ui.h" +#include "esp_attr.h" + #include #include "esp_log.h" @@ -29,13 +31,12 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_semantic.h" #include "ui_theme.h" static const char *TAG = "IR_SEND_UI"; -#define SIG_GREEN 0x00E676 -#define COL_DIM 0x8A8594 -#define IR_ICON "/assets/icons/podcasts.bin" +#define IR_ICON "/assets/icons/podcasts.bin" #define TICK_MS 50 #define SENDING_MS 1600 @@ -55,7 +56,7 @@ static const char *TAG = "IR_SEND_UI"; #define HINT_SENT "BACK = Exit" #define HINT_OPTIONS "UP/DOWN choose OK do BACK exit" -#define EMPTY_NAME "No signals — use Learn" +#define EMPTY_NAME "No signals - use Learn" typedef enum { VIEW_LIST = 0, @@ -68,7 +69,7 @@ static menu_component_t s_menu; static send_view_t s_view = VIEW_LIST; static int s_sel = 0; -static ir_store_entry_t s_entries[IR_STORE_MAX_ENTRIES]; +EXT_RAM_BSS_ATTR static ir_store_entry_t s_entries[IR_STORE_MAX_ENTRIES]; static int s_count = 0; static lv_timer_t *s_tick_timer = NULL; @@ -151,7 +152,7 @@ static void set_status(const char *text, bool success) { return; lv_label_set_text(s_status_label, text); lv_obj_set_style_text_color( - s_status_label, success ? lv_color_hex(SIG_GREEN) : current_theme.text_main, 0); + s_status_label, success ? lv_color_hex(UI_COL_SUCCESS) : current_theme.text_main, 0); } static void set_hint(const char *text) { diff --git a/firmware_p4/components/Applications/ui/screens/lora/lora_channels_ui.c b/firmware_p4/components/Applications/ui/screens/lora/lora_channels_ui.c index ac530b28a..c78643597 100644 --- a/firmware_p4/components/Applications/ui/screens/lora/lora_channels_ui.c +++ b/firmware_p4/components/Applications/ui/screens/lora/lora_channels_ui.c @@ -32,6 +32,7 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #define HDR_TITLE "CHANNELS" @@ -39,7 +40,7 @@ #define FOOTER_HINT "UP/DN OK RENAME RIGHT OFF BACK" #define BODY_TOP UI_CHROME_HEADER_H -#define BODY_H (LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) +#define BODY_H (ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) #define GRID_PAD 7 #define GRID_GAP 6 @@ -70,7 +71,6 @@ #define COL_ACC2 0xB89AFF #define COL_CYAN 0x37E0A8 -#define COL_DIM 0x8A8594 #define COL_SLOT 0x4A4556 typedef struct { @@ -170,7 +170,7 @@ static void build_filled_tile(lv_obj_t *tile, int i) { lv_label_set_text_fmt(idx, "%d", i); lv_obj_set_style_text_font(idx, &lv_font_montserrat_12, 0); lv_obj_set_style_text_color( - idx, primary ? current_theme.border_accent : lv_color_hex(COL_DIM), 0); + idx, primary ? current_theme.border_accent : current_theme.text_secondary, 0); lv_obj_t *d = lv_obj_create(head); lv_obj_remove_flag(d, LV_OBJ_FLAG_SCROLLABLE); @@ -206,7 +206,7 @@ static void build_filled_tile(lv_obj_t *tile, int i) { lv_obj_t *stub = lv_label_create(tile); lv_label_set_text(stub, s_slot[i].role); lv_obj_set_style_text_font(stub, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(stub, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(stub, current_theme.text_secondary, 0); lv_obj_t *bars = bare_box(tile, lv_pct(100), BAR_MAX_H); lv_obj_set_flex_flow(bars, LV_FLEX_FLOW_ROW); @@ -299,7 +299,7 @@ static void build_grid_content(void) { lv_obj_add_flag(s_empty_msg, LV_OBJ_FLAG_FLOATING); lv_label_set_text(s_empty_msg, "Start a protocol first"); lv_obj_set_style_text_font(s_empty_msg, &lv_font_montserrat_14, 0); - lv_obj_set_style_text_color(s_empty_msg, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s_empty_msg, current_theme.text_secondary, 0); lv_obj_center(s_empty_msg); return; } @@ -438,7 +438,7 @@ void ui_lora_channels_open(void) { ui_chrome_header(s_screen, HDR_TITLE, HDR_ICON); lv_obj_t *grid = lv_obj_create(s_screen); - lv_obj_set_size(grid, LCD_H_RES, BODY_H); + lv_obj_set_size(grid, ui_screen_w(), BODY_H); lv_obj_align(grid, LV_ALIGN_TOP_MID, 0, BODY_TOP); lv_obj_set_style_bg_opa(grid, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(grid, 0, 0); diff --git a/firmware_p4/components/Applications/ui/screens/lora/lora_chat_ui.c b/firmware_p4/components/Applications/ui/screens/lora/lora_chat_ui.c index d4a65826d..30107dde9 100644 --- a/firmware_p4/components/Applications/ui/screens/lora/lora_chat_ui.c +++ b/firmware_p4/components/Applications/ui/screens/lora/lora_chat_ui.c @@ -26,6 +26,7 @@ #include "assets_manager.h" #include "keyboard_ui.h" #include "lora_session.h" +#include "meshcore_phoneapi.h" #include "menu_component_ui.h" #include "meshtastic_presets.h" #include "meshtastic_regions.h" @@ -36,6 +37,8 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" +#include "ui_semantic.h" #include "ui_theme.h" #include "waves_ui.h" @@ -45,8 +48,6 @@ static const char *TAG = "LORA_MESH"; #define CHAT_POLL_MS 500 #define CHAT_BATCH 8 #define LORA_START_TASK_STACK 12288 -#define SIG_GREEN 0x00E676 -#define COL_DIM 0x8A8594 #define ENTRY_MS 220 #define BADGE_SIZE 28 @@ -319,7 +320,7 @@ static void build_home_view(void) { menu_component_add_item(&s_menu, "/assets/icons/lan.bin", "Traceroute"); if (s_linked) - menu_component_set_item_label_color(&s_menu, 0, lv_color_hex(SIG_GREEN)); + menu_component_set_item_label_color(&s_menu, 0, lv_color_hex(UI_COL_SUCCESS)); menu_component_select(&s_menu, s_home_sel); menu_component_set_hint(&s_menu, @@ -328,15 +329,15 @@ static void build_home_view(void) { static void link_style(int i, lv_color_t *col, int *w, bool *dashed) { if (!s_nodes[i].strong) { - *col = lv_color_hex(COL_DIM); + *col = current_theme.text_secondary; *w = 1; *dashed = true; } else if (s_nodes[i].rssi >= RSSI_STRONG) { - *col = lv_color_hex(SIG_GREEN); + *col = lv_color_hex(UI_COL_SUCCESS); *w = 3; *dashed = false; } else if (s_nodes[i].rssi >= RSSI_GOOD) { - *col = lv_color_hex(SIG_GREEN); + *col = lv_color_hex(UI_COL_SUCCESS); *w = 2; *dashed = false; } else { @@ -351,8 +352,9 @@ static void node_style_pin(int i, bool selected) { if (pin == NULL) return; bool online = s_nodes[i].strong; - lv_color_t edge = selected ? current_theme.border_accent - : (online ? lv_color_hex(SIG_GREEN) : lv_color_hex(COL_DIM)); + lv_color_t edge = selected + ? current_theme.border_accent + : (online ? lv_color_hex(UI_COL_SUCCESS) : current_theme.text_secondary); lv_obj_set_style_border_color(pin, edge, 0); lv_obj_set_style_border_width(pin, selected ? 2 : 1, 0); lv_obj_set_style_opa(pin, (online || selected) ? LV_OPA_COVER : LV_OPA_50, 0); @@ -366,11 +368,11 @@ static void node_style_pin(int i, bool selected) { lv_obj_set_style_shadow_opa(pin, LV_OPA_TRANSP, 0); } if (s_node_names[i] != NULL) - lv_obj_set_style_text_color(s_node_names[i], - selected - ? current_theme.border_accent - : (online ? current_theme.text_main : lv_color_hex(COL_DIM)), - 0); + lv_obj_set_style_text_color( + s_node_names[i], + selected ? current_theme.border_accent + : (online ? current_theme.text_main : current_theme.text_secondary), + 0); } static void node_update_info(int i) { @@ -380,7 +382,7 @@ static void node_update_info(int i) { if (s_info_name != NULL) { lv_label_set_text(s_info_name, s_nodes[i].name); lv_obj_set_style_text_color( - s_info_name, online ? current_theme.text_main : lv_color_hex(COL_DIM), 0); + s_info_name, online ? current_theme.text_main : current_theme.text_secondary, 0); } if (s_info_meta != NULL) { char meta[40]; @@ -389,7 +391,7 @@ static void node_update_info(int i) { snprintf(meta, sizeof(meta), "%s \xC2\xB7 %s", rssi_s, online ? "ONLINE" : "OFFLINE"); lv_label_set_text(s_info_meta, meta); lv_obj_set_style_text_color( - s_info_meta, online ? lv_color_hex(SIG_GREEN) : lv_color_hex(COL_DIM), 0); + s_info_meta, online ? lv_color_hex(UI_COL_SUCCESS) : current_theme.text_secondary, 0); } if (s_info_bar != NULL) { int pct = (s_nodes[i].rssi - SNR_RSSI_FLOOR) * 100 / SNR_RSSI_SPAN; @@ -429,10 +431,10 @@ static void node_style_row(int i, bool selected) { } if (s_list_name[i] != NULL) lv_obj_set_style_text_color( - s_list_name[i], selected ? current_theme.text_main : lv_color_hex(COL_DIM), 0); + s_list_name[i], selected ? current_theme.text_main : current_theme.text_secondary, 0); if (s_list_val[i] != NULL) lv_obj_set_style_text_color( - s_list_val[i], selected ? current_theme.border_accent : lv_color_hex(COL_DIM), 0); + s_list_val[i], selected ? current_theme.border_accent : current_theme.text_secondary, 0); } static void node_select(int sel) { @@ -457,7 +459,8 @@ static void build_nodes_list(void) { online++; s_node_list = lv_obj_create(s_screen); - lv_obj_set_size(s_node_list, lv_pct(100), LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); + lv_obj_set_size( + s_node_list, lv_pct(100), ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); lv_obj_align(s_node_list, LV_ALIGN_TOP_MID, 0, UI_CHROME_HEADER_H); lv_obj_set_style_bg_opa(s_node_list, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(s_node_list, 0, 0); @@ -477,13 +480,13 @@ static void build_nodes_list(void) { lv_obj_t *cap = lv_label_create(s_node_list); lv_label_set_text_fmt(cap, "%d / %d s_nodes ONLINE", online, s_rnode_count); - lv_obj_set_style_text_color(cap, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(cap, current_theme.text_secondary, 0); lv_obj_set_style_text_font(cap, &lv_font_montserrat_12, 0); if (s_rnode_count == 0) { lv_obj_t *empty = lv_label_create(s_node_list); lv_label_set_text(empty, "Listening for nodes..."); - lv_obj_set_style_text_color(empty, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(empty, current_theme.text_secondary, 0); lv_obj_set_style_text_font(empty, &lv_font_montserrat_14, 0); } @@ -517,10 +520,10 @@ static void build_nodes_list(void) { lv_obj_set_style_radius(dot, LV_RADIUS_CIRCLE, 0); lv_obj_set_style_border_width(dot, 0, 0); lv_obj_set_style_bg_color( - dot, node_online ? lv_color_hex(SIG_GREEN) : lv_color_hex(COL_DIM), 0); + dot, node_online ? lv_color_hex(UI_COL_SUCCESS) : current_theme.text_secondary, 0); lv_obj_set_style_bg_opa(dot, LV_OPA_COVER, 0); if (node_online) { - lv_obj_set_style_shadow_color(dot, lv_color_hex(SIG_GREEN), 0); + lv_obj_set_style_shadow_color(dot, lv_color_hex(UI_COL_SUCCESS), 0); lv_obj_set_style_shadow_width(dot, LIST_DOT_GLOW_W, 0); lv_obj_set_style_shadow_opa(dot, LV_OPA_50, 0); } @@ -545,6 +548,26 @@ static void build_nodes_list(void) { node_select(s_node); } +#define MAP_DESIGN_CX 120 +#define MAP_DESIGN_CY 150 +#define MAP_MAX_OFFY 84 + +static int map_scale_num(void) { + int cy = ui_screen_h() / 2; + int room_up = cy - UI_CHROME_HEADER_H - 22; + int room_dn = ui_screen_h() - MESH_INFO_BOT - MESH_INFO_H - cy - 8; + int room = LV_MIN(room_up, room_dn); + if (room < 20) + room = 20; + return LV_MIN(room, MAP_MAX_OFFY); +} + +static void map_xy(int nx, int ny, int *px, int *py) { + int num = map_scale_num(); + *px = ui_screen_w() / 2 + (nx - MAP_DESIGN_CX) * num / MAP_MAX_OFFY; + *py = ui_screen_h() / 2 + (ny - MAP_DESIGN_CY) * num / MAP_MAX_OFFY; +} + static void build_nodes_view(void) { lv_color_t accent = ui_theme_get_accent(); ui_chrome_header(s_screen, "s_nodes", "/assets/icons/hub.bin"); @@ -569,7 +592,7 @@ static void build_nodes_view(void) { online++; lv_obj_t *cap = lv_label_create(s_screen); lv_label_set_text_fmt(cap, "%d / %d s_nodes ONLINE", online, s_rnode_count); - lv_obj_set_style_text_color(cap, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(cap, current_theme.text_secondary, 0); lv_obj_set_style_text_font(cap, &lv_font_montserrat_12, 0); lv_obj_align(cap, LV_ALIGN_TOP_MID, 0, MESH_CAP_Y); @@ -578,10 +601,13 @@ static void build_nodes_view(void) { int w; bool dashed; link_style(i, &col, &w, &dashed); - s_link_pts[i][0].x = MAP_CENTER_X; - s_link_pts[i][0].y = MAP_CENTER_Y; - s_link_pts[i][1].x = NODE_POS[i].x; - s_link_pts[i][1].y = NODE_POS[i].y; + int cx, cy, nx, ny; + map_xy(MAP_CENTER_X, MAP_CENTER_Y, &cx, &cy); + map_xy(NODE_POS[i].x, NODE_POS[i].y, &nx, &ny); + s_link_pts[i][0].x = cx; + s_link_pts[i][0].y = cy; + s_link_pts[i][1].x = nx; + s_link_pts[i][1].y = ny; lv_obj_t *ln = lv_line_create(s_screen); lv_obj_align(ln, LV_ALIGN_TOP_LEFT, 0, 0); lv_line_set_points(ln, s_link_pts[i], 2); @@ -601,8 +627,9 @@ static void build_nodes_view(void) { lv_obj_set_style_pad_row(wrap, 1, 0); lv_obj_set_flex_flow(wrap, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_align(wrap, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); - lv_obj_align( - wrap, LV_ALIGN_CENTER, NODE_POS[i].x - LCD_H_RES / 2, NODE_POS[i].y - LCD_V_RES / 2); + int nx, ny; + map_xy(NODE_POS[i].x, NODE_POS[i].y, &nx, &ny); + lv_obj_align(wrap, LV_ALIGN_CENTER, nx - ui_screen_w() / 2, ny - ui_screen_h() / 2); s_node_pins[i] = make_badge(wrap, "/assets/icons/settings_input_antenna.bin", accent); @@ -623,8 +650,9 @@ static void build_nodes_view(void) { lv_obj_set_style_pad_row(you_wrap, 1, 0); lv_obj_set_flex_flow(you_wrap, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_align(you_wrap, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); - lv_obj_align( - you_wrap, LV_ALIGN_CENTER, MAP_CENTER_X - LCD_H_RES / 2, MAP_CENTER_Y - LCD_V_RES / 2); + int you_x, you_y; + map_xy(MAP_CENTER_X, MAP_CENTER_Y, &you_x, &you_y); + lv_obj_align(you_wrap, LV_ALIGN_CENTER, you_x - ui_screen_w() / 2, you_y - ui_screen_h() / 2); lv_obj_t *you_pin = make_badge(you_wrap, "/assets/icons/hub.bin", accent); lv_obj_set_size(you_pin, YOU_PIN_SZ, YOU_PIN_SZ); @@ -697,7 +725,7 @@ static void build_configs_view(void) { } static lv_obj_t *make_companion_card(lv_obj_t *parent, bool linked, lv_color_t accent) { - lv_color_t edge = linked ? lv_color_hex(SIG_GREEN) : accent; + lv_color_t edge = linked ? lv_color_hex(UI_COL_SUCCESS) : accent; lv_obj_t *card = lit_panel(parent, 200, 60, edge); lv_obj_set_style_pad_all(card, 8, 0); lv_obj_set_flex_flow(card, LV_FLEX_FLOW_ROW); @@ -722,13 +750,14 @@ static lv_obj_t *make_companion_card(lv_obj_t *parent, bool linked, lv_color_t a lv_obj_t *sub = lv_label_create(txt); lv_label_set_text(sub, linked ? "Linked \xC2\xB7 BLE" : "v1.2 \xC2\xB7 BLE"); - lv_obj_set_style_text_color(sub, linked ? lv_color_hex(SIG_GREEN) : lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color( + sub, linked ? lv_color_hex(UI_COL_SUCCESS) : current_theme.text_secondary, 0); lv_obj_set_style_text_font(sub, &lv_font_montserrat_12, 0); if (linked) { lv_obj_t *chk = lv_label_create(card); lv_label_set_text(chk, LV_SYMBOL_OK); - lv_obj_set_style_text_color(chk, lv_color_hex(SIG_GREEN), 0); + lv_obj_set_style_text_color(chk, lv_color_hex(UI_COL_SUCCESS), 0); lv_obj_set_style_text_font(chk, &lv_font_montserrat_14, 0); } return card; @@ -779,6 +808,15 @@ static void build_connect(void) { lv_obj_set_style_text_font(s_status_label, &lv_font_montserrat_14, 0); lv_obj_align(s_status_label, LV_ALIGN_TOP_MID, 0, 52); + if (proto_for_sel(s_proto) == LORA_PROTO_MESHCORE) { + lv_obj_t *pin_lbl = lv_label_create(s_screen); + lv_label_set_text_fmt( + pin_lbl, "Pairing PIN %06lu", (unsigned long)meshcore_phoneapi_get_pin()); + lv_obj_set_style_text_color(pin_lbl, accent, 0); + lv_obj_set_style_text_font(pin_lbl, &lv_font_montserrat_16, 0); + lv_obj_align(pin_lbl, LV_ALIGN_TOP_MID, 0, 76); + } + lv_anim_t ad; lv_anim_init(&ad); lv_anim_set_var(&ad, s_status_label); @@ -800,7 +838,7 @@ static void build_connect(void) { } else { lv_obj_t *st = lv_label_create(s_screen); lv_label_set_text(st, "Companion linked!"); - lv_obj_set_style_text_color(st, lv_color_hex(SIG_GREEN), 0); + lv_obj_set_style_text_color(st, lv_color_hex(UI_COL_SUCCESS), 0); lv_obj_set_style_text_font(st, &lv_font_montserrat_14, 0); lv_obj_align(st, LV_ALIGN_TOP_MID, 0, 52); lv_obj_fade_in(st, ENTRY_MS, 0); @@ -918,7 +956,8 @@ static void build_chat(void) { s_hint = ui_chrome_footer(s_screen, LV_SYMBOL_KEYBOARD " OK write BACK nodes"); s_chat_list = lv_obj_create(s_screen); - lv_obj_set_size(s_chat_list, LCD_H_RES, LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); + lv_obj_set_size( + s_chat_list, ui_screen_w(), ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); lv_obj_align(s_chat_list, LV_ALIGN_TOP_LEFT, 0, UI_CHROME_HEADER_H); lv_obj_set_style_bg_opa(s_chat_list, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(s_chat_list, 0, 0); diff --git a/firmware_p4/components/Applications/ui/screens/lora/lora_mqtt_ui.c b/firmware_p4/components/Applications/ui/screens/lora/lora_mqtt_ui.c index d765b98e9..4b75859ed 100644 --- a/firmware_p4/components/Applications/ui/screens/lora/lora_mqtt_ui.c +++ b/firmware_p4/components/Applications/ui/screens/lora/lora_mqtt_ui.c @@ -24,13 +24,11 @@ #include "notify_ui.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_semantic.h" #include "ui_theme.h" static const char *TAG = "LORA_MQTT"; -#define SIG_GREEN 0x00E676 -#define COL_DIM 0x8A8594 - #define FIELD_MAX 40 #define PASS_MASK "******" #define PASS_UNSET "(unset)" @@ -88,10 +86,12 @@ static void apply_status(void) { menu_component_set_selector_value( &s_menu, ROW_STATUS, s_connected ? "Connected" : "Disconnected"); menu_component_set_selector_value(&s_menu, ROW_ACTION, s_connected ? "Disconnect" : "Connect"); + menu_component_set_item_label_color(&s_menu, + ROW_STATUS, + s_connected ? lv_color_hex(UI_COL_SUCCESS) + : current_theme.text_secondary); menu_component_set_item_label_color( - &s_menu, ROW_STATUS, s_connected ? lv_color_hex(SIG_GREEN) : lv_color_hex(COL_DIM)); - menu_component_set_item_label_color( - &s_menu, ROW_ACTION, s_connected ? lv_color_hex(SIG_GREEN) : current_theme.text_main); + &s_menu, ROW_ACTION, s_connected ? lv_color_hex(UI_COL_SUCCESS) : current_theme.text_main); } static void toggle_connect(void) { diff --git a/firmware_p4/components/Applications/ui/screens/lora/lora_position_ui.c b/firmware_p4/components/Applications/ui/screens/lora/lora_position_ui.c index 93a40805c..36c161d80 100644 --- a/firmware_p4/components/Applications/ui/screens/lora/lora_position_ui.c +++ b/firmware_p4/components/Applications/ui/screens/lora/lora_position_ui.c @@ -31,6 +31,8 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" +#include "ui_semantic.h" #include "ui_theme.h" #define HDR_TITLE "POSITION" @@ -38,7 +40,7 @@ #define FOOTER_HINT "MOVE " LV_SYMBOL_RIGHT " EDIT OK BCAST BACK" #define BODY_TOP UI_CHROME_HEADER_H -#define BODY_H (LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) +#define BODY_H (ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) #define ROOT_PAD 8 #define ROOT_GAP 7 @@ -60,8 +62,6 @@ #define BC_RAD 8 #define BC_PAD_H 9 -#define COL_DIM 0x8A8594 -#define COL_OK 0x00E676 #define COL_BC_BORDER 0x234A3E #define LAT_E7_LIMIT 900000000 @@ -226,7 +226,7 @@ static void refresh_fields(void) { lv_obj_set_style_shadow_width(s_row[i], act ? GLOW_W : 0, 0); lv_obj_set_style_shadow_opa(s_row[i], act ? LV_OPA_30 : LV_OPA_TRANSP, 0); lv_obj_set_style_text_color( - s_tag[i], act ? current_theme.border_accent : lv_color_hex(COL_DIM), 0); + s_tag[i], act ? current_theme.border_accent : current_theme.text_secondary, 0); if (act) lv_obj_remove_flag(s_caret[i], LV_OBJ_FLAG_HIDDEN); else @@ -255,11 +255,11 @@ static void refresh_broadcast(void) { return; if (s_bcast) { lv_label_set_text(s_bc_val, "ON"); - lv_obj_set_style_text_color(s_bc_val, lv_color_hex(COL_OK), 0); + lv_obj_set_style_text_color(s_bc_val, lv_color_hex(UI_COL_SUCCESS), 0); lv_obj_set_style_border_color(s_bc_row, lv_color_hex(COL_BC_BORDER), 0); } else { lv_label_set_text(s_bc_val, "OFF"); - lv_obj_set_style_text_color(s_bc_val, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s_bc_val, current_theme.text_secondary, 0); lv_obj_set_style_border_color(s_bc_row, current_theme.border_inactive, 0); } } @@ -362,10 +362,10 @@ static void build_placeholder(void) { lv_obj_t *msg = lv_label_create(s_screen); lv_label_set_long_mode(msg, LV_LABEL_LONG_WRAP); - lv_obj_set_width(msg, LCD_H_RES - 2 * ROOT_PAD); + lv_obj_set_width(msg, ui_screen_w() - 2 * ROOT_PAD); lv_label_set_text(msg, "Start a protocol first"); lv_obj_set_style_text_font(msg, &lv_font_montserrat_14, 0); - lv_obj_set_style_text_color(msg, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(msg, current_theme.text_secondary, 0); lv_obj_set_style_text_align(msg, LV_TEXT_ALIGN_CENTER, 0); lv_obj_center(msg); @@ -418,7 +418,7 @@ void ui_lora_position_open(void) { lv_obj_t *root = lv_obj_create(s_screen); lv_obj_remove_flag(root, LV_OBJ_FLAG_SCROLLABLE); - lv_obj_set_size(root, LCD_H_RES, BODY_H); + lv_obj_set_size(root, ui_screen_w(), BODY_H); lv_obj_align(root, LV_ALIGN_TOP_MID, 0, BODY_TOP); lv_obj_set_style_bg_opa(root, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(root, 0, 0); @@ -443,7 +443,7 @@ void ui_lora_position_open(void) { lv_obj_t *cap = lv_label_create(card); lv_label_set_text(cap, "FIXED POSITION"); lv_obj_set_style_text_font(cap, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(cap, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(cap, current_theme.text_secondary, 0); for (int i = 0; i < FIELD_CNT; i++) make_field(card, i); @@ -483,7 +483,7 @@ void ui_lora_position_open(void) { ? "manual fix (no GPS) \xE2\x80\xA2 rebroadcast every 15 min" : "manual fix (no GPS) \xE2\x80\xA2 sent in each advert"); lv_obj_set_style_text_font(cap2, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(cap2, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(cap2, current_theme.text_secondary, 0); lv_obj_set_style_text_align(cap2, LV_TEXT_ALIGN_CENTER, 0); refresh_fields(); diff --git a/firmware_p4/components/Applications/ui/screens/lora/lora_rnode_ui.c b/firmware_p4/components/Applications/ui/screens/lora/lora_rnode_ui.c index 8b1709923..eded3a9bb 100644 --- a/firmware_p4/components/Applications/ui/screens/lora/lora_rnode_ui.c +++ b/firmware_p4/components/Applications/ui/screens/lora/lora_rnode_ui.c @@ -22,6 +22,8 @@ #include "ui_chrome.h" #include "ui_manager.h" +#include "ui_metrics.h" +#include "ui_semantic.h" #include "ui_theme.h" static const char *TAG = "LORA_RNODE"; @@ -29,12 +31,10 @@ static const char *TAG = "LORA_RNODE"; #define DATA_TICK_MS 700 #define ENTRY_MS 220 -#define SIG_GREEN 0x00E676 -#define COL_DIM 0x8A8594 - -#define CARD_W 216 -#define CFG_CARD_H 110 -#define CNT_CARD_H 82 +#define CARD_W 216 +#define CFG_CARD_H 110 +#define CNT_CARD_H \ + LV_MIN(82, ui_screen_h() - (BODY_TOP_Y + CFG_CARD_H + CARD_GAP) - UI_CHROME_FOOTER_H) #define CARD_RADIUS 13 #define CARD_PAD 12 #define CARD_GAP 12 @@ -122,7 +122,7 @@ static void build_config_card(lv_obj_t *parent, lv_color_t accent) { lv_obj_set_size(dot, DOT_SIZE, DOT_SIZE); lv_obj_set_style_radius(dot, LV_RADIUS_CIRCLE, 0); lv_obj_set_style_border_width(dot, 0, 0); - lv_obj_set_style_bg_color(dot, lv_color_hex(SIG_GREEN), 0); + lv_obj_set_style_bg_color(dot, lv_color_hex(UI_COL_SUCCESS), 0); lv_obj_set_style_bg_opa(dot, LV_OPA_COVER, 0); lv_anim_t a; @@ -152,13 +152,13 @@ static void build_counter_card(lv_obj_t *parent, lv_color_t accent) { s_counter_lbl = lv_label_create(card); lv_label_set_text_fmt(s_counter_lbl, "RX %lu TX %lu", s_rx, s_tx); - lv_obj_set_style_text_color(s_counter_lbl, lv_color_hex(SIG_GREEN), 0); + lv_obj_set_style_text_color(s_counter_lbl, lv_color_hex(UI_COL_SUCCESS), 0); lv_obj_set_style_text_font(s_counter_lbl, &lv_font_montserrat_14, 0); s_last_lbl = lv_label_create(card); lv_label_set_text_fmt( s_last_lbl, "Last: RSSI %d SNR %d.%d", s_last_rssi, s_last_snr / 10, s_last_snr % 10); - lv_obj_set_style_text_color(s_last_lbl, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s_last_lbl, current_theme.text_secondary, 0); lv_obj_set_style_text_font(s_last_lbl, &lv_font_montserrat_12, 0); } diff --git a/firmware_p4/components/Applications/ui/screens/lora/lora_securedm_ui.c b/firmware_p4/components/Applications/ui/screens/lora/lora_securedm_ui.c index 59a63eee0..3ea447258 100644 --- a/firmware_p4/components/Applications/ui/screens/lora/lora_securedm_ui.c +++ b/firmware_p4/components/Applications/ui/screens/lora/lora_securedm_ui.c @@ -32,6 +32,8 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" +#include "ui_semantic.h" #include "ui_theme.h" #define HDR_TITLE "SECURE DM" @@ -40,7 +42,7 @@ #define FOOTER_CHAT "OK WRITE UP/DOWN SCROLL BACK" #define BODY_TOP UI_CHROME_HEADER_H -#define BODY_BOT (LCD_V_RES - UI_CHROME_FOOTER_H) +#define BODY_BOT (ui_screen_h() - UI_CHROME_FOOTER_H) #define BANNER_H 36 #define INPUT_H 32 @@ -78,8 +80,6 @@ #define DM_MAX_CONTACTS 32 -#define COL_DIM 0x8A8594 -#define COL_OK 0x00E676 #define COL_OUTTX 0xF2EEFF #define COL_OUTGR 0x2A1F52 @@ -238,11 +238,11 @@ static lv_obj_t *make_seal(lv_obj_t *parent, lv_color_t col) { } static void build_banner(lv_obj_t *parent, const char *name, const char *sub, bool secure) { - lv_color_t acc = secure ? lv_color_hex(COL_OK) : lv_color_hex(COL_DIM); + lv_color_t acc = secure ? lv_color_hex(UI_COL_SUCCESS) : current_theme.text_secondary; lv_obj_t *banner = lv_obj_create(parent); lv_obj_remove_flag(banner, LV_OBJ_FLAG_SCROLLABLE); - lv_obj_set_size(banner, LCD_H_RES, BANNER_H); + lv_obj_set_size(banner, ui_screen_w(), BANNER_H); lv_obj_align(banner, LV_ALIGN_TOP_MID, 0, BODY_TOP); lv_obj_set_style_radius(banner, 0, 0); lv_obj_set_style_bg_color(banner, acc, 0); @@ -335,7 +335,7 @@ static void add_bubble(lv_obj_t *list, bool outgoing, const char *text, const ch if (outgoing) { lv_obj_set_flex_align(meta, LV_FLEX_ALIGN_END, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); } else { - make_seal(meta, lv_color_hex(COL_OK)); + make_seal(meta, lv_color_hex(UI_COL_SUCCESS)); } lv_obj_t *tlbl = lv_label_create(meta); @@ -347,7 +347,7 @@ static void add_bubble(lv_obj_t *list, bool outgoing, const char *text, const ch lv_obj_set_style_text_opa(tlbl, LV_OPA_70, 0); } else { lv_label_set_text(tlbl, ts); - lv_obj_set_style_text_color(tlbl, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(tlbl, current_theme.text_secondary, 0); } lv_obj_set_style_text_font(tlbl, &lv_font_montserrat_12, 0); @@ -356,7 +356,7 @@ static void add_bubble(lv_obj_t *list, bool outgoing, const char *text, const ch static void build_list_container(lv_obj_t *parent) { s_list = lv_obj_create(parent); - lv_obj_set_size(s_list, LCD_H_RES, BODY_BOT - INPUT_H - (BODY_TOP + BANNER_H)); + lv_obj_set_size(s_list, ui_screen_w(), BODY_BOT - INPUT_H - (BODY_TOP + BANNER_H)); lv_obj_align(s_list, LV_ALIGN_TOP_MID, 0, BODY_TOP + BANNER_H); lv_obj_set_style_bg_opa(s_list, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(s_list, 0, 0); @@ -379,7 +379,7 @@ static void add_placeholder(lv_obj_t *list, const char *txt) { lv_label_set_long_mode(l, LV_LABEL_LONG_WRAP); lv_label_set_text(l, txt); lv_obj_set_style_text_align(l, LV_TEXT_ALIGN_CENTER, 0); - lv_obj_set_style_text_color(l, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(l, current_theme.text_secondary, 0); lv_obj_set_style_text_font(l, &lv_font_montserrat_14, 0); } @@ -400,7 +400,7 @@ static void make_contact_row(lv_obj_t *list, int i, const dm_contact_t *c) { lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(row, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); - make_lock(row, lv_color_hex(COL_OK)); + make_lock(row, lv_color_hex(UI_COL_SUCCESS)); lv_obj_t *col = bare_box(row, LV_SIZE_CONTENT, LV_SIZE_CONTENT); lv_obj_set_flex_flow(col, LV_FLEX_FLOW_COLUMN); @@ -413,14 +413,14 @@ static void make_contact_row(lv_obj_t *list, int i, const dm_contact_t *c) { lv_obj_set_width(nm, lv_pct(100)); lv_label_set_text(nm, c->name); lv_obj_set_style_text_font(nm, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(nm, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(nm, current_theme.text_secondary, 0); lv_obj_t *fp = lv_label_create(col); char sub[24]; lv_snprintf(sub, sizeof(sub), "key %s", c->fp); lv_label_set_text(fp, sub); lv_obj_set_style_text_font(fp, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(fp, lv_color_hex(COL_OK), 0); + lv_obj_set_style_text_color(fp, lv_color_hex(UI_COL_SUCCESS), 0); s_rows[i] = row; s_row_name[i] = nm; @@ -446,7 +446,7 @@ static void row_style(int i, bool selected) { } if (s_row_name[i] != NULL) lv_obj_set_style_text_color( - s_row_name[i], selected ? current_theme.text_main : lv_color_hex(COL_DIM), 0); + s_row_name[i], selected ? current_theme.text_main : current_theme.text_secondary, 0); } static void select_contact(int sel) { @@ -488,7 +488,7 @@ static void build_thread_list(lv_obj_t *parent) { static void build_input(lv_obj_t *parent, const char *placeholder) { lv_obj_t *strip = lv_obj_create(parent); lv_obj_remove_flag(strip, LV_OBJ_FLAG_SCROLLABLE); - lv_obj_set_size(strip, LCD_H_RES, INPUT_H); + lv_obj_set_size(strip, ui_screen_w(), INPUT_H); lv_obj_align(strip, LV_ALIGN_TOP_MID, 0, BODY_BOT - INPUT_H); lv_obj_set_style_radius(strip, 0, 0); lv_obj_set_style_bg_opa(strip, LV_OPA_TRANSP, 0); @@ -501,7 +501,7 @@ static void build_input(lv_obj_t *parent, const char *placeholder) { lv_obj_set_flex_flow(strip, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(strip, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); - make_lock(strip, lv_color_hex(COL_OK)); + make_lock(strip, lv_color_hex(UI_COL_SUCCESS)); lv_obj_t *pill = lv_obj_create(strip); lv_obj_remove_flag(pill, LV_OBJ_FLAG_SCROLLABLE); @@ -520,7 +520,7 @@ static void build_input(lv_obj_t *parent, const char *placeholder) { lv_obj_t *ph = lv_label_create(pill); lv_label_set_text(ph, placeholder); lv_obj_set_style_text_font(ph, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(ph, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(ph, current_theme.text_secondary, 0); } static void on_kb_submit(const char *text, void *user_data) { diff --git a/firmware_p4/components/Applications/ui/screens/lora/lora_telemetry_ui.c b/firmware_p4/components/Applications/ui/screens/lora/lora_telemetry_ui.c index 0205b03b7..474a7408d 100644 --- a/firmware_p4/components/Applications/ui/screens/lora/lora_telemetry_ui.c +++ b/firmware_p4/components/Applications/ui/screens/lora/lora_telemetry_ui.c @@ -26,6 +26,8 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" +#include "ui_semantic.h" #include "ui_theme.h" #define SPARK_MS 700 @@ -35,7 +37,7 @@ #define FOOTER_HINT "UP/DOWN OK NODE BACK" #define BODY_TOP UI_CHROME_HEADER_H -#define BODY_H (LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) +#define BODY_H (ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) #define ROOT_PAD 8 #define ROOT_GAP 7 @@ -73,8 +75,6 @@ #define COL_ACC2 0xB89AFF #define COL_CYAN 0x37E0A8 -#define COL_DIM 0x8A8594 -#define COL_OK 0x00E676 #define COL_WARN 0xFFC23D #define COL_BAD 0xFF5470 #define COL_TRACK 0x241F31 @@ -210,7 +210,7 @@ static uint32_t nb_fill(const lora_node_t *nd, char *buf, size_t n) { int at = (t < 0) ? -t : t; snprintf(buf, n, "%c%d.%d", sign, at / 10, at % 10); if (nd->snr >= SNR_OK_DB) - return COL_OK; + return UI_COL_SUCCESS; if (nd->snr >= SNR_WARN_DB) return COL_WARN; return COL_BAD; @@ -218,13 +218,13 @@ static uint32_t nb_fill(const lora_node_t *nd, char *buf, size_t n) { if (nd->rssi != 0) { snprintf(buf, n, "%d", nd->rssi); if (nd->rssi >= RSSI_OK_DBM) - return COL_OK; + return UI_COL_SUCCESS; if (nd->rssi >= RSSI_WARN_DBM) return COL_WARN; return COL_BAD; } snprintf(buf, n, "--"); - return COL_DIM; + return 0x8A8594; // TODO: not themed (raw hex arg) } static void refresh_chips(void) { @@ -240,7 +240,7 @@ static void refresh_chips(void) { lv_obj_set_style_shadow_opa(s_chips[i], sel ? LV_OPA_40 : LV_OPA_TRANSP, 0); lv_obj_set_style_shadow_spread(s_chips[i], sel ? -2 : 0, 0); lv_obj_set_style_text_color( - s_chip_name[i], sel ? current_theme.text_main : lv_color_hex(COL_DIM), 0); + s_chip_name[i], sel ? current_theme.text_main : current_theme.text_secondary, 0); } } @@ -344,7 +344,7 @@ static lv_obj_t *make_panel(lv_obj_t *root, lv_obj_t *cap = lv_label_create(head); lv_label_set_text(cap, title); lv_obj_set_style_text_font(cap, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(cap, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(cap, current_theme.text_secondary, 0); lv_obj_t *val = lv_label_create(head); lv_label_set_text(val, "--"); @@ -446,7 +446,7 @@ void ui_lora_telemetry_open(void) { lv_obj_t *root = lv_obj_create(s_screen); lv_obj_remove_flag(root, LV_OBJ_FLAG_SCROLLABLE); - lv_obj_set_size(root, LCD_H_RES, BODY_H); + lv_obj_set_size(root, ui_screen_w(), BODY_H); lv_obj_align(root, LV_ALIGN_TOP_MID, 0, BODY_TOP); lv_obj_set_style_bg_opa(root, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(root, 0, 0); @@ -495,12 +495,12 @@ void ui_lora_telemetry_open(void) { s_link_sub = lv_label_create(batt); lv_label_set_text(s_link_sub, "--"); lv_obj_set_style_text_font(s_link_sub, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(s_link_sub, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s_link_sub, current_theme.text_secondary, 0); lv_obj_t *nlbl = lv_label_create(root); lv_label_set_text(nlbl, "NEIGHBORS"); lv_obj_set_style_text_font(nlbl, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(nlbl, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(nlbl, current_theme.text_secondary, 0); lv_obj_t *chips = lv_obj_create(root); lv_obj_remove_flag(chips, LV_OBJ_FLAG_SCROLLABLE); @@ -536,7 +536,7 @@ void ui_lora_telemetry_open(void) { lv_obj_t *sv = lv_label_create(chip); lv_label_set_text(sv, "--"); lv_obj_set_style_text_font(sv, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(sv, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(sv, current_theme.text_secondary, 0); s_chip_val[i] = sv; s_chips[i] = chip; @@ -545,7 +545,7 @@ void ui_lora_telemetry_open(void) { s_nb_empty = lv_label_create(chips); lv_label_set_text(s_nb_empty, "Listening for nodes..."); lv_obj_set_style_text_font(s_nb_empty, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(s_nb_empty, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s_nb_empty, current_theme.text_secondary, 0); lv_obj_add_flag(s_nb_empty, LV_OBJ_FLAG_HIDDEN); rebuild_points(); diff --git a/firmware_p4/components/Applications/ui/screens/lora/lora_traceroute_ui.c b/firmware_p4/components/Applications/ui/screens/lora/lora_traceroute_ui.c index def47b2ee..b0f47ca8e 100644 --- a/firmware_p4/components/Applications/ui/screens/lora/lora_traceroute_ui.c +++ b/firmware_p4/components/Applications/ui/screens/lora/lora_traceroute_ui.c @@ -28,6 +28,7 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #include "waves_ui.h" @@ -39,8 +40,6 @@ static const char *TAG = "LORA_TRACERT"; #define ENTRY_MS 220 #define ROW_STAGGER_MS 90 -#define COL_DIM 0x8A8594 - #define BODY_TOP_Y 46 #define HOP_LIST_W 228 #define HOP_ROW_W 214 @@ -229,7 +228,7 @@ static void make_hop_row(lv_obj_t *parent, int idx, int count, lv_color_t accent lv_obj_t *sub = lv_label_create(txt); lv_label_set_text(sub, hop_role(idx, count)); - lv_obj_set_style_text_color(sub, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(sub, current_theme.text_secondary, 0); lv_obj_set_style_text_font(sub, &lv_font_montserrat_12, 0); } @@ -277,7 +276,7 @@ static void pick_style_row(int i, bool selected) { } if (s_pick_names[i] != NULL) lv_obj_set_style_text_color( - s_pick_names[i], selected ? current_theme.text_main : lv_color_hex(COL_DIM), 0); + s_pick_names[i], selected ? current_theme.text_main : current_theme.text_secondary, 0); } static void pick_select(int sel) { @@ -292,7 +291,7 @@ static void pick_select(int sel) { static void centered_note(const char *text) { lv_obj_t *msg = lv_label_create(s_screen); lv_label_set_text(msg, text); - lv_obj_set_style_text_color(msg, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(msg, current_theme.text_secondary, 0); lv_obj_set_style_text_font(msg, &lv_font_montserrat_14, 0); lv_obj_center(msg); } @@ -314,7 +313,8 @@ static void build_picker(void) { } s_pick_list = lv_obj_create(s_screen); - lv_obj_set_size(s_pick_list, LCD_H_RES, LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); + lv_obj_set_size( + s_pick_list, ui_screen_w(), ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); lv_obj_align(s_pick_list, LV_ALIGN_TOP_MID, 0, UI_CHROME_HEADER_H); lv_obj_set_style_bg_opa(s_pick_list, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(s_pick_list, 0, 0); @@ -386,8 +386,7 @@ static void build_result(void) { } lv_obj_t *list = lv_obj_create(s_screen); - lv_obj_remove_flag(list, LV_OBJ_FLAG_SCROLLABLE); - lv_obj_set_size(list, HOP_LIST_W, LV_SIZE_CONTENT); + lv_obj_set_size(list, HOP_LIST_W, ui_screen_h() - BODY_TOP_Y - UI_CHROME_FOOTER_H); lv_obj_align(list, LV_ALIGN_TOP_MID, 0, BODY_TOP_Y); lv_obj_set_style_bg_opa(list, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(list, 0, 0); @@ -395,6 +394,9 @@ static void build_result(void) { lv_obj_set_style_pad_row(list, HOP_ROW_GAP, 0); lv_obj_set_flex_flow(list, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_align(list, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_scroll_dir(list, LV_DIR_VER); + lv_obj_set_scrollbar_mode(list, LV_SCROLLBAR_MODE_AUTO); + lv_obj_remove_flag(list, LV_OBJ_FLAG_SCROLL_ELASTIC | LV_OBJ_FLAG_SCROLL_MOMENTUM); lv_color_t accent = ui_theme_get_accent(); for (int i = 0; i < n; i++) diff --git a/firmware_p4/components/Applications/ui/screens/menu/menu_ui.c b/firmware_p4/components/Applications/ui/screens/menu/menu_ui.c index ca9d07d84..ad44af5ac 100644 --- a/firmware_p4/components/Applications/ui/screens/menu/menu_ui.c +++ b/firmware_p4/components/Applications/ui/screens/menu/menu_ui.c @@ -165,6 +165,14 @@ static menu_ui_item_t s_menu_data[] = { {NULL}, {NULL}, SCREEN_FILES}, + {"APPS", + {"/assets/frames/apps_frame_0.bin", + "/assets/frames/apps_frame_1.bin", + "/assets/frames/apps_frame_2.bin"}, + BASE_FRAMES, + {NULL}, + {NULL}, + SCREEN_GAMES_MENU}, {"PLAYER", {"/assets/frames/player_glyph.bin", "/assets/frames/player_glyph.bin", @@ -201,7 +209,7 @@ static void place_item(size_t item_idx, bool anim); static void fix_z_order(void); static void update_view(bool anim); static void on_anim_done(lv_anim_t *a); -static void on_key_event(lv_event_t *e); +static void menu_event_cb(lv_event_t *e); static int32_t carousel_slot(size_t item_idx) { int32_t n = (int32_t)MENU_ITEM_COUNT; @@ -217,6 +225,14 @@ static void on_anim_done(lv_anim_t *a) { s_is_animating = false; } +// lv_obj_set_style_opa takes a third (selector) argument, so it cannot be used +// directly as a 2-arg animation exec callback: the selector would be an +// uninitialized register. Under -O2 that garbage selector made each frame add a +// fresh local-style entry, growing the style list until taskLVGL stalled (WDT). +static void anim_set_opa_cb(void *var, int32_t v) { + lv_obj_set_style_opa((lv_obj_t *)var, (lv_opa_t)v, 0); +} + static void load_item_frame(size_t item_idx, int frame) { if (s_menu_data[item_idx].icon_frames[frame] != NULL && s_menu_data[item_idx].icon_dscs[frame] == NULL) @@ -298,7 +314,7 @@ static void place_item(size_t item_idx, bool anim) { lv_anim_set_var(&a, s_base_imgs[item_idx]); lv_anim_set_values(&a, lv_obj_get_style_opa(s_base_imgs[item_idx], 0), to); - lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_obj_set_style_opa); + lv_anim_set_exec_cb(&a, anim_set_opa_cb); lv_anim_start(&a); lv_anim_set_var(&a, s_icon_imgs[item_idx]); @@ -354,8 +370,9 @@ static void update_view(bool anim) { if (s_fav_badge != NULL) { bool fav = favorites_is(s_menu_data[s_selected].target); - lv_label_set_text(s_fav_badge, fav ? LV_SYMBOL_OK " FAVORITED" : ""); - lv_obj_set_style_text_color(s_fav_badge, lv_color_hex(FAV_ACCENT), 0); + lv_label_set_text(s_fav_badge, fav ? LV_SYMBOL_OK " FAVORITED" : "hold OK to favorite"); + lv_obj_set_style_text_color( + s_fav_badge, fav ? lv_color_hex(FAV_ACCENT) : current_theme.text_secondary, 0); } if (anim) { @@ -364,7 +381,7 @@ static void update_view(bool anim) { lv_anim_set_var(&a, s_label); lv_anim_set_values(&a, LV_OPA_0, LV_OPA_COVER); lv_anim_set_duration(&a, LABEL_FADE_MS); - lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_obj_set_style_opa); + lv_anim_set_exec_cb(&a, anim_set_opa_cb); lv_anim_start(&a); } @@ -405,8 +422,26 @@ static void ensure_radio_on(screen_id_t target) { } } -static void on_key_event(lv_event_t *e) { - if (lv_event_get_code(e) != LV_EVENT_KEY) +static void menu_open_selected(void) { + screen_id_t target = s_menu_data[s_selected].target; + ensure_radio_on(target); + ui_switch_screen(target); +} + +static void menu_event_cb(lv_event_t *e) { + const lv_event_code_t code = lv_event_get_code(e); + + if (code == LV_EVENT_SHORT_CLICKED) { + menu_open_selected(); + return; + } + if (code == LV_EVENT_LONG_PRESSED) { + favorites_toggle(s_menu_data[s_selected].target); + ui_feedback(UI_FB_SELECT); + update_view(false); + return; + } + if (code != LV_EVENT_KEY) return; uint32_t k = lv_event_get_key(e); @@ -428,9 +463,7 @@ static void on_key_event(lv_event_t *e) { } if (k == LV_KEY_DOWN) { - favorites_toggle(s_menu_data[s_selected].target); - ui_feedback(UI_FB_SELECT); - update_view(false); + menu_open_selected(); return; } @@ -438,12 +471,6 @@ static void on_key_event(lv_event_t *e) { ui_switch_screen(SCREEN_HOME); return; } - - if (k == LV_KEY_ENTER) { - screen_id_t target = s_menu_data[s_selected].target; - ensure_radio_on(target); - ui_switch_screen(target); - } } int menu_catalog_count(void) { @@ -512,7 +539,10 @@ void ui_menu_open(void) { update_view(false); - lv_obj_add_event_cb(s_screen, on_key_event, LV_EVENT_KEY, NULL); + lv_obj_add_flag(s_screen, LV_OBJ_FLAG_CLICKABLE); + lv_obj_add_event_cb(s_screen, menu_event_cb, LV_EVENT_KEY, NULL); + lv_obj_add_event_cb(s_screen, menu_event_cb, LV_EVENT_SHORT_CLICKED, NULL); + lv_obj_add_event_cb(s_screen, menu_event_cb, LV_EVENT_LONG_PRESSED, NULL); if (main_group != NULL) { lv_group_add_obj(main_group, s_screen); diff --git a/firmware_p4/components/Applications/ui/screens/nfc/card_emu_ui.c b/firmware_p4/components/Applications/ui/screens/nfc/card_emu_ui.c index ca5f24970..7b8605e0e 100644 --- a/firmware_p4/components/Applications/ui/screens/nfc/card_emu_ui.c +++ b/firmware_p4/components/Applications/ui/screens/nfc/card_emu_ui.c @@ -24,6 +24,7 @@ #include "page_dots_ui.h" #include "ui_chrome.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #define REFRESH_MS 33 @@ -238,7 +239,7 @@ static void emulate_start(const nfc_sim_card_t *card) { int H = lv_display_get_vertical_resolution(NULL); if (H < 200) - H = 320; + H = ui_screen_h(); int top_y = H * 8 / 100; if (top_y < 4) top_y = 4; diff --git a/firmware_p4/components/Applications/ui/screens/nfc/nfc_bankcard_ui.c b/firmware_p4/components/Applications/ui/screens/nfc/nfc_bankcard_ui.c index fb8ad8b1b..286d68d22 100644 --- a/firmware_p4/components/Applications/ui/screens/nfc/nfc_bankcard_ui.c +++ b/firmware_p4/components/Applications/ui/screens/nfc/nfc_bankcard_ui.c @@ -20,11 +20,12 @@ #include "ui_chrome.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #define MX 8 -#define BODY_H (LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) -#define CONTENT_W (LCD_H_RES - 2 * MX) +#define BODY_H (ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) +#define CONTENT_W (ui_screen_w() - 2 * MX) #define ROW_GAP 6 #define CARD_H 120 @@ -41,7 +42,6 @@ #define VISA_Y 12 #define EDGE_Y (-12) -#define COL_DIM 0x8A8594 #define COL_WHITE 0xFFFFFF #define COL_GOLD 0xD9A521 #define CARD_TOP 0x3A2F6A @@ -81,7 +81,7 @@ static void make_kv(lv_obj_t *parent, const char *k, const char *v, lv_color_t v lv_obj_t *kl = lv_label_create(row); lv_label_set_text(kl, k); lv_obj_set_style_text_font(kl, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(kl, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(kl, current_theme.text_secondary, 0); lv_obj_t *vl = lv_label_create(row); lv_label_set_text(vl, v); @@ -175,7 +175,7 @@ void ui_nfc_bankcard_open(void) { lv_obj_t *body = lv_obj_create(s_screen); lv_obj_remove_flag(body, LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(body, LV_OBJ_FLAG_CLICKABLE); - lv_obj_set_size(body, LCD_H_RES, BODY_H); + lv_obj_set_size(body, ui_screen_w(), BODY_H); lv_obj_align(body, LV_ALIGN_TOP_MID, 0, UI_CHROME_HEADER_H); lv_obj_set_style_bg_opa(body, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(body, 0, 0); diff --git a/firmware_p4/components/Applications/ui/screens/nfc/nfc_config_ui.c b/firmware_p4/components/Applications/ui/screens/nfc/nfc_config_ui.c index bac33f478..8e3bc168a 100644 --- a/firmware_p4/components/Applications/ui/screens/nfc/nfc_config_ui.c +++ b/firmware_p4/components/Applications/ui/screens/nfc/nfc_config_ui.c @@ -75,7 +75,7 @@ static void nfc_config_input(const input_event_t *ev, void *ctx) { } else if (sel == CFG_DIAG) { msgbox_open( LV_SYMBOL_WARNING, - "ST25R3916: no reply\nSPI3 MISO blocked\n(GPIO36 jumper) —\nrunning simulated", + "ST25R3916: no reply\nSPI3 MISO blocked\n(GPIO36 jumper) -\nrunning simulated", NULL, NULL, NULL); diff --git a/firmware_p4/components/Applications/ui/screens/nfc/nfc_desfire_ui.c b/firmware_p4/components/Applications/ui/screens/nfc/nfc_desfire_ui.c index 271fa82da..d38a37e1e 100644 --- a/firmware_p4/components/Applications/ui/screens/nfc/nfc_desfire_ui.c +++ b/firmware_p4/components/Applications/ui/screens/nfc/nfc_desfire_ui.c @@ -20,11 +20,13 @@ #include "ui_chrome.h" #include "ui_manager.h" +#include "ui_metrics.h" +#include "ui_semantic.h" #include "ui_theme.h" #define MX 8 -#define BODY_H (LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) -#define CONTENT_W (LCD_H_RES - 2 * MX) +#define BODY_H (ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) +#define CONTENT_W (ui_screen_w() - 2 * MX) #define ROW_GAP 6 #define CARD_H 74 @@ -38,8 +40,6 @@ #define DUMP_RAD 8 #define DUMP_PAD 8 -#define COL_DIM 0x8A8594 -#define COL_OK 0x00E676 #define COL_CYAN 0x37E0A8 #define COL_PANEL2 0x1A1626 #define COL_LINE 0x2A2636 @@ -85,7 +85,7 @@ static void make_kv(lv_obj_t *parent, const char *k, const char *v) { lv_obj_t *kl = lv_label_create(row); lv_label_set_text(kl, k); lv_obj_set_style_text_font(kl, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(kl, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(kl, current_theme.text_secondary, 0); lv_obj_t *vl = lv_label_create(row); lv_label_set_text(vl, v); @@ -121,7 +121,7 @@ static void build_auth_card(lv_obj_t *parent) { lv_obj_t *ok = lv_label_create(top); lv_label_set_text(ok, LV_SYMBOL_OK " " TXT_AUTH); lv_obj_set_style_text_font(ok, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(ok, lv_color_hex(COL_OK), 0); + lv_obj_set_style_text_color(ok, lv_color_hex(UI_COL_SUCCESS), 0); lv_obj_t *spacer = lv_obj_create(top); lv_obj_remove_flag(spacer, LV_OBJ_FLAG_SCROLLABLE); @@ -154,7 +154,7 @@ static void build_dump(lv_obj_t *parent) { lv_obj_t *lbl = lv_label_create(parent); lv_label_set_text(lbl, TXT_FILE); lv_obj_set_style_text_font(lbl, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(lbl, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(lbl, current_theme.text_secondary, 0); lv_obj_t *box = lv_obj_create(parent); lv_obj_remove_flag(box, LV_OBJ_FLAG_SCROLLABLE); @@ -212,7 +212,7 @@ void ui_nfc_desfire_open(void) { lv_obj_t *body = lv_obj_create(s_screen); lv_obj_remove_flag(body, LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(body, LV_OBJ_FLAG_CLICKABLE); - lv_obj_set_size(body, LCD_H_RES, BODY_H); + lv_obj_set_size(body, ui_screen_w(), BODY_H); lv_obj_align(body, LV_ALIGN_TOP_MID, 0, UI_CHROME_HEADER_H); lv_obj_set_style_bg_opa(body, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(body, 0, 0); diff --git a/firmware_p4/components/Applications/ui/screens/nfc/nfc_emulate_ui.c b/firmware_p4/components/Applications/ui/screens/nfc/nfc_emulate_ui.c index c1e432b00..1857e1d40 100644 --- a/firmware_p4/components/Applications/ui/screens/nfc/nfc_emulate_ui.c +++ b/firmware_p4/components/Applications/ui/screens/nfc/nfc_emulate_ui.c @@ -28,12 +28,12 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" +#include "ui_semantic.h" #include "ui_theme.h" #define TICK_MS 33 #define CARD_ICON "/assets/icons/contactless.bin" -#define COL_DIM 0x8A8594 -#define SIG_GREEN 0x00E676 #define TX_DOTS 3 #define CARD_Y_OFS (-24) #define FIELD_BOX_W 210 @@ -136,7 +136,7 @@ static void build_empty(const char *icon, const char *title, const char *sub) { lv_obj_t *s = lv_label_create(card); lv_label_set_text(s, sub); lv_obj_set_style_text_font(s, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(s, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s, current_theme.text_secondary, 0); } static lv_obj_t * @@ -169,7 +169,7 @@ static void wallet_build(void) { s_body = lv_obj_create(s_screen); lv_obj_remove_flag(s_body, LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(s_body, LV_OBJ_FLAG_CLICKABLE); - lv_obj_set_size(s_body, lv_pct(100), LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); + lv_obj_set_size(s_body, lv_pct(100), ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); lv_obj_align(s_body, LV_ALIGN_TOP_MID, 0, UI_CHROME_HEADER_H); lv_obj_set_style_bg_opa(s_body, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(s_body, 0, 0); @@ -202,21 +202,22 @@ static void wallet_build(void) { lv_obj_set_style_pad_column(tap, 7, 0); lv_obj_set_flex_flow(tap, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(tap, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); - lv_obj_align(tap, LV_ALIGN_CENTER, 0, WALLET_TAP_Y); + const int emu_body_h = ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H; + lv_obj_align(tap, LV_ALIGN_CENTER, 0, LV_MIN(WALLET_TAP_Y, emu_body_h / 2 - 7)); lv_obj_t *dot = lv_obj_create(tap); lv_obj_remove_flag(dot, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_size(dot, 8, 8); lv_obj_set_style_radius(dot, LV_RADIUS_CIRCLE, 0); lv_obj_set_style_border_width(dot, 0, 0); - lv_obj_set_style_bg_color(dot, lv_color_hex(SIG_GREEN), 0); + lv_obj_set_style_bg_color(dot, lv_color_hex(UI_COL_SUCCESS), 0); lv_obj_set_style_bg_opa(dot, LV_OPA_COVER, 0); blink_loop(dot, 900, 0); lv_obj_t *txt = lv_label_create(tap); lv_label_set_text(txt, "tap to broadcast"); lv_obj_set_style_text_font(txt, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(txt, lv_color_hex(SIG_GREEN), 0); + lv_obj_set_style_text_color(txt, lv_color_hex(UI_COL_SUCCESS), 0); s_dots = page_dots_create(s_body, s_count, LV_ALIGN_BOTTOM_MID, 0, -4); page_dots_set(&s_dots, s_sel); diff --git a/firmware_p4/components/Applications/ui/screens/nfc/nfc_felica_ui.c b/firmware_p4/components/Applications/ui/screens/nfc/nfc_felica_ui.c index e952cd4eb..cab594353 100644 --- a/firmware_p4/components/Applications/ui/screens/nfc/nfc_felica_ui.c +++ b/firmware_p4/components/Applications/ui/screens/nfc/nfc_felica_ui.c @@ -21,11 +21,12 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #define MX 8 -#define BODY_H (LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) -#define CONTENT_W (LCD_H_RES - 2 * MX) +#define BODY_H (ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) +#define CONTENT_W (ui_screen_w() - 2 * MX) #define ROW_GAP 6 #define SVC_COUNT 3 @@ -39,7 +40,6 @@ #define DUMP_RAD 8 #define DUMP_PAD 8 -#define COL_DIM 0x8A8594 #define COL_CYAN 0x37E0A8 #define COL_LINE 0x2A2636 #define COL_PANEL2 0x1A1626 @@ -88,7 +88,8 @@ static void make_chip(lv_obj_t *parent, const char *txt, bool sel) { lv_obj_t *l = lv_label_create(chip); lv_label_set_text(l, txt); lv_obj_set_style_text_font(l, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(l, sel ? current_theme.border_accent : lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color( + l, sel ? current_theme.border_accent : current_theme.text_secondary, 0); lv_obj_center(l); } @@ -118,7 +119,7 @@ static lv_obj_t *make_row(lv_obj_t *parent, int i) { lv_obj_t *desc = lv_label_create(row); lv_label_set_text(desc, SVCS[i].desc); lv_obj_set_style_text_font(desc, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(desc, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(desc, current_theme.text_secondary, 0); s_code[i] = code; return row; @@ -239,7 +240,7 @@ void ui_nfc_felica_open(void) { lv_obj_t *body = lv_obj_create(s_screen); lv_obj_remove_flag(body, LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(body, LV_OBJ_FLAG_CLICKABLE); - lv_obj_set_size(body, LCD_H_RES, BODY_H); + lv_obj_set_size(body, ui_screen_w(), BODY_H); lv_obj_align(body, LV_ALIGN_TOP_MID, 0, UI_CHROME_HEADER_H); lv_obj_set_style_bg_opa(body, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(body, 0, 0); diff --git a/firmware_p4/components/Applications/ui/screens/nfc/nfc_iso15693_ui.c b/firmware_p4/components/Applications/ui/screens/nfc/nfc_iso15693_ui.c index 6a8125f70..a4232d5f3 100644 --- a/firmware_p4/components/Applications/ui/screens/nfc/nfc_iso15693_ui.c +++ b/firmware_p4/components/Applications/ui/screens/nfc/nfc_iso15693_ui.c @@ -23,11 +23,12 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #define MX 8 -#define BODY_H (LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) -#define CONTENT_W (LCD_H_RES - 2 * MX) +#define BODY_H (ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) +#define CONTENT_W (ui_screen_w() - 2 * MX) #define ROW_GAP 7 #define BLOCK_COUNT 28 @@ -45,7 +46,6 @@ #define CHIP_RAD 8 #define CHIP_PAD 6 -#define COL_DIM 0x8A8594 #define COL_GOLD 0xD9A521 #define COL_LINE 0x2A2636 #define COL_PANEL2 0x1A1626 @@ -92,7 +92,8 @@ static lv_obj_t *make_chip(lv_obj_t *parent, const char *txt, bool sel) { lv_obj_t *l = lv_label_create(chip); lv_label_set_text(l, txt); lv_obj_set_style_text_font(l, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(l, sel ? current_theme.border_accent : lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color( + l, sel ? current_theme.border_accent : current_theme.text_secondary, 0); lv_obj_center(l); return chip; } @@ -106,7 +107,7 @@ static void restyle_cells(void) { : lv_color_hex(COL_LINE); lv_color_t txt = sel ? current_theme.text_main : locked ? lv_color_hex(COL_GOLD) - : lv_color_hex(COL_DIM); + : current_theme.text_secondary; lv_obj_set_style_border_color(s_cell[i], border, 0); lv_obj_set_style_bg_color( s_cell[i], sel ? current_theme.bg_primary : lv_color_hex(COL_PANEL2), 0); @@ -190,7 +191,7 @@ static void build_body(lv_obj_t *parent) { kv, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); s_block_key = lv_label_create(kv); lv_obj_set_style_text_font(s_block_key, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(s_block_key, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s_block_key, current_theme.text_secondary, 0); s_block_val = lv_label_create(kv); lv_obj_set_style_text_font(s_block_val, &lv_font_montserrat_12, 0); lv_obj_set_style_text_color(s_block_val, current_theme.text_main, 0); @@ -268,7 +269,7 @@ void ui_nfc_iso15693_open(void) { lv_obj_t *body = lv_obj_create(s_screen); lv_obj_remove_flag(body, LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(body, LV_OBJ_FLAG_CLICKABLE); - lv_obj_set_size(body, LCD_H_RES, BODY_H); + lv_obj_set_size(body, ui_screen_w(), BODY_H); lv_obj_align(body, LV_ALIGN_TOP_MID, 0, UI_CHROME_HEADER_H); lv_obj_set_style_bg_opa(body, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(body, 0, 0); diff --git a/firmware_p4/components/Applications/ui/screens/nfc/nfc_keydict_ui.c b/firmware_p4/components/Applications/ui/screens/nfc/nfc_keydict_ui.c index de8bcfc9a..55b359c85 100644 --- a/firmware_p4/components/Applications/ui/screens/nfc/nfc_keydict_ui.c +++ b/firmware_p4/components/Applications/ui/screens/nfc/nfc_keydict_ui.c @@ -21,11 +21,12 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #define MX 8 -#define BODY_H (LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) -#define CONTENT_W (LCD_H_RES - 2 * MX) +#define BODY_H (ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) +#define CONTENT_W (ui_screen_w() - 2 * MX) #define ROW_GAP 5 #define KEY_COUNT 4 @@ -38,8 +39,6 @@ #define TAG_H 14 #define TAG_PAD 5 -#define COL_DIM 0x8A8594 - #define HDR_TITLE "KEY DICTIONARY" #define HDR_ICON "/assets/icons/nfc.bin" #define FOOTER_HINT "OK load UP/DOWN key +/- edit" @@ -86,7 +85,8 @@ static void make_chip(lv_obj_t *parent, const char *txt, bool sel) { lv_obj_t *l = lv_label_create(chip); lv_label_set_text(l, txt); lv_obj_set_style_text_font(l, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(l, sel ? current_theme.border_accent : lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color( + l, sel ? current_theme.border_accent : current_theme.text_secondary, 0); lv_obj_center(l); } @@ -133,7 +133,7 @@ static lv_obj_t *make_row(lv_obj_t *parent, int i) { lv_obj_t *tag = lv_label_create(row); lv_label_set_text(tag, KEYS[i].tag); lv_obj_set_style_text_font(tag, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(tag, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(tag, current_theme.text_secondary, 0); } s_hex[i] = hex; @@ -241,7 +241,7 @@ void ui_nfc_keydict_open(void) { lv_obj_t *body = lv_obj_create(s_screen); lv_obj_remove_flag(body, LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(body, LV_OBJ_FLAG_CLICKABLE); - lv_obj_set_size(body, LCD_H_RES, BODY_H); + lv_obj_set_size(body, ui_screen_w(), BODY_H); lv_obj_align(body, LV_ALIGN_TOP_MID, 0, UI_CHROME_HEADER_H); lv_obj_set_style_bg_opa(body, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(body, 0, 0); diff --git a/firmware_p4/components/Applications/ui/screens/nfc/nfc_ndef_ui.c b/firmware_p4/components/Applications/ui/screens/nfc/nfc_ndef_ui.c index e9dd7a4b9..31127f837 100644 --- a/firmware_p4/components/Applications/ui/screens/nfc/nfc_ndef_ui.c +++ b/firmware_p4/components/Applications/ui/screens/nfc/nfc_ndef_ui.c @@ -21,11 +21,12 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #define MX 8 -#define BODY_H (LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) -#define CONTENT_W (LCD_H_RES - 2 * MX) +#define BODY_H (ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) +#define CONTENT_W (ui_screen_w() - 2 * MX) #define ROW_GAP 6 #define REC_COUNT 3 @@ -37,8 +38,6 @@ #define CHIP_RAD 8 #define CHIP_PAD 6 -#define COL_DIM 0x8A8594 - #define HDR_TITLE "NDEF" #define HDR_ICON "/assets/icons/nfc.bin" #define FOOTER_HINT "OK edit UP/DOWN record BACK" @@ -82,7 +81,8 @@ static void make_chip(lv_obj_t *parent, const char *txt, bool sel) { lv_obj_t *l = lv_label_create(chip); lv_label_set_text(l, txt); lv_obj_set_style_text_font(l, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(l, sel ? current_theme.border_accent : lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color( + l, sel ? current_theme.border_accent : current_theme.text_secondary, 0); lv_obj_center(l); } @@ -127,7 +127,7 @@ static lv_obj_t *make_row(lv_obj_t *parent, int i) { lv_obj_t *sub = lv_label_create(col); lv_label_set_text(sub, RECS[i].sub); lv_obj_set_style_text_font(sub, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(sub, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(sub, current_theme.text_secondary, 0); s_icon[i] = ic; s_title[i] = title; @@ -146,9 +146,9 @@ static void refresh_selection(void) { lv_obj_set_style_shadow_color(s_row[i], current_theme.border_accent, 0); lv_obj_set_style_shadow_spread(s_row[i], sel ? -3 : 0, 0); lv_obj_set_style_text_color( - s_icon[i], sel ? current_theme.border_accent : lv_color_hex(COL_DIM), 0); + s_icon[i], sel ? current_theme.border_accent : current_theme.text_secondary, 0); lv_obj_set_style_text_color( - s_title[i], sel ? current_theme.text_main : lv_color_hex(COL_DIM), 0); + s_title[i], sel ? current_theme.text_main : current_theme.text_secondary, 0); } } @@ -225,7 +225,7 @@ void ui_nfc_ndef_open(void) { lv_obj_t *body = lv_obj_create(s_screen); lv_obj_remove_flag(body, LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(body, LV_OBJ_FLAG_CLICKABLE); - lv_obj_set_size(body, LCD_H_RES, BODY_H); + lv_obj_set_size(body, ui_screen_w(), BODY_H); lv_obj_align(body, LV_ALIGN_TOP_MID, 0, UI_CHROME_HEADER_H); lv_obj_set_style_bg_opa(body, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(body, 0, 0); diff --git a/firmware_p4/components/Applications/ui/screens/nfc/nfc_p2p_ui.c b/firmware_p4/components/Applications/ui/screens/nfc/nfc_p2p_ui.c index 059907e3e..f3538c52d 100644 --- a/firmware_p4/components/Applications/ui/screens/nfc/nfc_p2p_ui.c +++ b/firmware_p4/components/Applications/ui/screens/nfc/nfc_p2p_ui.c @@ -23,11 +23,13 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" +#include "ui_semantic.h" #include "ui_theme.h" #define MX 8 -#define BODY_H (LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) -#define CONTENT_W (LCD_H_RES - 2 * MX) +#define BODY_H (ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) +#define CONTENT_W (ui_screen_w() - 2 * MX) #define ROW_GAP 8 #define DEV_ROW_H 62 @@ -49,8 +51,6 @@ #define STREAM_STEP 4 #define STREAM_MAX 100 -#define COL_DIM 0x8A8594 -#define COL_OK 0x00E676 #define COL_CYAN 0x37E0A8 #define COL_LINE 0x2A2636 @@ -111,7 +111,7 @@ static lv_obj_t *make_device(lv_obj_t *parent, const char *glyph, const char *na lv_obj_t *nm = lv_label_create(col); lv_label_set_text(nm, name); lv_obj_set_style_text_font(nm, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(nm, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(nm, current_theme.text_secondary, 0); return col; } @@ -205,9 +205,9 @@ static void stream_cb(lv_timer_t *t) { if (s_pct >= STREAM_MAX) { s_pct = STREAM_MAX; lv_label_set_text(s_put_mark, LV_SYMBOL_OK); - lv_obj_set_style_text_color(s_put_mark, lv_color_hex(COL_OK), 0); + lv_obj_set_style_text_color(s_put_mark, lv_color_hex(UI_COL_SUCCESS), 0); lv_label_set_text(s_put_pct, STEP_DONE); - lv_obj_set_style_text_color(s_put_pct, lv_color_hex(COL_OK), 0); + lv_obj_set_style_text_color(s_put_pct, lv_color_hex(UI_COL_SUCCESS), 0); lv_timer_delete(t); s_stream_timer = NULL; return; @@ -243,18 +243,18 @@ static void build_body(lv_obj_t *parent) { make_step(steps, LV_SYMBOL_OK, - lv_color_hex(COL_OK), + lv_color_hex(UI_COL_SUCCESS), STEP1_TXT, STEP1_META, - lv_color_hex(COL_DIM), + current_theme.text_secondary, NULL, NULL); make_step(steps, LV_SYMBOL_OK, - lv_color_hex(COL_OK), + lv_color_hex(UI_COL_SUCCESS), STEP2_TXT, STEP2_META, - lv_color_hex(COL_DIM), + current_theme.text_secondary, NULL, NULL); make_step(steps, @@ -310,7 +310,7 @@ void ui_nfc_p2p_open(void) { lv_obj_t *body = lv_obj_create(s_screen); lv_obj_remove_flag(body, LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(body, LV_OBJ_FLAG_CLICKABLE); - lv_obj_set_size(body, LCD_H_RES, BODY_H); + lv_obj_set_size(body, ui_screen_w(), BODY_H); lv_obj_align(body, LV_ALIGN_TOP_MID, 0, UI_CHROME_HEADER_H); lv_obj_set_style_bg_opa(body, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(body, 0, 0); diff --git a/firmware_p4/components/Applications/ui/screens/nfc/nfc_read_ui.c b/firmware_p4/components/Applications/ui/screens/nfc/nfc_read_ui.c index f196474a1..42383825a 100644 --- a/firmware_p4/components/Applications/ui/screens/nfc/nfc_read_ui.c +++ b/firmware_p4/components/Applications/ui/screens/nfc/nfc_read_ui.c @@ -28,12 +28,12 @@ #include "nfc_ui_common.h" #include "ui_chrome.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #define REFRESH_MS 33 #define REVEAL_MS 3000 -#define COL_DIM 0x8A8594 #define DUMP_W 214 #define DUMP_Y 102 #define DUMP_ROW_GAP 2 @@ -98,7 +98,10 @@ static void build_dump(void) { lv_obj_remove_flag(s_dump, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_width(s_dump, DUMP_W); lv_obj_set_height(s_dump, LV_SIZE_CONTENT); - lv_obj_align(s_dump, LV_ALIGN_CENTER, 0, DUMP_Y); + const int dumpH = DUMP_LINE_COUNT * 14 + (DUMP_LINE_COUNT - 1) * DUMP_ROW_GAP; + const int dump_y = + LV_MIN(DUMP_Y, (ui_screen_h() - UI_CHROME_FOOTER_H) - ui_screen_h() / 2 - dumpH / 2); + lv_obj_align(s_dump, LV_ALIGN_CENTER, 0, dump_y); lv_obj_set_style_bg_opa(s_dump, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(s_dump, 0, 0); lv_obj_set_style_pad_all(s_dump, 0, 0); @@ -107,7 +110,7 @@ static void build_dump(void) { for (int i = 0; i < DUMP_LINE_COUNT; i++) { lv_obj_t *ln = lv_label_create(s_dump); lv_label_set_text(ln, DUMP_LINES[i]); - lv_obj_set_style_text_color(ln, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(ln, current_theme.text_secondary, 0); lv_obj_set_style_text_font(ln, &lv_font_montserrat_12, 0); } } diff --git a/firmware_p4/components/Applications/ui/screens/nfc/nfc_saved_ui.c b/firmware_p4/components/Applications/ui/screens/nfc/nfc_saved_ui.c index e9c652468..b1c1a62f4 100644 --- a/firmware_p4/components/Applications/ui/screens/nfc/nfc_saved_ui.c +++ b/firmware_p4/components/Applications/ui/screens/nfc/nfc_saved_ui.c @@ -31,12 +31,12 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #define TICK_MS 50 #define CARD_H 122 #define PEEK 60 -#define COL_DIM 0x8A8594 #define SAVED_ICON "/assets/icons/bookmarks.bin" #define CARD_ICON "/assets/icons/nfc.bin" #define CARD_REVEAL_MS 1100 @@ -109,7 +109,7 @@ static void build_empty(void) { lv_obj_t *s = lv_label_create(card); lv_label_set_text(s, "Read a tag first"); lv_obj_set_style_text_font(s, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(s, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s, current_theme.text_secondary, 0); } static void relayout(void) { @@ -227,7 +227,7 @@ static void on_del_confirm(bool confirm) { ui_feedback(UI_FB_WRITE); notify(NOTIFY_INFO, "Card deleted"); overlay_close(); - lv_async_call(rebuild_async, NULL); + ui_async_call(rebuild_async, NULL); } static void nfc_saved_tick_cb(lv_timer_t *t) { @@ -357,7 +357,7 @@ void ui_nfc_saved_open(void) { ui_chrome_footer(s_screen, "BACK Back"); } else { s_cont = lv_obj_create(s_screen); - lv_obj_set_size(s_cont, lv_pct(100), LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); + lv_obj_set_size(s_cont, lv_pct(100), ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); lv_obj_align(s_cont, LV_ALIGN_TOP_MID, 0, UI_CHROME_HEADER_H); lv_obj_set_style_bg_opa(s_cont, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(s_cont, 0, 0); diff --git a/firmware_p4/components/Applications/ui/screens/nfc/nfc_scan_ui.c b/firmware_p4/components/Applications/ui/screens/nfc/nfc_scan_ui.c index 49c96a18d..1c2fed39c 100644 --- a/firmware_p4/components/Applications/ui/screens/nfc/nfc_scan_ui.c +++ b/firmware_p4/components/Applications/ui/screens/nfc/nfc_scan_ui.c @@ -23,12 +23,11 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_semantic.h" #include "ui_theme.h" #define NFC_ICON "/assets/icons/contactless.bin" -#define SIG_GREEN 0x00E676 -#define COL_DIM 0x8A8594 #define COL_RAISE 0x170A28 #define SCAN_STEP_MS 480 @@ -127,8 +126,8 @@ static void set_row_state(int i, row_state_t state) { if (s_dot[i] == NULL || s_value[i] == NULL || s_row[i] == NULL) return; - lv_color_t dot_color = lv_color_hex(COL_DIM); - lv_color_t txt_color = lv_color_hex(COL_DIM); + lv_color_t dot_color = current_theme.text_secondary; + lv_color_t txt_color = current_theme.text_secondary; const char *txt = VAL_QUEUED; bool raise = false; @@ -140,13 +139,13 @@ static void set_row_state(int i, row_state_t state) { raise = true; break; case ROW_PRESENT: - dot_color = lv_color_hex(SIG_GREEN); - txt_color = lv_color_hex(SIG_GREEN); + dot_color = lv_color_hex(UI_COL_SUCCESS); + txt_color = lv_color_hex(UI_COL_SUCCESS); txt = s_present_value; break; case ROW_ABSENT: - dot_color = lv_color_hex(COL_DIM); - txt_color = lv_color_hex(COL_DIM); + dot_color = current_theme.text_secondary; + txt_color = current_theme.text_secondary; txt = VAL_ABSENT; break; case ROW_PENDING: @@ -217,7 +216,7 @@ static void build_card(void) { lv_obj_set_style_radius(dot, DOT_RADIUS, 0); lv_obj_set_style_border_width(dot, 0, 0); lv_obj_set_style_bg_opa(dot, LV_OPA_COVER, 0); - lv_obj_set_style_bg_color(dot, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_bg_color(dot, current_theme.text_secondary, 0); lv_obj_t *tech = lv_label_create(group); lv_label_set_text(tech, TECHS[i].tech); @@ -229,7 +228,7 @@ static void build_card(void) { lv_label_set_long_mode(value, LV_LABEL_LONG_DOT); lv_obj_set_flex_grow(value, 1); lv_label_set_text(value, VAL_QUEUED); - lv_obj_set_style_text_color(value, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(value, current_theme.text_secondary, 0); lv_obj_set_style_text_font(value, &lv_font_montserrat_12, 0); lv_obj_set_style_text_align(value, LV_TEXT_ALIGN_RIGHT, 0); } @@ -260,11 +259,11 @@ static void finish_scan(void) { snprintf(buf, sizeof(buf), "%d of %d present", present, TECH_COUNT); lv_label_set_text(s_summary, buf); lv_obj_set_style_text_color( - s_summary, present > 0 ? lv_color_hex(SIG_GREEN) : lv_color_hex(COL_DIM), 0); + s_summary, present > 0 ? lv_color_hex(UI_COL_SUCCESS) : current_theme.text_secondary, 0); } if (s_status != NULL) { lv_label_set_text(s_status, STATUS_DONE); - lv_obj_set_style_text_color(s_status, lv_color_hex(SIG_GREEN), 0); + lv_obj_set_style_text_color(s_status, lv_color_hex(UI_COL_SUCCESS), 0); } if (s_hint != NULL) ui_chrome_footer_set_text(s_hint, HINT_DONE); diff --git a/firmware_p4/components/Applications/ui/screens/nfc/nfc_ultralight_ui.c b/firmware_p4/components/Applications/ui/screens/nfc/nfc_ultralight_ui.c index 0f2ef09fd..50ff4c582 100644 --- a/firmware_p4/components/Applications/ui/screens/nfc/nfc_ultralight_ui.c +++ b/firmware_p4/components/Applications/ui/screens/nfc/nfc_ultralight_ui.c @@ -20,11 +20,12 @@ #include "ui_chrome.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #define MX 8 -#define BODY_H (LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) -#define CONTENT_W (LCD_H_RES - 2 * MX) +#define BODY_H (ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) +#define CONTENT_W (ui_screen_w() - 2 * MX) #define ROW_GAP 5 #define CHIP_H 16 @@ -35,7 +36,6 @@ #define DUMP_PAD 8 #define DUMP_LGAP 3 -#define COL_DIM 0x8A8594 #define COL_LINE 0x2A2636 #define COL_PANEL2 0x1A1626 @@ -82,7 +82,8 @@ static void make_chip(lv_obj_t *parent, const char *txt, bool sel) { lv_obj_t *l = lv_label_create(chip); lv_label_set_text(l, txt); lv_obj_set_style_text_font(l, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(l, sel ? current_theme.border_accent : lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color( + l, sel ? current_theme.border_accent : current_theme.text_secondary, 0); lv_obj_center(l); } @@ -157,7 +158,7 @@ void ui_nfc_ultralight_open(void) { lv_obj_t *body = lv_obj_create(s_screen); lv_obj_remove_flag(body, LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(body, LV_OBJ_FLAG_CLICKABLE); - lv_obj_set_size(body, LCD_H_RES, BODY_H); + lv_obj_set_size(body, ui_screen_w(), BODY_H); lv_obj_align(body, LV_ALIGN_TOP_MID, 0, UI_CHROME_HEADER_H); lv_obj_set_style_bg_opa(body, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(body, 0, 0); diff --git a/firmware_p4/components/Applications/ui/screens/nfc/nfc_write_ui.c b/firmware_p4/components/Applications/ui/screens/nfc/nfc_write_ui.c index bffbe8a46..ad9de0817 100644 --- a/firmware_p4/components/Applications/ui/screens/nfc/nfc_write_ui.c +++ b/firmware_p4/components/Applications/ui/screens/nfc/nfc_write_ui.c @@ -30,11 +30,11 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" +#include "ui_semantic.h" #include "ui_theme.h" #define WR_TICK_MS 33 -#define SIG_GREEN 0x00E676 -#define COL_DIM 0x8A8594 #define WRITE_ICON "/assets/icons/edit.bin" #define MAX_CARDS 10 @@ -44,7 +44,7 @@ #define SLOT_W 150 #define SLOT_H 72 #define SLOT_Y (ARROW_Y + 26) -#define SLOT_X ((LCD_H_RES - SLOT_W) / 2) +#define SLOT_X ((ui_screen_w() - SLOT_W) / 2) enum { WR_NONE, WR_PLACE, WR_WRITING, WR_DONE }; #define T_PLACE 1300 @@ -127,7 +127,7 @@ static void build_empty(const char *icon, const char *title, const char *sub) { lv_obj_t *s = lv_label_create(card); lv_label_set_text(s, sub); lv_obj_set_style_text_font(s, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(s, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s, current_theme.text_secondary, 0); } static lv_obj_t *dash_line(lv_obj_t *parent, lv_point_precise_t *pts, int x, int y) { @@ -153,12 +153,16 @@ static void bench_build(void) { s_body = lv_obj_create(s_screen); lv_obj_remove_flag(s_body, LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(s_body, LV_OBJ_FLAG_CLICKABLE); - lv_obj_set_size(s_body, lv_pct(100), LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); + lv_obj_set_size(s_body, lv_pct(100), ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); lv_obj_align(s_body, LV_ALIGN_TOP_MID, 0, UI_CHROME_HEADER_H); lv_obj_set_style_bg_opa(s_body, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(s_body, 0, 0); lv_obj_set_style_pad_all(s_body, 0, 0); + const int body_h = ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H; + const int slot_y = LV_MIN(SLOT_Y, body_h - SLOT_H); + const int arrow_y = LV_MIN(ARROW_Y, slot_y - 26); + lv_obj_t *src = nfc_ui_card_panel(s_body, nfc_sim_saved_get(s_sel)); lv_obj_align(src, LV_ALIGN_TOP_MID, 0, SRC_Y); card_rise(src); @@ -167,7 +171,7 @@ static void bench_build(void) { lv_label_set_text(arrow, LV_SYMBOL_DOWN); lv_obj_set_style_text_font(arrow, &lv_font_montserrat_14, 0); lv_obj_set_style_text_color(arrow, current_theme.border_accent, 0); - lv_obj_align(arrow, LV_ALIGN_TOP_MID, 0, ARROW_Y); + lv_obj_align(arrow, LV_ALIGN_TOP_MID, 0, arrow_y); top_pts[0].x = 0; top_pts[0].y = 0; @@ -185,17 +189,17 @@ static void bench_build(void) { rgt_pts[0].y = 0; rgt_pts[1].x = 0; rgt_pts[1].y = SLOT_H; - dash_line(s_body, top_pts, SLOT_X, SLOT_Y); - dash_line(s_body, bot_pts, SLOT_X, SLOT_Y + SLOT_H); - dash_line(s_body, lft_pts, SLOT_X, SLOT_Y); - dash_line(s_body, rgt_pts, SLOT_X + SLOT_W, SLOT_Y); + dash_line(s_body, top_pts, SLOT_X, slot_y); + dash_line(s_body, bot_pts, SLOT_X, slot_y + SLOT_H); + dash_line(s_body, lft_pts, SLOT_X, slot_y); + dash_line(s_body, rgt_pts, SLOT_X + SLOT_W, slot_y); lv_obj_t *slot_lbl = lv_label_create(s_body); lv_label_set_text(slot_lbl, "place blank tag\nto write"); lv_obj_set_style_text_align(slot_lbl, LV_TEXT_ALIGN_CENTER, 0); lv_obj_set_style_text_font(slot_lbl, &lv_font_montserrat_12, 0); lv_obj_set_style_text_color(slot_lbl, current_theme.border_accent, 0); - lv_obj_align(slot_lbl, LV_ALIGN_TOP_MID, 0, SLOT_Y + 20); + lv_obj_align(slot_lbl, LV_ALIGN_TOP_MID, 0, slot_y + 20); s_dots = page_dots_create(s_body, s_count, LV_ALIGN_BOTTOM_MID, 0, -2); page_dots_set(&s_dots, s_sel); @@ -282,18 +286,18 @@ static void begin_writing(void) { } static void finish_write(void) { - lv_obj_set_style_text_color(s_ov_status, lv_color_hex(SIG_GREEN), 0); + lv_obj_set_style_text_color(s_ov_status, lv_color_hex(UI_COL_SUCCESS), 0); lv_label_set_text(s_ov_status, "Written!"); if (s_ov_bar) { lv_bar_set_value(s_ov_bar, 100, LV_ANIM_OFF); - lv_obj_set_style_bg_color(s_ov_bar, lv_color_hex(SIG_GREEN), LV_PART_INDICATOR); + lv_obj_set_style_bg_color(s_ov_bar, lv_color_hex(UI_COL_SUCCESS), LV_PART_INDICATOR); } if (s_ov_card) { s_ov_ok = lv_obj_create(s_ov); lv_obj_remove_flag(s_ov_ok, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_size(s_ov_ok, 30, 30); lv_obj_set_style_radius(s_ov_ok, LV_RADIUS_CIRCLE, 0); - lv_obj_set_style_bg_color(s_ov_ok, lv_color_hex(SIG_GREEN), 0); + lv_obj_set_style_bg_color(s_ov_ok, lv_color_hex(UI_COL_SUCCESS), 0); lv_obj_set_style_bg_opa(s_ov_ok, LV_OPA_COVER, 0); lv_obj_set_style_border_width(s_ov_ok, 0, 0); lv_obj_align_to(s_ov_ok, s_ov_card, LV_ALIGN_TOP_RIGHT, -8, 8); diff --git a/firmware_p4/components/Applications/ui/screens/octobit/octobit_status_ui.c b/firmware_p4/components/Applications/ui/screens/octobit/octobit_status_ui.c index 34e2390e4..0195edcb5 100644 --- a/firmware_p4/components/Applications/ui/screens/octobit/octobit_status_ui.c +++ b/firmware_p4/components/Applications/ui/screens/octobit/octobit_status_ui.c @@ -52,7 +52,6 @@ #define OCTO_XP_MAX 2000 #define COL_RAISE 0x170A28 -#define COL_DIM 0x8A8594 #define REFRESH_MS 1000 @@ -156,7 +155,7 @@ static void refresh_values(void) { static void refresh_selection(void) { const lv_color_t accent = current_theme.border_accent; - const lv_color_t dim = lv_color_hex(COL_DIM); + const lv_color_t dim = current_theme.text_secondary; for (int i = 0; i < ST_COUNT; i++) { bool sel = (i == s_sel); lv_obj_set_style_border_color(s_row[i], sel ? accent : current_theme.border_inactive, 0); @@ -277,13 +276,13 @@ static void build_top_card(void) { lv_obj_t *xp_tag = lv_label_create(card); lv_label_set_text(xp_tag, "XP"); lv_obj_set_style_text_font(xp_tag, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(xp_tag, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(xp_tag, current_theme.text_secondary, 0); lv_obj_align(xp_tag, LV_ALIGN_LEFT_MID, col_x, -12); lv_obj_t *xp_val = lv_label_create(card); lv_label_set_text_fmt(xp_val, "%d / %d", OCTO_XP, OCTO_XP_MAX); lv_obj_set_style_text_font(xp_val, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(xp_val, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(xp_val, current_theme.text_secondary, 0); lv_obj_set_width(xp_val, col_w); lv_obj_set_style_text_align(xp_val, LV_TEXT_ALIGN_RIGHT, 0); lv_obj_align(xp_val, LV_ALIGN_LEFT_MID, col_x, -12); @@ -319,13 +318,13 @@ static void build_selector(void) { lv_obj_t *tag = lv_label_create(s_screen); lv_label_set_text(tag, "STATISTICS"); lv_obj_set_style_text_font(tag, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(tag, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(tag, current_theme.text_secondary, 0); lv_obj_align(tag, LV_ALIGN_TOP_LEFT, MX + 4, SELHD_Y); s_counter = lv_label_create(s_screen); lv_label_set_text(s_counter, "1/8"); lv_obj_set_style_text_font(s_counter, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(s_counter, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s_counter, current_theme.text_secondary, 0); lv_obj_align(s_counter, LV_ALIGN_TOP_RIGHT, -(MX + 4), SELHD_Y); lv_obj_t *wrap = lv_obj_create(s_screen); diff --git a/firmware_p4/components/Applications/ui/screens/power/power_ui.c b/firmware_p4/components/Applications/ui/screens/power/power_ui.c index 17f11dead..dd35be5db 100644 --- a/firmware_p4/components/Applications/ui/screens/power/power_ui.c +++ b/firmware_p4/components/Applications/ui/screens/power/power_ui.c @@ -24,6 +24,7 @@ #include "msgbox_ui.h" #include "ui_chrome.h" #include "ui_manager.h" +#include "ui_semantic.h" #include "ui_theme.h" #define REFRESH_MS 700 @@ -59,9 +60,7 @@ #define ICON_W 20 #define NAME_PAD_L 9 -#define SUCCESS_COLOR 0x00E676 -#define COL_DIM 0x8A8594 -#define COL_RAISE 0x170A28 +#define COL_RAISE 0x170A28 enum { ACT_CHARGE, ACT_SCAN, ACT_REGS, ACT_OFF, ACT_COUNT }; @@ -108,12 +107,12 @@ static const char *vbus_name(bq25896_vbus_status_t s) { static void style_state_chip(bool charging) { if (charging) { - lv_obj_set_style_text_color(s_state, lv_color_hex(SUCCESS_COLOR), 0); - lv_obj_set_style_bg_color(s_state, lv_color_hex(SUCCESS_COLOR), 0); + lv_obj_set_style_text_color(s_state, lv_color_hex(UI_COL_SUCCESS), 0); + lv_obj_set_style_bg_color(s_state, lv_color_hex(UI_COL_SUCCESS), 0); lv_obj_set_style_bg_opa(s_state, LV_OPA_20, 0); lv_obj_set_style_border_width(s_state, 0, 0); } else { - lv_obj_set_style_text_color(s_state, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s_state, current_theme.text_secondary, 0); lv_obj_set_style_bg_color(s_state, current_theme.bg_secondary, 0); lv_obj_set_style_bg_opa(s_state, LV_OPA_COVER, 0); lv_obj_set_style_border_width(s_state, CHIP_BORDER_W, 0); @@ -180,7 +179,7 @@ static void refresh_telem(void) { static void refresh_selection(void) { const lv_color_t accent = current_theme.border_accent; - const lv_color_t dim = lv_color_hex(COL_DIM); + const lv_color_t dim = current_theme.text_secondary; for (int i = 0; i < ACT_COUNT; i++) { bool sel = (i == s_sel); lv_obj_set_style_border_color(s_row[i], sel ? accent : current_theme.border_inactive, 0); @@ -235,7 +234,7 @@ static void build_hero(void) { s_src = lv_label_create(card); lv_label_set_text(s_src, ""); lv_obj_set_style_text_font(s_src, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(s_src, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s_src, current_theme.text_secondary, 0); lv_obj_align(s_src, LV_ALIGN_RIGHT_MID, SRC_X, SRC_Y); } diff --git a/firmware_p4/components/Applications/ui/screens/rfid/rfid_menu_ui.c b/firmware_p4/components/Applications/ui/screens/rfid/rfid_menu_ui.c index 365a1d478..7a90b9d31 100644 --- a/firmware_p4/components/Applications/ui/screens/rfid/rfid_menu_ui.c +++ b/firmware_p4/components/Applications/ui/screens/rfid/rfid_menu_ui.c @@ -33,6 +33,7 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_semantic.h" #include "ui_theme.h" #include "waves_ui.h" @@ -41,8 +42,6 @@ static const char *TAG = "RFID_UI"; #define TICK_MS 50 #define REVEAL_MS 3000 -#define SIG_GREEN 0x00E676 -#define COL_DIM 0x8A8594 #define COL_DANGER 0xFF5470 #define LIST_TITLE_ICON "/assets/icons/contactless.bin" @@ -176,7 +175,7 @@ static const char *TAG = "RFID_UI"; #define CARD_TITLE "EM4100" #define CARD_SUBTITLE "Low-Frequency 125 kHz" #define CARD_LINE "UID 1A 2B 3C 4D 55" -#define CARD_META "64-bit · Read-only" +#define CARD_META "64-bit | Read-only" static const char *const WIEGAND_LINES[] = { "Format: HID 26-bit", @@ -210,10 +209,10 @@ typedef struct { } rfid_card_t; static const rfid_card_t SAVED_CARDS[] = { - {"Office_Badge", "EM4100", "1A 2B 3C 4D 55", "64-bit · Read-only"}, - {"Garage_Fob", "HIDProx", "20 06 EC 0C 86", "44-bit · Read-only"}, - {"Gym_Tag", "Indala", "A0 00 1C FE 49", "64-bit · Read-only"}, - {"Locker_03", "EM4100", "09 FB 2D 77 11", "64-bit · Read-only"}, + {"Office_Badge", "EM4100", "1A 2B 3C 4D 55", "64-bit | Read-only"}, + {"Garage_Fob", "HIDProx", "20 06 EC 0C 86", "44-bit | Read-only"}, + {"Gym_Tag", "Indala", "A0 00 1C FE 49", "64-bit | Read-only"}, + {"Locker_03", "EM4100", "09 FB 2D 77 11", "64-bit | Read-only"}, }; #define SAVED_CARD_COUNT ((int)(sizeof(SAVED_CARDS) / sizeof(SAVED_CARDS[0]))) #define RFID_MAX_CARDS 16 @@ -587,7 +586,7 @@ static void reveal_captured_card(void) { lv_anim_delete(s_status_lbl, opa_cb); lv_obj_set_style_opa(s_status_lbl, LV_OPA_COVER, 0); lv_label_set_text(s_status_lbl, "Tag detected!"); - lv_obj_set_style_text_color(s_status_lbl, lv_color_hex(SIG_GREEN), 0); + lv_obj_set_style_text_color(s_status_lbl, lv_color_hex(UI_COL_SUCCESS), 0); } lv_obj_t *card = @@ -676,7 +675,7 @@ static void build_saved_empty(void) { lv_obj_t *s = lv_label_create(card); lv_label_set_text(s, "Read or add a tag first"); lv_obj_set_style_text_font(s, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(s, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s, current_theme.text_secondary, 0); s_hint = ui_chrome_footer(s_screen, HINT_SHOW); } @@ -703,7 +702,7 @@ static void saved_row_detail(const rfid_card_t *c, char *out, size_t n) { int fc = 0; int cn = 0; uid_wiegand(c->uid, &fc, &cn); - snprintf(out, n, "%s · FC %d CN %d", c->proto, fc, cn); + snprintf(out, n, "%s | FC %d CN %d", c->proto, fc, cn); } static void saved_apply_selection(void) { @@ -900,7 +899,7 @@ static void seed_emulate_default(void) { snprintf(s_emu_sub, sizeof(s_emu_sub), "%s", CARD_SUBTITLE); snprintf(s_emu_line, sizeof(s_emu_line), "%s", CARD_LINE); snprintf(s_emu_meta, sizeof(s_emu_meta), "%s", CARD_META); - snprintf(s_emu_freq, sizeof(s_emu_freq), "EM4100 · 125 kHz LF"); + snprintf(s_emu_freq, sizeof(s_emu_freq), "EM4100 | 125 kHz LF"); } static void seed_emulate_from_card(const rfid_card_t *c) { @@ -908,7 +907,7 @@ static void seed_emulate_from_card(const rfid_card_t *c) { snprintf(s_emu_sub, sizeof(s_emu_sub), "%s", c->proto); snprintf(s_emu_line, sizeof(s_emu_line), "UID %s", c->uid); snprintf(s_emu_meta, sizeof(s_emu_meta), "%s", c->bits); - snprintf(s_emu_freq, sizeof(s_emu_freq), "%s · 125 kHz LF", c->proto); + snprintf(s_emu_freq, sizeof(s_emu_freq), "%s | 125 kHz LF", c->proto); } static void emu_tick(void) { @@ -924,7 +923,7 @@ static void build_emulate(void) { s_status_lbl = lv_label_create(s_screen); lv_label_set_text(s_status_lbl, "Emulating"); - lv_obj_set_style_text_color(s_status_lbl, lv_color_hex(SIG_GREEN), 0); + lv_obj_set_style_text_color(s_status_lbl, lv_color_hex(UI_COL_SUCCESS), 0); lv_obj_set_style_text_font(s_status_lbl, &lv_font_montserrat_14, 0); lv_obj_align(s_status_lbl, LV_ALIGN_TOP_MID, 0, STATUS_Y); @@ -1066,7 +1065,7 @@ static void clone_tick(void) { } if (s_status_lbl != NULL) { lv_label_set_text(s_status_lbl, "Source captured!"); - lv_obj_set_style_text_color(s_status_lbl, lv_color_hex(SIG_GREEN), 0); + lv_obj_set_style_text_color(s_status_lbl, lv_color_hex(UI_COL_SUCCESS), 0); } s_clone_card = build_data_card(s_screen, CARD_TITLE, CARD_SUBTITLE, CARD_LINE, CARD_META, true, false); @@ -1089,7 +1088,7 @@ static void clone_tick(void) { if (el >= CLONE_WRITE_MS) { if (s_status_lbl != NULL) { lv_label_set_text(s_status_lbl, "Cloned!"); - lv_obj_set_style_text_color(s_status_lbl, lv_color_hex(SIG_GREEN), 0); + lv_obj_set_style_text_color(s_status_lbl, lv_color_hex(UI_COL_SUCCESS), 0); } ui_feedback(UI_FB_WRITE); ESP_LOGI(TAG, "mock rfid clone written: %s", CARD_TITLE); @@ -1134,13 +1133,13 @@ static void format_uid(const char *text, char *out, size_t n) { static void add_uid_submit(const char *text, void *ud) { (void)ud; format_uid(text, s_add_uid, sizeof(s_add_uid)); - menu_component_set_selector_value(&s_menu, 1, s_add_uid[0] ? s_add_uid : "—"); + menu_component_set_selector_value(&s_menu, 1, s_add_uid[0] ? s_add_uid : "-"); } static void build_add_manual(void) { s_menu = menu_component_create(s_screen, "ADD MANUALLY", WRITE_ICON); menu_component_add_selector(&s_menu, CARD_ICON, "Protocol", ADD_PROTOS[s_add_proto]); - menu_component_add_selector(&s_menu, COPY_ICON, "UID", s_add_uid[0] ? s_add_uid : "—"); + menu_component_add_selector(&s_menu, COPY_ICON, "UID", s_add_uid[0] ? s_add_uid : "-"); menu_component_add_selector(&s_menu, FILE_ICON, "Bits", ADD_BITS[s_add_bits]); menu_component_add_item(&s_menu, SAVED_ICON, "Save card"); menu_component_set_hint(&s_menu, HINT_ADD); @@ -1163,7 +1162,7 @@ static void add_manual_commit(void) { snprintf(c->name, sizeof(c->name), "Manual_%02d", s_card_count + 1); snprintf(c->proto, sizeof(c->proto), "%s", ADD_PROTOS[s_add_proto]); snprintf(c->uid, sizeof(c->uid), "%s", s_add_uid); - snprintf(c->bits, sizeof(c->bits), "%s · Read-only", ADD_BITS[s_add_bits]); + snprintf(c->bits, sizeof(c->bits), "%s | Read-only", ADD_BITS[s_add_bits]); s_saved_sel = s_card_count; s_card_count++; diff --git a/firmware_p4/components/Applications/ui/screens/settings/battery_settings_ui.c b/firmware_p4/components/Applications/ui/screens/settings/battery_settings_ui.c index 0e40cdaac..583f9b692 100644 --- a/firmware_p4/components/Applications/ui/screens/settings/battery_settings_ui.c +++ b/firmware_p4/components/Applications/ui/screens/settings/battery_settings_ui.c @@ -20,6 +20,8 @@ #include "battery_service.h" #include "ui_chrome.h" #include "ui_manager.h" +#include "ui_metrics.h" +#include "ui_semantic.h" #include "ui_theme.h" static const char *TAG = "BATTERY_SETTINGS_UI"; @@ -34,7 +36,6 @@ static const char *TAG = "BATTERY_SETTINGS_UI"; #define STAT_CARD_H 46 #define LOW_COLOR 0xE53935 #define MID_COLOR 0xFFB300 -#define OK_COLOR 0x00E676 #define TITLE_FONT "A:assets/fonts/Inter.bin" static lv_obj_t *s_screen = NULL; @@ -46,7 +47,7 @@ static lv_color_t level_color(int pct) { return lv_color_hex(LOW_COLOR); if (pct < 60) return lv_color_hex(MID_COLOR); - return lv_color_hex(OK_COLOR); + return lv_color_hex(UI_COL_SUCCESS); } static void arc_anim_exec_cb(void *obj, int32_t v) { @@ -158,7 +159,7 @@ void ui_battery_settings_open(void) { uint32_t chip_color; if (charging) { lv_label_set_text(chg, LV_SYMBOL_CHARGE " CHARGING"); - chip_color = OK_COLOR; + chip_color = UI_COL_SUCCESS; } else if (on_usb) { lv_label_set_text(chg, LV_SYMBOL_CHARGE " ON USB"); chip_color = MID_COLOR; @@ -177,8 +178,11 @@ void ui_battery_settings_open(void) { lv_obj_t *stats = lv_obj_create(s_screen); lv_obj_remove_style_all(stats); - lv_obj_set_size(stats, 216, LV_SIZE_CONTENT); - lv_obj_align(stats, LV_ALIGN_TOP_MID, 0, ARC_TOP_Y + ARC_SIZE + 16); + lv_obj_set_size(stats, ui_screen_w() - 24, LV_SIZE_CONTENT); + lv_obj_align(stats, + LV_ALIGN_TOP_MID, + 0, + LV_MIN(ARC_TOP_Y + ARC_SIZE + 16, ui_screen_h() - UI_CHROME_FOOTER_H - STAT_CARD_H)); lv_obj_remove_flag(stats, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_flex_flow(stats, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(stats, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); @@ -190,16 +194,16 @@ void ui_battery_settings_open(void) { add_stat_card(stats, on_usb ? "USB" : "BATT", "SOURCE", - on_usb ? lv_color_hex(OK_COLOR) : current_theme.text_main); + on_usb ? lv_color_hex(UI_COL_SUCCESS) : current_theme.text_main); const char *st = "IDLE"; lv_color_t st_col = current_theme.border_accent; if (charging) { st = (bs.chg == CHARGE_STATUS_PRECHARGE) ? "PRE" : "FAST"; - st_col = lv_color_hex(OK_COLOR); + st_col = lv_color_hex(UI_COL_SUCCESS); } else if (bs.chg == CHARGE_STATUS_CHARGE_DONE) { st = "FULL"; - st_col = lv_color_hex(OK_COLOR); + st_col = lv_color_hex(UI_COL_SUCCESS); } add_stat_card(stats, st, "STATUS", st_col); diff --git a/firmware_p4/components/Applications/ui/screens/settings/c5_status_ui.c b/firmware_p4/components/Applications/ui/screens/settings/c5_status_ui.c index a958eff71..b82e5b81c 100644 --- a/firmware_p4/components/Applications/ui/screens/settings/c5_status_ui.c +++ b/firmware_p4/components/Applications/ui/screens/settings/c5_status_ui.c @@ -27,10 +27,12 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" +#include "ui_semantic.h" #include "ui_theme.h" #define MX 8 -#define CONTENT_W (LCD_H_RES - 2 * MX) +#define CONTENT_W (ui_screen_w() - 2 * MX) #define INFO_Y 50 #define INFO_H 98 #define INFO_ROW_GAP 26 @@ -39,9 +41,7 @@ #define ROW_GAP 6 #define ROW_STEP (ROW_H + ROW_GAP) -#define COL_SUCCESS 0x00E676 -#define COL_DIM 0x8A8594 -#define COL_RAISE 0x170A28 +#define COL_RAISE 0x170A28 #define HDR_ICON "/assets/icons/developer_board.bin" #define HDR_TITLE "C5 STATUS" @@ -86,7 +86,7 @@ static lv_obj_t *info_row(lv_obj_t *card, int index, const char *tag_txt, const lv_obj_t *val = lv_label_create(card); lv_label_set_text(val, val_txt); lv_obj_set_style_text_font(val, &lv_font_montserrat_14, 0); - lv_obj_set_style_text_color(val, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(val, current_theme.text_secondary, 0); lv_obj_align(val, LV_ALIGN_TOP_RIGHT, 0, index * INFO_ROW_GAP); return val; } @@ -122,7 +122,7 @@ static void build_info_card(void) { lv_obj_t *link_val = info_row(card, 0, "Link", alive ? "ALIVE" : "DOWN"); if (alive) - lv_obj_set_style_text_color(link_val, lv_color_hex(COL_SUCCESS), 0); + lv_obj_set_style_text_color(link_val, lv_color_hex(UI_COL_SUCCESS), 0); info_row(card, 1, "C5 FW", cver); info_row(card, 2, "Expected", FIRMWARE_VERSION); } @@ -132,7 +132,12 @@ static lv_obj_t *make_action_row(lv_obj_t *parent, int i) { lv_obj_remove_flag(row, LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(row, LV_OBJ_FLAG_CLICKABLE); lv_obj_set_size(row, CONTENT_W, ROW_H); - lv_obj_align(row, LV_ALIGN_TOP_MID, 0, ACT_Y + i * ROW_STEP); + lv_obj_align( + row, + LV_ALIGN_TOP_MID, + 0, + LV_MIN(ACT_Y, ui_screen_h() - UI_CHROME_FOOTER_H - ((ACT_COUNT - 1) * ROW_STEP + ROW_H)) + + i * ROW_STEP); lv_obj_set_style_radius(row, 9, 0); lv_obj_set_style_bg_color(row, current_theme.bg_secondary, 0); lv_obj_set_style_bg_opa(row, LV_OPA_COVER, 0); @@ -163,7 +168,7 @@ static lv_obj_t *make_action_row(lv_obj_t *parent, int i) { static void refresh_selection(void) { const lv_color_t accent = current_theme.border_accent; - const lv_color_t dim = lv_color_hex(COL_DIM); + const lv_color_t dim = current_theme.text_secondary; for (int i = 0; i < ACT_COUNT; i++) { bool sel = (i == s_sel); lv_obj_set_style_border_color(s_act_row[i], sel ? accent : current_theme.border_inactive, 0); diff --git a/firmware_p4/components/Applications/ui/screens/settings/display_settings_ui.c b/firmware_p4/components/Applications/ui/screens/settings/display_settings_ui.c index ccec15e81..e8c6f6cf4 100644 --- a/firmware_p4/components/Applications/ui/screens/settings/display_settings_ui.c +++ b/firmware_p4/components/Applications/ui/screens/settings/display_settings_ui.c @@ -61,14 +61,31 @@ static menu_component_t s_menu; static bool s_changed = false; +// Menu row to re-focus after a rotation-triggered rebuild, so toggling rotation +// does not bounce the selection back to the top row. -1 = nothing pending. +static int s_pending_row = -1; + static void cycle_selector(int sel, int dir) { if (sel == ROW_ROTATION) { s_rotation_idx = (s_rotation_idx + dir + ROTATION_COUNT) % ROTATION_COUNT; - menu_component_set_selector_value(&s_menu, sel, ROTATION_OPTS[s_rotation_idx]); bool want_landscape = (s_rotation_idx == 1); if (want_landscape != lvgl_glue_is_landscape()) lvgl_glue_toggle_rotation(); - s_changed = true; + // The rebuild re-reads state from g_config_screen and resets s_changed, so + // capture every pending setting first, persist, then rebuild. This keeps the + // new orientation live and reflows this screen (and all screens opened after) + // at the new logical resolution without dropping a pending brightness/timeout + // edit made before the toggle. + g_config_screen.brightness = + menu_component_get_intensity(&s_menu, ROW_BRIGHTNESS) * BRIGHTNESS_STEP_PCT; + g_config_screen.rotation = want_landscape ? 2 : 1; + g_config_screen.auto_lock_seconds = TIMEOUT_SECS[s_timeout_idx]; + g_config_screen.auto_dim = menu_component_get_toggle(&s_menu, ROW_AUTODIM); + if (ui_sd_ready()) + tos_config_save(TOS_PATH_CONFIG_SCREEN, "screen"); + s_pending_row = ROW_ROTATION; + ui_relayout_current_screen(); + return; } else if (sel == ROW_TIMEOUT) { s_timeout_idx = (s_timeout_idx + dir + TIMEOUT_COUNT) % TIMEOUT_COUNT; menu_component_set_selector_value(&s_menu, sel, TIMEOUT_OPTS[s_timeout_idx]); @@ -184,6 +201,11 @@ void ui_display_settings_open(void) { &s_menu, "/assets/icons/brightness_auto.bin", "Auto-dim", g_config_screen.auto_dim); menu_component_add_toggle(&s_menu, "/assets/icons/invert_colors.bin", "Invert", false); + if (s_pending_row >= 0) { + menu_component_select(&s_menu, s_pending_row); + s_pending_row = -1; + } + if (s_menu.items_cont != NULL) lv_obj_fade_in(s_menu.items_cont, ENTRY_FADE_MS, 0); diff --git a/firmware_p4/components/Applications/ui/screens/settings/led_ctrl_ui.c b/firmware_p4/components/Applications/ui/screens/settings/led_ctrl_ui.c index 83122465d..a172f5c3a 100644 --- a/firmware_p4/components/Applications/ui/screens/settings/led_ctrl_ui.c +++ b/firmware_p4/components/Applications/ui/screens/settings/led_ctrl_ui.c @@ -24,10 +24,11 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #define MX 8 -#define CONTENT_W (LCD_H_RES - 2 * MX) +#define CONTENT_W (ui_screen_w() - 2 * MX) #define PREVIEW_Y 50 #define PREVIEW_D 72 @@ -42,26 +43,24 @@ #define PILL_W 52 #define PILL_H 24 -#define BRIGHT_MIN 5 // lowest level the LED still lights at +#define BRIGHT_MIN 5 #define BRIGHT_MAX 100 #define BRIGHT_STEP 5 #define BRIGHT_DEFAULT 80 -#define COL_DIM 0x8A8594 #define COL_OFF 0x101018 #define COL_TRACK 0x202028 #define HDR_ICON "/assets/icons/palette.bin" #define HDR_TITLE "LED SIGNALS" -// The three semantic signals whose color is being configured. enum { SIG_INFO = 0, SIG_WARNING, SIG_ERROR, SIG_COUNT }; static const char *SIG_NAMES[SIG_COUNT] = {"INFO", "WARNING", "ERROR"}; enum { - FOCUS_SIGNAL = 0, // which signal to edit (info / warning / error) - FOCUS_COLOR, // color assigned to the selected signal - FOCUS_BRIGHT, // global intensity (one value for all signals) + FOCUS_SIGNAL = 0, + FOCUS_COLOR, + FOCUS_BRIGHT, FOCUS_COUNT, }; @@ -70,9 +69,6 @@ typedef struct { uint32_t hex; } preset_t; -// Saturated primaries: these drive a physical RGB LED, so pale/pastel values -// (lots of all three channels) wash out to white. Keep the channels pure. The -// three signal defaults (purple/yellow/red) are all present so they map back. static const preset_t PRESETS[] = { {"Red", 0xFF0000}, {"Orange", 0xFF6000}, @@ -93,12 +89,10 @@ static lv_obj_t *s_sig_val = NULL; static int s_focus = FOCUS_SIGNAL; static int s_signal = SIG_INFO; -static int s_color_idx[SIG_COUNT]; // selected preset index per signal +static int s_color_idx[SIG_COUNT]; static int s_bright = BRIGHT_DEFAULT; static bool s_changed = false; -// Scale one 8-bit channel by a percentage, clamped to [0, 255] so an out-of-range -// percentage can never overflow the byte and wrap the color to a wrong hue. static uint8_t scale_channel(uint8_t chan, int pct) { if (pct < 0) pct = 0; @@ -129,8 +123,6 @@ static void update_preview(void) { lv_obj_set_style_shadow_color(s_preview, lv_color_hex(hex), 0); lv_obj_set_style_shadow_opa(s_preview, LV_OPA_60, 0); - // Drive the physical RGB LED (LP5816) to match the preview: selected signal's - // color scaled by the brightness percentage. led_set_color(scale_channel((hex >> 16) & 0xFF, s_bright), scale_channel((hex >> 8) & 0xFF, s_bright), scale_channel(hex & 0xFF, s_bright)); @@ -193,7 +185,7 @@ static lv_obj_t *make_ctrl_card(lv_obj_t *parent, const char *label, int h) { lv_obj_t *lbl = lv_label_create(card); lv_label_set_text(lbl, label); lv_obj_set_style_text_font(lbl, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(lbl, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(lbl, current_theme.text_secondary, 0); lv_obj_set_width(lbl, CARD_LABEL_W); return card; } @@ -292,7 +284,12 @@ static void build_controls(void) { lv_obj_remove_flag(col, LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(col, LV_OBJ_FLAG_CLICKABLE); lv_obj_set_size(col, CONTENT_W, LV_SIZE_CONTENT); - lv_obj_align(col, LV_ALIGN_TOP_MID, 0, CTRL_Y); + lv_obj_align(col, + LV_ALIGN_TOP_MID, + 0, + LV_MIN(CTRL_Y, + ui_screen_h() - UI_CHROME_FOOTER_H - + (SWATCH_CARD_H + BRIGHT_CARD_H + STEALTH_CARD_H + 2 * CTRL_GAP))); lv_obj_set_style_bg_opa(col, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(col, 0, 0); lv_obj_set_style_pad_all(col, 0, 0); @@ -310,7 +307,7 @@ static int preset_index_of(uint32_t hex) { if (PRESETS[i].hex == (hex & 0xFFFFFF)) return i; } - return 0; // custom/unknown color falls back to the first preset + return 0; } static void save_config(void) { @@ -319,14 +316,11 @@ static void save_config(void) { g_config_led.warning_color = PRESETS[s_color_idx[SIG_WARNING]].hex; g_config_led.error_color = PRESETS[s_color_idx[SIG_ERROR]].hex; - // Apply live so subsequent signals use the new colors/brightness immediately. led_set_signal_config(g_config_led.info_color, g_config_led.warning_color, g_config_led.error_color, g_config_led.brightness); - // Persist to SD only (matches the storage policy: no SD -> keep in RAM for this - // session but do not write anything). if (tos_config_save(TOS_PATH_CONFIG_LED, "led") == ESP_OK) { notify(NOTIFY_SAVED, "LED settings saved"); } else { diff --git a/firmware_p4/components/Applications/ui/screens/settings/sd_health_ui.c b/firmware_p4/components/Applications/ui/screens/settings/sd_health_ui.c index e22782ed2..1f257c439 100644 --- a/firmware_p4/components/Applications/ui/screens/settings/sd_health_ui.c +++ b/firmware_p4/components/Applications/ui/screens/settings/sd_health_ui.c @@ -25,6 +25,8 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" +#include "ui_semantic.h" #include "ui_theme.h" #include "vfs_core.h" #include "vfs_sdcard.h" @@ -37,11 +39,9 @@ #define FOOTER_TXT "OK REMOUNT R RETEST BACK" #define MX 8 -#define CONTENT_W (240 - 2 * MX) +#define CONTENT_W (ui_screen_w() - 2 * MX) -#define COL_DIM 0x8A8594 -#define COL_SUCCESS 0x00E676 -#define COL_ACC2 0xB89AFF +#define COL_ACC2 0xB89AFF #define HERO_Y 48 #define HERO_H 108 @@ -156,7 +156,7 @@ static void build_hero(void) { lv_obj_t *spec = lv_label_create(card); lv_label_set_text(spec, mounted ? "FAT32" : "Not mounted"); lv_obj_set_style_text_font(spec, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(spec, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(spec, current_theme.text_secondary, 0); lv_obj_align(spec, LV_ALIGN_TOP_LEFT, INFO_X, 26); lv_obj_t *chip = lv_obj_create(card); @@ -182,7 +182,7 @@ static void build_hero(void) { lv_obj_set_style_radius(bar, 5, LV_PART_MAIN); lv_obj_set_style_bg_color(bar, current_theme.bg_primary, LV_PART_MAIN); lv_obj_set_style_bg_opa(bar, LV_OPA_COVER, LV_PART_MAIN); - lv_obj_set_style_border_color(bar, lv_color_hex(COL_DIM), LV_PART_MAIN); + lv_obj_set_style_border_color(bar, current_theme.text_secondary, LV_PART_MAIN); lv_obj_set_style_border_opa(bar, LV_OPA_40, LV_PART_MAIN); lv_obj_set_style_border_width(bar, 1, LV_PART_MAIN); lv_obj_set_style_radius(bar, 5, LV_PART_INDICATOR); @@ -194,7 +194,7 @@ static void build_hero(void) { lv_obj_t *used = lv_label_create(card); lv_label_set_text(used, usedbuf); lv_obj_set_style_text_font(used, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(used, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(used, current_theme.text_secondary, 0); lv_obj_align(used, LV_ALIGN_TOP_LEFT, 0, KV_Y); lv_obj_t *free_lbl = lv_label_create(card); @@ -207,7 +207,8 @@ static void build_hero(void) { static void build_tile(int x, const char *caption, const char *value, lv_obj_t **val_out, lv_obj_t **sub_out) { lv_obj_t *card = make_card(s_screen, TILE_W, TILE_H); - lv_obj_align(card, LV_ALIGN_TOP_LEFT, x, TILE_Y); + lv_obj_align( + card, LV_ALIGN_TOP_LEFT, x, LV_MIN(TILE_Y, ui_screen_h() - UI_CHROME_FOOTER_H - TILE_H)); lv_obj_set_style_pad_ver(card, 4, 0); lv_obj_set_style_pad_row(card, 0, 0); lv_obj_set_flex_flow(card, LV_FLEX_FLOW_COLUMN); @@ -216,17 +217,17 @@ build_tile(int x, const char *caption, const char *value, lv_obj_t **val_out, lv lv_obj_t *cap = lv_label_create(card); lv_label_set_text(cap, caption); lv_obj_set_style_text_font(cap, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(cap, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(cap, current_theme.text_secondary, 0); lv_obj_t *val = lv_label_create(card); lv_label_set_text(val, value); lv_obj_set_style_text_font(val, &lv_font_montserrat_16, 0); - lv_obj_set_style_text_color(val, lv_color_hex(COL_SUCCESS), 0); + lv_obj_set_style_text_color(val, lv_color_hex(UI_COL_SUCCESS), 0); lv_obj_t *sub = lv_label_create(card); lv_label_set_text(sub, UNIT_OK); lv_obj_set_style_text_font(sub, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(sub, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(sub, current_theme.text_secondary, 0); *val_out = val; *sub_out = sub; @@ -236,7 +237,7 @@ static void build_cta(void) { lv_obj_t *row = lv_obj_create(s_screen); lv_obj_remove_flag(row, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_size(row, CONTENT_W, CTA_H); - lv_obj_align(row, LV_ALIGN_TOP_MID, 0, CTA_Y); + lv_obj_align(row, LV_ALIGN_TOP_MID, 0, LV_MIN(CTA_Y, ui_screen_h() - UI_CHROME_FOOTER_H - CTA_H)); lv_obj_set_style_radius(row, 9, 0); lv_obj_set_style_pad_all(row, 0, 0); lv_obj_set_style_bg_color(row, current_theme.bg_secondary, 0); diff --git a/firmware_p4/components/Applications/ui/screens/settings/settings_ui.c b/firmware_p4/components/Applications/ui/screens/settings/settings_ui.c index 52717a0b6..d1d044cae 100644 --- a/firmware_p4/components/Applications/ui/screens/settings/settings_ui.c +++ b/firmware_p4/components/Applications/ui/screens/settings/settings_ui.c @@ -49,7 +49,6 @@ static const char *TAG = "SETTINGS_UI"; #define C5_PASSTHROUGH_TASK_PRIO SYS_PRIO_SERVICE_HI #define REBOOT_DELAY_MS 80 -#define ACTION_TOGGLE_ROTATION (-1) #define ACTION_FLASH_C5 (-2) #define ACTION_C5_PASSTHROUGH (-3) #define ACTION_REBOOT_P4 (-4) @@ -84,7 +83,6 @@ static const settings_item_t MAIN_ITEMS[] = { {"DISPLAY", "/assets/icons/display_settings.bin", SCREEN_DISPLAY_SETTINGS}, {"INTERFACE", "/assets/icons/tune.bin", SCREEN_INTERFACE_SETTINGS}, {"THEME", "/assets/icons/palette.bin", SCREEN_THEME_SELECTOR}, - {"ROTATE SCREEN", "/assets/icons/screen_rotation.bin", ACTION_TOGGLE_ROTATION}, {"SOUND", "/assets/icons/volume_up.bin", SCREEN_SOUND_SETTINGS}, {"AUDIO & HAPTICS", "/assets/icons/graphic_eq.bin", GOTO_LAB}, {"BATTERY", "/assets/icons/battery_full.bin", SCREEN_BATTERY_SETTINGS}, @@ -174,11 +172,6 @@ view_table(settings_view_t view, int *count, const char **title, const char **ic } } -static void rotation_confirm_cb(bool confirm) { - if (confirm) - ui_manager_relayout_current(); -} - static void show_c5_progress(const char *msg) { if (s_c5_overlay == NULL) { s_c5_overlay = lv_obj_create(s_screen); @@ -270,7 +263,7 @@ static void c5_flash_task(void *arg) { esp_err_t r = (init_r != ESP_OK) ? init_r : c5_flasher_update(NULL, 0, SPI_OTA_TRANSPORT_SPI); ESP_LOGI(TAG, "c5_flasher result: %s", esp_err_to_name(r)); - lv_async_call(c5_flash_done_on_lvgl, (void *)(intptr_t)r); + ui_async_call(c5_flash_done_on_lvgl, (void *)(intptr_t)r); vTaskDelete(NULL); } @@ -295,6 +288,10 @@ static void start_c5_flash(void) { if (s_c5_prog_timer == NULL) s_c5_prog_timer = lv_timer_create(c5_progress_tick, C5_PROGRESS_TICK_MS, NULL); + if (s_c5_overlay) + lv_obj_move_foreground(s_c5_overlay); + lv_refr_now(NULL); + xTaskCreatePinnedToCore(c5_flash_task, "c5_flash", C5_FLASH_TASK_STACK, @@ -308,7 +305,7 @@ static void c5_rom_flash_task(void *arg) { (void)arg; esp_err_t r = c5_flasher_rom_flash(); ESP_LOGI(TAG, "c5_rom_flash result: %s", esp_err_to_name(r)); - lv_async_call(c5_flash_done_on_lvgl, (void *)(intptr_t)r); + ui_async_call(c5_flash_done_on_lvgl, (void *)(intptr_t)r); vTaskDelete(NULL); } @@ -413,14 +410,6 @@ static void start_reboot_p4(void) { static bool run_action(int target) { switch (target) { - case ACTION_TOGGLE_ROTATION: - - msgbox_open("/assets/icons/screen_rotation.bin", - "Switch portrait/landscape now?", - "YES", - "NO", - rotation_confirm_cb); - return true; case ACTION_FLASH_C5: start_c5_flash(); diff --git a/firmware_p4/components/Applications/ui/screens/settings/storage_settings_ui.c b/firmware_p4/components/Applications/ui/screens/settings/storage_settings_ui.c index 06436881d..7070bbe6d 100644 --- a/firmware_p4/components/Applications/ui/screens/settings/storage_settings_ui.c +++ b/firmware_p4/components/Applications/ui/screens/settings/storage_settings_ui.c @@ -29,6 +29,7 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #include "vfs_core.h" #include "vfs_sdcard.h" @@ -38,12 +39,11 @@ static const char *TAG = "STORAGE_UI"; #define SD_PATH "/sdcard" #define ASSETS_LABEL "assets" #define DATA_LABEL "storage" -#define CONTENT_W 204 +#define CONTENT_W (LIST_W - 14) #define BAR_H 9 #define G1 0x7A52D6 #define G2 0xB89AFF #define CYAN 0x00E5D0 -#define OK_COLOR 0x00E676 #define DANGER_COLOR 0xFF5470 #define ACT_EJECT 0 #define ACT_FORMAT 1 @@ -54,11 +54,11 @@ static const char *TAG = "STORAGE_UI"; #define LIST_LEFT 6 #define LIST_TOP_Y 46 -#define LIST_W 218 -#define LIST_H 248 -#define SB_TRACK_X 227 +#define LIST_W (ui_screen_w() - LIST_LEFT - 16) +#define LIST_H (ui_screen_h() - LIST_TOP_Y - 26) +#define SB_TRACK_X (ui_screen_w() - 13) #define SB_TRACK_Y 54 -#define SB_TRACK_LEN 232 +#define SB_TRACK_LEN (ui_screen_h() - SB_TRACK_Y - 34) #define SB_THUMB_H 45 #define SB_THUMB_ICON "/assets/icons/drag_indicator.bin" @@ -307,7 +307,7 @@ static void format_task(void *arg) { esp_err_t r = vfs_sdcard_format(); if (r == ESP_OK && !vfs_sdcard_is_mounted()) vfs_sdcard_init(); - lv_async_call(format_done_cb, (void *)(intptr_t)r); + ui_async_call(format_done_cb, (void *)(intptr_t)r); vTaskDelete(NULL); } diff --git a/firmware_p4/components/Applications/ui/screens/settings/system_update_ui.c b/firmware_p4/components/Applications/ui/screens/settings/system_update_ui.c index 8052ec319..497552edd 100644 --- a/firmware_p4/components/Applications/ui/screens/settings/system_update_ui.c +++ b/firmware_p4/components/Applications/ui/screens/settings/system_update_ui.c @@ -23,6 +23,7 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_semantic.h" #include "ui_theme.h" #include "waves_ui.h" @@ -76,9 +77,7 @@ #define PH_ERASE_MAX 42 #define PH_WRITE_MAX 96 -#define COL_SUCCESS 0x00E676 -#define COL_DIM 0x8A8594 -#define COL_TRACK 0x202028 +#define COL_TRACK 0x202028 #define NEW_VERSION "v2.1.0" #define INSTALLED_VERSION "v2.0.0" @@ -280,7 +279,7 @@ static void build_found(void) { lv_obj_t *cap = lv_label_create(card); lv_label_set_text(cap, CAPTION_NEW); lv_obj_set_style_text_font(cap, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(cap, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(cap, current_theme.text_secondary, 0); lv_obj_align(cap, LV_ALIGN_CENTER, 0, -20); lv_obj_t *pill = lv_obj_create(card); @@ -303,7 +302,7 @@ static void build_found(void) { lv_obj_t *inst = lv_label_create(card); lv_label_set_text(inst, INSTALLED_ROW); lv_obj_set_style_text_font(inst, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(inst, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(inst, current_theme.text_secondary, 0); lv_obj_align(inst, LV_ALIGN_BOTTOM_MID, 0, 0); } @@ -320,10 +319,10 @@ static void set_step_label(void) { char buf[STEP_BUF_LEN]; snprintf(buf, sizeof(buf), STEP_WRITE_FMT, s_pct); lv_label_set_text(s_step_lbl, buf); - lv_obj_set_style_text_color(s_step_lbl, lv_color_hex(COL_SUCCESS), 0); + lv_obj_set_style_text_color(s_step_lbl, lv_color_hex(UI_COL_SUCCESS), 0); } else { lv_label_set_text(s_step_lbl, STEP_FINALIZE); - lv_obj_set_style_text_color(s_step_lbl, lv_color_hex(COL_SUCCESS), 0); + lv_obj_set_style_text_color(s_step_lbl, lv_color_hex(UI_COL_SUCCESS), 0); } } @@ -381,7 +380,7 @@ static void build_applying(void) { lv_obj_set_style_bg_opa(s_bar, LV_OPA_COVER, LV_PART_MAIN); lv_obj_set_style_radius(s_bar, BAR_RAD, LV_PART_MAIN); lv_obj_set_style_bg_color(s_bar, current_theme.border_accent, LV_PART_INDICATOR); - lv_obj_set_style_bg_grad_color(s_bar, lv_color_hex(COL_SUCCESS), LV_PART_INDICATOR); + lv_obj_set_style_bg_grad_color(s_bar, lv_color_hex(UI_COL_SUCCESS), LV_PART_INDICATOR); lv_obj_set_style_bg_grad_dir(s_bar, LV_GRAD_DIR_HOR, LV_PART_INDICATOR); lv_obj_set_style_bg_opa(s_bar, LV_OPA_COVER, LV_PART_INDICATOR); lv_obj_set_style_radius(s_bar, BAR_RAD, LV_PART_INDICATOR); diff --git a/firmware_p4/components/Applications/ui/screens/subghz/include/subghz_config_ui.h b/firmware_p4/components/Applications/ui/screens/subghz/include/subghz_config_ui.h index d96324d90..2c723b78c 100644 --- a/firmware_p4/components/Applications/ui/screens/subghz/include/subghz_config_ui.h +++ b/firmware_p4/components/Applications/ui/screens/subghz/include/subghz_config_ui.h @@ -20,7 +20,7 @@ extern "C" { #endif -/** @brief Open the Sub-GHz radio configuration screen (tuner dashboard mock). */ +/** @brief Open the Sub-GHz radio configuration screen; applies preset/modulation to the CC1101. */ void ui_subghz_config_open(void); #ifdef __cplusplus diff --git a/firmware_p4/components/Applications/ui/screens/subghz/include/subghz_menu_ui.h b/firmware_p4/components/Applications/ui/screens/subghz/include/subghz_menu_ui.h index 4405ffabb..668618f75 100644 --- a/firmware_p4/components/Applications/ui/screens/subghz/include/subghz_menu_ui.h +++ b/firmware_p4/components/Applications/ui/screens/subghz/include/subghz_menu_ui.h @@ -20,13 +20,12 @@ extern "C" { #endif -/** @brief Open the Sub-GHz menu screen (MOCK): Read / Read RAW / Analyzer / Brute / Saved. No - * radio. */ +/** @brief Open the Sub-GHz menu screen: Read / Read RAW / Analyzer / Brute / Saved / Send. */ void ui_subghz_menu_open(void); /** - * @brief Open the Sub-GHz "Read" capture screen (MOCK): scanning waves -> canned - * captured signal -> save prompt. No radio. + * @brief Open the Sub-GHz "Read" capture screen: runs the CC1101 receiver, shows the + * decoded protocol/frequency/key, and can replay or persist the capture. */ void ui_subghz_read_open(void); diff --git a/firmware_p4/components/Applications/ui/screens/subghz/include/subghz_send_ui.h b/firmware_p4/components/Applications/ui/screens/subghz/include/subghz_send_ui.h index 6c42ca243..3f5adeb74 100644 --- a/firmware_p4/components/Applications/ui/screens/subghz/include/subghz_send_ui.h +++ b/firmware_p4/components/Applications/ui/screens/subghz/include/subghz_send_ui.h @@ -21,9 +21,8 @@ extern "C" { #endif /** - * @brief Open the Sub-GHz "Send" screen (MOCK): pick a saved signal from a list, - * then a pulse-train transmit animation that auto-confirms "Sent!". No - * radio. + * @brief Open the Sub-GHz "Send" screen: pick a saved capture from the SD library + * and replay it over the CC1101 transmitter. */ void ui_subghz_send_open(void); diff --git a/firmware_p4/components/Applications/ui/screens/subghz/subghz_brute_ui.c b/firmware_p4/components/Applications/ui/screens/subghz/subghz_brute_ui.c index 395490e66..1b1612f5b 100644 --- a/firmware_p4/components/Applications/ui/screens/subghz/subghz_brute_ui.c +++ b/firmware_p4/components/Applications/ui/screens/subghz/subghz_brute_ui.c @@ -20,24 +20,21 @@ #include "esp_log.h" #include "lvgl.h" -#include "capture_result_ui.h" -#include "msgbox_ui.h" #include "notify_ui.h" +#include "subghz_brute.h" #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" +#include "ui_semantic.h" #include "ui_theme.h" static const char *TAG = "SUBGHZ_BF"; -#define TICK_MS 33 -#define REVEAL_MS 3000 -#define BRUTE_MS 4200 +#define TICK_MS 120 #define SCOPE_TICK_MS 38 #define DOT_CYCLE_MS 350 -#define SIG_GREEN 0x00E676 - #define STATUS_Y 48 #define FREQ_Y 68 @@ -74,14 +71,13 @@ static const char *TAG = "SUBGHZ_BF"; #define BAR_W 192 #define BAR_H 8 -#define BAR_Y 210 -#define CODES_Y 234 -#define HIT_Y 258 +#define BAR_Y (CARD_Y + 20) +#define CODES_Y (CARD_Y + 46) #define TRACK_COL 0x202028 #define CARD_W 210 #define CARD_H 96 -#define CARD_Y 190 +#define CARD_Y LV_MIN(190, ui_screen_h() - UI_CHROME_FOOTER_H - CARD_H) #define CARD_RADIUS 12 #define CARD_SHADOW_W 14 #define CARD_SHADOW_SPREAD -3 @@ -89,13 +85,12 @@ static const char *TAG = "SUBGHZ_BF"; #define STATUS_RUN "Transmitting" #define HINT_RUN "BACK to stop" #define HINT_SHOW "BACK = Exit" -#define HINT_MENU "UP/DOWN choose OK do BACK exit" -#define BRUTE_PROTO "Princeton" -#define BRUTE_FREQ "433.92 MHz" -#define BRUTE_HIT "0x1A2B3C" -#define BRUTE_TOTAL 4096 -#define BRUTE_HIT_N (BRUTE_TOTAL / 3) +#define BRUTE_PROTO_NAME "CAME" +#define BRUTE_BITS 12 +#define BRUTE_FREQ_HZ 433920000 +#define BRUTE_TOTAL (1 << BRUTE_BITS) +#define BRUTE_FREQ_STR "433.92 MHz" static const uint8_t OOK_BITS[] = {0, 0, 0, 1, 1, 0}; #define OOK_BIT_COUNT ((int)(sizeof(OOK_BITS) / sizeof(OOK_BITS[0]))) @@ -111,20 +106,14 @@ static lv_obj_t *s_wave = NULL; static lv_obj_t *s_card = NULL; static lv_obj_t *s_bar = NULL; static lv_obj_t *s_codes = NULL; -static lv_obj_t *s_hit = NULL; static lv_obj_t *s_hint = NULL; -static capture_result_t s_cr = {0}; static lv_point_precise_t s_wave_pts[WAVE_POINTS]; static lv_point_precise_t s_ook_pts[OOK_MAX_PTS]; static int s_phase = 0; static int s_mod = 0; -static int s_code_count = 0; static uint32_t s_run_start = 0; -static uint32_t s_locked_at = 0; static bool s_locked = false; -static bool s_options = false; -static bool s_saved = false; static void brute_tick_cb(lv_timer_t *t); static void scope_tick_cb(lv_timer_t *t); @@ -270,88 +259,36 @@ static void build_readout(void) { lv_bar_set_value(s_bar, 0, LV_ANIM_OFF); s_codes = lv_label_create(s_screen); - lv_label_set_text_fmt(s_codes, "Codes 0 / %d", BRUTE_TOTAL); + lv_label_set_text_fmt(s_codes, "Sent 0 / %d", BRUTE_TOTAL); lv_obj_set_style_text_color(s_codes, current_theme.border_accent, 0); lv_obj_set_style_text_font(s_codes, &lv_font_montserrat_12, 0); lv_obj_align(s_codes, LV_ALIGN_TOP_MID, 0, CODES_Y); - - s_hit = lv_label_create(s_screen); - lv_label_set_text(s_hit, BRUTE_PROTO " " BRUTE_HIT); - lv_obj_set_style_text_color(s_hit, current_theme.text_main, 0); - lv_obj_set_style_text_font(s_hit, &lv_font_montserrat_12, 0); - lv_obj_align(s_hit, LV_ALIGN_TOP_MID, 0, HIT_Y); - lv_obj_add_flag(s_hit, LV_OBJ_FLAG_HIDDEN); } -static void resolve(void) { +static void finish_sweep(void) { s_locked = true; stop_timer(&s_scope_timer); if (s_wave != NULL) lv_obj_set_style_line_rounded(s_wave, false, 0); fill_ook(); - s_code_count = BRUTE_HIT_N; if (s_bar != NULL) - lv_bar_set_value(s_bar, s_code_count, LV_ANIM_OFF); + lv_bar_set_value(s_bar, BRUTE_TOTAL, LV_ANIM_OFF); if (s_codes != NULL) - lv_label_set_text_fmt(s_codes, "Hit at code %d", s_code_count); - if (s_hit != NULL) - lv_obj_remove_flag(s_hit, LV_OBJ_FLAG_HIDDEN); + lv_label_set_text_fmt(s_codes, "Sent %d / %d", BRUTE_TOTAL, BRUTE_TOTAL); if (s_status != NULL) { - lv_label_set_text(s_status, "Code found!"); - lv_obj_set_style_text_color(s_status, lv_color_hex(SIG_GREEN), 0); + lv_label_set_text(s_status, "Sweep complete"); + lv_obj_set_style_text_color(s_status, lv_color_hex(UI_COL_SUCCESS), 0); } if (s_hint != NULL) ui_chrome_footer_set_text(s_hint, HINT_SHOW); - s_locked_at = lv_tick_get(); - ESP_LOGI(TAG, "mock subghz brute hit: %s %s", BRUTE_PROTO, BRUTE_HIT); + ESP_LOGI(TAG, "brute sweep complete (%d codes)", BRUTE_TOTAL); ui_feedback(UI_FB_WRITE); } -static void show_options(void) { - if (s_scope != NULL) { - lv_obj_del(s_scope); - s_scope = NULL; - s_wave = NULL; - } - if (s_bar != NULL) { - lv_obj_del(s_bar); - s_bar = NULL; - } - if (s_codes != NULL) { - lv_obj_del(s_codes); - s_codes = NULL; - } - if (s_hit != NULL) { - lv_obj_del(s_hit); - s_hit = NULL; - } - if (s_card != NULL) { - lv_obj_del(s_card); - s_card = NULL; - } - if (s_status != NULL) - lv_obj_add_flag(s_status, LV_OBJ_FLAG_HIDDEN); - if (s_freq != NULL) - lv_obj_add_flag(s_freq, LV_OBJ_FLAG_HIDDEN); - - capture_result_cfg_t cfg = { - .accent = current_theme.border_accent, - .card_icon = "/assets/icons/bolt.bin", - .card_title = "Code found", - .card_sub = BRUTE_PROTO " (OOK)", - .card_value = BRUTE_HIT, - .primary_label = "Send", - .again_label = "Run again", - }; - s_cr = capture_result_create(s_screen, &cfg); - s_options = true; - if (s_hint != NULL) - ui_chrome_footer_set_text(s_hint, HINT_MENU); -} - void ui_subghz_brute_open(void) { + subghz_brute_stop(); stop_timer(&s_scope_timer); stop_timer(&s_tick_timer); if (s_screen != NULL) { @@ -365,16 +302,10 @@ void ui_subghz_brute_open(void) { s_card = NULL; s_bar = NULL; s_codes = NULL; - s_hit = NULL; s_hint = NULL; - s_cr = (capture_result_t){0}; s_phase = 0; s_mod = 0; - s_code_count = 0; s_locked = false; - s_options = false; - s_saved = false; - s_locked_at = 0; s_screen = lv_obj_create(NULL); lv_obj_set_style_bg_color(s_screen, current_theme.screen_base, 0); @@ -392,7 +323,7 @@ void ui_subghz_brute_open(void) { lv_obj_align(s_status, LV_ALIGN_TOP_MID, 0, STATUS_Y); s_freq = lv_label_create(s_screen); - lv_label_set_text(s_freq, BRUTE_FREQ); + lv_label_set_text(s_freq, BRUTE_PROTO_NAME " " BRUTE_FREQ_STR); lv_obj_set_style_text_color(s_freq, current_theme.border_accent, 0); lv_obj_set_style_text_font(s_freq, &lv_font_montserrat_12, 0); lv_obj_align(s_freq, LV_ALIGN_TOP_MID, 0, FREQ_Y); @@ -408,6 +339,19 @@ void ui_subghz_brute_open(void) { ui_input_set_screen_handler(subghz_brute_input, NULL); + esp_err_t berr = subghz_brute_start(BRUTE_PROTO_NAME, BRUTE_BITS, BRUTE_FREQ_HZ); + if (berr != ESP_OK) { + s_locked = true; + stop_timer(&s_scope_timer); + if (s_status != NULL) { + lv_label_set_text(s_status, "Unavailable"); + lv_obj_set_style_text_color(s_status, current_theme.text_secondary, 0); + } + if (s_hint != NULL) + ui_chrome_footer_set_text(s_hint, HINT_SHOW); + notify(NOTIFY_WARNING, "Brute unavailable"); + } + ui_screen_load_owned(&s_screen, s_screen); } @@ -428,96 +372,49 @@ static void brute_tick_cb(lv_timer_t *t) { s_tick_timer = NULL; return; } + if (s_locked) + return; - if (!s_locked) { - uint32_t el = lv_tick_get() - s_run_start; - if (s_status != NULL) { - int dots = (el / DOT_CYCLE_MS) % 4; - char buf[24]; - snprintf(buf, - sizeof(buf), - "%s%s", - STATUS_RUN, - dots == 1 ? "." - : dots == 2 ? ".." - : dots == 3 ? "..." - : ""); - lv_label_set_text(s_status, buf); - } - int count = (int)((uint64_t)el * BRUTE_TOTAL / BRUTE_MS); - if (count > BRUTE_TOTAL) - count = BRUTE_TOTAL; - s_code_count = count; - if (s_bar != NULL) - lv_bar_set_value(s_bar, count, LV_ANIM_OFF); - if (s_codes != NULL) - lv_label_set_text_fmt(s_codes, "Codes %d / %d", count, BRUTE_TOTAL); + if (s_status != NULL) { + int dots = ((lv_tick_get() - s_run_start) / DOT_CYCLE_MS) % 4; + char buf[24]; + snprintf(buf, + sizeof(buf), + "%s%s", + STATUS_RUN, + dots == 1 ? "." + : dots == 2 ? ".." + : dots == 3 ? "..." + : ""); + lv_label_set_text(s_status, buf); } - if (!s_locked) { - if (lv_tick_get() - s_run_start >= BRUTE_MS) - resolve(); - } else if (!s_options) { - if (lv_tick_get() - s_locked_at >= REVEAL_MS) - show_options(); + subghz_brute_status_t st; + if (!subghz_brute_get_status(&st)) + return; + + int total = st.total ? (int)st.total : BRUTE_TOTAL; + int sent = (int)st.sent; + if (s_bar != NULL) { + lv_bar_set_range(s_bar, 0, total); + lv_bar_set_value(s_bar, sent, LV_ANIM_OFF); } + if (s_codes != NULL) + lv_label_set_text_fmt(s_codes, "Sent %d / %d", sent, total); + + if (st.done) + finish_sweep(); } static void subghz_brute_input(const input_event_t *ev, void *ctx) { (void)ctx; const bool press = (ev->action == INPUT_ACTION_PRESS); - const bool nav = press || (ev->action == INPUT_ACTION_REPEAT); if (ev->button == INPUT_BTN_BACK) { - if (press) + if (press) { + subghz_brute_stop(); ui_switch_screen(SCREEN_SUBGHZ_MENU); + } return; } - - if (!s_options) - return; - - switch (ev->button) { - case INPUT_BTN_DOWN: - if (nav) { - capture_result_next(&s_cr); - ui_feedback(UI_FB_NAV); - } - break; - case INPUT_BTN_UP: - if (nav) { - capture_result_prev(&s_cr); - ui_feedback(UI_FB_NAV); - } - break; - case INPUT_BTN_OK: - if (press) { - switch (capture_result_selected(&s_cr)) { - case CAP_ACT_PRIMARY: - ui_feedback(UI_FB_EMULATE); - notify(NOTIFY_INFO, BRUTE_FREQ " sent"); - break; - case CAP_ACT_SAVE: - if (!s_saved) { - s_saved = true; - capture_result_mark_saved(&s_cr); - ESP_LOGI(TAG, "mock subghz brute saved: %s", BRUTE_HIT); - ui_feedback(UI_FB_WRITE); - notify(NOTIFY_SAVED, "Sub-GHz code saved"); - } - break; - case CAP_ACT_AGAIN: - ui_subghz_brute_open(); - return; - case CAP_ACT_DISCARD: - ui_switch_screen(SCREEN_SUBGHZ_MENU); - return; - default: - break; - } - } - break; - default: - break; - } } diff --git a/firmware_p4/components/Applications/ui/screens/subghz/subghz_config_ui.c b/firmware_p4/components/Applications/ui/screens/subghz/subghz_config_ui.c index fee85d7cd..8c1daf280 100644 --- a/firmware_p4/components/Applications/ui/screens/subghz/subghz_config_ui.c +++ b/firmware_p4/components/Applications/ui/screens/subghz/subghz_config_ui.c @@ -15,13 +15,18 @@ #include "subghz_config_ui.h" +#include + #include "lvgl.h" #include "st7789.h" +#include "cc1101.h" #include "notify_ui.h" +#include "subghz_settings.h" #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #define HDR_TITLE "RADIO CONFIG" @@ -29,7 +34,7 @@ #define FOOTER "UP/DN pick L/R adjust OK set" #define MX 8 -#define CONTENT_W (LCD_H_RES - 2 * MX) +#define CONTENT_W (ui_screen_w() - 2 * MX) #define CARD1_Y 50 #define CARD1_H 84 @@ -50,12 +55,12 @@ #define MARKER_GLOW 8 #define SCALE_Y 62 -#define GRID_Y (CARD1_Y + CARD1_H + 8) #define TILE_W 108 #define TILE_H 52 #define TILE_GAP 8 #define TILE_X_L MX #define TILE_X_R (MX + TILE_W + TILE_GAP) +#define GRID_Y LV_MIN(CARD1_Y + CARD1_H + 8, ui_screen_h() - UI_CHROME_FOOTER_H - 2 * TILE_H - 8) #define ROW2_Y (GRID_Y + TILE_H + 8) #define TILE_RADIUS 9 @@ -64,7 +69,6 @@ #define TILE_VAL_Y 25 #define TILE_GLOW_W 12 -#define COL_DIM 0x8A8594 #define COL_BAND_A 0x221F2E #define COL_BAND_B 0x3A2F55 @@ -90,7 +94,7 @@ static const rf_field_t FIELDS[FIELD_COUNT] = { }; static const int TILE_X[FIELD_COUNT] = {TILE_X_L, TILE_X_R, TILE_X_L, TILE_X_R}; -static const int TILE_Y[FIELD_COUNT] = {GRID_Y, GRID_Y, ROW2_Y, ROW2_Y}; +static int TILE_Y[FIELD_COUNT]; static const char *SCALE_LABELS[4] = {"300", "433", "700", "928"}; @@ -120,7 +124,7 @@ static void build_freq_card(void) { lv_obj_t *cap = lv_label_create(card); lv_label_set_text(cap, "TUNED FREQUENCY"); lv_obj_set_style_text_font(cap, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(cap, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(cap, current_theme.text_secondary, 0); lv_obj_align(cap, LV_ALIGN_TOP_MID, 0, FREQ_CAP_Y); lv_obj_t *grp = lv_obj_create(card); @@ -183,7 +187,7 @@ static void build_freq_card(void) { lv_obj_t *lbl = lv_label_create(scale); lv_label_set_text(lbl, SCALE_LABELS[i]); lv_obj_set_style_text_font(lbl, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(lbl, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(lbl, current_theme.text_secondary, 0); } } @@ -201,7 +205,7 @@ static lv_obj_t *build_tile(int i) { lv_obj_t *cap = lv_label_create(tile); lv_label_set_text(cap, FIELDS[i].name); lv_obj_set_style_text_font(cap, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(cap, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(cap, current_theme.text_secondary, 0); lv_obj_align(cap, LV_ALIGN_TOP_LEFT, TILE_PAD_L, TILE_CAP_Y); lv_obj_t *grp = lv_obj_create(tile); @@ -225,7 +229,7 @@ static lv_obj_t *build_tile(int i) { lv_obj_t *unit = lv_label_create(grp); lv_label_set_text(unit, FIELDS[i].unit); lv_obj_set_style_text_font(unit, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(unit, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(unit, current_theme.text_secondary, 0); } return tile; @@ -253,6 +257,28 @@ static void adjust_value(int dir) { lv_label_set_text(s_tile_num[s_sel], FIELDS[s_sel].values[s_val_idx[s_sel]]); } +#define CONFIG_FREQ_HZ 433920000 + +static const uint8_t MOD_MAP[4] = {0, 2, 1, 3}; +static const cc1101_preset_t PRESET_MAP[4] = { + CC1101_PRESET_2FSK_47KHZ, + CC1101_PRESET_2FSK_95KHZ, + CC1101_PRESET_OOK_270KHZ, + CC1101_PRESET_OOK_650KHZ, +}; + +static void apply_config(void) { + cc1101_preset_t preset = PRESET_MAP[s_val_idx[3]]; + cc1101_set_preset(preset, CONFIG_FREQ_HZ); + cc1101_set_modulation(MOD_MAP[s_val_idx[0]]); + cc1101_set_rx_bandwidth((float)atof(FIELDS[1].values[s_val_idx[1]])); + cc1101_set_data_rate((float)atof(FIELDS[2].values[s_val_idx[2]]) * 1000.0f); + cc1101_set_frequency(CONFIG_FREQ_HZ); + + subghz_settings_set_preset(preset); + subghz_settings_set_freq(CONFIG_FREQ_HZ); +} + static void subghz_config_input(const input_event_t *ev, void *ctx) { (void)ctx; const bool press = (ev->action == INPUT_ACTION_PRESS); @@ -291,6 +317,7 @@ static void subghz_config_input(const input_event_t *ev, void *ctx) { break; case INPUT_BTN_OK: if (press) { + apply_config(); notify(NOTIFY_SAVED, "Radio config applied"); ui_feedback(UI_FB_SELECT); } @@ -306,6 +333,10 @@ void ui_subghz_config_open(void) { s_screen = NULL; } s_sel = 0; + TILE_Y[0] = GRID_Y; + TILE_Y[1] = GRID_Y; + TILE_Y[2] = ROW2_Y; + TILE_Y[3] = ROW2_Y; for (int i = 0; i < FIELD_COUNT; i++) s_val_idx[i] = FIELDS[i].def; diff --git a/firmware_p4/components/Applications/ui/screens/subghz/subghz_menu_ui.c b/firmware_p4/components/Applications/ui/screens/subghz/subghz_menu_ui.c index 4a3ff0c00..9acec4adf 100644 --- a/firmware_p4/components/Applications/ui/screens/subghz/subghz_menu_ui.c +++ b/firmware_p4/components/Applications/ui/screens/subghz/subghz_menu_ui.c @@ -16,6 +16,7 @@ #include "subghz_menu_ui.h" #include +#include #include "esp_log.h" #include "lvgl.h" @@ -27,14 +28,17 @@ #include "notify_ui.h" #include "octobit_ui.h" #include "sigwave_ui.h" +#include "subghz_receiver.h" +#include "subghz_replay.h" #include "subghz_scope_ui.h" +#include "subghz_spectrum.h" +#include "subghz_storage.h" #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" -static const char *TAG = "SUBGHZ_UI"; - #define FADE_MS 200 #define OUTER_BORDER 4 @@ -50,7 +54,7 @@ static const char *TAG = "SUBGHZ_UI"; #define BAR_GAP 4 #define BAR_MIN_H 6 #define BAR_MAX_H 200 -#define BAR_BASELINE_Y 288 +#define BAR_BASELINE_Y (ui_screen_h() - UI_CHROME_FOOTER_H - 10) #define FREQ_CYCLE_MS 400 #define CARD_W 200 @@ -80,13 +84,12 @@ static const char *TAG = "SUBGHZ_UI"; #define SGC_BAR_W 6 #define SGC_BAR_MIN 3 #define SGC_BAR_SPAN 12 -#define SGC_TRACK_X 227 +#define SGC_TRACK_X (ui_screen_w() - 13) #define SGC_TRACK_Y 54 -#define SGC_TRACK_LEN 232 +#define SGC_TRACK_LEN LV_MIN(232, ui_screen_h() - UI_CHROME_FOOTER_H - SGC_TRACK_Y) #define SGC_THUMB_H 45 #define SGC_THUMB_ICON "/assets/icons/drag_indicator.bin" -#define COL_DIM 0x8A8594 #define COL_RAISE 0x170A28 #define HINT_SAVED "UP/DOWN choose OK open BACK exit" @@ -119,33 +122,26 @@ static const struct { #define IDX_SEND 5 #define IDX_CONFIG 6 -static const struct { - const char *name; - const char *freq; - const char *proto; -} SAVED_SIGS[] = { - {"Gate_433", "433.92 MHz", "Princeton"}, - {"Doorbell", "433.92 MHz", "CAME"}, - {"TPMS_FL", "315.00 MHz", "FSK TPMS"}, - {"Garage", "868.30 MHz", "Nice FLO"}, - {"Car_Fob", "433.92 MHz", "KeeLoq"}, - {"Barrier", "868.30 MHz", "BFT Mitto"}, - {"Weather", "433.92 MHz", "Oregon v3"}, - {"Remote_2", "315.00 MHz", "Holtek"}, - {"Sensor_A", "433.92 MHz", "Princeton"}, - {"Gate_868", "868.30 MHz", "Nice FLO"}, -}; -#define SAVED_COUNT ((int)(sizeof(SAVED_SIGS) / sizeof(SAVED_SIGS[0]))) +#define SAVED_MAX 64 +static subghz_storage_entry_t s_saved[SAVED_MAX]; +static int s_saved_count = 0; -static const struct { - const char *label; - const char *value; -} DETAIL_ROWS[] = { - {"Protocol", "Princeton"}, - {"Key", "0x1A2B3C"}, - {"Frequency", "433.92 MHz"}, -}; -#define DETAIL_ROW_COUNT ((int)(sizeof(DETAIL_ROWS) / sizeof(DETAIL_ROWS[0]))) +static void fmt_mhz(uint32_t hz, char *out, size_t n) { + if (hz == 0) { + snprintf(out, n, "-- MHz"); + return; + } + snprintf(out, + n, + "%lu.%02lu MHz", + (unsigned long)(hz / 1000000UL), + (unsigned long)((hz % 1000000UL) / 10000UL)); +} + +static void load_saved(void) { + int n = subghz_storage_list(s_saved, SAVED_MAX); + s_saved_count = (n < 0) ? 0 : n; +} static const struct { const char *icon; @@ -156,8 +152,11 @@ static const struct { }; #define INFO_ACTION_COUNT ((int)(sizeof(INFO_ACTIONS) / sizeof(INFO_ACTIONS[0]))) -static const char *ANALYZER_FREQS[] = {"433.92 MHz", "868.30 MHz", "315.00 MHz"}; -#define ANALYZER_FREQ_COUNT ((int)(sizeof(ANALYZER_FREQS) / sizeof(ANALYZER_FREQS[0]))) +#define ANALYZER_CENTER_HZ 433920000 +#define ANALYZER_SPAN_HZ 2000000 +#define ANALYZER_POLL_MS 140 +#define ANALYZER_DBM_FLOOR -110 +#define ANALYZER_DBM_CEIL -20 typedef enum { VIEW_LIST = 0, @@ -173,7 +172,6 @@ static view_t s_view = VIEW_LIST; static int s_saved_sel = 0; static lv_obj_t *s_freq_lbl = NULL; -static int s_freq_idx = 0; static lv_obj_t *s_info_rows[INFO_ACTION_COUNT]; static lv_obj_t *s_info_icons[INFO_ACTION_COUNT]; @@ -181,10 +179,11 @@ static lv_obj_t *s_info_labels[INFO_ACTION_COUNT]; static int s_info_sel = 0; static lv_obj_t *s_sig_cont = NULL; -static lv_obj_t *s_sig_row[SAVED_COUNT]; -static lv_obj_t *s_sig_name[SAVED_COUNT]; -static lv_obj_t *s_sig_val[SAVED_COUNT]; +static lv_obj_t *s_sig_row[SAVED_MAX]; +static lv_obj_t *s_sig_name[SAVED_MAX]; +static lv_obj_t *s_sig_val[SAVED_MAX]; static lv_obj_t *s_sig_thumb = NULL; +static lv_obj_t *s_spec_bars[BAR_COUNT]; static lv_obj_t *s_send_overlay = NULL; static lv_obj_t *s_send_status = NULL; @@ -222,37 +221,72 @@ static void fade_in(lv_obj_t *obj, uint32_t duration_ms) { lv_anim_start(&a); } -static void bar_height_cb(void *var, int32_t v) { - lv_obj_t *bar = (lv_obj_t *)var; - lv_obj_set_height(bar, v); - lv_obj_set_y(bar, BAR_BASELINE_Y - v); -} +#define SIGNAL_STRONG_COLOR 0x00E676 -static void freq_cycle_cb(lv_timer_t *t) { +static void spectrum_poll_cb(lv_timer_t *t) { if (lv_screen_active() != s_screen || s_view != VIEW_ANALYZER) { lv_timer_delete(t); s_freq_timer = NULL; return; } - s_freq_idx = (s_freq_idx + 1) % ANALYZER_FREQ_COUNT; - if (s_freq_lbl) - lv_label_set_text(s_freq_lbl, ANALYZER_FREQS[s_freq_idx]); -} -#define SIGNAL_STRONG_COLOR 0x00E676 + subghz_spectrum_line_t line; + if (!subghz_spectrum_get_line(&line)) + return; + + int per = SPECTRUM_SAMPLES / BAR_COUNT; + if (per < 1) + per = 1; + + for (int i = 0; i < BAR_COUNT; i++) { + float sum = 0.0f; + int cnt = 0; + for (int k = 0; k < per; k++) { + int idx = i * per + k; + if (idx < SPECTRUM_SAMPLES) { + sum += line.dbm_values[idx]; + cnt++; + } + } + float dbm = cnt ? (sum / (float)cnt) : (float)ANALYZER_DBM_FLOOR; + + int h = (int)(((dbm - ANALYZER_DBM_FLOOR) * (BAR_MAX_H - BAR_MIN_H)) / + (ANALYZER_DBM_CEIL - ANALYZER_DBM_FLOOR)) + + BAR_MIN_H; + if (h < BAR_MIN_H) + h = BAR_MIN_H; + if (h > BAR_MAX_H) + h = BAR_MAX_H; + + if (s_spec_bars[i] == NULL) + continue; + lv_obj_set_height(s_spec_bars[i], h); + lv_obj_set_y(s_spec_bars[i], BAR_BASELINE_Y - h); + + lv_color_t c; + if (dbm >= -45.0f) + c = lv_color_hex(SIGNAL_STRONG_COLOR); + else if (dbm >= -75.0f) + c = current_theme.border_accent; + else + c = current_theme.border_inactive; + lv_obj_set_style_bg_color(s_spec_bars[i], c, 0); + } +} static void build_analyzer(void) { ui_chrome_header(s_screen, "ANALYZER", "/assets/icons/graphic_eq.bin"); - s_freq_idx = 0; + char freq_str[16]; + fmt_mhz(ANALYZER_CENTER_HZ, freq_str, sizeof(freq_str)); s_freq_lbl = lv_label_create(s_screen); - lv_label_set_text(s_freq_lbl, ANALYZER_FREQS[0]); + lv_label_set_text(s_freq_lbl, freq_str); lv_obj_set_style_text_color(s_freq_lbl, current_theme.border_accent, 0); lv_obj_set_style_text_font(s_freq_lbl, &lv_font_montserrat_14, 0); lv_obj_align(s_freq_lbl, LV_ALIGN_TOP_MID, 0, UI_CHROME_HEADER_H + 14); int total_w = BAR_COUNT * BAR_W + (BAR_COUNT - 1) * BAR_GAP; - int x0 = (LCD_H_RES - total_w) / 2; + int x0 = (ui_screen_w() - total_w) / 2; static lv_point_precise_t base_pts[2]; base_pts[0].x = 0; @@ -269,43 +303,25 @@ static void build_analyzer(void) { lv_obj_set_style_line_dash_gap(baseline, 4, 0); for (int i = 0; i < BAR_COUNT; i++) { - int peak = BAR_MAX_H - (i % 5) * 12; - lv_obj_t *bar = lv_obj_create(s_screen); + s_spec_bars[i] = bar; lv_obj_remove_flag(bar, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_size(bar, BAR_W, BAR_MIN_H); lv_obj_set_style_radius(bar, 2, 0); lv_obj_set_style_bg_opa(bar, LV_OPA_COVER, 0); - - lv_color_t bar_color; - if (peak >= (BAR_MAX_H - 12)) - bar_color = lv_color_hex(SIGNAL_STRONG_COLOR); - else if (peak >= (BAR_MAX_H - 36)) - bar_color = current_theme.border_accent; - else - bar_color = current_theme.border_inactive; - lv_obj_set_style_bg_color(bar, bar_color, 0); + lv_obj_set_style_bg_color(bar, current_theme.border_inactive, 0); lv_obj_set_style_bg_grad_color(bar, current_theme.bg_secondary, 0); lv_obj_set_style_bg_grad_dir(bar, LV_GRAD_DIR_VER, 0); lv_obj_set_style_border_width(bar, 0, 0); - lv_obj_set_pos(bar, x0 + i * (BAR_W + BAR_GAP), BAR_BASELINE_Y - BAR_MIN_H); - - lv_anim_t a; - lv_anim_init(&a); - lv_anim_set_var(&a, bar); - lv_anim_set_exec_cb(&a, bar_height_cb); - lv_anim_set_values(&a, BAR_MIN_H, peak); - lv_anim_set_duration(&a, 420 + (i % 4) * 90); - lv_anim_set_playback_duration(&a, 420 + (i % 3) * 80); - lv_anim_set_delay(&a, i * 55); - lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE); - lv_anim_set_path_cb(&a, lv_anim_path_ease_in_out); - lv_anim_start(&a); } fade_in(s_freq_lbl, FADE_MS); - s_freq_timer = lv_timer_create(freq_cycle_cb, FREQ_CYCLE_MS, NULL); + + if (subghz_receiver_is_running()) + subghz_receiver_stop(); + subghz_spectrum_start(ANALYZER_CENTER_HZ, ANALYZER_SPAN_HZ); + s_freq_timer = lv_timer_create(spectrum_poll_cb, ANALYZER_POLL_MS, NULL); ui_chrome_footer(s_screen, HINT_ANALYZER); } @@ -317,7 +333,7 @@ static void build_saved_empty(void) { } static void move_sig_thumb(void) { - if (s_sig_thumb == NULL || SAVED_COUNT <= 1) + if (s_sig_thumb == NULL || s_saved_count <= 1) return; int thumb_h = lv_obj_get_height(s_sig_thumb); if (thumb_h <= 0) @@ -325,7 +341,7 @@ static void move_sig_thumb(void) { int travel = SGC_TRACK_LEN - thumb_h; if (travel < 0) travel = 0; - int pos = SGC_TRACK_Y + (s_saved_sel * travel) / (SAVED_COUNT - 1); + int pos = SGC_TRACK_Y + (s_saved_sel * travel) / (s_saved_count - 1); lv_obj_set_y(s_sig_thumb, pos); } @@ -344,12 +360,12 @@ static void style_sig_row(int i, bool sel) { lv_obj_set_style_border_opa(card, LV_OPA_60, 0); lv_obj_set_style_shadow_width(card, 0, 0); lv_obj_set_style_shadow_opa(card, LV_OPA_TRANSP, 0); - lv_obj_set_style_text_color(s_sig_val[i], lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s_sig_val[i], current_theme.text_secondary, 0); } } static void update_sig_selection(void) { - for (int i = 0; i < SAVED_COUNT; i++) + for (int i = 0; i < s_saved_count; i++) style_sig_row(i, i == s_saved_sel); if (s_sig_cont != NULL && s_sig_row[s_saved_sel] != NULL) { lv_obj_update_layout(s_sig_cont); @@ -359,7 +375,8 @@ static void update_sig_selection(void) { } static void build_saved_list(void) { - if (SAVED_COUNT == 0) { + load_saved(); + if (s_saved_count == 0) { build_saved_empty(); return; } @@ -367,13 +384,14 @@ static void build_saved_list(void) { if (s_saved_sel < 0) s_saved_sel = 0; - if (s_saved_sel >= SAVED_COUNT) - s_saved_sel = SAVED_COUNT - 1; + if (s_saved_sel >= s_saved_count) + s_saved_sel = s_saved_count - 1; lv_obj_t *cont = lv_obj_create(s_screen); s_sig_cont = cont; - lv_obj_set_size( - cont, LCD_H_RES - SGC_LEFT - SGC_GUTTER, LCD_V_RES - SGC_TOP_Y - UI_CHROME_FOOTER_H - 4); + lv_obj_set_size(cont, + ui_screen_w() - SGC_LEFT - SGC_GUTTER, + ui_screen_h() - SGC_TOP_Y - UI_CHROME_FOOTER_H - 4); lv_obj_align(cont, LV_ALIGN_TOP_LEFT, SGC_LEFT, SGC_TOP_Y); lv_obj_set_style_bg_opa(cont, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(cont, 0, 0); @@ -385,7 +403,7 @@ static void build_saved_list(void) { lv_obj_set_scrollbar_mode(cont, LV_SCROLLBAR_MODE_OFF); lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLL_ELASTIC | LV_OBJ_FLAG_SCROLL_MOMENTUM); - for (int i = 0; i < SAVED_COUNT; i++) { + for (int i = 0; i < s_saved_count; i++) { lv_obj_t *card = lv_obj_create(cont); s_sig_row[i] = card; lv_obj_remove_flag(card, LV_OBJ_FLAG_SCROLLABLE); @@ -403,14 +421,16 @@ static void build_saved_list(void) { s_sig_name[i] = name; lv_obj_set_width(name, lv_pct(68)); lv_label_set_long_mode(name, LV_LABEL_LONG_DOT); - lv_label_set_text(name, SAVED_SIGS[i].name); + lv_label_set_text(name, s_saved[i].name); lv_obj_set_style_text_font(name, &lv_font_montserrat_14, 0); lv_obj_set_style_text_color(name, current_theme.text_main, 0); lv_obj_align(name, LV_ALIGN_TOP_LEFT, 0, 0); + char fbuf[16]; + fmt_mhz(s_saved[i].frequency, fbuf, sizeof(fbuf)); lv_obj_t *val = lv_label_create(card); s_sig_val[i] = val; - lv_label_set_text(val, SAVED_SIGS[i].freq); + lv_label_set_text(val, fbuf); lv_obj_set_style_text_font(val, &lv_font_montserrat_12, 0); lv_obj_set_style_text_color(val, current_theme.border_accent, 0); lv_obj_align(val, LV_ALIGN_TOP_RIGHT, 0, 2); @@ -418,9 +438,9 @@ static void build_saved_list(void) { lv_obj_t *proto = lv_label_create(card); lv_obj_set_width(proto, lv_pct(100)); lv_label_set_long_mode(proto, LV_LABEL_LONG_DOT); - lv_label_set_text(proto, SAVED_SIGS[i].proto); + lv_label_set_text(proto, s_saved[i].protocol); lv_obj_set_style_text_font(proto, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(proto, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(proto, current_theme.text_secondary, 0); lv_obj_align(proto, LV_ALIGN_TOP_LEFT, 0, 20); lv_obj_t *bars = lv_obj_create(card); @@ -500,7 +520,7 @@ static void style_info_row(int idx, bool sel) { lv_obj_set_style_border_opa(row, LV_OPA_COVER, 0); lv_obj_set_style_shadow_width(row, 0, 0); lv_obj_set_style_shadow_opa(row, LV_OPA_TRANSP, 0); - lv_obj_set_style_text_color(s_info_icons[idx], lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s_info_icons[idx], current_theme.text_secondary, 0); lv_obj_set_style_text_color(s_info_labels[idx], current_theme.text_main, 0); } } @@ -532,11 +552,47 @@ static void build_saved_info(void) { lv_obj_set_style_pad_row(card, 4, 0); lv_obj_t *name = lv_label_create(card); - lv_label_set_text(name, SAVED_SIGS[s_saved_sel].name); + lv_label_set_text(name, s_saved[s_saved_sel].name); lv_obj_set_style_text_color(name, current_theme.text_main, 0); lv_obj_set_style_text_font(name, &lv_font_montserrat_14, 0); - for (int i = 0; i < DETAIL_ROW_COUNT; i++) { + char freq_str[16]; + fmt_mhz(s_saved[s_saved_sel].frequency, freq_str, sizeof(freq_str)); + + char key_str[24]; + strlcpy(key_str, "RAW", sizeof(key_str)); + char content[512]; + if (subghz_storage_read(s_saved[s_saved_sel].name, content, sizeof(content)) > 0) { + const char *kp = strstr(content, "Key: "); + if (kp != NULL) { + unsigned b[8] = {0}; + if (sscanf(kp + 5, + "%x %x %x %x %x %x %x %x", + &b[0], + &b[1], + &b[2], + &b[3], + &b[4], + &b[5], + &b[6], + &b[7]) >= 8) { + uint32_t v = ((uint32_t)b[4] << 24) | ((uint32_t)b[5] << 16) | ((uint32_t)b[6] << 8) | + (uint32_t)b[7]; + snprintf(key_str, sizeof(key_str), "0x%lX", (unsigned long)v); + } + } + } + + const struct { + const char *label; + const char *value; + } rows[] = { + {"Protocol", s_saved[s_saved_sel].protocol}, + {"Key", key_str}, + {"Frequency", freq_str}, + }; + + for (int i = 0; i < (int)(sizeof(rows) / sizeof(rows[0])); i++) { lv_obj_t *row = lv_obj_create(card); lv_obj_remove_flag(row, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_width(row, lv_pct(100)); @@ -549,12 +605,12 @@ static void build_saved_info(void) { row, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_t *label = lv_label_create(row); - lv_label_set_text(label, DETAIL_ROWS[i].label); + lv_label_set_text(label, rows[i].label); lv_obj_set_style_text_color(label, current_theme.border_inactive, 0); lv_obj_set_style_text_font(label, &lv_font_montserrat_12, 0); lv_obj_t *value = lv_label_create(row); - lv_label_set_text(value, DETAIL_ROWS[i].value); + lv_label_set_text(value, rows[i].value); lv_obj_set_style_text_color(value, current_theme.border_accent, 0); lv_obj_set_style_text_font(value, &lv_font_montserrat_12, 0); } @@ -611,15 +667,17 @@ static void build_saved_info(void) { static void info_del_confirm(bool confirm) { if (!confirm) return; - ESP_LOGI(TAG, "mock saved delete: %s", SAVED_SIGS[s_saved_sel].name); + esp_err_t err = subghz_storage_delete(s_saved[s_saved_sel].name); ui_feedback(UI_FB_WRITE); - notify(NOTIFY_INFO, "Signal deleted"); + notify(err == ESP_OK ? NOTIFY_INFO : NOTIFY_WARNING, + err == ESP_OK ? "Signal deleted" : "Delete failed"); s_view = VIEW_SAVED; - lv_async_call(rebuild_async, NULL); + ui_async_call(rebuild_async, NULL); } static void build_screen(void) { stop_freq_timer(); + subghz_spectrum_stop(); if (s_screen != NULL) { lv_obj_del(s_screen); s_screen = NULL; @@ -702,6 +760,16 @@ static void send_lock_cb(lv_timer_t *t) { } static void start_saved_send(void) { + esp_err_t rerr = subghz_replay_file(s_saved[s_saved_sel].name); + if (rerr == ESP_ERR_NOT_SUPPORTED) { + notify(NOTIFY_WARNING, "Replay not supported"); + return; + } + if (rerr != ESP_OK) { + notify(NOTIFY_WARNING, "Send failed"); + return; + } + s_send_overlay = lv_obj_create(s_screen); lv_obj_set_size(s_send_overlay, lv_pct(100), lv_pct(100)); lv_obj_center(s_send_overlay); @@ -711,8 +779,6 @@ static void start_saved_send(void) { lv_obj_set_style_border_width(s_send_overlay, 0, 0); lv_obj_set_style_pad_all(s_send_overlay, 0, 0); - // Transient transmit overlay: snapshot header (no rebind) so freeing it never - // dangles the live parent screen's dynamic header. ui_chrome_header_overlay(s_send_overlay, "SEND", "/assets/icons/settings_input_antenna.bin"); s_send_status = lv_label_create(s_send_overlay); @@ -721,8 +787,10 @@ static void start_saved_send(void) { lv_obj_set_style_text_font(s_send_status, &lv_font_montserrat_14, 0); lv_obj_align(s_send_status, LV_ALIGN_TOP_MID, 0, SEND_STATUS_Y); + char freq_str[16]; + fmt_mhz(s_saved[s_saved_sel].frequency, freq_str, sizeof(freq_str)); lv_obj_t *freq = lv_label_create(s_send_overlay); - lv_label_set_text(freq, SAVED_SIGS[s_saved_sel].freq); + lv_label_set_text(freq, freq_str); lv_obj_set_style_text_color(freq, current_theme.border_accent, 0); lv_obj_set_style_text_font(freq, &lv_font_montserrat_12, 0); lv_obj_align(freq, LV_ALIGN_TOP_MID, 0, SEND_FREQ_Y); @@ -800,7 +868,7 @@ static void subghz_menu_input(const input_event_t *ev, void *ctx) { case VIEW_SAVED: switch (ev->button) { case INPUT_BTN_DOWN: - if (nav && s_saved_sel < SAVED_COUNT - 1) { + if (nav && s_saved_sel < s_saved_count - 1) { s_saved_sel++; update_sig_selection(); ui_feedback(UI_FB_NAV); @@ -815,7 +883,6 @@ static void subghz_menu_input(const input_event_t *ev, void *ctx) { break; case INPUT_BTN_OK: if (press) { - ESP_LOGI(TAG, "mock saved open: %s", SAVED_SIGS[s_saved_sel].name); s_view = VIEW_SAVED_INFO; build_screen(); } diff --git a/firmware_p4/components/Applications/ui/screens/subghz/subghz_read_ui.c b/firmware_p4/components/Applications/ui/screens/subghz/subghz_read_ui.c index 4d9b25635..360a733b7 100644 --- a/firmware_p4/components/Applications/ui/screens/subghz/subghz_read_ui.c +++ b/firmware_p4/components/Applications/ui/screens/subghz/subghz_read_ui.c @@ -25,25 +25,23 @@ #include "msgbox_ui.h" #include "notify_ui.h" #include "subghz_receiver.h" +#include "subghz_replay.h" +#include "subghz_settings.h" #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" +#include "ui_semantic.h" #include "ui_theme.h" static const char *TAG = "SUBGHZ_RD"; -#define RX_PRESET CC1101_PRESET_OOK_800KHZ -#define RX_FREQ_HOPPING 0 - #define TICK_MS 33 -#define REVEAL_MS 3000 -#define SCAN_MS 2600 -#define FREQ_CYCLE_MS 420 +#define REVEAL_MS 1600 +#define POLL_MS 120 #define SCOPE_TICK_MS 38 #define DOT_CYCLE_MS 350 -#define SIG_GREEN 0x00E676 - #define HEADER_TITLE_Y 10 #define HEADER_RULE_Y 32 #define HEADER_RULE_W 70 @@ -84,7 +82,7 @@ static const char *TAG = "SUBGHZ_RD"; #define GRID_OPA LV_OPA_20 #define READOUT_W 192 -#define READOUT_Y 198 +#define READOUT_Y LV_MIN(198, ui_screen_h() - UI_CHROME_FOOTER_H - 70) #define READOUT_ROW_GAP 5 #define READOUT_FADE_MS 240 #define READOUT_STAGGER 70 @@ -96,35 +94,18 @@ static const char *TAG = "SUBGHZ_RD"; #define HINT_SHOW "BACK = Exit" #define HINT_MENU "UP/DOWN choose OK do BACK exit" -#define SIG_PROTO "Princeton" -#define SIG_LOCK_FREQ "433.92 MHz" - -static const char *SCAN_FREQS[] = { - "433.92 MHz", - "868.30 MHz", - "315.00 MHz", - "915.00 MHz", -}; -#define SCAN_FREQ_COUNT ((int)(sizeof(SCAN_FREQS) / sizeof(SCAN_FREQS[0]))) - -static const struct { - const char *label; - const char *value; -} SIG_ROWS[] = { - {"Protocol", SIG_PROTO}, - {"Modulation", "OOK"}, - {"Bitrate", "4.8 kb/s"}, - {"Key", "0x1A2B3C"}, -}; -#define SIG_ROW_COUNT ((int)(sizeof(SIG_ROWS) / sizeof(SIG_ROWS[0]))) +#define ROW_MAX 4 + +typedef struct { + char label[16]; + char value[24]; +} readout_row_t; static const uint8_t OOK_BITS[] = {0, 0, 0, 1, 1, 0}; #define OOK_BIT_COUNT ((int)(sizeof(OOK_BITS) / sizeof(OOK_BITS[0]))) static lv_obj_t *s_screen = NULL; static lv_timer_t *s_tick_timer = NULL; -static lv_timer_t *s_scan_timer = NULL; -static lv_timer_t *s_freq_timer = NULL; static lv_timer_t *s_scope_timer = NULL; static lv_obj_t *s_status = NULL; @@ -141,14 +122,19 @@ static lv_point_precise_t s_wave_pts[WAVE_POINTS]; static lv_point_precise_t s_ook_pts[OOK_MAX_PTS]; static int s_phase = 0; static int s_mod = 0; -static int s_freq_idx = 0; static uint32_t s_scan_start = 0; static bool s_locked = false; static bool s_saved = false; +static subghz_rx_result_t s_result; +static readout_row_t s_rows[ROW_MAX]; +static int s_row_count = 0; +static char s_freq_str[16]; +static char s_proto_str[24]; +static char s_mod_str[16]; +static char s_card_sub[40]; + static void read_tick_cb(lv_timer_t *t); -static void scan_done_cb(lv_timer_t *t); -static void freq_cycle_cb(lv_timer_t *t); static void scope_tick_cb(lv_timer_t *t); static void subghz_read_input(const input_event_t *ev, void *ctx); @@ -164,6 +150,18 @@ static void stop_rx(void) { subghz_receiver_stop(); } +static void fmt_mhz(uint32_t hz, char *out, size_t n) { + if (hz == 0) { + snprintf(out, n, "Hopping"); + return; + } + snprintf(out, + n, + "%lu.%02lu MHz", + (unsigned long)(hz / 1000000UL), + (unsigned long)((hz % 1000000UL) / 10000UL)); +} + static void opa_cb(void *var, int32_t v) { lv_obj_set_style_opa((lv_obj_t *)var, (lv_opa_t)v, 0); } @@ -181,34 +179,6 @@ static void fade_in(lv_obj_t *obj, uint32_t duration_ms, uint32_t delay_ms) { lv_anim_start(&a); } -static void build_header(const char *text) { - lv_obj_t *title = lv_label_create(s_screen); - lv_label_set_text(title, text); - lv_obj_set_style_text_color(title, current_theme.border_accent, 0); - lv_obj_set_style_text_font(title, &lv_font_montserrat_14, 0); - lv_obj_align(title, LV_ALIGN_TOP_MID, 0, HEADER_TITLE_Y); - - lv_obj_t *rule = lv_obj_create(s_screen); - lv_obj_remove_flag(rule, LV_OBJ_FLAG_SCROLLABLE); - lv_obj_remove_flag(rule, LV_OBJ_FLAG_CLICKABLE); - lv_obj_set_size(rule, lv_pct(HEADER_RULE_W), HEADER_RULE_H); - lv_obj_align(rule, LV_ALIGN_TOP_MID, 0, HEADER_RULE_Y); - lv_obj_set_style_border_width(rule, 0, 0); - lv_obj_set_style_radius(rule, 1, 0); - lv_obj_set_style_bg_color(rule, current_theme.border_accent, 0); - lv_obj_set_style_bg_opa(rule, LV_OPA_40, 0); -} - -static lv_obj_t *make_hint(const char *text) { - lv_obj_t *hint = lv_label_create(s_screen); - lv_label_set_text(hint, text); - lv_obj_set_style_text_color(hint, current_theme.text_main, 0); - lv_obj_set_style_text_opa(hint, LV_OPA_60, 0); - lv_obj_set_style_text_font(hint, &lv_font_montserrat_12, 0); - lv_obj_align(hint, LV_ALIGN_BOTTOM_MID, 0, HINT_Y_OFS); - return hint; -} - static int clamp_y(int y) { if (y < 0) return 0; @@ -314,8 +284,6 @@ static void build_scope(void) { void ui_subghz_read_open(void) { stop_rx(); - stop_timer(&s_scan_timer); - stop_timer(&s_freq_timer); stop_timer(&s_scope_timer); stop_timer(&s_tick_timer); if (s_screen != NULL) { @@ -331,9 +299,11 @@ void ui_subghz_read_open(void) { s_cr = (capture_result_t){0}; s_options = false; s_locked_at = 0; - s_freq_idx = 0; s_locked = false; s_saved = false; + s_row_count = 0; + + subghz_settings_t cfg = subghz_settings_get(); s_screen = lv_obj_create(NULL); lv_obj_set_style_bg_color(s_screen, current_theme.screen_base, 0); @@ -350,8 +320,9 @@ void ui_subghz_read_open(void) { lv_obj_set_style_text_font(s_status, &lv_font_montserrat_14, 0); lv_obj_align(s_status, LV_ALIGN_TOP_MID, 0, STATUS_Y); + fmt_mhz(cfg.freq, s_freq_str, sizeof(s_freq_str)); s_freq = lv_label_create(s_screen); - lv_label_set_text(s_freq, SCAN_FREQS[0]); + lv_label_set_text(s_freq, s_freq_str); lv_obj_set_style_text_color(s_freq, current_theme.border_accent, 0); lv_obj_set_style_text_font(s_freq, &lv_font_montserrat_12, 0); lv_obj_align(s_freq, LV_ALIGN_TOP_MID, 0, FREQ_Y); @@ -361,15 +332,12 @@ void ui_subghz_read_open(void) { s_hint = ui_chrome_footer(s_screen, HINT_SCAN); s_scan_start = lv_tick_get(); - s_scan_timer = lv_timer_create(scan_done_cb, SCAN_MS, NULL); - lv_timer_set_repeat_count(s_scan_timer, 1); - s_freq_timer = lv_timer_create(freq_cycle_cb, FREQ_CYCLE_MS, NULL); s_scope_timer = lv_timer_create(scope_tick_cb, SCOPE_TICK_MS, NULL); s_tick_timer = lv_timer_create(read_tick_cb, TICK_MS, NULL); ui_input_set_screen_handler(subghz_read_input, NULL); - esp_err_t rx = subghz_receiver_start(SUBGHZ_MODE_SCAN, RX_PRESET, RX_FREQ_HOPPING); + esp_err_t rx = subghz_receiver_start(SUBGHZ_MODE_SCAN, cfg.preset, cfg.freq); if (rx != ESP_OK) ESP_LOGE(TAG, "subghz_receiver_start failed: %s", esp_err_to_name(rx)); @@ -387,17 +355,6 @@ static void scope_tick_cb(lv_timer_t *t) { fill_wave(true); } -static void freq_cycle_cb(lv_timer_t *t) { - if (lv_screen_active() != s_screen) { - lv_timer_delete(t); - s_freq_timer = NULL; - return; - } - s_freq_idx = (s_freq_idx + 1) % SCAN_FREQ_COUNT; - if (s_freq) - lv_label_set_text(s_freq, SCAN_FREQS[s_freq_idx]); -} - static void build_signal_readout(void) { lv_obj_t *col = lv_obj_create(s_screen); s_readout = col; @@ -411,7 +368,7 @@ static void build_signal_readout(void) { lv_obj_set_flex_flow(col, LV_FLEX_FLOW_COLUMN); lv_obj_set_style_pad_row(col, READOUT_ROW_GAP, 0); - for (int i = 0; i < SIG_ROW_COUNT; i++) { + for (int i = 0; i < s_row_count; i++) { lv_obj_t *row = lv_obj_create(col); lv_obj_remove_flag(row, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_width(row, lv_pct(100)); @@ -424,12 +381,12 @@ static void build_signal_readout(void) { row, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_t *label = lv_label_create(row); - lv_label_set_text(label, SIG_ROWS[i].label); + lv_label_set_text(label, s_rows[i].label); lv_obj_set_style_text_color(label, current_theme.border_inactive, 0); lv_obj_set_style_text_font(label, &lv_font_montserrat_12, 0); lv_obj_t *value = lv_label_create(row); - lv_label_set_text(value, SIG_ROWS[i].value); + lv_label_set_text(value, s_rows[i].value); lv_obj_set_style_text_color(value, current_theme.border_accent, 0); lv_obj_set_style_text_font(value, &lv_font_montserrat_12, 0); @@ -437,10 +394,45 @@ static void build_signal_readout(void) { } } -static void scan_done_cb(lv_timer_t *t) { - (void)t; - s_scan_timer = NULL; - stop_timer(&s_freq_timer); +static void add_row(const char *label, const char *value) { + if (s_row_count >= ROW_MAX) + return; + snprintf(s_rows[s_row_count].label, sizeof(s_rows[s_row_count].label), "%s", label); + snprintf(s_rows[s_row_count].value, sizeof(s_rows[s_row_count].value), "%s", value); + s_row_count++; +} + +static void build_rows_from_result(void) { + s_row_count = 0; + + const char *mod = + (s_result.analysis.modulation_hint != NULL && s_result.analysis.modulation_hint[0] != '\0') + ? s_result.analysis.modulation_hint + : "OOK"; + snprintf(s_mod_str, sizeof(s_mod_str), "%s", mod); + + if (s_result.decoded) { + snprintf(s_proto_str, sizeof(s_proto_str), "%s", s_result.data.protocol_name); + char key[24]; + snprintf(key, sizeof(key), "0x%lX", (unsigned long)s_result.data.raw_value); + char bits[16]; + snprintf(bits, sizeof(bits), "%u bits", (unsigned)s_result.data.bit_count); + add_row("Protocol", s_proto_str); + add_row("Modulation", s_mod_str); + add_row("Bits", bits); + add_row("Key", key); + } else { + snprintf(s_proto_str, sizeof(s_proto_str), "Unknown"); + char te[20]; + snprintf(te, sizeof(te), "%lu us", (unsigned long)s_result.analysis.estimated_te); + add_row("Protocol", s_proto_str); + add_row("Modulation", s_mod_str); + add_row("TE", te); + add_row("Saved", "RAW"); + } +} + +static void do_lock(void) { stop_timer(&s_scope_timer); stop_rx(); if (lv_screen_active() != s_screen) @@ -451,12 +443,15 @@ static void scan_done_cb(lv_timer_t *t) { lv_obj_set_style_line_rounded(s_wave, false, 0); fill_ook(); + fmt_mhz(s_result.freq, s_freq_str, sizeof(s_freq_str)); + build_rows_from_result(); + if (s_status) { lv_label_set_text(s_status, "Signal locked!"); - lv_obj_set_style_text_color(s_status, lv_color_hex(SIG_GREEN), 0); + lv_obj_set_style_text_color(s_status, lv_color_hex(UI_COL_SUCCESS), 0); } if (s_freq) - lv_label_set_text(s_freq, SIG_LOCK_FREQ); + lv_label_set_text(s_freq, s_freq_str); build_signal_readout(); @@ -464,7 +459,7 @@ static void scan_done_cb(lv_timer_t *t) { ui_chrome_footer_set_text(s_hint, HINT_SHOW); s_locked_at = lv_tick_get(); - ESP_LOGI(TAG, "mock subghz capture: %s %s", SIG_PROTO, SIG_LOCK_FREQ); + ESP_LOGI(TAG, "captured: %s %s", s_proto_str, s_freq_str); ui_feedback(UI_FB_READ); } @@ -483,12 +478,14 @@ static void show_options(void) { if (s_freq) lv_obj_add_flag(s_freq, LV_OBJ_FLAG_HIDDEN); + snprintf(s_card_sub, sizeof(s_card_sub), "%s (%s)", s_proto_str, s_mod_str); + capture_result_cfg_t cfg = { .accent = current_theme.border_accent, .card_icon = "/assets/icons/graphic_eq.bin", .card_title = "Signal captured", - .card_sub = SIG_PROTO " (OOK)", - .card_value = SIG_LOCK_FREQ, + .card_sub = s_card_sub, + .card_value = s_freq_str, .primary_label = "Send", .again_label = "Capture again", }; @@ -505,18 +502,24 @@ static void read_tick_cb(lv_timer_t *t) { return; } - if (!s_locked && s_status != NULL) { - int dots = ((lv_tick_get() - s_scan_start) / DOT_CYCLE_MS) % 4; - char buf[20]; - snprintf(buf, - sizeof(buf), - "%s%s", - STATUS_SCAN, - dots == 1 ? "." - : dots == 2 ? ".." - : dots == 3 ? "..." - : ""); - lv_label_set_text(s_status, buf); + if (!s_locked) { + if (s_status != NULL) { + int dots = ((lv_tick_get() - s_scan_start) / DOT_CYCLE_MS) % 4; + char buf[20]; + snprintf(buf, + sizeof(buf), + "%s%s", + STATUS_SCAN, + dots == 1 ? "." + : dots == 2 ? ".." + : dots == 3 ? "..." + : ""); + lv_label_set_text(s_status, buf); + } + + if (subghz_receiver_get_result(&s_result)) + do_lock(); + return; } if (s_locked && !s_options) { @@ -525,6 +528,21 @@ static void read_tick_cb(lv_timer_t *t) { } } +static void send_current(void) { + ui_feedback(UI_FB_EMULATE); + if (s_result.save_name[0] == '\0') { + notify(NOTIFY_WARNING, "Nothing to send"); + return; + } + esp_err_t err = subghz_replay_file(s_result.save_name); + if (err == ESP_OK) + notify(NOTIFY_INFO, "Signal sent"); + else if (err == ESP_ERR_NOT_SUPPORTED) + notify(NOTIFY_WARNING, "Replay not supported"); + else + notify(NOTIFY_WARNING, "Send failed"); +} + static void subghz_read_input(const input_event_t *ev, void *ctx) { (void)ctx; const bool press = (ev->action == INPUT_ACTION_PRESS); @@ -558,16 +576,14 @@ static void subghz_read_input(const input_event_t *ev, void *ctx) { if (press) { switch (capture_result_selected(&s_cr)) { case CAP_ACT_PRIMARY: - ui_feedback(UI_FB_EMULATE); - notify(NOTIFY_INFO, SIG_LOCK_FREQ " sent"); + send_current(); break; case CAP_ACT_SAVE: if (!s_saved) { s_saved = true; capture_result_mark_saved(&s_cr); - ESP_LOGI(TAG, "mock subghz saved: %s", SIG_PROTO); ui_feedback(UI_FB_WRITE); - notify(NOTIFY_SAVED, "Sub-GHz signal saved"); + notify(NOTIFY_SAVED, "Saved to SD"); } break; case CAP_ACT_AGAIN: diff --git a/firmware_p4/components/Applications/ui/screens/subghz/subghz_send_ui.c b/firmware_p4/components/Applications/ui/screens/subghz/subghz_send_ui.c index 1863deba9..2e48aa817 100644 --- a/firmware_p4/components/Applications/ui/screens/subghz/subghz_send_ui.c +++ b/firmware_p4/components/Applications/ui/screens/subghz/subghz_send_ui.c @@ -16,6 +16,7 @@ #include "subghz_send_ui.h" #include +#include #include "esp_log.h" #include "lvgl.h" @@ -24,15 +25,17 @@ #include "assets_manager.h" #include "capture_result_ui.h" #include "notify_ui.h" +#include "octobit_ui.h" +#include "subghz_replay.h" #include "subghz_scope_ui.h" +#include "subghz_storage.h" #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" +#include "ui_semantic.h" #include "ui_theme.h" -static const char *TAG = "SUBGHZ_SEND_UI"; - -#define SIG_GREEN 0x00E676 #define ANTENNA_ICON "/assets/icons/settings_input_antenna.bin" #define SIGNAL_ICON "/assets/icons/graphic_eq.bin" @@ -46,7 +49,6 @@ static const char *TAG = "SUBGHZ_SEND_UI"; #define SCOPE_Y_OFS -8 #define WAVES_ICON "/assets/icons/waves.bin" -#define DIM_COLOR 0x8A8594 #define LIST_PAD_SIDE 8 #define LIST_PAD_ROW 6 @@ -68,17 +70,27 @@ static const char *TAG = "SUBGHZ_SEND_UI"; #define HINT_LIST "UP/DOWN choose OK send BACK exit" #define HINT_OPTIONS "UP/DOWN choose OK do BACK exit" -static const struct { - const char *name; - const char *freq; - const char *proto; -} SIGNALS[] = { - {"Gate_433", "433.92 MHz", "Princeton"}, - {"Doorbell", "433.92 MHz", "CAME"}, - {"TPMS_FL", "315.00 MHz", "TPMS"}, - {"Garage", "868.30 MHz", "Nice FLO"}, -}; -#define SIGNAL_COUNT ((int)(sizeof(SIGNALS) / sizeof(SIGNALS[0]))) +#define SIGNAL_MAX 64 +static subghz_storage_entry_t s_sigs[SIGNAL_MAX]; +static int s_sig_count = 0; +static char s_card_freq[16]; + +static void fmt_mhz(uint32_t hz, char *out, size_t n) { + if (hz == 0) { + snprintf(out, n, "-- MHz"); + return; + } + snprintf(out, + n, + "%lu.%02lu MHz", + (unsigned long)(hz / 1000000UL), + (unsigned long)((hz % 1000000UL) / 10000UL)); +} + +static void load_signals(void) { + int n = subghz_storage_list(s_sigs, SIGNAL_MAX); + s_sig_count = (n < 0) ? 0 : n; +} typedef enum { VIEW_LIST = 0, @@ -88,9 +100,9 @@ typedef enum { static lv_obj_t *s_screen = NULL; static lv_obj_t *s_list = NULL; -static lv_obj_t *s_rows[SIGNAL_COUNT]; -static lv_obj_t *s_row_name[SIGNAL_COUNT]; -static lv_obj_t *s_row_val[SIGNAL_COUNT]; +static lv_obj_t *s_rows[SIGNAL_MAX]; +static lv_obj_t *s_row_name[SIGNAL_MAX]; +static lv_obj_t *s_row_val[SIGNAL_MAX]; static send_view_t s_view = VIEW_LIST; static int s_sel = 0; @@ -135,7 +147,7 @@ static void set_status(const char *text, bool success) { return; lv_label_set_text(s_status_label, text); lv_obj_set_style_text_color( - s_status_label, success ? lv_color_hex(SIG_GREEN) : current_theme.text_main, 0); + s_status_label, success ? lv_color_hex(UI_COL_SUCCESS) : current_theme.text_main, 0); } static void set_hint(const char *text) { @@ -160,13 +172,13 @@ static void style_row(int i, bool sel) { lv_obj_set_style_bg_color(r, current_theme.bg_primary, 0); lv_obj_set_style_border_color(r, current_theme.border_inactive, 0); lv_obj_set_style_shadow_width(r, 0, 0); - lv_obj_set_style_text_color(s_row_name[i], lv_color_hex(DIM_COLOR), 0); - lv_obj_set_style_text_color(s_row_val[i], lv_color_hex(DIM_COLOR), 0); + lv_obj_set_style_text_color(s_row_name[i], current_theme.text_secondary, 0); + lv_obj_set_style_text_color(s_row_val[i], current_theme.text_secondary, 0); } } static void update_selection(void) { - for (int i = 0; i < SIGNAL_COUNT; i++) + for (int i = 0; i < s_sig_count; i++) style_row(i, i == s_sel); if (s_list != NULL && s_rows[s_sel] != NULL) { lv_obj_update_layout(s_list); @@ -188,17 +200,26 @@ static void build_list(void) { s_cr = (capture_result_t){0}; s_options = false; - if (s_sel < 0) - s_sel = 0; - if (s_sel >= SIGNAL_COUNT) - s_sel = SIGNAL_COUNT - 1; + load_signals(); s_screen = new_screen(); ui_chrome_header(s_screen, "SEND", ANTENNA_ICON); + if (s_sig_count == 0) { + octobit_create(s_screen, "No saved signals yet"); + s_hint_label = ui_chrome_footer(s_screen, "BACK exit"); + ui_screen_load_owned(&s_screen, s_screen); + return; + } + + if (s_sel < 0) + s_sel = 0; + if (s_sel >= s_sig_count) + s_sel = s_sig_count - 1; + lv_obj_t *cont = lv_obj_create(s_screen); s_list = cont; - lv_obj_set_size(cont, lv_pct(100), LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); + lv_obj_set_size(cont, lv_pct(100), ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); lv_obj_align(cont, LV_ALIGN_TOP_MID, 0, UI_CHROME_HEADER_H); lv_obj_set_style_bg_opa(cont, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(cont, 0, 0); @@ -216,7 +237,7 @@ static void build_list(void) { lv_obj_set_style_width(cont, LIST_SB_W, LV_PART_SCROLLBAR); lv_obj_set_style_radius(cont, LIST_SB_RADIUS, LV_PART_SCROLLBAR); - for (int i = 0; i < SIGNAL_COUNT; i++) { + for (int i = 0; i < s_sig_count; i++) { lv_obj_t *r = lv_obj_create(cont); s_rows[i] = r; lv_obj_remove_flag(r, LV_OBJ_FLAG_SCROLLABLE); @@ -254,12 +275,14 @@ static void build_list(void) { s_row_name[i] = name; lv_label_set_long_mode(name, LV_LABEL_LONG_SCROLL_CIRCULAR); lv_obj_set_flex_grow(name, 1); - lv_label_set_text(name, SIGNALS[i].name); + lv_label_set_text(name, s_sigs[i].name); lv_obj_set_style_text_font(name, &lv_font_montserrat_14, 0); + char fbuf[16]; + fmt_mhz(s_sigs[i].frequency, fbuf, sizeof(fbuf)); lv_obj_t *val = lv_label_create(r); s_row_val[i] = val; - lv_label_set_text(val, SIGNALS[i].freq); + lv_label_set_text(val, fbuf); lv_obj_set_style_text_font(val, &lv_font_montserrat_12, 0); } @@ -289,8 +312,10 @@ static void build_sending(void) { lv_obj_set_style_text_align(s_status_label, LV_TEXT_ALIGN_CENTER, 0); lv_obj_align(s_status_label, LV_ALIGN_TOP_MID, 0, STATUS_Y); + char freq_str[16]; + fmt_mhz(s_sigs[s_sel].frequency, freq_str, sizeof(freq_str)); s_freq_label = lv_label_create(s_screen); - lv_label_set_text(s_freq_label, SIGNALS[s_sel].freq); + lv_label_set_text(s_freq_label, freq_str); lv_obj_set_style_text_color(s_freq_label, current_theme.border_accent, 0); lv_obj_set_style_text_font(s_freq_label, &lv_font_montserrat_12, 0); lv_obj_align(s_freq_label, LV_ALIGN_TOP_MID, 0, FREQ_Y); @@ -313,12 +338,13 @@ static void show_options(void) { if (s_freq_label != NULL) lv_obj_add_flag(s_freq_label, LV_OBJ_FLAG_HIDDEN); + fmt_mhz(s_sigs[s_sel].frequency, s_card_freq, sizeof(s_card_freq)); capture_result_cfg_t cfg = { .accent = current_theme.border_accent, .card_icon = SIGNAL_ICON, - .card_title = SIGNALS[s_sel].name, - .card_sub = SIGNALS[s_sel].proto, - .card_value = SIGNALS[s_sel].freq, + .card_title = s_sigs[s_sel].name, + .card_sub = s_sigs[s_sel].protocol, + .card_value = s_card_freq, .primary_label = "Send again", .again_label = "Pick another", }; @@ -329,10 +355,18 @@ static void show_options(void) { } static void start_send(void) { + esp_err_t err = subghz_replay_file(s_sigs[s_sel].name); + if (err == ESP_ERR_NOT_SUPPORTED) { + notify(NOTIFY_WARNING, "Replay not supported"); + return; + } + if (err != ESP_OK) { + notify(NOTIFY_WARNING, "Send failed"); + return; + } stop_send_timer(); s_view = VIEW_SENDING; s_send_start = lv_tick_get(); - ESP_LOGI(TAG, "mock send: %s %s", SIGNALS[s_sel].name, SIGNALS[s_sel].freq); build_sending(); ui_feedback(UI_FB_SELECT); s_send_timer = lv_timer_create(send_done_cb, SENDING_MS, NULL); @@ -396,7 +430,7 @@ static void subghz_send_input(const input_event_t *ev, void *ctx) { case VIEW_LIST: switch (ev->button) { case INPUT_BTN_DOWN: - if (nav && s_sel < SIGNAL_COUNT - 1) { + if (nav && s_sel < s_sig_count - 1) { s_sel++; update_selection(); ui_feedback(UI_FB_NAV); @@ -474,9 +508,8 @@ static void subghz_send_input(const input_event_t *ev, void *ctx) { if (!s_saved) { s_saved = true; capture_result_mark_saved(&s_cr); - ESP_LOGI(TAG, "mock signal saved: %s", SIGNALS[s_sel].name); ui_feedback(UI_FB_WRITE); - notify(NOTIFY_SAVED, "Sub-GHz signal saved"); + notify(NOTIFY_INFO, "Already on SD"); } break; case CAP_ACT_AGAIN: diff --git a/firmware_p4/components/Applications/ui/screens/video/include/media_thumb.h b/firmware_p4/components/Applications/ui/screens/video/include/media_thumb.h new file mode 100644 index 000000000..ebed4f62b --- /dev/null +++ b/firmware_p4/components/Applications/ui/screens/video/include/media_thumb.h @@ -0,0 +1,58 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#ifndef MEDIA_THUMB_H +#define MEDIA_THUMB_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include "lvgl.h" + +/** + * @brief A decoded RGB565 image ready to hand to an lv_image. Its `dsc.data` + * buffer is owned by this struct; release it with media_thumb_free(). + */ +typedef struct { + lv_image_dsc_t dsc; + void *buf; ///< backing pixel buffer (freed by media_thumb_free) + uint16_t w; + uint16_t h; +} media_thumb_t; + +/** + * @brief Decode a baseline-JPEG blob (e.g. the MP4 'covr' artwork) to an + * RGB565 lv_image using the ESP32-P4 hardware JPEG engine. Progressive + * JPEG and PNG are NOT supported (returns false) — the caller should + * fall back to a placeholder glyph. Run this OFF the LVGL thread. + * + * @return true on success (out is filled, free with media_thumb_free), false + * on any error (unsupported format, decode failure, OOM). + */ +bool media_thumb_decode_jpeg(const uint8_t *jpeg, size_t len, media_thumb_t *out); + +/** @brief Release a media_thumb_t produced by media_thumb_decode_jpeg. */ +void media_thumb_free(media_thumb_t *t); + +#ifdef __cplusplus +} +#endif + +#endif // MEDIA_THUMB_H diff --git a/firmware_p4/components/Applications/ui/screens/video/include/mp4_meta.h b/firmware_p4/components/Applications/ui/screens/video/include/mp4_meta.h new file mode 100644 index 000000000..3d5c6cf00 --- /dev/null +++ b/firmware_p4/components/Applications/ui/screens/video/include/mp4_meta.h @@ -0,0 +1,82 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#ifndef MP4_META_H +#define MP4_META_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** @brief Video codec of the first video track, detected from the sample-entry fourcc. */ +typedef enum { + MP4_VCODEC_NONE = 0, ///< no video track + MP4_VCODEC_H264, ///< avc1 / avc3 — NOT decodable in realtime on ESP32-P4 (no HW decoder) + MP4_VCODEC_MJPEG, ///< mjpa / jpeg / MJPG — decodable via the HW JPEG engine + MP4_VCODEC_MPEG4, ///< mp4v — not supported + MP4_VCODEC_OTHER, +} mp4_vcodec_t; + +/** @brief Cover-art image format (from the iTunes 'covr' data-atom type flag). */ +typedef enum { + MP4_COVER_NONE = 0, + MP4_COVER_JPEG, ///< covr data type 13 + MP4_COVER_PNG, ///< covr data type 14 +} mp4_cover_fmt_t; + +/** @brief Parsed MP4 metadata. Strings are always NUL-terminated (empty if absent). */ +typedef struct { + char title[64]; ///< ilst \xA9nam + char artist[64]; ///< ilst \xA9ART + char album[64]; ///< ilst \xA9alb + char year[8]; ///< ilst \xA9day (first 4 chars) + + uint32_t duration_sec; ///< from mvhd (duration / timescale) + uint16_t width; ///< first video track sample-entry width + uint16_t height; ///< first video track sample-entry height + mp4_vcodec_t vcodec; + bool has_audio; ///< an 'soun' handler track is present + + mp4_cover_fmt_t cover_fmt; ///< embedded cover art (from 'covr'); read via mp4_meta_read_cover() + long cover_off; ///< absolute file offset of the image bytes (0 if none) + uint32_t cover_size; ///< size of the image blob in bytes +} mp4_meta_t; + +/** + * @brief Parse the metadata of an .mp4 / .m4v / .mov file. Reads only the box + * tree (moov/udta/ilst + track sample entries), never the media data. + * Self-contained, no decoder needed. Returns false if the file is not a + * readable ISO-BMFF container. + */ +bool mp4_meta_parse(const char *path, mp4_meta_t *out); + +/** + * @brief Read the embedded cover-art blob into a freshly malloc'd buffer that + * the caller must free(). Returns NULL if there is no cover or on error; + * on success *out_size holds the byte count. + */ +uint8_t *mp4_meta_read_cover(const char *path, const mp4_meta_t *m, uint32_t *out_size); + +/** @brief Human-readable codec name for the status line. */ +const char *mp4_vcodec_name(mp4_vcodec_t c); + +#ifdef __cplusplus +} +#endif + +#endif // MP4_META_H diff --git a/firmware_p4/components/Applications/ui/screens/video/include/mp4_player_ui.h b/firmware_p4/components/Applications/ui/screens/video/include/mp4_player_ui.h new file mode 100644 index 000000000..c940538db --- /dev/null +++ b/firmware_p4/components/Applications/ui/screens/video/include/mp4_player_ui.h @@ -0,0 +1,42 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#ifndef MP4_PLAYER_UI_H +#define MP4_PLAYER_UI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Set the .mp4 / .m4v / .mov file to open. Call BEFORE switching to + * SCREEN_MP4_PLAYER (e.g. from the Files screen). The string is copied. + */ +void ui_mp4_player_set_path(const char *path); + +/** + * @brief Set the screen to return to on BACK (as a screen_id_t value). + * Defaults to the Files screen. + */ +void ui_mp4_player_set_return(int screen); + +/** @brief Open the MP4 player screen: parses metadata + cover and shows them. */ +void ui_mp4_player_open(void); + +#ifdef __cplusplus +} +#endif + +#endif // MP4_PLAYER_UI_H diff --git a/firmware_p4/components/Applications/ui/screens/video/media_thumb.c b/firmware_p4/components/Applications/ui/screens/video/media_thumb.c new file mode 100644 index 000000000..27843fb85 --- /dev/null +++ b/firmware_p4/components/Applications/ui/screens/video/media_thumb.c @@ -0,0 +1,128 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#include "media_thumb.h" + +#include +#include + +#include "esp_heap_caps.h" +#include "esp_log.h" + +#include "driver/jpeg_decode.h" + +static const char *TAG = "MEDIA_THUMB"; + +#define THUMB_MAX_DIM 2048 +#define JPEG_DECODE_TIMEOUT_MS 70 + +bool media_thumb_decode_jpeg(const uint8_t *jpeg, size_t len, media_thumb_t *out) { + if (jpeg == NULL || len < 4 || out == NULL) + return false; + memset(out, 0, sizeof(*out)); + + if (jpeg[0] != 0xFF || jpeg[1] != 0xD8) + return false; + + jpeg_decoder_handle_t jpgd = NULL; + jpeg_decode_engine_cfg_t engine_cfg = { + .timeout_ms = JPEG_DECODE_TIMEOUT_MS, + }; + if (jpeg_new_decoder_engine(&engine_cfg, &jpgd) != ESP_OK) { + ESP_LOGW(TAG, "jpeg_new_decoder_engine failed"); + return false; + } + + bool ok = false; + uint8_t *in_buf = NULL; + uint8_t *out_buf = NULL; + jpeg_decode_picture_info_t info = {0}; + size_t out_buf_size = 0; + size_t in_buf_size = 0; + uint32_t decoded = 0; + jpeg_decode_cfg_t decode_cfg = { + .output_format = JPEG_DECODE_OUT_FORMAT_RGB565, + .rgb_order = JPEG_DEC_RGB_ELEMENT_ORDER_BGR, + .conv_std = JPEG_YUV_RGB_CONV_STD_BT601, + }; + jpeg_decode_memory_alloc_cfg_t out_mem_cfg = { + .buffer_direction = JPEG_DEC_ALLOC_OUTPUT_BUFFER, + }; + jpeg_decode_memory_alloc_cfg_t in_mem_cfg = { + .buffer_direction = JPEG_DEC_ALLOC_INPUT_BUFFER, + }; + + in_buf = (uint8_t *)jpeg_alloc_decoder_mem(len, &in_mem_cfg, &in_buf_size); + if (in_buf == NULL) { + ESP_LOGW(TAG, "input mem alloc failed (%u bytes)", (unsigned)len); + goto done; + } + memcpy(in_buf, jpeg, len); + + if (jpeg_decoder_get_info(in_buf, (uint32_t)len, &info) != ESP_OK) { + ESP_LOGW(TAG, "get_info failed (progressive JPEG?)"); + goto done; + } + ESP_LOGI( + TAG, "cover %ux%u, %u bytes", (unsigned)info.width, (unsigned)info.height, (unsigned)len); + if (info.width == 0 || info.height == 0 || info.width > THUMB_MAX_DIM || + info.height > THUMB_MAX_DIM) { + ESP_LOGW(TAG, "bad cover dims %ux%u", (unsigned)info.width, (unsigned)info.height); + goto done; + } + + out_buf = (uint8_t *)jpeg_alloc_decoder_mem( + (size_t)info.width * info.height * 2, &out_mem_cfg, &out_buf_size); + if (out_buf == NULL) { + ESP_LOGW(TAG, "decoder mem alloc failed (%u bytes)", (unsigned)(info.width * info.height * 2)); + goto done; + } + + if (jpeg_decoder_process( + jpgd, &decode_cfg, in_buf, (uint32_t)len, out_buf, out_buf_size, &decoded) != ESP_OK) { + ESP_LOGW(TAG, "jpeg_decoder_process failed"); + goto done; + } + + out->buf = out_buf; + out->w = info.width; + out->h = info.height; + out->dsc.header.magic = LV_IMAGE_HEADER_MAGIC; + out->dsc.header.cf = LV_COLOR_FORMAT_RGB565; + out->dsc.header.w = info.width; + out->dsc.header.h = info.height; + out->dsc.header.stride = (uint32_t)info.width * 2; + out->dsc.data = out_buf; + out->dsc.data_size = (uint32_t)info.width * info.height * 2; + out_buf = NULL; + ok = true; + +done: + if (in_buf != NULL) + free(in_buf); + if (out_buf != NULL) + free(out_buf); + if (jpgd != NULL) + jpeg_del_decoder_engine(jpgd); + return ok; +} + +void media_thumb_free(media_thumb_t *t) { + if (t == NULL) + return; + if (t->buf != NULL) + free(t->buf); + memset(t, 0, sizeof(*t)); +} diff --git a/firmware_p4/components/Applications/ui/screens/video/mp4_meta.c b/firmware_p4/components/Applications/ui/screens/video/mp4_meta.c new file mode 100644 index 000000000..107e06829 --- /dev/null +++ b/firmware_p4/components/Applications/ui/screens/video/mp4_meta.c @@ -0,0 +1,305 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#include "mp4_meta.h" + +#include +#include +#include + +static const char *TAG = "MP4_META"; + +#define MP4_MAX_DEPTH 8 +#define MP4_MAX_BOXES 4096 +#define ILST_STR_MAX 128 +#define MP4_MAX_COVER_BYTES (4u * 1024u * 1024u) + +typedef struct { + mp4_meta_t *m; + uint32_t timescale; + uint64_t duration; + int cur_hdlr; + int boxes_seen; +} mp4_ctx_t; + +static uint32_t be32(const uint8_t *p) { + return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3]; +} +static uint16_t be16(const uint8_t *p) { + return (uint16_t)(((uint16_t)p[0] << 8) | (uint16_t)p[1]); +} +static uint64_t be64(const uint8_t *p) { + return ((uint64_t)be32(p) << 32) | (uint64_t)be32(p + 4); +} + +static bool read_at(FILE *f, long off, void *buf, size_t len) { + if (fseek(f, off, SEEK_SET) != 0) + return false; + return fread(buf, 1, len, f) == len; +} + +static void str_copy_trunc(char *dst, size_t dstcap, const char *src, size_t srclen) { + if (dstcap == 0) + return; + size_t k = srclen < dstcap - 1 ? srclen : dstcap - 1; + memcpy(dst, src, k); + dst[k] = '\0'; +} + +static void +handle_ilst_data(FILE *f, const uint8_t *key, long payload_off, long payload_end, mp4_ctx_t *c) { + uint8_t hd[8]; + if (payload_end - payload_off < 8 || !read_at(f, payload_off, hd, 8)) + return; + uint32_t type_class = be32(hd) & 0x00FFFFFF; + long val_off = payload_off + 8; + long val_len = payload_end - val_off; + if (val_len <= 0) + return; + + const uint8_t nam[4] = {0xA9, 'n', 'a', 'm'}; + const uint8_t art[4] = {0xA9, 'A', 'R', 'T'}; + const uint8_t alb[4] = {0xA9, 'a', 'l', 'b'}; + const uint8_t day[4] = {0xA9, 'd', 'a', 'y'}; + + char *dst = NULL; + size_t dstcap = 0; + if (memcmp(key, nam, 4) == 0) { + dst = c->m->title; + dstcap = sizeof(c->m->title); + } else if (memcmp(key, art, 4) == 0) { + dst = c->m->artist; + dstcap = sizeof(c->m->artist); + } else if (memcmp(key, alb, 4) == 0) { + dst = c->m->album; + dstcap = sizeof(c->m->album); + } else if (memcmp(key, day, 4) == 0) { + dst = c->m->year; + dstcap = sizeof(c->m->year); + } else if (memcmp(key, "covr", 4) == 0) { + uint8_t t = (uint8_t)(type_class & 0xFF); + if (t == 13) + c->m->cover_fmt = MP4_COVER_JPEG; + else if (t == 14) + c->m->cover_fmt = MP4_COVER_PNG; + else + c->m->cover_fmt = MP4_COVER_JPEG; + c->m->cover_off = val_off; + c->m->cover_size = (uint32_t)val_len; + return; + } + + if (dst != NULL && dstcap > 0) { + char tmp[ILST_STR_MAX]; + size_t rd = (size_t)val_len < sizeof(tmp) ? (size_t)val_len : sizeof(tmp); + if (read_at(f, val_off, tmp, rd)) + str_copy_trunc(dst, dstcap, tmp, rd); + } +} + +static void handle_stsd(FILE *f, long payload_off, long payload_end, mp4_ctx_t *c) { + uint8_t h[8]; + if (payload_end - payload_off < 8 || !read_at(f, payload_off, h, 8)) + return; + long entry_off = payload_off + 8; + uint8_t se[36]; + long avail = payload_end - entry_off; + size_t rd = avail < (long)sizeof(se) ? (size_t)avail : sizeof(se); + if (rd < 8 || !read_at(f, entry_off, se, rd)) + return; + const uint8_t *fourcc = se + 4; + + if (c->cur_hdlr == 'v' && c->m->vcodec == MP4_VCODEC_NONE) { + if (memcmp(fourcc, "avc1", 4) == 0 || memcmp(fourcc, "avc3", 4) == 0) + c->m->vcodec = MP4_VCODEC_H264; + else if (memcmp(fourcc, "mjpa", 4) == 0 || memcmp(fourcc, "jpeg", 4) == 0 || + memcmp(fourcc, "MJPG", 4) == 0 || memcmp(fourcc, "mjpg", 4) == 0) + c->m->vcodec = MP4_VCODEC_MJPEG; + else if (memcmp(fourcc, "mp4v", 4) == 0) + c->m->vcodec = MP4_VCODEC_MPEG4; + else + c->m->vcodec = MP4_VCODEC_OTHER; + if (rd >= 28) { + c->m->width = be16(se + 24); + c->m->height = be16(se + 26); + } + } +} + +static void walk(FILE *f, long start, long end, int depth, const uint8_t *ilst_key, mp4_ctx_t *c) { + if (depth > MP4_MAX_DEPTH) + return; + long pos = start; + while (pos + 8 <= end && c->boxes_seen < MP4_MAX_BOXES) { + uint8_t hdr[8]; + if (!read_at(f, pos, hdr, 8)) + return; + c->boxes_seen++; + uint64_t size = be32(hdr); + const uint8_t *type = hdr + 4; + long header = 8; + if (size == 1) { + uint8_t big[8]; + if (!read_at(f, pos + 8, big, 8)) + return; + size = be64(big); + header = 16; + } else if (size == 0) { + size = (uint64_t)(end - pos); + } + if (size < (uint64_t)header) + return; + long box_end = pos + (long)size; + if (box_end > end || box_end <= pos) + box_end = end; + long payload = pos + header; + + if (ilst_key != NULL) { + if (memcmp(type, "data", 4) == 0) + handle_ilst_data(f, ilst_key, payload, box_end, c); + } else if (memcmp(type, "moov", 4) == 0 || memcmp(type, "trak", 4) == 0 || + memcmp(type, "mdia", 4) == 0 || memcmp(type, "minf", 4) == 0 || + memcmp(type, "stbl", 4) == 0 || memcmp(type, "udta", 4) == 0) { + if (memcmp(type, "trak", 4) == 0) + c->cur_hdlr = 0; + walk(f, payload, box_end, depth + 1, NULL, c); + } else if (memcmp(type, "meta", 4) == 0) { + walk(f, payload + 4, box_end, depth + 1, NULL, c); + } else if (memcmp(type, "ilst", 4) == 0) { + long ip = payload; + while (ip + 8 <= box_end && c->boxes_seen < MP4_MAX_BOXES) { + uint8_t ih[8]; + if (!read_at(f, ip, ih, 8)) + break; + c->boxes_seen++; + uint64_t isz = be32(ih); + if (isz < 8) + break; + long iend = ip + (long)isz; + if (iend > box_end || iend <= ip) + break; + walk(f, ip + 8, iend, depth + 1, ih + 4, c); + ip = iend; + } + } else if (memcmp(type, "mvhd", 4) == 0) { + uint8_t mv[24]; + if (read_at(f, payload, mv, sizeof(mv))) { + if (mv[0] == 0) { + c->timescale = be32(mv + 12); + c->duration = be32(mv + 16); + } else { + c->timescale = be32(mv + 20); + uint8_t d8[8]; + if (read_at(f, payload + 24, d8, 8)) + c->duration = be64(d8); + } + } + } else if (memcmp(type, "hdlr", 4) == 0) { + uint8_t hb[12]; + if (read_at(f, payload, hb, sizeof(hb))) { + if (memcmp(hb + 8, "vide", 4) == 0) + c->cur_hdlr = 'v'; + else if (memcmp(hb + 8, "soun", 4) == 0) { + c->cur_hdlr = 's'; + c->m->has_audio = true; + } + } + } else if (memcmp(type, "stsd", 4) == 0) { + handle_stsd(f, payload, box_end, c); + } + + pos = box_end; + } +} + +bool mp4_meta_parse(const char *path, mp4_meta_t *out) { + if (path == NULL || out == NULL) + return false; + memset(out, 0, sizeof(*out)); + out->vcodec = MP4_VCODEC_NONE; + out->cover_fmt = MP4_COVER_NONE; + + FILE *f = fopen(path, "rb"); + if (f == NULL) + return false; + + if (fseek(f, 0, SEEK_END) != 0) { + fclose(f); + return false; + } + long fsize = ftell(f); + if (fsize < 16) { + fclose(f); + return false; + } + + uint8_t top[8]; + if (!read_at(f, 0, top, 8) || + (memcmp(top + 4, "ftyp", 4) != 0 && memcmp(top + 4, "moov", 4) != 0 && + memcmp(top + 4, "free", 4) != 0 && memcmp(top + 4, "wide", 4) != 0 && + memcmp(top + 4, "mdat", 4) != 0 && memcmp(top + 4, "skip", 4) != 0)) { + fclose(f); + return false; + } + + mp4_ctx_t c = {.m = out}; + walk(f, 0, fsize, 0, NULL, &c); + fclose(f); + + if (c.timescale > 0) + out->duration_sec = (uint32_t)(c.duration / c.timescale); + + return true; +} + +uint8_t *mp4_meta_read_cover(const char *path, const mp4_meta_t *m, uint32_t *out_size) { + if (path == NULL || m == NULL || m->cover_fmt == MP4_COVER_NONE || m->cover_size == 0) + return NULL; + if (m->cover_size > MP4_MAX_COVER_BYTES) + return NULL; + + FILE *f = fopen(path, "rb"); + if (f == NULL) + return NULL; + uint8_t *buf = malloc(m->cover_size); + if (buf == NULL) { + fclose(f); + return NULL; + } + if (!read_at(f, m->cover_off, buf, m->cover_size)) { + free(buf); + fclose(f); + return NULL; + } + fclose(f); + if (out_size) + *out_size = m->cover_size; + return buf; +} + +const char *mp4_vcodec_name(mp4_vcodec_t c) { + switch (c) { + case MP4_VCODEC_H264: + return "H.264"; + case MP4_VCODEC_MJPEG: + return "MJPEG"; + case MP4_VCODEC_MPEG4: + return "MPEG-4"; + case MP4_VCODEC_OTHER: + return "video"; + default: + return "no video"; + } +} diff --git a/firmware_p4/components/Applications/ui/screens/video/mp4_player_ui.c b/firmware_p4/components/Applications/ui/screens/video/mp4_player_ui.c new file mode 100644 index 000000000..d43b32a37 --- /dev/null +++ b/firmware_p4/components/Applications/ui/screens/video/mp4_player_ui.c @@ -0,0 +1,301 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#include "mp4_player_ui.h" + +#include +#include +#include + +#include "st7789.h" + +#include "media_thumb.h" +#include "mp4_meta.h" +#include "ui_chrome.h" +#include "ui_feedback.h" +#include "ui_manager.h" +#include "ui_metrics.h" +#include "ui_theme.h" + +static const char *TAG = "MP4_PLAYER"; + +#define PATH_MAX_LEN 256 +#define REFRESH_TIMER_MS 120 +#define COVER_BOX 118 +#define META_LINE_LEN 96 +#define DUR_BUF_LEN 16 +#define ACC1 0x7A52D6 +#define ACC2 0xB89AFF + +static char s_path[PATH_MAX_LEN]; +static screen_id_t s_return = SCREEN_FILES; + +static lv_obj_t *s_screen = NULL; +static lv_timer_t *s_refresh_timer = NULL; +static lv_obj_t *s_status = NULL; +static media_thumb_t s_thumb; +static bool s_thumb_valid = false; +static uint8_t *s_png_blob = NULL; +static lv_image_dsc_t s_png_dsc; +static bool s_png_valid = false; +static mp4_meta_t s_meta; + +void ui_mp4_player_set_path(const char *path) { + if (path == NULL) { + s_path[0] = '\0'; + return; + } + strncpy(s_path, path, sizeof(s_path) - 1); + s_path[sizeof(s_path) - 1] = '\0'; +} + +void ui_mp4_player_set_return(int screen) { + s_return = (screen_id_t)screen; +} + +static void free_thumb(void) { + if (s_thumb_valid) { + media_thumb_free(&s_thumb); + s_thumb_valid = false; + } + if (s_png_blob != NULL) { + free(s_png_blob); + s_png_blob = NULL; + } + s_png_valid = false; +} + +static void load_cover(void) { + free_thumb(); + if (s_meta.cover_fmt == MP4_COVER_NONE || s_meta.cover_size == 0) + return; + uint32_t blob_len = 0; + uint8_t *blob = mp4_meta_read_cover(s_path, &s_meta, &blob_len); + if (blob == NULL) + return; + if (s_meta.cover_fmt == MP4_COVER_JPEG) { + s_thumb_valid = media_thumb_decode_jpeg(blob, blob_len, &s_thumb); + free(blob); + } else { + s_png_blob = blob; + memset(&s_png_dsc, 0, sizeof(s_png_dsc)); + s_png_dsc.header.magic = LV_IMAGE_HEADER_MAGIC; + s_png_dsc.header.cf = LV_COLOR_FORMAT_RAW; + s_png_dsc.data = s_png_blob; + s_png_dsc.data_size = blob_len; + s_png_valid = true; + } +} + +static void fmt_duration(char *out, size_t n, uint32_t sec) { + if (sec >= 3600) + snprintf(out, + n, + "%u:%02u:%02u", + (unsigned)(sec / 3600), + (unsigned)((sec / 60) % 60), + (unsigned)(sec % 60)); + else + snprintf(out, n, "%u:%02u", (unsigned)(sec / 60), (unsigned)(sec % 60)); +} + +static void mp4_player_input(const input_event_t *ev, void *ctx) { + (void)ctx; + if (ev->action != INPUT_ACTION_PRESS) + return; + switch (ev->button) { + case INPUT_BTN_BACK: + ui_switch_screen(s_return); + break; + case INPUT_BTN_OK: + ui_feedback(UI_FB_SELECT); + if (s_status != NULL) { + if (s_meta.vcodec == MP4_VCODEC_H264) + lv_label_set_text(s_status, "H.264 video can't play on\nthis chip - transcode to MJPEG"); + else if (s_meta.vcodec == MP4_VCODEC_MJPEG) + lv_label_set_text(s_status, "MJPEG playback pipeline\nis the next build stage"); + else + lv_label_set_text(s_status, "Audio decode is the next\nbuild stage"); + } + break; + default: + ui_feedback(UI_FB_NAV); + break; + } +} + +static void refresh_timer_cb(lv_timer_t *t) { + if (lv_screen_active() != s_screen) { + lv_timer_delete(t); + s_refresh_timer = NULL; + } +} + +static lv_obj_t * +make_label(lv_obj_t *parent, const char *txt, const lv_font_t *font, lv_color_t col, int width) { + lv_obj_t *l = lv_label_create(parent); + lv_label_set_text(l, txt); + lv_obj_set_style_text_font(l, font, 0); + lv_obj_set_style_text_color(l, col, 0); + if (width > 0) { + lv_obj_set_width(l, width); + lv_label_set_long_mode(l, LV_LABEL_LONG_DOT); + lv_obj_set_style_text_align(l, LV_TEXT_ALIGN_CENTER, 0); + } + return l; +} + +void ui_mp4_player_open(void) { + if (s_screen != NULL) { + lv_obj_del(s_screen); + s_screen = NULL; + } + + bool parsed = mp4_meta_parse(s_path, &s_meta); + if (!parsed) + memset(&s_meta, 0, sizeof(s_meta)); + load_cover(); + + s_screen = lv_obj_create(NULL); + lv_obj_set_style_bg_color(s_screen, current_theme.screen_base, 0); + lv_obj_set_style_bg_opa(s_screen, LV_OPA_COVER, 0); + lv_obj_remove_flag(s_screen, LV_OBJ_FLAG_SCROLLABLE); + + ui_chrome_header(s_screen, "VIDEO", "/assets/icons/description.bin"); + ui_chrome_footer(s_screen, "OK info BACK exit"); + + lv_obj_t *body = lv_obj_create(s_screen); + lv_obj_remove_style_all(body); + lv_obj_set_size(body, 224, ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H); + lv_obj_align(body, LV_ALIGN_TOP_MID, 0, UI_CHROME_HEADER_H); + lv_obj_remove_flag(body, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_flex_flow(body, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align( + body, LV_FLEX_ALIGN_SPACE_EVENLY, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_style_pad_all(body, 4, 0); + + lv_obj_t *cover = lv_obj_create(body); + lv_obj_remove_style_all(cover); + lv_obj_set_size(cover, COVER_BOX, COVER_BOX); + lv_obj_remove_flag(cover, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_style_radius(cover, 10, 0); + lv_obj_set_style_clip_corner(cover, true, 0); + lv_obj_set_style_bg_color(cover, current_theme.bg_secondary, 0); + lv_obj_set_style_bg_opa(cover, LV_OPA_COVER, 0); + lv_obj_set_style_border_width(cover, 1, 0); + lv_obj_set_style_border_color(cover, current_theme.border_inactive, 0); + lv_obj_set_style_shadow_width(cover, 12, 0); + lv_obj_set_style_shadow_color(cover, lv_color_hex(ACC1), 0); + lv_obj_set_style_shadow_opa(cover, LV_OPA_30, 0); + + const lv_image_dsc_t *cover_src = NULL; + int cw = 0, ch = 0; + if (s_thumb_valid && s_thumb.w > 0 && s_thumb.h > 0) { + cover_src = &s_thumb.dsc; + cw = s_thumb.w; + ch = s_thumb.h; + } else if (s_png_valid) { + cover_src = &s_png_dsc; + lv_image_header_t hdr = {0}; + if (lv_image_decoder_get_info(&s_png_dsc, &hdr) == LV_RESULT_OK) { + cw = hdr.w; + ch = hdr.h; + } + } + if (cover_src != NULL) { + lv_obj_t *img = lv_image_create(cover); + lv_image_set_src(img, cover_src); + lv_obj_center(img); + int longest = cw > ch ? cw : ch; + if (longest > 0) { + int zoom = (COVER_BOX * 256) / longest; + if (zoom < 1) + zoom = 1; + lv_image_set_scale(img, (uint16_t)zoom); + } + } else { + lv_obj_t *ph = lv_label_create(cover); + lv_label_set_text(ph, LV_SYMBOL_VIDEO); + lv_obj_set_style_text_font(ph, &lv_font_montserrat_14, 0); + lv_obj_set_style_text_color(ph, current_theme.text_secondary, 0); + lv_obj_center(ph); + } + + const char *slash = strrchr(s_path, '/'); + const char *fallback = s_path[0] ? (slash ? slash + 1 : s_path) : "no file"; + const char *title = s_meta.title[0] ? s_meta.title : fallback; + make_label(body, title, &lv_font_montserrat_14, current_theme.text_main, 210); + + if (s_meta.artist[0]) + make_label(body, s_meta.artist, &lv_font_montserrat_12, current_theme.border_accent, 210); + + if (s_meta.album[0] || s_meta.year[0]) { + char line[META_LINE_LEN]; + if (s_meta.album[0] && s_meta.year[0]) + snprintf(line, sizeof(line), "%s | %s", s_meta.album, s_meta.year); + else + snprintf(line, sizeof(line), "%s", s_meta.album[0] ? s_meta.album : s_meta.year); + make_label(body, line, &lv_font_montserrat_12, current_theme.text_secondary, 210); + } + + char meta_line[META_LINE_LEN]; + char dur[DUR_BUF_LEN] = ""; + if (s_meta.duration_sec > 0) + fmt_duration(dur, sizeof(dur), s_meta.duration_sec); + if (s_meta.width > 0 && s_meta.height > 0) + snprintf(meta_line, + sizeof(meta_line), + "%s | %ux%u%s%s", + mp4_vcodec_name(s_meta.vcodec), + (unsigned)s_meta.width, + (unsigned)s_meta.height, + dur[0] ? " | " : "", + dur); + else + snprintf(meta_line, + sizeof(meta_line), + "%s%s%s", + mp4_vcodec_name(s_meta.vcodec), + dur[0] ? " | " : "", + dur); + make_label(body, meta_line, &lv_font_montserrat_12, lv_color_hex(0x00E5D0), 210); + + s_status = lv_label_create(body); + lv_obj_set_width(s_status, 210); + lv_label_set_long_mode(s_status, LV_LABEL_LONG_WRAP); + lv_obj_set_style_text_align(s_status, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_style_text_font(s_status, &lv_font_montserrat_12, 0); + if (!parsed) + lv_obj_set_style_text_color(s_status, lv_color_hex(0xFF5252), 0); + else + lv_obj_set_style_text_color(s_status, current_theme.text_secondary, 0); + + if (!parsed) + lv_label_set_text(s_status, "Not a readable MP4"); + else if (s_meta.vcodec == MP4_VCODEC_H264) + lv_label_set_text(s_status, "H.264 - transcode to MJPEG to play"); + else if (s_meta.vcodec == MP4_VCODEC_MJPEG) + lv_label_set_text(s_status, "MJPEG - playback: next build stage"); + else if (s_meta.has_audio) + lv_label_set_text(s_status, "Audio - playback: next build stage"); + else + lv_label_set_text(s_status, "Metadata only"); + + ui_input_set_screen_handler(mp4_player_input, NULL); + if (s_refresh_timer == NULL) + s_refresh_timer = lv_timer_create(refresh_timer_cb, REFRESH_TIMER_MS, NULL); + + ui_screen_load_owned(&s_screen, s_screen); +} diff --git a/firmware_p4/components/Applications/ui/screens/wifi/wifi_attack_ui.c b/firmware_p4/components/Applications/ui/screens/wifi/wifi_attack_ui.c index 64556f0da..992a90fde 100644 --- a/firmware_p4/components/Applications/ui/screens/wifi/wifi_attack_ui.c +++ b/firmware_p4/components/Applications/ui/screens/wifi/wifi_attack_ui.c @@ -340,7 +340,7 @@ static void attack_start_task(void *arg) { } s_is_attack_started = ok; s_is_attack_starting = false; - lv_async_call(attack_started_cb, NULL); + ui_async_call(attack_started_cb, NULL); vTaskDelete(NULL); } @@ -428,7 +428,7 @@ static void ap_pick_task(void *arg) { s_ap_count = n; s_is_pick_scanning = false; - lv_async_call(ap_pick_done_cb, NULL); + ui_async_call(ap_pick_done_cb, NULL); vTaskDelete(NULL); } diff --git a/firmware_p4/components/Applications/ui/screens/wifi/wifi_channel_ui.c b/firmware_p4/components/Applications/ui/screens/wifi/wifi_channel_ui.c index 5aeb3fcd7..9fbe4a4b5 100644 --- a/firmware_p4/components/Applications/ui/screens/wifi/wifi_channel_ui.c +++ b/firmware_p4/components/Applications/ui/screens/wifi/wifi_channel_ui.c @@ -27,6 +27,7 @@ #include "menu_component_ui.h" #include "ui_chrome.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #include "waves_ui.h" #include "wifi_service.h" @@ -41,17 +42,19 @@ static const char *TAG = "WIFI_CHAN_UI"; #define COLOR_QUIET_HEX 0x00E676 #define COLOR_BUSY_HEX 0xFFC107 #define COLOR_CROWDED_HEX 0xF44336 -#define COLOR_DIM_HEX 0x8A8594 #define OCCUPANCY_FULL_APS 6 #define SCAN_WAVES_Y_OFS -6 #define SCAN_CAPTION_Y_OFS 78 -#define SPEC_PANEL_TOP (UI_CHROME_HEADER_H + 8) -#define SPEC_PANEL_MARGIN 8 -#define SPEC_PANEL_W (LCD_H_RES - SPEC_PANEL_MARGIN * 2) -#define SPEC_PANEL_H 210 +#define SPEC_PANEL_TOP (UI_CHROME_HEADER_H + 8) +#define SPEC_PANEL_MARGIN 8 +#define SPEC_PANEL_W (ui_screen_w() - SPEC_PANEL_MARGIN * 2) +// Fit the spectrum panel to the live height (leaving room for the footer and the +// hint below) so it does not overflow the shorter landscape screen. In portrait +// this resolves to ~212, matching the previous fixed 210. +#define SPEC_PANEL_H (ui_screen_h() - SPEC_PANEL_TOP - UI_CHROME_FOOTER_H - 30) #define SPEC_PANEL_PAD 8 #define SPEC_PANEL_RADIUS 10 #define SPEC_PANEL_BG_HEX 0x0A0614 @@ -67,7 +70,7 @@ static const char *TAG = "WIFI_CHAN_UI"; #define SPEC_LABEL_W 30 #define SPEC_LABEL_Y_OFS 4 #define SPEC_GLOW_W 14 -#define SPEC_HINT_Y 266 +#define SPEC_HINT_Y (SPEC_PANEL_TOP + SPEC_PANEL_H + 6) #define REC_CANDIDATE_SPAN 2 @@ -217,7 +220,7 @@ static void build_spectrum(void) { lv_obj_t *hint = lv_label_create(s_screen); lv_label_set_text_fmt( hint, "Ch %d crowded %d AP pick Ch %d", s_row_channel[cr], s_row_ap_count[cr], rec); - lv_obj_set_style_text_color(hint, lv_color_hex(COLOR_DIM_HEX), 0); + lv_obj_set_style_text_color(hint, current_theme.text_secondary, 0); lv_obj_set_style_text_font(hint, &lv_font_montserrat_12, 0); lv_obj_set_style_text_align(hint, LV_TEXT_ALIGN_CENTER, 0); lv_obj_align(hint, LV_ALIGN_TOP_MID, 0, SPEC_HINT_Y); @@ -286,7 +289,7 @@ static void wifi_channel_task(void *arg) { s_row_count = 0; s_scan_state = SCAN_FAIL; s_scanning = false; - lv_async_call(scan_done_cb, NULL); + ui_async_call(scan_done_cb, NULL); vTaskDelete(NULL); return; } @@ -324,7 +327,7 @@ static void wifi_channel_task(void *arg) { s_row_count = rows; s_scan_state = SCAN_DONE; s_scanning = false; - lv_async_call(scan_done_cb, NULL); + ui_async_call(scan_done_cb, NULL); vTaskDelete(NULL); } diff --git a/firmware_p4/components/Applications/ui/screens/wifi/wifi_client_ui.c b/firmware_p4/components/Applications/ui/screens/wifi/wifi_client_ui.c index d03151d31..44fee0d7a 100644 --- a/firmware_p4/components/Applications/ui/screens/wifi/wifi_client_ui.c +++ b/firmware_p4/components/Applications/ui/screens/wifi/wifi_client_ui.c @@ -30,6 +30,7 @@ #include "msgbox_ui.h" #include "ui_chrome.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #include "waves_ui.h" #include "wifi_service.h" @@ -44,13 +45,12 @@ static const char *TAG = "WIFI_CLI_UI"; #define COLOR_STRONG_HEX 0x00E676 #define COLOR_WEAK_HEX 0xF5B13D -#define COLOR_DIM_HEX 0x8A8594 #define RSSI_STRONG_DBM -60 #define SCAN_WAVES_Y_OFS -6 #define SCAN_CAPTION_Y_OFS 78 -#define MAP_BODY_H (LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) +#define MAP_BODY_H (ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) #define MAP_PAD 10 #define MAP_AP_X 8 #define MAP_AP_W 96 @@ -142,7 +142,7 @@ add_card_text(lv_obj_t *card, const char *title, lv_color_t title_color, const c lv_obj_t *s = lv_label_create(card); lv_label_set_text(s, sub); - lv_obj_set_style_text_color(s, lv_color_hex(COLOR_DIM_HEX), 0); + lv_obj_set_style_text_color(s, current_theme.text_secondary, 0); lv_obj_set_style_text_font(s, &lv_font_montserrat_12, 0); lv_obj_align(s, LV_ALIGN_BOTTOM_MID, 0, -4); } @@ -182,7 +182,7 @@ static void build_map(void) { ui_chrome_header(s_screen, "Clients", CLI_HEADER_ICON); lv_obj_t *body = lv_obj_create(s_screen); - lv_obj_set_size(body, LCD_H_RES, MAP_BODY_H); + lv_obj_set_size(body, ui_screen_w(), MAP_BODY_H); lv_obj_align(body, LV_ALIGN_TOP_MID, 0, UI_CHROME_HEADER_H); lv_obj_remove_flag(body, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_style_bg_opa(body, LV_OPA_TRANSP, 0); @@ -324,7 +324,7 @@ static void wifi_client_task(void *arg) { derive_aps(); s_scan_state = SCAN_DONE; s_scanning = false; - lv_async_call(scan_done_cb, NULL); + ui_async_call(scan_done_cb, NULL); vTaskDelete(NULL); } diff --git a/firmware_p4/components/Applications/ui/screens/wifi/wifi_deauth_detector_ui.c b/firmware_p4/components/Applications/ui/screens/wifi/wifi_deauth_detector_ui.c index 7aa72f382..bb9ffe1de 100644 --- a/firmware_p4/components/Applications/ui/screens/wifi/wifi_deauth_detector_ui.c +++ b/firmware_p4/components/Applications/ui/screens/wifi/wifi_deauth_detector_ui.c @@ -55,7 +55,6 @@ static const char *TAG = "WIFI_DEAUTH_DET_UI"; #define ALERT_COLOR 0xE53935 #define CALM_COLOR 0x00E676 -#define DIM_COLOR 0x8A8594 #define POLL_INTERVAL_MS 1000 #define TASK_STACK_SIZE 8192 @@ -108,7 +107,7 @@ static void apply_state(bool alert) { if (s_status_label != NULL) { lv_label_set_text(s_status_label, alert ? "Deauth flood detected" : "Monitoring..."); lv_obj_set_style_text_color( - s_status_label, alert ? lv_color_hex(ALERT_COLOR) : lv_color_hex(DIM_COLOR), 0); + s_status_label, alert ? lv_color_hex(ALERT_COLOR) : current_theme.text_secondary, 0); } if (s_banner != NULL) { @@ -149,7 +148,7 @@ static void detector_worker(void *arg) { while (s_is_poll_running) { uint32_t count = deauther_detector_get_count(); - lv_async_call(count_ready_cb, (void *)(uintptr_t)count); + ui_async_call(count_ready_cb, (void *)(uintptr_t)count); vTaskDelay(pdMS_TO_TICKS(POLL_INTERVAL_MS)); } @@ -234,12 +233,12 @@ void ui_wifi_deauth_detector_open(void) { lv_obj_t *caption = lv_label_create(s_card); lv_label_set_text(caption, "DEAUTH FRAMES"); lv_obj_set_style_text_font(caption, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(caption, lv_color_hex(DIM_COLOR), 0); + lv_obj_set_style_text_color(caption, current_theme.text_secondary, 0); lv_obj_t *info = lv_label_create(s_screen); lv_label_set_text(info, "Watching channels 1-13"); lv_obj_set_style_text_font(info, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(info, lv_color_hex(DIM_COLOR), 0); + lv_obj_set_style_text_color(info, current_theme.text_secondary, 0); lv_obj_set_style_text_align(info, LV_TEXT_ALIGN_CENTER, 0); lv_obj_align_to(info, s_card, LV_ALIGN_OUT_BOTTOM_MID, 0, 14); diff --git a/firmware_p4/components/Applications/ui/screens/wifi/wifi_evil_twin_ui.c b/firmware_p4/components/Applications/ui/screens/wifi/wifi_evil_twin_ui.c index 14e8ebb82..7e32221e7 100644 --- a/firmware_p4/components/Applications/ui/screens/wifi/wifi_evil_twin_ui.c +++ b/firmware_p4/components/Applications/ui/screens/wifi/wifi_evil_twin_ui.c @@ -169,7 +169,8 @@ static void ap_scan_task(void *arg) { wifi_ap_record_t *recs = ap_scanner_get_results(&count); if (recs != NULL) { for (uint16_t i = 0; i < count && n < ET_AP_MAX; i++) { - if (recs[i].ssid[0] == '\0') + // Cloning is by SSID, so skip hidden networks (masked as a placeholder). + if (recs[i].ssid[0] == '\0' || strcmp((const char *)recs[i].ssid, "[rede oculta]") == 0) continue; strncpy(s_ap_ssids[n], (const char *)recs[i].ssid, ET_SSID_LEN - 1); s_ap_ssids[n][ET_SSID_LEN - 1] = '\0'; @@ -181,7 +182,7 @@ static void ap_scan_task(void *arg) { s_ap_count = n; s_is_scanning = false; - lv_async_call(scan_done_cb, NULL); + ui_async_call(scan_done_cb, NULL); vTaskDelete(NULL); } @@ -210,7 +211,7 @@ static void attack_task(void *arg) { evil_twin_get_last_password(s_password, sizeof(s_password)); s_is_captured = true; captured = true; - lv_async_call(password_ready_cb, NULL); + ui_async_call(password_ready_cb, NULL); } vTaskDelay(pdMS_TO_TICKS(ET_POLL_MS)); } diff --git a/firmware_p4/components/Applications/ui/screens/wifi/wifi_handshake_ui.c b/firmware_p4/components/Applications/ui/screens/wifi/wifi_handshake_ui.c index ac355caaf..e72ef51d0 100644 --- a/firmware_p4/components/Applications/ui/screens/wifi/wifi_handshake_ui.c +++ b/firmware_p4/components/Applications/ui/screens/wifi/wifi_handshake_ui.c @@ -28,6 +28,8 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" +#include "ui_semantic.h" #include "ui_theme.h" #include "wifi_service.h" #include "wifi_sniffer.h" @@ -49,9 +51,9 @@ static const char *TAG = "WIFI_HANDSHAKE_UI"; #define FOOTER_HINT "BACK: Exit" #define MX 8 -#define CONTENT_W (LCD_H_RES - 2 * MX) +#define CONTENT_W (ui_screen_w() - 2 * MX) #define BODY_TOP UI_CHROME_HEADER_H -#define BODY_H (LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) +#define BODY_H (ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) #define STACK_GAP 7 #define CARD1_H 76 @@ -85,9 +87,6 @@ static const char *TAG = "WIFI_HANDSHAKE_UI"; #define SAVED_GAP 6 #define DOT_SIZE 9 -#define COL_OK 0x00E676 -#define COL_DIM 0x8A8594 - #define AP_NAME "Monitor" #define AP_CHAN "HOP" #define LBL_BSSID "BSSID" @@ -140,7 +139,7 @@ static const lv_point_precise_t WAVE_PTS[] = { #define WAVE_PT_COUNT ((int)(sizeof(WAVE_PTS) / sizeof(WAVE_PTS[0]))) static void style_badge(int i, bool lit) { - lv_color_t c = lit ? lv_color_hex(COL_OK) : lv_color_hex(COL_DIM); + lv_color_t c = lit ? lv_color_hex(UI_COL_SUCCESS) : current_theme.text_secondary; lv_obj_set_style_text_color(s_badge[i], c, 0); lv_obj_set_style_border_color(s_badge_box[i], c, 0); lv_obj_set_style_border_opa(s_badge_box[i], lit ? LV_OPA_40 : LV_OPA_20, 0); @@ -152,9 +151,11 @@ static void set_progress(int lit) { bool done = (lit >= BADGE_COUNT); lv_label_set_text(s_status, done ? TXT_COMPLETE : TXT_CAPTURING); - lv_obj_set_style_text_color(s_status, done ? lv_color_hex(COL_OK) : lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color( + s_status, done ? lv_color_hex(UI_COL_SUCCESS) : current_theme.text_secondary, 0); - lv_obj_set_style_bg_color(s_saved_dot, done ? lv_color_hex(COL_OK) : lv_color_hex(COL_DIM), 0); + lv_obj_set_style_bg_color( + s_saved_dot, done ? lv_color_hex(UI_COL_SUCCESS) : current_theme.text_secondary, 0); lv_label_set_text(s_saved_txt, done ? TXT_CAPTURED : TXT_CAPTURING); } @@ -195,7 +196,7 @@ static void state_ready_cb(void *param) { lv_obj_set_style_text_color(s_pmkid_val, current_theme.border_accent, 0); } else { lv_label_set_text(s_pmkid_val, VAL_NONE); - lv_obj_set_style_text_color(s_pmkid_val, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s_pmkid_val, current_theme.text_secondary, 0); } } } @@ -217,7 +218,7 @@ static void poll_worker(void *arg) { flags |= FLAG_PMKID; wifi_sniffer_get_pmkid_bssid(s_pm_bssid); } - lv_async_call(state_ready_cb, (void *)(uintptr_t)flags); + ui_async_call(state_ready_cb, (void *)(uintptr_t)flags); vTaskDelay(pdMS_TO_TICKS(POLL_INTERVAL_MS)); } @@ -284,12 +285,13 @@ static void build_info_card(lv_obj_t *parent) { lv_obj_t *chip_l = make_label(chip, AP_CHAN, &lv_font_montserrat_12, current_theme.border_accent); lv_obj_center(chip_l); - lv_obj_t *b_tag = make_label(card, LBL_BSSID, &lv_font_montserrat_12, lv_color_hex(COL_DIM)); + lv_obj_t *b_tag = + make_label(card, LBL_BSSID, &lv_font_montserrat_12, current_theme.text_secondary); lv_obj_align(b_tag, LV_ALIGN_TOP_LEFT, 0, BSSID_Y); s_bssid_val = make_label(card, BSSID_NONE, &lv_font_montserrat_12, current_theme.text_main); lv_obj_align(s_bssid_val, LV_ALIGN_TOP_RIGHT, 0, BSSID_Y); - lv_obj_t *s_tag = make_label(card, LBL_STA, &lv_font_montserrat_12, lv_color_hex(COL_DIM)); + lv_obj_t *s_tag = make_label(card, LBL_STA, &lv_font_montserrat_12, current_theme.text_secondary); lv_obj_align(s_tag, LV_ALIGN_TOP_LEFT, 0, STA_Y); lv_obj_t *s_val = make_label(card, VAL_NONE, &lv_font_montserrat_12, current_theme.text_main); lv_obj_align(s_val, LV_ALIGN_TOP_RIGHT, 0, STA_Y); @@ -307,10 +309,10 @@ static void build_eapol_card(lv_obj_t *parent) { lv_obj_set_style_border_width(card, 1, 0); lv_obj_set_style_pad_all(card, CARD2_PAD, 0); - lv_obj_t *lbl = make_label(card, LBL_EAPOL, &lv_font_montserrat_12, lv_color_hex(COL_DIM)); + lv_obj_t *lbl = make_label(card, LBL_EAPOL, &lv_font_montserrat_12, current_theme.text_secondary); lv_obj_align(lbl, LV_ALIGN_TOP_LEFT, 0, EAPOL_LBL_Y); - s_status = make_label(card, TXT_CAPTURING, &lv_font_montserrat_12, lv_color_hex(COL_DIM)); + s_status = make_label(card, TXT_CAPTURING, &lv_font_montserrat_12, current_theme.text_secondary); lv_obj_align(s_status, LV_ALIGN_TOP_RIGHT, 0, EAPOL_LBL_Y); lv_obj_t *wave = lv_line_create(card); @@ -345,7 +347,8 @@ static void build_eapol_card(lv_obj_t *parent) { lv_obj_set_style_bg_opa(b, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(b, 1, 0); lv_obj_set_style_pad_all(b, 0, 0); - lv_obj_t *bl = make_label(b, BADGE_TXT[i], &lv_font_montserrat_12, lv_color_hex(COL_DIM)); + lv_obj_t *bl = + make_label(b, BADGE_TXT[i], &lv_font_montserrat_12, current_theme.text_secondary); lv_obj_center(bl); s_badge[i] = bl; s_badge_box[i] = b; @@ -354,9 +357,9 @@ static void build_eapol_card(lv_obj_t *parent) { static void build_pmkid_row(lv_obj_t *parent) { lv_obj_t *row = make_row(parent, PMKID_ROW_H); - lv_obj_t *tag = make_label(row, LBL_PMKID, &lv_font_montserrat_12, lv_color_hex(COL_DIM)); + lv_obj_t *tag = make_label(row, LBL_PMKID, &lv_font_montserrat_12, current_theme.text_secondary); lv_obj_align(tag, LV_ALIGN_LEFT_MID, 0, 0); - s_pmkid_val = make_label(row, VAL_NONE, &lv_font_montserrat_12, lv_color_hex(COL_DIM)); + s_pmkid_val = make_label(row, VAL_NONE, &lv_font_montserrat_12, current_theme.text_secondary); lv_obj_align(s_pmkid_val, LV_ALIGN_RIGHT_MID, 0, 0); } @@ -372,9 +375,10 @@ static void build_saved_row(lv_obj_t *parent) { lv_obj_set_style_radius(s_saved_dot, LV_RADIUS_CIRCLE, 0); lv_obj_set_style_border_width(s_saved_dot, 0, 0); lv_obj_set_style_bg_opa(s_saved_dot, LV_OPA_COVER, 0); - lv_obj_set_style_bg_color(s_saved_dot, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_bg_color(s_saved_dot, current_theme.text_secondary, 0); - s_saved_txt = make_label(row, TXT_CAPTURING, &lv_font_montserrat_12, lv_color_hex(COL_DIM)); + s_saved_txt = + make_label(row, TXT_CAPTURING, &lv_font_montserrat_12, current_theme.text_secondary); } static void wifi_handshake_input(const input_event_t *ev, void *ctx) { diff --git a/firmware_p4/components/Applications/ui/screens/wifi/wifi_hotspot_ui.c b/firmware_p4/components/Applications/ui/screens/wifi/wifi_hotspot_ui.c index 5dfe604db..5e1b48077 100644 --- a/firmware_p4/components/Applications/ui/screens/wifi/wifi_hotspot_ui.c +++ b/firmware_p4/components/Applications/ui/screens/wifi/wifi_hotspot_ui.c @@ -26,6 +26,8 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" +#include "ui_semantic.h" #include "ui_theme.h" #include "wifi_service.h" @@ -36,9 +38,9 @@ #define FOOTER_HINT "UP/DOWN OK TOGGLE BACK" #define MX 8 -#define CONTENT_W (LCD_H_RES - 2 * MX) +#define CONTENT_W (ui_screen_w() - 2 * MX) #define BODY_TOP UI_CHROME_HEADER_H -#define BODY_H (LCD_V_RES - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) +#define BODY_H (ui_screen_h() - UI_CHROME_HEADER_H - UI_CHROME_FOOTER_H) #define STACK_GAP 9 #define STATUS_H 54 @@ -70,8 +72,6 @@ #define TILE_GAP 6 #define RCOL_GAP 6 -#define COL_OK 0x00E676 -#define COL_DIM 0x8A8594 #define COL_CYAN 0x37E0A8 #define COL_ACC2 0xB89AFF @@ -166,7 +166,7 @@ static lv_obj_t *make_tile( lv_obj_set_flex_align(tile, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); lv_obj_set_style_pad_row(tile, 1, 0); - make_label(tile, lbl, &lv_font_montserrat_12, lv_color_hex(COL_DIM)); + make_label(tile, lbl, &lv_font_montserrat_12, current_theme.text_secondary); make_label(tile, val, vfont, vcol); return tile; } @@ -178,15 +178,15 @@ static void update_uptime_label(void) { char buf[UPTIME_BUF]; snprintf(buf, sizeof(buf), UPTIME_ONLINE_FMT, s_secs / 60, s_secs % 60); lv_label_set_text(s_uptime, buf); - lv_obj_set_style_text_color(s_uptime, lv_color_hex(COL_OK), 0); + lv_obj_set_style_text_color(s_uptime, lv_color_hex(UI_COL_SUCCESS), 0); } else { lv_label_set_text(s_uptime, TXT_OFFLINE); - lv_obj_set_style_text_color(s_uptime, lv_color_hex(COL_DIM), 0); + lv_obj_set_style_text_color(s_uptime, current_theme.text_secondary, 0); } } static void apply_online(void) { - lv_color_t c = s_online ? lv_color_hex(COL_OK) : lv_color_hex(COL_DIM); + lv_color_t c = s_online ? lv_color_hex(UI_COL_SUCCESS) : current_theme.text_secondary; if (s_dot != NULL) lv_obj_set_style_bg_color(s_dot, c, 0); @@ -249,7 +249,7 @@ static void build_status_card(lv_obj_t *parent) { lv_obj_set_style_radius(s_dot, LV_RADIUS_CIRCLE, 0); lv_obj_set_style_border_width(s_dot, 0, 0); lv_obj_set_style_bg_opa(s_dot, LV_OPA_COVER, 0); - lv_obj_set_style_bg_color(s_dot, lv_color_hex(COL_OK), 0); + lv_obj_set_style_bg_color(s_dot, lv_color_hex(UI_COL_SUCCESS), 0); lv_obj_t *txt = lv_obj_create(card); lv_obj_remove_flag(txt, LV_OBJ_FLAG_SCROLLABLE); @@ -264,7 +264,7 @@ static void build_status_card(lv_obj_t *parent) { lv_obj_set_style_pad_row(txt, 2, 0); make_label(txt, AP_NAME, &lv_font_montserrat_14, current_theme.text_main); - s_uptime = make_label(txt, "", &lv_font_montserrat_12, lv_color_hex(COL_OK)); + s_uptime = make_label(txt, "", &lv_font_montserrat_12, lv_color_hex(UI_COL_SUCCESS)); s_toggle = lv_obj_create(card); lv_obj_remove_flag(s_toggle, LV_OBJ_FLAG_SCROLLABLE); @@ -272,9 +272,9 @@ static void build_status_card(lv_obj_t *parent) { lv_obj_set_size(s_toggle, TOGGLE_W, TOGGLE_H); lv_obj_set_style_radius(s_toggle, TOGGLE_RADIUS, 0); lv_obj_set_style_pad_all(s_toggle, 0, 0); - lv_obj_set_style_bg_color(s_toggle, lv_color_hex(COL_OK), 0); + lv_obj_set_style_bg_color(s_toggle, lv_color_hex(UI_COL_SUCCESS), 0); lv_obj_set_style_bg_opa(s_toggle, LV_OPA_20, 0); - lv_obj_set_style_border_color(s_toggle, lv_color_hex(COL_OK), 0); + lv_obj_set_style_border_color(s_toggle, lv_color_hex(UI_COL_SUCCESS), 0); lv_obj_set_style_border_width(s_toggle, 1, 0); s_knob = lv_obj_create(s_toggle); @@ -283,7 +283,7 @@ static void build_status_card(lv_obj_t *parent) { lv_obj_set_style_radius(s_knob, LV_RADIUS_CIRCLE, 0); lv_obj_set_style_border_width(s_knob, 0, 0); lv_obj_set_style_bg_opa(s_knob, LV_OPA_COVER, 0); - lv_obj_set_style_bg_color(s_knob, lv_color_hex(COL_OK), 0); + lv_obj_set_style_bg_color(s_knob, lv_color_hex(UI_COL_SUCCESS), 0); lv_obj_align(s_knob, LV_ALIGN_RIGHT_MID, -KNOB_INSET, 0); } @@ -304,7 +304,7 @@ static void build_arc(lv_obj_t *parent) { lv_obj_t *center = make_label(arc, ARC_CENTER, &lv_font_montserrat_14, current_theme.text_main); lv_obj_align(center, LV_ALIGN_CENTER, 0, -6); - lv_obj_t *sub = make_label(arc, ARC_SUB, &lv_font_montserrat_12, lv_color_hex(COL_DIM)); + lv_obj_t *sub = make_label(arc, ARC_SUB, &lv_font_montserrat_12, current_theme.text_secondary); lv_obj_align(sub, LV_ALIGN_CENTER, 0, 11); } @@ -413,7 +413,9 @@ void ui_wifi_hotspot_open(void) { ui_chrome_footer(s_screen, FOOTER_HINT); lv_obj_t *stack = lv_obj_create(s_screen); - lv_obj_remove_flag(stack, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_add_flag(stack, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_scroll_dir(stack, LV_DIR_VER); + lv_obj_set_scrollbar_mode(stack, LV_SCROLLBAR_MODE_AUTO); lv_obj_remove_flag(stack, LV_OBJ_FLAG_CLICKABLE); lv_obj_set_size(stack, CONTENT_W, BODY_H); lv_obj_align(stack, LV_ALIGN_TOP_MID, 0, BODY_TOP); diff --git a/firmware_p4/components/Applications/ui/screens/wifi/wifi_names_ui.c b/firmware_p4/components/Applications/ui/screens/wifi/wifi_names_ui.c index 22da54185..be72f6d3c 100644 --- a/firmware_p4/components/Applications/ui/screens/wifi/wifi_names_ui.c +++ b/firmware_p4/components/Applications/ui/screens/wifi/wifi_names_ui.c @@ -80,7 +80,7 @@ static void on_kb_submit(const char *text, void *ud) { wifi_names_remove(s_edit_index); } s_edit_index = -1; - lv_async_call(rebuild_async, NULL); + ui_async_call(rebuild_async, NULL); } static void wifi_names_input(const input_event_t *ev, void *ctx) { diff --git a/firmware_p4/components/Applications/ui/screens/wifi/wifi_packets_ui.c b/firmware_p4/components/Applications/ui/screens/wifi/wifi_packets_ui.c index c01b7555a..fd22da05a 100644 --- a/firmware_p4/components/Applications/ui/screens/wifi/wifi_packets_ui.c +++ b/firmware_p4/components/Applications/ui/screens/wifi/wifi_packets_ui.c @@ -145,7 +145,7 @@ static void packets_worker(void *arg) { hs = wifi_sniffer_handshake_captured(); s_pkt_count = count; s_hs_captured = hs; - lv_async_call(packets_update_cb, NULL); + ui_async_call(packets_update_cb, NULL); vTaskDelay(pdMS_TO_TICKS(PACKETS_POLL_MS)); } diff --git a/firmware_p4/components/Applications/ui/screens/wifi/wifi_port_scan_ui.c b/firmware_p4/components/Applications/ui/screens/wifi/wifi_port_scan_ui.c index d71accd05..10d0ff284 100644 --- a/firmware_p4/components/Applications/ui/screens/wifi/wifi_port_scan_ui.c +++ b/firmware_p4/components/Applications/ui/screens/wifi/wifi_port_scan_ui.c @@ -139,7 +139,7 @@ static void scan_task(void *arg) { s_count = n; s_state = SCAN_DONE; s_scanning = false; - lv_async_call(scan_done_cb, NULL); + ui_async_call(scan_done_cb, NULL); vTaskDelete(NULL); } diff --git a/firmware_p4/components/Applications/ui/screens/wifi/wifi_probe_mon_ui.c b/firmware_p4/components/Applications/ui/screens/wifi/wifi_probe_mon_ui.c index 4ec12fd8b..c5782e888 100644 --- a/firmware_p4/components/Applications/ui/screens/wifi/wifi_probe_mon_ui.c +++ b/firmware_p4/components/Applications/ui/screens/wifi/wifi_probe_mon_ui.c @@ -130,7 +130,7 @@ static void probe_worker(void *arg) { probe_monitor_free_results(); s_ui_count = (uint16_t)copied; s_seen_total = n; - lv_async_call(probe_refresh_cb, NULL); + ui_async_call(probe_refresh_cb, NULL); vTaskDelay(pdMS_TO_TICKS(POLL_INTERVAL_MS)); } diff --git a/firmware_p4/components/Applications/ui/screens/wifi/wifi_scan_ui.c b/firmware_p4/components/Applications/ui/screens/wifi/wifi_scan_ui.c index ed1152566..50e119d60 100644 --- a/firmware_p4/components/Applications/ui/screens/wifi/wifi_scan_ui.c +++ b/firmware_p4/components/Applications/ui/screens/wifi/wifi_scan_ui.c @@ -175,7 +175,7 @@ static void wifi_scan_task(void *arg) { s_ap_count = n; s_scan_state = SCAN_DONE; s_scanning = false; - lv_async_call(scan_done_cb, NULL); + ui_async_call(scan_done_cb, NULL); vTaskDelete(NULL); } diff --git a/firmware_p4/components/Applications/ui/screens/wifi/wifi_signal_locator_ui.c b/firmware_p4/components/Applications/ui/screens/wifi/wifi_signal_locator_ui.c index 199938529..d0807803b 100644 --- a/firmware_p4/components/Applications/ui/screens/wifi/wifi_signal_locator_ui.c +++ b/firmware_p4/components/Applications/ui/screens/wifi/wifi_signal_locator_ui.c @@ -28,6 +28,7 @@ #include "ui_chrome.h" #include "ui_feedback.h" #include "ui_manager.h" +#include "ui_metrics.h" #include "ui_theme.h" #include "waves_ui.h" @@ -75,7 +76,6 @@ static const char *TAG = "WIFI_SIG_LOC_UI"; #define NEAR_COLOR 0x00E676 #define WARM_COLOR 0x00E676 #define COLD_COLOR 0x29B6F6 -#define DIM_COLOR 0x8A8594 typedef enum { SL_PICK_SCANNING, SL_PICK_LIST, SL_LOCATING } sl_state_t; @@ -182,7 +182,7 @@ static void apply_signal(int prev_rssi) { hc = lv_color_hex(COLD_COLOR); ht = LV_SYMBOL_DOWN " COLDER"; } else { - hc = lv_color_hex(DIM_COLOR); + hc = current_theme.text_secondary; ht = LV_SYMBOL_MINUS " HOLD"; } lv_label_set_text(s_hint, ht); @@ -279,9 +279,14 @@ static void build_locator(void) { lv_obj_set_style_pad_ver(target, 3, 0); lv_obj_align(target, LV_ALIGN_TOP_MID, 0, TARGET_Y); + const int arc_stack_h = ARC_SIZE + 10 + 32 + 18; + int arc_top_y = LV_MIN(ARC_TOP_Y, ui_screen_h() - UI_CHROME_FOOTER_H - arc_stack_h); + int pill_y = arc_top_y + ARC_SIZE + 10; + int prox_y = pill_y + 32; + s_arc = lv_arc_create(s_screen); lv_obj_set_size(s_arc, ARC_SIZE, ARC_SIZE); - lv_obj_align(s_arc, LV_ALIGN_TOP_MID, 0, ARC_TOP_Y); + lv_obj_align(s_arc, LV_ALIGN_TOP_MID, 0, arc_top_y); lv_arc_set_rotation(s_arc, ARC_ROTATION); lv_arc_set_bg_angles(s_arc, 0, 360); lv_arc_set_range(s_arc, RANGE_MIN, RANGE_MAX); @@ -304,7 +309,7 @@ static void build_locator(void) { lv_obj_t *unit = lv_label_create(s_screen); lv_label_set_text(unit, "dBm"); lv_obj_set_style_text_font(unit, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(unit, lv_color_hex(DIM_COLOR), 0); + lv_obj_set_style_text_color(unit, current_theme.text_secondary, 0); lv_obj_align_to(unit, s_arc, LV_ALIGN_CENTER, 0, 16); s_hint = lv_label_create(s_screen); @@ -315,14 +320,14 @@ static void build_locator(void) { lv_obj_set_style_border_width(s_hint, 1, 0); lv_obj_set_style_pad_hor(s_hint, 12, 0); lv_obj_set_style_pad_ver(s_hint, 3, 0); - lv_obj_align(s_hint, LV_ALIGN_TOP_MID, 0, PILL_Y); + lv_obj_align(s_hint, LV_ALIGN_TOP_MID, 0, pill_y); s_prox_label = lv_label_create(s_screen); lv_label_set_text(s_prox_label, proximity_text(rssi_to_pct(s_rssi))); lv_obj_set_style_text_font(s_prox_label, &lv_font_montserrat_12, 0); - lv_obj_set_style_text_color(s_prox_label, lv_color_hex(DIM_COLOR), 0); + lv_obj_set_style_text_color(s_prox_label, current_theme.text_secondary, 0); lv_obj_set_style_text_align(s_prox_label, LV_TEXT_ALIGN_CENTER, 0); - lv_obj_align(s_prox_label, LV_ALIGN_TOP_MID, 0, PROX_Y); + lv_obj_align(s_prox_label, LV_ALIGN_TOP_MID, 0, prox_y); apply_signal(s_rssi); @@ -357,7 +362,7 @@ static void signal_worker(void *arg) { while (s_poll_run && !s_restart_pending) { s_shared_rssi = signal_monitor_get_rssi(); - lv_async_call(signal_update_cb, NULL); + ui_async_call(signal_update_cb, NULL); vTaskDelay(pdMS_TO_TICKS(SIGNAL_POLL_MS)); } @@ -421,7 +426,7 @@ static void ap_pick_task(void *arg) { s_ap_count = n; s_pick_scanning = false; - lv_async_call(ap_pick_done_cb, NULL); + ui_async_call(ap_pick_done_cb, NULL); vTaskDelete(NULL); } diff --git a/firmware_p4/components/Applications/ui/screens/wifi/wifi_target_clients_ui.c b/firmware_p4/components/Applications/ui/screens/wifi/wifi_target_clients_ui.c index 6d8bbc5b7..60f0288df 100644 --- a/firmware_p4/components/Applications/ui/screens/wifi/wifi_target_clients_ui.c +++ b/firmware_p4/components/Applications/ui/screens/wifi/wifi_target_clients_ui.c @@ -202,7 +202,7 @@ static void client_worker(void *arg) { bool started = target_scanner_start(bssid, channel); s_client_count = 0; s_client_active = started; - lv_async_call(client_update_cb, NULL); + ui_async_call(client_update_cb, NULL); bool scanning = started; while (started && s_poll_run && !s_restart_pending && scanning) { @@ -223,7 +223,7 @@ static void client_worker(void *arg) { s_client_count = n; scanning = is_scanning; s_client_active = is_scanning; - lv_async_call(client_update_cb, NULL); + ui_async_call(client_update_cb, NULL); } target_scanner_free_results(); @@ -296,7 +296,7 @@ static void ap_pick_task(void *arg) { s_ap_count = n; s_pick_scanning = false; - lv_async_call(ap_pick_done_cb, NULL); + ui_async_call(ap_pick_done_cb, NULL); vTaskDelete(NULL); } diff --git a/firmware_p4/components/Applications/ui/ui_manager.c b/firmware_p4/components/Applications/ui/ui_manager.c index fda621bd9..c4e0e90c8 100644 --- a/firmware_p4/components/Applications/ui/ui_manager.c +++ b/firmware_p4/components/Applications/ui/ui_manager.c @@ -78,10 +78,19 @@ #include "haptic_ui.h" #include "speaker_ui.h" #include "micrec_ui.h" +#include "mp3_player_ui.h" +#include "mp4_player_ui.h" #include "wav_player_ui.h" +#include "image_viewer_ui.h" +#include "usb_storage_ui.h" #include "wav_library_ui.h" #include "spectrum_ui.h" #include "lora_chat_ui.h" +#include "games_menu_ui.h" +#include "snake_ui.h" +#include "breakout_ui.h" +#include "gb_ui.h" +#include "doom_real_ui.h" #include "octobit_status_ui.h" #include "dev_menu_ui.h" #include "subghz_menu_ui.h" @@ -174,20 +183,15 @@ uint32_t ui_render_beat(void) { return s_render_beat; } -// Bumped by an lv_timer inside the LVGL port task, so it advances only while -// that task is servicing timers. It stalls on a frozen renderer (lock deadlock, -// runaway screen callback, or a dead/suspended task); sys_monitor polls it. static void render_beat_cb(lv_timer_t *t) { (void)t; s_render_beat++; } -// Event-driven input. One pump (created in ui_init) drains input_manager events -// and dispatches them to the active screen's handler, replacing the ~110 -// per-screen polling lv_timers. Input is swallowed while a transition lock is -// active or a modal overlay (msgbox/keyboard) is up, matching the old per-screen -// guards; the dropdown refreshes the transition lock while open, so it is covered -// too. +void ui_render_beat_kick(void) { + s_render_beat++; +} + static ui_input_handler_t s_screen_handler = NULL; static void *s_screen_handler_ctx = NULL; @@ -204,26 +208,63 @@ bool ui_sd_ready(void) { } static bool input_dispatch_blocked(void) { - // While the screen is asleep, swallow input: the press that wakes it (tracked - // by input_manager, which wakes the power policy) must not also act on the UI. return ui_input_is_locked() || msgbox_is_open() || keyboard_is_open() || power_policy_is_asleep(); } +static input_button_t input_remap_for_rotation(input_button_t b) { + if (!lvgl_glue_is_landscape()) + return b; + switch (b) { + case INPUT_BTN_UP: + return INPUT_BTN_LEFT; + case INPUT_BTN_LEFT: + return INPUT_BTN_DOWN; + case INPUT_BTN_DOWN: + return INPUT_BTN_RIGHT; + case INPUT_BTN_RIGHT: + return INPUT_BTN_UP; + default: + return b; + } +} + +bool ui_nav_pressed(input_button_t logical) { + if (!lvgl_glue_is_landscape()) + return input_is_down(logical); + input_button_t phys = logical; + switch (logical) { + case INPUT_BTN_UP: + phys = INPUT_BTN_RIGHT; + break; + case INPUT_BTN_DOWN: + phys = INPUT_BTN_LEFT; + break; + case INPUT_BTN_LEFT: + phys = INPUT_BTN_UP; + break; + case INPUT_BTN_RIGHT: + phys = INPUT_BTN_DOWN; + break; + default: + break; + } + return input_is_down(phys); +} + static void ui_input_pump(lv_timer_t *t) { (void)t; input_event_t ev; while (input_get_event(&ev, 0)) { + ev.button = input_remap_for_rotation(ev.button); if (screen_tips_active()) { screen_tips_handle_input(&ev); continue; } if (s_screen_handler == NULL || input_dispatch_blocked()) { - continue; // drain and discard so stale events do not fire once unblocked + continue; } ui_input_handler_t handler = s_screen_handler; handler(&ev, s_screen_handler_ctx); - // If the handler switched screens, stop draining: remaining events belong to - // the transition (and are flushed by the input lock ui_switch_screen sets). if (s_screen_handler != handler) { break; } @@ -239,26 +280,14 @@ void ui_init(void) { ui_feedback_init(); - // Global power policy (display sleep + low-battery) and the global quick- - // settings dropdown. Both live on the top layer so they survive screen - // switches; created under the LVGL lock. The display is already registered - // (lvgl_glue_init ran before ui_init), so lv_layer_top() is valid here. if (ui_acquire()) { power_policy_init(); dropdown_ui_global_init(); - // Render-progress heartbeat: this lv_timer runs inside the LVGL port task, - // so it advances only while that task is servicing timers. sys_monitor polls - // ui_render_beat() to detect a frozen renderer. See ui_liveness.h. lv_timer_create(render_beat_cb, RENDER_BEAT_MS, NULL); - // Single input pump for all event-driven screens (replaces per-screen timers). lv_timer_create(ui_input_pump, UI_INPUT_PUMP_MS, NULL); ui_release(); } - // Boot orchestration runs once in a transient task (it needs the 5 s splash - // delay and a deep stack for screen construction), then deletes itself. There - // is no perpetual UI task: rendering lives in the LVGL port task and each - // screen's lv_timers, and liveness is supervised by sys_monitor. xTaskCreatePinnedToCore(ui_boot_task, "ui_boot", UI_BOOT_TASK_STACK_SIZE, @@ -277,9 +306,6 @@ void ui_init_safe_mode(void) { ui_theme_init(); ui_feedback_init(); - // Minimal LVGL infra: render heartbeat (so sys_monitor still sees liveness) - // and the shared input pump. No power policy (screen stays on), no dropdown, - // no boot animation, no home. Just the recovery screen. if (ui_acquire()) { lv_timer_create(render_beat_cb, RENDER_BEAT_MS, NULL); lv_timer_create(ui_input_pump, UI_INPUT_PUMP_MS, NULL); @@ -313,21 +339,16 @@ static void ui_boot_task(void *pvParameter) { ui_release(); } - vTaskDelete(NULL); // boot done; nothing to loop on + vTaskDelete(NULL); } static void clear_current_screen(void) { - // Drop the outgoing screen's input handler; a migrated screen re-registers its - // own in its open function. Non-migrated screens leave it NULL and keep using - // their own polling timer. ui_input_set_screen_handler(NULL, NULL); if (main_group != NULL) { lv_group_remove_all_objs(main_group); } } -// Tick-safe comparisons: lv_tick_get() is a uint32_t ms counter that wraps every -// ~49.7 days, so compare signed deltas instead of the raw values. bool ui_input_is_locked(void) { return (int32_t)(lv_tick_get() - input_lock_until) < 0; } @@ -341,12 +362,6 @@ void ui_input_lock(uint32_t ms) { typedef void (*ui_open_fn_t)(void); typedef void (*ui_close_fn_t)(void); -// Lifecycle contract (item 13): the close fn a screen registers to release the -// hardware / stop the task it started. ui_switch_screen() calls it on the -// outgoing screen before opening the next, so a radio/capture never keeps running -// after the user leaves its screen. Only screens that own hardware or a task need -// one; every other screen returns NULL (its widgets are freed by the screen -// swap). New hardware screens: add a public *_stop and a case here. static ui_close_fn_t screen_close_fn(screen_id_t s) { switch (s) { case SCREEN_SUBGHZ_READ: @@ -354,6 +369,14 @@ static ui_close_fn_t screen_close_fn(screen_id_t s) { case SCREEN_NFC_READ: case SCREEN_NFC_EMULATE: return nfc_manager_stop; + case SCREEN_WAV_PLAYER: + return ui_wav_player_stop; + case SCREEN_MP3_PLAYER: + return ui_mp3_player_stop; + case SCREEN_IMAGE_VIEWER: + return ui_image_viewer_stop; + case SCREEN_USB_STORAGE: + return ui_usb_storage_stop; default: return NULL; } @@ -431,12 +454,30 @@ static ui_open_fn_t screen_open_fn(screen_id_t s) { return ui_micrec_open; case SCREEN_WAV_PLAYER: return ui_wav_player_open; + case SCREEN_MP4_PLAYER: + return ui_mp4_player_open; + case SCREEN_MP3_PLAYER: + return ui_mp3_player_open; case SCREEN_PLAYER: return ui_wav_library_open; + case SCREEN_IMAGE_VIEWER: + return ui_image_viewer_open; + case SCREEN_USB_STORAGE: + return ui_usb_storage_open; case SCREEN_SPECTRUM: return ui_spectrum_open; case SCREEN_LORA_CHAT: return ui_lora_chat_open; + case SCREEN_GAMES_MENU: + return ui_games_menu_open; + case SCREEN_GAME_SNAKE: + return ui_snake_open; + case SCREEN_GAME_BREAKOUT: + return ui_breakout_open; + case SCREEN_GAME_GB: + return ui_gb_open; + case SCREEN_GAME_DOOM: + return ui_doom_real_open; case SCREEN_OCTOBIT_STATUS: return ui_octobit_status_open; case SCREEN_DEV_MENU: @@ -587,10 +628,9 @@ static ui_open_fn_t screen_open_fn(screen_id_t s) { } bool ui_screen_shows_chrome(screen_id_t s) { - if (s == SCREEN_NONE) // boot splash / no screen yet: no chrome, no dropdown + if (s == SCREEN_NONE) return false; switch (s) { - // --- Wi-Fi: live scan / attack / capture / monitor --- case SCREEN_WIFI_SCAN_MENU: case SCREEN_WIFI_CHANNELS: case SCREEN_WIFI_CLIENTS: @@ -604,7 +644,6 @@ bool ui_screen_shows_chrome(screen_id_t s) { case SCREEN_WIFI_SIGNAL_LOCATOR: case SCREEN_WIFI_HANDSHAKE: case SCREEN_WIFI_HOTSPOT: - // --- BLE: live scan / spam / sniff / emulate --- case SCREEN_BLE_SCAN: case SCREEN_BLE_SPAM: case SCREEN_BLE_BEACON_SPAM: @@ -616,32 +655,31 @@ bool ui_screen_shows_chrome(screen_id_t s) { case SCREEN_BLE_TRACK_DEVICE: case SCREEN_BLE_KEYBOARD: case SCREEN_BLE_MOUSE: - // --- NFC: live read / write / emulate / scan --- case SCREEN_NFC_READ: case SCREEN_NFC_WRITE: case SCREEN_NFC_EMULATE: case SCREEN_CARD_EMU: case SCREEN_NFC_SCAN: case SCREEN_NFC_P2P: - // --- IR: transmit / capture --- case SCREEN_IR_RECEIVE: case SCREEN_IR_SEND: case SCREEN_IR_BURST: case SCREEN_IR_CONTROLLER: case SCREEN_IR_RAW: - // --- SubGHz: read / send / brute --- case SCREEN_SUBGHZ_READ: case SCREEN_SUBGHZ_SEND: case SCREEN_SUBGHZ_BRUTE: - // --- Audio / LoRa / sensors: play / record / live monitor --- case SCREEN_WAV_PLAYER: + case SCREEN_MP4_PLAYER: + case SCREEN_MP3_PLAYER: + case SCREEN_IMAGE_VIEWER: + case SCREEN_USB_STORAGE: case SCREEN_SPECTRUM: case SCREEN_MIC_REC: case SCREEN_LORA_RNODE: case SCREEN_LORA_TELEMETRY: case SCREEN_USB_MOUSE: case SCREEN_IMU_MONITOR: - // --- Dev / system: live terminal / running payload / update --- case SCREEN_DEV_CONSOLE: case SCREEN_DEV_DIAG: case SCREEN_BOOT_MAP: @@ -655,6 +693,25 @@ bool ui_screen_shows_chrome(screen_id_t s) { } } +void sx1262_stop_rx(void); +esp_err_t sx1262_receive_continuous(void); + +static bool is_lora_screen(screen_id_t s) { + switch (s) { + case SCREEN_LORA_CHAT: + case SCREEN_LORA_TRACEROUTE: + case SCREEN_LORA_RNODE: + case SCREEN_LORA_MQTT: + case SCREEN_LORA_CHANNELS: + case SCREEN_LORA_POSITION: + case SCREEN_LORA_TELEMETRY: + case SCREEN_LORA_SECURE_DM: + return true; + default: + return false; + } +} + void ui_switch_screen(screen_id_t new_screen) { ui_open_fn_t open_fn = screen_open_fn(new_screen); if (open_fn == NULL) { @@ -665,23 +722,23 @@ void ui_switch_screen(screen_id_t new_screen) { input_lock_until = lv_tick_get() + INPUT_LOCK_MS; if (ui_acquire()) { + screen_id_t from = current_screen_id; lv_obj_t *outgoing = lv_screen_active(); - // Lifecycle: stop the outgoing screen's hardware/task before tearing it down. ui_close_fn_t close_fn = screen_close_fn(current_screen_id); if (close_fn != NULL) { close_fn(); } clear_current_screen(); - // Returning to the top level clears the breadcrumb root so a stale category - // ("NFC") never prefixes a fresh navigation. Category menus re-set it on open. if (new_screen == SCREEN_HOME || new_screen == SCREEN_MENU) ui_chrome_set_breadcrumb_root(""); - // Tell the chrome header whether to carry the shared status cluster before - // the screen builds it (current_screen_id only updates after open_fn()). ui_chrome_set_status_enabled(ui_screen_shows_chrome(new_screen)); open_fn(); current_screen_id = new_screen; - if (outgoing != NULL && outgoing != lv_screen_active()) + if (is_lora_screen(from) && !is_lora_screen(new_screen)) + sx1262_stop_rx(); + else if (!is_lora_screen(from) && is_lora_screen(new_screen)) + (void)sx1262_receive_continuous(); + if (outgoing != NULL && outgoing != lv_screen_active() && lv_obj_is_valid(outgoing)) lv_obj_del_async(outgoing); screen_tips_hook(new_screen); ui_release(); @@ -689,8 +746,6 @@ void ui_switch_screen(screen_id_t new_screen) { } bool ui_acquire(void) { - // Finite timeout so a task that never releases the lock can no longer freeze - // every other UI caller forever. Callers already treat false as "skip". if (!lvgl_glue_lock(UI_LOCK_TIMEOUT_MS)) { ESP_LOGW(TAG, "ui_acquire timed out after %d ms; UI lock held elsewhere", UI_LOCK_TIMEOUT_MS); return false; @@ -702,6 +757,13 @@ void ui_release(void) { lvgl_glue_unlock(); } +void ui_async_call(lv_async_cb_t cb, void *user_data) { + if (ui_acquire()) { + lv_async_call(cb, user_data); + ui_release(); + } +} + void ui_screen_load(lv_obj_t *scr) { lv_screen_load(scr); } @@ -723,6 +785,11 @@ screen_id_t ui_current_screen(void) { return current_screen_id; } +void ui_relayout_current_screen(void) { + if (current_screen_id != SCREEN_NONE) + ui_switch_screen(current_screen_id); +} + void ui_manager_relayout_current(void) {} bool ui_btn_up(void) { diff --git a/firmware_p4/components/Applications/ui/ui_theme.c b/firmware_p4/components/Applications/ui/ui_theme.c index cfe286dfd..ce8bcc4f8 100644 --- a/firmware_p4/components/Applications/ui/ui_theme.c +++ b/firmware_p4/components/Applications/ui/ui_theme.c @@ -90,6 +90,8 @@ void ui_theme_load_idx(int color_idx) { if (color_idx < 0 || color_idx > (THEME_COUNT - 1)) color_idx = 0; + theme_idx = color_idx; + FILE *f = fopen(THEME_CONFIG_PATH, "r"); if (f == NULL) { ESP_LOGW(TAG, "Theme file not found, using built-in default palette"); @@ -101,6 +103,7 @@ void ui_theme_load_idx(int color_idx) { current_theme.border_interface = lv_color_hex(0xBF00FF); current_theme.border_inactive = lv_color_hex(0x2A2A2A); current_theme.text_main = lv_color_hex(0xFFFFFF); + current_theme.text_secondary = lv_color_hex(0x8A8594); current_theme.screen_base = lv_color_hex(0x000000); current_theme.protocol_nfc = lv_color_hex(0xCC00FF); current_theme.protocol_wifi = lv_color_hex(0xBF00FF); @@ -150,6 +153,9 @@ void ui_theme_load_idx(int color_idx) { lv_color_hex(hex_to_int(cJSON_GetObjectItem(theme_node, "screen_base")->valuestring)); cJSON *v; + v = cJSON_GetObjectItem(theme_node, "text_secondary"); + current_theme.text_secondary = + v ? lv_color_hex(hex_to_int(v->valuestring)) : lv_color_hex(0x8A8594); v = cJSON_GetObjectItem(theme_node, "protocol_nfc"); current_theme.protocol_nfc = v ? lv_color_hex(hex_to_int(v->valuestring)) : lv_color_hex(0x2196F3); @@ -198,6 +204,8 @@ static void apply_conf_color(const char *key, const char *value, int section) { current_theme.border_inactive = color; else if (strcmp(key, "text_main") == 0) current_theme.text_main = color; + else if (strcmp(key, "text_secondary") == 0) + current_theme.text_secondary = color; else if (strcmp(key, "screen_base") == 0) current_theme.screen_base = color; } else if (section == CONF_SECTION_PROTOCOL) { @@ -311,6 +319,9 @@ static void parse_theme_json(const char *data) { v = cJSON_GetObjectItem(colors, "text_main"); if (v) current_theme.text_main = lv_color_hex(hex_to_int(v->valuestring)); + v = cJSON_GetObjectItem(colors, "text_secondary"); + if (v) + current_theme.text_secondary = lv_color_hex(hex_to_int(v->valuestring)); v = cJSON_GetObjectItem(colors, "screen_base"); if (v) current_theme.screen_base = lv_color_hex(hex_to_int(v->valuestring)); @@ -403,6 +414,13 @@ void ui_theme_load_from_name(const char *theme_name) { free(data); + for (int i = 0; i < THEME_COUNT; i++) { + if (strcmp(theme_name, theme_names[i]) == 0) { + theme_idx = i; + break; + } + } + assets_unload_sd(); char asset_dir[ASSET_DIR_PATH_MAX]; @@ -472,4 +490,4 @@ void ui_theme_init(void) { ui_theme_load_settings(); ui_theme_load_idx(theme_idx); ESP_LOGI(TAG, "Theme initialized: %s", theme_names[theme_idx]); -} \ No newline at end of file +} diff --git a/firmware_p4/components/Applications/wifi/ap_scanner.c b/firmware_p4/components/Applications/wifi/ap_scanner.c index f3e33ac10..37bc34782 100644 --- a/firmware_p4/components/Applications/wifi/ap_scanner.c +++ b/firmware_p4/components/Applications/wifi/ap_scanner.c @@ -23,9 +23,9 @@ #include "cJSON.h" #include "led_control.h" -#include "spi_bridge.h" #include "storage_write.h" #include "tos_storage_paths.h" +#include "wifi_service.h" static const char *TAG = "AP_SCANNER"; @@ -38,73 +38,47 @@ static uint16_t s_cached_count = 0; static bool s_is_scan_ready = false; static wifi_ap_record_t s_empty_record; -static bool fetch_results(void) { - spi_header_t resp; - uint8_t payload[2]; - uint16_t magic_count = SPI_DATA_INDEX_COUNT; +bool ap_scanner_start(void) { + ap_scanner_free_results(); - if (spi_bridge_send_command( - SPI_ID_SYSTEM_DATA, (uint8_t *)&magic_count, 2, &resp, payload, sizeof(payload), 1000) != - ESP_OK) { + wifi_service_start(); + if (wifi_service_scan() != ESP_OK) { + ESP_LOGW(TAG, "AP scan failed"); + led_signal_error(); return false; } - uint16_t count = 0; - memcpy(&count, payload, 2); - - if (s_cached_results != NULL) { - free(s_cached_results); - s_cached_results = NULL; - } - s_cached_count = 0; - + uint16_t count = wifi_service_get_ap_count(); if (count == 0) { s_is_scan_ready = true; + led_signal_warning(); return true; } s_cached_results = (wifi_ap_record_t *)malloc(count * sizeof(wifi_ap_record_t)); if (s_cached_results == NULL) { ESP_LOGW(TAG, "Failed to allocate AP results buffer"); + led_signal_error(); return false; } + uint16_t n = 0; for (uint16_t i = 0; i < count; i++) { - if (spi_bridge_send_command(SPI_ID_SYSTEM_DATA, - (uint8_t *)&i, - 2, - &resp, - (uint8_t *)&s_cached_results[i], - sizeof(s_cached_results[i]), - 1000) != ESP_OK) { - free(s_cached_results); - s_cached_results = NULL; - return false; - } + // wifi_service sanitizes each record (printable SSID, hidden -> placeholder) + // and returns a pointer to a shared buffer, so copy it into our cache. + wifi_ap_record_t *rec = wifi_service_get_ap_record(i); + if (rec == NULL) + continue; + s_cached_results[n++] = *rec; } - s_cached_count = count; + s_cached_count = n; s_is_scan_ready = true; - return true; -} -bool ap_scanner_start(void) { - ap_scanner_free_results(); - esp_err_t err = spi_bridge_run_scan(SPI_ID_WIFI_APP_SCAN_AP, SPI_ID_WIFI_SCAN_STATUS, NULL, 0); - if (err != ESP_OK) { - ESP_LOGW(TAG, "AP scan failed over SPI"); - led_signal_error(); - return false; - } - bool ok = fetch_results(); - if (ok) { - // Auto-persist to SD when a card is present; a no-op otherwise (SD-only). - ap_scanner_save_results_to_sd_card(); - s_cached_count > 0 ? led_signal_info() : led_signal_warning(); - } else { - led_signal_error(); - } - return ok; + // Auto-persist to SD when a card is present; a no-op otherwise (SD-only). + ap_scanner_save_results_to_sd_card(); + s_cached_count > 0 ? led_signal_info() : led_signal_warning(); + return true; } wifi_ap_record_t *ap_scanner_get_results(uint16_t *out_count) { diff --git a/firmware_p4/components/Applications/wifi/include/pcap_serializer.h b/firmware_p4/components/Applications/wifi/include/pcap_serializer.h index a568b21f3..cd6a68d37 100644 --- a/firmware_p4/components/Applications/wifi/include/pcap_serializer.h +++ b/firmware_p4/components/Applications/wifi/include/pcap_serializer.h @@ -22,10 +22,11 @@ extern "C" { #include -#define PCAP_MAGIC_NUMBER 0xa1b2c3d4 -#define PCAP_VERSION_MAJOR 2 -#define PCAP_VERSION_MINOR 4 -#define PCAP_LINK_TYPE_802_11 105 +#define PCAP_MAGIC_NUMBER 0xa1b2c3d4 +#define PCAP_VERSION_MAJOR 2 +#define PCAP_VERSION_MINOR 4 +#define PCAP_LINK_TYPE_802_11 105 +#define PCAP_LINK_TYPE_802_11_RADIOTAP 127 /** * @brief PCAP global file header. diff --git a/firmware_p4/components/Applications/wifi/wifi_sniffer.c b/firmware_p4/components/Applications/wifi/wifi_sniffer.c index ec41382fb..4bd18afe5 100644 --- a/firmware_p4/components/Applications/wifi/wifi_sniffer.c +++ b/firmware_p4/components/Applications/wifi/wifi_sniffer.c @@ -18,41 +18,174 @@ #include "led_control.h" #include +#include #include "esp_log.h" +#include "pcap_serializer.h" #include "spi_bridge.h" #include "spi_session.h" +#include "storage_mkdir.h" #include "storage_stream.h" +#include "tos_loot.h" +#include "tos_storage_paths.h" static const char *TAG = "WIFI_SNIFFER"; -#define WIFI_SNIFFER_MIN_FRAME_LEN 3 +#define WIFI_SNIFFER_PATH_MAX 256 +#define WIFI_SNIFFER_PCAP_SNAPLEN 65535 + +#define RADIOTAP_PRESENT_FLAGS (1u << 1) +#define RADIOTAP_PRESENT_CHANNEL (1u << 3) +#define RADIOTAP_PRESENT_DBM (1u << 5) +#define RADIOTAP_F_FCS 0x10 // frame data ends with a 4-byte FCS +#define RADIOTAP_CHAN_2GHZ 0x0080 // channel flags: 2 GHz band +#define RADIOTAP_CHAN_5GHZ 0x0100 // channel flags: 5 GHz band + +// Little-endian on-wire layout; pad_channel keeps chan_freq 2-byte aligned. +typedef struct { + uint8_t version; + uint8_t pad; + uint16_t len; + uint32_t present; + uint8_t flags; + uint8_t pad_channel; + uint16_t chan_freq; + uint16_t chan_flags; + int8_t dbm_signal; +} __attribute__((packed)) radiotap_header_t; + +static uint16_t channel_to_freq(uint8_t channel) { + if (channel >= 1 && channel <= 13) + return 2407 + channel * 5; + if (channel == 14) + return 2484; + if (channel >= 32) + return 5000 + channel * 5; + return 0; +} static spi_sniffer_stats_t s_cached_stats; static wifi_sniffer_cb_t s_stream_cb = NULL; static storage_stream_t s_capture_stream = NULL; static uint32_t s_session_id = SPI_SESSION_INVALID_ID; +static uint8_t s_reasm_buf[SPI_WIFI_SNIFFER_FRAME_MAX]; +static uint16_t s_reasm_total; +static uint16_t s_reasm_have; +static int8_t s_reasm_rssi; +static uint8_t s_reasm_channel; +static bool s_reasm_active; + bool wifi_sniffer_stop_capture(void); -static void session_stream_cb(const uint8_t *payload, uint8_t len) { - if (payload == NULL || len < WIFI_SNIFFER_MIN_FRAME_LEN) - return; - const spi_wifi_sniffer_frame_t *frame = (const spi_wifi_sniffer_frame_t *)payload; - uint16_t data_len = frame->len; - if (data_len > (len - WIFI_SNIFFER_MIN_FRAME_LEN)) - data_len = (len - WIFI_SNIFFER_MIN_FRAME_LEN); +static void ensure_parent_dir(const char *path) { + char dir[WIFI_SNIFFER_PATH_MAX]; + strncpy(dir, path, sizeof(dir) - 1); + dir[sizeof(dir) - 1] = '\0'; + char *slash = strrchr(dir, '/'); + if (slash != NULL && slash != dir) { + *slash = '\0'; + storage_mkdir_recursive(dir); + } +} + +static void +auto_capture_path(wifi_sniffer_type_t type, bool monitor_mode, char *out, size_t out_size) { + const char *dir = TOS_PATH_WIFI_LOOT_PCAPS; + const char *prefix = "raw"; + if (monitor_mode || type == WIFI_SNIFFER_TYPE_EAPOL) { + dir = TOS_PATH_WIFI_LOOT_HS; + prefix = "handshake"; + } else if (type == WIFI_SNIFFER_TYPE_BEACON) { + prefix = "beacon"; + } else if (type == WIFI_SNIFFER_TYPE_PROBE) { + prefix = "probe"; + } else if (type == WIFI_SNIFFER_TYPE_PMKID) { + prefix = "pmkid"; + } + tos_loot_generate_path(dir, prefix, "pcap", out, out_size, NULL, 0); +} + +static void reasm_reset(void) { + s_reasm_active = false; + s_reasm_total = 0; + s_reasm_have = 0; +} +static void emit_frame(const uint8_t *data, uint16_t len, int8_t rssi, uint8_t channel) { if (s_capture_stream != NULL) { - storage_stream_write(s_capture_stream, frame->data, data_len); + const radiotap_header_t radiotap = { + .version = 0, + .pad = 0, + .len = sizeof(radiotap_header_t), + .present = RADIOTAP_PRESENT_FLAGS | RADIOTAP_PRESENT_CHANNEL | RADIOTAP_PRESENT_DBM, + .flags = RADIOTAP_F_FCS, + .pad_channel = 0, + .chan_freq = channel_to_freq(channel), + .chan_flags = (channel >= 32) ? RADIOTAP_CHAN_5GHZ : RADIOTAP_CHAN_2GHZ, + .dbm_signal = rssi, + }; + + struct timeval tv; + gettimeofday(&tv, NULL); + uint32_t total_len = sizeof(radiotap) + len; + const pcap_packet_header_t rec = { + .ts_sec = (uint32_t)tv.tv_sec, + .ts_usec = (uint32_t)tv.tv_usec, + .incl_len = total_len, + .orig_len = total_len, + }; + storage_stream_write(s_capture_stream, &rec, sizeof(rec)); + storage_stream_write(s_capture_stream, &radiotap, sizeof(radiotap)); + storage_stream_write(s_capture_stream, data, len); } if (s_stream_cb != NULL) { - s_stream_cb(frame->data, data_len, frame->rssi, frame->channel); + s_stream_cb(data, len, rssi, channel); } } +static void session_stream_cb(const uint8_t *payload, uint8_t len) { + if (payload == NULL || len < sizeof(spi_wifi_sniffer_frame_t)) + return; + const spi_wifi_sniffer_frame_t *frag = (const spi_wifi_sniffer_frame_t *)payload; + + uint16_t avail = (uint16_t)(len - sizeof(spi_wifi_sniffer_frame_t)); + uint16_t frag_len = frag->frag_len; + if (frag_len > avail) + frag_len = avail; // never read past what actually arrived + + if (frag->frag_off == 0) { + // Start of a new frame; abandon any partial one still in the buffer. + reasm_reset(); + if (frag->total_len == 0 || frag->total_len > sizeof(s_reasm_buf)) + return; // implausible or larger than we can hold + s_reasm_total = frag->total_len; + s_reasm_rssi = frag->rssi; + s_reasm_channel = frag->channel; + s_reasm_active = true; + } else if (!s_reasm_active || frag->total_len != s_reasm_total || + frag->frag_off != s_reasm_have) { + reasm_reset(); // gap, reorder, or mismatched frame: unrecoverable + return; + } + + if ((uint32_t)s_reasm_have + frag_len > s_reasm_total) { + reasm_reset(); // more data than the frame declared + return; + } + memcpy(s_reasm_buf + s_reasm_have, frag->data, frag_len); + s_reasm_have = (uint16_t)(s_reasm_have + frag_len); + + if (frag->flags & SPI_WIFI_SNIFFER_FRAG_MORE) + return; // wait for the rest + + if (s_reasm_have == s_reasm_total) + emit_frame(s_reasm_buf, s_reasm_have, s_reasm_rssi, s_reasm_channel); + reasm_reset(); +} + static void session_lost_cb(uint32_t session_id, spi_id_t op_id) { (void)op_id; if (session_id == s_session_id) { @@ -82,9 +215,23 @@ start_internal(wifi_sniffer_type_t type, uint8_t channel, bool monitor_mode, wif payload[2] = monitor_mode ? 1 : 0; memset(&s_cached_stats, 0, sizeof(s_cached_stats)); s_stream_cb = cb; + reasm_reset(); + + // Ask the C5 for the whole frame; fragments are reassembled on our side. + wifi_sniffer_set_snaplen(SPI_WIFI_SNIFFER_FRAME_MAX); + + // Persist to the SD card by default, whoever starts the sniffer (UI, console, + // ...). A caller that already opened an explicit file keeps its own path. + if (s_capture_stream == NULL) { + char path[WIFI_SNIFFER_PATH_MAX]; + auto_capture_path(type, monitor_mode, path, sizeof(path)); + wifi_sniffer_start_capture(path); + } + s_session_id = spi_session_start( SPI_ID_WIFI_APP_SNIFFER, payload, sizeof(payload), session_stream_cb, session_lost_cb); if (s_session_id == SPI_SESSION_INVALID_ID) { + wifi_sniffer_stop_capture(); s_stream_cb = NULL; led_signal_error(); return false; @@ -111,6 +258,7 @@ void wifi_sniffer_stop(void) { s_session_id = SPI_SESSION_INVALID_ID; } wifi_sniffer_stop_capture(); + reasm_reset(); s_stream_cb = NULL; } @@ -142,11 +290,25 @@ bool wifi_sniffer_start_capture(const char *path) { storage_stream_close(s_capture_stream); s_capture_stream = NULL; } + ensure_parent_dir(path); + s_capture_stream = storage_stream_open(path, "wb"); if (s_capture_stream == NULL) { ESP_LOGE(TAG, "Failed to open capture file: %s", path); return false; } + + const pcap_global_header_t header = { + .magic_number = PCAP_MAGIC_NUMBER, + .version_major = PCAP_VERSION_MAJOR, + .version_minor = PCAP_VERSION_MINOR, + .thiszone = 0, + .sigfigs = 0, + .snaplen = WIFI_SNIFFER_PCAP_SNAPLEN, + .network = PCAP_LINK_TYPE_802_11_RADIOTAP, + }; + storage_stream_write(s_capture_stream, &header, sizeof(header)); + ESP_LOGI(TAG, "Capture started: %s", path); return true; } diff --git a/firmware_p4/components/Drivers/audio_i2s/README.md b/firmware_p4/components/Drivers/audio_i2s/README.md new file mode 100644 index 000000000..1f15ad60c --- /dev/null +++ b/firmware_p4/components/Drivers/audio_i2s/README.md @@ -0,0 +1,7 @@ +# Audio I2S - P4 (MAX98357 + PDM mic) + +Documentation for this component lives in the project docs hub (single source of truth): + +- [docs/audio_i2s/README.md](../../../../docs/audio_i2s/README.md) + +This file is only a pointer: edit the docs there to keep things from drifting. diff --git a/firmware_p4/components/Drivers/bq25896/README.md b/firmware_p4/components/Drivers/bq25896/README.md new file mode 100644 index 000000000..174bd8f00 --- /dev/null +++ b/firmware_p4/components/Drivers/bq25896/README.md @@ -0,0 +1,7 @@ +# BQ25896 - P4 (charger / PMIC) + +Documentation for this component lives in the project docs hub (single source of truth): + +- [docs/bq25896/README.md](../../../../docs/bq25896/README.md) + +This file is only a pointer: edit the docs there to keep things from drifting. diff --git a/firmware_p4/components/Drivers/cc1101/cc1101.c b/firmware_p4/components/Drivers/cc1101/cc1101.c index ea77ff09f..47d1b0f7a 100644 --- a/firmware_p4/components/Drivers/cc1101/cc1101.c +++ b/firmware_p4/components/Drivers/cc1101/cc1101.c @@ -49,6 +49,8 @@ #define CC1101_MOD_4FSK 3 #define CC1101_MOD_MSK 4 +#define CC1101_BUS_LOCK_TIMEOUT_MS 1000 + static const char *TAG = "CC1101_DRIVER"; static spi_device_handle_t s_cc1101_spi = NULL; @@ -133,6 +135,13 @@ static void cc1101_enable_sniffer_mode(uint32_t freq_hz, uint8_t modulation) { cc1101_strobe(CC1101_SRX); } +static void cc1101_txn(spi_transaction_t *t) { + bool locked = spi_bus_lock_take(CC1101_BUS_LOCK_TIMEOUT_MS); + spi_device_transmit(s_cc1101_spi, t); + if (locked) + spi_bus_lock_give(); +} + void cc1101_write_burst(uint8_t reg, const uint8_t *buf, uint8_t len) { if (s_cc1101_spi == NULL) return; @@ -150,7 +159,7 @@ void cc1101_write_burst(uint8_t reg, const uint8_t *buf, uint8_t len) { t.tx_buffer = tx_buf; t.rx_buffer = NULL; - spi_device_transmit(s_cc1101_spi, &t); + cc1101_txn(&t); free(tx_buf); } @@ -164,7 +173,7 @@ void cc1101_write_reg(uint8_t reg, uint8_t val) { t.flags = SPI_TRANS_USE_TXDATA; t.tx_data[0] = reg; t.tx_data[1] = val; - spi_device_transmit(s_cc1101_spi, &t); + cc1101_txn(&t); } uint8_t cc1101_read_reg(uint8_t reg) { @@ -176,7 +185,7 @@ uint8_t cc1101_read_reg(uint8_t reg) { t.flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_USE_RXDATA; t.tx_data[0] = 0x80 | reg; // Read bit t.tx_data[1] = 0x00; - spi_device_transmit(s_cc1101_spi, &t); + cc1101_txn(&t); return t.rx_data[1]; } @@ -203,7 +212,7 @@ void cc1101_read_burst(uint8_t reg, uint8_t *buf, uint8_t len) { t.tx_buffer = tx_buf; t.rx_buffer = rx_buf; - spi_device_transmit(s_cc1101_spi, &t); + cc1101_txn(&t); memcpy(buf, &rx_buf[1], len); @@ -219,7 +228,7 @@ void cc1101_strobe(uint8_t cmd) { t.length = 8; t.flags = SPI_TRANS_USE_TXDATA; t.tx_data[0] = cmd; - spi_device_transmit(s_cc1101_spi, &t); + cc1101_txn(&t); } void cc1101_calibrate(void) { diff --git a/firmware_p4/components/Drivers/drv2605l/README.md b/firmware_p4/components/Drivers/drv2605l/README.md new file mode 100644 index 000000000..bba71fdbd --- /dev/null +++ b/firmware_p4/components/Drivers/drv2605l/README.md @@ -0,0 +1,7 @@ +# DRV2605L - P4 (haptic driver) + +Documentation for this component lives in the project docs hub (single source of truth): + +- [docs/drv2605l/README.md](../../../../docs/drv2605l/README.md) + +This file is only a pointer: edit the docs there to keep things from drifting. diff --git a/firmware_p4/components/Drivers/i2c_init/README.md b/firmware_p4/components/Drivers/i2c_init/README.md new file mode 100644 index 000000000..822267d8b --- /dev/null +++ b/firmware_p4/components/Drivers/i2c_init/README.md @@ -0,0 +1,7 @@ +# I2C Init - P4 + +Documentation for this component lives in the project docs hub (single source of truth): + +- [docs/i2c_init/README.md](../../../../docs/i2c_init/README.md) + +This file is only a pointer: edit the docs there to keep things from drifting. diff --git a/firmware_p4/components/Drivers/led/README.md b/firmware_p4/components/Drivers/led/README.md new file mode 100644 index 000000000..45215fb1e --- /dev/null +++ b/firmware_p4/components/Drivers/led/README.md @@ -0,0 +1,7 @@ +# LED - P4 (LP5816 status LED) + +Documentation for this component lives in the project docs hub (single source of truth): + +- [docs/led/README.md](../../../../docs/led/README.md) + +This file is only a pointer: edit the docs there to keep things from drifting. diff --git a/firmware_p4/components/Drivers/pins/README.md b/firmware_p4/components/Drivers/pins/README.md new file mode 100644 index 000000000..a66dbef0f --- /dev/null +++ b/firmware_p4/components/Drivers/pins/README.md @@ -0,0 +1,7 @@ +# Pins - P4 (GPIO map) + +Documentation for this component lives in the project docs hub (single source of truth): + +- [docs/pins/README.md](../../../../docs/pins/README.md) + +This file is only a pointer: edit the docs there to keep things from drifting. diff --git a/firmware_p4/components/Drivers/spi/include/spi.h b/firmware_p4/components/Drivers/spi/include/spi.h index 31234da99..90140709b 100644 --- a/firmware_p4/components/Drivers/spi/include/spi.h +++ b/firmware_p4/components/Drivers/spi/include/spi.h @@ -20,11 +20,14 @@ extern "C" { #endif -#include +#include #include +#include -#include "esp_err.h" #include "driver/spi_master.h" +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" /** * @brief Registered SPI device identifiers. @@ -123,6 +126,36 @@ esp_err_t spi_transmit(spi_device_id_t id, const uint8_t *data, size_t len); */ esp_err_t spi_bus_deinit(spi_host_device_t host); +/** + * @brief Take the shared SPI3 bus lock. + * + * SPI3 is shared by the ST7789 display and the SX1262 LoRa radio. Both must + * hold this lock around their transactions so a radio transfer never overlaps + * or starves a display flush (which would stall the LVGL renderer). Callers use + * a bounded timeout and proceed on failure: the ESP-IDF per-bus lock still + * serializes the actual transfers, so a timeout only loses fairness, never + * correctness. The lock is created lazily by spi_bus_init(SPI3_HOST); before + * that this is a no-op returning true. + * + * @param timeout_ms Milliseconds to wait for the lock. + * @return true if the lock was taken (caller must give it), false on timeout. + */ +bool spi_bus_lock_take(uint32_t timeout_ms); + +/** + * @brief Release the shared SPI3 bus lock from task context. + */ +void spi_bus_lock_give(void); + +/** + * @brief Release the shared SPI3 bus lock from an ISR (e.g. the LCD + * color-transfer-done callback). + * + * @param hpw Set to pdTRUE if a higher-priority task was woken; pass to + * portYIELD_FROM_ISR() at the end of the ISR. May be NULL. + */ +void spi_bus_lock_give_from_isr(BaseType_t *hpw); + #ifdef __cplusplus } #endif diff --git a/firmware_p4/components/Drivers/spi/spi.c b/firmware_p4/components/Drivers/spi/spi.c index bcd7aecae..84c7ecfd3 100644 --- a/firmware_p4/components/Drivers/spi/spi.c +++ b/firmware_p4/components/Drivers/spi/spi.c @@ -23,11 +23,13 @@ static const char *TAG = "SPI_BUS"; -#define SPI_MAX_TRANSFER_SIZE 32768 +#define SPI_MAX_TRANSFER_SIZE 49152 static spi_device_handle_t s_device_handles[SPI_DEVICE_MAX] = {NULL}; static bool s_bus_active[SOC_SPI_PERIPH_NUM] = {false}; +static SemaphoreHandle_t s_spi3_lock = NULL; + esp_err_t spi_bus_init(spi_host_device_t host, int mosi, int miso, int sclk) { if (host >= SOC_SPI_PERIPH_NUM) { return ESP_ERR_INVALID_ARG; @@ -49,11 +51,38 @@ esp_err_t spi_bus_init(spi_host_device_t host, int mosi, int miso, int sclk) { if (ret == ESP_OK) { s_bus_active[host] = true; ESP_LOGI(TAG, "Bus host %d initialized", host); + if (host == SPI3_HOST && s_spi3_lock == NULL) { + s_spi3_lock = xSemaphoreCreateBinary(); + if (s_spi3_lock != NULL) { + xSemaphoreGive(s_spi3_lock); + } else { + ESP_LOGE(TAG, "Failed to create SPI3 bus lock"); + } + } } return ret; } +bool spi_bus_lock_take(uint32_t timeout_ms) { + if (s_spi3_lock == NULL) { + return true; + } + return xSemaphoreTake(s_spi3_lock, pdMS_TO_TICKS(timeout_ms)) == pdTRUE; +} + +void spi_bus_lock_give(void) { + if (s_spi3_lock != NULL) { + xSemaphoreGive(s_spi3_lock); + } +} + +void spi_bus_lock_give_from_isr(BaseType_t *hpw) { + if (s_spi3_lock != NULL) { + xSemaphoreGiveFromISR(s_spi3_lock, hpw); + } +} + esp_err_t spi_init(void) { return spi_bus_init(SPI3_HOST, GPIO_SPI_MOSI_PIN, GPIO_SPI_MISO_PIN, GPIO_SPI_SCLK_PIN); } diff --git a/firmware_p4/components/Drivers/st7789/include/st7789.h b/firmware_p4/components/Drivers/st7789/include/st7789.h index 8b3f9c1b6..0a3e9afca 100644 --- a/firmware_p4/components/Drivers/st7789/include/st7789.h +++ b/firmware_p4/components/Drivers/st7789/include/st7789.h @@ -34,8 +34,18 @@ extern "C" { * (garbled/white images) above 20 MHz. */ #define LCD_PIXEL_CLOCK_HZ (20 * 1000 * 1000) -#define LCD_H_RES 240 -#define LCD_V_RES 320 + +/** + * @brief Drive strength for the SPI3 SCLK/MOSI pins that feed the display FFC. + * The FFC is long/capacitive and unterminated: at the IDF default the + * edges barely settle at 20 MHz, so a radio's EMI corrupts the still- + * settling edge and garbles long transfers. Strongest drive (CAP_3) makes + * the edges settle faster for more timing margin. (A weaker cap starves + * the FFC so hard the panel will not even init, so do not lower this.) + */ +#define LCD_SPI_DRIVE_CAP GPIO_DRIVE_CAP_3 +#define LCD_H_RES 240 +#define LCD_V_RES 320 /** * @brief Physical panel dimensions (used by lvgl_glue / esp_lvgl_port). Same * values as LCD_H/V_RES here since the panel runs in fixed portrait. diff --git a/firmware_p4/components/Drivers/st7789/st7789.c b/firmware_p4/components/Drivers/st7789/st7789.c index b2e3615d5..cae51473f 100644 --- a/firmware_p4/components/Drivers/st7789/st7789.c +++ b/firmware_p4/components/Drivers/st7789/st7789.c @@ -20,6 +20,7 @@ #include #include "esp_log.h" +#include "driver/gpio.h" #include "driver/ledc.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" @@ -265,6 +266,12 @@ esp_err_t st7789_init(void) { return ret; } + // Drive the SCLK/MOSI edges harder on the long, unterminated display FFC so + // they settle within the 20 MHz bit period; at the default cap the edge is + // still settling when a radio's EMI hits it and long transfers garble. + gpio_set_drive_capability(GPIO_SPI_SCLK_PIN, LCD_SPI_DRIVE_CAP); + gpio_set_drive_capability(GPIO_SPI_MOSI_PIN, LCD_SPI_DRIVE_CAP); + esp_lcd_panel_dev_config_t panel_config = { .reset_gpio_num = GPIO_ST7789_RST_PIN, .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, diff --git a/firmware_p4/components/Drivers/sx1262/README.md b/firmware_p4/components/Drivers/sx1262/README.md new file mode 100644 index 000000000..eb25d406e --- /dev/null +++ b/firmware_p4/components/Drivers/sx1262/README.md @@ -0,0 +1,7 @@ +# SX1262 LoRa Transceiver Driver + +Documentation for this component lives in the project docs hub (single source of truth): + +- [docs/sx1262/README.md](../../../../docs/sx1262/README.md) + +This file is only a pointer: edit the docs there to keep things from drifting. diff --git a/firmware_p4/components/Drivers/sx1262/sx1262.c b/firmware_p4/components/Drivers/sx1262/sx1262.c index 5eaf18aef..4277e5555 100644 --- a/firmware_p4/components/Drivers/sx1262/sx1262.c +++ b/firmware_p4/components/Drivers/sx1262/sx1262.c @@ -41,6 +41,10 @@ static TaskHandle_t s_stop_caller_handle = NULL; #define SX1262_IRQ_TASK_PRIO SYS_PRIO_REALTIME #define SX1262_IRQ_TASK_CORE SYS_CORE_RADIO +#define SX1262_INIT_MAX_ATTEMPTS 3 +#define SX1262_IRQ_FAIL_RECOVER 5 + +static esp_err_t sx1262_hw_bringup(const sx1262_config_t *config); static esp_err_t validate_hal(const sx1262_hal_t *hal); static esp_err_t validate_config(const sx1262_config_t *config); static esp_err_t hw_reset(sx1262_hal_t *hal); @@ -67,9 +71,42 @@ esp_err_t sx1262_init(const sx1262_config_t *config) { } memcpy(&s_config, config, sizeof(sx1262_config_t)); + + ret = ESP_ERR_INVALID_STATE; + for (int attempt = 1; attempt <= SX1262_INIT_MAX_ATTEMPTS; attempt++) { + ret = sx1262_hw_bringup(config); + if (ret == ESP_OK) { + break; + } + ESP_LOGW(TAG, + "Init attempt %d/%d failed (%s) — retrying", + attempt, + SX1262_INIT_MAX_ATTEMPTS, + esp_err_to_name(ret)); + } + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Init failed after %d attempts", SX1262_INIT_MAX_ATTEMPTS); + return ret; + } + + sx1262_irq_init(&s_config.hal, &s_config, &s_callbacks); + + sx1262_radio_init(&s_config.hal, &s_config); + + ESP_LOGI(TAG, + "Initialized — freq: %lu, sf: %d, bw: 0x%02X, power: %d dBm", + (unsigned long)config->frequency_hz, + config->sf, + config->bw, + config->tx_power_dbm); + + return ESP_OK; +} + +static esp_err_t sx1262_hw_bringup(const sx1262_config_t *config) { sx1262_hal_t *hal = &s_config.hal; - ret = hw_reset(hal); + esp_err_t ret = hw_reset(hal); if (ret != ESP_OK) { return ret; } @@ -149,10 +186,6 @@ esp_err_t sx1262_init(const sx1262_config_t *config) { return ret; } - sx1262_irq_init(hal, &s_config, &s_callbacks); - - sx1262_radio_init(hal, &s_config); - uint8_t status = 0; ret = sx1262_get_status(&status); if (ret != ESP_OK) { @@ -160,18 +193,20 @@ esp_err_t sx1262_init(const sx1262_config_t *config) { } uint8_t chip_mode = (status & SX1262_STATUS_CHIP_MODE_MASK) >> SX1262_STATUS_CHIP_MODE_SHIFT; + if (chip_mode != SX1262_CHIP_MODE_STDBY_RC) { + ESP_LOGW(TAG, + "Bring-up left chip in bad state: status=0x%02X chip_mode=%d (want %d)", + status, + chip_mode, + SX1262_CHIP_MODE_STDBY_RC); + return ESP_ERR_INVALID_STATE; + } ESP_LOGI(TAG, "Init OK — status: 0x%02X, chip_mode: %d (STDBY_RC=%d)", status, chip_mode, SX1262_CHIP_MODE_STDBY_RC); - ESP_LOGI(TAG, - "Initialized — freq: %lu, sf: %d, bw: 0x%02X, power: %d dBm", - (unsigned long)config->frequency_hz, - config->sf, - config->bw, - config->tx_power_dbm); return ESP_OK; } @@ -444,10 +479,16 @@ esp_err_t sx1262_receive_single(uint32_t timeout_ms) { } esp_err_t sx1262_receive_continuous(void) { + if (!s_is_running) { + return ESP_ERR_INVALID_STATE; + } return sx1262_radio_receive_continuous(); } void sx1262_stop_rx(void) { + if (!s_is_running) { + return; + } sx1262_radio_stop_rx(); } esp_err_t sx1262_cad_start(void) { @@ -465,14 +506,34 @@ esp_err_t sx1262_set_rx_duty_cycle(uint32_t rx_ms, uint32_t sleep_ms) { return sx1262_radio_set_rx_duty_cycle(rx_ms, sleep_ms); } +static void sx1262_recover(void) { + ESP_LOGW(TAG, "SX1262 wedged — hardware reset + reconfigure"); + if (sx1262_hw_bringup(&s_config) != ESP_OK) { + ESP_LOGE(TAG, "recover: bring-up failed"); + return; + } + sx1262_irq_init(&s_config.hal, &s_config, &s_callbacks); + sx1262_radio_init(&s_config.hal, &s_config); + (void)sx1262_receive_continuous(); + ESP_LOGI(TAG, "SX1262 recovered"); +} + static void irq_task(void *arg) { (void)arg; sx1262_hal_t *hal = &s_config.hal; + int fail_streak = 0; ESP_LOGI(TAG, "IRQ task running"); while (s_is_running) { - sx1262_irq_process(); + if (sx1262_irq_process() != ESP_OK) { + if (++fail_streak >= SX1262_IRQ_FAIL_RECOVER) { + sx1262_recover(); + fail_streak = 0; + } + } else { + fail_streak = 0; + } hal->delay_ms(hal->ctx, 10); } @@ -610,7 +671,7 @@ static esp_err_t apply_workaround_w2(sx1262_hal_t *hal) { return ret; } - val |= 0x1E; /* Set bits 4:1 to 1111 */ + val |= 0x1E; ret = sx1262_cmd_write_register(hal, SX1262_REG_TX_CLAMP_CONFIG, &val, 1); if (ret != ESP_OK) { diff --git a/firmware_p4/components/Drivers/sx1262/sx1262_hal.c b/firmware_p4/components/Drivers/sx1262/sx1262_hal.c index a3960c873..1fc259bd7 100644 --- a/firmware_p4/components/Drivers/sx1262/sx1262_hal.c +++ b/firmware_p4/components/Drivers/sx1262/sx1262_hal.c @@ -25,8 +25,11 @@ #include "freertos/semphr.h" #include "pin_def.h" +#include "spi.h" #include "sx1262_regs.h" +#define SPI3_BUS_LOCK_TIMEOUT_MS 1000 + #define PIN_SCK GPIO_LORA_SCLK_PIN #define PIN_MOSI GPIO_LORA_MOSI_PIN #define PIN_MISO GPIO_LORA_MISO_PIN @@ -38,7 +41,7 @@ #define PIN_RXEN GPIO_LORA_RXEN_PIN #define SPI_HOST_ID SPI3_HOST -#define SPI_FREQ_HZ 8000000 +#define SPI_FREQ_HZ 4000000 #define SPI_MAX_TRANSFER 264 static const char *TAG = "SX1262_HAL_ESP32"; @@ -48,6 +51,7 @@ typedef struct { SemaphoreHandle_t spi_mutex; portMUX_TYPE critical_mux; bool is_initialized; + bool took_bus_lock; } hal_esp32_ctx_t; static hal_esp32_ctx_t s_ctx = { @@ -55,6 +59,7 @@ static hal_esp32_ctx_t s_ctx = { .spi_mutex = NULL, .critical_mux = portMUX_INITIALIZER_UNLOCKED, .is_initialized = false, + .took_bus_lock = false, }; static int hal_spi_transfer(void *ctx, const uint8_t *tx, uint8_t *rx, size_t len) { @@ -106,12 +111,17 @@ static uint32_t hal_get_tick_ms(void *ctx) { static void hal_lock(void *ctx) { hal_esp32_ctx_t *c = (hal_esp32_ctx_t *)ctx; xSemaphoreTake(c->spi_mutex, portMAX_DELAY); + c->took_bus_lock = spi_bus_lock_take(SPI3_BUS_LOCK_TIMEOUT_MS); spi_device_acquire_bus(c->spi, portMAX_DELAY); } static void hal_unlock(void *ctx) { hal_esp32_ctx_t *c = (hal_esp32_ctx_t *)ctx; spi_device_release_bus(c->spi); + if (c->took_bus_lock) { + spi_bus_lock_give(); + c->took_bus_lock = false; + } xSemaphoreGive(c->spi_mutex); } diff --git a/firmware_p4/components/Drivers/sx1262/sx1262_irq.c b/firmware_p4/components/Drivers/sx1262/sx1262_irq.c index e463311c4..1b643cb4e 100644 --- a/firmware_p4/components/Drivers/sx1262/sx1262_irq.c +++ b/firmware_p4/components/Drivers/sx1262/sx1262_irq.c @@ -102,7 +102,7 @@ esp_err_t sx1262_irq_process(void) { uint8_t next_head = (s_rx_head + 1) % SX1262_RX_RING_SIZE; if (next_head == s_rx_tail) { s_hal->exit_critical(s_hal->ctx); - ESP_LOGW(TAG, "Ring buffer full — packet discarded"); + ESP_LOGD(TAG, "Ring buffer full — packet discarded"); } else { memcpy(&s_rx_ring[s_rx_head], &pkt, sizeof(sx1262_packet_t)); s_rx_head = next_head; @@ -128,13 +128,13 @@ esp_err_t sx1262_irq_process(void) { } if ((irq_flags & SX1262_IRQ_CRC_ERR) && !(irq_flags & SX1262_IRQ_RX_DONE)) { - ESP_LOGW(TAG, "IRQ: CRC error (standalone)"); + ESP_LOGD(TAG, "IRQ: CRC error (standalone)"); if (s_cbs.on_error != NULL) { s_cbs.on_error(ESP_ERR_INVALID_CRC, s_cbs.cb_ctx); } } if ((irq_flags & SX1262_IRQ_HEADER_ERR) && !(irq_flags & SX1262_IRQ_RX_DONE)) { - ESP_LOGW(TAG, "IRQ: Header error (standalone)"); + ESP_LOGD(TAG, "IRQ: Header error (standalone)"); if (s_cbs.on_error != NULL) { s_cbs.on_error(ESP_FAIL, s_cbs.cb_ctx); } @@ -187,6 +187,8 @@ bool sx1262_irq_has_packet(void) { } static esp_err_t read_rx_packet(sx1262_packet_t *out_pkt, uint16_t irq_flags) { + bool errored = (irq_flags & (SX1262_IRQ_CRC_ERR | SX1262_IRQ_HEADER_ERR)) != 0; + uint8_t rx_buf_status[2] = {0}; esp_err_t ret = sx1262_cmd_read(s_hal, SX1262_OP_GET_RX_BUFFER_STATUS, rx_buf_status, 2); if (ret != ESP_OK) { @@ -204,7 +206,7 @@ static esp_err_t read_rx_packet(sx1262_packet_t *out_pkt, uint16_t irq_flags) { return ret; } - if (payload_len > 0) { + if (payload_len > 0 && !errored) { ret = sx1262_cmd_read_buffer(s_hal, buf_offset, out_pkt->buf, payload_len); if (ret != ESP_OK) { ESP_LOGE(TAG, "ReadBuffer failed"); @@ -212,7 +214,7 @@ static esp_err_t read_rx_packet(sx1262_packet_t *out_pkt, uint16_t irq_flags) { } } - out_pkt->len = payload_len; + out_pkt->len = errored ? 0 : payload_len; out_pkt->rssi_pkt_dbm = -(int16_t)(pkt_status[0] / 2); out_pkt->snr_pkt_db = (int8_t)pkt_status[1] / 4; out_pkt->signal_rssi_dbm = -(int16_t)(pkt_status[2] / 2); diff --git a/firmware_p4/components/Drivers/sx1262/sx1262_radio.c b/firmware_p4/components/Drivers/sx1262/sx1262_radio.c index 40bcb9e43..0341532f0 100644 --- a/firmware_p4/components/Drivers/sx1262/sx1262_radio.c +++ b/firmware_p4/components/Drivers/sx1262/sx1262_radio.c @@ -209,7 +209,7 @@ esp_err_t sx1262_radio_receive_continuous(void) { return ret; } - ESP_LOGI(TAG, "RX continuous started"); + ESP_LOGD(TAG, "RX continuous started"); return ESP_OK; } diff --git a/firmware_p4/components/Drivers/tusb_desc/include/tusb_desc.h b/firmware_p4/components/Drivers/tusb_desc/include/tusb_desc.h index c4966e7b5..dc91a957b 100644 --- a/firmware_p4/components/Drivers/tusb_desc/include/tusb_desc.h +++ b/firmware_p4/components/Drivers/tusb_desc/include/tusb_desc.h @@ -27,15 +27,24 @@ extern "C" { // Composite device interfaces: HID (BadUSB) + CDC-ACM (companion host link). // CDC uses two interfaces (comm + data), so the data interface is CDC+1. -#define TUSB_DESC_ITF_NUM_HID 0 -#define TUSB_DESC_ITF_NUM_CDC 1 // comm; data interface = 2 +#define TUSB_DESC_ITF_NUM_HID 0 +#define TUSB_DESC_ITF_NUM_CDC 1 // comm; data interface = 2 +#if CFG_TUD_MSC +#define TUSB_DESC_ITF_NUM_MSC 3 // mass storage (SD as USB drive), after CDC data (2) +#define TUSB_DESC_ITF_NUM_TOTAL 4 +#else #define TUSB_DESC_ITF_NUM_TOTAL 3 +#endif // Endpoint addresses #define TUSB_DESC_EP_HID_IN 0x81 #define TUSB_DESC_EP_CDC_NOTIF 0x82 #define TUSB_DESC_EP_CDC_OUT 0x03 #define TUSB_DESC_EP_CDC_IN 0x83 +#if CFG_TUD_MSC +#define TUSB_DESC_EP_MSC_OUT 0x04 +#define TUSB_DESC_EP_MSC_IN 0x84 +#endif /** * @brief Initialize the TinyUSB driver with HID composite descriptors. @@ -52,6 +61,18 @@ extern "C" { */ esp_err_t busb_init(void); +/** + * @brief Advertise (or hide) the mass-storage interface on the composite. + * + * MSC must only be exposed while the SD is actually handed to the USB host, + * because the storage-backed SCSI callbacks assume an initialized handle. Off by + * default; enter/exit mass-storage mode via this before/after switching the mux. + * If TinyUSB is already up, the host is re-enumerated so it re-reads the config. + * + * @param exposed true while mass-storage mode is active; false otherwise. + */ +void busb_set_msc_exposed(bool exposed); + /** * @brief Configure the USB-C data mux (TS3USB221) and default it to the UART * bridge. diff --git a/firmware_p4/components/Drivers/tusb_desc/tusb_desc.c b/firmware_p4/components/Drivers/tusb_desc/tusb_desc.c index db6153e94..168fb2ed8 100644 --- a/firmware_p4/components/Drivers/tusb_desc/tusb_desc.c +++ b/firmware_p4/components/Drivers/tusb_desc/tusb_desc.c @@ -19,6 +19,8 @@ #include "esp_log.h" #include "driver/gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" #include "pin_def.h" #include "soc/usb_dwc_struct.h" #include "tinyusb.h" @@ -38,6 +40,7 @@ static const char *TAG = "TUSB_DESC"; // USB Configuration #define USB_MAX_POWER_MA 100 #define USB_HID_POLL_INTERVAL_MS 1 +#define BUSB_REENUM_DELAY_MS 100 // detach window so the host notices the re-enumeration // String descriptor indices #define STR_IDX_LANGID 0 @@ -45,8 +48,14 @@ static const char *TAG = "TUSB_DESC"; #define STR_IDX_PRODUCT 2 #define STR_IDX_SERIAL 3 #define STR_IDX_CDC 4 +#if CFG_TUD_MSC +#define STR_IDX_MSC 5 +#endif -#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN + TUD_CDC_DESC_LEN) +// MSC is a runtime-selected variant, exposed only in mass-storage mode: an +// unbacked MSC LUN crashes the TinyUSB task on the host's first SCSI command. +#define ITF_NUM_BASE 3 // HID + CDC (comm + data) +#define CONFIG_LEN_BASE (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN + TUD_CDC_DESC_LEN) // CDC data (bulk) endpoint max packet size is speed-dependent: USB requires it // to be EXACTLY 512 at High Speed and 8/16/32/64 at Full Speed. The P4 USB is @@ -83,30 +92,43 @@ static const uint8_t s_desc_hid_report[] = { // Configuration Descriptor — composite: HID (BadUSB) + CDC-ACM (companion link). // Identical for both speeds except the CDC bulk endpoint size (see above). -#define CONFIG_DESCRIPTOR(cdc_ep_size) \ - TUD_CONFIG_DESCRIPTOR(1, \ - TUSB_DESC_ITF_NUM_TOTAL, \ - 0, \ - CONFIG_TOTAL_LEN, \ - TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, \ - USB_MAX_POWER_MA), \ - TUD_HID_DESCRIPTOR(TUSB_DESC_ITF_NUM_HID, \ - 0, \ - HID_ITF_PROTOCOL_KEYBOARD, \ - sizeof(s_desc_hid_report), \ - TUSB_DESC_EP_HID_IN, \ - CFG_TUD_HID_EP_BUFSIZE, \ - USB_HID_POLL_INTERVAL_MS), \ - TUD_CDC_DESCRIPTOR(TUSB_DESC_ITF_NUM_CDC, \ - STR_IDX_CDC, \ - TUSB_DESC_EP_CDC_NOTIF, \ - 8, \ - TUSB_DESC_EP_CDC_OUT, \ - TUSB_DESC_EP_CDC_IN, \ +#define HID_CDC_BLOCK(itf_total, total_len, cdc_ep_size) \ + TUD_CONFIG_DESCRIPTOR( \ + 1, (itf_total), 0, (total_len), TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, USB_MAX_POWER_MA), \ + TUD_HID_DESCRIPTOR(TUSB_DESC_ITF_NUM_HID, \ + 0, \ + HID_ITF_PROTOCOL_KEYBOARD, \ + sizeof(s_desc_hid_report), \ + TUSB_DESC_EP_HID_IN, \ + CFG_TUD_HID_EP_BUFSIZE, \ + USB_HID_POLL_INTERVAL_MS), \ + TUD_CDC_DESCRIPTOR(TUSB_DESC_ITF_NUM_CDC, \ + STR_IDX_CDC, \ + TUSB_DESC_EP_CDC_NOTIF, \ + 8, \ + TUSB_DESC_EP_CDC_OUT, \ + TUSB_DESC_EP_CDC_IN, \ (cdc_ep_size)) -static const uint8_t s_desc_configuration_hs[] = {CONFIG_DESCRIPTOR(CDC_EP_SIZE_HS)}; -static const uint8_t s_desc_configuration_fs[] = {CONFIG_DESCRIPTOR(CDC_EP_SIZE_FS)}; +static const uint8_t s_desc_configuration_hs[] = { + HID_CDC_BLOCK(ITF_NUM_BASE, CONFIG_LEN_BASE, CDC_EP_SIZE_HS)}; +static const uint8_t s_desc_configuration_fs[] = { + HID_CDC_BLOCK(ITF_NUM_BASE, CONFIG_LEN_BASE, CDC_EP_SIZE_FS)}; + +#if CFG_TUD_MSC +#define ITF_NUM_MSC_TOTAL 4 +#define CONFIG_LEN_MSC (CONFIG_LEN_BASE + TUD_MSC_DESC_LEN) +#define MSC_INTERFACE(ep_size) \ + TUD_MSC_DESCRIPTOR( \ + TUSB_DESC_ITF_NUM_MSC, STR_IDX_MSC, TUSB_DESC_EP_MSC_OUT, TUSB_DESC_EP_MSC_IN, (ep_size)) + +static const uint8_t s_desc_configuration_msc_hs[] = { + HID_CDC_BLOCK(ITF_NUM_MSC_TOTAL, CONFIG_LEN_MSC, CDC_EP_SIZE_HS), + MSC_INTERFACE(CDC_EP_SIZE_HS)}; +static const uint8_t s_desc_configuration_msc_fs[] = { + HID_CDC_BLOCK(ITF_NUM_MSC_TOTAL, CONFIG_LEN_MSC, CDC_EP_SIZE_FS), + MSC_INTERFACE(CDC_EP_SIZE_FS)}; +#endif // String Descriptors static const char *s_string_desc_arr[] = { @@ -115,12 +137,19 @@ static const char *s_string_desc_arr[] = { "BadUSB Device", // Product "123456", // Serial Number "TentacleOS Companion", // CDC interface (host link) +#if CFG_TUD_MSC + "TentacleOS SD", +#endif }; #define STRING_DESC_COUNT (sizeof(s_string_desc_arr) / sizeof(s_string_desc_arr[0])) static uint16_t s_desc_str_buf[32]; +// Whether the composite currently advertises the MSC interface (mass-storage +// mode). Off by default so plain native-USB bring-ups stay HID+CDC only. +static volatile bool s_msc_exposed = false; + // TinyUSB Descriptor Callbacks const uint8_t *tud_descriptor_device_cb(void) { @@ -131,7 +160,13 @@ const uint8_t *tud_descriptor_configuration_cb(uint8_t index) { (void)index; // Serve the descriptor whose CDC bulk endpoint size matches the negotiated // link speed (512 at HS, 64 at FS), so tu_edpt_validate accepts the config. - return (tud_speed_get() == TUSB_SPEED_HIGH) ? s_desc_configuration_hs : s_desc_configuration_fs; + bool high_speed = (tud_speed_get() == TUSB_SPEED_HIGH); +#if CFG_TUD_MSC + if (s_msc_exposed) { + return high_speed ? s_desc_configuration_msc_hs : s_desc_configuration_msc_fs; + } +#endif + return high_speed ? s_desc_configuration_hs : s_desc_configuration_fs; } const uint16_t *tud_descriptor_string_cb(uint8_t index, uint16_t langid) { @@ -195,10 +230,11 @@ static void force_hs_otg_session_valid(void) { USB_DWC_HS.gotgctl_reg = otg; } +static bool s_installed = false; + esp_err_t busb_init(void) { // HID (BadUSB) and CDC (companion) share one TinyUSB install — whoever calls // first brings the composite up; later calls are no-ops. - static bool s_installed = false; if (s_installed) { return ESP_OK; } @@ -248,6 +284,27 @@ esp_err_t busb_init(void) { return ESP_OK; } +void busb_set_msc_exposed(bool exposed) { +#if CFG_TUD_MSC + if (s_msc_exposed == exposed) { + return; + } + // Not up yet: the flag alone decides what the first enumeration advertises. + if (!s_installed) { + s_msc_exposed = exposed; + return; + } + // Already enumerated with the other layout; drop off the bus, swap the + // advertised config, and re-attach so the host re-reads the descriptor. + tud_disconnect(); + vTaskDelay(pdMS_TO_TICKS(BUSB_REENUM_DELAY_MS)); + s_msc_exposed = exposed; + tud_connect(); +#else + (void)exposed; +#endif +} + // USB-C data mux (TS3USB221) on GPIO_USB_MUX_SEL_PIN. LOW routes the single // Type-C to the CP2105 USB-UART bridge (serial console / flashing); HIGH routes // it to the P4 native USB PHY (TinyUSB HID + CDC). The two share one connector, diff --git a/firmware_p4/components/Drivers/ys_rfid2/README.md b/firmware_p4/components/Drivers/ys_rfid2/README.md new file mode 100644 index 000000000..1de11f4f5 --- /dev/null +++ b/firmware_p4/components/Drivers/ys_rfid2/README.md @@ -0,0 +1,7 @@ +# YS-RFID2 - P4 (UART RFID reader) + +Documentation for this component lives in the project docs hub (single source of truth): + +- [docs/ys_rfid2/README.md](../../../../docs/ys_rfid2/README.md) + +This file is only a pointer: edit the docs there to keep things from drifting. diff --git a/firmware_p4/components/Service/CMakeLists.txt b/firmware_p4/components/Service/CMakeLists.txt index ebcec3859..943cb25b0 100644 --- a/firmware_p4/components/Service/CMakeLists.txt +++ b/firmware_p4/components/Service/CMakeLists.txt @@ -32,6 +32,8 @@ file(GLOB_RECURSE BRIDGE_MANAGER_SRCS "bridge_manager/*.c") file(GLOB_RECURSE BT_SERVICE_SRCS "bluetooth/*.c") file(GLOB_RECURSE HOST_LINK_SRCS "host_link/*.c") file(GLOB_RECURSE STORAGE_API_SRCS "storage_api/*.c") +# storage_vfs/*.c includes usb_msc.c (expose the SD as a USB drive). NOTE: adding +# a new .c under storage_vfs requires a CMake reconfigure to re-run this glob. file(GLOB_RECURSE STORAGE_VFS_SRCS "storage_vfs/*.c") file(GLOB_RECURSE STORAGE_ASSETS_SRCS "storage_assets/*.c") diff --git a/firmware_p4/components/Service/bridge_manager/README.md b/firmware_p4/components/Service/bridge_manager/README.md new file mode 100644 index 000000000..6bc1e4815 --- /dev/null +++ b/firmware_p4/components/Service/bridge_manager/README.md @@ -0,0 +1,7 @@ +# Bridge Manager - P4 (SPI bridge lifecycle / link manager) + +Documentation for this component lives in the project docs hub (single source of truth): + +- [docs/bridge_manager/README.md](../../../../docs/bridge_manager/README.md) + +This file is only a pointer: edit the docs there to keep things from drifting. diff --git a/firmware_p4/components/Service/console/commands/cmd_system.c b/firmware_p4/components/Service/console/commands/cmd_system.c index 5594b2264..84f997143 100644 --- a/firmware_p4/components/Service/console/commands/cmd_system.c +++ b/firmware_p4/components/Service/console/commands/cmd_system.c @@ -59,7 +59,7 @@ static int cmd_c5(int argc, char **argv) { return 1; } if (transport == SPI_OTA_TRANSPORT_UART) { - c5_flasher_init(); // UART1 is only needed when the bytes travel over UART + c5_flasher_init(); } esp_err_t r = c5_flasher_update(NULL, 0, transport); printf("C5 OTA (%s): %s\n", @@ -145,8 +145,7 @@ static int cmd_firstboot(int argc, char **argv) { nvs_commit(h); nvs_close(h); } - printf("First-boot wizard + screen tips cleared. Restarting...\n"); - esp_restart(); + printf("First-boot wizard + screen tips cleared. Will run on the next reset.\n"); return 0; } @@ -254,7 +253,6 @@ static const stack_alloc_t STACK_ALLOC[] = { {"hl_log", 6144}, {"SysMonitor", 4096}, {"wifi_status", 4096}, - {"tos_log", 4096}, {"hl_ble", 4096}, }; #define STACK_ALLOC_COUNT (sizeof(STACK_ALLOC) / sizeof(STACK_ALLOC[0])) @@ -432,7 +430,7 @@ void register_system_commands(void) { const esp_console_cmd_t cmd_firstboot_def = { .command = "firstboot", - .help = "Clear the first-boot onboarding flag and restart to run it again", + .help = "Clear the first-boot onboarding flag so it runs on the next reset", .hint = NULL, .func = &cmd_firstboot, }; diff --git a/firmware_p4/components/Service/ir/README.md b/firmware_p4/components/Service/ir/README.md new file mode 100644 index 000000000..df11c9f6f --- /dev/null +++ b/firmware_p4/components/Service/ir/README.md @@ -0,0 +1,7 @@ +# IR - P4 (infrared TX/RX) + +Documentation for this component lives in the project docs hub (single source of truth): + +- [docs/ir/README.md](../../../../docs/ir/README.md) + +This file is only a pointer: edit the docs there to keep things from drifting. diff --git a/firmware_p4/components/Service/lvgl/include/lvgl_glue.h b/firmware_p4/components/Service/lvgl/include/lvgl_glue.h index b84df61d2..26dab043e 100644 --- a/firmware_p4/components/Service/lvgl/include/lvgl_glue.h +++ b/firmware_p4/components/Service/lvgl/include/lvgl_glue.h @@ -61,6 +61,27 @@ bool lvgl_glue_lock(int timeout_ms); */ void lvgl_glue_unlock(void); +/** + * @brief Direct-draw (panel takeover) support for a full-screen app — e.g. the + * Game Boy emulator — that holds the LVGL lock and drives the ST7789 itself via + * raw esp_lcd_panel_draw_bitmap() calls. + * + * The ST7789 SPI is async: draw_bitmap queues the transfer and returns, so + * reusing a single blit buffer for the next strip while the previous strip's DMA + * is still reading it corrupts the image (horizontal noise). Between _begin() and + * _end(), the shared color-transfer-done ISR raises a semaphore instead of + * signalling LVGL's flush-ready; the app calls lvgl_glue_wait_flush() after each + * draw to block until that strip's DMA has finished, making single-buffer reuse + * safe. Call _begin() right after taking the LVGL lock and _end() before + * releasing it. Outside this window the ISR drives LVGL exactly as before. + */ +void lvgl_glue_direct_begin(void); +void lvgl_glue_direct_end(void); + +/** Block until the most recent esp_lcd color transfer's DMA completes (or the + * timeout elapses). Only meaningful between lvgl_glue_direct_begin/_end(). */ +void lvgl_glue_wait_flush(uint32_t timeout_ms); + /** * @brief Toggle between portrait (LV_DISPLAY_ROTATION_0) and landscape- * left (LV_DISPLAY_ROTATION_90). diff --git a/firmware_p4/components/Service/lvgl/lvgl_glue.c b/firmware_p4/components/Service/lvgl/lvgl_glue.c index 7e5573b97..cc549e7ce 100644 --- a/firmware_p4/components/Service/lvgl/lvgl_glue.c +++ b/firmware_p4/components/Service/lvgl/lvgl_glue.c @@ -8,20 +8,29 @@ #include "lvgl_glue.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" + #include "esp_heap_caps.h" +#include "esp_lcd_panel_io.h" #include "esp_lvgl_port.h" #include "esp_log.h" +#include "draw/lv_draw_buf_private.h" + +#include "spi.h" #include "st7789.h" #include "sys_prio.h" static const char *TAG = "LVGL_GLUE"; +#define SPI3_FLUSH_TIMEOUT_MS 50 + #define LVGL_PORT_TASK_PRIORITY SYS_PRIO_RENDER -#define LVGL_PORT_TASK_STACK (8 * 1024) +#define LVGL_PORT_TASK_STACK (16 * 1024) #define LVGL_PORT_MAX_SLEEP_MS 500 #define LVGL_PORT_TIMER_PERIOD_MS 5 -#define LVGL_BUF_LINES (LCD_PANEL_H / 8) +#define LVGL_BUF_LINES (LCD_PANEL_H / 4) #define ROTATION_LOCK_TIMEOUT_MS 2000 static bool s_ready = false; @@ -29,7 +38,52 @@ static bool s_landscape = false; static lv_display_t *s_disp = NULL; static volatile lvgl_glue_strip_cb_t s_capture_cb = NULL; +static SemaphoreHandle_t s_trans_done = NULL; +static volatile bool s_direct_mode = false; +static volatile bool s_flush_took_bus = false; + +#define DRAWBUF_PSRAM_THRESHOLD (48 * 1024) + +static void *draw_buf_psram_malloc(size_t size, lv_color_format_t cf) { + (void)cf; + size += LV_DRAW_BUF_ALIGN - 1; + void *p = NULL; + if (size > DRAWBUF_PSRAM_THRESHOLD) + p = heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + if (p == NULL) + p = heap_caps_malloc(size, MALLOC_CAP_DEFAULT); + return p; +} + +static void draw_buf_free(void *buf) { + heap_caps_free(buf); +} + +static bool +trans_done_cb(esp_lcd_panel_io_handle_t io, esp_lcd_panel_io_event_data_t *ed, void *ctx) { + (void)io; + (void)ed; + BaseType_t hp = pdFALSE; + // s_flush_took_bus marks an LVGL-driven flush (set in capture_flush_start_cb). + // Complete it even in direct mode, else lv_refr's wait_for_flushing() spins + // forever (disp->flushing never clears) and the SPI3 bus lock leaks. App direct + // draws (DOOM/Game Boy) never set this flag; they only pulse the wait semaphore. + if (s_flush_took_bus) { + lv_display_flush_ready((lv_display_t *)ctx); + s_flush_took_bus = false; + spi_bus_lock_give_from_isr(&hp); + } else if (s_direct_mode) { + if (s_trans_done != NULL) + xSemaphoreGiveFromISR(s_trans_done, &hp); + } else { + lv_display_flush_ready((lv_display_t *)ctx); + } + return hp == pdTRUE; +} + static void capture_flush_start_cb(lv_event_t *e) { + s_flush_took_bus = spi_bus_lock_take(SPI3_FLUSH_TIMEOUT_MS); + lvgl_glue_strip_cb_t cb = s_capture_cb; if (cb == NULL) { return; @@ -91,8 +145,21 @@ esp_err_t lvgl_glue_init(void) { ESP_LOGE(TAG, "lvgl_port_add_disp returned NULL"); return ESP_FAIL; } + + lv_draw_buf_handlers_t *dbh = lv_draw_buf_get_handlers(); + dbh->buf_malloc_cb = draw_buf_psram_malloc; + dbh->buf_free_cb = draw_buf_free; + lv_draw_buf_handlers_t *idbh = lv_draw_buf_get_image_handlers(); + idbh->buf_malloc_cb = draw_buf_psram_malloc; + idbh->buf_free_cb = draw_buf_free; lv_display_add_event_cb(s_disp, capture_flush_start_cb, LV_EVENT_FLUSH_START, NULL); + s_trans_done = xSemaphoreCreateBinary(); + const esp_lcd_panel_io_callbacks_t io_cbs = { + .on_color_trans_done = trans_done_cb, + }; + esp_lcd_panel_io_register_event_callbacks(io_handle, &io_cbs, s_disp); + ESP_LOGI(TAG, "LVGL up — %dx%d, partial double buffer (%d lines) in internal DMA RAM", LCD_PANEL_W, @@ -114,6 +181,21 @@ void lvgl_glue_capture_end(void) { s_capture_cb = NULL; } +void lvgl_glue_direct_begin(void) { + if (s_trans_done != NULL) + xSemaphoreTake(s_trans_done, 0); + s_direct_mode = true; +} + +void lvgl_glue_direct_end(void) { + s_direct_mode = false; +} + +void lvgl_glue_wait_flush(uint32_t timeout_ms) { + if (s_trans_done != NULL) + xSemaphoreTake(s_trans_done, pdMS_TO_TICKS(timeout_ms)); +} + bool lvgl_glue_lock(int timeout_ms) { return lvgl_port_lock(timeout_ms); } diff --git a/firmware_p4/components/Service/power_manager/README.md b/firmware_p4/components/Service/power_manager/README.md new file mode 100644 index 000000000..79f7f5849 --- /dev/null +++ b/firmware_p4/components/Service/power_manager/README.md @@ -0,0 +1,7 @@ +# Power Manager - P4 (esp_pm DFS / light-sleep) + +Documentation for this component lives in the project docs hub (single source of truth): + +- [docs/power_manager/README.md](../../../../docs/power_manager/README.md) + +This file is only a pointer: edit the docs there to keep things from drifting. diff --git a/firmware_p4/components/Service/spi_bridge/include/spi_protocol.h b/firmware_p4/components/Service/spi_bridge/include/spi_protocol.h index 707abeab0..02c4c350e 100644 --- a/firmware_p4/components/Service/spi_bridge/include/spi_protocol.h +++ b/firmware_p4/components/Service/spi_bridge/include/spi_protocol.h @@ -575,15 +575,37 @@ typedef struct { } __attribute__((packed)) spi_wifi_scan_record_t; /** - * @brief WiFi sniffer stream frame. + * @brief WiFi sniffer stream fragment. + * + * A single 802.11 frame can exceed one SPI transfer (payload capped at + * SPI_MAX_PAYLOAD), so the C5 splits large frames into ordered fragments and + * the P4 reassembles them. rssi/channel/total_len are repeated on every + * fragment so the P4 can validate cheaply. frag_off is this fragment's byte + * offset within the full frame; SPI_WIFI_SNIFFER_FRAG_MORE is set on every + * fragment except the last. A frame that fits in one transfer is a single + * fragment with frag_off == 0 and the MORE bit clear. + * + * Max data bytes per fragment = SPI_MAX_PAYLOAD - sizeof(spi_stream_meta_t) + * - sizeof(spi_wifi_sniffer_frame_t). */ typedef struct { - int8_t rssi; - uint8_t channel; - uint8_t len; + int8_t rssi; // dBm signal of the frame + uint8_t channel; // primary channel + uint16_t total_len; // full 802.11 frame length across all fragments + uint16_t frag_off; // byte offset of this fragment within the frame + uint8_t frag_len; // data bytes carried by this fragment + uint8_t flags; // SPI_WIFI_SNIFFER_FRAG_* bits uint8_t data[0]; } __attribute__((packed)) spi_wifi_sniffer_frame_t; +#define SPI_WIFI_SNIFFER_FRAG_MORE 0x01u // more fragments follow this one +#define SPI_WIFI_SNIFFER_FRAME_MAX 2346 // max reassembled 802.11 frame (bytes) + +// Max frame bytes carried by a single fragment (transfer cap minus the stream +// meta prepended by the session layer minus this fragment header). +#define SPI_WIFI_SNIFFER_FRAG_DATA_MAX \ + ((int)(SPI_MAX_PAYLOAD - sizeof(spi_stream_meta_t) - sizeof(spi_wifi_sniffer_frame_t))) + /** * @brief BLE sniffer stream frame. */ diff --git a/firmware_p4/components/Service/spi_bridge/include/spi_timeouts.h b/firmware_p4/components/Service/spi_bridge/include/spi_timeouts.h index bd04f3279..c14c516df 100644 --- a/firmware_p4/components/Service/spi_bridge/include/spi_timeouts.h +++ b/firmware_p4/components/Service/spi_bridge/include/spi_timeouts.h @@ -35,6 +35,14 @@ extern "C" { // ~30 s. Keep the master's wait above that or the command times out mid-scan and // the late response desyncs the bridge. #define SPI_TIMEOUT_WIFI_MS 40000 +// The *_BLE_INIT / *_BLE_STOP commands run the full NimBLE lifecycle on the C5 +// (tear down the other GATT services via a blocking nimble_port_stop, then +// nimble_port_init + service add) synchronously on the single SPI bridge task of +// a single-core chip, so bring-up regularly overruns the 1 s default. When it +// does, the late response desyncs the bridge and the P4 keeps retrying (which +// tears the just-started service back down). Give these commands enough headroom +// to complete on the first try. +#define SPI_TIMEOUT_BLE_LIFECYCLE_MS 5000 #ifdef __cplusplus } diff --git a/firmware_p4/components/Service/spi_bridge/spi_bridge.c b/firmware_p4/components/Service/spi_bridge/spi_bridge.c index aa713016c..601d07baf 100644 --- a/firmware_p4/components/Service/spi_bridge/spi_bridge.c +++ b/firmware_p4/components/Service/spi_bridge/spi_bridge.c @@ -254,7 +254,17 @@ uint32_t spi_bridge_get_timeout(spi_id_t id) { if (id >= SPI_ID_WIFI_SCAN && id <= SPI_ID_WIFI_APP_PROBE_MON) { return SPI_TIMEOUT_WIFI_MS; } - return SPI_TIMEOUT_DEFAULT_MS; + switch (id) { + case SPI_ID_MESH_BLE_INIT: + case SPI_ID_MESH_BLE_STOP: + case SPI_ID_MCORE_BLE_INIT: + case SPI_ID_MCORE_BLE_STOP: + case SPI_ID_HOST_BLE_INIT: + case SPI_ID_HOST_BLE_STOP: + return SPI_TIMEOUT_BLE_LIFECYCLE_MS; + default: + return SPI_TIMEOUT_DEFAULT_MS; + } } #define SCAN_STATUS_POLL_MS 250 diff --git a/firmware_p4/components/Service/storage_api/storage_impl.c b/firmware_p4/components/Service/storage_api/storage_impl.c index cff9db4fe..f4ae3a031 100644 --- a/firmware_p4/components/Service/storage_api/storage_impl.c +++ b/firmware_p4/components/Service/storage_api/storage_impl.c @@ -68,7 +68,7 @@ esp_err_t storage_file_get_info(const char *path, storage_file_info_t *info) { return ret; } - strncpy(info->path, full_path, sizeof(info->path) - 1); + snprintf(info->path, sizeof(info->path), "%s", full_path); info->size = st.size; info->modified_time = st.mtime; info->created_time = st.ctime; diff --git a/firmware_p4/components/Service/storage_api/storage_write.c b/firmware_p4/components/Service/storage_api/storage_write.c index d119987de..667f872d6 100644 --- a/firmware_p4/components/Service/storage_api/storage_write.c +++ b/firmware_p4/components/Service/storage_api/storage_write.c @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include "esp_log.h" @@ -31,6 +33,36 @@ static const char *TAG = "STORAGE_WRITE"; #define FORMAT_BUF_SIZE 512 +static esp_err_t ensure_parent_dir(const char *full_path) { + char dir[VFS_MAX_PATH]; + strncpy(dir, full_path, sizeof(dir) - 1); + dir[sizeof(dir) - 1] = '\0'; + + char *slash = strrchr(dir, '/'); + if (slash == NULL || slash == dir) { + return ESP_OK; + } + *slash = '\0'; + + esp_err_t ret = storage_mkdir_recursive(dir); + if (ret != ESP_OK) { + return ret; + } + + // An earlier bug created directories where files should be. Remove such a + // stale directory so the write can open the path as a file. + struct stat st; + if (stat(full_path, &st) == 0 && S_ISDIR(st.st_mode)) { + ESP_LOGW(TAG, "Removing stale directory in place of file: %s", full_path); + if (rmdir(full_path) != 0) { + ESP_LOGE(TAG, "Failed to remove stale directory: %s", full_path); + return ESP_FAIL; + } + } + + return ESP_OK; +} + esp_err_t storage_write_string(const char *path, const char *data) { if (!storage_is_mounted() || path == NULL || data == NULL) { return !storage_is_mounted() ? ESP_ERR_INVALID_STATE : ESP_ERR_INVALID_ARG; @@ -39,7 +71,7 @@ esp_err_t storage_write_string(const char *path, const char *data) { char full_path[VFS_MAX_PATH]; storage_resolve_path(path, full_path, sizeof(full_path)); - esp_err_t ret = storage_mkdir_recursive(full_path); + esp_err_t ret = ensure_parent_dir(full_path); if (ret != ESP_OK) { return ret; } @@ -60,7 +92,7 @@ esp_err_t storage_append_string(const char *path, const char *data) { char full_path[VFS_MAX_PATH]; storage_resolve_path(path, full_path, sizeof(full_path)); - esp_err_t ret = storage_mkdir_recursive(full_path); + esp_err_t ret = ensure_parent_dir(full_path); if (ret != ESP_OK) { return ret; } @@ -81,7 +113,7 @@ esp_err_t storage_write_binary(const char *path, const void *data, size_t size) char full_path[VFS_MAX_PATH]; storage_resolve_path(path, full_path, sizeof(full_path)); - esp_err_t ret = storage_mkdir_recursive(full_path); + esp_err_t ret = ensure_parent_dir(full_path); if (ret != ESP_OK) { return ret; } @@ -102,7 +134,7 @@ esp_err_t storage_append_binary(const char *path, const void *data, size_t size) char full_path[VFS_MAX_PATH]; storage_resolve_path(path, full_path, sizeof(full_path)); - esp_err_t ret = storage_mkdir_recursive(full_path); + esp_err_t ret = ensure_parent_dir(full_path); if (ret != ESP_OK) { return ret; } @@ -143,7 +175,7 @@ esp_err_t storage_write_formatted(const char *path, const char *format, ...) { char full_path[VFS_MAX_PATH]; storage_resolve_path(path, full_path, sizeof(full_path)); - esp_err_t ret = storage_mkdir_recursive(full_path); + esp_err_t ret = ensure_parent_dir(full_path); if (ret != ESP_OK) { return ret; } @@ -179,7 +211,7 @@ esp_err_t storage_append_formatted(const char *path, const char *format, ...) { char full_path[VFS_MAX_PATH]; storage_resolve_path(path, full_path, sizeof(full_path)); - esp_err_t ret = storage_mkdir_recursive(full_path); + esp_err_t ret = ensure_parent_dir(full_path); if (ret != ESP_OK) { return ret; } @@ -235,7 +267,7 @@ esp_err_t storage_write_csv_row(const char *path, const char **columns, size_t n char full_path[VFS_MAX_PATH]; storage_resolve_path(path, full_path, sizeof(full_path)); - esp_err_t ret = storage_mkdir_recursive(full_path); + esp_err_t ret = ensure_parent_dir(full_path); if (ret != ESP_OK) { return ret; } @@ -265,7 +297,7 @@ esp_err_t storage_append_csv_row(const char *path, const char **columns, size_t char full_path[VFS_MAX_PATH]; storage_resolve_path(path, full_path, sizeof(full_path)); - esp_err_t ret = storage_mkdir_recursive(full_path); + esp_err_t ret = ensure_parent_dir(full_path); if (ret != ESP_OK) { return ret; } diff --git a/firmware_p4/components/Service/storage_api/tos_log.c b/firmware_p4/components/Service/storage_api/tos_log.c deleted file mode 100644 index bea5369cc..000000000 --- a/firmware_p4/components/Service/storage_api/tos_log.c +++ /dev/null @@ -1,342 +0,0 @@ -// Copyright (c) 2025 HIGH CODE LLC -// -// TentacleOS 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 3 of the License, or -// (at your option) any later version. -// -// TentacleOS 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 TentacleOS. If not, see . - -#include "tos_log.h" - -#include -#include -#include -#include - -#include "esp_log.h" -#include "freertos/FreeRTOS.h" -#include "freertos/semphr.h" -#include "freertos/task.h" -#include "sys_prio.h" - -#include "storage_init.h" -#include "tos_storage_paths.h" - -static const char *TAG = "TOS_LOG"; - -#define LOG_DIR TOS_PATH_LOGS -#define LOG_FILE_FMT LOG_DIR "/sys.%d.log" -#define LOG_MAX_FILES 5 -#define LOG_MAX_SIZE (2 * 1024 * 1024) // 2 MB per file (total max = 10 MB) -#define LOG_PATH_LEN 64 - -// The log hook runs in the context of whichever task called ESP_LOGx, so it may -// never touch the filesystem: doing so put a flash write (cache disabled, non-IRAM -// interrupts masked) on the hot path of radio/UI tasks. Instead it formats the -// line and appends it to a RAM buffer under a mutex; a dedicated task drains the -// buffer to flash in batches, off the logging tasks' critical paths, and is the -// only code that opens, writes, flushes, or rotates the file. -#define LOG_BUF_SIZE 2048 // RAM buffer, double-buffered on flush -#define LOG_LINE_MAX 256 // max bytes captured from one log line -#define LOG_FLUSH_MS 1000 // periodic drain interval -#define LOG_HIGH_WATER (LOG_BUF_SIZE * 3 / 4) // early-drain threshold for bursts -#define LOG_LOCK_WAIT_MS 50 // append gives up rather than block -#define LOG_TASK_STACK 4096 -#define LOG_TASK_PRIO SYS_PRIO_BACKGROUND -#define LOG_STOP_WAIT_MS 2000 - -static FILE *s_log_file = NULL; -static vprintf_like_t s_original_vprintf = NULL; - -static SemaphoreHandle_t s_lock = NULL; // guards s_buf, s_buf_len, s_dropped -static SemaphoreHandle_t s_stop_done = NULL; // flush task signals teardown complete -static TaskHandle_t s_flush_task = NULL; -static volatile bool s_ready = false; -static volatile bool s_stop = false; - -static uint8_t s_buf[LOG_BUF_SIZE]; // accumulation buffer (append path) -static uint8_t s_scratch[LOG_BUF_SIZE]; // drain buffer (flush task only) -static size_t s_buf_len = 0; -static size_t s_dropped = 0; // bytes dropped while buffer was full - -static void build_path(char *out_path, int index) { - snprintf(out_path, LOG_PATH_LEN, LOG_FILE_FMT, index); -} - -static long get_file_size(const char *path) { - struct stat st; - if (stat(path, &st) != 0) { - return 0; - } - return st.st_size; -} - -/** - * Rotate log files (flush task / init only, never the append path): - * sys.5.log -> deleted - * sys.4.log -> sys.5.log - * sys.3.log -> sys.4.log - * sys.2.log -> sys.3.log - * sys.1.log -> sys.2.log - * (new sys.1.log created) - */ -static void rotate_logs(void) { - if (s_log_file != NULL) { - fclose(s_log_file); - s_log_file = NULL; - } - - char old_path[LOG_PATH_LEN]; - char new_path[LOG_PATH_LEN]; - - build_path(old_path, LOG_MAX_FILES); - remove(old_path); - - for (int i = LOG_MAX_FILES - 1; i >= 1; i--) { - build_path(old_path, i); - build_path(new_path, i + 1); - rename(old_path, new_path); - } - - build_path(old_path, 1); - s_log_file = fopen(old_path, "w"); -} - -// Move the accumulated buffer to flash. Holds the lock only for the swap so -// logging tasks never wait on flash. All file I/O and rotation happen here. -static void flush_once(void) { - if (s_log_file == NULL) { - return; - } - - size_t len = 0; - size_t dropped = 0; - - if (xSemaphoreTake(s_lock, portMAX_DELAY) == pdTRUE) { - if (s_buf_len > 0) { - memcpy(s_scratch, s_buf, s_buf_len); - len = s_buf_len; - s_buf_len = 0; - } - dropped = s_dropped; - s_dropped = 0; - xSemaphoreGive(s_lock); - } - - if (len == 0 && dropped == 0) { - return; - } - - if (len > 0) { - fwrite(s_scratch, 1, len, s_log_file); - } - if (dropped > 0) { - fprintf(s_log_file, "\n[TOS_LOG] %u bytes dropped (buffer full)\n", (unsigned)dropped); - } - fflush(s_log_file); - - if (ftell(s_log_file) >= LOG_MAX_SIZE) { - rotate_logs(); - } -} - -static void flush_task(void *pvParameters) { - (void)pvParameters; - - while (!s_stop) { - // Wake on the periodic tick or on an early-drain notification from a burst. - ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(LOG_FLUSH_MS)); - flush_once(); - } - - flush_once(); // final drain on teardown - xSemaphoreGive(s_stop_done); - vTaskDelete(NULL); -} - -static int log_to_file(const char *fmt, va_list args) { - // Re-entrancy guard: this IS the global vprintf hook, so if anything in the - // serial-print path below logs (e.g. a UART/PM driver warning during a DFS - // frequency switch), it would re-enter here and recurse until the stack - // overflows. Break that: while we are inside our own s_original_vprintf on this - // core, drop any nested log line. Per-core (not global) so the two cores never - // clobber each other's guard; the recursion we care about is synchronous on - // one core. - static volatile bool s_in_vprintf[portNUM_PROCESSORS]; - BaseType_t core = xPortGetCoreID(); - if (s_in_vprintf[core]) { - return 0; // nested log from within the print path: drop to stop recursion - } - - s_in_vprintf[core] = true; - int ret = s_original_vprintf(fmt, args); - s_in_vprintf[core] = false; - - if (!s_ready || !storage_is_mounted()) { - return ret; - } - - // This is the global vprintf hook, so it runs on whatever task is logging - - // keep it off the caller's stack. Take the lock first, then format into a - // shared static buffer (guarded by the same lock) instead of a stack temp. - if (xSemaphoreTake(s_lock, pdMS_TO_TICKS(LOG_LOCK_WAIT_MS)) != pdTRUE) { - return ret; // contended: skip file copy, serial already has the line - } - - // vsnprintf returns the length it would have written; clamp to what fits - // (drops the trailing NUL and any overflow of an unusually long line). - static char line[LOG_LINE_MAX]; - va_list ap; - va_copy(ap, args); - int n = vsnprintf(line, sizeof(line), fmt, ap); - va_end(ap); - if (n <= 0) { - xSemaphoreGive(s_lock); - return ret; - } - size_t wlen = (n >= (int)sizeof(line)) ? (sizeof(line) - 1) : (size_t)n; - - bool over_high_water = false; - if (s_buf_len + wlen > LOG_BUF_SIZE) { - // Drop the whole line so the file never holds a torn record. - s_dropped += wlen; - } else { - memcpy(s_buf + s_buf_len, line, wlen); - s_buf_len += wlen; - over_high_water = (s_buf_len >= LOG_HIGH_WATER); - } - xSemaphoreGive(s_lock); - - if (over_high_water && s_flush_task != NULL) { - xTaskNotifyGive(s_flush_task); - } - - return ret; -} - -esp_err_t tos_log_init(void) { - if (s_ready) { - return ESP_ERR_INVALID_STATE; - } - - if (!storage_is_mounted()) { - return ESP_ERR_INVALID_STATE; - } - - esp_err_t err = ESP_OK; - - // Open current log file (append to existing sys.1.log). - char path[LOG_PATH_LEN]; - build_path(path, 1); - s_log_file = fopen(path, "a"); - if (s_log_file == NULL) { - ESP_LOGE(TAG, "Failed to open log file: %s", path); - return ESP_FAIL; - } - - // Rotate on startup if the current file is already full (single-threaded here, - // before the hook and flush task exist). - if (get_file_size(path) >= LOG_MAX_SIZE) { - rotate_logs(); - if (s_log_file == NULL) { - ESP_LOGE(TAG, "Failed to reopen log file after startup rotation"); - return ESP_FAIL; - } - } - - s_lock = xSemaphoreCreateMutex(); - s_stop_done = xSemaphoreCreateBinary(); - if (s_lock == NULL || s_stop_done == NULL) { - err = ESP_ERR_NO_MEM; - goto cleanup; - } - - s_buf_len = 0; - s_dropped = 0; - s_stop = false; - - if (xTaskCreatePinnedToCore(flush_task, - "tos_log", - LOG_TASK_STACK, - NULL, - LOG_TASK_PRIO, - &s_flush_task, - SYS_CORE_RADIO) != pdPASS) { - ESP_LOGE(TAG, "Failed to create log flush task"); - err = ESP_FAIL; - goto cleanup; - } - - // Install the hook last, once the buffer and drain task are ready to receive. - s_ready = true; - s_original_vprintf = esp_log_set_vprintf(log_to_file); - - ESP_LOGI(TAG, - "Log system initialized (file: %s, %d files x %d MB, buffer %d B, flush %d ms)", - path, - LOG_MAX_FILES, - LOG_MAX_SIZE / (1024 * 1024), - LOG_BUF_SIZE, - LOG_FLUSH_MS); - - return ESP_OK; - -cleanup: - if (s_flush_task != NULL) { - s_flush_task = NULL; - } - if (s_stop_done != NULL) { - vSemaphoreDelete(s_stop_done); - s_stop_done = NULL; - } - if (s_lock != NULL) { - vSemaphoreDelete(s_lock); - s_lock = NULL; - } - if (s_log_file != NULL) { - fclose(s_log_file); - s_log_file = NULL; - } - return err; -} - -void tos_log_deinit(void) { - // Restore the default handler first so no new lines are buffered during teardown. - if (s_original_vprintf != NULL) { - esp_log_set_vprintf(s_original_vprintf); - s_original_vprintf = NULL; - } - s_ready = false; - - // Stop the flush task and let it drain what is left. - if (s_flush_task != NULL) { - s_stop = true; - xTaskNotifyGive(s_flush_task); - if (s_stop_done != NULL) { - xSemaphoreTake(s_stop_done, pdMS_TO_TICKS(LOG_STOP_WAIT_MS)); - } - s_flush_task = NULL; - } - - if (s_log_file != NULL) { - fclose(s_log_file); - s_log_file = NULL; - } - if (s_stop_done != NULL) { - vSemaphoreDelete(s_stop_done); - s_stop_done = NULL; - } - if (s_lock != NULL) { - vSemaphoreDelete(s_lock); - s_lock = NULL; - } - - ESP_LOGI(TAG, "Log system deinitialized"); -} diff --git a/firmware_p4/components/Service/storage_vfs/include/usb_msc.h b/firmware_p4/components/Service/storage_vfs/include/usb_msc.h new file mode 100644 index 000000000..791c15092 --- /dev/null +++ b/firmware_p4/components/Service/storage_vfs/include/usb_msc.h @@ -0,0 +1,73 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +/** + * @file usb_msc.h + * @brief USB Mass Storage mode: expose the microSD to a host PC as a USB drive. + */ +#ifndef USB_MSC_H +#define USB_MSC_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief USB Mass Storage mode state. + */ +typedef enum { + USB_MSC_IDLE = 0, /**< Not in USB-storage mode. */ + USB_MSC_ENTERING, /**< Detaching SD / bringing USB up. */ + USB_MSC_ACTIVE, /**< SD exposed to the host as a USB drive. */ + USB_MSC_ERROR, /**< Could not enter (SD restored, safe to leave). */ + USB_MSC_EXITING, /**< Tearing down / remounting SD. */ +} usb_msc_state_t; + +/** + * @brief Get the current USB Mass Storage mode state (poll this from the UI). + * + * @return The current ::usb_msc_state_t value. + */ +usb_msc_state_t usb_msc_get_state(void); + +/** + * @brief Check whether the host PC currently has the drive mounted. + * + * @return true while the host PC has the drive mounted, false otherwise. + */ +bool usb_msc_host_connected(void); + +/** + * @brief Enter USB Mass Storage mode: detach the app's /sdcard FAT, expose the + * raw card to the host, and switch the USB mux to native. On failure the + * SD is restored. Blocking — run on a worker task, NEVER the LVGL thread. + */ +void usb_msc_enter(void); + +/** + * @brief Leave USB Mass Storage mode and RESUME normal operation (no reboot): + * stop exposing the card, remount /sdcard for the app, and route the USB + * connector back to the UART bridge. Only if the remount fails does it + * reboot to recover. Blocking — run on a worker task. + */ +void usb_msc_exit(void); + +#ifdef __cplusplus +} +#endif + +#endif // USB_MSC_H diff --git a/firmware_p4/components/Service/storage_vfs/include/vfs_sdcard.h b/firmware_p4/components/Service/storage_vfs/include/vfs_sdcard.h index 5e17b2809..bfe1d03c9 100644 --- a/firmware_p4/components/Service/storage_vfs/include/vfs_sdcard.h +++ b/firmware_p4/components/Service/storage_vfs/include/vfs_sdcard.h @@ -47,6 +47,23 @@ esp_err_t vfs_sdcard_format(void); esp_err_t vfs_register_sd_backend(void); esp_err_t vfs_unregister_sd_backend(void); +/** + * @brief Give up the app's FAT mount and hand back the SD as a RAW, still-powered + * block device for USB Mass Storage. The card is re-initialized (never + * formatted). There is no restore path — exit USB-MSC mode via reboot. + * @param out_card receives an @c sdmmc_card_t* (as @c void*) on success. + * @return ESP_OK, or an error (the app FAT mount is left detached on failure). + */ +esp_err_t vfs_sdcard_detach_for_msc(void **out_card); + +/** + * @brief Undo vfs_sdcard_detach_for_msc(): release the raw SDMMC card+host that + * was handed to USB MSC and remount the app FAT at /sdcard. Call AFTER the + * MSC storage layer is torn down. Lets the firmware resume without a reboot. + * @param card the handle returned by vfs_sdcard_detach_for_msc() (may be NULL). + */ +esp_err_t vfs_sdcard_reattach_after_msc(void *card); + #ifdef __cplusplus } #endif diff --git a/firmware_p4/components/Service/storage_vfs/usb_msc.c b/firmware_p4/components/Service/storage_vfs/usb_msc.c new file mode 100644 index 000000000..acbe3878d --- /dev/null +++ b/firmware_p4/components/Service/storage_vfs/usb_msc.c @@ -0,0 +1,93 @@ +// Copyright (c) 2025 HIGH CODE LLC +// +// TentacleOS 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 3 of the License, or +// (at your option) any later version. +// +// TentacleOS 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 TentacleOS. If not, see . + +#include "usb_msc.h" + +#include "esp_log.h" +#include "esp_system.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "sdmmc_cmd.h" +#include "tusb_msc_storage.h" + +#include "tusb_desc.h" +#include "vfs_sdcard.h" + +static const char *TAG = "USB_MSC"; + +#define USB_MSC_DETACH_FAIL_DELAY_MS 800 +#define USB_MSC_REMOUNT_FAIL_DELAY_MS 500 + +static volatile usb_msc_state_t s_state = USB_MSC_IDLE; +static void *s_card = NULL; + +usb_msc_state_t usb_msc_get_state(void) { + return s_state; +} + +bool usb_msc_host_connected(void) { + return (s_state == USB_MSC_ACTIVE) && tinyusb_msc_storage_in_use_by_usb_host(); +} + +void usb_msc_enter(void) { + s_state = USB_MSC_ENTERING; + + void *raw = NULL; + if (vfs_sdcard_detach_for_msc(&raw) != ESP_OK || raw == NULL) { + ESP_LOGE(TAG, "SD detach failed - rebooting to recover"); + vTaskDelay(pdMS_TO_TICKS(USB_MSC_DETACH_FAIL_DELAY_MS)); + esp_restart(); + } + s_card = raw; + + const tinyusb_msc_sdmmc_config_t msc_cfg = {.card = (sdmmc_card_t *)raw}; + if (tinyusb_msc_storage_init_sdmmc(&msc_cfg) != ESP_OK) { + ESP_LOGE(TAG, "MSC storage init failed - restoring SD"); + (void)vfs_sdcard_reattach_after_msc(s_card); + s_card = NULL; + s_state = USB_MSC_ERROR; + return; + } + + busb_set_msc_exposed(true); + usb_mux_set_native(true); + s_state = USB_MSC_ACTIVE; + ESP_LOGI(TAG, "USB drive live"); +} + +void usb_msc_exit(void) { + if (s_state != USB_MSC_ACTIVE) { + s_state = USB_MSC_IDLE; + return; + } + s_state = USB_MSC_EXITING; + + // Hide MSC (re-enumerating the host) while the storage handle is still valid, + // so no SCSI command lands on a deinitialized backend. + busb_set_msc_exposed(false); + tinyusb_msc_storage_deinit(); + usb_mux_set_native(false); + + esp_err_t r = vfs_sdcard_reattach_after_msc(s_card); + s_card = NULL; + if (r != ESP_OK) { + ESP_LOGE(TAG, "SD remount failed (%s) - rebooting to recover", esp_err_to_name(r)); + vTaskDelay(pdMS_TO_TICKS(USB_MSC_REMOUNT_FAIL_DELAY_MS)); + esp_restart(); + } + + s_state = USB_MSC_IDLE; + ESP_LOGI(TAG, "Left USB storage mode; /sdcard restored"); +} diff --git a/firmware_p4/components/Service/storage_vfs/vfs_sdcard.c b/firmware_p4/components/Service/storage_vfs/vfs_sdcard.c index db959ce4f..02fa87770 100644 --- a/firmware_p4/components/Service/storage_vfs/vfs_sdcard.c +++ b/firmware_p4/components/Service/storage_vfs/vfs_sdcard.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -344,4 +345,68 @@ esp_err_t vfs_sdcard_format(void) { return vfs_sdcard_init(); } +esp_err_t vfs_sdcard_detach_for_msc(void **out_card) { + if (out_card == NULL) { + return ESP_ERR_INVALID_ARG; + } + *out_card = NULL; + + if (s_sdcard.mounted) { + esp_err_t r = vfs_sdcard_deinit(); + if (r != ESP_OK) { + ESP_LOGE(TAG, "detach: unmount failed: %s", esp_err_to_name(r)); + return r; + } + } + + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + slot_config.width = 4; + slot_config.clk = GPIO_SDMMC_CLK_PIN; + slot_config.cmd = GPIO_SDMMC_CMD_PIN; + slot_config.d0 = GPIO_SDMMC_D0_PIN; + slot_config.d1 = GPIO_SDMMC_D1_PIN; + slot_config.d2 = GPIO_SDMMC_D2_PIN; + slot_config.d3 = GPIO_SDMMC_D3_PIN; + slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP; + + esp_err_t r = sdmmc_host_init(); + if (r != ESP_OK) { + ESP_LOGE(TAG, "detach: host init: %s", esp_err_to_name(r)); + return r; + } + r = sdmmc_host_init_slot(host.slot, &slot_config); + if (r != ESP_OK) { + ESP_LOGE(TAG, "detach: slot init: %s", esp_err_to_name(r)); + sdmmc_host_deinit(); + return r; + } + sdmmc_card_t *card = calloc(1, sizeof(sdmmc_card_t)); + if (card == NULL) { + sdmmc_host_deinit(); + return ESP_ERR_NO_MEM; + } + r = sdmmc_card_init(&host, card); + if (r != ESP_OK) { + ESP_LOGE(TAG, "detach: card init: %s", esp_err_to_name(r)); + free(card); + sdmmc_host_deinit(); + return r; + } + + *out_card = card; + ESP_LOGI(TAG, "SD detached from app FAT and handed to USB MSC (raw)"); + return ESP_OK; +} + +esp_err_t vfs_sdcard_reattach_after_msc(void *card) { + (void)sdmmc_host_deinit(); + if (card != NULL) { + free(card); + } + return vfs_sdcard_init(); +} + #endif diff --git a/firmware_p4/components/Service/sys_time/README.md b/firmware_p4/components/Service/sys_time/README.md new file mode 100644 index 000000000..2316cb21d --- /dev/null +++ b/firmware_p4/components/Service/sys_time/README.md @@ -0,0 +1,7 @@ +# System Time - P4 + +Documentation for this component lives in the project docs hub (single source of truth): + +- [docs/sys_time/README.md](../../../../docs/sys_time/README.md) + +This file is only a pointer: edit the docs there to keep things from drifting. diff --git a/firmware_p4/components/Service/sys_time/sys_time.c b/firmware_p4/components/Service/sys_time/sys_time.c index 62eca32c3..7fe3e6960 100644 --- a/firmware_p4/components/Service/sys_time/sys_time.c +++ b/firmware_p4/components/Service/sys_time/sys_time.c @@ -85,7 +85,7 @@ void sys_time_init(void) { time_t baseline = sys_time_build_epoch(); time_t saved = sys_time_saved_epoch(); - if (saved > baseline) { + if (saved >= SYS_TIME_EPOCH_MIN) { baseline = saved; } diff --git a/firmware_p4/main/idf_component.yml b/firmware_p4/main/idf_component.yml index 44ac1ff3f..59138166a 100644 --- a/firmware_p4/main/idf_component.yml +++ b/firmware_p4/main/idf_component.yml @@ -14,3 +14,5 @@ dependencies: # compile. Revisit when upstream fixes the guard or we move to IDF 6. espressif/esp_lvgl_port: '>=2.0.0,<2.9.0' espressif/esp-dsp: ^1.5.0 + # Helix fixed-point MP3 decoder (classic MP3* API via mp3dec.h) for the player. + chmorgan/esp-libhelix-mp3: "^1.0.3" diff --git a/firmware_p4/partitions.csv b/firmware_p4/partitions.csv index 9d8e8681e..273ba82a1 100644 --- a/firmware_p4/partitions.csv +++ b/firmware_p4/partitions.csv @@ -2,7 +2,7 @@ nvs, data, nvs, 0x9000, 24K, otadata, data, ota, 0xf000, 8K, phy_init, data, phy, 0x11000, 4K, -ota_0, app, ota_0, 0x20000, 0x270000, -ota_1, app, ota_1, 0x290000, 0x270000, -coredump, data, coredump, 0x500000, 64K, -assets, data, littlefs, 0x510000, 0x2E0000, +ota_0, app, ota_0, 0x20000, 0x2E0000, +ota_1, app, ota_1, 0x300000, 0x2E0000, +coredump, data, coredump, 0x5E0000, 64K, +assets, data, littlefs, 0x5F0000, 0x210000, diff --git a/firmware_p4/sdkconfig.defaults b/firmware_p4/sdkconfig.defaults index 96015997a..603856d6a 100644 --- a/firmware_p4/sdkconfig.defaults +++ b/firmware_p4/sdkconfig.defaults @@ -22,6 +22,13 @@ CONFIG_ESP32P4_REV_MIN_100=y # CPU 360MHz (P4 maximum) CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_360=y +# Build the whole app + IDF at -O2 (perf) instead of the -Og default. The LVGL +# software renderer is fully CPU-bound, so this is the biggest transparent speed +# win (identical output). Assertions stay on (ASSERTIONS_ENABLE) for safety. +# Trade-off: harder step-debugging; flip back to _DEBUG for interactive debug. +CONFIG_COMPILER_OPTIMIZATION_PERF=y +# CONFIG_COMPILER_OPTIMIZATION_DEBUG is not set + # PSRAM hex-octal mode, 200MHz - P4 specific (V3 prototype has PSRAM populated) CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_HEX=y @@ -29,6 +36,11 @@ CONFIG_SPIRAM_SPEED_200M=y CONFIG_SPIRAM_USE_MALLOC=y CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=2048 CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768 +# Allow static .bss to live in PSRAM (via EXT_RAM_BSS_ATTR). The UI screens keep +# large static list buffers (image_viewer file list, gb paths, ir/wav lists, ...) +# that were pinning ~200-300 KB of the scarce internal DRAM; moving them to PSRAM +# with EXT_RAM_BSS_ATTR is what needs this on. +CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y # Larger stack for kernel_init CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192 @@ -45,6 +57,8 @@ CONFIG_TINYUSB_MODE_DMA=y # TinyUSB CDC-ACM — companion host link (composite alongside the HID) CONFIG_TINYUSB_CDC_ENABLED=y CONFIG_TINYUSB_CDC_COUNT=1 +CONFIG_TINYUSB_MSC_ENABLED=y +CONFIG_TINYUSB_MSC_BUFSIZE=4096 # Required for sys_monitor (uxTaskGetSystemState / vTaskList) CONFIG_FREERTOS_USE_TRACE_FACILITY=y @@ -80,6 +94,9 @@ CONFIG_MBEDTLS_HKDF_C=y # LVGL QR code widget — companion pairing screen renders the PSK as a QR CONFIG_LV_USE_QRCODE=y +CONFIG_LV_USE_LODEPNG=y +CONFIG_LV_USE_GIF=y +CONFIG_LV_USE_TJPGD=y # LVGL snapshot — NFC card-emulation screen snapshots a panel to animate it CONFIG_LV_USE_SNAPSHOT=y diff --git a/pics/Highboy_repo.png b/pics/Highboy_repo.png deleted file mode 100644 index da36c58bd..000000000 Binary files a/pics/Highboy_repo.png and /dev/null differ diff --git a/pics/banner.png b/pics/banner.png new file mode 100644 index 000000000..5e1451e9b Binary files /dev/null and b/pics/banner.png differ diff --git a/tools/format.sh b/tools/format.sh index 5837aac87..d9be50d30 100755 --- a/tools/format.sh +++ b/tools/format.sh @@ -83,6 +83,7 @@ if [ -n "$BASE_REF" ]; then while IFS= read -r -d '' path; do case "$path" in + */Applications/doom/*) ;; # vendored doomgeneric engine: not our style *.c|*.h) FILES+=("$REPO_ROOT/$path") ;; esac done < <(git -C "$REPO_ROOT" diff --name-only -z --diff-filter=ACMR \ @@ -103,7 +104,8 @@ else while IFS= read -r -d '' path; do FILES+=("$path") done < <(find "${EXISTING_TARGETS[@]}" -type f \( -name "*.c" -o -name "*.h" \) \ - -not -path "*/managed_components/*" -not -path "*/build/*" -print0) + -not -path "*/managed_components/*" -not -path "*/build/*" \ + -not -path "*/Applications/doom/*" -print0) fi COUNT="${#FILES[@]}"