<?php
class ABGap extends Plugins
{
const URL = 'https://www.lfs.net/forum/thread/89031-ABGap-%28code-dump%2C-unsupported%29';
const NAME = 'ABGap';
const AUTHOR = 'notanillusion';
const VERSION = '0.0.6';
const DESCRIPTION = 'Gap delta to car ahead and behind';
public function __construct() {
$this->registerPacket('onLap', ISP_LAP); // record times, display gaps
$this->registerPacket('onMci', ISP_MCI); // update positions
$this->registerPacket('onNpl', ISP_NPL); // add new plids
$this->registerPacket('onPll', ISP_PLL); // remove unusued plids
$this->registerPacket('onRst', ISP_RST); // update total splits and laps, fresh db table
$this->registerPacket('onSpx', ISP_SPX); // record times, display gaps
$this->registerPacket('onSta', ISP_STA); // update viewplid
$this->consts = array(
'GAP_AHEAD' => -1,
'GAP_BEHIND' => 1,
'SPLIT_LAP' => 255
);
$this->db = $this->newDb();
if (!$this->db)
die('Failed to create database.'.PHP_EOL);
$result = $this->createTable($this->db);
if (!$result)
die('Failed to create table.'.PHP_EOL);
$this->playerData = array(); // plid => array(pos, lap), ...
$this->viewPlid = 0;
$this->laps = 0;
$this->splits = 0;
}
public function __destruct() {
$this->db->close();
}
/// *** PACKET FUNCTIONS START *** ///
public function onLap(IS_LAP $lap) {
if (!count($this->playerData)) // ignore hotlap mode
return;
$pos = $this->playerData[$lap->PLID]['pos'];
$q = "INSERT INTO timing (plid, lap, split, pos, time)
VALUES ($lap->PLID, $lap->LapsDone, ".$this->consts['SPLIT_LAP'].", $pos, $lap->ETime)";
$result = $this->db->exec($q);
// Don't check for gap ahead if we're 1st.
if ($lap->PLID === $this->viewPlid && $this->playerData[$this->viewPlid]['pos'] > 1) {
$plidAhead = $this->getPlidAhead($pos, $lap->LapsDone, $this->consts['SPLIT_LAP']);
// Sometimes when cars are really close to one another across a split, the SPX for the car behind in positions
// can arrive first. Don't display gap when that happens.
if ($plidAhead === null) {
return;
}
$this->sendGap($plidAhead, $this->consts['GAP_AHEAD'], $lap->LapsDone, $this->consts['SPLIT_LAP']);
}
// Don't check for gap behind if we're last.
if (!$this->isLastPlaceByPlid($this->viewPlid)) {
if ($this->getPlidAhead($pos, $lap->LapsDone, $this->consts['SPLIT_LAP']) == $this->viewPlid)
$this->sendGap($lap->PLID, $this->consts['GAP_BEHIND'], $lap->LapsDone, $this->consts['SPLIT_LAP']);
}
}
public function onMci(IS_MCI $mci) {
foreach ($mci->Info as $compcar) {
if (!$compcar->Position)
continue;
$this->playerData[$compcar->PLID]['pos'] = $compcar->Position;
$this->playerData[$compcar->PLID]['lap'] = $compcar->Lap;
}
}
public function onNpl(IS_NPL $npl) {
$this->playerData[$npl->PLID]['pos'] = $npl->NumP;
$this->playerData[$npl->PLID]['lap'] = 1;
}
public function onPll(IS_PLL $pll) {
unset($this->playerData[$pll->PLID]);
}
public function onRst(IS_RST $rst) {
$this->laps = $rst->RaceLaps;
$this->splits = $this->getNumSplitsRst($rst);
$this->playerData = array();
$result = $this->createTable($this->db);
if (!$result) {
echo('Failed to create table.'.PHP_EOL);
return;
}
}
public function onSpx(IS_SPX $spx) {
if (!count($this->playerData))
return;
$pos = $this->playerData[$spx->PLID]['pos'];
$lap = $this->playerData[$spx->PLID]['lap'];
if (!$lap) $lap++;
$q = "INSERT INTO timing (plid, lap, split, pos, time)
VALUES ($spx->PLID, $lap, $spx->Split, {$this->playerData[$spx->PLID]['pos']}, $spx->ETime)";
$result = $this->db->exec($q);
if ($spx->PLID === $this->viewPlid && $this->playerData[$this->viewPlid]['pos'] > 1) {
$plidAhead = $this->getPlidAhead($pos, $lap, $spx->Split);
if ($plidAhead === null) {
return;
}
$this->sendGap($plidAhead, $this->consts['GAP_AHEAD'], $lap, $spx->Split);
}
if (!$this->isLastPlaceByPlid($this->viewPlid)) {
if ($this->getPlidAhead($pos, $lap, $spx->Split) == $this->viewPlid) {
$this->sendGap($spx->PLID, $this->consts['GAP_BEHIND'], $lap, $spx->Split);
}
}
}
public function onSta(IS_STA $sta) {
$this->viewPlid = $sta->ViewPLID;
}
/// *** PACKET FUNCTIONS END *** ///
function createTable($db) {
$q = 'DROP TABLE timing';
@$this->db->exec($q);
$q = 'CREATE TABLE timing(
id INTEGER PRIMARY KEY ASC,
plid INTEGER,
lap INTEGER,
split INTEGER,
pos INTEGER,
time INTEGER
)';
$result = $this->db->exec($q);
return $result;
}
function getNumSplitsRst($rst) {
$splits = 0;
for ($i = 3; $i > 0; $i--) {
if ($rst->{"Split$i"} && $rst->{"Split$i"} != 65535) {
$splits = $i;
break;
}
}
return $splits;
}
function getPlidAhead($pos, $lap, $split) {
$pos--;
$q = "SELECT plid FROM timing WHERE pos=$pos AND lap=$lap AND split=$split;";
$result = $this->db->query($q);
if ($result === false)
return;
$row = $result->fetchArray();
return $row['plid'];
}
function getTime($plid, $lap, $split) {
$q = "SELECT * FROM timing WHERE plid=$plid AND lap=$lap AND split=$split;";
$result = $this->db->query($q);
$time = 0;
while($row = $result->fetchArray()) {
if (array_key_exists('time', $row))
$time = $row['time'];
}
return $time;
}
function isLastPlaceByPlid($plid) {
if ($this->playerData[$plid]['pos'] >= count($this->playerData)) {
return true;
}
return false;
}
function ms2str ($ms, $ahead) {
$retval = '';
$hour = abs($ms / (1000 * 60 * 60));
if ($hour >= 1) {
$retval .= strval(intval($hour)) . ':';
}
$min = ($hour - intval($hour)) * 60;
if ($hour >= 1 && $min < 1) {
$retval .= '00:';
}
else if ($min >= 10) {
$retval .= strval(intval($min)) . ':';
}
else if ($min > 1 && $min < 10) {
$retval .= '0' . strval(intval($min)) . ':';
}
$sec = ($min - intval($min)) * 60;
$retval .= number_format($sec, 3);
if ($ms >= 0 && $ahead < 0) {
$retval = "^1+$retval";
}
if ($ms < 0 && $ahead < 0) {
$retval = "^2-$retval";
}
if ($ms >= 0 && $ahead > 0) {
$retval = "^1-$retval";
}
if ($ms < 0 && $ahead > 0) {
$retval = "^2+$retval";
}
return $retval;
}
function newDb() {
$db = new SQLite3(':memory:');
return $db;
}
function getGap($a, $b, $lap, $split) {
$plidACurTime = $this->getTime($a, $lap, $split);
$plidBCurTime = $this->getTime($b, $lap, $split);
$gap = $plidACurTime - $plidBCurTime;
return $gap;
}
function sendGap($plid, $mode, $lap, $split) {
if ($split === false)
return;
if ($this->splits === 0) {
$split = 255;
}
$curGap = $this->getGap($this->viewPlid, $plid, $lap, $split);
switch ($split) {
case 255:
$split = $this->splits;
break;
case 1:
$split = 255;
$lap--;
break;
default:
if ($this->splits === 0) {
$lap--;
} else {
$split--;
}
break;
}
$prevGap = $this->getGap($this->viewPlid, $plid, $lap, $split);
$gapDelta = $curGap - $prevGap;
if ($mode == $this->consts['GAP_BEHIND']) {
$msg = '^3Gap delta to car behind: ';
} else {
$msg = '^3Gap delta to car ahead: ';
}
$timemsg = $this->ms2str($gapDelta, $mode);
$msg .= $timemsg;
IS_MSL()->Msg($msg)->Send();
}
}
?>