private Boolean opponentIsRacing(float currentOpponentX, float currentOpponentY, float previousOpponentX, float previousOpponentY, pCarsAPIParticipantStruct playerData, pCarsAPIParticipantStruct previousPlayerData, float interval) { float deltaX = Math.Abs(currentOpponentX - playerData.mWorldPosition[0]); float deltaY = Math.Abs(currentOpponentY - playerData.mWorldPosition[2]); if (deltaX > trackWidth || deltaY > trackWidth) { return(false); } float opponentVelocityX = Math.Abs(currentOpponentX - previousOpponentX) / interval; float opponentVelocityY = Math.Abs(currentOpponentY - previousOpponentY) / interval; // hard code this - if the opponent car is going < 4m/s on both axis we're not interested if (opponentVelocityX < 4 && opponentVelocityY < 4) { return(false); } float playerVelocityX = Math.Abs(playerData.mWorldPosition[0] - previousPlayerData.mWorldPosition[0]) / interval; float playerVelocityY = Math.Abs(playerData.mWorldPosition[2] - previousPlayerData.mWorldPosition[2]) / interval; if (Math.Abs(playerVelocityX - opponentVelocityX) > maxClosingSpeed || Math.Abs(playerVelocityY - opponentVelocityY) > maxClosingSpeed) { // Console.WriteLine("high closing speed: x = " + (playerVelocityX - opponentVelocityX) + " y = " + (playerVelocityY - opponentVelocityY)); return(false); } return(true); }
public void trigger(Object lastStateObj, Object currentStateObj) { if (paused) { return; } CrewChiefV4.PCars.PCarsSharedMemoryReader.PCarsStructWrapper currentWrapper = (CrewChiefV4.PCars.PCarsSharedMemoryReader.PCarsStructWrapper)currentStateObj; pCarsAPIStruct currentState = currentWrapper.data; // game state is 3 for paused, 5 for replay. No idea what 4 is... if (currentState.mGameState == (uint)eGameState.GAME_FRONT_END || (currentState.mGameState == (uint)eGameState.GAME_INGAME_PAUSED && !System.Diagnostics.Debugger.IsAttached) || currentState.mGameState == (uint)eGameState.GAME_VIEWING_REPLAY || currentState.mGameState == (uint)eGameState.GAME_EXITED) { // don't ignore the paused game updates if we're in debug mode return; } CrewChiefV4.PCars.PCarsSharedMemoryReader.PCarsStructWrapper previousWrapper = (CrewChiefV4.PCars.PCarsSharedMemoryReader.PCarsStructWrapper)lastStateObj; pCarsAPIStruct lastState = previousWrapper.data; DateTime now = new DateTime(currentWrapper.ticksWhenRead); float interval = (float)(((double)currentWrapper.ticksWhenRead - (double)previousWrapper.ticksWhenRead) / (double)TimeSpan.TicksPerSecond); if (currentState.mRaceState == (int)eRaceState.RACESTATE_RACING && lastState.mRaceState != (int)eRaceState.RACESTATE_RACING) { timeToStartSpotting = now.Add(TimeSpan.FromSeconds(timeAfterRaceStartToActivate)); } // this check looks a bit funky... whe we start a practice session, the raceState is not_started // until we cross the line for the first time. Which is retarded really. if (currentState.mRaceState == (int)eRaceState.RACESTATE_INVALID || now < timeToStartSpotting || (currentState.mSessionState == (int)eSessionState.SESSION_RACE && currentState.mRaceState == (int) eRaceState.RACESTATE_NOT_STARTED)) { return; } if (enabled && currentState.mNumParticipants > 1 && (enableSpotterInTimetrial || currentState.mSessionState != (uint)eSessionState.SESSION_TIME_ATTACK)) { Tuple<int, pCarsAPIParticipantStruct> playerDataWithIndex = PCarsGameStateMapper.getPlayerDataStruct(currentState.mParticipantData, currentState.mViewedParticipantIndex); int playerIndex = playerDataWithIndex.Item1; pCarsAPIParticipantStruct playerData = playerDataWithIndex.Item2; float[] currentPlayerPosition = new float[] { playerData.mWorldPosition[0], playerData.mWorldPosition[2] }; if (currentState.mPitMode == (uint)ePitMode.PIT_MODE_NONE) { List<float[]> currentOpponentPositions = new List<float[]>(); float[] playerVelocityData = new float[3]; playerVelocityData[0] = currentState.mSpeed; playerVelocityData[1] = currentState.mWorldVelocity[0]; playerVelocityData[2] = currentState.mWorldVelocity[2]; for (int i = 0; i < currentState.mParticipantData.Count(); i++) { if (i == playerIndex) { continue; } pCarsAPIParticipantStruct opponentData = currentState.mParticipantData[i]; if (opponentData.mIsActive) { float[] currentPositions = new float[] { opponentData.mWorldPosition[0], opponentData.mWorldPosition[2] }; currentOpponentPositions.Add(currentPositions); } } if (currentOpponentPositions.Count() > 0) { float playerRotation = currentState.mOrientation[1]; if (playerRotation < 0) { playerRotation = (float)(2 * Math.PI) + playerRotation; } playerRotation = (float)(2 * Math.PI) - playerRotation; internalSpotter.triggerInternal(playerRotation, currentPlayerPosition, playerVelocityData, currentOpponentPositions); } } } }
public static pCarsAPIStruct MergeWithExistingState(pCarsAPIStruct existingState, sTelemetryData udpTelemetryData) { if (existingState.isSameClassAsPlayer == null) { existingState.isSameClassAsPlayer = new Boolean[(int)eAPIStructLengths.NUM_PARTICIPANTS]; } existingState.hasOpponentClassData = false; existingState.hasNewPositionData = false; existingState.mGameState = (uint)udpTelemetryData.sGameSessionState & 7; existingState.mSessionState = (uint)udpTelemetryData.sGameSessionState >> 4; existingState.mRaceState = (uint)udpTelemetryData.sRaceStateFlags & 7; // Participant Info existingState.mViewedParticipantIndex = udpTelemetryData.sViewedParticipantIndex; existingState.mNumParticipants = udpTelemetryData.sNumParticipants; // Unfiltered Input existingState.mUnfilteredThrottle = (float)udpTelemetryData.sUnfilteredThrottle / 255f; existingState.mUnfilteredBrake = (float)udpTelemetryData.sUnfilteredBrake / 255f; existingState.mUnfilteredSteering = (float)udpTelemetryData.sUnfilteredSteering / 127f; existingState.mUnfilteredClutch = (float)udpTelemetryData.sUnfilteredClutch / 255f; existingState.mLapsInEvent = udpTelemetryData.sLapsInEvent; existingState.mTrackLength = udpTelemetryData.sTrackLength; // Timing & Scoring existingState.mLapInvalidated = (udpTelemetryData.sRaceStateFlags >> 3 & 1) == 1; existingState.mSessionFastestLapTime = udpTelemetryData.sBestLapTime; existingState.mLastLapTime = udpTelemetryData.sLastLapTime; existingState.mCurrentTime = udpTelemetryData.sCurrentTime; existingState.mSplitTimeAhead = udpTelemetryData.sSplitTimeAhead; existingState.mSplitTimeBehind = udpTelemetryData.sSplitTimeBehind; existingState.mSplitTime = udpTelemetryData.sSplitTime; existingState.mEventTimeRemaining = udpTelemetryData.sEventTimeRemaining; existingState.mPersonalFastestLapTime = udpTelemetryData.sPersonalFastestLapTime; existingState.mWorldFastestLapTime = udpTelemetryData.sWorldFastestLapTime; existingState.mCurrentSector1Time = udpTelemetryData.sCurrentSector1Time; existingState.mCurrentSector2Time = udpTelemetryData.sCurrentSector2Time; existingState.mCurrentSector3Time = udpTelemetryData.sCurrentSector3Time; existingState.mSessionFastestSector1Time = udpTelemetryData.sFastestSector1Time; existingState.mSessionFastestSector2Time = udpTelemetryData.sFastestSector2Time; existingState.mSessionFastestSector3Time = udpTelemetryData.sFastestSector3Time; existingState.mPersonalFastestSector1Time = udpTelemetryData.sPersonalFastestSector1Time; existingState.mPersonalFastestSector2Time = udpTelemetryData.sPersonalFastestSector2Time; existingState.mPersonalFastestSector3Time = udpTelemetryData.sPersonalFastestSector3Time; existingState.mWorldFastestSector1Time = udpTelemetryData.sWorldFastestSector1Time; existingState.mWorldFastestSector2Time = udpTelemetryData.sWorldFastestSector2Time; existingState.mWorldFastestSector3Time = udpTelemetryData.sWorldFastestSector3Time; // Flags existingState.mHighestFlagColour = (uint)udpTelemetryData.sHighestFlag & 7; existingState.mHighestFlagReason = (uint)udpTelemetryData.sHighestFlag >> 3 & 3; // Pit Info existingState.mPitMode = (uint)udpTelemetryData.sPitModeSchedule & 7; existingState.mPitSchedule = (uint)udpTelemetryData.sPitModeSchedule >> 3 & 3; // Car State existingState.mCarFlags = udpTelemetryData.sCarFlags; existingState.mOilTempCelsius = udpTelemetryData.sOilTempCelsius; existingState.mOilPressureKPa = udpTelemetryData.sOilPressureKPa; existingState.mWaterTempCelsius = udpTelemetryData.sWaterTempCelsius; existingState.mWaterPressureKPa = udpTelemetryData.sWaterPressureKpa; existingState.mFuelPressureKPa = udpTelemetryData.sFuelPressureKpa; existingState.mFuelLevel = udpTelemetryData.sFuelLevel; existingState.mFuelCapacity = udpTelemetryData.sFuelCapacity; existingState.mSpeed = udpTelemetryData.sSpeed; existingState.mRPM = udpTelemetryData.sRpm; existingState.mMaxRPM = udpTelemetryData.sMaxRpm; existingState.mBrake = (float)udpTelemetryData.sBrake / 255f; existingState.mThrottle = (float)udpTelemetryData.sThrottle / 255f; existingState.mClutch = (float)udpTelemetryData.sClutch / 255f; existingState.mSteering = (float)udpTelemetryData.sSteering / 127f; existingState.mGear = udpTelemetryData.sGearNumGears & 15; existingState.mNumGears = udpTelemetryData.sGearNumGears >> 4; existingState.mOdometerKM = udpTelemetryData.sOdometerKM; existingState.mAntiLockActive = (udpTelemetryData.sRaceStateFlags >> 4 & 1) == 1; existingState.mBoostActive = (udpTelemetryData.sRaceStateFlags >> 5 & 1) == 1; existingState.mBoostAmount = udpTelemetryData.sBoostAmount; // Motion & Device Related existingState.mOrientation = udpTelemetryData.sOrientation; existingState.mLocalVelocity = udpTelemetryData.sLocalVelocity; existingState.mWorldVelocity = udpTelemetryData.sWorldVelocity; existingState.mAngularVelocity = udpTelemetryData.sAngularVelocity; existingState.mLocalAcceleration = udpTelemetryData.sLocalAcceleration; existingState.mWorldAcceleration = udpTelemetryData.sWorldAcceleration; existingState.mExtentsCentre = udpTelemetryData.sExtentsCentre; existingState.mTyreFlags = toUIntArray(udpTelemetryData.sTyreFlags); existingState.mTerrain = toUIntArray(udpTelemetryData.sTerrain); existingState.mTyreY = udpTelemetryData.sTyreY; existingState.mTyreRPS = udpTelemetryData.sTyreRPS; existingState.mTyreSlipSpeed = udpTelemetryData.sTyreSlipSpeed; existingState.mTyreTemp = toFloatArray(udpTelemetryData.sTyreTemp, 255); existingState.mTyreGrip = toFloatArray(udpTelemetryData.sTyreGrip, 255); existingState.mTyreHeightAboveGround = udpTelemetryData.sTyreHeightAboveGround; existingState.mTyreLateralStiffness = udpTelemetryData.sTyreLateralStiffness; existingState.mTyreWear = toFloatArray(udpTelemetryData.sTyreWear, 255); existingState.mBrakeDamage = toFloatArray(udpTelemetryData.sBrakeDamage, 255); existingState.mSuspensionDamage = toFloatArray(udpTelemetryData.sSuspensionDamage, 255); existingState.mBrakeTempCelsius = toFloatArray(udpTelemetryData.sBrakeTempCelsius, 1); existingState.mTyreTreadTemp = toFloatArray(udpTelemetryData.sTyreTreadTemp, 1); existingState.mTyreLayerTemp = toFloatArray(udpTelemetryData.sTyreLayerTemp, 1); existingState.mTyreCarcassTemp = toFloatArray(udpTelemetryData.sTyreCarcassTemp, 1); existingState.mTyreRimTemp = toFloatArray(udpTelemetryData.sTyreRimTemp, 1); existingState.mTyreInternalAirTemp = toFloatArray(udpTelemetryData.sTyreInternalAirTemp, 1); existingState.mWheelLocalPosition = udpTelemetryData.sWheelLocalPositionY; existingState.mRideHeight = udpTelemetryData.sRideHeight; existingState.mSuspensionTravel = udpTelemetryData.sSuspensionTravel; existingState.mSuspensionVelocity = udpTelemetryData.sSuspensionVelocity; existingState.mAirPressure = toFloatArray(udpTelemetryData.sAirPressure, 1); existingState.mEngineSpeed = udpTelemetryData.sEngineSpeed; existingState.mEngineTorque = udpTelemetryData.sEngineTorque; existingState.mEnforcedPitStopLap = udpTelemetryData.sEnforcedPitStopLap; // Car Damage existingState.mCrashState = udpTelemetryData.sCrashState; existingState.mAeroDamage = (float)udpTelemetryData.sAeroDamage / 255f; existingState.mEngineDamage = (float)udpTelemetryData.sEngineDamage / 255f; // Weather existingState.mAmbientTemperature = udpTelemetryData.sAmbientTemperature; existingState.mTrackTemperature = udpTelemetryData.sTrackTemperature; existingState.mRainDensity = (float)udpTelemetryData.sRainDensity / 255f; existingState.mWindSpeed = udpTelemetryData.sWindSpeed * 2; existingState.mWindDirectionX = (float)udpTelemetryData.sWindDirectionX / 127f; existingState.mWindDirectionY = (float)udpTelemetryData.sWindDirectionY / 127f; //existingState.mCloudBrightness = udpTelemetryData.sCloudBrightness / 255; if (existingState.mParticipantData == null) { existingState.mParticipantData = new pCarsAPIParticipantStruct[56]; } if (existingState.mLastSectorData == null) { existingState.mLastSectorData = new float[56]; } if (existingState.mLapInvalidatedData == null) { existingState.mLapInvalidatedData = new Boolean[56]; } for (int i = 0; i < udpTelemetryData.sParticipantInfo.Count(); i++) { sParticipantInfo newPartInfo = udpTelemetryData.sParticipantInfo[i]; Boolean isActive = (newPartInfo.sRacePosition >> 7) == 1; pCarsAPIParticipantStruct existingPartInfo = existingState.mParticipantData[i]; if (isActive) { existingPartInfo.mIsActive = i < existingState.mNumParticipants; existingPartInfo.mCurrentLap = newPartInfo.sCurrentLap; existingPartInfo.mCurrentLapDistance = newPartInfo.sCurrentLapDistance; existingPartInfo.mLapsCompleted = (uint)newPartInfo.sLapsCompleted & 127; Boolean lapInvalidated = (newPartInfo.sLapsCompleted >> 7) == 1; existingPartInfo.mRacePosition = (uint)newPartInfo.sRacePosition & 127; existingPartInfo.mCurrentSector = (uint)newPartInfo.sSector & 7; Boolean sameClassAsPlayer = (newPartInfo.sSector >> 3 & 1) == 1; if (sameClassAsPlayer) { existingState.hasOpponentClassData = true; } existingState.isSameClassAsPlayer[i] = sameClassAsPlayer; // and now the bit magic for the extra position precision... float[] newWorldPositions = toFloatArray(newPartInfo.sWorldPosition, 1); float xAdjustment = ((float)((uint)newPartInfo.sSector >> 6 & 3)) / 4f; float zAdjustment = ((float)((uint)newPartInfo.sSector >> 4 & 3)) / 4f; newWorldPositions[0] = newWorldPositions[0] + xAdjustment; newWorldPositions[2] = newWorldPositions[2] + zAdjustment; if (!existingState.hasNewPositionData && i != udpTelemetryData.sViewedParticipantIndex && (existingPartInfo.mWorldPosition == null || (newWorldPositions[0] != existingPartInfo.mWorldPosition[0] || newWorldPositions[2] != existingPartInfo.mWorldPosition[2]))) { existingState.hasNewPositionData = true; } existingPartInfo.mWorldPosition = newWorldPositions; // LastSectorTime is now in the UDP data, but there's no slot for this in the participants struct // so bung it in a separate array at the end existingState.mLastSectorData[i] = newPartInfo.sLastSectorTime; existingState.mLapInvalidatedData[i] = lapInvalidated; } else { existingPartInfo.mWorldPosition = new float[] { 0, 0, 0 }; existingPartInfo.mIsActive = false; } existingState.mParticipantData[i] = existingPartInfo; } return(existingState); }
public void trigger(Object lastStateObj, Object currentStateObj) { if (paused) { audioPlayer.closeChannel(); return; } CrewChiefV4.PCars.PCarsSharedMemoryReader.PCarsStructWrapper currentWrapper = (CrewChiefV4.PCars.PCarsSharedMemoryReader.PCarsStructWrapper)currentStateObj; pCarsAPIStruct currentState = currentWrapper.data; // game state is 3 for paused, 5 for replay. No idea what 4 is... if (currentState.mGameState == (uint)eGameState.GAME_FRONT_END || (currentState.mGameState == (uint)eGameState.GAME_INGAME_PAUSED && !System.Diagnostics.Debugger.IsAttached) || currentState.mGameState == (uint)eGameState.GAME_VIEWING_REPLAY || currentState.mGameState == (uint)eGameState.GAME_EXITED) { // don't ignore the paused game updates if we're in debug mode audioPlayer.closeChannel(); return; } CrewChiefV4.PCars.PCarsSharedMemoryReader.PCarsStructWrapper previousWrapper = (CrewChiefV4.PCars.PCarsSharedMemoryReader.PCarsStructWrapper)lastStateObj; pCarsAPIStruct lastState = previousWrapper.data; DateTime now = new DateTime(currentWrapper.ticksWhenRead); float interval = (float)(((double)currentWrapper.ticksWhenRead - (double)previousWrapper.ticksWhenRead) / (double)TimeSpan.TicksPerSecond); if (currentState.mRaceState == (int)eRaceState.RACESTATE_RACING && lastState.mRaceState != (int)eRaceState.RACESTATE_RACING) { timeToStartSpotting = now.Add(TimeSpan.FromSeconds(timeAfterRaceStartToActivate)); } // this check looks a bit funky... whe we start a practice session, the raceState is not_started // until we cross the line for the first time. Which is retarded really. if (currentState.mRaceState == (int)eRaceState.RACESTATE_INVALID || now < timeToStartSpotting || (currentState.mSessionState == (int)eSessionState.SESSION_RACE && currentState.mRaceState == (int)eRaceState.RACESTATE_NOT_STARTED)) { return; } float currentSpeed = currentState.mSpeed; float previousSpeed = lastState.mSpeed; if (enabled && currentState.mParticipantData.Count() > 1) { Tuple <int, pCarsAPIParticipantStruct> playerDataWithIndex = PCarsGameStateMapper.getPlayerDataStruct(currentState.mParticipantData, currentState.mViewedParticipantIndex); int playerIndex = playerDataWithIndex.Item1; pCarsAPIParticipantStruct playerData = playerDataWithIndex.Item2; float playerX = playerData.mWorldPosition[0]; float playerY = playerData.mWorldPosition[2]; if (playerX == 0 || playerY == 0 || playerX == -1 || playerY == -1 || lastState.mParticipantData == null || lastState.mParticipantData.Length == 0 || lastState.mViewedParticipantIndex < 0) { return; } Tuple <int, pCarsAPIParticipantStruct> previousPlayerDataWithIndex = PCarsGameStateMapper.getPlayerDataStruct(lastState.mParticipantData, lastState.mViewedParticipantIndex); pCarsAPIParticipantStruct previousPlayerData = previousPlayerDataWithIndex.Item2; if (currentSpeed > minSpeedForSpotterToOperate && currentState.mPitMode == (uint)ePitMode.PIT_MODE_NONE) { int carsOnLeft = 0; int carsOnRight = 0; for (int i = 0; i < currentState.mParticipantData.Count(); i++) { if (i == playerIndex) { continue; } if (carsOnLeft >= 1 && carsOnRight >= 1) { // stop processing - we already know there's a car on both sides break; } pCarsAPIParticipantStruct opponentData = currentState.mParticipantData[i]; float previousOpponentX = 0; float previousOpponentY = 0; try { pCarsAPIParticipantStruct previousOpponentData = PCarsGameStateMapper.getParticipantDataForName(lastState.mParticipantData, opponentData.mName, i); previousOpponentX = previousOpponentData.mWorldPosition[0]; previousOpponentY = previousOpponentData.mWorldPosition[2]; } catch (Exception) { // ignore - the mParticipantData array is frequently full of crap } float currentOpponentX = opponentData.mWorldPosition[0]; float currentOpponentY = opponentData.mWorldPosition[2]; if (opponentData.mIsActive) { if (currentOpponentX != 0 && currentOpponentY != 0 && currentOpponentX != -1 && currentOpponentY != -1 && previousOpponentX != 0 && previousOpponentY != 0 && previousOpponentX != -1 && previousOpponentY != -1 && opponentIsRacing(currentOpponentX, currentOpponentY, previousOpponentX, previousOpponentY, playerData, previousPlayerData, interval)) { Side side = getSide(currentState.mOrientation[1], playerX, playerY, currentOpponentX, currentOpponentY); if (side == Side.left) { carsOnLeft++; if (lastKnownOpponentState.ContainsKey(opponentData.mName)) { lastKnownOpponentState[opponentData.mName] = Side.left; } else { lastKnownOpponentState.Add(opponentData.mName, Side.left); } } else if (side == Side.right) { carsOnRight++; if (lastKnownOpponentState.ContainsKey(opponentData.mName)) { lastKnownOpponentState[opponentData.mName] = Side.right; } else { lastKnownOpponentState.Add(opponentData.mName, Side.right); } } else { if (lastKnownOpponentState.ContainsKey(opponentData.mName)) { lastKnownOpponentState[opponentData.mName] = Side.none; } else { lastKnownOpponentState.Add(opponentData.mName, Side.none); } } } else { // no usable position data, use the last known state if (lastKnownOpponentState.ContainsKey(opponentData.mName)) { int lastStateUseCount = 1; if (lastKnownOpponentStateUseCounter.ContainsKey(opponentData.mName)) { lastStateUseCount = lastKnownOpponentStateUseCounter[opponentData.mName] + 1; } else { lastKnownOpponentStateUseCounter.Add(opponentData.mName, 0); } if (lastStateUseCount < maxSavedStateReuse) { lastKnownOpponentStateUseCounter[opponentData.mName] = lastStateUseCount; if (lastKnownOpponentState[opponentData.mName] == Side.left) { carsOnLeft++; } else if (lastKnownOpponentState[opponentData.mName] == Side.right) { carsOnRight++; } } else { // we've used too many saved states for this missing opponent position lastKnownOpponentState.Remove(opponentData.mName); lastKnownOpponentStateUseCounter.Remove(opponentData.mName); } } } } } getNextMessage(carsOnLeft, carsOnRight, now); playNextMessage(carsOnLeft, carsOnRight, now); hasCarLeft = carsOnLeft > 0; hasCarRight = carsOnRight > 0; } else if (hasCarLeft || hasCarRight) { if (!channelLeftOpenTimerStarted) { timeWhenChannelShouldBeClosed = now.Add(timeToWaitBeforeClosingChannelLeftOpen); channelLeftOpenTimerStarted = true; } if (now > timeWhenChannelShouldBeClosed) { Console.WriteLine("Closing channel left open in spotter"); timeWhenChannelShouldBeClosed = DateTime.MaxValue; hasCarLeft = false; hasCarRight = false; audioPlayer.closeChannel(); channelLeftOpenTimerStarted = false; } } } }