295 lines
9.8 KiB
C
Executable file
295 lines
9.8 KiB
C
Executable file
|
|
/*
|
|
* This program source code file is part of SystemK, a library in the KTag project.
|
|
*
|
|
* 🛡️ <https://ktag.clubk.club> 🃞
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
|
|
#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;
|
|
}
|