/** * 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 = new Participant(); // 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; } // 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)->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; }
/** * @see \Simresults\Data_Reader::getSessions() */ public function getSessions() { // Get array data $data = $this->array_data; // Init sessions array $sessions = array(); // Remember last qualify session to make up grid positions $last_qualify_session = null; // Loop each session from data foreach ($data as $session_data) { // Remember which vehicles are parsed $vehicle_names = array(); // Init session $session = new Session(); // Set session type $type = null; switch ($session_data['type']) { case 'qualify': $type = Session::TYPE_QUALIFY; break; case 'practice': $type = Session::TYPE_PRACTICE; break; case 'warmup': $type = Session::TYPE_PRACTICE; break; case 'race': $type = Session::TYPE_RACE; break; } $session->setType($type); // Set session name if (isset($session_data['name'])) { $session->setName($session_data['name']); } // Set max time if (isset($session_data['time'])) { $session->setMaxMinutes($session_data['time']); } // Set max laps if (isset($session_data['laps'])) { $session->setMaxLaps($session_data['laps']); } // Set game $game = new Game(); $game->setName('Assetto Corsa'); $session->setGame($game); // Has track if (isset($session_data['track'])) { $track = new Track(); $track->setVenue($session_data['track']); $session->setTrack($track); } // Has date if (isset($session_data['date'])) { // Set it $session->setDateString($session_data['date']); } // Set server $server = new Server(); $server->setDedicated(true); if (isset($session_data['server'])) { $server->setName($session_data['server']); } else { $server->setName('Unknown'); } $session->setServer($server); // Add allowed vehicles foreach ($session_data['car_list'] as $vehicle_name) { $vehicle = new Vehicle(); $vehicle->setName($vehicle_name); $session->addAllowedVehicle($vehicle); } // Set chats foreach ($session_data['chats'] as $chat_message) { $chat = new Chat(); $chat->setMessage($chat_message); $session->addChat($chat); } // Set participants $participants = array(); foreach ($session_data['participants'] as $part_data) { // No name if (!Helper::arrayGet($part_data, 'name')) { continue; } // Create driver $driver = new Driver(); $driver->setName($part_data['name']); // Total time not greater than 0 if (0 >= ($total_time = Helper::arrayGet($part_data, 'total_time'))) { // Total time is null $total_time = null; } // Create participant and add driver $participant = new Participant(); $participant->setDrivers(array($driver))->setTotalTime($total_time); // Has total time parsed data and should not be a forced DNF if ($total_time and !Helper::arrayGet($part_data, 'force_dnf')) { $participant->setFinishStatus(Participant::FINISH_NORMAL); } else { $participant->setFinishStatus(Participant::FINISH_DNF); } // Remember vehicle instances by vehicle name $vehicles = array(); // Create vehicle and add to participant $vehicle = null; if (isset($part_data['vehicle'])) { // Init vehicle $vehicle = new Vehicle(); $vehicle->setName($part_data['vehicle']); $participant->setVehicle($vehicle); // Remember vehicle instance $vehicles[$part_data['vehicle']] = $vehicle; // Remember vehicle names for this entire session $vehicle_names[$part_data['vehicle']] = 1; } // Has team if (isset($part_data['team'])) { $participant->setTeam($part_data['team']); } // Has guid if (isset($part_data['guid'])) { $driver->setDriverId($part_data['guid']); } // Collect laps foreach (Helper::arrayGet($part_data, 'laps', array()) as $lap_i => $lap_data) { // Init new lap $lap = new Lap(); // Set participant $lap->setParticipant($participant); // Set first driver of participant as lap driver. AC does // not support swapping $lap->setDriver($participant->getDriver()); // Set lap number $lap->setNumber($lap_i + 1); // Set lap times $lap->setTime($lap_data['time']); // No lap vehicle if (!$lap_data['vehicle']) { // Just use participant vehicle if it is available if ($vehicle) { $lap->setVehicle($vehicle); } } elseif (isset($vehicles[$v = $lap_data['vehicle']])) { // Set vehicle instance $lap->setVehicle($vehicles[$v]); } else { // Init vehicle $vehicle = new Vehicle(); $vehicle->setName($lap_data['vehicle']); $lap->setVehicle($vehicle); // Remember vehicle $vehicles[$lap_data['vehicle']] = $vehicle; } // Add lap to participant $participant->addLap($lap); } // No laps and race result if (!$participant->getLaps() and $session->getType() === Session::TYPE_RACE) { // Did not finish $participant->setFinishStatus(Participant::FINISH_DNF); } // Add participant to collection $participants[] = $participant; } // Is race result if ($session->getType() === Session::TYPE_RACE) { // Sort participants by total time $participants = Helper::sortParticipantsByTotalTime($participants); } else { // Sort by best lap $participants = Helper::sortParticipantsByBestLap($participants); } // Fix participant positions foreach ($participants as $key => $part) { $part->setPosition($key + 1); } // Set participants to session $session->setParticipants($participants); // Fix elapsed seconds for all participant laps foreach ($session->getParticipants() as $participant) { $elapsed_time = 0; foreach ($participant->getLaps() as $lap) { // Set elapsed seconds and increment it $lap->setElapsedSeconds($elapsed_time); $elapsed_time += $lap->getTime(); } } // Is qualify if ($session->getType() === Session::TYPE_QUALIFY) { // Remember last qualify session $last_qualify_session = $session; } else { if ($session->getType() === Session::TYPE_RACE and $last_qualify_session) { // Get pairticpants of last qualify session and store names $last_qualify_session_participants = array(); foreach ($last_qualify_session->getParticipants() as $part) { $last_qualify_session_participants[] = $part->getDriver()->getName(); } // Loop this session participants foreach ($participants as $part) { // Found participant in qualify array if (false !== ($key = array_search($part->getDriver()->getName(), $last_qualify_session_participants))) { $part->setGridPosition($key + 1); } } } } // Fix driver positions for laps $session_lasted_laps = $session->getLastedLaps(); // Loop each lap number, beginning from 2, because we can't // figure out positions for lap 1 in AC // TODO: Duplicate code with RACE07 and AC normal reader for ($i = 2; $i <= $session_lasted_laps; $i++) { // Get laps sorted by elapsed time $laps_sorted = $session->getLapsByLapNumberSortedByTime($i); // Sort laps by elapsed time $laps_sorted = Helper::sortLapsByElapsedTime($laps_sorted); // Loop each lap and fix position data foreach ($laps_sorted as $lap_key => $lap) { // Only fix position if lap has a time, this way users of this // library can easier detect whether it's a dummy lap and // decide how to show them if ($lap->getTime() or $lap->getElapsedSeconds()) { $lap->setPosition($lap_key + 1); } } } // Only one vehicle type in this session if (count($vehicle_names) === 1) { // Find any participant without vehicle and fix missing. // This is an easy last resort fix when parsing was bugged // We assume everybody has this vehicle foreach ($session->getParticipants() as $participant) { if (!$participant->getVehicle()) { // Init vehicle $vehicle = new Vehicle(); $vehicle->setName(key($vehicle_names)); $participant->setVehicle($vehicle); } } } // Add session to collection $sessions[] = $session; } // Return all sessions return $sessions; }