3030 lines
127 KiB
XML
Executable File
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!
|
|
//
|
|
// *****************************************************************************************
|
|
// *****************************************************************************************
|
|
// *****************************************************************************************
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|