Initial public release of SystemK.

This commit is contained in:
Joe Kearney 2025-01-25 13:45:14 -06:00
parent 387f57cdda
commit 6f51f5b006
129 changed files with 11654 additions and 2 deletions

View file

@ -0,0 +1,239 @@
/*
* 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)
{
BLE_UpdateStatusPacket();
}
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();
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)
{
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();
}
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))
{
if (packet->damage > 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 = (uint8_t)Get_Health();
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);
}
}
}
}
}

View file

@ -0,0 +1,47 @@
/*
* 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/>.
*/
#ifndef STATE_PLAYING_H
#define STATE_PLAYING_H
inline bool STATE_IsPlayingSubstate(StateID_T state)
{
bool is_substate = false;
if ((state == STATE_PLAYING__INTERACTING) ||
(state == STATE_PLAYING__TAGGED_OUT))
{
is_substate = true;
}
return is_substate;
}
void Playing_Entry(StateMachineContext_T * context);
void Playing_Exit(StateMachineContext_T * context);
void HandleBLEStatusPacket(const BLE_StatusPacket_T * const packet);
void HandleBLETagPacket(const BLE_TagPacket_T * const packet);
/* Include Files */
#include "States/Playing/State_Playing__Interacting.h"
#include "States/Playing/State_Playing__Tagged_Out.h"
#endif // STATE_PLAYING_H

View file

@ -0,0 +1,425 @@
/*
* 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 Files */
#include "SystemK.h"
#include <stdlib.h>
#define LOG_INTERACTING_SUBSTATE
static void Playing__Interacting_Entry(StateMachineContext_T * context);
static void Playing__Interacting_Do(StateMachineContext_T * context);
static void Playing__Interacting_Exit(StateMachineContext_T * context);
static uint8_t Health_in_percent;
//! Activities for the **Interacting** substate of the **Playing** state.
const StateActivity_T STATE_PLAYING__INTERACTING_Activities =
{
.Entry = Playing__Interacting_Entry,
.Do = Playing__Interacting_Do,
.Exit = Playing__Interacting_Exit
};
static TickType_t Time_Of_Last_Shot = 0;
static const uint32_t FIXED_SHOT_HOLDOFF_in_ms = 3000;
static const uint32_t RANDOM_SHOT_HOLDOFF_in_ms = 1000;
static TickType_t Shot_Holdoff_Time;
static color_t ReceivedTagColor = 0x00000000;
static const uint32_t PURPLE_TAG_INVINCIBILITY_WINDOW_in_ms = 1000;
#ifdef LOG_INTERACTING_SUBSTATE
static const char *KLOG_TAG = "STATE_PLAYING__INTERACTING";
#endif // LOG_INTERACTING_SUBSTATE
//! Sets up the Interacting substate.
/*!
* \param context Context in which this substate is being run.
*/
static void Playing__Interacting_Entry(StateMachineContext_T * context)
{
if (STATE_IsPlayingSubstate(context->States.Previous_State) == false)
{
Playing_Entry(context);
srand(xTaskGetTickCount());
}
LOG("Entering the Interacting substate of the Playing state.");
}
//! Executes the Interacting substate.
/*!
* \param context Context in which this substate is being run.
*/
static void Playing__Interacting_Do(StateMachineContext_T * context)
{
portBASE_TYPE xStatus;
static KEvent_T Event;
xStatus = Receive_KEvent(&Event);
if (xStatus == pdPASS)
{
switch (Event.ID)
{
case KEVENT_TRIGGER_SWITCH_PRESSED:
#ifdef LOG_INTERACTING_SUBSTATE
KLOG_INFO(KLOG_TAG, "Trigger pressed.");
#endif // LOG_INTERACTING_SUBSTATE
if ((xTaskGetTickCount() - Time_Of_Last_Shot) > Shot_Holdoff_Time)
{
if (Send_Tag() != SYSTEMK_RESULT_SUCCESS)
{
KEvent_T misfire_event = { .ID = KEVENT_MISFIRE, .Data = (void *)0x00 };
Post_KEvent(&misfire_event);
}
}
else
{
KEvent_T misfire_event = { .ID = KEVENT_MISFIRE, .Data = (void *)0x00 };
Post_KEvent(&misfire_event);
}
break;
case KEVENT_TRIGGER_SWITCH_RELEASED:
#ifdef LOG_INTERACTING_SUBSTATE
KLOG_INFO(KLOG_TAG, "Trigger released.");
#endif // LOG_INTERACTING_SUBSTATE
{
uint32_t duration_of_press_in_ms = (uint32_t)Event.Data;
if (duration_of_press_in_ms > 10000)
{
AudioAction_T audio_action = {.ID = AUDIO_PLAY_ELECTRONIC_DANCE_MUSIC, .Data = (void *)0x00};
Perform_Audio_Action(&audio_action);
NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_TEST_PATTERN, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00};
xQueueSend(xQueueNeoPixels, &neopixels_action, 0);
}
// Was it a "long" press?
else if (duration_of_press_in_ms > 3000)
{
Health_in_percent = (uint8_t)Get_Health();
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);
}
}
break;
case KEVENT_UP_SWITCH_LONG_PRESSED:
{
AudioAction_T audio_action = {.ID = AUDIO_PLAY_ELECTRONIC_DANCE_MUSIC, .Data = (void *)0x00};
Perform_Audio_Action(&audio_action);
NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_TEST_PATTERN, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00};
xQueueSend(xQueueNeoPixels, &neopixels_action, 0);
}
break;
case KEVENT_UP_SWITCH_RELEASED:
{
//AudioAction_T audio_action = {.ID = AUDIO_SILENCE, .Data = (void *)0x00};
//xQueueSend(xQueueAudio, &audio_action, 0);
//NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_ALL_OFF, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00};
//xQueueSend(xQueueNeoPixels, &neopixels_action, 0);
}
break;
case KEVENT_DOWN_SWITCH_LONG_PRESSED:
{
NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_FLASHLIGHT_ON, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00};
xQueueSend(xQueueNeoPixels, &neopixels_action, 0);
}
break;
case KEVENT_DOWN_SWITCH_RELEASED:
{
NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_ALL_OFF, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00};
xQueueSend(xQueueNeoPixels, &neopixels_action, 0);
}
break;
case KEVENT_FORWARD_SWITCH_PRESSED:
{
NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_FLAMETHROWER, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00};
xQueueSend(xQueueNeoPixels, &neopixels_action, 0);
}
break;
case KEVENT_FORWARD_SWITCH_RELEASED:
{
NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_ALL_OFF, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00};
xQueueSend(xQueueNeoPixels, &neopixels_action, 0);
}
break;
case KEVENT_BACKWARD_SWITCH_PRESSED:
{
Health_in_percent = (uint8_t)Get_Health();
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);
}
break;
case KEVENT_BACKWARD_SWITCH_RELEASED:
{
NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_ALL_OFF, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00};
xQueueSend(xQueueNeoPixels, &neopixels_action, 0);
}
break;
case KEVENT_TAG_SENT:
{
// "Flip ya'? Double or nuthin'!"
uint32_t holdoff_in_ms = (rand() % 2) * RANDOM_SHOT_HOLDOFF_in_ms;
holdoff_in_ms += FIXED_SHOT_HOLDOFF_in_ms;
Shot_Holdoff_Time = (holdoff_in_ms / portTICK_PERIOD_MS);
#ifdef LOG_INTERACTING_SUBSTATE
KLOG_INFO(KLOG_TAG, "Tag sent. Holdoff: %lu ms", holdoff_in_ms);
#endif // LOG_INTERACTING_SUBSTATE
Time_Of_Last_Shot = xTaskGetTickCount();
AudioAction_T audio_action = {.ID = AUDIO_PLAY_SHOT_FIRED, .Data = (void *)0x00};
Perform_Audio_Action(&audio_action);
NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_PLAY_SHOT_FIRED, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00};
xQueueSend(xQueueNeoPixels, &neopixels_action, 0);
Increment_Shots_Fired();
}
break;
case KEVENT_TAG_RECEIVED:
{
uint8_t protocol = ((DecodedPacket_T*)Event.Data)->Tag.protocol;
uint16_t team_ID = ((DecodedPacket_T*)Event.Data)->Tag.team_ID;
uint16_t player_ID = ((DecodedPacket_T*)Event.Data)->Tag.player_ID;
uint16_t damage = ((DecodedPacket_T*)Event.Data)->Tag.damage;
ReceivedTagColor = ((DecodedPacket_T*)Event.Data)->Tag.color;
TagSensorLocation_T receiver = ((DecodedPacket_T*)Event.Data)->Tag.receiver;
TeamID_t rxd_common_team_ID = Resolve_Common_Team_ID(team_ID);
#ifdef LOG_INTERACTING_SUBSTATE
switch (receiver)
{
default:
KLOG_INFO(KLOG_TAG, "Tag from unknown sensor: Team: %u Player: %u Damage: %u", team_ID, player_ID, damage);
break;
case TAG_SENSOR_FORWARD:
KLOG_INFO(KLOG_TAG, "Tag from forward sensor: Team: %u Player: %u Damage: %u", team_ID, player_ID, damage);
break;
case TAG_SENSOR_LEFT:
KLOG_INFO(KLOG_TAG, "Tag from left sensor: Team: %u Player: %u Damage: %u", team_ID, player_ID, damage);
break;
case TAG_SENSOR_RIGHT:
KLOG_INFO(KLOG_TAG, "Tag from right sensor: Team: %u Player: %u Damage: %u", team_ID, player_ID, damage);
break;
case TAG_SENSOR_REMOTE:
KLOG_INFO(KLOG_TAG, "Tag from remote sensor: Team: %u Player: %u Damage: %u", team_ID, player_ID, damage);
break;
}
#endif // LOG_INTERACTING_SUBSTATE
if (Still_Playing() == true)
{
uint8_t my_team_ID;
uint8_t my_player_ID;
(void) SETTINGS_get_uint8_t(SYSTEMK_SETTING_TEAMID, &my_team_ID);
(void) SETTINGS_get_uint8_t(SYSTEMK_SETTING_PLAYERID, &my_player_ID);
if (Team_Can_Tag_Me(rxd_common_team_ID) == true)
{
// Checks to make sure the tagger is not seeing its own tag.
bool tag_might_be_mine = false;
if (Resolve_Common_Team_ID(team_ID) == TEAM_PURPLE)
{
if ((xTaskGetTickCount() - Time_Of_Last_Shot) < (PURPLE_TAG_INVINCIBILITY_WINDOW_in_ms / portTICK_PERIOD_MS))
{
// Special handling for receiving a Purple Tag while sending a Purple Tag using the DBQ Protocol.
if (protocol == DUBUQUE_PROTOCOL)
{
if (damage <= 20)
{
tag_might_be_mine = true;
}
else
{
// If the accumulated damage is more than 20, it means I've received my own tag AND another tag.
// Set the damage to the value of a single tag.
damage = 10;
}
}
// For protocols other than DBQ, we cannot distinguish my tag from yours. The solution is to
// allow a period of invincibility from Purple tags after sending ours.
// Note that this means for most protocols, simultaneous Purple vs. Purple results in no tags.
else
{
tag_might_be_mine = true;
}
}
}
// If the tag is clearly not mine, process it.
if (tag_might_be_mine == false)
{
Reduce_Health(damage);
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);
Increment_Tags_Received();
}
}
else if (player_ID != my_player_ID)
{
// Friendly fire!
AudioAction_T audio_action = {.ID = AUDIO_PLAY_FRIENDLY_FIRE, .Data = (void *)0x00};
Perform_Audio_Action(&audio_action);
}
}
}
FreeDecodedPacketBuffer(Event.Data);
break;
case KEVENT_TAGGED_OUT:
{
context->States.Next_State = STATE_PLAYING__TAGGED_OUT;
}
break;
case KEVENT_MISFIRE:
{
AudioAction_T audio_action = {.ID = AUDIO_PLAY_MISFIRE, .Data = (void *)0x00};
Perform_Audio_Action(&audio_action);
}
break;
case KEVENT_NEAR_MISS:
{
AudioAction_T audio_action = {.ID = AUDIO_PLAY_NEAR_MISS, .Data = (void *)0x00};
Perform_Audio_Action(&audio_action);
}
break;
case KEVENT_COMMAND_RECEIVED:
FreeDecodedPacketBuffer(Event.Data);
break;
case KEVENT_BLE_PACKET_RECEIVED:
#ifdef LOG_INTERACTING_SUBSTATE
//KLOG_INFO(KLOG_TAG, "KEVENT_BLE_PACKET_RECEIVED from %02X:%02X:%02X:%02X:%02X:%02X",
// ((BLE_Packet_T *)Event.Data)->Generic.BD_ADDR[5],
// ((BLE_Packet_T *)Event.Data)->Generic.BD_ADDR[4],
// ((BLE_Packet_T *)Event.Data)->Generic.BD_ADDR[3],
// ((BLE_Packet_T *)Event.Data)->Generic.BD_ADDR[2],
// ((BLE_Packet_T *)Event.Data)->Generic.BD_ADDR[1],
// ((BLE_Packet_T *)Event.Data)->Generic.BD_ADDR[0]
//);
#endif // LOG_INTERACTING_SUBSTATE
if (((BLE_Packet_T *)Event.Data)->Generic.type == BLE_PACKET_TYPE_STATUS)
{
HandleBLEStatusPacket((BLE_StatusPacket_T *)Event.Data);
}
else if (((BLE_Packet_T *)Event.Data)->Generic.type == BLE_PACKET_TYPE_TAG)
{
HandleBLETagPacket((BLE_TagPacket_T *)Event.Data);
}
BLE_FreePacketBuffer(Event.Data);
break;
case KEVENT_ACCESSORY_SWITCH_PRESSED:
{
#ifdef LOG_INTERACTING_SUBSTATE
uint32_t time_since_last_press_in_ms = (uint32_t)Event.Data;
KLOG_INFO(KLOG_TAG, "Accessory pressed after %lu ms.", time_since_last_press_in_ms);
#endif // LOG_INTERACTING_SUBSTATE
if (Use_Bomb_If_Available() == true)
{
// BOOM!
BLE_UpdateTagPacket(100, COLOR_YELLOW, (uint8_t[BD_ADDR_SIZE]){0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
AudioAction_T audio_action = {.ID = AUDIO_PLAY_BOMB, .Data = (void *)0x00};
Perform_Audio_Action(&audio_action);
}
else
{
AudioAction_T audio_action = {.ID = AUDIO_PLAY_BONK, .Data = (void *)0x00};
Perform_Audio_Action(&audio_action);
}
}
break;
case KEVENT_ACCESSORY_SWITCH_RELEASED:
{
#ifdef LOG_INTERACTING_SUBSTATE
uint32_t duration_of_press_in_ms = (uint32_t)Event.Data;
KLOG_INFO(KLOG_TAG, "Accessory released after %lu ms.", duration_of_press_in_ms);
#endif // LOG_INTERACTING_SUBSTATE
}
break;
case KEVENT_GAME_OVER:
{
AudioAction_T audio_action = {.ID = AUDIO_PLAY_GAME_OVER, .Data = (void *)0x00};
Perform_Audio_Action(&audio_action);
Transition_For_Event(context, STATE_WRAPPING_UP, &Event);
}
break;
default:
// All other events are ignored in this state.
ProcessUnhandledEvent(&Event);
break;
}
}
}
//! Cleans up the Interacting substate.
/*!
* \param context Context in which this substate is being run.
*/
static void Playing__Interacting_Exit(StateMachineContext_T * context)
{
if (STATE_IsPlayingSubstate(context->States.Next_State) == false)
{
Playing_Exit(context);
}
}

View file

@ -0,0 +1,32 @@
/*
* 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/>.
*/
#ifndef STATE_PLAYING__INTERACTING_H
#define STATE_PLAYING__INTERACTING_H
/* Include Files */
/* Definitions */
/* External Data */
extern const StateActivity_T STATE_PLAYING__INTERACTING_Activities;
#endif // STATE_PLAYING__INTERACTING_H

View file

@ -0,0 +1,146 @@
/*
* 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 Files */
#include "SystemK.h"
static void Playing__Tagged_Out_Entry(StateMachineContext_T * context);
static void Playing__Tagged_Out_Do(StateMachineContext_T * context);
static void Playing__Tagged_Out_Exit(StateMachineContext_T * context);
//! Activities for the **Tagged Out** substate of the **Playing** state.
const StateActivity_T STATE_PLAYING__TAGGED_OUT_Activities =
{
.Entry = Playing__Tagged_Out_Entry,
.Do = Playing__Tagged_Out_Do,
.Exit = Playing__Tagged_Out_Exit
};
//! Sets up the Tagged Out substate.
/*!
* \param context Context in which this substate is being run.
*/
static void Playing__Tagged_Out_Entry(StateMachineContext_T * context)
{
if (STATE_IsPlayingSubstate(context->States.Previous_State) == false)
{
Playing_Entry(context);
}
LOG("Entering the Tagged Out substate of the Playing state.");
AudioAction_T audio_action = {.ID = AUDIO_PLAY_TAGGED_OUT, .Data = (void *)0x00};
Perform_Audio_Action(&audio_action);
NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_TAGGED_OUT, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00};
xQueueSend(xQueueNeoPixels, &neopixels_action, 0);
BLE_UpdateStatusPacket();
Increment_Times_Tagged_Out();
}
//! Executes the Tagged Out substate.
/*!
* \param context Context in which this substate is being run.
*/
static void Playing__Tagged_Out_Do(StateMachineContext_T * context)
{
portBASE_TYPE xStatus;
static KEvent_T Event;
xStatus = Receive_KEvent(&Event);
if (xStatus == pdPASS)
{
switch (Event.ID)
{
case KEVENT_BLE_PACKET_RECEIVED:
if (((BLE_Packet_T *)Event.Data)->Generic.type == BLE_PACKET_TYPE_TAG)
{
HandleBLETagPacket((BLE_TagPacket_T *)Event.Data);
if (Back_In() == true)
{
// TODO: Get this from settings.
Set_Available_Bombs(1);
Transition_For_Event(context, STATE_PLAYING__INTERACTING, &Event);
}
}
else
{
BLE_FreePacketBuffer(Event.Data);
}
break;
case KEVENT_TRIGGER_SWITCH_RELEASED:
{
uint32_t duration_of_press_in_ms = (uint32_t)Event.Data;
// Was it a "long" press?
if (duration_of_press_in_ms > 5000)
{
Set_Health(MAX_HEALTH);
// TODO: Get this from settings.
Set_Available_Bombs(1);
Transition_For_Event(context, STATE_PLAYING__INTERACTING, &Event);
}
}
break;
case KEVENT_GAME_OVER:
{
AudioAction_T audio_action = {.ID = AUDIO_PLAY_GAME_OVER, .Data = (void *)0x00};
Perform_Audio_Action(&audio_action);
Transition_For_Event(context, STATE_WRAPPING_UP, &Event);
}
break;
case KEVENT_PLAY_PRESSED_ON_REMOTE:
Set_Health(MAX_HEALTH);
// TODO: Get this from settings.
Set_Available_Bombs(1);
Transition_For_Event(context, STATE_PLAYING__INTERACTING, &Event);
break;
default:
// All other events are ignored in this state.
ProcessUnhandledEvent(&Event);
break;
}
}
}
//! Cleans up the Tagged Out substate.
/*!
* \param context Context in which this substate is being run.
*/
static void Playing__Tagged_Out_Exit(StateMachineContext_T * context)
{
NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_ALL_OFF, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00};
xQueueSend(xQueueNeoPixels, &neopixels_action, 0);
AudioAction_T audio_action = {.ID = AUDIO_SILENCE, .Data = (void *)0x00};
Perform_Audio_Action(&audio_action);
if (STATE_IsPlayingSubstate(context->States.Next_State) == false)
{
Playing_Exit(context);
}
}

View file

@ -0,0 +1,32 @@
/*
* 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/>.
*/
#ifndef STATE_PLAYING__TAGGED_OUT_H
#define STATE_PLAYING__TAGGED_OUT_H
/* Include Files */
/* Definitions */
/* External Data */
extern const StateActivity_T STATE_PLAYING__TAGGED_OUT_Activities;
#endif // STATE_PLAYING__TAGGED_OUT_H