Files
2025-09-29 00:52:08 +02:00

3030 lines
127 KiB
XML
Executable File

// *****************************************************************************************
// *****************************************************************************************
// *****************************************************************************************
//
// SCRIPT NAME : TRI_Triathlon_AI.sch
// AUTHOR : Carlos Mijares (CM)
// DESCRIPTION : Functions and processes that affect AI in Triathlon races.
//
// *****************************************************************************************
// *****************************************************************************************
// *****************************************************************************************
// =====================================
// FILE INCLUDES
// =====================================
// -----------------------------------
// GENERAL INCLUDES
// -----------------------------------
USING "minigames_helpers.sch"
USING "minigame_uiinputs.sch"
USING "commands_xml.sch"
USING "commands_water.sch"
// -----------------------------------
// SPR FILE INCLUDES
// -----------------------------------
USING "tri_helpers_triathlon.sch"
USING "tri_dialogue.sch"
// =====================================
// E N D FILE INCLUDES
// =====================================
// ===================================
// TRIATHLON AI VARIABLES
// ===================================
structTimer timerToUpdateAI
CONST_FLOAT fSecondsBeforeUpdateAIAgain 1.0
// Number of racers in Triathlon.
CONST_INT iTriNumRacers 8
// Tracks when the racers are set to their respective anims once the race ends.
BOOL bIsEndOfRaceRacerAnimSet
// Task sequences.
SEQUENCE_INDEX seqRacerGetInBike
SEQUENCE_INDEX seqRacerEndOfRaceRace[iTriNumRacers]
SEQUENCE_INDEX seqRacerFinishRace[iTriNumRacers]
//SEQUENCE_INDEX seqNextGate
CONST_FLOAT TRI_TURN_ROUNDING_SCALAR 0.1
BOOL bIsRacerFinishRaceSequenceSet[iTriNumRacers]
// Track a racer's last destination gate to ensure it's far past the final gate.
VECTOR vLastGateRacerDestination
// Vehicle recordings used by Triathlon racers in Ironman.
STRING szTriBikeRecordingName_IronMan_0
STRING szTriBikeRecordingName_IronMan_1
STRING szTriBikeRecordingName_IronMan_2
STRING szTriBikeRecordingName_IronMan_3
STRING szTriBikeRecordingName_IronMan_4
STRING szExlusiveScenarioGroup = NULL
// -------------------------------------------
// AI SPEED AND RUBBER-BANDING VARIABLES
// -------------------------------------------
// Retain original min and max speed values before they're updated.
FLOAT fTriOriginalMinAIRacerRunSpeeds[iTriNumRacers]
FLOAT fTriOriginalMaxAIRacerRunSpeeds[iTriNumRacers]
// This is the max value to scale the move blend ratio of a ped.
CONST_FLOAT fOnFootMaxMoveBlendRatio 1.15
// Distance constants determining how close a racer is to the player.
CONST_FLOAT fCloseDistanceBetweenRacers 6.75
// Struct storing the speed values of AI racers.
STRUCT TRI_RACER_AI_SPEED_STRUCT
// Min and max speed limits an AI racer can ever swim in.
FLOAT fMinDeltaSwimLimit
FLOAT fMaxDeltaSwimLimit
// Min and max speed limits an AI racer can ever run in.
FLOAT fMinDeltaRunLimit
FLOAT fMaxDeltaRunLimit
// Min and max speed limits an AI racer can ever ride a bike in.
FLOAT fMinDeltaBikeLimit
FLOAT fMaxDeltaBikeLimit
// Default max speed of a TRIBIKE vehicle on a flat surface.
FLOAT fDefaultTribikeTopSpeedOnFlatSurface
ENDSTRUCT
TRI_RACER_AI_SPEED_STRUCT Tri_Racer_AI_Speed
// Timer limits for switching between compete modes.
CONST_INT iTimerLimitInCompeteDefaultMode 10
CONST_INT iTimerLimitInCompeteTiredMode 7
CONST_INT iTimerLimitInCompeteAggressiveMode 7
// Rubber-banding distance constants for swim leg.
CONST_INT iShortRubberBandSwimDistance 5
CONST_INT iMediumRubberBandSwimDistance 11
CONST_INT iLongRubberBandSwimDistance 20
// Rubber-banding distance constants for bike leg.
CONST_INT iShortRubberBandBikeDistance 10
CONST_INT iMediumRubberBandBikeDistance 22
CONST_INT iLongRubberBandBikeDistance 40
// Rubber-banding distance constants for run leg.
CONST_INT iShortRubberBandRunDistance 5
CONST_INT iMediumRubberBandRunDistance 11
CONST_INT iLongRubberBandRunDistance 20
// -------------------------------------------
// AI COMPETE MODE VARIABLES
// -------------------------------------------
INT iLimitOfAggressiveRacers
INT iLimitOfTiredRacers
INT iLimitOfForcedTiredRacers
INT iRacersInAggressiveMode
INT iRacersInTiredMode
INT iRacersInForcedTiredMode
// -----------------------------
// AI ANIMATION VARIABLES
// -----------------------------
// Number of end-of-race animations used.
CONST_INT iNumTriAnimDicts 4
CONST_INT iNumTriWinningAnims 4
CONST_INT iNumTriFinishedAnims 3
// Array holding all possible end-of-race animations.
STRING szRaceWinningAnims[iNumTriWinningAnims]
STRING szRaceFinishedAnims[iNumTriFinishedAnims]
// End-of-race animations dictionary name.
STRING szTriAnimDicts[iNumTriAnimDicts]
// -----------------------------
// AI SUPERDUMMY VARIABLES
// -----------------------------
INT iPlayerCurrentGateToWarpSuperDummySwimRacer
// -----------------------------
// DEBUG AI VARIABLES
// -----------------------------
#IF IS_DEBUG_BUILD
VECTOR vCurrentGatePos_Debug
VECTOR vNextGatePos_Debug
VECTOR vMidPointBetweenCurrentAndNextGate_Debug
VECTOR vGateGotoWithOffset_Debug
#ENDIF
// ===================================
// E N D TRIATHLON AI VARIABLES
// ===================================
// ======================================================
// AI GENERAL FUNCTIONS AND PROCESSES
// ======================================================
/// PURPOSE:
/// Gets a random animation clip from a animation dictionary
/// used in Triathlon.
FUNC STRING GET_RANDOM_ANIM_CLIP_FROM_TRI_ANIM_DICTIONARY(STRING szAnimDictName)
IF ARE_STRINGS_EQUAL(szAnimDictName, szTriAnimDicts[0])
RETURN szRaceWinningAnims[ GET_RANDOM_INT_IN_RANGE(0, COUNT_OF(szRaceWinningAnims)) ]
ELIF ARE_STRINGS_EQUAL(szAnimDictName, szTriAnimDicts[1])
RETURN szRaceFinishedAnims[GET_RANDOM_INT_IN_RANGE(0, COUNT_OF(szRaceFinishedAnims)) ]
ENDIF
// Script should never reach here.
RETURN "NULL"
ENDFUNC
// ======================================================
// E N D AI GENERAL FUNCTIONS AND PROCESSES
// ======================================================
// ===========================================================================
// RACER SPEED UPDATE FUNCTIONS AND PROCESSES
// ===========================================================================
/// PURPOSE:
/// Speed up racers when they're trying to get on a bike.
PROC TASK_TRI_RACERS_TO_SPRINT_TO_BIKES_AFTER_SWIM_LEG(TRI_RACE_STRUCT& Race, INT iRacerIndex)
IF iRacerIndex <> 0
IF Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_BIKE
IF NOT IS_ENTITY_DEAD(Race.Racer[iRacerIndex].Driver)
IF NOT IS_TRI_RACER_GETTING_ON_OR_IS_HE_ALREADY_ON_A_TRIBIKE(Race, iRacerIndex)
SET_PED_MOVE_RATE_OVERRIDE(Race.Racer[iRacerIndex].Driver, 1.15)
ENDIF
ENDIF
ENDIF
ENDIF
ENDPROC
/// PURPOSE:
/// Waits for the racer to be off the bike, then tells him he can remove his helmet per B*1472881
PROC TASK_TRI_RACERS_TO_REMOVE_HELMETS_AFTER_BIKE_LEG(TRI_RACE_STRUCT& Race, INT iRacerIndex)
IF Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_RUN
#IF IS_DEBUG_BUILD
#IF TRI_DEBUG_DISPLAY_TEXT
TEXT_LABEL_31 sDebugHelmetState
sDebugHelmetState = "On Run Leg # "
sDebugHelmetState += iRacerIndex
DRAW_DEBUG_TEXT_2D(sDebugHelmetState,<<0.1,(0.12 + (iRacerIndex * 0.02)), 0.0>>)
#ENDIF
#ENDIF
IF GET_PED_CONFIG_FLAG(Race.Racer[iRacerIndex].Driver,PCF_DontTakeOffHelmet)
IF IS_PED_WEARING_HELMET(Race.Racer[iRacerIndex].Driver)
#IF IS_DEBUG_BUILD
#IF TRI_DEBUG_DISPLAY_TEXT
TEXT_LABEL_31 sDebugHelmetOn
sDebugHelmetOn = "Helmet on for # "
sDebugHelmetOn += iRacerIndex
DRAW_DEBUG_TEXT_2D(sDebugHelmetOn,<<0.26,(0.12 + (iRacerIndex * 0.02)), 0.0>>)
#ENDIF
#ENDIF
REMOVE_PED_HELMET(Race.Racer[iRacerIndex].Driver,FALSE)
SET_PED_CONFIG_FLAG(Race.Racer[iRacerIndex].Driver,PCF_DontTakeOffHelmet,FALSE)
ENDIF
ENDIF
ENDIF
ENDPROC
/// PURPOSE:
/// Sets the maximum speed a racer bike can ever go.
///
/// NOTE: While 100 is too high a value, this is to ensure
/// racers can easily reach whatever high values are set
/// for their max speed. If the top bike speed is
/// less than the max allowed speed, the bike will never reach
/// said max speed, so top bike speed always needs to be higher.
PROC SET_TRI_RACER_TOP_BIKE_POSSIBLE_SPEED(TRI_RACE_STRUCT& Race, INT iRacerIndex)
IF NOT IS_ENTITY_DEAD(Race.Racer[iRacerIndex].Vehicle)
MODIFY_VEHICLE_TOP_SPEED(Race.Racer[iRacerIndex].Vehicle, 100.00)
ENDIF
ENDPROC
/// PURPOSE:
/// Set up weak and strong bike racers.
///
/// NOTE: This is called once per race to determine which racers will
/// be fast, and which will be slowed. Based on the iSkillPlacement
/// value they're randomly set to, their min and max bike speeds
/// will vary.
///
/// This is called once the player has chosen a bike, to guarantee
/// the player's bike isn't speed-altered like other racers'.
PROC SET_TRI_RACERS_SKILL_LEVEL_IN_BIKE_LEG(TRI_RACE_STRUCT& Race)
INT iLoopCounter
// Temporarily store skill values (1 through 7) in an array.
INT iTempSkillPlacementArray[7]
REPEAT COUNT_OF(iTempSkillPlacementArray) iLoopCounter
iTempSkillPlacementArray[iLoopCounter] = iLoopCounter + 1
ENDREPEAT
// Randomize the array, so the racers are randomly given a kill level.
RANDOMIZE_ARRAY_OF_INTS(iTempSkillPlacementArray)
// Assign the skill values to each racer.
INT iRacerCounter
REPEAT Race.iRacerCnt iRacerCounter
IF NOT IS_ENTITY_DEAD(Race.Racer[iRacerCounter].Driver)
IF iRacerCounter <> 0
Race.Racer[iRacerCounter].iSkillPlacement = iTempSkillPlacementArray[iRacerCounter - 1]
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI.sch->SET_TRI_RACERS_SKILL_LEVEL_IN_BIKE_LEG] Racer #", iRacerCounter, " is set to skill level ", Race.Racer[iRacerCounter].iSkillPlacement)
SET_TRI_RACER_TOP_BIKE_POSSIBLE_SPEED(Race, iRacerCounter)
ELSE
Race.Racer[0].iSkillPlacement = -1
ENDIF
ENDIF
ENDREPEAT
ENDPROC
// ---------------------------------------------------------------
// AI LEG MODIFIER PROCEDURES AND FUNCTIONS
//
// These procedures are for changing AI behavior at very specific
// points in a race, in case the AI system isn't enough to handle
// a desired behavior.
//
// ---------------------------------------------------------------
/// PURPOSE:
/// Increment the amount of AI racers that can be in a FORCED TIRED compete state
/// at any one time in the swim leg as AI racers reach a specified gate. This
/// will force more AI to remain at the slowest state.
PROC UPDATE_TRI_RACER_AI_SWIM_LEG_MODIFIER_SLOWDOWN(TRI_RACE_STRUCT& Race, INT iRacerIndex)
// As a racer reaches the middle of the swim leg, allow more tired racers.
IF (Tri_Is_Racer_In_Second_Half_Of_Swim_Leg(Race, iRacerIndex) AND eCurrentTriRace = TRIATHLON_RACE_IRONMAN)
OR eCurrentTriRace <> TRIATHLON_RACE_IRONMAN
IF (iRacersInForcedTiredMode < iLimitOfForcedTiredRacers)
IF NOT (Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_FORCED_TIRED)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_FORCED_TIRED
iRacersInForcedTiredMode++
ENDIF
ENDIF
ELIF Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_FORCED_TIRED
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_DEFAULT
ENDIF
// In Ironman, update the limit racers can be forced to tire, so the finish is more spread out.
SWITCH eCurrentTriRace
CASE TRIATHLON_RACE_ALAMO_SEA
CASE TRIATHLON_RACE_VESPUCCI
IF (Race.Racer[iRacerIndex].iGateCur >= (Tri_Get_Middle_Swim_Gate() + 1)) AND (iLimitOfForcedTiredRacers < 5)
iLimitOfForcedTiredRacers++
ELIF (Race.Racer[iRacerIndex].iGateCur >= (Tri_Get_Middle_Swim_Gate() + 2)) AND (iLimitOfForcedTiredRacers < 6)
iLimitOfForcedTiredRacers++
ENDIF
BREAK
CASE TRIATHLON_RACE_IRONMAN
IF (Race.Racer[iRacerIndex].iGateCur >= 5) AND (iLimitOfForcedTiredRacers < 5)
iLimitOfForcedTiredRacers++
ELIF (Race.Racer[iRacerIndex].iGateCur >= 6) AND (iLimitOfForcedTiredRacers < 6)
iLimitOfForcedTiredRacers++
ENDIF
BREAK
ENDSWITCH
ENDPROC
/// PURPOSE:
/// Apply any modifiers to a racer in the swim leg.
PROC UPDATE_TRI_RACER_AI_SWIM_LEG_MODIFIER(TRI_RACE_STRUCT& Race, INT iRacerIndex)
UPDATE_TRI_RACER_AI_SWIM_LEG_MODIFIER_SLOWDOWN(Race, iRacerIndex)
ENDPROC
/// PURPOSE:
/// Increment the amount of AI racers that can be in a FORCED TIRED compete state
/// at any one time in the run leg as AI racers reach a specified gate. This
/// will force more AI to remain at the slowest state.
PROC UPDATE_TRI_RACER_AI_RUN_LEG_MODIFIER_SLOWDOWN(TRI_RACE_STRUCT& Race, INT iRacerIndex)
// As a racer reaches the middle of the run leg, allow more tired racers.
IF NOT Tri_Is_Racer_In_Second_Half_Of_Run_Leg(Race, iRacerIndex)
IF (iRacersInForcedTiredMode < iLimitOfForcedTiredRacers)
IF NOT (Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_FORCED_TIRED)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_FORCED_TIRED
iRacersInForcedTiredMode++
ENDIF
ENDIF
ENDIF
// In Ironman, update the limit racers can be forced to tire, so the finish is more spread out.
SWITCH eCurrentTriRace
CASE TRIATHLON_RACE_ALAMO_SEA
CASE TRIATHLON_RACE_VESPUCCI
IF (Race.Racer[iRacerIndex].iGateCur >= (Tri_Get_Bike_To_Run_Transition_Gate() + 2)) AND (iLimitOfForcedTiredRacers < 5)
iLimitOfForcedTiredRacers++
ELIF (Race.Racer[iRacerIndex].iGateCur >= (Tri_Get_Bike_To_Run_Transition_Gate() + 3)) AND (iLimitOfForcedTiredRacers < 6)
iLimitOfForcedTiredRacers++
ENDIF
BREAK
CASE TRIATHLON_RACE_IRONMAN
IF (Race.Racer[iRacerIndex].iGateCur >= 94) AND (iLimitOfForcedTiredRacers < 5)
iLimitOfForcedTiredRacers++
ELIF (Race.Racer[iRacerIndex].iGateCur >= 102) AND (iLimitOfForcedTiredRacers < 6)
iLimitOfForcedTiredRacers++
ENDIF
BREAK
ENDSWITCH
ENDPROC
/// PURPOSE:
/// Apply any modifiers to a racer in the run leg.
PROC UPDATE_TRI_RACER_AI_RUN_LEG_MODIFIER(TRI_RACE_STRUCT& Race, INT iRacerIndex)
UPDATE_TRI_RACER_AI_RUN_LEG_MODIFIER_SLOWDOWN(Race, iRacerIndex)
ENDPROC
/// PURPOSE:
/// Change the number of racers allowed in a compete state based on current player leg.
PROC UPDATE_TRI_RACER_AI_LEG_MODIFIER_BY_PLAYER_LEG(TRI_RACE_STRUCT& Race)
TRI_TRI_RACE_LEG eCurrentPlayerTriLeg = Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[0])
SWITCH (eCurrentPlayerTriLeg)
CASE TRI_TRI_RACE_LEG_SWIM
// Limit of Aggro/Tired racers set at start of race.
BREAK
CASE TRI_TRI_RACE_LEG_BIKE
// Limit the number of aggro or tired racers in the bike leg.
iLimitOfAggressiveRacers = 4
iLimitOfTiredRacers = 2
iLimitOfForcedTiredRacers = 0
BREAK
CASE TRI_TRI_RACE_LEG_RUN
// Limit the number of aggro or tired racers in the run leg.
iLimitOfAggressiveRacers = 2
iLimitOfTiredRacers = 2
iLimitOfForcedTiredRacers = 0
BREAK
ENDSWITCH
ENDPROC
/// PURPOSE:
/// Set a limit to how fast or slow AI can be set to, based on player gate position.
/// These values are used to cap the player's speed when it's increased through the
/// racer's compete mode and rubber-banding.
///
/// NOTE: Alternatively, we can change a racer's max speed based on his position
/// in the race, or position based on gate. Or both.
PROC UPDATE_TRI_RACER_AI_LEG_MODIFIER_MIN_AND_MAX_SPEED_LIMITS(TRI_RACE_STRUCT& Race, INT iRacerIndex)
INT iPlayerCurrentGate
INT iRacerCurrentGate
// Gate-based speed modifiers.
SWITCH eCurrentTriRace
CASE TRIATHLON_RACE_ALAMO_SEA
IF iRacerIndex = 0
iPlayerCurrentGate = Race.Racer[0].iGateCur
SWITCH iPlayerCurrentGate
// Third swim gate.
CASE 2
Tri_Racer_AI_Speed.fMaxDeltaSwimLimit = 2.4
BREAK
// Second-to-last gate of the race.
CASE 21
Tri_Racer_AI_Speed.fMaxDeltaRunLimit = 2.7
BREAK
ENDSWITCH
ELSE
iRacerCurrentGate = Race.Racer[iRacerIndex].iGateCur
SWITCH iRacerCurrentGate
// Last gate of the race.
CASE 22
Tri_Racer_AI_Speed.fMinDeltaRunLimit = 1.1
BREAK
ENDSWITCH
ENDIF
BREAK
CASE TRIATHLON_RACE_VESPUCCI
IF iRacerIndex = 0
iPlayerCurrentGate = Race.Racer[0].iGateCur
SWITCH iPlayerCurrentGate
// Third swim gate.
CASE 2
Tri_Racer_AI_Speed.fMaxDeltaSwimLimit = 2.4
BREAK
// Second-to-last gate of the race.
CASE 23
Tri_Racer_AI_Speed.fMaxDeltaRunLimit = 2.7
BREAK
ENDSWITCH
ELSE
iRacerCurrentGate = Race.Racer[iRacerIndex].iGateCur
SWITCH iRacerCurrentGate
// Last gate of the race.
CASE 24
Tri_Racer_AI_Speed.fMinDeltaRunLimit = 1.1
BREAK
ENDSWITCH
ENDIF
BREAK
CASE TRIATHLON_RACE_IRONMAN
IF iRacerIndex = 0
iPlayerCurrentGate = Race.Racer[0].iGateCur
SWITCH iPlayerCurrentGate
// Third swim gate.
CASE 2
Tri_Racer_AI_Speed.fMaxDeltaSwimLimit = 2.4
BREAK
// Second-to-last gate of the race.
CASE 114
Tri_Racer_AI_Speed.fMaxDeltaRunLimit = 2.7
BREAK
ENDSWITCH
ELSE
iRacerCurrentGate = Race.Racer[iRacerIndex].iGateCur
SWITCH iRacerCurrentGate
// Last gate of the race.
CASE 115
Tri_Racer_AI_Speed.fMinDeltaRunLimit = 1.1
BREAK
ENDSWITCH
ENDIF
BREAK
ENDSWITCH
ENDPROC
/// PURPOSE:
/// Assist the compete and rubber-banding systems with a desired racer behavior.
///
/// NOTE: This command is intended to provide additional functionality to the
/// existing AI compete/rubber-banding system. There are times when
/// we want very specific behavior from a race, like X number of racers
/// slowing down as player clears X gate. For these specific instances,
/// just create the functionality in these commands.
PROC UPDATE_TRI_RACER_AI_LEG_MODIFIER(TRI_RACE_STRUCT& Race, INT iRacerIndex)
// Update minimum and maximum AI racer speed allowed.
UPDATE_TRI_RACER_AI_LEG_MODIFIER_MIN_AND_MAX_SPEED_LIMITS(Race, iRacerIndex)
// Change the number of racers allowed in a compete state based on current player leg.
UPDATE_TRI_RACER_AI_LEG_MODIFIER_BY_PLAYER_LEG(Race)
IF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_SWIM)
UPDATE_TRI_RACER_AI_SWIM_LEG_MODIFIER(Race, iRacerIndex)
ELIF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_RUN)
UPDATE_TRI_RACER_AI_RUN_LEG_MODIFIER(Race, iRacerIndex)
ENDIF
ENDPROC
// ---------------------------------------------------------------
// E N D AI LEG MODIFIER PROCEDURES AND FUNCTIONS
// ---------------------------------------------------------------
// ---------------------------------------------------------------
// SWIM LEG SPEED FUNCTIONS AND PROCEDURES
//
// In V, player can only go in the following speeds in water,
// with negligible floating-point differences:
// Treading water: 1.0
// Swimming: 2.0
// Swimming fast: 3.0
//
// AI racers can be set to any floating value between 1 and 3,
// which thankfully allows for variety in the race:
// Treading water: < 1.0 (Rarely used. Few racers should be seen treading water, and only rarely).
// Swimming: > 1.0 to < 2.0
// Swimming fast: > 2.0
//
// NOTE: Notice the swim and run player movement speed parallels.
//
// ---------------------------------------------------------------
/// PURPOSE:
/// Max out a racer's speed for the swimming leg.
PROC Tri_AI_Maximize_Racer_Swim_Speed(TRI_RACE_STRUCT& Race, INT iRacerIndex)
IF NOT IS_ENTITY_DEAD(Race.Racer[iRacerIndex].Driver)
IF NOT (Race.Racer[iRacerIndex].Driver = Race.Racer[0].Driver)
IF NOT IS_PED_IN_ANY_VEHICLE(Race.Racer[iRacerIndex].Driver)
IF IS_ENTITY_IN_WATER(Race.Racer[iRacerIndex].Driver)
IF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_SWIM)
//SET_PED_MOVE_RATE_OVERRIDE(Race.Racer[iRacerIndex].Driver, fOnFootMaxMoveBlendRatio)
Race.Racer[iRacerIndex].fMinMoveBlendRatioInWater = Tri_Racer_AI_Speed.fMaxDeltaSwimLimit
Race.Racer[iRacerIndex].fMaxMoveBlendRatioInWater = Tri_Racer_AI_Speed.fMaxDeltaSwimLimit
ENDIF
ENDIF
ENDIF
ENDIF
ENDIF
ENDPROC
/// PURPOSE:
/// Increase a racer's speed for the swimming leg if he's a certain distance away from the player.
PROC Tri_AI_Increase_Racer_Swim_Speed(TRI_RACE_STRUCT& Race, INT iRacerIndex, FLOAT fDistanceBetweenRacerAndPlayer)
IF NOT IS_ENTITY_DEAD(Race.Racer[iRacerIndex].Driver)
IF NOT (Race.Racer[iRacerIndex].Driver = Race.Racer[0].Driver)
IF NOT IS_PED_IN_ANY_VEHICLE(Race.Racer[iRacerIndex].Driver)
IF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_SWIM)
//FLOAT fRacerCurrentBlendRatio = GET_PED_DESIRED_MOVE_BLEND_RATIO(Race.Racer[iRacerIndex].Driver)
//SET_PED_MOVE_RATE_OVERRIDE(Race.Racer[iRacerIndex].Driver, fOnFootMaxMoveBlendRatio)
IF IS_ENTITY_IN_WATER(Race.Racer[iRacerIndex].Driver)
SET_PED_MOVE_RATE_OVERRIDE(Race.Racer[iRacerIndex].Driver, 0.70)
ELSE
SET_PED_MOVE_RATE_OVERRIDE(Race.Racer[iRacerIndex].Driver, fOnFootMaxMoveBlendRatio)
ENDIF
// Determine whether or not to rubber-band based on how much the distance between the player and AI racer is.
IF (fDistanceBetweenRacerAndPlayer > iShortRubberBandSwimDistance) AND (fDistanceBetweenRacerAndPlayer < iMediumRubberBandSwimDistance)
IF (Tri_Racer_AI_Speed.fMaxDeltaSwimLimit > Race.Racer[iRacerIndex].fMinMoveBlendRatioInWater)
Race.Racer[iRacerIndex].fMinMoveBlendRatioInWater += 0.1
ENDIF
IF (Tri_Racer_AI_Speed.fMaxDeltaSwimLimit > Race.Racer[iRacerIndex].fMaxMoveBlendRatioInWater)
Race.Racer[iRacerIndex].fMaxMoveBlendRatioInWater += 0.1
ENDIF
ELIF (fDistanceBetweenRacerAndPlayer > iMediumRubberBandSwimDistance) AND (fDistanceBetweenRacerAndPlayer < iLongRubberBandSwimDistance)
IF (Tri_Racer_AI_Speed.fMaxDeltaSwimLimit > Race.Racer[iRacerIndex].fMinMoveBlendRatioInWater)
Race.Racer[iRacerIndex].fMinMoveBlendRatioInWater += 0.3
ENDIF
IF (Tri_Racer_AI_Speed.fMaxDeltaSwimLimit > Race.Racer[iRacerIndex].fMaxMoveBlendRatioInWater)
Race.Racer[iRacerIndex].fMaxMoveBlendRatioInWater += 0.3
ENDIF
ELIF (fDistanceBetweenRacerAndPlayer > iLongRubberBandSwimDistance)
IF (Tri_Racer_AI_Speed.fMaxDeltaSwimLimit > Race.Racer[iRacerIndex].fMinMoveBlendRatioInWater)
Race.Racer[iRacerIndex].fMinMoveBlendRatioInWater += 1.0
ENDIF
IF (Tri_Racer_AI_Speed.fMaxDeltaSwimLimit > Race.Racer[iRacerIndex].fMaxMoveBlendRatioInWater)
Race.Racer[iRacerIndex].fMaxMoveBlendRatioInWater += 1.0
ENDIF
ELIF (fDistanceBetweenRacerAndPlayer <= iShortRubberBandSwimDistance)
Race.Racer[iRacerIndex].fMinMoveBlendRatioInWater = Tri_Racer_AI_Speed.fMinDeltaSwimLimit
Race.Racer[iRacerIndex].fMaxMoveBlendRatioInWater = Tri_Racer_AI_Speed.fMaxDeltaSwimLimit
ENDIF
// Ensure the values don't go over the limit.
IF (Tri_Racer_AI_Speed.fMaxDeltaSwimLimit < Race.Racer[iRacerIndex].fMinMoveBlendRatioInWater)
Race.Racer[iRacerIndex].fMinMoveBlendRatioInWater = Tri_Racer_AI_Speed.fMaxDeltaSwimLimit
ENDIF
// Ensure the values don't go over the limit.
IF (Tri_Racer_AI_Speed.fMaxDeltaSwimLimit < Race.Racer[iRacerIndex].fMaxMoveBlendRatioInWater)
Race.Racer[iRacerIndex].fMaxMoveBlendRatioInWater = Tri_Racer_AI_Speed.fMaxDeltaSwimLimit
ENDIF
ENDIF // E N D Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_SWIM
ENDIF // E N D NOT IS_PED_IN_ANY_VEHICLE(Race.Racer[iRacerIndex].Driver)
ENDIF // E N D NOT (Race.Racer[iRacerIndex].Driver = Race.Racer[0].Driver)
ENDIF // E N D NOT IS_ENTITY_DEAD(Race.Racer[iRacerIndex].Driver)
ENDPROC
/// PURPOSE:
/// Decrease a racer's speed for the swimming leg.
PROC Tri_AI_Decrease_Racer_Swim_Speed(TRI_RACE_STRUCT& Race, INT iRacerIndex, FLOAT fDistanceBetweenRacerAndPlayer)
IF NOT IS_ENTITY_DEAD(Race.Racer[iRacerIndex].Driver)
IF NOT (Race.Racer[iRacerIndex].Driver = Race.Racer[0].Driver)
IF NOT IS_PED_IN_ANY_VEHICLE(Race.Racer[iRacerIndex].Driver)
IF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_SWIM)
//FLOAT fRacerCurrentBlendRatio = GET_PED_DESIRED_MOVE_BLEND_RATIO(Race.Racer[iRacerIndex].Driver)
IF IS_ENTITY_IN_WATER(Race.Racer[iRacerIndex].Driver)
SET_PED_MOVE_RATE_OVERRIDE(Race.Racer[iRacerIndex].Driver, 0.60)
ENDIF
// Determine whether or not to rubber-band based on how much the distance between the two is.
IF (fDistanceBetweenRacerAndPlayer > iShortRubberBandSwimDistance + 10) AND (fDistanceBetweenRacerAndPlayer < iMediumRubberBandSwimDistance + 10)
IF (Tri_Racer_AI_Speed.fMinDeltaSwimLimit < Race.Racer[iRacerIndex].fMinMoveBlendRatioInWater)
Race.Racer[iRacerIndex].fMinMoveBlendRatioInWater -= 0.05
ENDIF
IF (Tri_Racer_AI_Speed.fMinDeltaSwimLimit < Race.Racer[iRacerIndex].fMaxMoveBlendRatioInWater)
Race.Racer[iRacerIndex].fMaxMoveBlendRatioInWater -= 0.05
ENDIF
ELIF (fDistanceBetweenRacerAndPlayer > iMediumRubberBandSwimDistance + 10) AND (fDistanceBetweenRacerAndPlayer < iLongRubberBandSwimDistance + 10)
IF (Tri_Racer_AI_Speed.fMinDeltaSwimLimit < Race.Racer[iRacerIndex].fMinMoveBlendRatioInWater)
Race.Racer[iRacerIndex].fMinMoveBlendRatioInWater -= 0.1
ENDIF
IF (Tri_Racer_AI_Speed.fMinDeltaSwimLimit < Race.Racer[iRacerIndex].fMaxMoveBlendRatioInWater)
Race.Racer[iRacerIndex].fMaxMoveBlendRatioInWater -= 0.1
ENDIF
ELIF (fDistanceBetweenRacerAndPlayer > iLongRubberBandSwimDistance + 10)
IF (Tri_Racer_AI_Speed.fMinDeltaSwimLimit < Race.Racer[iRacerIndex].fMinMoveBlendRatioInWater)
Race.Racer[iRacerIndex].fMinMoveBlendRatioInWater -= 0.5
ENDIF
IF (Tri_Racer_AI_Speed.fMinDeltaSwimLimit < Race.Racer[iRacerIndex].fMaxMoveBlendRatioInWater)
Race.Racer[iRacerIndex].fMaxMoveBlendRatioInWater -= 0.5
ENDIF
ELIF (fDistanceBetweenRacerAndPlayer < iShortRubberBandSwimDistance + 10)
Race.Racer[iRacerIndex].fMinMoveBlendRatioInWater = Tri_Racer_AI_Speed.fMinDeltaSwimLimit
Race.Racer[iRacerIndex].fMaxMoveBlendRatioInWater = Tri_Racer_AI_Speed.fMaxDeltaSwimLimit
ENDIF
// Ensure the values don't reach below their limit
IF (Tri_Racer_AI_Speed.fMinDeltaSwimLimit > Race.Racer[iRacerIndex].fMinMoveBlendRatioInWater)
Race.Racer[iRacerIndex].fMinMoveBlendRatioInWater = Tri_Racer_AI_Speed.fMinDeltaSwimLimit
ENDIF
// Ensure the values don't go over the limit.
IF (Tri_Racer_AI_Speed.fMinDeltaSwimLimit > Race.Racer[iRacerIndex].fMaxMoveBlendRatioInWater)
Race.Racer[iRacerIndex].fMaxMoveBlendRatioInWater = Tri_Racer_AI_Speed.fMinDeltaSwimLimit
ENDIF
ENDIF // E N D Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_SWIM
ENDIF // E N D NOT IS_PED_IN_ANY_VEHICLE(Race.Racer[iRacerIndex].Driver)
ENDIF // E N D NOT (Race.Racer[iRacerIndex].Driver = Race.Racer[0].Driver)
ENDIF // E N D NOT IS_ENTITY_DEAD(Race.Racer[iRacerIndex].Driver)
ENDPROC
/// PURPOSE:
/// Minimize a racer's speed for the swimming leg.
PROC Tri_AI_Minimize_Racer_Swim_Speed(TRI_RACE_STRUCT& Race, INT iRacerIndex)
IF NOT IS_ENTITY_DEAD(Race.Racer[iRacerIndex].Driver)
IF NOT (Race.Racer[iRacerIndex].Driver = Race.Racer[0].Driver)
IF NOT IS_PED_IN_ANY_VEHICLE(Race.Racer[iRacerIndex].Driver)
IF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_SWIM)
Race.Racer[iRacerIndex].fMinMoveBlendRatioInWater = Tri_Racer_AI_Speed.fMinDeltaSwimLimit
Race.Racer[iRacerIndex].fMaxMoveBlendRatioInWater = Tri_Racer_AI_Speed.fMinDeltaSwimLimit
ENDIF
ENDIF
ENDIF
ENDIF
ENDPROC
// ---------------------------------------------------------------
// E N D SWIM LEG SPEED FUNCTIONS AND PROCEDURES
// ---------------------------------------------------------------
// ---------------------------------------------------------------
// BIKE LEG SPEED FUNCTIONS AND PROCEDURES
//
// The TRIBIKE has a max speed of about 18.33 on a flat surface,
// but racers can easily reach speeds in the low 20s.
//
// Unlike the swim and run legs, it's harder to control how
// fast AI racers can go in the bike leg, because bike speed
// is affected by the terrain and code AI. Racers will slow
// down when they think they need to, yet at the same time will
// take bad turns and crash if they're set to a speed too high for
// them to control in anything other than a straight line.
//
// ---------------------------------------------------------------
/// PURPOSE:
/// Sets the default bike speeds of each racer based on the
/// skill levels randomly given to them in each race.
PROC Tri_Set_AI_Racer_Bike_Speed_Limits(TRI_RACE_STRUCT& Race, INT iRacerIndex)
IF IS_TRI_AI_RACER_VALID(Race, iRacerIndex, TRUE)
SWITCH (Race.Racer[iRacerIndex].iSkillPlacement)
CASE 1
//SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface + 4)
Race.Racer[iRacerIndex].fMinBikeSpeed = 18.0
Race.Racer[iRacerIndex].fMaxBikeSpeed = 24.0
BREAK
CASE 2
//SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface + 3)
Race.Racer[iRacerIndex].fMinBikeSpeed = 18.0
Race.Racer[iRacerIndex].fMaxBikeSpeed = 22.0
BREAK
CASE 3
//SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface + 2)
Race.Racer[iRacerIndex].fMinBikeSpeed = 18.0
Race.Racer[iRacerIndex].fMaxBikeSpeed = 22.0
BREAK
CASE 4
//SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface + 1)
Race.Racer[iRacerIndex].fMinBikeSpeed = 17.0
Race.Racer[iRacerIndex].fMaxBikeSpeed = 20.0
BREAK
CASE 5
//SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface)
Race.Racer[iRacerIndex].fMinBikeSpeed = 15.0
Race.Racer[iRacerIndex].fMaxBikeSpeed = 17.0
BREAK
CASE 6
//SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface - 2)
Race.Racer[iRacerIndex].fMinBikeSpeed = 11.0
Race.Racer[iRacerIndex].fMaxBikeSpeed = 14.0
BREAK
CASE 7
//SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface - 6)
Race.Racer[iRacerIndex].fMinBikeSpeed = 10.0
Race.Racer[iRacerIndex].fMaxBikeSpeed = 12.0
BREAK
ENDSWITCH
ENDIF
ENDPROC
/// PURPOSE:
/// Maximize a racer's speed for the bike leg.
PROC Tri_AI_Maximize_Racer_Bike_Speed(TRI_RACE_STRUCT& Race, INT iRacerIndex)
IF IS_TRI_AI_RACER_VALID(Race, iRacerIndex, TRUE)
IF IS_PED_IN_VEHICLE(Race.Racer[iRacerIndex].Driver, Race.Racer[iRacerIndex].Vehicle)
IF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_BIKE)
//SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface * 2.0)
Race.Racer[iRacerIndex].fMinBikeSpeed = Tri_Racer_AI_Speed.fMaxDeltaBikeLimit
Race.Racer[iRacerIndex].fMaxBikeSpeed = Tri_Racer_AI_Speed.fMaxDeltaBikeLimit
ENDIF
ENDIF
ENDIF
ENDPROC
/// PURPOSE:
/// Increase a racer's speed for the bike leg.
///
/// NOTE: Each AI racer is assigned a skill placement value
/// when they first get on their bikes. 1 = BEST, 7 = WORST.
/// This affects how fast a racer can go in the bike leg.
/// We use these values to determine the limits of how much
/// to increase or decrease the speed of a racer, so strong
/// racers can reach high speeds to catch up, whether weak
/// ones cannot.
PROC Tri_AI_Increase_Racer_Bike_Speed(TRI_RACE_STRUCT& Race, INT iRacerIndex, FLOAT fDistanceBetweenRacerAndPlayer)
IF IS_TRI_AI_RACER_VALID(Race, iRacerIndex, TRUE)
IF IS_PED_IN_VEHICLE(Race.Racer[iRacerIndex].Driver, Race.Racer[iRacerIndex].Vehicle)
IF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_BIKE)
IF fDistanceBetweenRacerAndPlayer > 50.0
SWITCH (Race.Racer[iRacerIndex].iSkillPlacement)
CASE 1
//SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface + 15)
Race.Racer[iRacerIndex].fMinBikeSpeed = 26.0
Race.Racer[iRacerIndex].fMaxBikeSpeed = 28.0
BREAK
CASE 2
//SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface + 14)
Race.Racer[iRacerIndex].fMinBikeSpeed = 26.0
Race.Racer[iRacerIndex].fMaxBikeSpeed = 28.0
BREAK
CASE 3
//SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface + 13)
Race.Racer[iRacerIndex].fMinBikeSpeed = 26.0
Race.Racer[iRacerIndex].fMaxBikeSpeed = 28.0
BREAK
CASE 4
//SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface + 12)
Race.Racer[iRacerIndex].fMinBikeSpeed = 26.0
Race.Racer[iRacerIndex].fMaxBikeSpeed = 28.0
BREAK
CASE 5
//SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface + 11)
Race.Racer[iRacerIndex].fMinBikeSpeed = 26.0
Race.Racer[iRacerIndex].fMaxBikeSpeed = 26.0
BREAK
CASE 6
//SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface + 10)
Race.Racer[iRacerIndex].fMinBikeSpeed = 18.0
Race.Racer[iRacerIndex].fMaxBikeSpeed = 24.0
BREAK
CASE 7
//SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface + 9)
Race.Racer[iRacerIndex].fMinBikeSpeed = 18.0
Race.Racer[iRacerIndex].fMaxBikeSpeed = 18.0
BREAK
ENDSWITCH
ELSE
IF (fDistanceBetweenRacerAndPlayer > 15.0) AND (fDistanceBetweenRacerAndPlayer < 35.0)
SWITCH (Race.Racer[iRacerIndex].iSkillPlacement)
CASE 1
//SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface + 7)
Race.Racer[iRacerIndex].fMinBikeSpeed = 24.0
Race.Racer[iRacerIndex].fMaxBikeSpeed = 27.0
BREAK
CASE 2
//SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface + 6)
Race.Racer[iRacerIndex].fMinBikeSpeed = 24.0
Race.Racer[iRacerIndex].fMaxBikeSpeed = 27.0
BREAK
CASE 3
//SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface + 5)
Race.Racer[iRacerIndex].fMinBikeSpeed = 24.0
Race.Racer[iRacerIndex].fMaxBikeSpeed = 27.0
BREAK
CASE 4
//SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface + 4)
Race.Racer[iRacerIndex].fMinBikeSpeed = 24.0
Race.Racer[iRacerIndex].fMaxBikeSpeed = 27.0
BREAK
CASE 5
//SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface + 3)
Race.Racer[iRacerIndex].fMinBikeSpeed = 21.0
Race.Racer[iRacerIndex].fMaxBikeSpeed = 22.0
BREAK
CASE 6
//SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface + 2)
Race.Racer[iRacerIndex].fMinBikeSpeed = 17.0
Race.Racer[iRacerIndex].fMaxBikeSpeed = 20.0
BREAK
CASE 7
//SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface + 1)
Race.Racer[iRacerIndex].fMinBikeSpeed = 16.0
Race.Racer[iRacerIndex].fMaxBikeSpeed = 18.0
BREAK
ENDSWITCH
ELSE
Tri_Set_AI_Racer_Bike_Speed_Limits(Race, iRacerIndex)
ENDIF
ENDIF
ENDIF
ENDIF
ENDIF
ENDPROC
/// PURPOSE:
/// Decrease a racer's speed for the bike leg.
///
/// NOTE: Each AI racer is assigned a skill placement value
/// when they first get on their bikes. 1 = BEST, 7 = WORST.
/// This affects how fast a racer can go in the bike leg.
/// We use these values to determine the limits of how much
/// to increase or decrease the speed of a racer, so strong
/// racers can reach high speeds to catch up, whether weak
/// ones cannot.
PROC Tri_AI_Decrease_Racer_Bike_Speed(TRI_RACE_STRUCT& Race, INT iRacerIndex, FLOAT fDistanceBetweenRacerAndPlayer)
IF IS_TRI_AI_RACER_VALID(Race, iRacerIndex, TRUE)
IF IS_PED_IN_VEHICLE(Race.Racer[iRacerIndex].Driver, Race.Racer[iRacerIndex].Vehicle)
IF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_BIKE)
IF fDistanceBetweenRacerAndPlayer > 150.0
SWITCH (Race.Racer[iRacerIndex].iSkillPlacement)
CASE 1
SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface / 2)
BREAK
CASE 2
SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface / 2)
BREAK
CASE 3
SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface / 2)
BREAK
CASE 4
SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface / 2)
BREAK
CASE 5
SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface / 3)
BREAK
CASE 6
SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface / 3)
BREAK
CASE 7
SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface / 4)
BREAK
ENDSWITCH
ELIF fDistanceBetweenRacerAndPlayer > 100.0
SWITCH (Race.Racer[iRacerIndex].iSkillPlacement)
CASE 1
SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface / 1.5)
BREAK
CASE 2
SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface / 1.5)
BREAK
CASE 3
SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface / 1.5)
BREAK
CASE 4
SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface / 1.5)
BREAK
CASE 5
SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface / 2)
BREAK
CASE 6
SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface / 2)
BREAK
CASE 7
SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface / 2)
BREAK
ENDSWITCH
ELSE
IF (fDistanceBetweenRacerAndPlayer > 25.0) AND (fDistanceBetweenRacerAndPlayer < 50.0)
SWITCH (Race.Racer[iRacerIndex].iSkillPlacement)
CASE 1
SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface)
BREAK
CASE 2
SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface)
BREAK
CASE 3
SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface - 1.5)
BREAK
CASE 4
SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface - 2.0)
BREAK
CASE 5
SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface - 3.0)
BREAK
CASE 6
SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface - 4.0)
BREAK
CASE 7
SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface - 5.0)
BREAK
ENDSWITCH
ELSE
Tri_Set_AI_Racer_Bike_Speed_Limits(Race, iRacerIndex)
ENDIF
ENDIF
ENDIF
ENDIF
ENDIF
ENDPROC
/// PURPOSE:
/// Minimize a racer's speed for the bike leg.
PROC Tri_AI_Minimize_Racer_Bike_Speed(TRI_RACE_STRUCT& Race, INT iRacerIndex)
IF NOT ( IS_ENTITY_DEAD(Race.Racer[iRacerIndex].Driver) OR IS_ENTITY_DEAD(Race.Racer[iRacerIndex].Vehicle) )
IF IS_PED_IN_VEHICLE(Race.Racer[iRacerIndex].Driver, Race.Racer[iRacerIndex].Vehicle)
IF NOT (Race.Racer[iRacerIndex].Driver = Race.Racer[0].Driver)
IF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_BIKE)
//SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface / 2.0)
Race.Racer[iRacerIndex].fMinBikeSpeed = Tri_Racer_AI_Speed.fMinDeltaBikeLimit
Race.Racer[iRacerIndex].fMaxBikeSpeed = Tri_Racer_AI_Speed.fMinDeltaBikeLimit
ENDIF
ENDIF
ENDIF
ENDIF
ENDPROC
// ---------------------------------------------------------------
// E N D BIKE LEG SPEED FUNCTIONS AND PROCEDURES
// ---------------------------------------------------------------
// ---------------------------------------------------------------
// RUN LEG SPEED FUNCTIONS AND PROCEDURES
//
// In V, player can only go in the following speeds on foot,
// with negligible floating-point differences:
// Walk: 1.0
// Run: 2.0
// Sprint: 3.0
//
// AI racers can be set to any floating value between 1 and 3,
// which thankfully allows for variety in the race:
// Walk: < 1.0 (Rarely used. Few racers should be seen walking, and only rarely).
// Run: > 1.0 to < 2.0
// Sprint: > 2.0
//
// NOTE: Notice the swim and run player movement speed parallels.
//
// ---------------------------------------------------------------
/// PURPOSE:
/// Maximize a racer's speed for the running leg.
PROC Tri_AI_Maximize_Racer_Run_Speed(TRI_RACE_STRUCT& Race, INT iRacerIndex)
IF NOT IS_ENTITY_DEAD(Race.Racer[iRacerIndex].Driver)
IF NOT (Race.Racer[iRacerIndex].Driver = Race.Racer[0].Driver)
IF NOT IS_PED_IN_ANY_VEHICLE(Race.Racer[iRacerIndex].Driver)
IF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_RUN)
//SET_PED_MOVE_RATE_OVERRIDE(Race.Racer[iRacerIndex].Driver, fOnFootMaxMoveBlendRatio)
Race.Racer[iRacerIndex].fMinMoveBlendRatioOnFoot = Tri_Racer_AI_Speed.fMaxDeltaRunLimit
Race.Racer[iRacerIndex].fMaxMoveBlendRatioOnFoot = Tri_Racer_AI_Speed.fMaxDeltaRunLimit
ENDIF
ENDIF
ENDIF
ENDIF
ENDPROC
/// PURPOSE:
/// Increase a racer's speed for the running leg.
PROC Tri_AI_Increase_Racer_Run_Speed(TRI_RACE_STRUCT& Race, INT iRacerIndex, FLOAT fDistanceBetweenRacerAndPlayer)
IF NOT IS_ENTITY_DEAD(Race.Racer[iRacerIndex].Driver)
IF NOT (Race.Racer[iRacerIndex].Driver = Race.Racer[0].Driver)
IF NOT IS_PED_IN_ANY_VEHICLE(Race.Racer[iRacerIndex].Driver)
IF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_RUN)
//FLOAT fRacerCurrentBlendRatio = GET_PED_DESIRED_MOVE_BLEND_RATIO(Race.Racer[iRacerIndex].Driver)
//SET_PED_MOVE_RATE_OVERRIDE(Race.Racer[iRacerIndex].Driver, fOnFootMaxMoveBlendRatio)
// Determine whether or not to rubber-band based on how much the distance between the two is.
IF (fDistanceBetweenRacerAndPlayer > iShortRubberBandRunDistance) AND (fDistanceBetweenRacerAndPlayer < iMediumRubberBandRunDistance)
IF (fTriOriginalMaxAIRacerRunSpeeds[iRacerIndex] > Race.Racer[iRacerIndex].fMinMoveBlendRatioOnFoot)
Race.Racer[iRacerIndex].fMinMoveBlendRatioOnFoot += 0.1
ENDIF
IF (fTriOriginalMaxAIRacerRunSpeeds[iRacerIndex] > Race.Racer[iRacerIndex].fMaxMoveBlendRatioOnFoot)
Race.Racer[iRacerIndex].fMaxMoveBlendRatioOnFoot += 0.1
ENDIF
ELIF (fDistanceBetweenRacerAndPlayer > iMediumRubberBandRunDistance) AND (fDistanceBetweenRacerAndPlayer < iLongRubberBandRunDistance)
IF (fTriOriginalMaxAIRacerRunSpeeds[iRacerIndex] > Race.Racer[iRacerIndex].fMinMoveBlendRatioOnFoot)
Race.Racer[iRacerIndex].fMinMoveBlendRatioOnFoot += 0.3
ENDIF
IF (fTriOriginalMaxAIRacerRunSpeeds[iRacerIndex] > Race.Racer[iRacerIndex].fMaxMoveBlendRatioOnFoot)
Race.Racer[iRacerIndex].fMaxMoveBlendRatioOnFoot += 0.3
ENDIF
ELIF (fDistanceBetweenRacerAndPlayer > iLongRubberBandRunDistance)
IF (fTriOriginalMaxAIRacerRunSpeeds[iRacerIndex] > Race.Racer[iRacerIndex].fMinMoveBlendRatioOnFoot)
Race.Racer[iRacerIndex].fMinMoveBlendRatioOnFoot += 1.0
ENDIF
IF (fTriOriginalMaxAIRacerRunSpeeds[iRacerIndex] > Race.Racer[iRacerIndex].fMaxMoveBlendRatioOnFoot)
Race.Racer[iRacerIndex].fMaxMoveBlendRatioOnFoot += 1.0
ENDIF
ELIF (fDistanceBetweenRacerAndPlayer <= iShortRubberBandRunDistance)
Race.Racer[iRacerIndex].fMinMoveBlendRatioOnFoot = fTriOriginalMinAIRacerRunSpeeds[iRacerIndex]
Race.Racer[iRacerIndex].fMaxMoveBlendRatioOnFoot = fTriOriginalMaxAIRacerRunSpeeds[iRacerIndex]
ENDIF
// Ensure the values don't go over the limit.
IF (fTriOriginalMaxAIRacerRunSpeeds[iRacerIndex] < Race.Racer[iRacerIndex].fMinMoveBlendRatioOnFoot)
Race.Racer[iRacerIndex].fMinMoveBlendRatioOnFoot = fTriOriginalMaxAIRacerRunSpeeds[iRacerIndex]
ENDIF
// Ensure the values don't go over the limit.
/* This sets it to max. May want to set it to a smaller value.
IF (fTriOriginalMaxAIRacerRunSpeeds[iRacerIndex] < Race.Racer[iRacerIndex].fMaxMoveBlendRatioOnFoot)
Race.Racer[iRacerIndex].fMaxMoveBlendRatioOnFoot = fTriOriginalMaxAIRacerRunSpeeds[iRacerIndex]
ENDIF
*/
IF (Tri_Racer_AI_Speed.fMaxDeltaRunLimit < Race.Racer[iRacerIndex].fMaxMoveBlendRatioOnFoot)
Race.Racer[iRacerIndex].fMaxMoveBlendRatioOnFoot = Tri_Racer_AI_Speed.fMaxDeltaRunLimit
ENDIF
ENDIF
ENDIF
ENDIF
ENDIF
ENDPROC
/// PURPOSE:
/// Decrease a racer's speed for the running leg.
PROC Tri_AI_Decrease_Racer_Run_Speed(TRI_RACE_STRUCT& Race, INT iRacerIndex, FLOAT fDistanceBetweenRacerAndPlayer)
IF NOT IS_ENTITY_DEAD(Race.Racer[iRacerIndex].Driver)
IF NOT (Race.Racer[iRacerIndex].Driver = Race.Racer[0].Driver)
IF NOT IS_PED_IN_ANY_VEHICLE(Race.Racer[iRacerIndex].Driver)
IF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_RUN)
SET_PED_MOVE_RATE_OVERRIDE(Race.Racer[iRacerIndex].Driver, 1.00)
// Determine whether or not to rubber-band based on how much the distance between the two is.
IF (fDistanceBetweenRacerAndPlayer > iShortRubberBandRunDistance + 10) AND (fDistanceBetweenRacerAndPlayer < iMediumRubberBandRunDistance + 10)
IF (fTriOriginalMinAIRacerRunSpeeds[iRacerIndex] < Race.Racer[iRacerIndex].fMinMoveBlendRatioOnFoot)
Race.Racer[iRacerIndex].fMinMoveBlendRatioOnFoot -= 0.05
ENDIF
IF (fTriOriginalMinAIRacerRunSpeeds[iRacerIndex] < Race.Racer[iRacerIndex].fMaxMoveBlendRatioOnFoot)
Race.Racer[iRacerIndex].fMaxMoveBlendRatioOnFoot -= 0.05
ENDIF
ELIF (fDistanceBetweenRacerAndPlayer > iMediumRubberBandRunDistance + 10) AND (fDistanceBetweenRacerAndPlayer < iLongRubberBandRunDistance + 10)
IF (fTriOriginalMinAIRacerRunSpeeds[iRacerIndex] < Race.Racer[iRacerIndex].fMinMoveBlendRatioOnFoot)
Race.Racer[iRacerIndex].fMinMoveBlendRatioOnFoot -= 0.1
ENDIF
IF (fTriOriginalMinAIRacerRunSpeeds[iRacerIndex] < Race.Racer[iRacerIndex].fMaxMoveBlendRatioOnFoot)
Race.Racer[iRacerIndex].fMaxMoveBlendRatioOnFoot -= 0.1
ENDIF
ELIF (fDistanceBetweenRacerAndPlayer > iLongRubberBandRunDistance + 10)
IF (fTriOriginalMinAIRacerRunSpeeds[iRacerIndex] < Race.Racer[iRacerIndex].fMinMoveBlendRatioOnFoot)
Race.Racer[iRacerIndex].fMinMoveBlendRatioOnFoot -= 0.5
ENDIF
IF (fTriOriginalMinAIRacerRunSpeeds[iRacerIndex] < Race.Racer[iRacerIndex].fMaxMoveBlendRatioOnFoot)
Race.Racer[iRacerIndex].fMaxMoveBlendRatioOnFoot -= 0.5
ENDIF
ELIF (fDistanceBetweenRacerAndPlayer < iShortRubberBandRunDistance + 10)
Race.Racer[iRacerIndex].fMinMoveBlendRatioOnFoot = fTriOriginalMinAIRacerRunSpeeds[iRacerIndex]
Race.Racer[iRacerIndex].fMaxMoveBlendRatioOnFoot = fTriOriginalMaxAIRacerRunSpeeds[iRacerIndex]
ENDIF
// Ensure the speed doesn't go under the limit.
IF (fTriOriginalMinAIRacerRunSpeeds[iRacerIndex] > Race.Racer[iRacerIndex].fMinMoveBlendRatioOnFoot)
Race.Racer[iRacerIndex].fMinMoveBlendRatioOnFoot = fTriOriginalMinAIRacerRunSpeeds[iRacerIndex]
ENDIF
IF (Tri_Racer_AI_Speed.fMinDeltaRunLimit > Race.Racer[iRacerIndex].fMaxMoveBlendRatioOnFoot)
Race.Racer[iRacerIndex].fMaxMoveBlendRatioOnFoot = Tri_Racer_AI_Speed.fMinDeltaRunLimit
ENDIF
ENDIF
ENDIF
ENDIF
ENDIF
ENDPROC
/// PURPOSE:
/// Minimize a racer's speed for the running leg.
PROC Tri_AI_Minimize_Racer_Run_Speed(TRI_RACE_STRUCT& Race, INT iRacerIndex)
IF NOT IS_ENTITY_DEAD(Race.Racer[iRacerIndex].Driver)
IF NOT (Race.Racer[iRacerIndex].Driver = Race.Racer[0].Driver)
IF NOT IS_PED_IN_ANY_VEHICLE(Race.Racer[iRacerIndex].Driver)
IF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_RUN)
Race.Racer[iRacerIndex].fMinMoveBlendRatioOnFoot = Tri_Racer_AI_Speed.fMinDeltaRunLimit
Race.Racer[iRacerIndex].fMaxMoveBlendRatioOnFoot = Tri_Racer_AI_Speed.fMinDeltaRunLimit
ENDIF
ENDIF
ENDIF
ENDIF
ENDPROC
// ---------------------------------------------------------------
// E N D RUN LEG SPEED FUNCTIONS AND PROCEDURES
// ---------------------------------------------------------------
// ---------------------------------------------------------------
// SPEED INCREASE AND DECREASE FUNCTIONS AND PROCEDURES
// ---------------------------------------------------------------
/// PURPOSE:
/// Maximize a racer's speed based on the current leg he's on.
PROC Tri_AI_Maximize_Racer_Speed(TRI_RACE_STRUCT& Race, INT iRacerIndex)
IF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_SWIM)
Tri_AI_Maximize_Racer_Swim_Speed(Race, iRacerIndex)
ELIF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_BIKE)
//Tri_AI_Maximize_Racer_Bike_Speed(Race, iRacerIndex)
ELIF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_RUN)
Tri_AI_Maximize_Racer_Run_Speed(Race, iRacerIndex)
ENDIF
ENDPROC
/// PURPOSE:
/// Increase a racer's speed based on the current leg he's on.
PROC Tri_AI_Increase_Racer_Speed(TRI_RACE_STRUCT& Race, INT iRacerIndex, FLOAT fDistanceBetweenRacerAndPlayer)
IF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_SWIM)
Tri_AI_Increase_Racer_Swim_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
ELIF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_BIKE)
Tri_AI_Increase_Racer_Bike_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
//Tri_AI_Set_Racer_Bike_Speed_To_Default(Race, iRacerIndex)
ELIF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_RUN)
Tri_AI_Increase_Racer_Run_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
ENDIF
ENDPROC
/// PURPOSE:
/// Decrease a racer's speed based on the current leg he's on.
PROC Tri_AI_Decrease_Racer_Speed(TRI_RACE_STRUCT& Race, INT iRacerIndex, FLOAT fDistanceBetweenRacerAndPlayer)
IF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_SWIM)
Tri_AI_Decrease_Racer_Swim_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
ELIF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_BIKE)
Tri_AI_Decrease_Racer_Bike_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
//Tri_AI_Set_Racer_Bike_Speed_To_Default(Race, iRacerIndex) // Commenting this out, as the bike leg is competitive without slowing down AI racers.
ELIF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_RUN)
Tri_AI_Decrease_Racer_Run_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
ENDIF
ENDPROC
/// PURPOSE:
/// Minimize a racer's speed based on the current leg he's on.
PROC Tri_AI_Minimize_Racer_Speed(TRI_RACE_STRUCT& Race, INT iRacerIndex)
IF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_SWIM)
Tri_AI_Minimize_Racer_Swim_Speed(Race, iRacerIndex)
ELIF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_BIKE)
//Tri_AI_Minimize_Racer_Bike_Speed(Race, iRacerIndex) // Commenting this out, as the bike leg is competitive without slowing down AI racers.
ELIF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_RUN)
Tri_AI_Minimize_Racer_Run_Speed(Race, iRacerIndex)
ENDIF
ENDPROC
// ---------------------------------------------------------------
// E N D SPEED INCREASE AND DECREASE FUNCTIONS AND PROCEDURES
// ---------------------------------------------------------------
// ---------------------------------------------------------------
// COMPETE MODE FUNCTIONS AND PROCEDURES
// ---------------------------------------------------------------
/// PURPOSE:
/// Update a default racer's speed behavior.
PROC Tri_AI_Update_Racer_Compete_Default_Mode(TRI_RACE_STRUCT& Race, INT iRacerIndex, FLOAT fDistanceBetweenRacerAndPlayer)
INT iRandomNum
IF (Race.Racer[iRacerIndex].iRank > Race.Racer[0].iRank) // Player is ahead of current racer.
IF TIMER_DO_WHEN_READY(Race.Racer[iRacerIndex].timerInCurrentCompeteMode, iTimerLimitInCompeteDefaultMode)
IF (fDistanceBetweenRacerAndPlayer <= fCloseDistanceBetweenRacers) // Distance between player and racer is close.
IF (iRacersInAggressiveMode < iLimitOfAggressiveRacers) OR (Race.Racer[0].iRank < 4)
iRandomNum = GET_RANDOM_INT_IN_RANGE(0, 101)
IF (iRandomNum < 60)
Tri_AI_Maximize_Racer_Speed(Race, iRacerIndex)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_AGGRESSIVE
iRacersInAggressiveMode++
ELSE
Tri_AI_Increase_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
ENDIF
ELIF iRacersInTiredMode < iLimitOfTiredRacers
iRandomNum = GET_RANDOM_INT_IN_RANGE(0, 101)
IF (iRandomNum < 10)
Tri_AI_Minimize_Racer_Speed(Race, iRacerIndex)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_TIRED
iRacersInTiredMode++
ELSE
Tri_AI_Increase_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
ENDIF
ELSE
Tri_AI_Increase_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
ENDIF
ELSE // Distance between player and racer isn't close.
IF (iRacersInAggressiveMode < iLimitOfAggressiveRacers)
Tri_AI_Maximize_Racer_Speed(Race, iRacerIndex)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_AGGRESSIVE
iRacersInAggressiveMode++
ELSE
Tri_AI_Increase_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
ENDIF
ENDIF
RESTART_TIMER_AT(Race.Racer[iRacerIndex].timerInCurrentCompeteMode, 0.0)
ELSE
Tri_AI_Increase_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
ENDIF
ELSE // Racer is ahead of current player.
IF TIMER_DO_WHEN_READY(Race.Racer[iRacerIndex].timerInCurrentCompeteMode, iTimerLimitInCompeteDefaultMode)
IF (fDistanceBetweenRacerAndPlayer > (fCloseDistanceBetweenRacers + 10) ) // Distance between player and racer is far.
IF (iRacersInTiredMode < iLimitOfTiredRacers)
iRandomNum = GET_RANDOM_INT_IN_RANGE(0, 101)
IF (iRandomNum < 25)
Tri_AI_Minimize_Racer_Speed(Race, iRacerIndex)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_TIRED
iRacersInTiredMode++
ELSE
Tri_AI_Decrease_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
ENDIF
ELIF (iRacersInAggressiveMode < iLimitOfAggressiveRacers)
iRandomNum = GET_RANDOM_INT_IN_RANGE(0, 101)
IF (iRandomNum < 30 )
Tri_AI_Maximize_Racer_Speed(Race, iRacerIndex)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_AGGRESSIVE
iRacersInAggressiveMode++
ELSE
Tri_AI_Decrease_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
ENDIF
ELSE
Tri_AI_Decrease_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
ENDIF
ELSE // Distance between player and racer is close.
IF iRacersInTiredMode < iLimitOfTiredRacers
iRandomNum = GET_RANDOM_INT_IN_RANGE(0, 101)
IF (iRandomNum < 10)
Tri_AI_Minimize_Racer_Speed(Race, iRacerIndex)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_TIRED
iRacersInTiredMode++
ELSE
Tri_AI_Decrease_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
ENDIF
ELSE
Tri_AI_Decrease_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
ENDIF
ENDIF
RESTART_TIMER_AT(Race.Racer[iRacerIndex].timerInCurrentCompeteMode, 0.0)
ELSE
Tri_AI_Decrease_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
ENDIF
ENDIF
ENDPROC
/// PURPOSE:
/// Update a tired racer's speed behavior.
PROC Tri_AI_Update_Racer_Compete_Tired_Mode(TRI_RACE_STRUCT& Race, INT iRacerIndex, FLOAT fDistanceBetweenRacerAndPlayer)
INT iRandomNum
IF (Race.Racer[iRacerIndex].iRank > Race.Racer[0].iRank) // Player is ahead of current racer.
IF TIMER_DO_WHEN_READY(Race.Racer[iRacerIndex].timerInCurrentCompeteMode, iTimerLimitInCompeteTiredMode)
IF (fDistanceBetweenRacerAndPlayer <= fCloseDistanceBetweenRacers) // Racer is close to player.
iRandomNum = GET_RANDOM_INT_IN_RANGE(0, 101)
IF (iRandomNum < 90)
Tri_AI_Increase_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_DEFAULT
iRacersInTiredMode--
ELIF (iRacersInAggressiveMode < iLimitOfAggressiveRacers)
iRandomNum = GET_RANDOM_INT_IN_RANGE(0, 101)
IF (iRandomNum < 25)
Tri_AI_Maximize_Racer_Speed(Race, iRacerIndex)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_AGGRESSIVE
iRacersInAggressiveMode++
iRacersInTiredMode--
ELSE
Tri_AI_Minimize_Racer_Speed(Race, iRacerIndex)
ENDIF
ELIF (iRacersInTiredMode < iLimitOfTiredRacers)
Tri_AI_Minimize_Racer_Speed(Race, iRacerIndex)
ELSE
Tri_AI_Increase_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_DEFAULT
iRacersInTiredMode--
ENDIF
ELSE // The racer is far from the player.
Tri_AI_Increase_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_DEFAULT
iRacersInTiredMode--
ENDIF
RESTART_TIMER_AT(Race.Racer[iRacerIndex].timerInCurrentCompeteMode, 0.0)
ELSE
Tri_AI_Minimize_Racer_Speed(Race, iRacerIndex)
ENDIF
ELSE // Current racer is ahead of the player.
IF TIMER_DO_WHEN_READY(Race.Racer[iRacerIndex].timerInCurrentCompeteMode, iTimerLimitInCompeteTiredMode)
IF (fDistanceBetweenRacerAndPlayer <= fCloseDistanceBetweenRacers)
IF (iRandomNum < 75)
Tri_AI_Increase_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_DEFAULT
iRacersInTiredMode--
ELIF (iRacersInTiredMode < iLimitOfTiredRacers)
iRandomNum = GET_RANDOM_INT_IN_RANGE(0, 100)
IF (iRandomNum < 20)
Tri_AI_Minimize_Racer_Speed(Race, iRacerIndex)
ELSE
Tri_AI_Decrease_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_DEFAULT
iRacersInTiredMode--
ENDIF
ELIF (iRacersInAggressiveMode < iLimitOfAggressiveRacers)
iRandomNum = GET_RANDOM_INT_IN_RANGE(0, 101)
IF (iRandomNum < 25)
Tri_AI_Maximize_Racer_Speed(Race, iRacerIndex)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_AGGRESSIVE
iRacersInAggressiveMode++
ELSE
Tri_AI_Decrease_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_DEFAULT
ENDIF
iRacersInTiredMode--
ELSE
Tri_AI_Decrease_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_DEFAULT
iRacersInTiredMode--
ENDIF
ELSE
IF (iRacersInTiredMode < iLimitOfTiredRacers)
iRandomNum = GET_RANDOM_INT_IN_RANGE(0, 101)
IF (iRandomNum < 50)
Tri_AI_Minimize_Racer_Speed(Race, iRacerIndex)
ELSE
Tri_AI_Decrease_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_DEFAULT
iRacersInTiredMode--
ENDIF
ELSE
Tri_AI_Decrease_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_DEFAULT
iRacersInTiredMode--
ENDIF
ENDIF
RESTART_TIMER_AT(Race.Racer[iRacerIndex].timerInCurrentCompeteMode, 0.0)
ELSE
Tri_AI_Minimize_Racer_Speed(Race, iRacerIndex)
ENDIF
ENDIF
ENDPROC
/// PURPOSE:
/// Update aggressive racer's speed behavior.
PROC Tri_AI_Update_Racer_Compete_Aggressive_Mode(TRI_RACE_STRUCT& Race, INT iRacerIndex, FLOAT fDistanceBetweenRacerAndPlayer)
INT iRandomNum
IF (Race.Racer[iRacerIndex].iRank > Race.Racer[0].iRank) // Player is ahead of the current racer.
IF TIMER_DO_WHEN_READY(Race.Racer[iRacerIndex].timerInCurrentCompeteMode, iTimerLimitInCompeteAggressiveMode)
IF (fDistanceBetweenRacerAndPlayer <= fCloseDistanceBetweenRacers) // Racer is close to player
IF iRacersInAggressiveMode < iLimitOfAggressiveRacers
iRandomNum = GET_RANDOM_INT_IN_RANGE(0, 100)
IF (iRandomNum < 50)
Tri_AI_Maximize_Racer_Speed(Race, iRacerIndex)
ELSE
Tri_AI_Increase_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_DEFAULT
iRacersInAggressiveMode--
ENDIF
ELIF iRacersInTiredMode < iLimitOfTiredRacers
iRandomNum = GET_RANDOM_INT_IN_RANGE(0, 101)
IF (iRandomNum < 5)
Tri_AI_Minimize_Racer_Speed(Race, iRacerIndex)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_TIRED
iRacersInTiredMode++
ELSE
Tri_AI_Increase_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_DEFAULT
ENDIF
iRacersInAggressiveMode--
ELSE
Tri_AI_Increase_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_DEFAULT
iRacersInAggressiveMode--
ENDIF
ELSE // Racer is far from player.
IF iRacersInAggressiveMode < iLimitOfAggressiveRacers
Tri_AI_Maximize_Racer_Speed(Race, iRacerIndex)
ELSE
Tri_AI_Increase_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_DEFAULT
iRacersInAggressiveMode--
ENDIF
ENDIF
RESTART_TIMER_AT(Race.Racer[iRacerIndex].timerInCurrentCompeteMode, 0.0)
ELSE
Tri_AI_Maximize_Racer_Speed(Race, iRacerIndex)
ENDIF
ELSE // Current racer is ahead of the player.
IF TIMER_DO_WHEN_READY(Race.Racer[iRacerIndex].timerInCurrentCompeteMode, iTimerLimitInCompeteAggressiveMode)
IF (fDistanceBetweenRacerAndPlayer <= fCloseDistanceBetweenRacers) // Racer is close to player.
iRandomNum = GET_RANDOM_INT_IN_RANGE(0, 101)
IF (iRandomNum < 50)
Tri_AI_Decrease_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_DEFAULT
iRacersInAggressiveMode--
ELIF (iRacersInAggressiveMode < iLimitOfAggressiveRacers)
iRandomNum = GET_RANDOM_INT_IN_RANGE(0, 101)
IF (iRandomNum < 50)
Tri_AI_Maximize_Racer_Speed(Race, iRacerIndex)
ELSE
Tri_AI_Decrease_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_DEFAULT
iRacersInAggressiveMode--
ENDIF
ELIF (iRacersInTiredMode < iLimitOfTiredRacers)
iRandomNum = GET_RANDOM_INT_IN_RANGE(0, 101)
IF (iRandomNum < 10)
Tri_AI_Minimize_Racer_Speed(Race, iRacerIndex)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_TIRED
iRacersInTiredMode--
ELSE
Tri_AI_Decrease_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_DEFAULT
ENDIF
iRacersInAggressiveMode--
ELSE
Tri_AI_Decrease_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_DEFAULT
iRacersInAggressiveMode--
ENDIF
ELSE // Racer is not close to player.
IF (iRacersInTiredMode < iLimitOfTiredRacers)
iRandomNum = GET_RANDOM_INT_IN_RANGE(0, 101)
IF (iRandomNum < 15)
Tri_AI_Minimize_Racer_Speed(Race, iRacerIndex)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_TIRED
iRacersInTiredMode++
ELSE
Tri_AI_Decrease_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_DEFAULT
ENDIF
ELSE
Tri_AI_Decrease_Racer_Speed(Race, iRacerIndex, fDistanceBetweenRacerAndPlayer)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_DEFAULT
ENDIF
iRacersInAggressiveMode--
ENDIF
RESTART_TIMER_AT(Race.Racer[iRacerIndex].timerInCurrentCompeteMode, 0.0)
ELSE
Tri_AI_Maximize_Racer_Speed(Race, iRacerIndex)
ENDIF
ENDIF
ENDPROC
/// PURPOSE:
/// Update forced tired racer's speed behavior.
PROC Tri_AI_Update_Racer_Compete_Forced_Tired_Mode(TRI_RACE_STRUCT& Race, INT iRacerIndex)
IF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_SWIM)
Race.Racer[iRacerIndex].fMinMoveBlendRatioInWater = 1.7
Race.Racer[iRacerIndex].fMaxMoveBlendRatioInWater = 2.2
ELIF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_BIKE)
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_DEFAULT
ELIF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_RUN)
Race.Racer[iRacerIndex].fMinMoveBlendRatioOnFoot = 1.1
Race.Racer[iRacerIndex].fMaxMoveBlendRatioOnFoot = 1.6
ENDIF
ENDPROC
/// PURPOSE:
/// Update each racer's speed behavior.
///
/// OVERVIEW ON HOW AI SYSTEM WORKS:
/// - Compete modes are applied in all legs of the race.
///
///
/// - All AI racers start the race in TRI_RACER_COMPETE_MODE_DEFAULT.
///
///
/// - Each compete mode has its own update procedure:
/// Tri_AI_Update_Racer_Compete_Default_Mode
/// Tri_AI_Update_Racer_Compete_Tired_Mode
/// Tri_AI_Update_Racer_Compete_Aggressive_Mode
/// Tri_AI_Update_Racer_Compete_Forced_Tired_Mode
///
///
/// - Each racer stays in their compete mode for the amount of time determined
/// by the mode's exclusive timer:
/// iTimerLimitInCompeteDefaultMode (Default mode lasts the longest)
/// iTimerLimitInCompeteTiredMode
/// iTimerLimitInCompeteAggressiveMode
///
///
/// - While the compete mode is being updated, specific procedures are called to
/// alter or maintain the speed of racers. These procedures themselves call
/// leg-specific procedures based on which leg a racer is in:
///
/// * TRI_RACER_COMPETE_MODE_DEFAULT calls Tri_AI_Decrease_Racer_Speed and
/// Tri_AI_Increase_Racer_Speed. These are rubber-banding functions,
/// so they increment and decrement speed based on player/racer distance,
/// rank position in race, etc. Each racer has speed limits, so rubber-banding
/// cannot go below or above these limits. This is to allow racers of different
/// skills in the race to prevent them all from competing the same way.
///
/// * TRI_RACER_COMPETE_MODE_TIRED calls Tri_AI_Minimize_Racer_Speed, which sets
/// racers to a constant, low speed.
///
/// * TRI_RACER_COMPETE_MODE_AGGRESSIVE Tri_AI_Maximize_Racer_Speed, which sets
/// racers to a constant, high speed.
///
///
/// - Once the timer is up, there are several factors that determine what compete
/// mode the racer will be set to next:
///
/// * The compete mode that was just completed.
///
/// * Racer's rank in the race compared to the player.
///
/// * Distance between racer and player.
///
/// * How many racers are in a specific compete mode: iRacersInTiredMode, iRacersInAggressiveMode
///
/// * A random roll of the dice, which tends to favor TRI_RACER_COMPETE_MODE_DEFAULT, which
/// is also why we have a limit to how many tired and aggressive racers can be active
/// at any one time (iLimitOfTiredRacers, iLimitOfAggressiveRacers). However, if a racer is
/// in an unfavorable position, there is a higher chance he'll change to an aggro state, and if
/// he's in too favorable a position, there's a higher likelihood of the racer being set to a tired state.
///
///
/// - TRI_RACER_COMPETE_MODE_FORCED_TIRED forces racers into tired mode, without any regard for
/// dice-rolling or player distance or rank. This is set from other commands that check where the player
/// or racer are in the race, usually based on their current gate, to force a change in the current race dynamic.
/// This is used mainly to force a spread between racers. For example, anywhere from 2 to 3 AI racers are
/// forced to get tired in the last third of the swim leg, so not all racers are crowded at the time they reach shore
/// and run for the bikes.
///
/// * There is no automatic exit from TRI_RACER_COMPETE_MODE_FORCED_TIRED, so the compete mode state has to be updated
/// manually to exit a racer from this state.
///
///
/// - The ongoing goal is to make the race be competitive without being too frustrating, but at the
/// same time prevent ALL racers from being competitive at the same time, with the player being able to
/// tell there are some racers that are faster than others.
///
///
/// NOTE: In hindsight, a factor that doesn't play a role in this system is player speed, which could have proved useful.
PROC UPDATE_TRI_RACERS_COMPETE_MODES_AND_LEG_MODIFIERS(TRI_RACE_STRUCT& Race, INT iFrameCounter)
IF IS_TRI_AI_RACER_VALID(Race, iFrameCounter, FALSE)
FLOAT fDistanceBetweenRacerAndPlayer = GET_DISTANCE_BETWEEN_ENTITIES(Race.Racer[iFrameCounter].Driver, Race.Racer[0].Driver)
SWITCH (Race.Racer[iFrameCounter].eCompeteMode)
// The racer is racing normally.
CASE TRI_RACER_COMPETE_MODE_DEFAULT
Tri_AI_Update_Racer_Compete_Default_Mode(Race, iFrameCounter, fDistanceBetweenRacerAndPlayer)
BREAK
// The racer is racing slowly.
CASE TRI_RACER_COMPETE_MODE_TIRED
Tri_AI_Update_Racer_Compete_Tired_Mode(Race, iFrameCounter, fDistanceBetweenRacerAndPlayer)
BREAK
// The racer is racing at max speed.
CASE TRI_RACER_COMPETE_MODE_AGGRESSIVE
Tri_AI_Update_Racer_Compete_Aggressive_Mode(Race, iFrameCounter, fDistanceBetweenRacerAndPlayer)
BREAK
// The racer is forced to slow down by a specific condition in the race.
CASE TRI_RACER_COMPETE_MODE_FORCED_TIRED
Tri_AI_Update_Racer_Compete_Forced_Tired_Mode(Race, iFrameCounter)
BREAK
ENDSWITCH
// Assist the compete and rubber-banding systems with a desired racer behavior.
UPDATE_TRI_RACER_AI_LEG_MODIFIER(Race, iFrameCounter)
ENDIF
ENDPROC
// ---------------------------------------------------------------
// E N D COMPETE MODE FUNCTIONS AND PROCEDURES
// ---------------------------------------------------------------
// ---------------------------------------------------------------
// UPDATE RACERS SPEEDS FUNCTIONS AND PROCEDURES
// ---------------------------------------------------------------
/// PURPOSE:
/// Update the racers' speeds in the swim leg of the race.
///
/// NOTE: Only use swim speed if ped is in water. Otherwise, use running speed. This is for when the racers
/// start the race. They're running towards the water, but are technically in the swim leg.
PROC UPDATE_TRI_RACERS_SPEEDS_IN_SWIM_LEG(TRI_RACE_STRUCT& Race, INT iRacerIndex)
IF IS_TRI_AI_RACER_VALID(Race, iRacerIndex, FALSE)
IF IS_ENTITY_IN_WATER(Race.Racer[iRacerIndex].Driver)
SET_PED_DESIRED_MOVE_BLEND_RATIO(Race.Racer[iRacerIndex].Driver, GET_RANDOM_FLOAT_IN_RANGE(Race.Racer[iRacerIndex].fMinMoveBlendRatioInWater,
Race.Racer[iRacerIndex].fMaxMoveBlendRatioInWater))
ELSE
SET_PED_DESIRED_MOVE_BLEND_RATIO(Race.Racer[iRacerIndex].Driver, GET_RANDOM_FLOAT_IN_RANGE(Race.Racer[iRacerIndex].fMinMoveBlendRatioOnFoot,
Race.Racer[iRacerIndex].fMaxMoveBlendRatioOnFoot))
ENDIF
ENDIF
ENDPROC
/// PURPOSE:
/// Update the racers' speeds in the bike leg of the race.
PROC UPDATE_TRI_RACERS_SPEEDS_IN_BIKE_LEG(TRI_RACE_STRUCT& Race, INT iRacerIndex)
IF IS_TRI_AI_RACER_VALID(Race, iRacerIndex, TRUE)
IF IS_PED_IN_VEHICLE(Race.Racer[iRacerIndex].Driver, Race.Racer[iRacerIndex].Vehicle)
SET_DRIVE_TASK_CRUISE_SPEED(Race.Racer[iRacerIndex].Driver, GET_RANDOM_FLOAT_IN_RANGE(Race.Racer[iRacerIndex].fMinBikeSpeed,
Race.Racer[iRacerIndex].fMaxBikeSpeed))
ENDIF
ENDIF
ENDPROC
/// PURPOSE:
/// Update the racers' speeds in the run leg of the race.
PROC UPDATE_TRI_RACERS_SPEEDS_IN_RUN_LEG(TRI_RACE_STRUCT& Race, INT iRacerIndex)
IF IS_TRI_AI_RACER_VALID(Race, iRacerIndex, FALSE) AND IS_WAYPOINT_PLAYBACK_GOING_ON_FOR_PED(Race.Racer[iRacerIndex].Driver)
FLOAT fMBR = GET_RANDOM_FLOAT_IN_RANGE(Race.Racer[iRacerIndex].fMinMoveBlendRatioOnFoot, Race.Racer[iRacerIndex].fMaxMoveBlendRatioOnFoot)
WAYPOINT_PLAYBACK_OVERRIDE_SPEED(Race.Racer[iRacerIndex].Driver, fMBR)
// SET_PED_DESIRED_MOVE_BLEND_RATIO(Race.Racer[iRacerIndex].Driver, fMBR)
ENDIF
ENDPROC
/// PURPOSE:
/// Update the racers' speeds in each leg.
PROC UPDATE_TRI_RACERS_SPEEDS(TRI_RACE_STRUCT& Race)
INT iRacerCounter
REPEAT Race.iRacerCnt iRacerCounter
IF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerCounter]) = TRI_TRI_RACE_LEG_SWIM)
UPDATE_TRI_RACERS_SPEEDS_IN_SWIM_LEG(Race, iRacerCounter)
ELIF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerCounter]) = TRI_TRI_RACE_LEG_BIKE)
UPDATE_TRI_RACERS_SPEEDS_IN_BIKE_LEG(Race, iRacerCounter)
ELIF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerCounter]) = TRI_TRI_RACE_LEG_RUN)
UPDATE_TRI_RACERS_SPEEDS_IN_RUN_LEG(Race, iRacerCounter)
ENDIF
ENDREPEAT
ENDPROC
// ---------------------------------------------------------------
// E N D UPDATE RACERS SPEEDS FUNCTIONS AND PROCEDURES
// ---------------------------------------------------------------
// ===========================================================================
// E N D RACER SPEED FUNCTIONS AND PROCESSES
// ===========================================================================
// ======================================================
// AI RACE FLOW TASKING FUNCTIONS AND PROCEDURES
//
// Implement all necessary tasking to ensure AI
// racers follow the race course.
//
// ======================================================
/// PURPOSE:
/// Set up a task sequence for racers getting on bikes after exiting water.
PROC TASK_TRI_RACER_TO_GET_ON_BIKE(TRI_RACE_STRUCT& Race, INT iRacerIndex)
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI.sch->TASK_TRI_RACER_TO_GET_ON_BIKE] Procedure started.")
IF NOT ( IS_PED_IN_VEHICLE(Race.Racer[iRacerIndex].Driver, Race.Racer[iRacerIndex].Vehicle) OR IS_PED_GETTING_INTO_A_VEHICLE(Race.Racer[iRacerIndex].Driver) )
SET_PED_PATH_PREFER_TO_AVOID_WATER(Race.Racer[iRacerIndex].Driver, TRUE)
VECTOR vBikePos = GET_ENTITY_COORDS(Race.Racer[iRacerIndex].Vehicle)
VECTOR vOffset = (GET_ENTITY_COORDS(Race.Racer[iRacerIndex].Driver) - vBikePos) * 0.15
VECTOR vDest = vBikePos + vOffset
#IF IS_DEBUG_BUILD
TEXT_LABEL_23 texLabel = "DriverDest"
texLabel += iRacerIndex
DEBUG_RECORD_SPHERE(texLabel, vDest, 1.0, (<<0,0,255>>))
#ENDIF
CLEAR_SEQUENCE_TASK(seqRacerGetInBike)
OPEN_SEQUENCE_TASK(seqRacerGetInBike)
// Navigate the ped to the bike.
TASK_FOLLOW_NAV_MESH_TO_COORD(NULL, vDest, PEDMOVE_SPRINT, DEFAULT_TIME_BEFORE_WARP, 3.0, ENAV_NO_STOPPING)
// Tell racer to get on bike.
TASK_ENTER_VEHICLE(NULL, Race.Racer[iRacerIndex].Vehicle, DEFAULT_TIME_BEFORE_WARP, VS_DRIVER, PEDMOVEBLENDRATIO_SPRINT)
// Tell racer to follow waypoint recording.
TASK_VEHICLE_FOLLOW_WAYPOINT_RECORDING(NULL, Race.Racer[iRacerIndex].Vehicle, Race.Racer[iRacerIndex].szCurrentBikeRecordingName,
DRIVINGMODE_AVOIDCARS | DF_SteerAroundPeds,
0, EWAYPOINT_START_FROM_CLOSEST_POINT, -1, -1)
CLOSE_SEQUENCE_TASK(seqRacerGetInBike)
TASK_PERFORM_SEQUENCE(Race.Racer[iRacerIndex].Driver, seqRacerGetInBike)
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI.sch->TASK_TRI_RACER_TO_GET_ON_BIKE] Performing get-on-bike task for racer #", iRacerIndex)
ELSE
TASK_VEHICLE_FOLLOW_WAYPOINT_RECORDING(Race.Racer[iRacerIndex].Driver, Race.Racer[iRacerIndex].Vehicle, Race.Racer[iRacerIndex].szCurrentBikeRecordingName,
DRIVINGMODE_AVOIDCARS | DF_SteerAroundPeds,
0, EWAYPOINT_START_FROM_CLOSEST_POINT, -1, -1)
ENDIF
ENDPROC
/// PURPOSE:
/// Checks if the player has taken a bike originally assigned for an AI racer.
/// Swaps the assigned vehicles if this is the case.
PROC UPDATE_TRI_RACER_VEHICLE_ASSIGNMENT(TRI_RACE_STRUCT &Race, VEHICLE_INDEX vehBikePlayerIsOn)
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI.sch->UPDATE_TRI_RACER_VEHICLE_ASSIGNMENT] Procedure started.")
INT iRacerCounter
REPEAT (Race.iRacerCnt) iRacerCounter
IF iRacerCounter <> 0
IF NOT IS_ENTITY_DEAD(Race.Racer[iRacerCounter].Vehicle)
// Check if the bike the player is on was originally assigned to an AI racer.
IF vehBikePlayerIsOn = Race.Racer[iRacerCounter].Vehicle
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI.sch->UPDATE_TRI_RACER_VEHICLE_ASSIGNMENT] Player is on racer #", iRacerCounter, "'s bike. Swap.")
// If so, swap the vehicle assignment between the player and AI racer.
VEHICLE_INDEX vehTempVehicle
vehTempVehicle = Race.Racer[iRacerCounter].Vehicle
Race.Racer[iRacerCounter].Vehicle = Race.Racer[0].Vehicle
Race.Racer[0].Vehicle = vehTempVehicle
// Retask the AI racer to his new vehicle if he's in the bike leg and trying to get on his old bike.
// Otherwise, the AI racer will go to his newly assigned bike once he finishes the swim leg.
IF Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerCounter]) = TRI_TRI_RACE_LEG_BIKE
CLEAR_PED_TASKS(Race.Racer[iRacerCounter].Driver)
TASK_TRI_RACER_TO_GET_ON_BIKE(Race, iRacerCounter)
ELSE
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI.sch->UPDATE_TRI_RACER_VEHICLE_ASSIGNMENT] Racer is not in the bike leg yet. He'll be tasked after clearing the swim leg.")
ENDIF
// We don't need to check anymore, as final bike assignment have been set. Exit procedure.
EXIT
ENDIF
ENDIF
ENDIF
ENDREPEAT
ENDPROC
/// PURPOSE:
/// Update racer's current vehicle waypoint recording.
///
/// NOTE: In the entire course of the Ironman race, there are several four coords we
/// track that, when a racer enters one of these coords their current vehicle
/// waypoint recording is updated to the next one.
///
/// In the comments, the numbers reference the vehicle recording:
/// 0: First Ironman racer vehicle waypoint recording.
/// 1: Second Ironman racer vehicle waypoint recoring.
/// 2: Third Ironman racer vehicle waypoint recoring, etc.
PROC UPDATE_TRI_RACER_IRONMAN_VEHICLE_WAYPOINT_RECORDING(TRI_RACE_STRUCT& Race, INT iRacerIndex)
IF eCurrentTriRace = TRIATHLON_RACE_IRONMAN
IF IS_TRI_AI_RACER_VALID(Race, iRacerIndex, TRUE)
// If player is in 0->1 coordinate, update racer recording name to 1.
IF IS_ENTITY_AT_COORD(Race.Racer[iRacerIndex].Vehicle, << 145.87, 1615.00, 228.27 >>, << 10.0, 10.0, 10.0 >>)
Race.Racer[iRacerIndex].szCurrentBikeRecordingName = szTriBikeRecordingName_IronMan_1
TASK_VEHICLE_FOLLOW_WAYPOINT_RECORDING(Race.Racer[iRacerIndex].Driver, Race.Racer[iRacerIndex].Vehicle, Race.Racer[iRacerIndex].szCurrentBikeRecordingName,
DRIVINGMODE_AVOIDCARS | DF_SteerAroundPeds,
0, EWAYPOINT_START_FROM_CLOSEST_POINT)
// If player is in 1->2 coordinate, update racer recording name to 2.
ELIF IS_ENTITY_AT_COORD(Race.Racer[iRacerIndex].Vehicle, << 1188.63, -1082.86, 39.74 >>, << 10.0, 10.0, 10.0 >>)
Race.Racer[iRacerIndex].szCurrentBikeRecordingName = szTriBikeRecordingName_IronMan_2
TASK_VEHICLE_FOLLOW_WAYPOINT_RECORDING(Race.Racer[iRacerIndex].Driver, Race.Racer[iRacerIndex].Vehicle, Race.Racer[iRacerIndex].szCurrentBikeRecordingName,
DRIVINGMODE_AVOIDCARS | DF_SteerAroundPeds,
0, EWAYPOINT_START_FROM_CLOSEST_POINT)
// If player is in 2->3 coordinate, update racer recording name to 3.
ELIF IS_ENTITY_AT_COORD(Race.Racer[iRacerIndex].Vehicle, << -1027.92, -1081.72, 1.66 >>, << 10.0, 10.0, 10.0 >>)
Race.Racer[iRacerIndex].szCurrentBikeRecordingName = szTriBikeRecordingName_IronMan_3
TASK_VEHICLE_FOLLOW_WAYPOINT_RECORDING(Race.Racer[iRacerIndex].Driver, Race.Racer[iRacerIndex].Vehicle, Race.Racer[iRacerIndex].szCurrentBikeRecordingName,
DRIVINGMODE_AVOIDCARS | DF_SteerAroundPeds,
0, EWAYPOINT_START_FROM_CLOSEST_POINT)
// If player is in 3->4 coordinate, update racer recording name to 4.
ELIF IS_ENTITY_AT_COORD(Race.Racer[iRacerIndex].Vehicle, << -901.19, 244.36, 69.76 >>, << 10.0, 10.0, 10.0 >>)
Race.Racer[iRacerIndex].szCurrentBikeRecordingName = szTriBikeRecordingName_IronMan_4
TASK_VEHICLE_FOLLOW_WAYPOINT_RECORDING(Race.Racer[iRacerIndex].Driver, Race.Racer[iRacerIndex].Vehicle, Race.Racer[iRacerIndex].szCurrentBikeRecordingName,
DRIVINGMODE_AVOIDCARS | DF_SteerAroundPeds,
0, EWAYPOINT_START_FROM_CLOSEST_POINT)
ENDIF
ENDIF
ENDIF
ENDPROC
/// PURPOSE:
/// Task racer to follow his vehicle waypoint recording.
PROC TASK_TRI_RACER_TO_FOLLOW_VEHICLE_WAYPOINT_RECORDING(TRI_RACE_STRUCT& Race, INT iRacerIndex)
IF IS_TRI_AI_RACER_VALID(Race, iRacerIndex, TRUE)
TASK_VEHICLE_FOLLOW_WAYPOINT_RECORDING(Race.Racer[iRacerIndex].Driver, Race.Racer[iRacerIndex].Vehicle, Race.Racer[iRacerIndex].szCurrentBikeRecordingName,
DRIVINGMODE_AVOIDCARS | DF_SteerAroundPeds | DRIVINGMODE_AVOIDCARS_RECKLESS , 0, EWAYPOINT_START_FROM_CLOSEST_POINT, -1, -1)
ENDIF
ENDPROC
/// PURPOSE:
/// Task the player to go to the next run gate.
PROC TASK_TRI_RACER_TO_GO_TO_NEXT_RUN_GATE(TRI_RACE_STRUCT& Race, INT iRacerIndex)
IF NOT (Race.Racer[iRacerIndex].iGateCur = (Race.iGateCnt - 1) )
TRI_CONTROL_FLAGS eFlag
SWITCH iRacerIndex
CASE 1 eFlag = TCF_RACER_WAYPOINTED_1 BREAK
CASE 2 eFlag = TCF_RACER_WAYPOINTED_2 BREAK
CASE 3 eFlag = TCF_RACER_WAYPOINTED_3 BREAK
CASE 4 eFlag = TCF_RACER_WAYPOINTED_4 BREAK
CASE 5 eFlag = TCF_RACER_WAYPOINTED_5 BREAK
CASE 6 eFlag = TCF_RACER_WAYPOINTED_6 BREAK
CASE 7 eFlag = TCF_RACER_WAYPOINTED_7 BREAK
DEFAULT SCRIPT_ASSERT("TASK_TRI_RACER_TO_GO_TO_NEXT_RUN_GATE Hitting the default flag, contact Rob Pearsall") BREAK
ENDSWITCH
IF Race.Racer[iRacerIndex].iGateCur > 0 AND NOT IS_TRI_CONTROL_FLAG_SET(eFlag)
STRING sRecording
IF eCurrentTriRace = TRIATHLON_RACE_ALAMO_SEA
sRecording = "Tri1_Run"
ELIF eCurrentTriRace = TRIATHLON_RACE_VESPUCCI
sRecording = "Tri2_Run"
ELIF eCurrentTriRace = TRIATHLON_RACE_IRONMAN
sRecording = "Tri3_Run"
ENDIF
TASK_FOLLOW_WAYPOINT_RECORDING(Race.Racer[iRacerIndex].Driver, sRecording, default, EWAYPOINT_NAVMESH_TO_INITIAL_WAYPOINT | EWAYPOINT_START_FROM_CLOSEST_POINT | EWAYPOINT_NAVMESH_BACK_TO_WAYPOINT_IF_LEFT_ROUTE | EWAYPOINT_ALLOW_STEERING_AROUND_PEDS)
SET_TRI_CONTROL_FLAG(eFlag)
CPRINTLN(DEBUG_TRIATHLON, "TASK_TRI_RACER_TO_GO_TO_NEXT_RUN_GATE Racer[", iRacerIndex, "] tasked with TASK_PERFORM_SEQUENCE(seqNextGate)")
// ELSE
// TASK_FOLLOW_NAV_MESH_TO_COORD(Race.Racer[iRacerIndex].Driver, vGateGotoWithOffset, PEDMOVE_SPRINT, -1, 1.0, ENAV_NO_STOPPING)
// CPRINTLN(DEBUG_TRIATHLON, "TASK_TRI_RACER_TO_GO_TO_NEXT_RUN_GATE Racer[", iRacerIndex, "] tasked with TASK_FOLLOW_NAV_MESH_TO_COORD")
ENDIF
IF NOT IS_ENTITY_DEAD(Race.Racer[iRacerIndex].Vehicle)
SET_VEHICLE_IS_CONSIDERED_BY_PLAYER(Race.Racer[iRacerIndex].Vehicle, TRUE)
ENDIF
ELSE
// When racer goes to last gate, set destination a bit past the last gate's position, so the racers don't run into each other
// as they reach the end of the race.
SET_PED_STEERS_AROUND_OBJECTS(Race.Racer[iRacerIndex].Driver, FALSE)
FLOAT fVariance = 3.0
VECTOR vLastGate = GET_OFFSET_FROM_COORD_IN_WORLD_COORDS(vLastGateRacerDestination, 0.0, << GET_RANDOM_FLOAT_IN_RANGE(-fVariance, fVariance), GET_RANDOM_FLOAT_IN_RANGE(-fVariance, fVariance), 0.0 >>)
TASK_FOLLOW_NAV_MESH_TO_COORD(Race.Racer[iRacerIndex].Driver, vLastGate, PEDMOVE_SPRINT, -1, 3.45, ENAV_NO_STOPPING)
ENDIF
ENDPROC
/// PURPOSE:
/// Task the player to enter the bike, or continue riding on the vehicle waypoint recoring
/// while in the bike leg.
PROC TASK_TRI_RACER_TO_GO_TO_NEXT_BIKE_GATE(TRI_RACE_STRUCT& Race, INT iRacerIndex)
// If the racer is approaching the first bike gate, ensure the swim modifiers are removed.
IF (Race.Racer[iRacerIndex].iGateCur = Tri_Get_Swim_To_Bike_Transition_Gate())
IF Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_FORCED_TIRED
Race.Racer[iRacerIndex].eCompeteMode = TRI_RACER_COMPETE_MODE_DEFAULT
iRacersInForcedTiredMode--
ENDIF
ENDIF
// Set an AI racer to follow the waypoint recording, or get on the bike first before following recording.
IF NOT IS_ENTITY_DEAD(Race.Racer[iRacerIndex].Vehicle)
IF NOT IS_TRI_AI_CONTROL_FLAG_SET(Race.Racer[iRacerIndex],TACF_BHASBEENONABIKE)
OR ( NOT IS_PED_IN_ANY_VEHICLE(Race.Racer[iRacerIndex].Driver, TRUE) AND GET_SCRIPT_TASK_STATUS(Race.Racer[iRacerIndex].Driver, SCRIPT_TASK_PERFORM_SEQUENCE) <> PERFORMING_TASK )
TASK_TRI_RACER_TO_GET_ON_BIKE(Race, iRacerIndex)
ELIF GET_SCRIPT_TASK_STATUS(Race.Racer[iRacerIndex].Driver, SCRIPT_TASK_VEHICLE_FOLLOW_WAYPOINT_RECORDING) = PERFORMING_TASK
CPRINTLN(DEBUG_TRIATHLON, "TASK_TRI_RACER_TO_FOLLOW_VEHICLE_WAYPOINT_RECORDING(Race, ", iRacerIndex, ")")
SET_DRIVER_RACING_MODIFIER(Race.Racer[iRacerIndex].Driver, 1)
SET_VEHICLE_IS_RACING(Race.Racer[iRacerIndex].Vehicle, TRUE)
TASK_TRI_RACER_TO_FOLLOW_VEHICLE_WAYPOINT_RECORDING(Race, iRacerIndex)
ENDIF
ENDIF
ENDPROC
/// PURPOSE:
/// Task AI racer to go to next swim gate.
PROC TASK_TRI_RACER_TO_GO_TO_NEXT_SWIM_GATE(TRI_RACE_STRUCT& Race, VECTOR vCurrentGatePos, INT iRacerIndex)
VECTOR vGateGotoWithOffset
FLOAT fOffset = 4.0
vGateGotoWithOffset = GET_OFFSET_FROM_COORD_IN_WORLD_COORDS(vCurrentGatePos, 0.0, << GET_RANDOM_FLOAT_IN_RANGE(-fOffset, fOffset), GET_RANDOM_FLOAT_IN_RANGE(-fOffset, fOffset), 0.0 >>)
// CPRINTLN(DEBUG_TRIATHLON, "TASK_TRI_RACER_TO_GO_TO_NEXT_SWIM_GATE :: iRacerIndex=", iRacerIndex, ", iGateCur=", Race.Racer[iRacerIndex].iGateCur)
IF Race.Racer[iRacerIndex].iGateCur >= 1
SET_PED_STEERS_AROUND_OBJECTS(Race.Racer[iRacerIndex].Driver, TRUE)
ENDIF
IF IS_TRI_AI_CONTROL_FLAG_SET(Race.Racer[iRacerIndex],TACF_BSETSPEED)
IF Race.Racer[iRacerIndex].iGateCur = 1
CLEAR_TRI_AI_CONTROL_FLAG(Race.Racer[iRacerIndex],TACF_BSETSPEED)
ENDIF
ENDIF
// Using the nav mesh causes racers to take odd paths while swimming, so just task them to go straight to the gate.
IF Race.Racer[iRacerIndex].iGateCur = 0
SEQUENCE_INDEX siSeq
TEXT_LABEL_63 waypointRec = "triathlon_"
SWITCH eCurrentTriRace
CASE TRIATHLON_RACE_VESPUCCI
waypointRec += "ves_"
BREAK
CASE TRIATHLON_RACE_ALAMO_SEA
waypointRec += "ala_"
BREAK
CASE TRIATHLON_RACE_IRONMAN
waypointRec += "coy_"
BREAK
ENDSWITCH
waypointRec += "racer"
waypointRec += iRacerIndex
OPEN_SEQUENCE_TASK(siSeq)
TASK_FOLLOW_WAYPOINT_RECORDING(NULL, waypointRec, DEFAULT, EWAYPOINT_START_FROM_CLOSEST_POINT)
//TASK_FOLLOW_NAV_MESH_TO_COORD(NULL, TRI_GET_RACER_INITIAL_GO_TO(iRacerIndex), PEDMOVE_SPRINT, DEFAULT_TIME_NEVER_WARP, DEFAULT, ENAV_DONT_AVOID_PEDS | ENAV_DONT_AVOID_OBJECTS | ENAV_NO_STOPPING)
TASK_FOLLOW_NAV_MESH_TO_COORD(NULL, vGateGotoWithOffset, PEDMOVE_SPRINT, DEFAULT_TIME_NEVER_WARP, DEFAULT, ENAV_DONT_AVOID_PEDS | ENAV_DONT_AVOID_OBJECTS | ENAV_NO_STOPPING)
//TASK_GO_STRAIGHT_TO_COORD(NULL, vGateGotoWithOffset, PEDMOVE_SPRINT, -1)
CLOSE_SEQUENCE_TASK(siSeq)
TASK_PERFORM_SEQUENCE(Race.Racer[iRacerIndex].Driver, siSeq)
CLEAR_SEQUENCE_TASK(siSeq)
SET_TRI_AI_CONTROL_FLAG(Race.Racer[iRacerIndex],TACF_BSETSPEED)
//TASK_FOLLOW_NAV_MESH_TO_COORD( Race.Racer[iRacerIndex].Driver, vGateGotoWithOffset, PEDMOVE_SPRINT, DEFAULT_TIME_NEVER_WARP )
ELSE
TASK_GO_STRAIGHT_TO_COORD(Race.Racer[iRacerIndex].Driver, vGateGotoWithOffset, PEDMOVE_SPRINT, -1)
ENDIF
ENDPROC
/// PURPOSE:
/// Task AI racer to go to next gate position in Triathlon.
PROC TASK_TRI_RACER_TO_FOLLOW_RACE_COURSE(TRI_RACE_STRUCT& Race, VECTOR vCurrentGatePos, INT iRacerIndex)
SWITCH ( Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) )
CASE TRI_TRI_RACE_LEG_SWIM
TASK_TRI_RACER_TO_GO_TO_NEXT_SWIM_GATE(Race, vCurrentGatePos, iRacerIndex)
BREAK
CASE TRI_TRI_RACE_LEG_BIKE
TASK_TRI_RACER_TO_GO_TO_NEXT_BIKE_GATE(Race, iRacerIndex)
BREAK
CASE TRI_TRI_RACE_LEG_RUN
TASK_TRI_RACER_TO_GO_TO_NEXT_RUN_GATE(Race, iRacerIndex)
BREAK
ENDSWITCH
ENDPROC
// ======================================================
// E N D AI RACE FLOW TASKING FUNCTIONS AND PROCEDURES
// ======================================================
// ================================================
// AI SUPERDUMMY FUNCTIONS AND PROCEDURES
//
// Account for edge cases where racers aren't
// updating as a result of being outside the
// collision/ physics bounds of the world.
//
// ================================================
/// PURPOSE:
/// Warp a bike racer that is too behind and idle back in the race, as long as he's a few
/// gates behind the player and out of view.
PROC WARP_TRI_AI_RACERS_IN_SUPERDUMMY_MODE_AND_BIKE_LEG_TO_THEIR_BIKES(TRI_RACE_STRUCT& Race, INT iRacerIndex)
IF IS_TRI_AI_RACER_OR_PLAYER_VALID(Race, iRacerIndex, TRUE)
IF (Race.Racer[0].iRank < Race.Racer[iRacerIndex].iRank)
IF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_BIKE)
IF IS_PED_IN_VEHICLE(Race.Racer[iRacerIndex].Driver, Race.Racer[iRacerIndex].Vehicle)
IF Race.Racer[iRacerIndex].iGateCur > (Tri_Get_Swim_To_Bike_Transition_Gate() + 3)
IF (Race.Racer[0].iGateCur - Race.Racer[iRacerIndex].iGateCur) >= 3
IF IS_ENTITY_OCCLUDED(Race.Racer[iRacerIndex].Driver)
IF TIMER_DO_WHEN_READY(Race.Racer[iRacerIndex].timerRacerVehicleIdle, 6.0)
CPRINTLN(DEBUG_TRIATHLON, "WARP_TRI_AI_RACERS_IN_SUPERDUMMY_MODE_AND_BIKE_LEG_TO_THEIR_BIKES, iRacerIndex=", iRacerIndex)
SET_PED_COORDS_KEEP_VEHICLE(Race.Racer[iRacerIndex].Driver, Race.sGate[Race.Racer[iRacerIndex].iGateCur].vPos)
Race.Racer[iRacerIndex].iGateCur++
TASK_TRI_RACER_TO_FOLLOW_RACE_COURSE(Race, Race.sGate[Race.Racer[iRacerIndex].iGateCur].vPos, iRacerIndex)
SET_VEHICLE_FORWARD_SPEED(Race.Racer[iRacerIndex].Vehicle, 15.0)
RESTART_TIMER_NOW(Race.Racer[iRacerIndex].timerRacerVehicleIdle)
ENDIF
ENDIF
ENDIF
ENDIF
ENDIF
ENDIF
ENDIF
ENDIF
ENDPROC
/// PURPOSE:
/// Warp swim racers to their bikes in the swim->bike transition gate.
PROC WARP_TRI_AI_RACER_TO_HIS_BIKE(TRI_RACE_STRUCT& Race, INT iRacerIndex)
IF IS_TRI_AI_RACER_VALID(Race, iRacerIndex, TRUE)
CPRINTLN(DEBUG_TRIATHLON, "WARP_TRI_AI_RACER_TO_HIS_BIKE, iRacerIndex=", iRacerIndex)
INT iBikeGate = Tri_Get_Swim_To_Bike_Transition_Gate() + 1
SET_ENTITY_COORDS(Race.Racer[iRacerIndex].Driver, Race.sGate[iBikeGate].vPos)
Race.Racer[iRacerIndex].iGateCur = iBikeGate + 1
SET_PED_INTO_VEHICLE(Race.Racer[iRacerIndex].Driver, Race.Racer[iRacerIndex].Vehicle)
TASK_TRI_RACER_TO_FOLLOW_RACE_COURSE(Race, Race.sGate[Race.Racer[iRacerIndex].iGateCur].vPos, iRacerIndex)
ENDIF
ENDPROC
/// PURPOSE:
/// If the player is far enough into the bike leg, and there are racers still in the swim leg
/// as a result of being outside the collision bounds, warp them to their bikes and task
/// them to continue race.
PROC WARP_TRI_AI_RACER_IN_SUPERDUMMY_MODE_AND_SWIM_LEG_TO_HIS_BIKES(TRI_RACE_STRUCT& Race, INT iRacerIndex)
IF NOT IS_ENTITY_DEAD(Race.Racer[0].Driver)
IF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[0]) = TRI_TRI_RACE_LEG_BIKE)
IF (Tri_Get_Racer_Current_Race_Leg(Race, Race.Racer[iRacerIndex]) = TRI_TRI_RACE_LEG_SWIM)
IF Race.Racer[0].iGateCur = iPlayerCurrentGateToWarpSuperDummySwimRacer
CPRINTLN(DEBUG_TRIATHLON, "WARP_TRI_AI_RACER_IN_SUPERDUMMY_MODE_AND_SWIM_LEG_TO_HIS_BIKES, iRacerIndex=", iRacerIndex)
WARP_TRI_AI_RACER_TO_HIS_BIKE(Race, iRacerIndex)
ENDIF
ENDIF
ENDIF
ENDIF
ENDPROC
/// PURPOSE:
/// Keep track of whether or not the racer vehicle is idle.
///
/// NOTE: This could be used as an additional means to check if racer
/// is idle in bike. Obviously, could be modified to account
/// for the swim and run legs of the race.
PROC UPDATE_TRI_RACER_VEHICLE_IDLE_TIMER(TRI_RACE_STRUCT& Race, INT iRacerIndex)
IF IS_TRI_AI_RACER_VALID(Race, iRacerIndex, TRUE)
IF IS_PED_IN_VEHICLE(Race.Racer[iRacerIndex].Driver, Race.Racer[iRacerIndex].Vehicle)
IF GET_ENTITY_SPEED(Race.Racer[iRacerIndex].Vehicle) > 0.0
RESTART_TIMER_NOW(Race.Racer[iRacerIndex].timerRacerVehicleIdle)
ELSE
IF NOT IS_TIMER_STARTED(Race.Racer[iRacerIndex].timerRacerVehicleIdle)
START_TIMER_NOW(Race.Racer[iRacerIndex].timerRacerVehicleIdle)
ENDIF
ENDIF
ENDIF
ENDIF
ENDPROC
/// PURPOSE:
/// Ensure racer doesn't stop participating as a result of their being outside the collision world bounds.
PROC UPDATE_TRI_RACER_SUPERDUMMY_MODE_MANAGER(TRI_RACE_STRUCT& Race, INT iRacerIndex)
// Keep track of whether or not the racer vehicle is idle.
UPDATE_TRI_RACER_VEHICLE_IDLE_TIMER(Race, iRacerIndex)
// If the player is far enough into the bike leg, and there are racers still in the swim leg
// as a result of being outside the collision bounds, warp them to their bikes and task
// them to continue race.
WARP_TRI_AI_RACER_IN_SUPERDUMMY_MODE_AND_SWIM_LEG_TO_HIS_BIKES(Race, iRacerIndex)
// Warp a bike racer that is too behind and idle back in the race, as long as he's a few
// gates behind the player and out of view.
WARP_TRI_AI_RACERS_IN_SUPERDUMMY_MODE_AND_BIKE_LEG_TO_THEIR_BIKES(Race, iRacerIndex)
// MAYBE TODO: Check for any racer with a speed of 0 for X amount of time.
// If they have a speed of 0, warp them to a gate safely inside collision bounds
// as long as they remain behind the player and out of view.
// Retask them to resume race.
// If they're on a vehicle in Ironman, ensure they're placed on the right waypoint recording,
// possibly by putting them in the same recording they were before. Hard to do.
ENDPROC
// ================================================
// E N D AI SUPERDUMMY FUNCTIONS AND PROCEDURES
// ================================================
// ================================================
// AI END OF RACE FUNCTIONS AND PROCEDURES
// ================================================
/// PURPOSE:
/// Keep checking if the racer reached the end of the race.
FUNC BOOL HAS_TRI_RACER_FINISHED_RACE(TRI_RACE_STRUCT& Race, TRI_RACER_STRUCT& Racer)
IF ( Racer.iGateCur > 0 )
IF (Racer.iGateCur >= Race.iGateCnt )
//CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI.sch->HAS_TRI_RACER_FINISHED_RACE] Racer has finished the race in #", Racer.iRank," position.")
RETURN TRUE
ENDIF
ENDIF
RETURN FALSE
ENDFUNC
PROC TRI_GET_NEW_FINISH_POSITION_IF_NEAR_PLAYER_FINISH(VECTOR& vPositionToCheck)
VECTOR vScenePos
FLOAT fHeading
VECTOR vNewFinishPosition
//the positions where the player plays their sycned scene finsh anims (taken from tri_cutscenes.sch
IF eCurrentTriRace = TRIATHLON_RACE_VESPUCCI
fHeading = 201.6718
vScenePos = <<-1332.92249, -1043.14160, 6.65>>
ELIF eCurrentTriRace = TRIATHLON_RACE_ALAMO_SEA
fHeading = -20.0
vScenePos = <<1759.43518, 3894.69409, 33.789>>
ELSE
fHeading = 167.8617
vScenePos = <<-2304.44312, 462.66916, 173.4493>>
ENDIF
FLOAT fHeadingAdjust
fHeadingAdjust = GET_RANDOM_FLOAT_IN_RANGE( -TRI_AI_FINISH_HEADING_ADJUST, TRI_AI_FINISH_HEADING_ADJUST )
fHeading += fHeadingAdjust
//if the goto position chosen is within 5 m of the player's position - have the racer keep running forward out of the scene
IF VDIST2(vPositionToCheck, vScenePos) <= 25
vNewFinishPosition = GET_OFFSET_FROM_COORD_IN_WORLD_COORDS(vPositionToCheck, fHeading, << 0, 10.0, 0.0>>)
vPositionToCheck = vNewFinishPosition
CPRINTLN(DEBUG_TRIATHLON, "TRI_RACER_END_POSITION_TOO_CLOSE_TO_PLAYER setting 10m ahead of previous value. New coord is: << ", vPositionToCheck.x, ",", vPositionToCheck.y, vPositionToCheck, ",", vPositionToCheck.z, " >>." )
ENDIF
ENDPROC
/// PURPOSE:
/// Play the racer's appropriate animation once they finish the race. This also accounts for
/// the player's finish animations.
///
/// NOTE: The racer is also tasked to run a bit past the gate here, before playing
/// his finish animation, so racers are technically crossing the finish line.
/// This extra tasking does not apply to the player.
PROC TASK_TRI_RACER_FINISHED_RACE_ANIMATION(TRI_RACE_STRUCT& Race, INT iRacerIndex)
IF NOT IS_ENTITY_DEAD(Race.Racer[iRacerIndex].Driver)
IF iRacerIndex <> 0
CPRINTLN(DEBUG_TRIATHLON, "TASK_TRI_RACER_FINISHED_RACE_ANIMATION :: iRacerIndex=", iRacerIndex)
VECTOR vOffsetForwardFromRacer
vOffsetForwardFromRacer = GET_OFFSET_FROM_ENTITY_IN_WORLD_COORDS(Race.Racer[iRacerIndex].Driver, << 0.0, GET_RANDOM_FLOAT_IN_RANGE(3.0, 8.0), 0.0 >>)
TRI_GET_NEW_FINISH_POSITION_IF_NEAR_PLAYER_FINISH(vOffsetForwardFromRacer)
// Check whether or not the racer reached first place, to determine which animation to play.
CLEAR_SEQUENCE_TASK(seqRacerFinishRace[iRacerIndex])
OPEN_SEQUENCE_TASK(seqRacerFinishRace[iRacerIndex])
TASK_FOLLOW_NAV_MESH_TO_COORD(NULL, vOffsetForwardFromRacer, PEDMOVE_WALK, DEFAULT_TIME_BEFORE_WARP, DEFAULT_NAVMESH_RADIUS, ENAV_NO_STOPPING)
IF (Race.Racer[iRacerIndex].iRank = 1)
STRING szTempAnim = GET_RANDOM_ANIM_CLIP_FROM_TRI_ANIM_DICTIONARY(szTriAnimDicts[0])
TASK_PLAY_ANIM(NULL, szTriAnimDicts[0], szTempAnim, NORMAL_BLEND_IN, NORMAL_BLEND_OUT, -1, AF_LOOPING, GET_RANDOM_FLOAT_IN_RANGE(0.0, 1.0))
ELSE
STRING szTempAnim = GET_RANDOM_ANIM_CLIP_FROM_TRI_ANIM_DICTIONARY(szTriAnimDicts[1])
TASK_PLAY_ANIM(NULL, szTriAnimDicts[1], szTempAnim, NORMAL_BLEND_IN, NORMAL_BLEND_OUT, -1, AF_LOOPING, GET_RANDOM_FLOAT_IN_RANGE(0.0, 1.0))
ENDIF
CLOSE_SEQUENCE_TASK(seqRacerFinishRace[iRacerIndex])
TASK_PERFORM_SEQUENCE(Race.Racer[iRacerIndex].Driver, seqRacerFinishRace[iRacerIndex])
ENDIF
bIsRacerFinishRaceSequenceSet[iRacerIndex] = TRUE
ENDIF
ENDPROC
/// PURPOSE:
/// Ensure racers who have completed the race copntinue to play their finished animations
/// once the player completes the race, which is when the race stops updating. Also,
/// ensure racers who haven't completed the race continue racing towards their next gate.
PROC TASK_AI_RACER_AFTER_PLAYER_FINISHES_RACE(TRI_RACE_STRUCT& Race, INT iRacerIndex)
CPRINTLN(DEBUG_TRIATHLON, "TASK_AI_RACER_AFTER_PLAYER_FINISHES_RACE :: iRacerIndex=", iRacerIndex)
BOOL bIsRacerCurrentGateLastOne = FALSE
STRING sAnimDict = "mini@triathlon"
INT iRacerCurrentGateIndex = Race.Racer[iRacerIndex].iGateCur
VECTOR vCurrentGatePos = Race.sGate[iRacerCurrentGateIndex].vPos
VECTOR vCurrentGatePosOffset = GET_OFFSET_FROM_COORD_IN_WORLD_COORDS(vCurrentGatePos, 0.0, << GET_RANDOM_FLOAT_IN_RANGE(-5.0, 5.0), GET_RANDOM_FLOAT_IN_RANGE(-5.0, 5.0), 0.0>>)
VECTOR vNextGatePos
VECTOR vNextGateGotoWithOffset
STRING sAnim
FLOAT fRand = GET_RANDOM_FLOAT_IN_RANGE()
IF fRand < 0.33
sAnim = "jog_idle_d"
ELIF fRand < 0.66
sAnim = "jog_idle_e"
ELSE
sAnim = "jog_idle_f"
ENDIF
// Check if the racer's current gate is the last gate.
IF ( iRacerCurrentGateIndex >= (Race.iGateCnt - 1) )
bIsRacerCurrentGateLastOne = TRUE
ELSE
bIsRacerCurrentGateLastOne = FALSE
vNextGatePos = Race.sGate[iRacerCurrentGateIndex + 1].vPos
vNextGateGotoWithOffset = GET_OFFSET_FROM_COORD_IN_WORLD_COORDS(vNextGatePos, 0.0, << 0, 10, 0.0>>)
TRI_GET_NEW_FINISH_POSITION_IF_NEAR_PLAYER_FINISH(vNextGateGotoWithOffset)
ENDIF
// If the racer already finished the race, keep playing their finished animations. Otherwise, task him to keep racing.
IF HAS_TRI_RACER_FINISHED_RACE(Race, Race.Racer[iRacerIndex]) AND iRacerIndex <> 0
IF NOT (GET_SCRIPT_TASK_STATUS(Race.Racer[iRacerIndex].Driver, SCRIPT_TASK_PLAY_ANIM) = PERFORMING_TASK)
AND NOT (GET_SCRIPT_TASK_STATUS(Race.Racer[iRacerIndex].Driver, SCRIPT_TASK_FOLLOW_NAV_MESH_TO_COORD) = PERFORMING_TASK )
IF Race.Racer[iRacerIndex].iRank = 1
TASK_PLAY_ANIM(Race.Racer[iRacerIndex].Driver, sAnimDict, "male_unarmed_b", 2, -2, -1, AF_LOOPING)
ELSE
TASK_PLAY_ANIM(Race.Racer[iRacerIndex].Driver, sAnimDict, sAnim, 2, -2, -1, AF_LOOPING)
ENDIF
ENDIF
ELIF iRacerIndex <> 0
IF bIsRacerCurrentGateLastOne
// Navigate racer to his current gate, and play his tired animation.
CLEAR_SEQUENCE_TASK(seqRacerEndOfRaceRace[iRacerIndex])
OPEN_SEQUENCE_TASK(seqRacerEndOfRaceRace[iRacerIndex])
TASK_FOLLOW_NAV_MESH_TO_COORD(NULL, vCurrentGatePosOffset, PEDMOVE_RUN, DEFAULT_TIME_BEFORE_WARP, DEFAULT_NAVMESH_RADIUS, ENAV_NO_STOPPING)
TASK_PLAY_ANIM(NULL, sAnimDict, sAnim, 2, -2, -1, AF_LOOPING)
CLOSE_SEQUENCE_TASK(seqRacerEndOfRaceRace[iRacerIndex])
TASK_PERFORM_SEQUENCE(Race.Racer[iRacerIndex].Driver, seqRacerEndOfRaceRace[iRacerIndex])
ELSE
// Navigate racer to his current gate, and then his next gate, so it looks like he's still racing.
IF NOT IS_ENTITY_DEAD(Race.Racer[iRacerIndex].Driver)
IF NOT IS_PED_IN_ANY_VEHICLE(Race.Racer[iRacerIndex].Driver)
CLEAR_SEQUENCE_TASK(seqRacerEndOfRaceRace[iRacerIndex])
OPEN_SEQUENCE_TASK(seqRacerEndOfRaceRace[iRacerIndex])
TASK_FOLLOW_NAV_MESH_TO_COORD(NULL, vCurrentGatePosOffset, PEDMOVE_RUN, DEFAULT_TIME_BEFORE_WARP, DEFAULT_NAVMESH_RADIUS, ENAV_NO_STOPPING)
TASK_FOLLOW_NAV_MESH_TO_COORD(NULL, vNextGateGotoWithOffset, PEDMOVE_RUN, DEFAULT_TIME_BEFORE_WARP, DEFAULT_NAVMESH_RADIUS, ENAV_NO_STOPPING)
TASK_PLAY_ANIM(NULL, sAnimDict, sAnim, 2, -2, -1, AF_LOOPING)
CLOSE_SEQUENCE_TASK(seqRacerEndOfRaceRace[iRacerIndex])
TASK_PERFORM_SEQUENCE(Race.Racer[iRacerIndex].Driver, seqRacerEndOfRaceRace[iRacerIndex])
ELSE
SET_PED_KEEP_TASK(Race.Racer[iRacerIndex].Driver, TRUE)
ENDIF
ENDIF
ENDIF
ENDIF
ENDPROC
/// PURPOSE:
/// Set the racers once the player completes the race, tasking AI racers,
/// playing animations and dialog as appropriate.
PROC SET_TRI_RACERS_ONCE_RACE_ENDS(TRI_RACE_STRUCT& Race)
IF NOT bIsEndOfRaceRacerAnimSet
INT iRacerCounter
// STRING sAnim
// STRING sAnimDict = "mini@triathlon"
// FLOAT fRand = GET_RANDOM_FLOAT_IN_RANGE()
// IF fRand < 0.33
// sAnim = "idle_d"
// ELIF fRand < 0.66
// sAnim = "idle_e"
// ELSE
// sAnim = "idle_f"
// ENDIF
REPEAT (Race.iRacerCnt) iRacerCounter
IF NOT IS_ENTITY_DEAD(Race.Racer[iRacerCounter].Driver)
IF iRacerCounter <> 0
TASK_AI_RACER_AFTER_PLAYER_FINISHES_RACE(Race, iRacerCounter)
// Set racer to keep his task.
SET_PED_KEEP_TASK(Race.Racer[iRacerCounter].Driver, TRUE)
ENDIF
ENDIF
ENDREPEAT
IF NOT IS_PED_RAGDOLL(PLAYER_PED_ID())
// Play player dialog when he finishes the race.
PLAY_FINISHED_RACE_TRI_PLAYER_DIALOG(Race)
ENDIF
// Start timer for camera showing player's reaction to finishing race.
START_TIMER_NOW_SAFE(timerShowPlayerFinishedRace)
bIsEndOfRaceRacerAnimSet = TRUE
ENDIF
ENDPROC
// ================================================
// E N D AI END OF RACE FUNCTIONS AND PROCESSES
// ================================================
// ==============================================
// TRI AI MAIN FUNCTIONS AND PROCEDURES
// ==============================================
/// PURPOSE:
/// Keep checking the racers' status every frame.
PROC UPDATE_TRI_RACER_AI_STATUS(TRI_RACE_STRUCT& Race, INT iFameCount)
INT iRacerCounter
REPEAT Race.iRacerCnt iRacerCounter
// Keep setting specific on-foot flags to persist for peds in every frame.
IF NOT IS_ENTITY_DEAD(Race.Racer[iRacerCounter].Driver)
IF NOT IS_ENTITY_DEAD(Race.Racer[iRacerCounter].vehicle)
IF NOT (Race.Racer[iRacerCounter].Driver = Race.Racer[0].Driver)
IF NOT IS_PED_IN_VEHICLE(Race.Racer[iRacerCounter].Driver, Race.Racer[iRacerCounter].Vehicle)
SET_PED_RESET_FLAG(Race.Racer[iRacerCounter].Driver, PRF_AllowUpdateIfNoCollisionLoaded, TRUE)
ENDIF
IF IS_ENTITY_IN_WATER(Race.Racer[iRacerCounter].Driver)
SET_PED_INCREASED_AVOIDANCE_RADIUS(Race.Racer[iRacerCounter].Driver)
ENDIF
ENDIF
ENDIF
ENDIF
// Check if the racer has completed the race.
IF HAS_TRI_RACER_FINISHED_RACE(Race, Race.Racer[iRacerCounter]) AND (NOT bIsRacerFinishRaceSequenceSet[iRacerCounter])
TASK_TRI_RACER_FINISHED_RACE_ANIMATION(Race, iRacerCounter)
ELIF NOT IS_TRI_AI_CONTROL_FLAG_SET(Race.Racer[iRacerCounter],TACF_BANIMSPED) AND HAS_TRI_RACER_FINISHED_RACE(Race, Race.Racer[iRacerCounter])
AND GET_SCRIPT_TASK_STATUS(Race.Racer[iRacerCounter].Driver, SCRIPT_TASK_PLAY_ANIM) = PERFORMING_TASK
SET_ANIM_RATE(Race.racer[iRacerCounter].Driver, GET_RANDOM_FLOAT_IN_RANGE(1.0, 2.5))
SET_TRI_AI_CONTROL_FLAG(Race.Racer[iRacerCounter],TACF_BANIMSPED)
ENDIF
ENDREPEAT
// Speed up racers when they're trying to get on a bike.
TASK_TRI_RACERS_TO_SPRINT_TO_BIKES_AFTER_SWIM_LEG(Race, iFameCount)
//When racer's are dismounting their bikes get them to remove their helmets
TASK_TRI_RACERS_TO_REMOVE_HELMETS_AFTER_BIKE_LEG(Race, iFameCount)
// Update racer's current vehicle waypoint recording.
UPDATE_TRI_RACER_IRONMAN_VEHICLE_WAYPOINT_RECORDING(Race, iFameCount)
// Ensure racer doesn't stop participating as a result of their being outside the collision world bounds.
UPDATE_TRI_RACER_SUPERDUMMY_MODE_MANAGER(Race, iFameCount)
ENDPROC
INT iFrameCounter
/// PURPOSE:
/// Update racers' AI.
PROC UPDATE_TRI_AI(TRI_RACE_STRUCT& Race)
// Keep checking the racers' status every frame.
UPDATE_TRI_RACER_AI_STATUS(Race, iFrameCounter)
UPDATE_TRI_RACERS_COMPETE_MODES_AND_LEG_MODIFIERS(Race, iFrameCounter)
IF iFrameCounter % 2 = 0
// Update the racers' speeds in each leg.
UPDATE_TRI_RACERS_SPEEDS(Race)
ENDIF
IF iFrameCounter >= TRI_RACER_MAX-1
iFrameCounter = 0
ELSE
iFrameCounter++
ENDIF
ENDPROC
// ==============================================
// E N D TRI AI MAIN FUNCTIONS AND PROCEDURES
// ==============================================
// ====================================================
// TRI AI SETUP FUNCTIONS AND PROCEDURES
// ====================================================
/// PURPOSE:
/// Set all racers to have never gotten on a bike.
PROC SET_TRI_AI_RACERS_BIKE_MOUNTING_STATUS(TRI_RACE_STRUCT& Race)
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI.sch->SET_AI_RACERS_BIKE_MOUNTING_STATUS] Procedure started.")
INT iRacerCounter
REPEAT Race.iRacerCnt iRacerCounter
IF iRacerCounter <> 0
CLEAR_TRI_AI_CONTROL_FLAG(Race.Racer[iRacerCounter],TACF_BHASBEENONABIKE)
ENDIF
ENDREPEAT
ENDPROC
/// PURPOSE:
/// Store the initial run speeds for the racers, so they can be reset during rubber-banding.
PROC STORE_TRI_AI_RACERS_INITIAL_RUN_SPEEDS(TRI_RACE_STRUCT& Race)
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI.sch->STORE_TRI_AI_RACERS_INITIAL_RUN_SPEEDS] Procedure started.")
INT iRacerCounter
REPEAT Race.iRacerCnt iRacerCounter
fTriOriginalMinAIRacerRunSpeeds[iRacerCounter] = Race.Racer[iRacerCounter].fMinMoveBlendRatioOnFoot
fTriOriginalMaxAIRacerRunSpeeds[iRacerCounter] = Race.Racer[iRacerCounter].fMaxMoveBlendRatioOnFoot
ENDREPEAT
ENDPROC
/// PURPOSE:
/// Store the default TRIBIKE speed to reset bike speeds during rubber-banding.
PROC STORE_TRI_RACE_TRIBIKES_DEFAULT_MAX_SPEED(TRI_RACE_STRUCT& Race)
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI.sch->STORE_TRI_RACE_TRIBIKES_DEFAULT_MAX_SPEED] Procedure started.")
IF NOT IS_ENTITY_DEAD(Race.Racer[0].Vehicle)
Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface = GET_VEHICLE_ESTIMATED_MAX_SPEED(Race.Racer[0].Vehicle)
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI.sch->SET_AI_RACERS_SPEED] Set default tribike max speed to ", Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface,
", using GET_VEHICLE_ESTIMATED_MAX_SPEED.")
ELSE
Tri_Racer_AI_Speed.fDefaultTribikeTopSpeedOnFlatSurface = 18.33
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI.sch->SET_AI_RACERS_SPEED] ERROR: Vehicle 0 is DEAD. Setting default tribike max speed manually to 18.33")
ENDIF
ENDPROC
/// PURPOSE:
/// Store initial values to reset AI racers' speeds during rubber-banding.
PROC STORE_TRI_AI_RACERS_INITIAL_SPEEDS(TRI_RACE_STRUCT& Race)
// Store the initial run speeds for the racers, so they can be reset during rubber-banding.
STORE_TRI_AI_RACERS_INITIAL_RUN_SPEEDS(Race)
// Store the default TRIBIKE max speed to reset bike speeds during rubber-banding.
STORE_TRI_RACE_TRIBIKES_DEFAULT_MAX_SPEED(Race)
ENDPROC
/// PURPOSE:
/// Set how slow or fast racers are allowed to go in each leg of the race.
///
/// NOTE: These are the limits used in the rubber-banding commands (increase/decrease speed)
/// when a racer is in compete mode: TRI_RACER_COMPETE_MODE_DEFAULT, to ensure the
/// player stays at a speed between desired bounds.
///
/// These limits are "delta" because commands that minimize or maximize a racer's
/// speed can alter these limits to severely speed up or slow down a racer
/// depending on his position relative to the player.
PROC SET_TRI_AI_RACERS_RUBBER_BAND_SPEED()
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI.sch->SET_TRI_AI_RACERS_RUBBER_BAND_SPEED] Procedure started.")
// Swim leg speed limits.
Tri_Racer_AI_Speed.fMinDeltaSwimLimit = 1.2
Tri_Racer_AI_Speed.fMaxDeltaSwimLimit = 2.8
// Run leg speed limits.
Tri_Racer_AI_Speed.fMinDeltaRunLimit = 0.5
Tri_Racer_AI_Speed.fMaxDeltaRunLimit = 2.8
// Bike leg speed limits.
Tri_Racer_AI_Speed.fMinDeltaBikeLimit = 9.5
Tri_Racer_AI_Speed.fMaxDeltaBikeLimit = 28.0
ENDPROC
/// PURPOSE:
/// Setup the initial swim speed values.
///
/// NOTE: These speed values will start being incremented
/// or decremented by the compete system as soon
/// as the race starts.
PROC SET_TRI_AI_RACERS_INITIAL_SWIM_SPEEDS(TRI_RACE_STRUCT& Race)
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI.sch->SET_TRI_AI_RACERS_INITIAL_SWIM_SPEEDS] Procedure started.")
INT iRacerCounter
REPEAT Race.iRacerCnt iRacerCounter
IF DOES_ENTITY_EXIST(Race.Racer[iRacerCounter].Driver)
SWITCH(iRacerCounter)
// This is the player, which won't be using these parameters, so just initialize them to 0.
CASE 0
Race.Racer[iRacerCounter].fMinMoveBlendRatioInWater = 0.0
Race.Racer[iRacerCounter].fMaxMoveBlendRatioInWater = 0.0
BREAK
// AI Racer 1
CASE 1
Race.Racer[iRacerCounter].fMinMoveBlendRatioInWater = 1.2
Race.Racer[iRacerCounter].fMaxMoveBlendRatioInWater = 2.3
BREAK
// AI Racer 2
CASE 2
Race.Racer[iRacerCounter].fMinMoveBlendRatioInWater = 1.3
Race.Racer[iRacerCounter].fMaxMoveBlendRatioInWater = 2.2
BREAK
// AI Racer 3
CASE 3
Race.Racer[iRacerCounter].fMinMoveBlendRatioInWater = 1.1
Race.Racer[iRacerCounter].fMaxMoveBlendRatioInWater = 2.5
BREAK
// AI Racer 4
CASE 4
Race.Racer[iRacerCounter].fMinMoveBlendRatioInWater = 2.5
Race.Racer[iRacerCounter].fMaxMoveBlendRatioInWater = 3.0
BREAK
// AI Racer 5
CASE 5
Race.Racer[iRacerCounter].fMinMoveBlendRatioInWater = 1.1
Race.Racer[iRacerCounter].fMaxMoveBlendRatioInWater = 3.0
BREAK
// AI Racer 6
CASE 6
Race.Racer[iRacerCounter].fMinMoveBlendRatioInWater = 1.1
Race.Racer[iRacerCounter].fMaxMoveBlendRatioInWater = 2.1
BREAK
// AI Racer 7
CASE 7
Race.Racer[iRacerCounter].fMinMoveBlendRatioInWater = 1.1
Race.Racer[iRacerCounter].fMaxMoveBlendRatioOnFoot = 2.3
BREAK
// In the unlikely evet that we have more racers than 12, initialize them all to
// avoid array overruns. However, their values should be set individually if this ever happens!
DEFAULT
Race.Racer[iRacerCounter].fMinMoveBlendRatioInWater = 1.3
Race.Racer[iRacerCounter].fMaxMoveBlendRatioInWater = 2.5
BREAK
ENDSWITCH
ENDIF
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI.sch->SET_TRI_AI_RACERS_INITIAL_RUN_SPEEDS] Racer #", iRacerCounter, "'s minimum swim speed set to :",
Race.Racer[iRacerCounter].fMinMoveBlendRatioInWater, ", and his maximum swim speed set to: ", Race.Racer[iRacerCounter].fMaxMoveBlendRatioInWater)
ENDREPEAT
ENDPROC
/// PURPOSE:
/// Setup the initial bike speed values.
///
/// NOTE: These speed values will start being incremented
/// or decremented by the compete system as soon
/// as the race starts.
///
/// In the case of bikes, since speed varies wildly
/// compared to swim and run legs, we can set
/// all bikes to start at the same speed.
PROC SET_TRI_AI_RACERS_INITIAL_BIKE_SPEEDS(TRI_RACE_STRUCT& Race)
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI.sch->SET_TRI_AI_RACERS_INITIAL_BIKE_SPEEDS] Procedure started.")
INT iRacerCounter
REPEAT Race.iRacerCnt iRacerCounter
Race.Racer[iRacerCounter].fMinBikeSpeed = 18.32
Race.Racer[iRacerCounter].fMaxBikeSpeed = 18.32
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI.sch->SET_TRI_AI_RACERS_INITIAL_BIKE_SPEEDS] Racer #", iRacerCounter, "'s minimum bike speed set to :",
Race.Racer[iRacerCounter].fMinBikeSpeed, ", and his maximum bike speed set to: ", Race.Racer[iRacerCounter].fMaxBikeSpeed)
ENDREPEAT
ENDPROC
/// PURPOSE:
/// Setup the initial run speed values.
///
/// NOTE: These speed values will start being incremented
/// or decremented by the compete system as soon
/// as the race starts.
PROC SET_TRI_AI_RACERS_INITIAL_RUN_SPEEDS(TRI_RACE_STRUCT& Race)
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI.sch->SET_TRI_AI_RACERS_INITIAL_RUN_SPEEDS] Procedure started.")
INT iRacerCounter
REPEAT Race.iRacerCnt iRacerCounter
IF DOES_ENTITY_EXIST(Race.Racer[iRacerCounter].Driver)
SWITCH(iRacerCounter)
// This is the player, which won't be using these parameters, so just initialize them to 0.
CASE 0
Race.Racer[iRacerCounter].fMinMoveBlendRatioOnFoot = 0.0
Race.Racer[iRacerCounter].fMaxMoveBlendRatioOnFoot = 0.0
BREAK
// AI Racer 1
CASE 1
Race.Racer[iRacerCounter].fMinMoveBlendRatioOnFoot = 2.0
Race.Racer[iRacerCounter].fMaxMoveBlendRatioOnFoot = 3.0
BREAK
// AI Racer 2
CASE 2
Race.Racer[iRacerCounter].fMinMoveBlendRatioOnFoot = 2.0
Race.Racer[iRacerCounter].fMaxMoveBlendRatioOnFoot = 2.7
BREAK
// AI Racer 3
CASE 3
Race.Racer[iRacerCounter].fMinMoveBlendRatioOnFoot = 2.0
Race.Racer[iRacerCounter].fMaxMoveBlendRatioOnFoot = 3.0
BREAK
// AI Racer 4
CASE 4
Race.Racer[iRacerCounter].fMinMoveBlendRatioOnFoot = 2.5
Race.Racer[iRacerCounter].fMaxMoveBlendRatioOnFoot = 3.0
BREAK
// AI Racer 5
CASE 5
Race.Racer[iRacerCounter].fMinMoveBlendRatioOnFoot = 2.2
Race.Racer[iRacerCounter].fMaxMoveBlendRatioOnFoot = 2.7
BREAK
// AI Racer 6
CASE 6
Race.Racer[iRacerCounter].fMinMoveBlendRatioOnFoot = 2.0
Race.Racer[iRacerCounter].fMaxMoveBlendRatioOnFoot = 3.0
BREAK
// AI Racer 7
CASE 7
Race.Racer[iRacerCounter].fMinMoveBlendRatioOnFoot = 2.0
Race.Racer[iRacerCounter].fMaxMoveBlendRatioOnFoot = 3.0
BREAK
// In the unlikely evet that we have more racers than 12, initialize them all to
// avoid array overruns. However, their values should be set individually if this ever happens!
DEFAULT
Race.Racer[iRacerCounter].fMinMoveBlendRatioOnFoot = 2.0
Race.Racer[iRacerCounter].fMaxMoveBlendRatioOnFoot = 3.0
BREAK
ENDSWITCH
ENDIF
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI.sch->SET_TRI_AI_RACERS_INITIAL_RUN_SPEEDS] Racer #", iRacerCounter, "'s minimum run speed set to :",
Race.Racer[iRacerCounter].fMinMoveBlendRatioOnFoot, ", and his maximum run speed set to: ", Race.Racer[iRacerCounter].fMaxMoveBlendRatioOnFoot)
ENDREPEAT
ENDPROC
/// PURPOSE:
/// Set any variables that control the speed of an AI racer at any given time.
PROC SET_TRI_AI_RACERS_INITIAL_SPEED(TRI_RACE_STRUCT& Race)
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI.sch->SET_TRI_AI_RACERS_INITIAL_SPEED] Procedure started.")
SET_TRI_AI_RACERS_INITIAL_SWIM_SPEEDS(Race)
SET_TRI_AI_RACERS_INITIAL_BIKE_SPEEDS(Race)
SET_TRI_AI_RACERS_INITIAL_RUN_SPEEDS(Race)
ENDPROC
/// PURPOSE:
/// Set any variables that control the speed of an AI racer at any given time.
PROC SET_TRI_AI_RACERS_SPEED(TRI_RACE_STRUCT& Race)
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI.sch->SET_AI_RACERS_SPEED] Procedure started.")
// Set the initial speed values the AI racer will start a leg with.
SET_TRI_AI_RACERS_INITIAL_SPEED(Race)
// Set how slow or fast racers are allowed to go in each leg of the race.
SET_TRI_AI_RACERS_RUBBER_BAND_SPEED()
// Store initial values to reset AI racers' speeds during rubber-banding.
STORE_TRI_AI_RACERS_INITIAL_SPEEDS(Race)
ENDPROC
/// PURPOSE:
/// Initialize the tracking value of racers' task sequences.
PROC SET_TRI_AI_RACERS_TASK_SEQUENCES_TRACKING(TRI_RACE_STRUCT& Race)
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI->SET_TRI_AI_RACERS_TASK_SEQUENCES_TRACKING] Procedure started.")
INT iRacerCounter
REPEAT (Race.iRacerCnt) iRacerCounter
IF iRacerCounter <> 0
bIsRacerFinishRaceSequenceSet[iRacerCounter] = FALSE
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI->SET_TRI_AI_RACERS_TASK_SEQUENCES_TRACKING] Set racer #", iRacerCounter,"'s task sequence.")
ENDIF
ENDREPEAT
ENDPROC
/// PURPOSE:
/// Set the racers to a compete mode at the start of the race.
PROC SET_TRI_AI_RACERS_INITIAL_COMPETE_MODE(TRI_RACE_STRUCT& Race)
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI.sch->SET_TRI_AI_RACERS_INITIAL_COMPETE_MODE] Procedure started.")
INT iRacerCounter
REPEAT Race.iRacerCnt iRacerCounter
Race.Racer[iRacerCounter].eCompeteMode = TRI_RACER_COMPETE_MODE_DEFAULT
START_TIMER_AT(Race.Racer[iRacerCounter].timerInCurrentCompeteMode, 15.0)
ENDREPEAT
ENDPROC
/// PURPOSE:
/// Set the limits racers can be at a certain compete mode.
PROC SET_TRI_AI_RACERS_COMPETE_MODE_LIMITS()
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI.sch->SET_TRI_AI_RACERS_COMPETE_MODE_LIMITS] Procedure started.")
// Initialize the compete mode racer limits. These are set every race to a random value within a narrow range to vary races at every attempt.
iLimitOfAggressiveRacers = GET_RANDOM_INT_IN_RANGE(1, 3)
iLimitOfTiredRacers = GET_RANDOM_INT_IN_RANGE(1, 3)
iLimitOfForcedTiredRacers = GET_RANDOM_INT_IN_RANGE(2, 5)
// Initialize the counters that track how many racers are in specific competitive states.
iRacersInAggressiveMode = 0
iRacersInTiredMode = 0
iRacersInForcedTiredMode = 0
ENDPROC
/// PURPOSE:
/// Set the racers to be immune to water damage while in the race.
PROC SET_TRI_AI_RACERS_WATER_RESISTANCE(TRI_RACE_STRUCT& Race)
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI.sch->SET_TRI_AI_RACERS_WATER_RESISTANCE] Procedure started.")
INT iRacerCounter
REPEAT Race.iRacerCnt iRacerCounter
IF NOT IS_ENTITY_DEAD(Race.Racer[iRacerCounter].Driver)
IF iRacerCounter <> 0
SET_PED_DIES_IN_WATER(Race.Racer[iRacerCounter].Driver, FALSE)
ENDIF
SET_PED_DIES_INSTANTLY_IN_WATER(Race.Racer[iRacerCounter].Driver, FALSE)
SET_PED_MAX_TIME_IN_WATER(Race.Racer[iRacerCounter].Driver, 600.0)
//SET_PED_PATH_PREFER_TO_AVOID_WATER(Race.Racer[iRacerCounter].Driver, FALSE)
ENDIF
ENDREPEAT
ENDPROC
/// PURPOSE:
/// Assign the vehicle waypoints recordings used by racers.
PROC SET_TRI_RACERS_VEHICLE_WAYPOINT_RECORDINGS(TRI_RACE_STRUCT& Race)
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI.sch->SET_TRI_RACERS_VEHICLE_WAYPOINT_RECORDINGS] Procedure started.")
INT iRacerCounter
REPEAT Race.iRacerCnt iRacerCounter
SWITCH (eCurrentTriRace)
CASE TRIATHLON_RACE_ALAMO_SEA
Race.Racer[iRacerCounter].szCurrentBikeRecordingName = "Tri1_Bk_0"
BREAK
CASE TRIATHLON_RACE_VESPUCCI
Race.Racer[iRacerCounter].szCurrentBikeRecordingName = "Tri2_Bk_0"
BREAK
CASE TRIATHLON_RACE_IRONMAN
Race.Racer[iRacerCounter].szCurrentBikeRecordingName = szTriBikeRecordingName_IronMan_0
// In the case of the player, he only needs to follow the last vehicle recording in Ironman.
IF iRacerCounter = 0
Race.Racer[0].szCurrentBikeRecordingName = szTriBikeRecordingName_IronMan_4
ENDIF
BREAK
ENDSWITCH
ENDREPEAT
ENDPROC
/// PURPOSE:
/// Prevent racers from interrupting script commands.
PROC SET_TRI_RACERS_UNDER_COMPLETE_SCRIPT_CONTROL(TRI_RACE_STRUCT& Race)
INT iRacerCounter
REPEAT Race.iRacerCnt iRacerCounter
IF NOT IS_ENTITY_DEAD(Race.Racer[iRacerCounter].Driver)
SET_BLOCKING_OF_NON_TEMPORARY_EVENTS(Race.Racer[iRacerCounter].Driver, TRUE)
ENDIF
ENDREPEAT
ENDPROC
/// PURPOSE:
/// Set up the racers's AI.
PROC SETUP_TRI_AI(TRI_RACE_STRUCT& Race)
CPRINTLN(DEBUG_TRIATHLON, "[TRI_Triathlon_AI.sch->SETUP_TRI_AI] Procedure started.")
// Prevent racers from interrupting script commands.
SET_TRI_RACERS_UNDER_COMPLETE_SCRIPT_CONTROL(Race)
// Assign the vehicle waypoints recordings used by racers.
SET_TRI_RACERS_VEHICLE_WAYPOINT_RECORDINGS(Race)
// Assign the vehicle waypoints recordings used by racers.
SET_TRI_AI_RACERS_WATER_RESISTANCE(Race)
// Set the limits racers can be at a certain compete mode.
SET_TRI_AI_RACERS_COMPETE_MODE_LIMITS()
// Set the racers to a compete mode at the start of the race.
SET_TRI_AI_RACERS_INITIAL_COMPETE_MODE(Race)
// Initialize the tracking value of racers' task sequences.
SET_TRI_AI_RACERS_TASK_SEQUENCES_TRACKING(Race)
// Set any variables that control the speed of an AI racer at any given time.
SET_TRI_AI_RACERS_SPEED(Race)
// Set all racers to not having gotten in a bike.
SET_TRI_AI_RACERS_BIKE_MOUNTING_STATUS(Race)
ENDPROC
PROC UPDATE_TRI_SCENARIO_GROUP()
SET_EXCLUSIVE_SCENARIO_GROUP(szExlusiveScenarioGroup)
ENDPROC
// ====================================================
// E N D TRI AI SETUP FUNCTIONS AND PROCEDURES
// ====================================================
// *****************************************************************************************
// *****************************************************************************************
// *****************************************************************************************
//
// END OF FILE - DO NOT ADD ANYTHING BELOW THIS BLOCK!
//
// *****************************************************************************************
// *****************************************************************************************
// *****************************************************************************************