Laser Tag C06 – Game Variables

Love Jesus, Love Code

This tutorial will take approximately 30-60 mins.

Why dedicate a whole post to game variables?

The reason is simple: game variables shape the way taggers operate.

Every laser tag system functions differently, reflecting the priorities of its designer. When developing our laser taggers, we can either start from scratch with our game specifications or design our guns to be compatible with an existing system. I have a friend named Tom who already owns a set of MilesTag guns, so I chose to make my taggers compatible with his system. The Miles Tag system offers significant flexibility, and my goal in these tutorials is to create a tagger that adheres to the Miles Tag 2 protocol.

You can find a reference file for the protocol at https://openlasertag.org/download/miles-ii-ir-protocol-pdf/ or download it here.

The Miles Tag protocol encompasses the following IR signals:

  1. Shot packet
  2. Message packet
  3. Cloning signal

In this tutorial, we will:

  1. Include all the variables necessary to implement the Miles Tag protocol
  2. Display player names and team names instead of playerID and teamID
  3. Allow modifications to the Miles Tag variables through a web interface

Feature Cycle

We will continue to follow the feature cycle:

Before creating these new features, save your project as “LaserTag_C06_Game_Variables”.

Variables

There are no new libraries or code that need to be added to the setup() or loop() subroutines; however, there are many variables.

Please add the highlighted line below to include the milesTagPage.h.

Just under the //Game Variables add the following code:

// Game variables in order of Cloning Data 
byte volume = 0x00; //volume is from 0-5 with 0 being the loudest, this is 30 for the jq6500 module. 
byte fieldID = 0x00; // 0x00 = A, 0x01 = B
byte killLed = 0x3C; // Kill LED timeout in seconds
byte soundSet = 0x00; // 0=Military, 1 = Sci-Fi, 2 = Silenced
byte SRKDamage = 0x0F; // mapped
byte SRKRange = 0x02; // mapped
byte flag = 0x00; // FlagView ENABLE = 0x01 FlagRept ENABLE = 0x02 Team Display MILITARY = 0x08 Main Display PLAYER = 0x10
byte myGunDamage = 0x05; //Mapped
byte magSize = 0x63; // 255=unlimited
byte magazines = 0x63; //202= unlimited
byte fireSelect = 0x02; // 0=semi-auto, 1=Burst, 2=Full Auto
byte burstSize = 0x03; // RESERVED bite, assume used for burst size. Default = 3
byte roundsMin = 0x04; // Mapped // 0=250, 1=300, 2=350,..., 12 =800;
byte reloadDelay = 0x03; // seconds
byte IRPowerMode = 0x01; // (in/out) // 0=indoor, 1=outdoor
byte range = 0x06; // Assume 6 is maximum range
byte muzFlash = 0x10; // ENABLE = 0x10 Short Range Kill ENABLE = 0x02 Hit Flash ENABLE = 0x08
byte respawnHealth = 0x24; // Mapped (life) // 1=1, 2=2,... 20=20, 21=25,..., 36=100 ,  increments of 5 to 200, increments of 50 to 72=999
byte bodyArmourMin = 0x01 ; 
byte bodyArmourMax = 0x32;
byte gameMode1 = 0xB4; //Zombie Mode ENABLE = 0x02 Friendly Fire ENABLE = 0x04 Unlimited Ammo ENABLE = 0x08 Respawn Flash ENABLE = 0x10 Flag Flash ENABLE = 0x20 Game Box RoR ENABLE = 0x40 Game Box Stay ENABLE = 0x80
byte gameMode2 = 0x58; // Body Armour ENABLE = 0x02 Zombie Flash ENABLE = 0x08 Near Miss ENABLE = 0x10 Flag End ENABLE = 0x20 Ammo RoR ENABLE = 0x40
byte hitDelay = 0x00; // Seconds
byte startDelay = 0x00; // Seconds (may also be respawn delay)
byte bleedOut = 0x00; // Seconds
byte gameLength = 0x0C; // Minutes 0x0C = 12
byte zombieLife = 0x3A; // Mapped
byte zombieDamage = 0x08; // Mapped
byte bodyArmourHeal = 0x02; // pts/sec
byte medicBoxHealth = 0x32; // raw value
byte ammoBoxClips = 0x05; // raw value
byte armourBoxPoints = 0x0A; // raw value
byte gameOptions = 0x80; // Weapon RoR ENABLE = 0x08 Zombie Sounds = 0x40 Dual Muzzle ENABLE = 0x80

// Milestag GameMode Settings
bool zombieMode = 0;
bool friendlyFire = 0;
bool unlimitedAmmo = 0;
bool respawnFlash = 1;
bool flagFlash = 1;
bool gameBoxRoR = 0;
bool gameBoxStay = 1;
bool bodyArmour = 0;
bool zombieFlash = 0;
bool nearMiss = 1;
bool flagEnd = 1;
bool AmmoRoR = 1;

// array to store all settings
byte milesTagSetting[44];

New Tab – MilesTagPage.h

Create a new tab named MilesTagPage.h.

This tab/file will contain a webpage that allows us to modify all the milestag variables. Since it’s a lengthy file, I’ve provided the complete text for you to copy and paste into your project.

//use a header to store html document
#ifndef MILESTAGPAGE_H
#define MILESTAGPAGE_H

const char milestag_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html><head><title>LaserTag MilesTag Settings</title></head>
<style>
  table {
  border-collapse: collapse;
  border: 1px solid black;
}

th, td {
  text-align: left;
  padding: 8px;
}

body{
  font-family: Arial, Helvetica, sans-serif;
}

th {
  background-color: #999999;
}

tr:nth-child(even) {
  background-color: #cccccc;
}
</style>
<body>
 <H1>LaserTag MilesTag Settings</H1>
  <p>Player: %PLACEHOLDER_PlayerName% | Team</td><td> %PLACEHOLDER_TeamName% </p>
  <form action="/getMiles" target="hidden-form" onsubmit="setTimeout(function(){window.location.reload();},10);">
  <table>
  <thead><tr><th>Item</th><th>Curent Value</th><th>Value Range</th><th>Explination</th><th>Update Value</th></tr>
  </thead>
  <tbody>
    
    <tr><td> Volume </td><td> %PLACEHOLDER_volume% </td><td> 0-5 </td><td> 0 = Loudest, 5 = quietest</td><td><input type="number" name="inputVolume" value=%PLACEHOLDER_volume% min="0" max="5" step="1" pattern="\d+"></td></tr>
    <tr><td> Field ID </td><td> %PLACEHOLDER_fieldID% </td><td> 0-1 </td><td>0 = Field A, 1 = Field B</td><td> <input type="number" name="inputFieldID" value=%PLACEHOLDER_fieldID% min="0" max="1" step="1" pattern="\d+"></td></tr>
    <tr><td> kill Led </td><td> %PLACEHOLDER_killLed% </td><td> 0-60 </td><td>Kill LED timeout in seconds</td><td> <input type="number" name="inputKillLed" value=%PLACEHOLDER_killLed% min="0" max="60" step="1" pattern="\d+"></td></tr>  
    <tr><td> Sound Set </td><td> %PLACEHOLDER_soundSet% </td><td> 0-2 </td><td>0=Military, 1 = Sci-Fi, 2 = Silenced</td><td> <input type="number" name="inputSoundSet" value=%PLACEHOLDER_soundSet% min="0" max="2" step="1" pattern="\d+"></td></tr>
    <tr><td> SRK Damage </td><td> %PLACEHOLDER_SRKDamage% </td><td> 0-255 </td><td>Mapped</td><td> <input type="number" name="inputSRKDamage" value=%PLACEHOLDER_SRKDamage% min="0" max="255" step="1" pattern="\d+"></td></tr>
    <tr><td> SRK Range </td><td> %PLACEHOLDER_SRKRange% </td><td> 0-255 </td><td>Mapped</td><td> <input type="number" name="inputSRKRange" value=%PLACEHOLDER_SRKRange% min="0" max="255" step="1" pattern="\d+"></td></tr>
    <tr><td> Flag </td><td> %PLACEHOLDER_flag% </td><td>0-16 </td><td>1=Flag view enable, 2=Flag Repeat enable, 8=Team display Military, 16= Main Display Player</td><td> <input type="number" name="inputFlag" value=%PLACEHOLDER_flag% min="0" max="16" step="1" pattern="\d+"></td></tr>
    <tr><td> My Gun Damage </td><td> %PLACEHOLDER_myGunDamage% </td><td> 0-255 </td><td></td><td> <input type="number" name="inputMyGunDamage" value=%PLACEHOLDER_myGunDamage% min="0" max="255" step="1" pattern="\d+"></td></tr>
    <tr><td> Mag Size </td><td> %PLACEHOLDER_magSize% </td><td> 0-255 </td><td></td><td> <input type="number" name="inputMagSize" value=%PLACEHOLDER_magSize% min="0" max="255" step="1" pattern="\d+"></td></tr>
    <tr><td> Magazines </td><td> %PLACEHOLDER_magazines% </td><td> 0-202 </td><td></td><td> <input type="number" name="inputMagazines" value=%PLACEHOLDER_magazines% min="0" max="202" step="1" pattern="\d+"></td></tr>
    <tr><td> Fire Select </td><td> %PLACEHOLDER_fireSelect% </td><td>0-2  </td><td>0=semi-auto, 1=Burst, 2=Full Auto</td><td> <input type="number" name="inputFireSelect" value=%PLACEHOLDER_fireSelect% min="0" max="2" step="1" pattern="\d+"></td></tr>
    <tr><td> Burst Size </td><td> %PLACEHOLDER_burstSize% </td><td> 0-255</td><td> Default=3 </td><td> <input type="number" name="inputBurstSize" value=%PLACEHOLDER_burstSize% min="0" max="255" step="1" pattern="\d+"></td></tr>
    <tr><td> Rounds Min </td><td> %PLACEHOLDER_roundsMin% </td><td> 0-12</td><td> 0=250, 1= 300, 2=350 ,.., 12=800 </td><td> <input type="number" name="inputRoundsMin" value=%PLACEHOLDER_roundsMin% min="0" max="12" step="1" pattern="\d+"></td></tr>
    <tr><td> Reload Delay </td><td> %PLACEHOLDER_reloadDelay% </td><td> 0-255 </td><td> default=3 </td><td> <input type="number" name="inputReloadDelay" value=%PLACEHOLDER_reloadDelay% min="0" max="255" step="1" pattern="\d+"></td></tr>
    <tr><td> IR Power Mode </td><td> %PLACEHOLDER_IRPowerMode% </td><td>0-1 </td><td>0=indoor, 1=outdoor </td><td> <input type="number" name="inputIRPowerMode" value=%PLACEHOLDER_IRPowerMode% min="0" max="1" step="1" pattern="\d+"></td></tr>
    <tr><td> Range </td><td> %PLACEHOLDER_range% </td><td> 0-6 </td><td> 6=max range </td><td> <input type="number" name="inputRange" value=%PLACEHOLDER_range% min="0" max="6" step="1" pattern="\d+"></td></tr>
    <tr><td> Muzzle Flash </td><td> %PLACEHOLDER_muzFlash% </td><td> 0-16</td><td>16=on, 8 = hit Flash, 2 = short range kill??</td><td> <input type="number" name="inputMuzFlash" value=%PLACEHOLDER_muzFlash% min="0" max="16" step="1" pattern="\d+"></td></tr>
    <tr><td> respawnHealth </td><td> %PLACEHOLDER_respawnHealth% </td><td> 0-72 </td><td>mapped, 36=100, 72=999</td><td> <input type="number" name="inputRespawnHealth" value=%PLACEHOLDER_respawnHealth% min="0" max="72" step="1" pattern="\d+"></td></tr>
    <tr><td> Body Armour Min </td><td> %PLACEHOLDER_bodyArmourMin% </td><td> 1-100  </td><td>Minimum damage to penetrate armour</td><td> <input type="number" name="inputBodyArmourMin" value=%PLACEHOLDER_bodyArmourMin% min="1" max="100" step="1" pattern="\d+"></td></tr>
    <tr><td> Body Armour Max </td><td> %PLACEHOLDER_bodyArmourMax% </td><td> 0-255 </td><td></td><td> <input type="number" name="inputBodyArmourMax" value=%PLACEHOLDER_bodyArmourMax% min="0" max="255" step="1" pattern="\d+"></td></tr>
    <tr><td> Game Mode 1 </td><td> %PLACEHOLDER_gameMode1% </td><td> 0-255* </td><td></td><td> <input type="number" name="inputGameMode1" value=%PLACEHOLDER_gameMode1% min="0" max="255" step="1" pattern="\d+"></td></tr>
    <tr><td> Game Mode 2 </td><td> %PLACEHOLDER_gameMode2% </td><td> 0-255^ </td><td></td><td> <input type="number" name="inputGameMode2" value=%PLACEHOLDER_gameMode2% min="0" max="255" step="1" pattern="\d+"></td></tr>
    <tr><td> Hit Delay </td><td> %PLACEHOLDER_hitDelay% </td><td> 0-255 </td><td></td><td> <input type="number" name="inputHitDelay" value=%PLACEHOLDER_hitDelay% min="0" max="1" step="255" pattern="\d+"></td></tr>
    <tr><td> Start Delay </td><td> %PLACEHOLDER_startDelay% </td><td>0-255</td><td>respawn delay  </td><td> <input type="number" name="inputStartDelay" value=%PLACEHOLDER_startDelay% min="0" max="1" step="255" pattern="\d+"></td></tr>
    <tr><td> Bleed Out </td><td> %PLACEHOLDER_bleedOut% </td><td> 0-255  </td><td>After being tagged out [killed] you can still be healed during this time</td><td> <input type="number" name="inputBleedOut" value=%PLACEHOLDER_bleedOut% min="0" max="1" step="1" pattern="\d+"></td></tr>
    <tr><td> Game Length </td><td> %PLACEHOLDER_gameLength% </td><td> 0-120 </td><td>Game time in minutes, 0=No Time Limit </td><td> <input type="number" name="inputGameLength" value=%PLACEHOLDER_gameLength% min="0" max="120" step="1" pattern="\d+"></td></tr>
    <tr><td> Zombie Life </td><td> %PLACEHOLDER_zombieLife% </td><td> 1-72 </td><td>(Mapped) 36=100, 58 = 300, 72=999</td><td> <input type="number" name="inputZombieLife" value=%PLACEHOLDER_zombieLife% min="1" max="72" step="1" pattern="\d+"></td></tr>
    <tr><td> Zombie Damage </td><td> %PLACEHOLDER_zombieDamage% </td><td> 0-16  </td><td>(Mapped) 0=1, 13=50, 16=100</td><td> <input type="number" name="inputZombieDamage" value=%PLACEHOLDER_zombieDamage% min="0" max="16" step="1" pattern="\d+"></td></tr>
    <tr><td> Body Armour Heal </td><td> %PLACEHOLDER_bodyArmourHeal% </td><td> 0-100  </td><td>points/second</td><td> <input type="number" name="inputBodyArmourHeal" value=%PLACEHOLDER_bodyArmourHeal% min="0" max="100" step="1" pattern="\d+"></td></tr>
    <tr><td> Medic Box Health </td><td> %PLACEHOLDER_medicBoxHealth% </td><td> 0-100 </td><td></td><td> <input type="number" name="inputMedicBoxHealth" value=%PLACEHOLDER_medicBoxHealth% min="0" max="100" step="1" pattern="\d+"></td></tr>
    <tr><td> ammoBoxClips </td><td> %PLACEHOLDER_ammoBoxClips% </td><td> 0-30 </td><td></td><td> <input type="number" name="inputAmmoBoxClips" value=%PLACEHOLDER_ammoBoxClips% min="0" max="30" step="1" pattern="\d+"></td></tr>
    <tr><td> armourBoxPoints </td><td> %PLACEHOLDER_armourBoxPoints% </td><td> 0-100 </td><td></td><td> <input type="number" name="inputArmourBoxPoints" value=%PLACEHOLDER_armourBoxPoints% min="0" max="100" step="1" pattern="\d+"></td></tr>
    <tr><td> Game Options </td><td> %PLACEHOLDER_gameOptions% </td><td> 0-255</td><td> Weapon RoR ENABLE (+8) Zombie Sounds (+64) Dual Muzzle ENABLE (+128) </td><td> <input type="number" name="inputGameOptions" value=%PLACEHOLDER_gameOptions% min="0" max="255" step="1" pattern="\d+"></td></tr>
  </tbody></table>
    <input type="submit" value="Submit" onclick="submitMessage()">
  </form><br>
  <p> * Game Mode 1: Zombie Mode ENABLE (+2) Friendly Fire ENABLE (+4) Unlimited Ammo ENABLE (+8) Respawn Flash ENABLE (+16) Flag Flash ENABLE (+32) Game Box RoR ENABLE (+64) Game Box Stay ENABLE (+128) </p>
  <p> ^ Game Mode 2: Body Armour ENABLE (+2) Zombie Flash ENABLE (+4) Near Miss ENABLE (+16) Flag End ENABLE (+32) Ammo RoR ENABLE (+64)
  <p></p>
  <p> Zombie Mode = %PLACEHOLDER_zombieMode% </p>
  <p> Friendly Fire = %PLACEHOLDER_friendlyFire% </p>
  <p> Unlimited Ammo = %PLACEHOLDER_unlimitedAmmo% </p>
  <p> Respawn Flash = %PLACEHOLDER_respawnFlash% </p>
  <p> Flag Flash = %PLACEHOLDER_flagFlash% </p>
  <p> Game Box RoR = %PLACEHOLDER_gameBoxRoR% </p>
  <p> Game Box Stay = %PLACEHOLDER_gameBoxStay% </p>
  <p> Body Armour = %PLACEHOLDER_bodyArmour% </p>
  <p> Zombie Flash = %PLACEHOLDER_zombieFlash% </p>
  <p> Near Miss = %PLACEHOLDER_nearMiss% </p>
  <p> Flag End = %PLACEHOLDER_flagEnd% </p>
  <p> Ammo RoR = %PLACEHOLDER_AmmoRoR% </p>

  <iframe style="display:none" name="hidden-form"></iframe>
</body>
</html>
)rawliteral";

#endif

New Tab – MilesTagMapping

We also need a new tab/file to map game variables to ensure compatibility with the Miles Tag protocol. Below, I have provided the text version of the code for you to cut and paste into your project.

String playerName(int playerID){
  switch (playerID){
    case 0: return "Eagle"; break;
    case 1: return "Joker"; break;
    case 2: return "Raven"; break;
    case 3: return "Sarge"; break;
    case 4: return "Angel"; break;
    case 5: return "Cosmo"; break;
    case 6: return "Gecko"; break;
    case 7: return "Blaze"; break;
    case 8: return "Camo"; break;
    case 9: return "Fury"; break;
    case 10: return "Flash"; break;
    case 11: return "Gizmo"; break;
    case 12: return "Homer"; break;
    case 13: return "Storm"; break;
    case 14: return "Habit"; break;
    case 15: return "Click"; break;
    case 16: return "Ronin"; break;
    case 17: return "Lucky"; break;
    case 18: return "Radar"; break;
    case 19: return "Blade"; break;
    case 20: return "Ninja"; break;
    case 21: return "Magic"; break;
    case 22: return "Gonzo"; break;
    case 23: return "Cobra"; break;
    case 24: return "Pappy"; break;
    case 25: return "Rambo"; break;
    case 26: return "Snake"; break;
    case 27: return "Audie"; break;
    case 28: return "Sting"; break;
    case 29: return "Zeena"; break;
    case 30: return "Bugsy"; break;
    case 31: return "Viper"; break;
    case 32: return "Jewel"; break;
    case 33: return "Genie"; break;
    case 34: return "Logan"; break;
    case 35: return "Razor"; break;
    case 36: return "Slick"; break;
    case 37: return "Venom"; break;
    case 38: return "Rocky"; break;
    case 39: return "Saber"; break;
    case 40: return "Crush"; break;
    case 41: return "Titan"; break;
    case 42: return "Orbit"; break;
    case 43: return "Vixen"; break;
    case 44: return "Tank"; break;
    case 45: return "Rogue"; break;
    case 46: return "Sheik"; break;
    case 47: return "Gizmo"; break;
    case 48: return "Siren"; break;
    case 49: return "Dozer"; break;
    case 50: return "Micro"; break;
    case 51: return "LgtMG"; break;
    case 52: return "HvyMG"; break;
    case 53: return "ZOOKA"; break;
    case 54: return "ROCKET"; break;
    case 55: return "GRNDE"; break;
    case 56: return "CLYMR"; break;
    case 57: return "MINE"; break;
    case 58: return "BOMB"; break;
    case 59: return "NUKE"; break;
    default: return "Uknown"; break;
  }
  
}

String teamName(int teamID){
  switch (teamID){
    case 0: return "red"; break;
    case 1: return "blue"; break;
    case 2: return "yellow"; break;
    case 3: return "green"; break; 
    default: return "Unknown"; break;
  }
}

String weaponName(int myFire){
  if (myFire==0) return "Semi";
  if (myFire==1) return "Burst";
  if (myFire==2) return "Full";
  else return "Null";
}

int damageMapped(int damage){
  switch (damage){
    case 0: return 1; break;
    case 1: return 2; break;
    case 2: return 4; break;
    case 3: return 5; break;
    case 4: return 7; break;
    case 5: return 10; break;
    case 6: return 15; break;
    case 7: return 17; break;
    case 8: return 20; break;
    case 9: return 25; break;
    case 10: return 30; break;
    case 11: return 35; break;
    case 12: return 40; break;
    case 13: return 50; break;
    case 14: return 75; break;
    case 15: return 100; break;
    default: return 0;
  }
}

int healthMapped(int health){
  if (health<=20){
    return health; 
  } 
  else if (health<=200){
    health -= 20;
    return ((health * 5) + 20);
  }
  else{
    health -= 56;
    return ((health * 50) + 200);
  }
}

void milesTagSettingsToArray(){
  milesTagSetting[0] = 0x87;
  milesTagSetting[1] = 0xD4;
  milesTagSetting[2] = 0xE8;
  milesTagSetting[3] = 0x00;  //reserved
  milesTagSetting[4] = volume;
  milesTagSetting[5] = fieldID;
  milesTagSetting[6] = 0x00;  //reserved
  milesTagSetting[7] = 0x00;  //reserved
  milesTagSetting[8] = killLed; 
  milesTagSetting[9] = soundSet;
  milesTagSetting[10] = SRKDamage;
  milesTagSetting[11] = SRKRange;
  milesTagSetting[12] = flag;
  milesTagSetting[13] = myGunDamage;
  milesTagSetting[14] = magSize;
  milesTagSetting[15] = magazines;
  milesTagSetting[16] = fireSelect;
  milesTagSetting[17] = 0x03;  //reserved
  milesTagSetting[18] = roundsMin;
  milesTagSetting[19] = reloadDelay;
  milesTagSetting[20] = IRPowerMode;
  milesTagSetting[21] = range;
  milesTagSetting[22] = muzFlash;
  milesTagSetting[23] = respawnHealth;
  milesTagSetting[24] = bodyArmourMin;
  milesTagSetting[25] = 0x00;  //reserved
  milesTagSetting[26] = bodyArmourMax;
  milesTagSetting[27] = gameMode1;
  milesTagSetting[28] = gameMode2;
  milesTagSetting[29] = hitDelay;
  milesTagSetting[30] = startDelay;
  milesTagSetting[31] = bleedOut;
  milesTagSetting[32] = gameLength;
  milesTagSetting[33] = 0x00;  //reserved
  milesTagSetting[34] = zombieLife;
  milesTagSetting[35] = zombieDamage;
  milesTagSetting[36] = bodyArmourHeal;
  milesTagSetting[37] = medicBoxHealth;
  milesTagSetting[38] = ammoBoxClips;
  milesTagSetting[39] = armourBoxPoints;
  milesTagSetting[40] = gameOptions;
  milesTagSetting[41] = 0x00;  //reserved
  milesTagSetting[42] = byteArrayChecksum(milesTagSetting);  //checksum

}

byte byteArrayChecksum (byte byteArray []){
  byte checkSum = 0;
  for (int i=3; i<42; i++){
    checkSum += byteArray [i];
    //Serial.println(checkSum, HEX);
  }
  return checkSum;
}


void milesTagSettingsFromArray(){
  volume = milesTagSetting[4];
  fieldID = milesTagSetting[5];
  //reserved = milesTagSetting[6];
  //reserved = milesTagSetting[7];
  killLed = milesTagSetting[8];
  soundSet = milesTagSetting[9];
  SRKDamage = milesTagSetting[10];
  SRKRange = milesTagSetting[11];
  flag = milesTagSetting[12];
  myGunDamage = milesTagSetting[13];
  magSize = milesTagSetting[14];
  magazines = milesTagSetting[15];
  fireSelect = milesTagSetting[16];
  //reserved  = milesTagSetting[17]; //This may be burst rounds = 3
  roundsMin = milesTagSetting[18];
  reloadDelay = milesTagSetting[19];
  IRPowerMode = milesTagSetting[20];
  range = milesTagSetting[21];
  muzFlash = milesTagSetting[22];
  respawnHealth = milesTagSetting[23];
  bodyArmourMin = milesTagSetting[24];
  //reserved  = milesTagSetting[25];
  bodyArmourMax = milesTagSetting[26];
  gameMode1 = milesTagSetting[27];
  gameMode2 = milesTagSetting[28];
  hitDelay = milesTagSetting[29];
  startDelay = milesTagSetting[30];
  bleedOut = milesTagSetting[31];
  gameLength = milesTagSetting[32];
  //reserved  = milesTagSetting[33];
  zombieLife = milesTagSetting[34];
  zombieDamage = milesTagSetting[35];
  bodyArmourHeal = milesTagSetting[36];
  medicBoxHealth = milesTagSetting[37];
  ammoBoxClips = milesTagSetting[38];
  armourBoxPoints = milesTagSetting[39];
  gameOptions = milesTagSetting[40];
  //reserved  = milesTagSetting[41];
  int checksum = milesTagSetting[42];
  if (checksum == byteArrayChecksum(milesTagSetting)){
    Serial.println("checksum correct");
  }
  else{
    Serial.println("checksum fail");
  }
  zombieMode = bitRead(gameMode1, 1); Serial.println("Zombie Mode = " + String(zombieMode));
  friendlyFire = bitRead(gameMode1, 2); Serial.println("Friendly Fire = " + String(friendlyFire));
  unlimitedAmmo = bitRead(gameMode1, 3); Serial.println("Unlimited Ammo = " + String(unlimitedAmmo));
  respawnFlash = bitRead(gameMode1, 4); Serial.println("Respawn Flash = " + String(respawnFlash));
  flagFlash = bitRead(gameMode1, 5); Serial.println("Flag Flash = " + String(flagFlash));
  gameBoxRoR = bitRead(gameMode1, 6); Serial.println("Game Box RoR = " + String(gameBoxRoR));
  gameBoxStay = bitRead(gameMode1, 7); Serial.println("Game Box Stay = " + String(gameBoxStay));
  bodyArmour = bitRead(gameMode2, 1); Serial.println("Body Armour = " + String(bodyArmour));
  zombieFlash = bitRead(gameMode2, 2); Serial.println("Zombie Flash = " + String(zombieFlash));
  nearMiss = bitRead(gameMode2, 3); Serial.println("Near Miss = " + String(nearMiss));
  flagEnd = bitRead(gameMode2, 4); Serial.println("Flag End = " + String(flagEnd));
  AmmoRoR = bitRead(gameMode2, 5); Serial.println("Ammo RoR = " + String(AmmoRoR));

}

You will notice a significant amount of repetition in the code above. Try to determine the purpose of each of the different functions. However, avoid simply cutting and pasting code that you do not understand. Not all of it will be applicable to the current code; some functions will be utilised in future feature updates.

bitRead()

Each byte consists of 8 bits, allowing us to store 8 toggle settings in every byte, which can be either on or off. These values can be easily accessed using the bitRead function, as demonstrated in the code above. Explore how this function works by analysing the code.

Functions and Code

In the WebActions tab, add the following code to invoke the new milestag web page.

In the WebActions tab, add a new “server.on” command between server.on(“/”… and server.on(“/debug”,…

I have also provided you with the text version of this code for you to copy into your project.

// Send a GET request for the milestag form ---------------------------------------------------------------
  server.on("/getMiles", HTTP_GET, [] (AsyncWebServerRequest *request) {
    String inputMessage;
    // GET volume from web form
    if (request->hasParam("inputVolume")) {
      inputMessage = request->getParam("inputVolume")->value();
      volume = inputMessage.toInt(); 
    }
    // GET fieldID from web form
    if (request->hasParam("inputFieldID")) {
      inputMessage = request->getParam("inputFieldID")->value();
      fieldID = inputMessage.toInt(); 
    }
    // GET killLed from web form
    if (request->hasParam("inputKillLed")) {
      inputMessage = request->getParam("inputKillLed")->value();
      killLed = inputMessage.toInt(); 
    }
    // GET SRKDamage from web form
    if (request->hasParam("inputSRKDamage")) {
      inputMessage = request->getParam("inputSRKDamage")->value();
      SRKDamage = inputMessage.toInt(); 
    }
    // GET SRKRange from web form
    if (request->hasParam("inputSRKRange")) {
      inputMessage = request->getParam("inputSRKRange")->value();
      SRKRange = inputMessage.toInt(); 
    }
    // GET flag from web form
    if (request->hasParam("inputFlag")) {
      inputMessage = request->getParam("inputFlag")->value();
      flag = inputMessage.toInt(); 
    }
    // GET myGunDamage from web form
    if (request->hasParam("inputMyGunDamage")) {
      inputMessage = request->getParam("inputMyGunDamage")->value();
      myGunDamage = inputMessage.toInt(); 
    }
    // GET magSize from web form
    if (request->hasParam("inputMagSize")) {
      inputMessage = request->getParam("inputMagSize")->value();
      magSize = inputMessage.toInt(); 
    }
    // GET magazines from web form
    if (request->hasParam("inputMagazines")) {
      inputMessage = request->getParam("inputMagazines")->value();
      magazines = inputMessage.toInt(); 
    }
    // GET fireSelect from web form
    if (request->hasParam("inputFireSelect")) {
      inputMessage = request->getParam("inputFireSelect")->value();
      fireSelect = inputMessage.toInt(); 
    }
    // GET burstSize from web form
    if (request->hasParam("inputBurstSize")) {
      inputMessage = request->getParam("inputBurstSize")->value();
      burstSize = inputMessage.toInt(); 
    }
    // GET roundsMin from web form
    if (request->hasParam("inputRoundsMin")) {
      inputMessage = request->getParam("inputRoundsMin")->value();
      roundsMin = inputMessage.toInt(); 
    }
    // GET reloadDelay from web form
    if (request->hasParam("inputReloadDelay")) {
      inputMessage = request->getParam("inputReloadDelay")->value();
      reloadDelay = inputMessage.toInt(); 
    }
    // GET IRPowerMode from web form
    if (request->hasParam("IRPowerMode")) {
      inputMessage = request->getParam("IRPowerMode")->value();
      IRPowerMode = inputMessage.toInt(); 
    }
    // GET range from web form
    if (request->hasParam("inputRange")) {
      inputMessage = request->getParam("inputRange")->value();
      range = inputMessage.toInt(); 
    }
    // GET muzFlash from web form
    if (request->hasParam("inputMuzFlash")) {
      inputMessage = request->getParam("inputMuzFlash")->value();
      muzFlash = inputMessage.toInt(); 
    }
    // GET respawnHealth from web form
    if (request->hasParam("inputRespawnHealth")) {
      inputMessage = request->getParam("inputRespawnHealth")->value();
      respawnHealth = inputMessage.toInt(); 
    }
    // GET bodyArmourMin from web form
    if (request->hasParam("inputBodyArmourMin")) {
      inputMessage = request->getParam("inputBodyArmourMin")->value();
      bodyArmourMin = inputMessage.toInt(); 
    }
    // GET bodyArmourMax from web form
    if (request->hasParam("inputBodyArmourMax")) {
      inputMessage = request->getParam("inputBodyArmourMax")->value();
      bodyArmourMax = inputMessage.toInt(); 
    }
    // GET gameMode1 from web form
    if (request->hasParam("inputGameMode1")) {
      inputMessage = request->getParam("inputGameMode1")->value();
      gameMode1 = inputMessage.toInt(); 
    }
    // GET gameMode2 from web form
    if (request->hasParam("inputGameMode2")) {
      inputMessage = request->getParam("inputGameMode2")->value();
      gameMode2 = inputMessage.toInt(); 
    }
    // GET hitDelay from web form
    if (request->hasParam("inputHitDelay")) {
      inputMessage = request->getParam("inputHitDelay")->value();
      hitDelay = inputMessage.toInt(); 
    }
    // GET startDelay from web form
    if (request->hasParam("inputStartDelay")) {
      inputMessage = request->getParam("inputStartDelay")->value();
      startDelay = inputMessage.toInt(); 
    }
    // GET bleedOut from web form
    if (request->hasParam("inputBleedOut")) {
      inputMessage = request->getParam("inputBleedOut")->value();
      bleedOut = inputMessage.toInt(); 
    }
    // GET gameLength from web form
    if (request->hasParam("inputGameLength")) {
      inputMessage = request->getParam("inputGameLength")->value();
      gameLength = inputMessage.toInt(); 
    }
    // GET zombieLife from web form
    if (request->hasParam("inputZombieLife")) {
      inputMessage = request->getParam("inputZombieLife")->value();
      zombieLife = inputMessage.toInt(); 
    }
    // GET zombieDamage from web form
    if (request->hasParam("inputZombieDamage")) {
      inputMessage = request->getParam("inputZombieDamage")->value();
      zombieDamage = inputMessage.toInt(); 
    }
    // GET bodyArmourHeal from web form
    if (request->hasParam("inputBodyArmourHeal")) {
      inputMessage = request->getParam("inputBodyArmourHeal")->value();
      bodyArmourHeal = inputMessage.toInt(); 
    }
    // GET medicBoxHealth from web form
    if (request->hasParam("inputMedicBoxHealth")) {
      inputMessage = request->getParam("inputMedicBoxHealth")->value();
      medicBoxHealth = inputMessage.toInt(); 
    }
    // GET ammoBoxClips from web form
    if (request->hasParam("inputAmmoBoxClips")) {
      inputMessage = request->getParam("inputAmmoBoxClips")->value();
      ammoBoxClips = inputMessage.toInt(); 
    }
    // GET armourBoxPoints from web form
    if (request->hasParam("inputArmourBoxPoints")) {
      inputMessage = request->getParam("inputArmourBoxPoints")->value();
      armourBoxPoints = inputMessage.toInt(); 
    }
    // GET gameOptions from web form
    if (request->hasParam("inputGameOptions")) {
      inputMessage = request->getParam("inputGameOptions")->value();
      gameOptions = inputMessage.toInt(); 
    }
  });
  // End of code for send a GET request for the form ---------------------------------------------------------------

You may have noticed that I included lines like the following:

  // End of code for send a GET request for the form ---------------------------------------------------------------

These lines help indicate the start and end of the form requests, making it easier to navigate the code, especially given its length.

In the WebActions Tab, replace the processor function. I have also provided a text version for you to copy directly into your project.

String processor(const String& var){
  //game variable placehoders
  if(var == "PLACEHOLDER_PlayerID"){return String(playerID);}
  else if(var == "PLACEHOLDER_PlayerName"){return playerName(playerID);}
  else if(var == "PLACEHOLDER_TeamID"){return String(teamID);}
  else if(var == "PLACEHOLDER_TeamName"){return teamName(teamID);}
  //game variable placehoders
  else if(var == "PLACEHOLDER_volume") return String(volume); 
  else if(var == "PLACEHOLDER_fieldID") return String(fieldID);
  else if(var == "PLACEHOLDER_killLed") return String(killLed);
  else if(var == "PLACEHOLDER_soundSet") return String(soundSet);
  else if(var == "PLACEHOLDER_SRKDamage") return String(SRKDamage);
  else if(var == "PLACEHOLDER_SRKRange") return String(SRKRange);
  else if(var == "PLACEHOLDER_flag") return String(flag);
  else if(var == "PLACEHOLDER_myGunDamage") return String(myGunDamage);
  else if(var == "PLACEHOLDER_magSize") return String(magSize);
  else if(var == "PLACEHOLDER_magazines") return String(magazines);
  else if(var == "PLACEHOLDER_fireSelect") return String(fireSelect);
  else if(var == "PLACEHOLDER_burstSize") return String(burstSize);
  else if(var == "PLACEHOLDER_roundsMin") return String(roundsMin);
  else if(var == "PLACEHOLDER_reloadDelay") return String(reloadDelay);
  else if(var == "PLACEHOLDER_IRPowerMode") return String(IRPowerMode);
  else if(var == "PLACEHOLDER_range") return String(range);
  else if(var == "PLACEHOLDER_muzFlash") return String(muzFlash);
  else if(var == "PLACEHOLDER_respawnHealth") return String(respawnHealth);
  else if(var == "PLACEHOLDER_bodyArmourMin") return String(bodyArmourMin);
  else if(var == "PLACEHOLDER_bodyArmourMax") return String(bodyArmourMax);
  else if(var == "PLACEHOLDER_gameMode1") return String(gameMode1);
  else if(var == "PLACEHOLDER_gameMode2") return String(gameMode2);
  else if(var == "PLACEHOLDER_hitDelay") return String(hitDelay);
  else if(var == "PLACEHOLDER_startDelay") return String(startDelay);
  else if(var == "PLACEHOLDER_bleedOut") return String(bleedOut);
  else if(var == "PLACEHOLDER_gameLength") return String(gameLength);
  else if(var == "PLACEHOLDER_zombieLife") return String(zombieLife);
  else if(var == "PLACEHOLDER_zombieDamage") return String(zombieDamage);
  else if(var == "PLACEHOLDER_bodyArmourHeal") return String(bodyArmourHeal);
  else if(var == "PLACEHOLDER_medicBoxHealth") return String(medicBoxHealth);
  else if(var == "PLACEHOLDER_ammoBoxClips") return String(ammoBoxClips);
  else if(var == "PLACEHOLDER_armourBoxPoints") return String(armourBoxPoints);
  else if(var == "PLACEHOLDER_gameOptions") return String(gameOptions);
  //gamemode placeholders
  else if(var == "PLACEHOLDER_zombieMode") return String(zombieMode);
  else if(var == "PLACEHOLDER_friendlyFire") return String(friendlyFire);
  else if(var == "PLACEHOLDER_unlimitedAmmo") return String(unlimitedAmmo);
  else if(var == "PLACEHOLDER_respawnFlash") return String(respawnFlash);
  else if(var == "PLACEHOLDER_flagFlash") return String(flagFlash);
  else if(var == "PLACEHOLDER_gameBoxRoR") return String(gameBoxRoR);
  else if(var == "PLACEHOLDER_gameBoxStay") return String(gameBoxStay);
  else if(var == "PLACEHOLDER_bodyArmour") return String(bodyArmour);
  else if(var == "PLACEHOLDER_zombieFlash") return String(zombieFlash);
  else if(var == "PLACEHOLDER_nearMiss") return String(nearMiss);
  else if(var == "PLACEHOLDER_flagEnd") return String(flagEnd);
  else if(var == "PLACEHOLDER_AmmoRoR") return String(AmmoRoR);
  return String();
}

You will notice that I have condensed the commands in the code above. Typically, an “if” statement would occupy three lines; however, to enhance readability, I have placed it all on one line. C++ does not concern itself with line breaks and spaces, making this possible.

In the indexPage.h tab, make the following changes to display the player name instead of the player ID.

Complie the changes

This little change should make the main webpage behave differently as displayed below:

The new MilesTag Settings Page should look like:

Christian Content

1 Corinthians 14:6–12 (HCSB)
6 But now, brothers, if I come to you speaking in other languages, how will I benefit you unless I speak to you with a revelation or knowledge or prophecy or teaching? 7 Even inanimate things that produce sounds—whether flute or harp —if they don’t make a distinction in the notes, how will what is played on the flute or harp be recognised? 8 In fact, if the trumpet makes an unclear sound, who will prepare for battle? 9 In the same way, unless you use your tongue for intelligible speech, how will what is spoken be known? For you will be speaking into the air. 10 There are doubtless many different kinds of languages in the world, and all have meaning. 11 Therefore, if I do not know the meaning of the language, I will be a foreigner to the speaker, and the speaker will be a foreigner to me. 12 So also you—since you are zealous for spiritual gifts, seek to excel in building up the church.

A common language that everyone understands is essential for effective communication. When we talk about the Miles Tag 2 protocol, we are referring to a standardised language that laser tag guns use. This protocol specifies important information, such as the meaning of each number in a shot packet and the association between a player ID and a player name. If guns with different protocols are introduced into the game, they will not be able to understand each other, resulting in a failed game. In such a scenario, shooting would be pointless, as the IR signals would be unintelligible. It would be as if you were shooting into thin air, rendering game goals useless and reducing your guns to nothing more than cheap light blasters.

It is unfortunate that not all laser tag guns use the same protocol, making it challenging to add guns from different brands or sources to the game. However, our guns are compatible with the Miles Tag 2 system, and if you learn programming, you may be able to adapt the code to work with other systems.

In this passage, the Bible refers to people speaking different languages, whether Greek, Japanese, or even angelic tongues. Paul’s point is that understanding between individuals is crucial in the church. Otherwise, as stated in verse 9, speaking becomes meaningless. A church cannot function effectively if its members cannot understand each other. Therefore, it is important to ensure that communication is clear and comprehensible.

Churches vary greatly, and even experienced churchgoers can feel like outsiders when entering an unfamiliar church. Without proper explanation, it can be difficult to comprehend the purpose of certain practices. Some churches excel at ensuring everyone understands, while others assume prior knowledge, leaving many things shrouded in mystery for extended periods.

To facilitate understanding, churches implement various strategies, such as:

  1. Offering children’s programs that use simpler language to help young ones comprehend.
  2. Conducting Bible classes before church for individuals whose second language is English, allowing them to familiarise themselves with the passage before the sermon.
  3. Organising classes for those exploring the faith, where they can ask questions for clarification.
  4. Hosting Bible studies that encourage discussion and questions about the sermon.

Consider whether there are individuals in your church who would benefit from assistance in understanding the messages conveyed during church gatherings. As Paul said, let us strive to build up the church.

The conclusion of 1 Corinthians 14 emphasises the importance of eagerly desiring the gift of prophecy. While not everyone may possess this spiritual gift, most Christians can read God’s word and assist others in understanding it. In this way, we serve as mouthpieces for God, acting like prophets.

Communication is important. It is needed for:

  1. Laser taggers to communicate with each other for an effective game.
  2. People in a church to communicate with each other to maintain a healthy church.

=

Leave a Reply