/**
  * @see \Simresults\Data_Reader::readSessions()
  */
 protected function readSessions()
 {
     // Get data
     $data = json_decode(self::cleanJSON($this->data), TRUE);
     $data = $data['stats'];
     // No session data
     if (!($history_data = $data['history'])) {
         // Throw exception
         throw new Exception\Reader('Cannot read the session data');
     }
     // Remove sessions without stages
     $history_data = array_filter($history_data, function ($data) {
         return (bool) $data['stages'];
     });
     // Get attribute info of project cars to figure out vehicle names etc
     $this->attribute_names = $this->getAttributeNames();
     // Init sessions array
     $sessions = array();
     // Loop each history item
     foreach ($history_data as $history) {
         /**
          * Collect all participants
          */
         $initial_participants_by_ref = array();
         // Depricated! TODO: Remove
         $initial_participants_by_id = array();
         // Loop all member entries and create participants
         foreach ($history['members'] as $part_ref => $part_data) {
             // Get participant
             $participant = $this->getParticipant($part_data);
             // Add participant to collection
             // $initial_participants_by_ref[$part_ref] = $participant;
             $initial_participants_by_id[$part_data['participantid']] = $participant;
         }
         // Get additional info from participants entries
         // Disabled due to duplicate refids bugs 2015-12-14
         // foreach ($history['participants'] as $part_data)
         // {
         //     // Get previously parsed participant
         //     $participant = $initial_participants_by_ref[
         //         $part_data['RefId']];
         //     // Set whether participant is human
         //     $participant->getDriver()->setHuman(
         //         (bool) $part_data['IsPlayer']);
         // }
         // Get server configuration
         $read_settings = array('DamageType', 'FuelUsageType', 'PenaltiesType', 'ServerControlsSetup', 'ServerControlsTrack', 'ServerControlsVehicle', 'ServerControlsVehicleClass', 'TireWearType');
         $session_settings = array();
         foreach ($history['setup'] as $setup_key => $setup_value) {
             if (in_array($setup_key, $read_settings)) {
                 $session_settings[$setup_key] = $setup_value;
             }
         }
         // Loop all stages data
         foreach ($history['stages'] as $type_key => $session_data) {
             // Make new unique array of participants to prevent reference
             // issues across multiple sessions
             $participants_by_ref = array();
             $participants_by_id = array();
             foreach ($initial_participants_by_ref as $part_key => $part) {
                 $participants_by_ref[$part_key] = clone $part;
             }
             foreach ($initial_participants_by_id as $part_key => $part) {
                 $participants_by_id[$part_key] = clone $part;
             }
             // Init session
             $session = Session::createInstance();
             // Practice session by default
             $type = Session::TYPE_PRACTICE;
             // Setup name for session type
             $type_setup_name = ucfirst($type_key);
             // Check session name to get type
             // TODO: Could we prevent duplicate code for this with other readers?
             switch (strtolower(preg_replace('#\\d#', '', $type_key))) {
                 case 'qualifying':
                     $type = Session::TYPE_QUALIFY;
                     $type_setup_name = 'Qualify';
                     break;
                 case 'warmup':
                     $type = Session::TYPE_WARMUP;
                     break;
                 case 'race':
                     $type = Session::TYPE_RACE;
                     break;
             }
             // Date of this session
             $date = new \DateTime();
             $date->setTimestamp($session_data['start_time']);
             $date->setTimezone(new \DateTimeZone(self::$default_timezone));
             // Set session values
             $session->setType($type)->setName($type_key)->setMaxLaps($history['setup'][$type_setup_name . 'Length'])->setDate($date)->setOtherSettings($session_settings);
             // Set game
             $game = new Game();
             $game->setName('Project Cars');
             $session->setGame($game);
             // Set server
             // TODO: Set configurations
             $server = new Server();
             $server->setName($data['server']['name']);
             $session->setServer($server);
             // Set track
             $track = new Track();
             // Have friendly track name
             if (isset($this->attribute_names['tracks'][$history['setup']['TrackId']])) {
                 $track->setVenue($this->attribute_names['tracks'][$history['setup']['TrackId']]['name']);
             } else {
                 // TODO: We should test this works too? Same for vehicles
                 // when our json attribute config is missing items
                 $track->setVenue((string) $history['setup']['TrackId']);
             }
             $session->setTrack($track);
             // Remember participants with actual events
             $participants_with_events = array();
             // Parse events first only to collect missing participants
             foreach ($session_data['events'] as $event) {
                 // Participant unknown
                 if (!isset($participants_by_id[$event['participantid']])) {
                     // Build it and fallback to less info
                     $part = $this->getParticipant($event);
                     $participants_by_id[$event['participantid']] = $part;
                 }
             }
             // Parse event data such as laps
             $cut_data = array();
             foreach ($session_data['events'] as $event) {
                 // Get participant
                 $part = $participants_by_id[$event['participantid']];
                 // Remember this participant
                 $participants_with_events[] = $part;
                 // Is lap and the lap is valid (only checked for non-race)
                 if ($event['event_name'] === 'Lap' and ($session->getType() === Session::TYPE_RACE or $event['attributes']['CountThisLapTimes'])) {
                     // Init new lap
                     $lap = new Lap();
                     // Set participant
                     $lap->setParticipant($part);
                     // Set first driver of participant as lap driver. PJ
                     // does not support swapping
                     $lap->setDriver($part->getDriver());
                     // Set total time
                     $lap->setTime(round($event['attributes']['LapTime'] / 1000, 4));
                     // Add sectors
                     for ($sector_i = 1; $sector_i <= 3; $sector_i++) {
                         $lap->addSectorTime(round($event['attributes']['Sector' . $sector_i . 'Time'] / 1000, 4));
                     }
                     // Set lap position
                     $lap->setPosition($event['attributes']['RacePosition']);
                     // Set number
                     $lap->setNumber($event['attributes']['Lap'] + 1);
                     // Add lap to participant
                     $part->addLap($lap);
                 } elseif ($event['event_name'] === 'Impact') {
                     // Other participant is unknown by default
                     $other_participant_name = 'unknown';
                     // Other participant known
                     if (-1 != ($other_id = $event['attributes']['OtherParticipantId']) and isset($participants_by_id[$other_id])) {
                         // Set other name
                         $other_participant_name = $participants_by_id[$other_id]->getDriver()->getName();
                     } else {
                         // Skip for now until we know what -1 means
                         continue;
                     }
                     $incident = new Incident();
                     $incident->setMessage(sprintf('%s reported contact with another vehicle ' . '%s. CollisionMagnitude: %s', $part->getDriver()->getName(), $other_participant_name, $event['attributes']['CollisionMagnitude']));
                     // TODO: Add elapsed time
                     $date = new \DateTime();
                     $date->setTimestamp($event['time']);
                     $incident->setDate($date);
                     $incident->setElapsedSeconds($date->getTimestamp() - $session->getDate()->getTimestamp());
                     $session->addIncident($incident);
                 } elseif (in_array($event['event_name'], array('CutTrackStart', 'CutTrackEnd'))) {
                     $cut_data[] = $event;
                 } elseif ($event['event_name'] === 'State' and $event['attributes']['NewState'] === 'Retired') {
                     $part->setFinishStatus(Participant::FINISH_DNF);
                 }
             }
             /**
              * TODO: So many duplicate code below regarding results array
              *       reading! Fix this
              */
             // Has results array we can read finish statusses from
             if ($results = $session_data['results']) {
                 // Loop each result and process the lap
                 foreach ($results as $result) {
                     // Participant not found, continue to next
                     if (!isset($participants_by_id[$result['participantid']])) {
                         continue;
                     }
                     // Has DNF state
                     if (in_array(strtolower($result['attributes']['State']), array('dnf', 'retired'))) {
                         // Get participant
                         $part = $participants_by_id[$result['participantid']];
                         // Set DNF
                         $part->setFinishStatus(Participant::FINISH_DNF);
                     }
                 }
             }
             // We did not have any events data to process but we have
             // final results. Let's use this data to atleast get 1 best
             // lap of these participants
             if (!$session_data['events'] and $results = $session_data['results']) {
                 // Loop each result and process the lap
                 foreach ($results as $result) {
                     // Participant not found, continue to next
                     if (!isset($participants_by_id[$result['participantid']])) {
                         continue;
                     }
                     // Get participant
                     $part = $participants_by_id[$result['participantid']];
                     // Remember this participant (fake it had events)
                     $participants_with_events[] = $part;
                     // Has best lap
                     if ($result['attributes']['FastestLapTime']) {
                         // Init new lap
                         $lap = new Lap();
                         // Set participant
                         $lap->setParticipant($part);
                         // Set first driver of participant as lap driver. PJ
                         // does not support swapping
                         $lap->setDriver($part->getDriver());
                         // Set total time
                         $lap->setTime(round($result['attributes']['FastestLapTime'] / 1000, 4));
                         // Set number
                         $lap->setNumber(1);
                         // Add lap to participant
                         $part->addLap($lap);
                     }
                 }
             }
             /**
              * Process cut info
              */
             foreach ($cut_data as $key => $event) {
                 // Get participant
                 $part = $participants_by_id[$event['participantid']];
                 // Start of cut and lap actually exists
                 if ($event['event_name'] === 'CutTrackStart' and $lap = $part->getLap($event['attributes']['Lap'] + 1)) {
                     // Find the end of cutting by looping following events
                     for ($end_key = $key + 1; $end_key < count($cut_data); $end_key++) {
                         $next_event = $cut_data[$end_key];
                         // Next event is another cut start. Ignore current
                         // cut as theres no proper end data
                         if ($next_event['event_name'] === 'CutTrackStart' and $next_event['participantid'] == $event['participantid']) {
                             // Theres no end
                             break;
                         } elseif ($next_event['event_name'] === 'CutTrackEnd' and $next_event['participantid'] == $event['participantid']) {
                             $cut = new Cut();
                             $cut->setCutTime(round($next_event['attributes']['ElapsedTime'] / 1000, 4));
                             $cut->setTimeSkipped(round($next_event['attributes']['SkippedTime'] / 1000, 4));
                             $date = new \DateTime();
                             $date->setTimestamp($next_event['time']);
                             $date->setTimezone(new \DateTimeZone(self::$default_timezone));
                             $cut->setDate($date);
                             $cut->setLap($lap);
                             $cut->setElapsedSeconds($date->getTimestamp() - $session->getDate()->getTimestamp());
                             $cut->setElapsedSecondsInLap(round($event['attributes']['LapTime'] / 1000, 4));
                             $lap->addCut($cut);
                             // Stop searching
                             break;
                         }
                     }
                 }
             }
             /**
              * Cleanup
              */
             $participants = $participants_by_id;
             // Remove any participant who did not participate
             foreach ($participants as $part_id => $part) {
                 // No laps and not marked as participated
                 // TODO: Make test for strict comparison (true arg), log
                 // is on Project Cars forum for test
                 if (!in_array($part, $participants_with_events, true)) {
                     unset($participants[$part_id]);
                 }
             }
             // Get participant with normal array keys
             $participants = array_values($participants);
             // Session has predefined race result positions
             // WARNING: We only do this for race sessions because for
             // qualify and practice some drivers are missing from the
             // result
             if ($results = $session_data['results'] and $session->getType() === Session::TYPE_RACE) {
                 // Sort participants using our own sort
                 $tmp_sort = Helper::sortParticipantsByTotalTime($participants);
                 // Find whether our leading participant using our own sort
                 // is in the result
                 $leading_is_in_result = false;
                 foreach ($results as $result) {
                     // Participant not found, continue to next
                     if (!isset($participants_by_id[$result['participantid']])) {
                         continue;
                     }
                     // Get participant
                     $participant = $participants_by_id[$result['participantid']];
                     // Leading found
                     if ($participant === $tmp_sort[0]) {
                         $leading_is_in_result = true;
                     }
                 }
                 // Leading participant is in the results array
                 if ($leading_is_in_result) {
                     // Init sorted result array
                     $participants_resultsorted = array();
                     foreach ($results as $result) {
                         // Participant not found, continue to next
                         if (!isset($participants_by_id[$result['participantid']])) {
                             continue;
                         }
                         // Get participant
                         $participant = $participants_by_id[$result['participantid']];
                         // Set total time
                         $participant->setTotalTime(round($result['attributes']['TotalTime'] / 1000, 4));
                         // Add to sorted array and remove from normal array
                         $participants_resultsorted[] = $participant;
                         unset($participants[array_search($participant, $participants, true)]);
                     }
                     // Sort participants not sorted by result by total time
                     $participants = Helper::sortParticipantsByTotalTime($participants);
                     // Merge the sorted participants result with normal sort
                     // array. Merge them and remove any duplicates
                     // NOTE: We are not using array_unique as it's causing
                     // recursive depedency
                     $merged = array_merge($participants_resultsorted, $participants);
                     $final = array();
                     foreach ($merged as $current) {
                         if (!in_array($current, $final, true)) {
                             $final[] = $current;
                         }
                     }
                     $participants = $final;
                 } else {
                     // Sort participants
                     $this->sortParticipantsAndFixPositions($participants, $session);
                 }
             } else {
                 // Is race
                 if ($session->getType() === Session::TYPE_RACE) {
                     // Set all participants on unknown finish status
                     // We should of had a result for proper statusses
                     foreach ($participants as $part) {
                         $part->setFinishStatus(Participant::FINISH_NONE);
                     }
                 }
                 // Sort participants
                 $this->sortParticipantsAndFixPositions($participants, $session, TRUE);
             }
             // Set participants (sorted)
             $session->setParticipants($participants);
             $sessions[] = $session;
         }
         // End stages loop
     }
     // End history loop
     // Swap warmup and race positions if wrong
     $prevous_session = null;
     foreach ($sessions as $key => $session) {
         // Found warmup after race session
         if ($prevous_session and $prevous_session->getType() === Session::TYPE_RACE and $session->getType() === Session::TYPE_WARMUP) {
             // Swap them
             $sessions[$key] = $prevous_session;
             $sessions[$key - 1] = $session;
         }
         // Remember previous session
         $prevous_session = $session;
     }
     // Return sessions
     return $sessions;
 }
Esempio n. 2
0
 /**
  * Sets the incidents on a session instance
  *
  * @param  Sesssion  $session
  */
 protected function setIncidents(Session $session)
 {
     // No incidents by default
     $incidents = array();
     // Get incidents from XML
     $incidents_dom = $this->dom->getElementsByTagName('Incident');
     // Way to many incidents!
     if ($incidents_dom->length > 2000) {
         // Create new dummy incident
         $incident = new Incident();
         $session->setIncidents(array($incident->setMessage('Sorry, way too many incidents to show!')->setDate(clone $session->getDate())));
         return;
     }
     // Loop each incident (if any)
     /* @var $incident_xml \DOMNode */
     foreach ($incidents_dom as $incident_xml) {
         // Create new incident
         $incident = new Incident();
         // Set message
         $incident->setMessage($incident_xml->nodeValue);
         // Clone session date
         $date = clone $session->getDate();
         // Add the seconds to date, ignoring any decimals
         $date->modify(sprintf('+%d seconds', (int) $incident_xml->getAttribute('et')));
         // Set real estimated seconds
         $incident->setElapsedSeconds((double) $incident_xml->getAttribute('et'));
         // Add date to incident
         $incident->setDate($date);
         // Is incident with another vehicle
         if (strpos(strtolower($incident->getMessage()), 'with another vehicle')) {
             // Match impact
             preg_match('/reported contact \\((.*)\\) with another vehicle/i', $incident->getMessage(), $matches);
             // Worth reviewing when impact is >= 60%
             $incident->setForReview(isset($matches[1]) and (double) $matches[1] >= 0.6);
         }
         // Add incident to incidents
         $incidents[] = $incident;
     }
     // Set incidents on session
     $session->setIncidents($incidents);
 }