/**
* Copyright (C) 2009-2014 Cars and Tracks Development Project (CTDP).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package net.ctdp.rfdynhud.gamedata;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import net.ctdp.rfdynhud.editor.EditorPresets;
import net.ctdp.rfdynhud.util.AbstractThreeLetterCodeGenerator;
import net.ctdp.rfdynhud.util.RFDHLog;
import net.ctdp.rfdynhud.util.ThreeLetterCodeGenerator;
import net.ctdp.rfdynhud.util.ThreeLetterCodeManager;
import net.ctdp.rfdynhud.util.TimingUtil;
/**
*
* @author Marvin Froehlich (CTDP)
*/
public abstract class ScoringInfo
{
protected final LiveGameData gameData;
protected final _LiveGameDataObjectsFactory gdFactory;
private boolean updatedInTimeScope = false;
private long updateId = 0L;
private long updateTimestamp = -1L;
private int sessionId = 0;
private boolean sessionRunning = false;
private int sessionJustStarted = 0;
private long sessionStartTimestamp = -1L;
private long cockpitEnteredTimestamp = -1L;
private int cockpitEnteredId = 0;
private boolean gamePausedCache = false;
private long lastUpdateTimestamp = -1L;
private long sessionBaseNanos = -1L;
private long extrapolationNanos = 0L;
private float extrapolationTime = 0.0f;
private long sessionNanos = -1L;
private float sessionTime = 0.0f;
private float trackLength = -1f;
private double raceLengthPercentage = 1.0;
private boolean classScoringCalculated = false;
public static interface ScoringInfoUpdateListener extends LiveGameData.GameDataUpdateListener
{
public void onScoringInfoUpdated( LiveGameData gameData, boolean isEditorMode );
public void onPlayerJoined( LiveGameData gameData, VehicleScoringInfo joinedVSI, boolean rejoined );
public void onPlayerLeft( LiveGameData gameData, Integer vsiID );
}
private ScoringInfoUpdateListener[] updateListeners = null;
public void registerListener( ScoringInfoUpdateListener l )
{
if ( updateListeners == null )
{
updateListeners = new ScoringInfoUpdateListener[] { l };
}
else
{
for ( int i = 0; i < updateListeners.length; i++ )
{
if ( updateListeners[i] == l )
return;
}
ScoringInfoUpdateListener[] tmp = new ScoringInfoUpdateListener[ updateListeners.length + 1 ];
System.arraycopy( updateListeners, 0, tmp, 0, updateListeners.length );
updateListeners = tmp;
updateListeners[updateListeners.length - 1] = l;
}
gameData.registerDataUpdateListener( l );
}
public void unregisterListener( ScoringInfoUpdateListener l )
{
if ( updateListeners == null )
return;
int index = -1;
for ( int i = 0; i < updateListeners.length; i++ )
{
if ( updateListeners[i] == l )
{
index = i;
break;
}
}
if ( index < 0 )
return;
if ( updateListeners.length == 1 )
{
updateListeners = null;
return;
}
ScoringInfoUpdateListener[] tmp = new ScoringInfoUpdateListener[ updateListeners.length - 1 ];
if ( index > 0 )
System.arraycopy( updateListeners, 0, tmp, 0, index );
if ( index < updateListeners.length - 1 )
System.arraycopy( updateListeners, index + 1, tmp, index, updateListeners.length - index - 1 );
updateListeners = tmp;
gameData.unregisterDataUpdateListener( l );
}
public abstract void readFromStream( InputStream in, EditorPresets editorPresets ) throws IOException;
/**
* Read default values. This is usually done in editor mode.
*
* @param editorPresets <code>null</code> in non editor mode
*/
public abstract void loadDefaultValues( EditorPresets editorPresets );
public abstract void writeToStream( OutputStream out ) throws IOException;
private VehicleScoringInfo[] vehicleScoringInfoCache = null;
private VehicleScoringInfo[] vehicleScoringInfo = null;
private int numVehicles = -1;
private boolean fixedViewedVSI = false;
private VehicleScoringInfo playerVSI = null;
private VehicleScoringInfo viewedVSI = null;
private VehicleScoringInfo controlledViewedVSI = null;
private VehicleScoringInfo fastestLapVSI = null;
private VehicleScoringInfo secondFastestLapVSI = null;
private VehicleScoringInfo fastestSector1VSI = null;
private VehicleScoringInfo fastestSector2VSI = null;
private VehicleScoringInfo fastestSector3VSI = null;
private VehicleScoringInfo controlledCompareVSI = null;
private String playerName = null;
private String playerFilename = null;
private String trackName = null;
private ThreeLetterCodeGenerator tlcGenerator;
private final Map<Integer, VehicleScoringInfo> oldIdVSIMap = new HashMap<Integer, VehicleScoringInfo>();
/**
* Gets the i-th (unsorted) {@link VehicleScoringInfo} instance in this {@link ScoringInfo}.
*
* @param i
*
* @return the i-th (unsorted) {@link VehicleScoringInfo} instance in this {@link ScoringInfo}.
*/
protected final VehicleScoringInfo getCachedVehicleScoringInfo( int i )
{
if ( ( vehicleScoringInfoCache == null ) || ( vehicleScoringInfoCache.length == 0 ) )
return ( null );
return ( vehicleScoringInfoCache[i] );
}
private void initVehicleScoringInfo( int numVehicles )
{
if ( sessionJustStarted == 1 )
this.sessionJustStarted = 2;
this.numVehicles = numVehicles;
if ( ( vehicleScoringInfoCache == null ) || ( vehicleScoringInfoCache.length < numVehicles ) )
{
vehicleScoringInfoCache = gdFactory.newVehicleScoringInfos( gameData, (int)( numVehicles * 1.5 ) + 1, vehicleScoringInfoCache );
}
if ( ( vehicleScoringInfo == null ) || ( vehicleScoringInfo.length != numVehicles ) )
{
vehicleScoringInfo = new VehicleScoringInfo[ numVehicles ];
System.arraycopy( vehicleScoringInfoCache, 0, vehicleScoringInfo, 0, numVehicles );
}
}
/**
*
* @param numVehicles
* @param userObject
*/
private void checkVSIs( int numVehicles, Object userObject )
{
final int n = numVehicles;
try
{
for ( int i = 0; i < n; i++ )
{
VehicleScoringInfo vsi = vehicleScoringInfo[i];
Integer id = vsi.refreshID( i );
//Logger.log( "Found data for " + vsi.getDriverName() + ", assigned id " + id + "." );
// Detect joined drivers...
if ( oldIdVSIMap.remove( id ) == null )
{
RFDHLog.debug( "[DEBUG]: Player joined: ", vsi.getDriverName(), ", id = ", id, ", index = ", i, ", fastest lap: " + TimingUtil.getTimeAsLaptimeString( vsi.getBestLapTime() ) );
//if ( vsi.getBestLapTime() > 0f )
{
Laptime lt = new Laptime( vsi.getDriverId(), 0, -1f, -1f, -1f, false, false, true );
lt.laptime = vsi.getBestLapTime();
vsi.setFastestLaptime( lt );
// TODO: Only for rejoins actually.
for ( int j = 0; j < vsi.laptimes.size(); j++ )
{
lt = vsi.laptimes.get( j );
if ( lt != null )
{
lt.sector1 = -1f;
lt.sector2 = -1f;
lt.sector3 = -1f;
lt.laptime = -1f;
}
}
}
//changedVSIs.add( new Object[] { +1, vsi } );
}
}
// Detect left drivers...
for ( Map.Entry<Integer, VehicleScoringInfo> leftVSI : oldIdVSIMap.entrySet() )
{
RFDHLog.debug( "[DEBUG]: ", leftVSI.getValue().getOldDriverName(), " left the game." );
for ( int i = 0; i < updateListeners.length; i++ )
{
try
{
updateListeners[i].onPlayerLeft( gameData, leftVSI.getKey() );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
//changedVSIs.add( new Object[] { -1, leftVSI.getValue().getDriverID() } );
}
for ( int i = 0; i < n; i++ )
{
VehicleScoringInfo vsi = vehicleScoringInfo[i];
oldIdVSIMap.put( vsi.getDriverID(), vsi );
}
/*
int firstFree = 0;
if ( idCapsuleMap.size() > 0 )
{
for ( Integer joinedID : idCapsuleMap.keySet() )
{
// Player joined
_VehicleScoringInfoCapsule data = idCapsuleMap.get( joinedID );
VehicleScoringInfo vsi = leftVSICache.remove( joinedID );
if ( vsi == null )
{
vsi = vehicleScoringInfoCache[firstFree++];
vsi.data = data;
vsi.setDriverName( data.getOriginalName(), data.getDriverName(), joinedID );
RFDHLog.debug( "[DEBUG]: Player joined: ", vsi.getDriverName(), ", id = ", joinedID, ", index = ", ( firstFree - 1 ), ", fastest lap: " + TimingUtil.getTimeAsLaptimeString( vsi.getBestLapTime() ) );
//if ( vsi.getBestLapTime() > 0f )
{
Laptime lt = new Laptime( vsi.getDriverId(), 0, -1f, -1f, -1f, false, false, true );
lt.laptime = vsi.getBestLapTime();
vsi.setFastestLaptime( lt );
}
changedVSIs.add( new Object[] { +1, vsi } );
}
else
{
vehicleScoringInfoCache[firstFree++] = vsi;
vsi.data = data;
RFDHLog.debug( "[DEBUG]: Player rejoined: ", vsi.getDriverName(), ", id = ", joinedID, ", index = ", ( firstFree - 1 ), ", fastest lap: " + TimingUtil.getTimeAsLaptimeString( vsi.getBestLapTime() ) );
if ( ( vsi._getFastestLaptime() == null ) && ( vsi.getBestLapTime() > 0f ) )
{
Laptime lt = new Laptime( vsi.getDriverId(), 0, -1f, -1f, -1f, false, false, true );
lt.laptime = vsi.getBestLapTime();
vsi.setFastestLaptime( lt );
}
else if ( vsi.getBestLapTime() < 0f )
{
// Player seems to have changed his unique ID.
Laptime lt = new Laptime( vsi.getDriverId(), 0, -1f, -1f, -1f, false, false, true );
lt.laptime = vsi.getBestLapTime();
vsi.setFastestLaptime( lt );
for ( int i = 0; i < vsi.laptimes.size(); i++ )
{
lt = vsi.laptimes.get( i );
lt.sector1 = -1f;
lt.sector2 = -1f;
lt.sector3 = -1f;
lt.laptime = -1f;
}
}
changedVSIs.add( new Object[] { +2, vsi } );
}
}
}
*/
/*
idCapsuleMap.clear();
// Check for errors and do a workaround...
int somethingWrong = 0;
for ( int i = 0; i < n; i++ )
{
if ( vehicleScoringInfoCache[i] == null )
{
somethingWrong++;
vehicleScoringInfoCache[i] = new VehicleScoringInfo( this, gameData.getProfileInfo(), gameData );
vehicleScoringInfoCache[i].data = gdFactory.newVehicleScoringInfoCapsule( gameData );
}
else if ( vehicleScoringInfoCache[i].data == null )
{
somethingWrong++;
vehicleScoringInfoCache[i].data = gdFactory.newVehicleScoringInfoCapsule( gameData );
}
}
if ( somethingWrong > 0 )
{
RFDHLog.printlnEx( "WARNING: Something went wrong when initializing VehicleScoringInfos. Had to add " + somethingWrong + " instances. numVehicles = " + n + ", old = " + oldNumVehicles );
}
*/
/*
System.arraycopy( vehicleScoringInfoCache, 0, vehicleScoringInfo, 0, n );
//for ( int i = n; i < vehicleScoringInfo.length; i++ )
for ( int i = n; i < oldNumVehicles; i++ )
vehicleScoringInfoCache[i].data = null;
*/
/*
int nn = 0;
Logger.log( "VSIs without data: " );
for ( int i = 0; i < n; i++ )
{
if ( vehicleScoringInfo[i].data == null )
{
Logger.log( i + ": " + vehicleScoringInfo[i].getDriverName() );
nn++;
}
}
Logger.log( nn );
*/
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
/*
if ( ( changedVSIs.size() > 0 ) && ( updateListeners != null ) )
{
for ( int j = 0; j < changedVSIs.size(); j++ )
{
Object[] change = changedVSIs.get( j );
for ( int i = 0; i < updateListeners.length; i++ )
{
try
{
int changeId = ( (Integer)change[0] ).intValue();
if ( changeId == -1 )
updateListeners[i].onPlayerLeft( gameData, (Integer)change[1] );
else if ( changeId == +1 )
updateListeners[i].onPlayerJoined( gameData, (VehicleScoringInfo)change[1], false );
else if ( changeId == +2 )
updateListeners[i].onPlayerJoined( gameData, (VehicleScoringInfo)change[1], true );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
}
}
changedVSIs.clear();
*/
}
protected void resetDerivateData()
{
playerName = null;
playerFilename = null;
trackName = null;
playerVSI = null;
viewedVSI = null;
controlledViewedVSI = null;
fastestLapVSI = null;
secondFastestLapVSI = null;
fastestSector1VSI = null;
fastestSector2VSI = null;
fastestSector3VSI = null;
controlledCompareVSI = null;
trackLength = -1f;
classScoringCalculated = false;
if ( vehicleScoringInfoCache != null )
{
for ( int i = 0; i < vehicleScoringInfoCache.length; i++ )
{
if ( vehicleScoringInfoCache[i] != null )
{
vehicleScoringInfoCache[i].resetDerivateData();
}
}
}
}
/**
*
* @param numVehicles
* @param userObject (could be an instance of {@link EditorPresets}), if in editor mode
* @param timestamp
*/
protected void prepareDataUpdate( int numVehicles, Object userObject, long timestamp )
{
lastUpdateTimestamp = timestamp;
initVehicleScoringInfo( numVehicles );
}
/**
*
* @param numVehicles
* @param userObject (could be an instance of {@link EditorPresets}), if in editor mode
* @param timestamp
*/
protected abstract void updateDataImpl( int numVehicles, Object userObject, long timestamp );
protected void applyEditorPresets( EditorPresets editorPresets )
{
if ( editorPresets == null )
return;
final int n = getNumVehicles();
for ( int i = 0; i < n; i++ )
getVehicleScoringInfo( i ).applyEditorPresets( i, editorPresets );
FuelUsageRecorder.MASTER_FUEL_USAGE_RECORDER.applyEditorPresets( gameData, editorPresets );
}
private void updateRaceLengthPercentage( boolean isEditorMode )
{
if ( ( getSessionType() != null ) && getSessionType().isRace() )
{
double trackRaceLaps = gameData.getTrackInfo().getRaceLaps();
if ( isEditorMode )
trackRaceLaps = 70;
else if ( trackRaceLaps < 0.0 ) // corrupt GDB file?
{
RFDHLog.exception( "WARNING: \"RaceLaps\" not found in GDB. Using rFactor default value 50." );
trackRaceLaps = 50; // rFactor standard value, if this is missing.
}
VehicleScoringInfo leader = getLeadersVehicleScoringInfo();
SessionLimit sessionLimit = leader.getSessionLimit();
if ( sessionLimit == SessionLimit.TIME )
{
if ( gameData.getModInfo().getRaceDuration() < 0f )
{
// fall back to lap limited to at least have something.
RFDHLog.exception( "WARNING: No \"RaceTime\" found in RFM." );
sessionLimit = SessionLimit.LAPS;
}
}
if ( sessionLimit == SessionLimit.TIME )
{
double modRaceSeconds = gameData.getModInfo().getRaceDuration();
double raceSeconds = getEndTime();
if ( raceSeconds > 0.0 )
{
// Time limit is always in minutes without fractions. rFactor adds some seconds to the end time.
raceSeconds = Math.floor( raceSeconds / 60.0 ) * 60.0;
//raceLengthPercentage = ( raceSeconds + 150.0 ) / modRaceSeconds;
raceLengthPercentage = ( raceSeconds + 150.0 * ( ( modRaceSeconds - raceSeconds ) / modRaceSeconds ) ) / modRaceSeconds;
}
else
{
raceLengthPercentage = 1.0;
}
}
else
{
double raceLaps = getEstimatedMaxLaps( leader );
//raceLengthPercentage = ( raceLaps + 1 ) / trackRaceLaps;
raceLengthPercentage = ( raceLaps + ( ( trackRaceLaps - raceLaps ) / trackRaceLaps ) ) / trackRaceLaps;
}
}
else
{
raceLengthPercentage = 1.0;
}
}
private GamePhase lastGamePhase = null;
protected void executeOnVSIDataUpdated( long timestamp )
{
for ( int i = 0; i < numVehicles; i++ )
vehicleScoringInfo[i].onDataUpdated( timestamp );
}
/**
* @param numVehicles
* @param userObject (could be an instance of {@link EditorPresets}), if in editor mode
* @param timestamp
*/
protected void onDataUpdatedImpl( int numVehicles, Object userObject, long timestamp )
{
}
/**
* @param numVehicles
* @param userObject (could be an instance of {@link EditorPresets}), if in editor mode
* @param timestamp
*/
protected final void onDataUpdated( int numVehicles, Object userObject, long timestamp )
{
try
{
resetDerivateData();
executeOnVSIDataUpdated( timestamp );
checkVSIs( numVehicles, userObject );
Arrays.sort( vehicleScoringInfo, VehicleScoringInfo.VSIPlaceComparator.INSTANCE );
this.updatedInTimeScope = true;
this.updateId++;
this.updateTimestamp = timestamp;
this.gamePausedCache = gameData.isGamePaused();
this.sessionBaseNanos = Math.round( getSessionTimeImpl() * 1000000000.0 );
updateSessionTime( updateTimestamp );
for ( int i = 0; i < numVehicles; i++ )
vehicleScoringInfo[i].updateSomeData();
boolean rlpUpdated = false;
if ( sessionJustStarted > 0 )
{
updateRaceLengthPercentage( userObject instanceof EditorPresets );
rlpUpdated = true;
for ( int i = 0; i < numVehicles; i++ )
vehicleScoringInfo[i].onSessionStarted();
this.sessionJustStarted = 0;
lastGamePhase = null;
}
if ( userObject instanceof EditorPresets )
applyEditorPresets( (EditorPresets)userObject );
if ( ( numVehicles > 0 ) && getLeadersVehicleScoringInfo().isLapJustStarted() && !rlpUpdated )
{
updateRaceLengthPercentage( userObject instanceof EditorPresets );
rlpUpdated = true;
}
GamePhase gamePhase = getGamePhase();
if ( ( gamePhase != lastGamePhase ) && !rlpUpdated )
{
updateRaceLengthPercentage( userObject instanceof EditorPresets );
rlpUpdated = true;
}
lastGamePhase = gamePhase;
if ( updateListeners != null )
{
for ( int i = 0; i < updateListeners.length; i++ )
{
try
{
updateListeners[i].onScoringInfoUpdated( gameData, userObject instanceof EditorPresets );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
}
onDataUpdatedImpl( numVehicles, userObject, timestamp );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
protected void updateData( int numVehicles, Object userObject, long timestamp )
{
if ( gameData.getProfileInfo().isValid() )
{
if ( userObject instanceof EditorPresets )
numVehicles = 64; // Just to have enough.
prepareDataUpdate( numVehicles, userObject, timestamp );
updateDataImpl( numVehicles, userObject, timestamp );
if ( userObject instanceof EditorPresets )
{
numVehicles = getNumVehiclesImpl();
prepareDataUpdate( numVehicles, userObject, timestamp );
updateDataImpl( numVehicles, userObject, timestamp );
}
onDataUpdated( numVehicles, userObject, timestamp );
}
}
/**
* Sets the generator to use to generate three-letter-codes and short forms from driver names.
*
* @param tlcGenerator
*/
public void setThreeLetterCodeGenerator( ThreeLetterCodeGenerator tlcGenerator )
{
if ( tlcGenerator == null )
throw new IllegalArgumentException( "tlcGenerator must not be null." );
this.tlcGenerator = tlcGenerator;
ThreeLetterCodeManager.resetMaps();
}
/**
* Gets the generator to use to generate three-letter-codes and short forms from driver names.
*
* @return the generator to use to generate three-letter-codes and short forms from driver names.
*/
public final ThreeLetterCodeGenerator getThreeLetterCodeGenerator()
{
return ( tlcGenerator );
}
/**
* @param timestamp
* @param isEditorMode
*/
protected void onSessionStarted( long timestamp, boolean isEditorMode )
{
this.sessionId++;
this.sessionStartTimestamp = timestamp;
this.sessionBaseNanos = 0L;
this.sessionRunning = true;
this.sessionJustStarted = 1;
this.updatedInTimeScope = false;
this.raceLengthPercentage = 1.0; // We don't know it better now!
}
/**
*
* @param timestamp
*/
protected void onSessionEnded( long timestamp )
{
this.sessionRunning = false;
this.updatedInTimeScope = false;
if ( vehicleScoringInfoCache != null )
{
for ( int i = 0; i < vehicleScoringInfoCache.length; i++ )
{
vehicleScoringInfoCache[i].onSessionEnded();
}
}
}
/**
* Gets whether a session is currently running or not.
*
* @return whether a session is currently running or not.
*/
public final boolean isSessionRunning()
{
return ( sessionRunning );
}
/**
* Gets the system timestamp in nanoseconds, at which the current session was started.
*
* @return the system timestamp in nanoseconds, at which the current session was started.
*/
public final long getSessionStartTimestamp()
{
return ( sessionStartTimestamp );
}
protected void onCockpitEntered( long timestamp )
{
this.cockpitEnteredTimestamp = timestamp;
this.cockpitEnteredId++;
this.updatedInTimeScope = true;
}
/**
*
* @param timestamp
*/
protected void onCockpitExited( long timestamp )
{
this.updatedInTimeScope = false;
}
/**
* Gets the system timestamp in nanoseconds, at which the player entered the cockpit.
*
* @return the system timestamp in nanoseconds, at which the player entered the cockpit.
*/
public final long getCockpitEnteredTimestamp()
{
return ( cockpitEnteredTimestamp );
}
/**
* This ID is incremented each time, the player enters realtime mode.
*
* @return the ID of realtime enter actions.
*/
public final int getRealtimeEntredId()
{
return ( cockpitEnteredId );
}
/**
* Gets, whether the last update of these data has been done while in running session resp. cockpit mode.
* @return whether the last update of these data has been done while in running session resp. cockpit mode.
*/
public final boolean isUpdatedInTimeScope()
{
return ( updatedInTimeScope );
}
/**
* Gets whether this data has been updated in the current session.
*
* @return whether this data has been updated in the current session.
*/
public final boolean isValid()
{
return ( sessionJustStarted == 0 );
}
/**
* Gets an ID, that in incremented every time, this {@link ScoringInfo} object is filled with new data from the game.
*
* @return an ID, that in incremented every time, this {@link ScoringInfo} object is filled with new data from the game.
*/
public final long getUpdateId()
{
return ( updateId );
}
/**
* Gets the system nano time for the last data update.
*
* @return the system nano time for the last data update.
*/
public final long getUpdateTimestamp()
{
return ( updateTimestamp );
}
/**
* This Session ID is incremented every time, a new session is started.
*
* @return a session ID unique for each started session.
*/
public final int getSessionId()
{
return ( sessionId );
}
/**
* Gets a multiplier in range [0, 1] for the race distance.
*
* @return a multiplier in range [0, 1] for the race distance.
*/
public final double getRaceLengthPercentage()
{
return ( raceLengthPercentage );
}
private final Set<Integer> handledClassIDs = new HashSet<Integer>();
final void updateClassScoring()
{
if ( classScoringCalculated )
return;
handledClassIDs.clear();
final int n = getNumVehicles();
for ( int i = 0; i < n; i++ )
{
VehicleScoringInfo vsi0 = getVehicleScoringInfo( i );
if ( handledClassIDs.add( vsi0.getVehicleClassID() ) )
{
short p = 1;
int numVehiclesInClass = 1;
float tbn = 0f;
int lbn = 0;
float tbl = 0f;
int lbl = 0;
vsi0.placeByClass = p++;
vsi0.timeBehindNextByClass = tbn;
vsi0.lapsBehindNextByClass = lbn;
vsi0.timeBehindLeaderByClass = tbl;
vsi0.lapsBehindLeaderByClass = lbl;
vsi0.classLeaderVSI = vsi0;
vsi0.classNextInFrontVSI = null;
for ( int j = vsi0.getPlace( false ) - 0; j < n; j++ )
{
VehicleScoringInfo vsi1 = getVehicleScoringInfo( j );
tbn += vsi1.getTimeBehindNextInFront( false );
lbn += vsi1.getLapsBehindNextInFront( false );
tbl += vsi1.getTimeBehindNextInFront( false );
lbl += vsi1.getLapsBehindNextInFront( false );
if ( vsi1.getVehicleClassId() == vsi0.getVehicleClassId() )
{
vsi1.placeByClass = p++;
vsi1.timeBehindNextByClass = tbn;
vsi1.lapsBehindNextByClass = lbn;
vsi1.timeBehindLeaderByClass = tbl;
vsi1.lapsBehindLeaderByClass = lbl;
vsi1.classLeaderVSI = vsi0.classLeaderVSI;
vsi1.classNextInFrontVSI = vsi0;
vsi0.classNextBehindVSI = vsi1;
tbn = 0f;
lbn = 0;
vsi0 = vsi1;
numVehiclesInClass++;
}
}
vsi0.classNextBehindVSI = null;
for ( int j = vsi0.getPlace( false ) - 1; j >= 0; j-- )
{
VehicleScoringInfo vsi1 = getVehicleScoringInfo( j );
if ( vsi1.getVehicleClassId() == vsi0.getVehicleClassId() )
vsi1.numVehiclesInClass = numVehiclesInClass;
}
}
}
classScoringCalculated = true;
}
/**
* Gets the current track name.
*
* @return the current track name.
*/
protected abstract String getTrackNameImpl();
/**
* Gets the current track name.
*
* @return the current track name.
*/
public final String getTrackName()
{
if ( trackName == null )
{
trackName = getTrackNameImpl();
}
return ( trackName );
}
/**
* Gets current session type.
*
* @return current session type.
*/
public abstract SessionType getSessionType();
final void updateSessionTime( long timestamp )
{
extrapolationNanos = timestamp - lastUpdateTimestamp;
gamePausedCache = gamePausedCache || gameData.isGamePaused();
if ( gamePausedCache )
{
extrapolationNanos = 0L;
}
extrapolationTime = extrapolationNanos / 1000000000.0f;
sessionNanos = sessionBaseNanos + extrapolationNanos;
sessionTime = sessionNanos / 1000000000.0f;
int n = getNumVehicles();
for ( int i = 0; i < n; i++ )
{
getVehicleScoringInfo( i ).resetExtrapolatedValues();
}
}
/**
* Gets the nano seconds, the current session is running.
*
* @return the nano seconds, the current session is running.
*/
public final long getSessionNanos()
{
return ( sessionNanos );
}
/**
* Returns the nano seconds since the last ScoringInfo update.
*
* @return the nano seconds since the last ScoringInfo update.
*/
public final long getExtrapolationNanos()
{
return ( extrapolationNanos );
}
/**
* Returns the seconds since the last ScoringInfo update.
*
* @return the seconds since the last ScoringInfo update.
*/
public final float getExtrapolationTime()
{
return ( extrapolationTime );
}
/**
* Gets current session time in seconds.
*
* @return current session time in seconds.
*/
protected abstract float getSessionTimeImpl();
/**
* Gets current session time in seconds.
*
* @return current session time in seconds.
*/
public final float getSessionTime()
{
if ( getGamePhase() == GamePhase.SESSION_OVER )
return ( 0f );
return ( sessionTime );
}
/**
* Gets session ending time.
*
* @return session ending time.
*/
public abstract float getEndTime();
/**
* Gets maximum laps.
*
* @return maximum laps.
*/
public abstract int getMaxLaps();
/**
* Gets the estimated max laps based on the session end time and average lap time.
* If the {@link SessionLimit} is defined to be LAPS, then max laps is known and returned.
* If the current session is a race, the estimated max laps of the leader are returned.
*
* @param vsi the vehicle (should be the leader)
*
* @return the estimated max laps.
*/
public final int getEstimatedMaxLaps( VehicleScoringInfo vsi )
{
if ( ( getSessionType() != null ) && getSessionType().isRace() )
return ( getLeadersVehicleScoringInfo().getEstimatedMaxLaps() );
return ( vsi.getEstimatedMaxLaps() );
}
/**
* Gets the distance around track.
*
* @return the distance around track.
*/
protected abstract float getTrackLengthImpl();
/**
* Gets the distance around track.
*
* @return the distance around track.
*/
public final float getTrackLength()
{
if ( trackLength < 0f )
{
trackLength = getTrackLengthImpl();
}
return ( trackLength );
}
/**
* Gets the current number of vehicles.
*
* @return the current number of vehicles.
*/
protected abstract int getNumVehiclesImpl();
/**
* Gets the current number of vehicles.
*
* @return the current number of vehicles.
*/
public final int getNumVehicles()
{
if ( sessionJustStarted == 1 )
return ( 0 );
//if ( numVehicles == -1 )
// numVehicles = getNumVehiclesImpl();
return ( numVehicles );
}
/**
* Gets the number of vehicles in the same vehicle class as the given one. This method counts on every call.
*
* @param vsi the vehicle
*
* @return the number of vehicles in the same vehicle class as the given one.
*/
public final int getNumVehiclesInSameClass( VehicleScoringInfo vsi )
{
int nc = 0;
final int n = getNumVehicles();
for ( int i = 0; i < n; i++ )
{
if ( getVehicleScoringInfo( i ).getVehicleClassId() == vsi.getVehicleClassId() )
nc++;
}
return ( nc );
}
/**
* Gets the current game phase.
*
* @return the current game phase.
*/
public abstract GamePhase getGamePhase();
/**
* Gets the current yellow flag state (applies to full-course only).
*
* @return the current yellow flag state.
*/
public abstract YellowFlagState getYellowFlagState();
/**
* Gets whether there are any local yellows at the moment in the sector.
*
* @param sector the queried sector (1,2,3)
*
* @return whether there are any local yellows at the moment in the sector
*/
public abstract boolean getSectorYellowFlag( int sector );
/**
* Gets the current start light frame (number depends on track).
*
* @see #getNumStartingLights()
*
* @return the current start light frame.
*/
public abstract int getStartLightFrame();
/**
* Gets the number of lights in start sequence.
*
* @see #getStartLightFrame()
*
* @return the number of lights in start sequence.
*/
public abstract int getNumStartingLights();
/**
* Gets the number of red lights in start sequence.
*
* @deprecated replaced by {@link #getNumStartingLights()}
*
* @return the number of red lights in start sequence.
*/
@Deprecated
public final int getNumRedLights()
{
return ( getNumStartingLights() );
}
/**
* Gets whether we're in realtime as opposed to at the monitor.
*
* @return whether we're in realtime as opposed to at the monitor.
*
* @deprecated replaced by {@link LiveGameData#isInCockpit()}
*/
@Deprecated
public final boolean isInRealtimeMode()
{
return ( gameData.isInCockpit() );
}
/**
* Gets the player name (including possible multiplayer override).
*
* @return the player name.
*/
protected abstract String getPlayerNameImpl();
/**
* Gets the player name (including possible multiplayer override).
*
* @return the player name.
*/
public final String getPlayerName()
{
if ( playerName == null )
{
playerName = getPlayerNameImpl();
}
return ( playerName );
}
/**
* Gets the player's filename (PLR) (may be encoded to be a legal filename).
*
* @return the player's filename.
*/
protected abstract String getPlayerFilenameImpl();
/**
* Gets the player's filename (PLR) (may be encoded to be a legal filename).
*
* @return the player's filename.
*/
public final String getPlayerFilename()
{
if ( playerFilename == null )
{
playerFilename = getPlayerFilenameImpl();
}
return ( playerFilename );
}
/**
* Gets the i-th vehicle scoring info.
*
* @param i the index
*
* @see #getNumVehicles()
*
* @return the i-th vehicle scoring info.
*/
public final VehicleScoringInfo getVehicleScoringInfo( int i )
{
// VehicleScoringInfoV2* mVehicle
if ( i >= getNumVehicles() )
throw new IllegalArgumentException( "There is no vehicle with the index " + i + ". There are only " + getNumVehicles() + " vehicles." );
return ( vehicleScoringInfo[i] );
}
/**
* Gets all the current {@link VehicleScoringInfo}s and writes them into the given array.
*
* @param vsis the target array (must be of at least {@link #getNumVehicles()} size.
*
* @return the number of {@link VehicleScoringInfo}s.
*/
public final int getVehicleScoringInfos( VehicleScoringInfo[] vsis )
{
if ( vsis == null )
throw new NullPointerException( "vsis parameter is null" );
int n = getNumVehicles();
if ( vsis.length < n )
throw new ArrayIndexOutOfBoundsException( "vsis array too small (" + vsis.length + " < " + n + ")." );
System.arraycopy( vehicleScoringInfo, 0, vsis, 0, n );
return ( n );
}
/**
* Gets the leader's {@link VehicleScoringInfo}.
* This is equivalent to getVehicleScoringInfo( 0 ).
*
* @return the leader's {@link VehicleScoringInfo}.
*/
public final VehicleScoringInfo getLeadersVehicleScoringInfo()
{
return ( getVehicleScoringInfo( 0 ) );
}
/**
* Gets the player's VehicleScroingInfo.
*
* @see #getOwnPlace(boolean)
*
* @return the player's VehicleScroingInfo.
*/
public final VehicleScoringInfo getPlayersVehicleScoringInfo()
{
if ( playerVSI == null )
{
int n = getNumVehicles();
for ( short i = 0; i < n; i++ )
{
if ( vehicleScoringInfo[i].isPlayer() )
{
playerVSI = vehicleScoringInfo[i];
break;
}
}
}
return ( playerVSI );
}
void toggleFixedViewedVSI()
{
this.fixedViewedVSI = !fixedViewedVSI;
this.viewedVSI = null;
}
/**
* Sets the viewed vehicle (updated on the next frame).<br />
* This operation can be ignored, if the underlying sim doesn't support it.
*
* @param vsi the next viewed vehicle
* @param cameraType the new camera type
*
* @see GraphicsInfo#CAMERA_TYPE_COCKPIT
* @see GraphicsInfo#CAMERA_TYPE_TV_COCKPIT
* @see GraphicsInfo#CAMERA_TYPE_NOSECAM
* @see GraphicsInfo#CAMERA_TYPE_SWINGMAN
* @see GraphicsInfo#CAMERA_TYPE_TRACKSIDE
*/
public abstract void setViewedVehicleScoringInfo( VehicleScoringInfo vsi, int cameraType );
void setControlledViewedVSI( VehicleScoringInfo controlledViewedVSI )
{
this.controlledViewedVSI = controlledViewedVSI;
}
protected abstract VehicleScoringInfo getViewedVehicleScoringInfoImpl();
/**
* Gets the viewed's VehicleScroingInfo (this is just a guess, but should be correct).
*
* @return the viewed's VehicleScroingInfo.
*/
public final VehicleScoringInfo getViewedVehicleScoringInfo()
{
if ( controlledViewedVSI != null )
return ( controlledViewedVSI );
if ( viewedVSI == null )
{
LiveGameDataController controller = gameData.getLiveGameDataController();
int controlledId = ( controller == null ) ? -1 : controller.getViewedVSIId();
int n = getNumVehicles();
if ( controlledId >= 0 )
{
for ( short i = 0; i < n; i++ )
{
if ( vehicleScoringInfo[i].getDriverId() == controlledId )
{
viewedVSI = vehicleScoringInfo[i];
return ( viewedVSI );
}
}
}
}
// Not found! Search the regular way...
if ( fixedViewedVSI )
return ( getPlayersVehicleScoringInfo() );
return ( getViewedVehicleScoringInfoImpl() );
}
/**
* Gets the position of the player.
*
* @param byClass only consider vehicles in the same class
*
* @return the position of the player.
*/
public final short getOwnPlace( boolean byClass )
{
return ( getPlayersVehicleScoringInfo().getPlace( byClass ) );
}
/**
* Gets the VehicleScoringInfo for the fastest sector1.
*
* @return the VehicleScoringInfo for the fastest sector1.
*/
public final VehicleScoringInfo getFastestSector1VSI()
{
if ( fastestSector1VSI == null )
{
fastestSector1VSI = vehicleScoringInfo[0];
float fs = fastestSector1VSI.getBestSector1();
for ( int i = 1; i < vehicleScoringInfo.length; i++ )
{
float fs_ = vehicleScoringInfo[i].getBestLapTime();
if ( ( fs_ > 0f ) && ( fs_ < fs ) )
{
fastestSector1VSI = vehicleScoringInfo[i];
fs = fs_;
}
}
}
return ( fastestSector1VSI );
}
/**
* Gets the VehicleScoringInfo for the fastest sector2.
*
* @return the VehicleScoringInfo for the fastest sector2.
*/
public final VehicleScoringInfo getFastestSector2VSI()
{
if ( fastestSector2VSI == null )
{
fastestSector2VSI = vehicleScoringInfo[0];
float fs = fastestSector2VSI.getBestSector2( false );
for ( int i = 1; i < vehicleScoringInfo.length; i++ )
{
float fs_ = vehicleScoringInfo[i].getBestSector2( false );
if ( ( fs_ > 0f ) && ( fs_ < fs ) )
{
fastestSector2VSI = vehicleScoringInfo[i];
fs = fs_;
}
}
}
return ( fastestSector2VSI );
}
/**
* Gets the VehicleScoringInfo for the fastest sector3.
*
* @return the VehicleScoringInfo for the fastest sector3.
*/
public final VehicleScoringInfo getFastestSector3VSI()
{
if ( fastestSector3VSI == null )
{
fastestSector3VSI = vehicleScoringInfo[0];
float fs = fastestSector3VSI.getBestSector3();
for ( int i = 1; i < vehicleScoringInfo.length; i++ )
{
float fs_ = vehicleScoringInfo[i].getBestSector3();
if ( ( fs_ > 0f ) && ( fs_ < fs ) )
{
fastestSector3VSI = vehicleScoringInfo[i];
fs = fs_;
}
}
}
return ( fastestSector3VSI );
}
/**
* Gets the VehicleScoringInfo for the fastest sector i.
*
* @param sector the queried sector
*
* @return the VehicleScoringInfo for the fastest sector i.
*/
public final VehicleScoringInfo getFastestSectorVSI( int sector )
{
if ( sector == 1 )
return ( getFastestSector1VSI() );
if ( sector == 2 )
return ( getFastestSector2VSI() );
if ( sector == 3 )
return ( getFastestSector3VSI() );
throw new IllegalArgumentException( "sector must be between 1 and 3." );
}
/**
* Gets the VehicleScoringInfo for the fastest lap.
*
* @return the VehicleScoringInfo for the fastest lap.
*/
public final VehicleScoringInfo getFastestLapVSI()
{
if ( fastestLapVSI == null )
{
secondFastestLapVSI = null;
if ( ( getSessionType() != null ) && !getSessionType().isRace() )
{
// VehicleScoringInfos are sorted by place, which is the same as by laptime in non-race sessions.
fastestLapVSI = vehicleScoringInfo[0];
//if ( ( vehicleScoringInfo2.length > 1 ) && ( vehicleScoringInfo2[1].getBestLapTime() > 0f ) )
if ( ( vehicleScoringInfo.length > 1 ) && ( vehicleScoringInfo[1].getFastestLaptime() != null ) )
{
secondFastestLapVSI = vehicleScoringInfo[1];
}
//RFDHLog.debug( TimingUtil.getTimeAsLaptimeString( getSessionTime() ) + ", " + fastestLapVSI.getLapsCompleted() + ": " + fastestLapVSI + ", " + fastestLapVSI.getFastestLaptime() );
return ( fastestLapVSI );
}
int i0;
for ( i0 = 0; i0 < vehicleScoringInfo.length; i0++ )
{
Laptime lt_ = vehicleScoringInfo[i0].getFastestLaptime();
if ( ( lt_ != null ) && ( lt_.getLapTime() > 0f ) && lt_.isFinished() )
break;
}
if ( i0 == vehicleScoringInfo.length )
{
fastestLapVSI = vehicleScoringInfo[0];
if ( vehicleScoringInfo.length > 1 )
secondFastestLapVSI = vehicleScoringInfo[1];
}
else
{
fastestLapVSI = vehicleScoringInfo[i0];
Laptime lt = fastestLapVSI.getFastestLaptime();
for ( int i = i0 + 1; i < vehicleScoringInfo.length; i++ )
{
Laptime lt_ = vehicleScoringInfo[i].getFastestLaptime();
if ( ( lt_ != null ) && ( lt_.getLapTime() < lt .getLapTime() ) )
{
secondFastestLapVSI = fastestLapVSI;
fastestLapVSI = vehicleScoringInfo[i];
lt = lt_;
}
}
if ( ( secondFastestLapVSI == null ) && ( vehicleScoringInfo.length > i0 ) )
{
Laptime lt2 = null;
for ( int i = i0 + 1; i < vehicleScoringInfo.length; i++ )
{
Laptime lt_ = vehicleScoringInfo[i].getFastestLaptime();
if ( lt_ != null )
{
if ( secondFastestLapVSI == null )
{
secondFastestLapVSI = vehicleScoringInfo[i];
lt2 = secondFastestLapVSI.getFastestLaptime();
}
else if ( ( lt2 == null ) || ( lt_.getLapTime() < lt2.getLapTime() ) )
{
secondFastestLapVSI = vehicleScoringInfo[i];
lt2 = lt_;
}
}
}
}
}
}
return ( fastestLapVSI );
}
/**
* Gets the VehicleScoringInfo for the second fastest lap (or <code>null</code>).
*
* @return the VehicleScoringInfo for the second fastest lap (or <code>null</code>).
*/
public final VehicleScoringInfo getSecondFastestLapVSI()
{
getFastestLapVSI();
return ( secondFastestLapVSI );
}
/**
* Gets the absolute fastes lap time.
*
* @return the absolute fastest lap time.
*/
public final Laptime getFastestLaptime()
{
return ( getFastestLapVSI().getFastestLaptime() );
}
void setControlledCompareVSI( VehicleScoringInfo controlledCompareVSI )
{
this.controlledCompareVSI = controlledCompareVSI;
}
/**
* <p>
* Gets the {@link VehicleScoringInfo} to compare against.
* </p>
*
* <p>
* By default this is <code>null</code>, which leads to default behavior. But a plugin can override this.
* </p>
*
* @return the {@link VehicleScoringInfo} to compare laptimes against.
*/
public final VehicleScoringInfo getCompareVSI()
{
return ( controlledCompareVSI );
}
/**
* @deprecated use {@link WeatherInfo#getCloudDarkness()}
*
* @return the cloud darkness.
*/
@Deprecated
public final float getCloudDarkness()
{
return ( gameData.getWeatherInfo().getCloudDarkness() );
}
/**
* @deprecated use {@link WeatherInfo#getRainingSeverity()}
*
* @return the rain severity.
*/
@Deprecated
public final float getRainingSeverity()
{
return ( gameData.getWeatherInfo().getRainingSeverity() );
}
/**
* @deprecated use {@link WeatherInfo#getAmbientTemperatureK()}
*
* @return the ambient temperature in °K.
*/
@Deprecated
public final float getAmbientTemperatureK()
{
return ( gameData.getWeatherInfo().getAmbientTemperatureK() );
}
/**
* @deprecated use {@link WeatherInfo#getAmbientTemperatureC()}
*
* @return the ambient temperature in °C.
*/
@Deprecated
public final float getAmbientTemperatureC()
{
return ( gameData.getWeatherInfo().getAmbientTemperatureC() );
}
/**
* @deprecated use {@link WeatherInfo#getAmbientTemperatureF()}
*
* @return the ambient temperature in °F.
*/
@Deprecated
public final float getAmbientTemperatureF()
{
return ( gameData.getWeatherInfo().getAmbientTemperatureF() );
}
/**
* @deprecated use {@link WeatherInfo#getAmbientTemperature()}
*
* @return the ambient temperature.
*/
@Deprecated
public final float getAmbientTemperature()
{
return ( gameData.getWeatherInfo().getAmbientTemperature() );
}
/**
* @deprecated use {@link WeatherInfo#getTrackTemperatureK()}
*
* @return the track temperature in °K.
*/
@Deprecated
public final float getTrackTemperatureK()
{
return ( gameData.getWeatherInfo().getTrackTemperatureK() );
}
/**
* @deprecated use {@link WeatherInfo#getTrackTemperatureC()}
*
* @return the track temperature in °C.
*/
@Deprecated
public final float getTrackTemperatureC()
{
return ( gameData.getWeatherInfo().getTrackTemperatureC() );
}
/**
* @deprecated use {@link WeatherInfo#getTrackTemperatureF()}
*
* @return the track temperature in °F.
*/
@Deprecated
public final float getTrackTemperatureF()
{
return ( gameData.getWeatherInfo().getTrackTemperatureF() );
}
/**
* @deprecated use {@link WeatherInfo#getTrackTemperature()}
*
* @return the track temperature.
*/
@Deprecated
public final float getTrackTemperature()
{
return ( gameData.getWeatherInfo().getTrackTemperature() );
}
/**
* @deprecated use {@link WeatherInfo#getWindSpeedMS(TelemVect3)}
*
* @param speed
*/
@Deprecated
public final void getWindSpeedMS( TelemVect3 speed )
{
gameData.getWeatherInfo().getWindSpeedMS( speed );
}
/**
* @deprecated use {@link WeatherInfo#getWindSpeedKmh(TelemVect3)}
*
* @param speed
*/
@Deprecated
public final void getWindSpeedKph( TelemVect3 speed )
{
gameData.getWeatherInfo().getWindSpeedKmh( speed );
}
/**
* @deprecated use {@link WeatherInfo#getWindSpeedMih(TelemVect3)}
*
* @param speed
*/
@Deprecated
public final void getWindSpeedMph( TelemVect3 speed )
{
gameData.getWeatherInfo().getWindSpeedMih( speed );
}
/**
* @deprecated use {@link WeatherInfo#getWindSpeed(TelemVect3)}
*
* @param speed
*/
@Deprecated
public final void getWindSpeed( TelemVect3 speed )
{
gameData.getWeatherInfo().getWindSpeed( speed );
}
/**
* @deprecated use {@link WeatherInfo#getOnPathWetness()}
*
* @return the on path wetness.
*/
@Deprecated
public final float getOnPathWetness()
{
return ( gameData.getWeatherInfo().getOnPathWetness() );
}
/**
* @deprecated use {@link WeatherInfo#getOffPathWetness()}
*
* @return the off path wetness.
*/
@Deprecated
public final float getOffPathWetness()
{
return ( gameData.getWeatherInfo().getOffPathWetness() );
}
protected ScoringInfo( LiveGameData gameData )
{
this.gameData = gameData;
this.gdFactory = gameData.getGameDataObjectsFactory();
//gdFactory.newVehicleScoringInfo( gameData ); // We need to call this to initialize the capsule class early to trick the invoked JVM.
this.tlcGenerator = AbstractThreeLetterCodeGenerator.initThreeLetterCodeGenerator( gameData.getFileSystem().getPluginINI().getGeneralThreeLetterCodeGeneratorClass() );
registerListener( LaptimesRecorder.INSTANCE );
registerListener( FuelUsageRecorder.MASTER_FUEL_USAGE_RECORDER );
registerListener( TopspeedRecorder.MASTER_TOPSPEED_RECORDER );
}
}