/** * 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.widgets.standard.misc; import java.awt.Font; import java.io.IOException; import net.ctdp.rfdynhud.gamedata.GamePhase; import net.ctdp.rfdynhud.gamedata.Laptime; import net.ctdp.rfdynhud.gamedata.LiveGameData; import net.ctdp.rfdynhud.gamedata.ProfileInfo.SpeedUnits; import net.ctdp.rfdynhud.gamedata.ScoringInfo; import net.ctdp.rfdynhud.gamedata.SessionType; import net.ctdp.rfdynhud.gamedata.VehicleScoringInfo; import net.ctdp.rfdynhud.properties.BooleanProperty; import net.ctdp.rfdynhud.properties.DelayProperty; import net.ctdp.rfdynhud.properties.PropertiesContainer; import net.ctdp.rfdynhud.properties.PropertyLoader; import net.ctdp.rfdynhud.render.DrawnString; import net.ctdp.rfdynhud.render.DrawnString.Alignment; import net.ctdp.rfdynhud.render.DrawnStringFactory; import net.ctdp.rfdynhud.render.TextureImage2D; import net.ctdp.rfdynhud.util.NumberUtil; import net.ctdp.rfdynhud.util.PropertyWriter; import net.ctdp.rfdynhud.util.SubTextureCollector; import net.ctdp.rfdynhud.util.TimingUtil; import net.ctdp.rfdynhud.valuemanagers.Clock; import net.ctdp.rfdynhud.values.EnumValue; import net.ctdp.rfdynhud.values.FloatValue; import net.ctdp.rfdynhud.values.IntValue; import net.ctdp.rfdynhud.values.ValidityTest; import net.ctdp.rfdynhud.widgets.base.widget.StatefulWidget; import net.ctdp.rfdynhud.widgets.base.widget.Widget; import net.ctdp.rfdynhud.widgets.standard._util.StandardWidgetSet; /** * The {@link MiscWidget} displays miscellaneous information like fastest lap, session time, top speed, etc.. * * @author Marvin Froehlich (CTDP) */ public class MiscWidget extends StatefulWidget<Object, LocalStore> { private final BooleanProperty displayScoring = new BooleanProperty( "displayScoring", true ); private final BooleanProperty displayTiming = new BooleanProperty( "displayTiming", true ); private final BooleanProperty displayVelocity = new BooleanProperty( "displayVelocity", true ); private final DelayProperty relTopspeedResetDelay = new DelayProperty( "relTopspeedResetDelay", DelayProperty.DisplayUnits.MILLISECONDS, 10000, 0, 60000 ); // ten seconds private DrawnString scoringString1 = null; private DrawnString scoringString2 = null; private DrawnString scoringString3 = null; private DrawnString lapString = null; private DrawnString stintString = null; private DrawnString sessionTimeString = null; private DrawnString absTopspeedString = null; private DrawnString relTopspeedString = null; private DrawnString velocityString = null; private int oldStintLength = -1; private final IntValue leaderID = new IntValue(); private boolean leaderValid = false; private final IntValue place = new IntValue( ValidityTest.GREATER_THAN, 0 ); private final FloatValue fastestLap = new FloatValue( ValidityTest.GREATER_THAN, 0f ); private final FloatValue sessionTime = new FloatValue( -1f, 0.1f ); private final EnumValue<GamePhase> gamePhase1 = new EnumValue<GamePhase>(); private final EnumValue<GamePhase> gamePhase2 = new EnumValue<GamePhase>(); private final IntValue lapsCompleted = new IntValue(); private float oldLapsRemaining = -1f; private float oldAbsTopspeed = -1f; private float oldRelTopspeed = -1f; private float relTopspeed = -1f; private int oldVelocity = -1; private boolean updateAbs = false; private int[] scoringColWidths = new int[ 2 ]; private int[] timingColWidths = new int[ 2 ]; private int[] velocityColWidths = new int[ 3 ]; private final Alignment[] scoringAlignment = new Alignment[] { Alignment.RIGHT, Alignment.LEFT }; private final Alignment[] timingAlignment = new Alignment[] { Alignment.RIGHT, Alignment.LEFT }; private final Alignment[] velocityAlignment = new Alignment[] { Alignment.RIGHT, Alignment.LEFT, Alignment.LEFT }; private static final int padding = 4; public MiscWidget() { super( StandardWidgetSet.INSTANCE, StandardWidgetSet.WIDGET_PACKAGE, 66.25f, 5.83f ); } /** * {@inheritDoc} */ @Override public void prepareForMenuItem() { super.prepareForMenuItem(); getFontProperty().setFont( "Dialog", Font.PLAIN, 5, false, true ); displayTiming.setBooleanValue( false ); displayVelocity.setBooleanValue( false ); } /** * {@inheritDoc} */ @Override public void saveProperties( PropertyWriter writer ) throws IOException { super.saveProperties( writer ); writer.writeProperty( displayScoring, "Display the scoring part of the Widget?" ); writer.writeProperty( displayTiming, "Display the timing part of the Widget?" ); writer.writeProperty( displayVelocity, "Display the velocity and top speed part of the Widget?" ); writer.writeProperty( relTopspeedResetDelay, "The delay after which the relative topspeed is resetted (in milliseconds)." ); } /** * {@inheritDoc} */ @Override public void loadProperty( PropertyLoader loader ) { super.loadProperty( loader ); if ( loader.loadProperty( displayScoring ) ); else if ( loader.loadProperty( displayTiming ) ); else if ( loader.loadProperty( displayVelocity ) ); else if ( loader.loadProperty( relTopspeedResetDelay ) ); } /** * {@inheritDoc} */ @Override public void getProperties( PropertiesContainer propsCont, boolean forceAll ) { super.getProperties( propsCont, forceAll ); propsCont.addGroup( "Misc" ); propsCont.addProperty( displayScoring ); propsCont.addProperty( displayTiming ); propsCont.addProperty( displayVelocity ); propsCont.addProperty( relTopspeedResetDelay ); } /** * {@inheritDoc} */ @Override public Object createGeneralStore() { return ( null ); } /** * {@inheritDoc} */ @Override public LocalStore createLocalStore() { return ( new LocalStore() ); } /** * {@inheritDoc} */ @Override public int getNeededData() { return ( Widget.NEEDED_DATA_SCORING ); } /** * {@inheritDoc} */ @Override public void onSessionStarted( SessionType sessionType, LiveGameData gameData, boolean isEditorMode ) { super.onSessionStarted( sessionType, gameData, isEditorMode ); oldAbsTopspeed = -1f; getLocalStore().lastDisplayedAbsTopspeed = 0f; } /** * {@inheritDoc} */ @Override public void onCockpitEntered( LiveGameData gameData, boolean isEditorMode ) { super.onCockpitEntered( gameData, isEditorMode ); oldStintLength = -1; leaderID.reset(); leaderValid = false; place.reset(); fastestLap.reset(); sessionTime.reset(); gamePhase1.reset(); gamePhase2.reset(); lapsCompleted.reset(); oldLapsRemaining = -1f; oldRelTopspeed = -1f; relTopspeed = -1f; getLocalStore().lastRelTopspeedTime = -1L; oldVelocity = -1; updateAbs = false; //forceReinitialization(); } /** * {@inheritDoc} */ @Override protected Boolean onVehicleControlChanged( VehicleScoringInfo viewedVSI, LiveGameData gameData, boolean isEditorMode ) { Boolean result = super.onVehicleControlChanged( viewedVSI, gameData, isEditorMode ); oldRelTopspeed = -1f; relTopspeed = -1f; getLocalStore().lastRelTopspeedTime = -1L; getLocalStore().lastDisplayedAbsTopspeed = viewedVSI.getTopspeed(); oldAbsTopspeed = -1f; oldVelocity = -1; updateAbs = true; return ( result ); } private static final String getSpeedUnits( SpeedUnits speedUnits ) { if ( speedUnits == SpeedUnits.MIH ) return ( Loc.velocity_units_IMPERIAL ); return ( Loc.velocity_units_METRIC ); } /** * {@inheritDoc} */ @Override protected void initSubTextures( LiveGameData gameData, boolean isEditorMode, int widgetInnerWidth, int widgetInnerHeight, SubTextureCollector collector ) { } /** * {@inheritDoc} */ @Override protected void initialize( LiveGameData gameData, boolean isEditorMode, DrawnStringFactory dsf, TextureImage2D texture, int width, int height ) { final java.awt.Font font = getFont(); final boolean fontAntiAliased = isFontAntiAliased(); final java.awt.Color fontColor = getFontColor(); final int left = 2; final int center = width / 2; final int right = width - 2; final int top = -2; final String speedUnits = getSpeedUnits( gameData.getProfileInfo().getSpeedUnits() ); { boolean b = displayScoring.getBooleanValue(); scoringString1 = dsf.newDrawnStringIf( b, "scoringString1", left, top, Alignment.LEFT, false, font, fontAntiAliased, fontColor ); scoringString2 = dsf.newDrawnStringIf( b, "scoringString2", null, scoringString1, left, 0, Alignment.LEFT, false, font, fontAntiAliased, fontColor ); scoringString3 = dsf.newDrawnStringIf( b, "scoringString3", null, scoringString2, left, 0, Alignment.LEFT, false, font, fontAntiAliased, fontColor ); } { boolean b = displayTiming.getBooleanValue(); if ( displayScoring.getBooleanValue() && displayVelocity.getBooleanValue() ) { lapString = dsf.newDrawnStringIf( b, "lapString", center, top, Alignment.CENTER, false, font, fontAntiAliased, fontColor ); stintString = dsf.newDrawnStringIf( b, "stintString", null, lapString, center, 0, Alignment.CENTER, false, font, fontAntiAliased, fontColor ); sessionTimeString = dsf.newDrawnStringIf( b, "sessionTimeString", null, stintString, center, 0, Alignment.CENTER, false, font, fontAntiAliased, fontColor ); } else if ( !displayScoring.getBooleanValue() ) { lapString = dsf.newDrawnStringIf( b, "lapString", left, top, Alignment.LEFT, false, font, fontAntiAliased, fontColor ); stintString = dsf.newDrawnStringIf( b, "stintString", null, lapString, left, 0, Alignment.LEFT, false, font, fontAntiAliased, fontColor ); sessionTimeString = dsf.newDrawnStringIf( b, "sessionTimeString", null, stintString, left, 0, Alignment.LEFT, false, font, fontAntiAliased, fontColor ); } else if ( !displayVelocity.getBooleanValue() ) { lapString = dsf.newDrawnStringIf( b, "lapString", right, top, Alignment.RIGHT, false, font, fontAntiAliased, fontColor ); stintString = dsf.newDrawnStringIf( b, "stintString", null, lapString, right, 0, Alignment.RIGHT, false, font, fontAntiAliased, fontColor ); sessionTimeString = dsf.newDrawnStringIf( b, "sessionTimeString", null, stintString, right, 0, Alignment.RIGHT, false, font, fontAntiAliased, fontColor ); } } if ( displayVelocity.getBooleanValue() ) { if ( displayScoring.getBooleanValue() || displayTiming.getBooleanValue() ) { absTopspeedString = dsf.newDrawnString( "absTopspeedString", right, top, Alignment.RIGHT, false, font, fontAntiAliased, fontColor ); relTopspeedString = dsf.newDrawnString( "relTopspeedString", null, absTopspeedString, right, 0, Alignment.RIGHT, false, font, fontAntiAliased, fontColor ); velocityString = dsf.newDrawnString( "velocityString", null, relTopspeedString, right, 0, Alignment.RIGHT, false, font, fontAntiAliased, fontColor ); } else { absTopspeedString = dsf.newDrawnString( "absTopspeedString", left, top, Alignment.LEFT, false, font, fontAntiAliased, fontColor ); relTopspeedString = dsf.newDrawnString( "relTopspeedString", null, absTopspeedString, left, 0, Alignment.LEFT, false, font, fontAntiAliased, fontColor ); velocityString = dsf.newDrawnString( "velocityString", null, relTopspeedString, left, 0, Alignment.LEFT, false, font, fontAntiAliased, fontColor ); } velocityColWidths[0] = 0; velocityColWidths[1] = 0; velocityColWidths[2] = 0; absTopspeedString.getMaxColWidths( new String[] { Loc.velocity_topspeed1_prefix + ":", "000.0", speedUnits }, velocityAlignment, padding, velocityColWidths ); relTopspeedString.getMaxColWidths( new String[] { Loc.velocity_topspeed2_prefix + ":", "000.0", speedUnits }, velocityAlignment, padding, velocityColWidths ); velocityString.getMaxColWidths( new String[] { Loc.velocity_velocity_prefix + ":", "000", speedUnits }, velocityAlignment, padding, velocityColWidths ); } else { absTopspeedString = null; relTopspeedString = null; velocityString = null; } } private void updateScoringColWidths( ScoringInfo scoringInfo, String leaderName ) { scoringColWidths[0] = 0; scoringColWidths[1] = 0; scoringString1.getMaxColWidths( new String[] { Loc.scoring_leader_prefix + ":", leaderName }, scoringAlignment, padding, scoringColWidths ); if ( place.isValid() ) scoringString2.getMaxColWidths( new String[] { Loc.scoring_place_prefix + ":", place.getValueAsString() + "/" + scoringInfo.getNumVehicles() }, scoringAlignment, padding, scoringColWidths ); else scoringString2.getMaxColWidths( new String[] { Loc.scoring_place_prefix + ":", Loc.scoring_place_na }, scoringAlignment, padding, scoringColWidths ); if ( fastestLap.isValid() ) scoringString3.getMaxColWidths( new String[] { Loc.scoring_fastest_lap_prefix + ":", TimingUtil.getTimeAsString( fastestLap.getValue(), true ) + " (" + scoringInfo.getFastestLapVSI().getDriverNameShort() + ")" }, scoringAlignment, padding, scoringColWidths ); else scoringString3.getMaxColWidths( new String[] { Loc.scoring_fastest_lap_prefix + ":", Loc.scoring_fastest_lap_na }, scoringAlignment, padding, scoringColWidths ); } private void updateTimingColWidths( String[] lapStringValue, String[] stintStringValue, String[] sessionTimeStringValue ) { timingColWidths[0] = 0; timingColWidths[1] = 0; lapString.getMaxColWidths( lapStringValue, timingAlignment, padding, timingColWidths ); stintString.getMaxColWidths( stintStringValue, timingAlignment, padding, timingColWidths ); sessionTimeString.getMaxColWidths( sessionTimeStringValue, timingAlignment, padding, timingColWidths ); } /** * {@inheritDoc} */ @Override protected void drawWidget( Clock clock, boolean needsCompleteRedraw, LiveGameData gameData, boolean isEditorMode, TextureImage2D texture, int offsetX, int offsetY, int width, int height ) { ScoringInfo scoringInfo = gameData.getScoringInfo(); VehicleScoringInfo vsi = scoringInfo.getViewedVehicleScoringInfo(); if ( displayScoring.getBooleanValue() ) { VehicleScoringInfo leaderVSI = scoringInfo.getLeadersVehicleScoringInfo(); leaderID.update( leaderVSI.getDriverId() ); String leaderName = leaderVSI.getDriverNameShort(); place.update( vsi.getPlace( getConfiguration().getUseClassScoring() ) ); VehicleScoringInfo fastestLapVSI = scoringInfo.getFastestLapVSI(); String fastestLapper = fastestLapVSI.getDriverNameShort( false ); Laptime fl = fastestLapVSI.getFastestLaptime(); fastestLap.update( ( fl == null ) ? -1f : fl.getLapTime() ); boolean colWidthsUpdated = false; boolean lv = scoringInfo.getSessionType().isRace() || ( leaderVSI.getFastestLaptime() != null ); if ( needsCompleteRedraw || leaderID.hasChanged() || ( lv != leaderValid ) ) { leaderValid = lv; if ( !colWidthsUpdated ) { updateScoringColWidths( scoringInfo, leaderName ); colWidthsUpdated = true; } if ( scoringInfo.getSessionType().isRace() ) { if ( leaderValid ) scoringString1.drawColumns( offsetX, offsetY, new String[] { Loc.scoring_leader_prefix + ":", leaderName }, scoringAlignment, padding, scoringColWidths, texture ); else scoringString1.drawColumns( offsetX, offsetY, new String[] { Loc.scoring_leader_prefix + ":", Loc.scoring_leader_na }, scoringAlignment, padding, scoringColWidths, texture ); } else { scoringString1.draw( offsetX, offsetY, "", texture ); } } if ( needsCompleteRedraw || place.hasValidityChanged() || place.hasChanged() ) { if ( !colWidthsUpdated ) { updateScoringColWidths( scoringInfo, leaderName ); colWidthsUpdated = true; } if ( place.isValid() ) scoringString2.drawColumns( offsetX, offsetY, new String[] { Loc.scoring_place_prefix + ":", place.getValueAsString() + "/" + ( getConfiguration().getUseClassScoring() ? vsi.getNumVehiclesInSameClass() : scoringInfo.getNumVehicles() ) }, scoringAlignment, padding, scoringColWidths, texture ); else scoringString2.drawColumns( offsetX, offsetY, new String[] { Loc.scoring_place_prefix + ":", Loc.scoring_place_na }, scoringAlignment, padding, scoringColWidths, texture ); } if ( needsCompleteRedraw || fastestLap.hasValidityChanged() || fastestLap.hasChanged() ) { if ( !colWidthsUpdated ) { updateScoringColWidths( scoringInfo, leaderName ); colWidthsUpdated = true; } if ( fastestLap.isValid() ) scoringString3.drawColumns( offsetX, offsetY, new String[] { Loc.scoring_fastest_lap_prefix + ":", TimingUtil.getTimeAsLaptimeString( fastestLap.getValue() ) + " (" + fastestLapper + ")" }, scoringAlignment, padding, scoringColWidths, texture ); else scoringString3.drawColumns( offsetX, offsetY, new String[] { Loc.scoring_fastest_lap_prefix + ":", Loc.scoring_fastest_lap_na }, scoringAlignment, padding, scoringColWidths, texture ); } } if ( displayTiming.getBooleanValue() ) { String[] lapStringValue = new String[ 2 ]; String[] stintStringValue = new String[ 2 ]; String[] sessionTimeStringValue = new String[ 2 ]; boolean needsLapRedraw = false; boolean needsStintRedraw = false; boolean needsTimeRedraw = false; if ( gameData.getProfileInfo().getShowCurrentLap() ) lapStringValue[0] = Loc.timing_current_lap_prefix + ":"; else lapStringValue[0] = Loc.timing_laps_done_prefix + ":"; lapsCompleted.update( vsi.getLapsCompleted() ); final int maxLaps = scoringInfo.getEstimatedMaxLaps( vsi ); if ( maxLaps > 0 ) { float lapsRemaining = vsi.getLapsRemaining( maxLaps ); float roundedLapsRemaining; if ( vsi.getNormalizedLapDistance() >= 0.9f ) roundedLapsRemaining = (float)Math.ceil( lapsRemaining * 10f ) / 10f; else roundedLapsRemaining = Math.round( lapsRemaining * 10f ) / 10f; if ( scoringInfo.getSessionType().isRace() && ( ( scoringInfo.getGamePhase() == GamePhase.BEFORE_SESSION_HAS_BEGUN ) || ( scoringInfo.getGamePhase() == GamePhase.FORMATION_LAP ) ) ) lapStringValue[1] = "0 / " + maxLaps + " / " + maxLaps; else if ( gameData.getProfileInfo().getShowCurrentLap() ) lapStringValue[1] = ( lapsCompleted.getValue() + 1 ) + " / " + maxLaps + " / " + NumberUtil.formatFloat( roundedLapsRemaining, 1, true ); else lapStringValue[1] = lapsCompleted + " / " + maxLaps + " / " + NumberUtil.formatFloat( roundedLapsRemaining, 1, true ); gamePhase1.update( scoringInfo.getGamePhase() ); if ( needsCompleteRedraw || ( roundedLapsRemaining != oldLapsRemaining ) || gamePhase1.hasChanged() ) { oldLapsRemaining = roundedLapsRemaining; needsLapRedraw = true; } } else { if ( gameData.getProfileInfo().getShowCurrentLap() ) lapStringValue[1] = String.valueOf( lapsCompleted.getValue() + 1 ); else lapStringValue[1] = String.valueOf( lapsCompleted ); if ( needsCompleteRedraw || lapsCompleted.hasChanged() ) { needsLapRedraw = true; } } { stintStringValue[0] = Loc.timing_stintlength_prefix + ":"; int stintLength = (int)( ( !isEditorMode ? vsi.getStintLength() : 5.2f ) * 10f ); boolean changed = ( stintLength != oldStintLength ); if ( vsi.isInPits() ) { if ( oldStintLength < 0 ) stintStringValue[1] = Loc.timing_stintlength_na; else stintStringValue[1] = String.valueOf( Math.round( oldStintLength / 10f ) ); } else { oldStintLength = stintLength; stintStringValue[1] = String.valueOf( oldStintLength / 10f ); } if ( needsCompleteRedraw || changed ) { needsStintRedraw = true; } } sessionTimeStringValue[0] = Loc.timing_sessiontime_prefix + ":"; sessionTime.update( gameData.getScoringInfo().getSessionTime() ); gamePhase2.update( scoringInfo.getGamePhase() ); float endTime = gameData.getScoringInfo().getEndTime(); if ( scoringInfo.getGamePhase() == GamePhase.SESSION_OVER ) sessionTimeStringValue[1] = "00:00:00"; else if ( scoringInfo.getSessionType().isRace() && ( ( scoringInfo.getGamePhase() == GamePhase.FORMATION_LAP ) || ( endTime < 0f ) || ( endTime > 3000000f ) ) ) sessionTimeStringValue[1] = "--:--:--"; else if ( scoringInfo.getSessionType().isTestDay() || ( endTime < 0f ) || ( endTime > 3000000f ) ) sessionTimeStringValue[1] = TimingUtil.getTimeAsString( sessionTime.getValue(), true, false ); else sessionTimeStringValue[1] = TimingUtil.getTimeAsString( Math.min( sessionTime.getValue() - endTime, 0f ), true, false ); if ( needsCompleteRedraw || ( clock.c() && ( sessionTime.hasChanged( false ) || gamePhase2.hasChanged( false ) ) ) ) { sessionTime.setUnchanged(); gamePhase2.setUnchanged(); needsTimeRedraw = true; } boolean colWidthsUpdated = false; if ( needsLapRedraw ) { if ( !colWidthsUpdated ) { updateTimingColWidths( lapStringValue, stintStringValue, sessionTimeStringValue ); colWidthsUpdated = true; } lapString.drawColumns( offsetX, offsetY, lapStringValue, timingAlignment, padding, timingColWidths, texture ); } if ( needsStintRedraw ) { if ( !colWidthsUpdated ) { updateTimingColWidths( lapStringValue, stintStringValue, sessionTimeStringValue ); colWidthsUpdated = true; } stintString.drawColumns( offsetX, offsetY, stintStringValue, timingAlignment, padding, timingColWidths, texture ); } if ( needsTimeRedraw ) { if ( !colWidthsUpdated ) { updateTimingColWidths( lapStringValue, stintStringValue, sessionTimeStringValue ); colWidthsUpdated = true; } sessionTimeString.drawColumns( offsetX, offsetY, sessionTimeStringValue, timingAlignment, padding, timingColWidths, texture ); } } if ( displayVelocity.getBooleanValue() ) { float floatVelocity = vsi.isPlayer() ? gameData.getTelemetryData().getScalarVelocity() : vsi.getScalarVelocity(); int velocity = Math.round( floatVelocity ); if ( floatVelocity >= relTopspeed ) { relTopspeed = floatVelocity; getLocalStore().lastRelTopspeedTime = scoringInfo.getSessionNanos(); } else if ( ( getLocalStore().lastRelTopspeedTime + relTopspeedResetDelay.getDelayNanos() < scoringInfo.getSessionNanos() ) && ( floatVelocity < relTopspeed - 50f ) ) { relTopspeed = floatVelocity; oldRelTopspeed = -1f; //lastRelTopspeedTime = scoringInfo.getSessionNanos(); updateAbs = true; } boolean forceUpdateAbs = false; float topspeed = vsi.getTopspeed(); if ( topspeed < getLocalStore().lastDisplayedAbsTopspeed ) { getLocalStore().lastDisplayedAbsTopspeed = 0f; oldAbsTopspeed = -1f; topspeed = 0f; updateAbs = true; forceUpdateAbs = true; } if ( isEditorMode ) { topspeed = 301.7f; getLocalStore().lastDisplayedAbsTopspeed = topspeed; relTopspeed = 274.3f; } final String speedUnits = getSpeedUnits( gameData.getProfileInfo().getSpeedUnits() ); if ( needsCompleteRedraw || ( ( clock.c() || forceUpdateAbs ) && updateAbs && ( topspeed > oldAbsTopspeed ) ) ) { if ( !needsCompleteRedraw ) getLocalStore().lastDisplayedAbsTopspeed = topspeed; updateAbs = false; oldAbsTopspeed = topspeed; absTopspeedString.drawColumns( offsetX, offsetY, new String[] { Loc.velocity_topspeed1_prefix + ":", NumberUtil.formatFloat( getLocalStore().lastDisplayedAbsTopspeed, 1, true ), speedUnits }, velocityAlignment, padding, velocityColWidths, texture ); } if ( needsCompleteRedraw || ( clock.c() && ( relTopspeed > oldRelTopspeed ) ) ) { oldRelTopspeed = relTopspeed; relTopspeedString.drawColumns( offsetX, offsetY, new String[] { Loc.velocity_topspeed2_prefix + ":", NumberUtil.formatFloat( oldRelTopspeed, 1, true ), speedUnits }, velocityAlignment, padding, velocityColWidths, texture ); } if ( needsCompleteRedraw || ( clock.c() && ( velocity != oldVelocity ) ) ) { oldVelocity = velocity; velocityString.drawColumns( offsetX, offsetY, new String[] { Loc.velocity_velocity_prefix + ":", String.valueOf( velocity ), speedUnits }, velocityAlignment, padding, velocityColWidths, texture ); } } } }