By Rob Evans • 10/5/2025
This is a free-for-all "5 lives and out" style game. Last one standing wins!
include "./protocol_p2p.bst";
include "./protocol_swaptx_ir.bst";
include "./battlecore/audio.bst";
include "./battlecore/fsm.bst";
include "./battlecore/game.bst";
include "./battlecore/lights.bst";
include "./battlecore/player.bst";
include "./battlecore/screens.bst";
include "./battlecore/weapon.bst";
program {
type "game";
name "Slayer Classic";
author "Irrelon Software Limited";
version "1.0.1";
}
enum StateEnum {
STARTUP = "STARTUP",
SETUP = "SETUP",
HOME = "HOME",
CREATE_GAME = "CREATE_GAME",
JOIN_GAME = "JOIN_GAME",
JOINING_GAME = "JOINING_GAME",
GAME_LOBBY = "GAME_LOBBY",
OPTION_SELECT = "OPTION_SELECT",
SETTINGS = "SETTINGS",
PAIR_DEVICE = "PAIR_DEVICE",
IR_LEARNING_MODE = "IR_LEARNING_MODE",
IN_GAME = "IN_GAME",
DEBUG = "DEBUG",
}
enum ModuleEnum {
UNKNOWN = "UNKNOWN", // These are unknown modules, custom peripherals etc
AUDIO = "AUDIO",
FSM = "FSM",
LIGHTS = "LIGHTS",
NFC = "NFC",
UI = "UI",
SERIAL = "SERIAL",
IR = "IR",
IMU = "IMU", // Inertial Measurement Unit
RUMBLE = "RUMBLE",
GPS = "GPS",
LORA = "LORA",
P2P = "P2P",
WIFI = "WIFI",
BLE = "BLE",
INPUT_TRIGGER = "INPUT_TRIGGER",
INPUT_RELOAD = "INPUT_RELOAD",
INPUT_AUX = "INPUT_AUX",
}
enum EventEnum {
// FSM events
ENTER = "ENTER",
EXIT = "EXIT",
// Input events
UP = "UP",
DOWN = "DOWN",
PRESS = "PRESS",
// WiFi specific events
LOST = "LOST",
CONNECTED = "CONNECTED",
DISCONNECTED = "DISCONNECTED",
AUTH_FAILED = "AUTH_FAILED",
AUTH_SUCCEEDED = "AUTH_SUCCEEDED",
// General events
IN = "IN",
}
// Disable serial logging
//std::debug.setSerialLogEnabled(false);
// Tell battlecore to create digital inputs on GPIO 0, 48 and 45
//battlecore::input.createDigital(ModuleEnum.INPUT_TRIGGER, 0);
//battlecore::input.createDigital(ModuleEnum.INPUT_RELOAD, 48);
//battlecore::input.createDigital(ModuleEnum.INPUT_AUX, 45);
bool gameIsTeamBased = false;
battlecore::audio.setAudioPack("robot");
battlecore::weapon.setAmmo(5);
battlecore::weapon.setAmmoMax(5);
//every 5000 {
// std::console.log("[BattleScript] 5 seconds passed");
//}
state StateEnum.STARTUP {
module ModuleEnum.FSM {
event EventEnum.ENTER (EventData evt) => {
std::console.log("[BattleScript] Startup complete, entering HOME state");
battlecore::fsm.setState(StateEnum.HOME);
}
}
}
state StateEnum.IN_GAME {
module ModuleEnum.FSM {
event EventEnum.ENTER (EventData evt) => {
std::console.log("[BattleScript] Left state: ", evt.oldState);
std::console.log("[BattleScript] Entering state: IN_GAME");
// Update the display to the IN_GAME screen
battlecore::screens.setScreen("IN_GAME");
// TODO: This is a placeholder audio track, we need to add a track for the game start
battlecore::audio.trackStart("02 - game/001 - enemy neutralised.wav");
}
}
module ModuleEnum.INPUT_TRIGGER {
event EventEnum.DOWN (EventData evt) => {
std::console.log("[BattleScript] INPUT_TRIGGER DOWN");
// Check if the player is currently alive
// TODO: This might no longer be required. We could simply define a DEAD and RESPAWNING
// state then apply logic for those states. This is more efficient and also makes
// sense because both those states are going to have different screens displayed.
if (!battlecore::player.getIsAlive()) {
// Check if the player is already respawning
if (battlecore::player.getIsRespawning()) {
return;
}
// Check if player-initiated respawns are allowed
if (battlecore::game.getRespawnMode() == 0) {
// Player-initiated respawns are allowed, respawn now
battlecore::player.respawn();
}
// Since the player is not alive, we don't want
// to take any other action so return now
return;
}
// Check if we are currently busy (reloading, changing weapon etc)
if (battlecore::weapon.getIsBusy()) {
std::console.log("[BattleScript] Weapon busy");
return;
}
// Check if we have enough ammo
if (battlecore::weapon.getAmmo() == 0) {
std::console.log("[BattleScript] No ammo");
// We don't have enough ammo, so don't fire, play out of ammo sound
int i = std::math.randomInt(0, 2);
if (i == 0) {
battlecore::audio.trackStart("03 - shots/008 - dry shot1.wav");
} else if (i == 1) {
battlecore::audio.trackStart("03 - shots/009 - dry shot2.wav");
} else {
battlecore::audio.trackStart("03 - shots/010 - dry shot3.wav");
}
battlecore::lights.flash(255, 0, 97, 60, 1);
return;
}
// Check if the current weapon is charge-based or shot-based
if (battlecore::weapon.getIsChargeBased()) {
// Weapon is charge based, start charging
battlecore::weapon.setIsCharging(true);
return;
}
// Weapon is not charge-based, fire shot
battlecore::audio.trackStart("03 - shots/002 - sniper shot1.wav");
battlecore::lights.sweep(255, 0, 0, 60, 200);
battlecore::weapon.fire();
std::console.log("[BattleScript] Firing!");
}
event EventEnum.UP (EventData evt) => {
// If our weapon isn't charge-based, simply return and do nothing
if (!battlecore::weapon.getIsChargeBased()) {
return;
}
// Stop charging and fire shot
battlecore::weapon.setIsCharging(false);
battlecore::audio.trackStart("03 - shots/007 - laser pistol shot5.wav");
battlecore::lights.sweep(255, 0, 0, 60, 200);
battlecore::weapon.fire();
std::console.log("[BattleScript] Firing!");
}
}
module ModuleEnum.INPUT_RELOAD {
// TODO: Support gears of war style reload where timing the reload
// to the exact bar gives you a power / damage boost on the next shot
event EventEnum.DOWN (EventData evt) => {
std::console.log("[BattleScript] INPUT_RELOAD DOWN");
// Check if we are currently busy (already reloading etc)
if (battlecore::weapon.getIsBusy()) {
return;
}
// Check if we are already at full ammo
if (battlecore::weapon.getAmmo() == battlecore::weapon.getAmmoMax()) {
return;
}
battlecore::weapon.incrementBusyCount(1);
// Play reload sound
battlecore::audio.trackStart("04 - reloads/001 - pistol reload1.wav");
battlecore::lights.sweep(0, 183, 255, 60, 608, false);
after 608 {
battlecore::lights.sweep(0, 183, 255, 60, 280, true);
}
after battlecore::weapon.getReloadDelayMs() {
// Reload
battlecore::weapon.reload();
battlecore::weapon.decrementBusyCount(1);
}
}
}
module ModuleEnum.IR {
event EventEnum.IN (EventData evt) => {
// Check if we are playing a team-based game and if the hit came from our own team
if (battlecore::game.getIsTeamBased() == true && evt.teamId == battlecore::player.getTeamId()) {
return;
}
// Check if the hit came from our own tagger
if (evt.playerId == battlecore::player.getPlayerId()) {
return;
}
// Check if bullet is a damage type
if (evt.bulletTypeId == 3) {
battlecore::player.takeDamage(evt.hitValue);
return;
}
// Check if bullet is a healing type
if (evt.bulletTypeId == 4) {
battlecore::player.takeHealing(evt.hitValue);
return;
}
}
}
}
state StateEnum.DEBUG {
module ModuleEnum.FSM {
event EventEnum.ENTER (EventData evt) => {
std::console.log("[BattleScript] Leaving state: ", evt.oldState);
std::console.log("[BattleScript] Entering state: ", evt.newState);
battlecore::screens.setScreen("DEBUG");
}
}
}
state "_" {
module "_" {
event "_" (EventData evt) => {
std::console.log("[BattleScript] Event fired: ", evt.stateName, evt.moduleName, evt.eventName);
}
}
}