SystemK/States/Playing/State_Playing.c
2025-02-09 18:10:31 -06:00

247 lines
8.7 KiB
C

/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡️ <https://ktag.clubk.club> 🃞
*
* Copyright © 2016-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 <string.h>
#include "SystemK.h"
static TimerHandle_t BLEStatusTimer = NULL;
static StaticTimer_t xBLEStatusTimerBuffer;
static TickType_t xBLEStatusTimerPeriod = 3000 / portTICK_PERIOD_MS;
static TimerHandle_t GameDurationTimer = NULL;
static StaticTimer_t xGameDurationTimerBuffer;
static BLENearby_T BLE_RXd_data;
static const uint_fast16_t MAX_TIME_TO_LIVE_in_ms = 1000;
static const int_fast8_t STATUS_RSSI_THRESHOLD = -80;
static const int_fast8_t TAG_RSSI_THRESHOLD = -75;
static void BLEStatusTimerCallback(TimerHandle_t xTimer)
{
StateMachineContext_T* context = GetContext();
BLE_UpdateStatusPacket(context->States.Current_State);
}
static void GameDurationTimerCallback(TimerHandle_t xTimer)
{
KEvent_T game_over_event = { .ID = KEVENT_GAME_OVER, .Data = (void *)0x00 };
Post_KEvent(&game_over_event);
}
//! Sets up the Playing state.
/*!
* \param context Context in which this state is being run.
*/
void Playing_Entry(StateMachineContext_T *context)
{
LOG("Entering the Playing state.");
static const char *KLOG_TAG = "STATE_PLAYING Entry";
for (uint_fast8_t slot = 0; slot < CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL; slot++)
{
BLE_RXd_data.neighbors[slot].TimeToLive_in_ms = 0;
}
BLE_UpdateStatusPacket(context->States.Current_State);
if (BLE_ScanAndAdvertise() != SYSTEMK_RESULT_SUCCESS)
{
KLOG_ERROR(KLOG_TAG, "Couldn't start BLE!");
}
NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_TEAM_COLORS, .Data = (void *)DISPLAY_STYLE_HEARTBEAT, .Prominence = NEOPIXELS_BACKGROUND};
xQueueSend(xQueueNeoPixels, &neopixels_action, 0);
if (BLEStatusTimer == NULL)
{
BLEStatusTimer = xTimerCreateStatic(
"BLEStatus",
xBLEStatusTimerPeriod,
pdTRUE,
(void *)0,
BLEStatusTimerCallback,
&xBLEStatusTimerBuffer);
}
if (BLEStatusTimer != NULL)
{
if (xTimerStart(BLEStatusTimer, 0) != pdPASS)
{
KLOG_ERROR(KLOG_TAG, "Couldn't start the BLEStatusTimer!");
}
}
else
{
KLOG_ERROR(KLOG_TAG, "Couldn't create the BLEStatusTimer!");
}
// The timer is only needed for timed games.
if ((Get_Time_Remaining_in_Game_in_ms() != UINT32_MAX) &&
(Get_Time_Remaining_in_Game_in_ms() != 0))
{
if (GameDurationTimer == NULL)
{
static TickType_t xGameDurationTimerPeriod;
xGameDurationTimerPeriod = Get_Time_Remaining_in_Game_in_ms() / portTICK_PERIOD_MS;
GameDurationTimer = xTimerCreateStatic(
"GameDuration",
xGameDurationTimerPeriod,
pdFALSE,
(void *)0,
GameDurationTimerCallback,
&xGameDurationTimerBuffer);
}
if (GameDurationTimer != NULL)
{
if (xTimerStart(GameDurationTimer, 0) != pdPASS)
{
KLOG_ERROR(KLOG_TAG, "Couldn't start the GameDurationTimer!");
}
}
else
{
KLOG_ERROR(KLOG_TAG, "Couldn't create the GameDurationTimer!");
}
}
}
//! Cleans up the Playing state.
/*!
* \param context Context in which this state is being run.
*/
void Playing_Exit(StateMachineContext_T *context)
{
LOG("Exiting the Playing state.");
xTimerStop(BLEStatusTimer, portMAX_DELAY);
BLE_UpdateStatusPacket(context->States.Next_State);
}
void HandleBLEStatusPacket(const BLE_StatusPacket_T *const packet)
{
bool found = false;
uint8_t first_empty_slot = 0xFF;
// Look through the neighbors, and see if this is one we already know about.
for (uint_fast8_t slot = 0; slot < CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL; slot++)
{
if (BLE_RXd_data.neighbors[slot].TimeToLive_in_ms > 0)
{
if (memcmp(packet->BD_ADDR, BLE_RXd_data.neighbors[slot].BD_ADDR, 6) == 0)
{
if ((packet->health > 0) && (packet->RSSI > STATUS_RSSI_THRESHOLD))
{
BLE_RXd_data.neighbors[slot].RSSI = packet->RSSI;
BLE_RXd_data.neighbors[slot].Color = packet->primary_color;
BLE_RXd_data.neighbors[slot].TimeToLive_in_ms = MAX_TIME_TO_LIVE_in_ms;
}
else
{
// Clear the slot.
BLE_RXd_data.neighbors[slot].TimeToLive_in_ms = 0;
}
found = true;
break;
}
}
else if (first_empty_slot == 0xFF)
{
first_empty_slot = slot;
}
}
if ((found == false) &&
(first_empty_slot < CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL) &&
(packet->health > 0) &&
(packet->RSSI > STATUS_RSSI_THRESHOLD))
{
// Welcome the new neighbor.
memcpy(BLE_RXd_data.neighbors[first_empty_slot].BD_ADDR, packet->BD_ADDR, 6);
BLE_RXd_data.neighbors[first_empty_slot].RSSI = packet->RSSI;
BLE_RXd_data.neighbors[first_empty_slot].Color = packet->primary_color;
BLE_RXd_data.neighbors[first_empty_slot].TimeToLive_in_ms = MAX_TIME_TO_LIVE_in_ms;
}
// NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_BLE_NEARBY, .Data = (void *)&BLE_RXd_data, .Prominence = NEOPIXELS_BACKGROUND};
// xQueueSend(xQueueNeoPixels, &neopixels_action, 0);
}
void HandleBLETagPacket(const BLE_TagPacket_T *const packet)
{
static int16_t Health_In_Percent;
static color_t ReceivedTagColor;
// Use this code for tuning RSSI.
//{
// static uint8_t normalized_rssi = 0;
// normalized_rssi = (uint8_t)abs(packet->RSSI);
//
// AudioAction_T rssi_action = {.ID = AUDIO_PRONOUNCE_NUMBER_0_TO_100, .Play_To_Completion = true, .Data = (void *)&normalized_rssi};
// Perform_Audio_Action(&rssi_action);
//}
// Throw away quiet packets without processing them.
if (packet->RSSI > TAG_RSSI_THRESHOLD)
{
if (BLE_IsBLEPacketForMe(packet->target_BD_ADDR))
{
if (BLE_IsPacketNew(packet->BD_ADDR, BLE_PACKET_TYPE_TAG, packet->event_number))
{
Health_In_Percent = (int16_t)Get_Health();
if (packet->damage > 0)
{
if (Health_In_Percent > 0)
{
Reduce_Health(packet->damage);
ReceivedTagColor = packet->color;
AudioAction_T audio_action = {.ID = AUDIO_PLAY_TAG_RECEIVED, .Data = (void *)0x00};
Perform_Audio_Action(&audio_action);
NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_TAG_RECEIVED, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)&ReceivedTagColor};
xQueueSend(xQueueNeoPixels, &neopixels_action, 0);
}
}
else if (packet->damage < 0)
{
Health_In_Percent -= packet->damage;
if (Health_In_Percent > MAX_HEALTH)
{
Health_In_Percent = MAX_HEALTH;
}
Health_In_Percent = MAX_HEALTH;
Set_Health(Health_In_Percent);
AudioAction_T audio_action = {.ID = AUDIO_PRONOUNCE_NUMBER_0_TO_100, .Play_To_Completion = true, .Data = (void *)&Health_In_Percent};
Perform_Audio_Action(&audio_action);
AudioAction_T audio_action_two = {.ID = AUDIO_PLAY_HEALTH_REMAINING, .Data = (void *)0x00};
Perform_Audio_Action(&audio_action_two);
NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_HEALTH_REPORT, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)&Health_In_Percent};
xQueueSend(xQueueNeoPixels, &neopixels_action, 0);
}
}
}
}
BLE_FreePacketBuffer(packet);
}