/** * @see \Simresults\Data_Reader::readSessions() */ protected function readSessions() { // Get data $data = json_decode($this->data, TRUE); // Get date preg_match('/\\d{10}/i', $data['Time'], $time_matches); $date = new \DateTime(); $date->setTimestamp($time_matches[0]); $date->setTimezone(new \DateTimeZone(self::$default_timezone)); // Get other settings $other_settings = array(); $known_setting_keys = array('Experience', 'Difficulty', 'FuelUsage', 'MechanicalDamage', 'FlagRules', 'CutRules', 'RaceSeriesFormat', 'WreckerPrevention', 'MandatoryPitstop', 'MandatoryPitstop'); foreach ($known_setting_keys as $setting) { if ($setting_value = Helper::arrayGet($data, $setting)) { $other_settings[$setting] = $setting_value; } } // Init sessions array $sessions = array(); // Gather all sessions foreach ($data['Sessions'] as $session_data) { // Init session $session = Session::createInstance(); // Practice session by default $type = Session::TYPE_PRACTICE; // Check session type switch (strtolower($name = $session_data['Type'])) { case 'qualify': $type = Session::TYPE_QUALIFY; break; case 'warmup': $type = Session::TYPE_WARMUP; break; case 'race': $type = Session::TYPE_RACE; break; } // Set session values $session->setType($type)->setDate($date)->setOtherSettings($other_settings); // Set game $game = new Game(); $game->setName('RaceRoom Racing Experience'); $session->setGame($game); // Set server $server = new Server(); $server->setName(Helper::arrayGet($data, 'Server')); $session->setServer($server); // Set track $track = new Track(); $track->setVenue(Helper::arrayGet($data, 'Track')); $session->setTrack($track); // Get participants and their best lap (only lap) $participants = array(); $players_data = Helper::arrayGet($session_data, 'Players', array()); foreach ($players_data as $player_index => $player_data) { // Create driver $driver = new Driver(); // Has name if ($name = Helper::arrayGet($player_data, 'Username') or $name = Helper::arrayGet($player_data, 'FullName')) { $driver->setName($name); } else { $driver->setName('unknown'); } // Create participant and add driver $participant = Participant::createInstance(); $participant->setDrivers(array($driver))->setPosition(Helper::arrayGet($player_data, 'Position', null)); // Has finish status if ($status = Helper::arrayGet($player_data, 'FinishStatus')) { // Figure out status switch (strtolower($status)) { case 'finished': $participant->setFinishStatus(Participant::FINISH_NORMAL); break; case 'disqualified': $participant->setFinishStatus(Participant::FINISH_DQ); break; default: $participant->setFinishStatus(Participant::FINISH_DNF); break; } } else { $participant->setFinishStatus(Participant::FINISH_NORMAL); } // Has total time if ($total_time = Helper::arrayGet($player_data, 'TotalTime')) { $participant->setTotalTime(round($player_data['TotalTime'] / 1000, 4)); } // Create vehicle and add to participant $vehicle = new Vehicle(); $vehicle->setName(Helper::arrayGet($player_data, 'Car')); $participant->setVehicle($vehicle); // Has laps if ($laps = Helper::arrayGet($player_data, 'RaceSessionLaps')) { foreach ($laps as $lap_key => $lap_data) { // Negative lap time, skip if ($lap_data['Time'] < 0) { continue; } // Init new lap $lap = new Lap(); // Set participant $lap->setParticipant($participant); // Set first driver of participant as lap driver. RR does // not support swapping $lap->setDriver($participant->getDriver()); // Set lap data $lap->setNumber($lap_key + 1); $lap->setPosition($lap_data['Position']); $lap->setPitLap($lap_data['PitStopOccured']); $lap->setTime(round($lap_data['Time'] / 1000, 4)); // Add lap to participant $participant->addLap($lap); } } elseif (0 < ($best_lap = Helper::arrayGet($player_data, 'BestLapTime'))) { // Init new lap $lap = new Lap(); // Set participant $lap->setParticipant($participant); // Set first driver of participant as lap driver. RR does // not support swapping $lap->setDriver($participant->getDriver()); // Set lap number $lap->setNumber(1); // Set lap time in seconds $lap->setTime(round($best_lap / 1000, 4)); // Add lap to participant $participant->addLap($lap); } // Add participant to collection $participants[] = $participant; } // Add participants to session $session->setParticipants($participants); // Add session to collection $sessions[] = $session; } // Return all sessions return $sessions; }
/** * @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; }