// ***************************************************************************************** // ***************************************************************************************** // ***************************************************************************************** // // 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! // // ***************************************************************************************** // ***************************************************************************************** // *****************************************************************************************