/* * This program source code file is part of SystemK, a library in the KTag project. * * 🛡️ 🃞 * * Copyright © 2025 Joseph P. Kearney and the KTag developers. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License as published by the Free * Software Foundation, either version 3 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 Affero General Public License for more * details. * * There should be a copy of the GNU Affero General Public License in the LICENSE * file in the root of this repository. If not, see . */ #include #include #include "SystemK.h" static const char *KLOG_TAG = "Nerf Laser Strike"; #define NERF_LASER_STRIKE_SOLO_TEAM 0b00 #define NERF_LASER_STRIKE_ONE_TEAM 0b01 #define NERF_LASER_STRIKE_TWO_TEAM 0b10 #define NERF_LASER_STRIKE_BLASTER 0b000 #define NERF_LASER_STRIKE_ROCKET 0b001 #define NERF_LASER_STRIKE_PLASMA 0b010 #define NERF_LASER_STRIKE_BURST 0b011 #define NERF_LASER_STRIKE_LASER 0b100 static const uint16_t Packet_Data[5][3] = { // Solo, Team 1, Team 2 {0x4D33, 0x34CC, 0x2B55}, // Blaster {0x52B2, 0x354B, 0x2CD4}, // Rocket {0x532D, 0x4ACA, 0x2D53}, // Plasma {0x54AC, 0x4B35, 0x32D2}, // Burst {0x552B, 0x4CB4, 0x334D} // Laser }; static inline uint16_t Get_Packet_Data(uint8_t team, uint8_t weapon) { uint16_t result = 0; if ((team <= NERF_LASER_STRIKE_TWO_TEAM) && (weapon <= NERF_LASER_STRIKE_LASER)) { result = Packet_Data[weapon][team]; } else { KLOG_ERROR(KLOG_TAG, "Requested team (%u) or weapon (%u) out of range!", team, weapon); } return result; } //! Given decoded packet data, this function searches the table to determine if the packet is a valid one. /*! * If it is valid, it will set team and weapon to their valid values, and return true. * If it does not find the packet data in the table, it will return false. */ static inline bool Is_Valid_Packet_Data(const uint16_t data, uint8_t *team, uint8_t *weapon) { bool valid = false; for (uint_fast8_t weapon_index = 0; (weapon_index < 5) && (valid == false); weapon_index++) { for (uint_fast8_t team_index = 0; (team_index < 3) && (valid == false); team_index++) { if (Packet_Data[weapon_index][team_index] == data) { *team = team_index; *weapon = weapon_index; valid = true; } } } return valid; } #define NERF_LASER_STRIKE_HEADER_MARK_DURATION_IN_us 6000 #define NERF_LASER_STRIKE_ZERO_DURATION_IN_us 500 #define NERF_LASER_STRIKE_ONE_DURATION_IN_us 1500 #define NERF_LASER_STRIKE_TOLERANCE_IN_us 450 #define MIN_ZERO_IN_us (NERF_LASER_STRIKE_ZERO_DURATION_IN_us - NERF_LASER_STRIKE_TOLERANCE_IN_us) #define MAX_ZERO_IN_us (NERF_LASER_STRIKE_ZERO_DURATION_IN_us + NERF_LASER_STRIKE_TOLERANCE_IN_us) #define MIN_ONE_IN_us (NERF_LASER_STRIKE_ONE_DURATION_IN_us - NERF_LASER_STRIKE_TOLERANCE_IN_us) #define MAX_ONE_IN_us (NERF_LASER_STRIKE_ONE_DURATION_IN_us + NERF_LASER_STRIKE_TOLERANCE_IN_us) static const TimedBit_T NERF_LASER_STRIKE_HEADER_MARK = {.symbol = MARK, .duration = NERF_LASER_STRIKE_HEADER_MARK_DURATION_IN_us}; static const TimedBit_T NERF_LASER_STRIKE_ZERO_SPACE = {.symbol = SPACE, .duration = NERF_LASER_STRIKE_ZERO_DURATION_IN_us}; static const TimedBit_T NERF_LASER_STRIKE_ONE_SPACE = {.symbol = SPACE, .duration = NERF_LASER_STRIKE_ONE_DURATION_IN_us}; static const TimedBit_T NERF_LASER_STRIKE_ZERO_MARK = {.symbol = MARK, .duration = NERF_LASER_STRIKE_ZERO_DURATION_IN_us}; static const TimedBit_T NERF_LASER_STRIKE_ONE_MARK = {.symbol = MARK, .duration = NERF_LASER_STRIKE_ONE_DURATION_IN_us}; static const TimedBit_T NERF_LASER_STRIKE_END = {.symbol = SPACE, .duration = LAST_PULSE}; static TimedPulseTrain_T Tag_Send_Buffer; static DecodedPacket_T Nerf_Laser_Strike_Tag_Received_Buffers[3] = { {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}, {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}, {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}}; static inline DecodedPacket_T *Get_Tag_Packet_Buffer(void) { if (Nerf_Laser_Strike_Tag_Received_Buffers[0].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE) { return &Nerf_Laser_Strike_Tag_Received_Buffers[0]; } else if (Nerf_Laser_Strike_Tag_Received_Buffers[1].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE) { return &Nerf_Laser_Strike_Tag_Received_Buffers[1]; } else { // Just use it. return &Nerf_Laser_Strike_Tag_Received_Buffers[2]; } } // Packs data into a pulse train, most significant bit first. static inline void PackPulseTrain(TimedPulseTrain_T *pulsetrain, uint16_t data) { pulsetrain->bitstream[0] = NERF_LASER_STRIKE_HEADER_MARK; for (uint8_t n = 0; n < 8; n++) { if ((data & 0x8000) == 0x8000) { pulsetrain->bitstream[1 + (n * 2)] = NERF_LASER_STRIKE_ONE_SPACE; } else { pulsetrain->bitstream[1 + (n * 2)] = NERF_LASER_STRIKE_ZERO_SPACE; } data = data << 1; if ((data & 0x8000) == 0x8000) { pulsetrain->bitstream[2 + (n * 2)] = NERF_LASER_STRIKE_ONE_MARK; } else { pulsetrain->bitstream[2 + (n * 2)] = NERF_LASER_STRIKE_ZERO_MARK; } data = data << 1; } pulsetrain->bitstream[17] = NERF_LASER_STRIKE_END; pulsetrain->count = 17; } TimedPulseTrain_T *NERF_LASER_STRIKE_EncodePacket(TagPacket_T *packet) { uint16_t packed_data = 0; if (packet->team_ID == TEAM_RED) { packed_data = Get_Packet_Data(NERF_LASER_STRIKE_ONE_TEAM, NERF_LASER_STRIKE_BLASTER); } else if (packet->team_ID == TEAM_BLUE) { packed_data = Get_Packet_Data(NERF_LASER_STRIKE_TWO_TEAM, NERF_LASER_STRIKE_BLASTER); } else // NERF_LASER_STRIKE_SOLO_TEAM { packed_data = Get_Packet_Data(NERF_LASER_STRIKE_SOLO_TEAM, NERF_LASER_STRIKE_BLASTER); } PackPulseTrain(&Tag_Send_Buffer, packed_data); return &Tag_Send_Buffer; } DecodedPacket_T *NERF_LASER_STRIKE_MaybeDecodePacket(TimedPulseTrain_T *packet) { // Some implementations (ESP32) do not report odd numbers of pulses. // The PSoC sees all three copies of the tag at once. if ((packet->count != 17) && (packet->count != 18) && (packet->count != 53)) { // Fail fast! return NULL; } else { // Convert pulse durations to bits, assuming most significant bit is sent first. // packet->bitstream[0] is the header mark--ignore it. uint16_t data = 0; for (uint16_t n = 16; n > 0; n--) { if ((packet->bitstream[n].duration > MIN_ONE_IN_us) && (packet->bitstream[n].duration < MAX_ONE_IN_us)) { // One data |= ((uint16_t)1 << (16 - n)); } else if ((packet->bitstream[n].duration > MIN_ZERO_IN_us) && (packet->bitstream[n].duration < MAX_ZERO_IN_us)) { // Zero--nothing to do. } else { // This pulse is neither a zero or a one; abort. return NULL; } } uint8_t team = 0; uint8_t weapon = 0; if (Is_Valid_Packet_Data(data, &team, &weapon) == false) { KLOG_DEBUG(KLOG_TAG, "Decoded packet was not recognized: 0x%4X", data); } else { DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer(); if (team == NERF_LASER_STRIKE_ONE_TEAM) { Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; Tag_Rx_Buffer->Tag.protocol = NERF_LASER_STRIKE_PROTOCOL; Tag_Rx_Buffer->Tag.player_ID = 0; Tag_Rx_Buffer->Tag.team_ID = TEAM_RED; Tag_Rx_Buffer->Tag.color = COLOR_RED; } else if (team == NERF_LASER_STRIKE_TWO_TEAM) { Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; Tag_Rx_Buffer->Tag.protocol = NERF_LASER_STRIKE_PROTOCOL; Tag_Rx_Buffer->Tag.player_ID = 0; Tag_Rx_Buffer->Tag.team_ID = TEAM_BLUE; Tag_Rx_Buffer->Tag.color = COLOR_BLUE; } else // NERF_LASER_STRIKE_SOLO_TEAM { Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; Tag_Rx_Buffer->Tag.protocol = NERF_LASER_STRIKE_PROTOCOL; Tag_Rx_Buffer->Tag.player_ID = 0; Tag_Rx_Buffer->Tag.team_ID = TEAM_PURPLE; Tag_Rx_Buffer->Tag.color = COLOR_PURPLE; } // Damages are 10x, normalized for MAX_HEALTH of 100. if (weapon == NERF_LASER_STRIKE_BLASTER) { Tag_Rx_Buffer->Tag.damage = 10; } else if (weapon == NERF_LASER_STRIKE_ROCKET) { Tag_Rx_Buffer->Tag.damage = 30; } else // NERF_LASER_STRIKE_PLASMA, NERF_LASER_STRIKE_BURST, and NERF_LASER_STRIKE_LASER { Tag_Rx_Buffer->Tag.damage = 20; } return Tag_Rx_Buffer; } return NULL; } } color_t NERF_LASER_STRIKE_GetTeamColor(uint8_t team_ID) { color_t result = COLOR_PURPLE; if (team_ID == NERF_LASER_STRIKE_ONE_TEAM) { result = COLOR_RED; } else if (team_ID == NERF_LASER_STRIKE_TWO_TEAM) { result = COLOR_BLUE; } else { // Nothing to do. } return result; }