/** * Add lap to this participant. When the lap number is not set on Lap, * this method adds the number. * * @param Lap $lap * @return Participant */ public function addLap(Lap $lap) { // No lap number set. Set lap number because we can't function without if (!$lap->getNumber()) { // No laps yet, so is the first if (!$this->laps) { $lap->setNumber(1); } else { $lap->setNumber($this->laps[count($this->laps) - 1]->getNumber() + 1); } } $this->laps[] = $lap; return $this; }
/** * Get participants sorted by ending position * * @return array */ protected function getParticipants() { // Drivers already read if ($this->participants !== null) { // Return already read participants return $this->participants; } // Init drivers array $participants = array(); // Remember all lap positions to detect corruption later $lap_positions = array(); // Remeber all lap instances per lap number so we fix position // corruption on them $all_laps_by_lap_number = array(); // Loop each driver (if any) /* @var $driver_xml \DOMNode */ foreach ($this->dom->getElementsByTagName('Driver') as $driver_xml) { // Create new driver $main_driver = new Driver(); // Get position $position = (int) $this->dom_value('Position', $driver_xml); // Set driver values $main_driver->setName($this->dom_value('Name', $driver_xml))->setHuman((bool) $this->dom_value('isPlayer', $driver_xml)); // Create new vehicle $vehicle = new Vehicle(); // Set vehicle values $vehicle->setName($this->dom_value('VehName', $driver_xml))->setType($this->dom_value('CarType', $driver_xml))->setClass($this->dom_value('CarClass', $driver_xml))->setNumber((int) $this->dom_value('CarNumber', $driver_xml)); // Create participant $participant = Participant::createInstance(); // Set participant values $participant->setTeam($this->dom_value('TeamName', $driver_xml))->setPosition((int) $this->dom_value('Position', $driver_xml))->setClassPosition((int) $this->dom_value('ClassPosition', $driver_xml))->setGridPosition((int) $this->dom_value('GridPos', $driver_xml))->setClassGridPosition((int) $this->dom_value('ClassGridPos', $driver_xml))->setPitstops((int) $this->dom_value('Pitstops', $driver_xml)); // Has finish time if ($finish_time = (double) $this->dom_value('FinishTime', $driver_xml)) { // Overwrite total time, because rfactor results tend to be // corrupted at times $participant->setTotalTime($finish_time); } // Get finish status value $finish_status = strtolower($this->dom_value('FinishStatus', $driver_xml)); // Has finished if ($finish_status === 'finished normally') { // Set finish status $participant->setFinishStatus(Participant::FINISH_NORMAL); } elseif ($finish_status === 'dq') { // Set disqualified status $participant->setFinishStatus(Participant::FINISH_DQ); } elseif ($finish_status === 'dnf') { // Set did not finish status $participant->setFinishStatus(Participant::FINISH_DNF); // Set finish comment (if any) if ($finish_status = $this->dom_value('DNFReason', $driver_xml)) { $participant->setFinishComment($finish_status); } } else { // Set no finish status $participant->setFinishStatus(Participant::FINISH_NONE); } // Get the driver swaps $swaps_xml = $driver_xml->getElementsByTagName('Swap'); // Init drivers array, a participant can have multiple $drivers = array(); // Remember the drivers per laps $drivers_per_laps = array(); // Remember drivers by name so we can re-use them $drivers_by_name = array(); // Remember the number of swaps (always -1 of number of swap // elements in XML, because first driver starts on grid, which is // actually not really a swap) $number_of_swaps = 0; // Loop each swap $first_swap = true; // First swap reminder, can't use $swap_xml_key // to detect because it is bugged in hhvm! foreach ($swaps_xml as $swap_xml_key => $swap_xml) { // Empty driver name if (!($driver_name = $swap_xml->nodeValue)) { // Skip this swap continue; } // Driver already processed if (array_key_exists($driver_name, $drivers_by_name)) { // Use existing found driver instance $swap_driver = $drivers_by_name[$driver_name]; } else { // Create new driver $swap_driver = new Driver(); // Set name $swap_driver->setName($driver_name); // Use human state the same of main driver within XML $swap_driver->setHuman($main_driver->isHuman()); // Add swap driver to drivers array $drivers[] = $swap_driver; // Remember swap driver by name $drivers_by_name[$driver_name] = $swap_driver; } // Add swap driver to drivers per lap $drivers_per_laps[] = array('start_lap' => (int) $swap_xml->getAttribute('startLap'), 'end_lap' => (int) $swap_xml->getAttribute('endLap'), 'driver' => $swap_driver); // Not first swap element, so this is a real swap that happend // within pits if (!$first_swap) { // Increment the number of swaps $number_of_swaps++; } // Not first swap anymore $first_swap = false; } // No drivers yet, so no drivers through swap info if (!$drivers) { // Add main driver to drivers array because we could not get // it from the swap info $drivers[] = $main_driver; } // Pitcounter is lower than number of swaps if ($participant->getPitstops() < $number_of_swaps) { // Set pitstop counter to the number of swaps $participant->setPitstops($number_of_swaps); } // Add vehicle to participant $participant->setVehicle($vehicle); // Add drivers to participant $participant->setDrivers($drivers); // Remember whether the drivers are human or not from the look at // the aids $is_human_by_aids = null; // Get lap aids information and convert to friendly array $aids_xml = $driver_xml->getElementsByTagName('ControlAndAids'); $aids = array(); foreach ($aids_xml as $aid_xml) { // Match the aids $matches = array(); preg_match_all('/([a-z]+)(=([a-z0-9]))?[,]?/i', (string) $aid_xml->nodeValue, $matches); // Prepare aid items array $aid_items = array(); // Loop each matched aid if (isset($matches[1])) { foreach ($matches[1] as $key => $aid_name) { // Get value $aid_value = $matches[3][$key]; // Is float if (is_float($aid_value)) { // Cast to float $aid_value = (double) $aid_value; } elseif (is_numeric($aid_value)) { // Cast to int $aid_value = (int) $aid_value; } // Is a human player if ($aid_name === 'PlayerControl') { // Remember this driver is human $is_human_by_aids = true; } elseif (($aid_name === 'UnknownControl' or $aid_name === 'AIControl') and $is_human_by_aids === null) { // Remember this driver is not human $is_human_by_aids = false; } // Set key => value of aid $aid_items[$aid_name] = $aid_value ? $aid_value : null; } } // Add aid information per lap $aids[] = array('start_lap' => (int) $aid_xml->getAttribute('startLap'), 'end_lap' => (int) $aid_xml->getAttribute('endLap'), 'aids' => $aid_items); } // No aids if (!$aids) { // Always human $is_human_by_aids = true; } //-- Set laps // Loop each available lap /* @var $lap_xml \DOMNode */ foreach ($driver_xml->getElementsByTagName('Lap') as $lap_xml) { // Create new lap $lap = new Lap(); // Lap time zero or lower if (($lap_time = (double) $lap_xml->nodeValue) <= 0.0) { // No lap time $lap_time = null; } // Get lap position and add it to known positions $lap_positions[] = $lap_position = (int) $lap_xml->getAttribute('p'); // Elapsed seconds by default null $elapsed_seconds = null; // Valid value if ($lap_xml->getAttribute('et') !== '--.---') { // Create float value $elapsed_seconds = (double) $lap_xml->getAttribute('et'); } // Default compound values $front_compound = null; $rear_compound = null; // Front compound isset if (($fcompound = $lap_xml->getAttribute('fcompound')) !== '') { $front_compound = $fcompound; } // Rear compound isset if (($rcompound = $lap_xml->getAttribute('rcompound')) !== '') { $rear_compound = $rcompound; } // Has fuel info and is positive $fuel = NULL; if ($fuel_data = (double) $lap_xml->getAttribute('fuel') and $fuel_data > 0) { // Get proper percentage $fuel = $fuel_data * 100; } $front_compound_left_wear = NULL; if ($wear_data = (double) $lap_xml->getAttribute('twfl') and $wear_data > 0) { // Get proper percentage $front_compound_left_wear = $wear_data * 100; } $front_compound_right_wear = NULL; if ($wear_data = (double) $lap_xml->getAttribute('twfr') and $wear_data > 0) { // Get proper percentage $front_compound_right_wear = $wear_data * 100; } $rear_compound_left_wear = NULL; if ($wear_data = (double) $lap_xml->getAttribute('twrl') and $wear_data > 0) { // Get proper percentage $rear_compound_left_wear = $wear_data * 100; } $rear_compound_right_wear = NULL; if ($wear_data = (double) $lap_xml->getAttribute('twrr') and $wear_data > 0) { // Get proper percentage $rear_compound_right_wear = $wear_data * 100; } // Set lap values $lap->setTime($lap_time)->setPosition($lap_position)->setNumber((int) $lap_xml->getAttribute('num'))->setParticipant($participant)->setElapsedSeconds($elapsed_seconds)->setFrontCompound($front_compound)->setRearCompound($rear_compound)->setFrontCompoundLeftWear($front_compound_left_wear)->setFrontCompoundRightWear($front_compound_right_wear)->setRearCompoundLeftWear($rear_compound_left_wear)->setRearCompoundRightWear($rear_compound_right_wear)->setFuel($fuel)->setPitLap((bool) $lap_xml->getAttribute('pit')); // Find lap aids foreach ($aids as $aid) { // Lap match if ($aid['start_lap'] <= $lap->getNumber() and $aid['end_lap'] >= $lap->getNumber()) { // Set aids $lap->setAids($aid['aids']); // Stop searching break; } } // Find lap driver foreach ($drivers_per_laps as $driver_lap) { // Lap match if ($driver_lap['start_lap'] <= $lap->getNumber() and $driver_lap['end_lap'] >= $lap->getNumber()) { // Set driver $lap->setDriver($driver_lap['driver']); // Stop searching break; } } // No driver yet if (!$lap->getDriver()) { // Just put first driver on lap $lap->setDriver($drivers[0]); } // Add each sector available $sector = 1; while ($lap_xml->hasAttribute($sector_attribute = 's' . $sector)) { // Add sector $lap->addSectorTime((double) $lap_xml->getAttribute($sector_attribute)); // Increment number of sector $sector++; } // Add lap to participant $participant->addLap($lap); // Remember lap $all_laps_by_lap_number[$lap->getNumber()][] = $lap; } // Detected human state by aids if ($is_human_by_aids !== null) { // Force human mark on all drivers foreach ($drivers as $driver) { $driver->setHuman($is_human_by_aids); } } // Set driver to drivers array based on his position $participants[$position - 1] = $participant; } // Make positions array unique and sort it $lap_positions = array_unique($lap_positions); sort($lap_positions); // Loop each position and detect corrupted and wrong position laps $corrupted_lap_positions = array(); $wrong_lap_positions = array(); foreach ($lap_positions as $key => $lap_position) { // Lap is 10 positions higher than previous, it's a too big gap, // we consider this full corruption if ($key > 0 and $lap_position - $lap_positions[$key - 1] > 9) { // Add lap position to corrupted list $corrupted_lap_positions[] = $lap_position; } // First position aint 1 OR lap is 2 positions higher than previous, // we have some wrong lap positions if ($key === 0 and $lap_position > 1 or $key > 0 and $lap_position - $lap_positions[$key - 1] > 1) { // Add lap position to wrong list $wrong_lap_positions[] = $lap_position; } } // We have corrupted lap positions if ($corrupted_lap_positions) { // Whether we need to refill $all_laps_by_lap_number $refill_all_laps_by_lap_number = false; // Loop each participant to find out if they are really all // corrupted foreach ($participants as $participant) { // By default all laps of participant are corrupted $all_corrupted = true; // By default we have no corruption at all $corruption = false; // Loop each lap to see whether all laps are corrupted foreach ($participant->getLaps() as $lap) { // Lap position is not corrupted if (!in_array($lap->getPosition(), $corrupted_lap_positions)) { // Not all corrupted $all_corrupted = false; } else { // No position known $lap->setPosition(null); // We have corruption $corruption = true; } } // All are corrupted if ($all_corrupted) { // Unset all participant laps $participant->setLaps(array()); // We need to refill all laps by lap number array $refill_all_laps_by_lap_number = true; } } // Refill all laps by lap number array because laps are removed if ($refill_all_laps_by_lap_number) { $all_laps_by_lap_number = array(); foreach ($participants as $participant) { // Loop each lap foreach ($participant->getLaps() as $lap) { // Remember lap $all_laps_by_lap_number[$lap->getNumber()][] = $lap; } } } } //--- Fix wrong positions of laps // We have wrong lap positions, we need to fix this if ($wrong_lap_positions) { // Loop all lap numbers and their laps foreach ($all_laps_by_lap_number as $lap_number => $laps) { // Lap number 1 if ($lap_number === 1) { // Just set each lap position to grid position foreach ($laps as $lap) { $lap->setPosition($lap->getParticipant()->getGridPosition()); } // Ignore futher continue; } // Sort the laps by elapsed time $laps = Helper::sortLapsByElapsedTime($laps); // Fix the positions foreach ($laps as $lap_key => $lap) { // Set new position if it's not null (null = corruption) if ($lap->getPosition() !== null) { $lap->setPosition($lap_key + 1); } } } } // Sort participants by key ksort($participants); // Fix array keys to 0 - n $participants = array_values($participants); // Cache and return all participants return $this->participants = $participants; }