// AI_character.cpp // Native state-machine replacement for ai_character.script. #include "precompiled.h" #pragma hdrstop #include "../Game_local.h" namespace { const float CHAR_WALKTURN = 65.0f; const float CHAR_MIN_TURN = 10.0f; const float CHAR_TURN_THRESHOLD = 45.0f; const float CHAR_BLEND_COWER_TO_IDLE = 6.0f; const float CHAR_BLEND_TURN_TO_IDLE = 0.0f; } rvmCharacter::rvmCharacter() { talkMode = CHAR_TALK_PRIMARY; talkSecondaryIndex = 1; customBlendOut = 0.0f; allowTurn = false; run = false; canTalk = false; noCower = false; noCowerSaved = false; ignorePush = false; skipOutOfPath = false; followNodes = true; dontPushOthers = false; lastPushTime = 0.0f; pushTime = 0.0f; talkEndTime = 0.0f; talkChannel = ANIMCHANNEL_LEGS; talkOriginalYaw = 0.0f; } void rvmCharacter::Init( idAI* aiOwner ) { rvmAIObject::Init( aiOwner ); if ( owner ) { owner->SetJointMod( GetIntKey( "head_look" ) ); owner->spawnArgs.Set( "conversationNext", "" ); } lastPushTime = 0.0f; pushTime = 0.0f; talkMode = CHAR_TALK_PRIMARY; talkSecondaryIndex = 1; followNodes = true; walkAnim.Clear(); run = false; canTalk = GetIntKey( "talks" ) != 0; noCower = GetIntKey( "no_cower" ) != 0; ignorePush = GetIntKey( "ignore_push" ) != 0; noCowerSaved = noCower; dontPushOthers = GetIntKey( "dont_push_others" ) != 0; allowTurn = GetFloatKey( "turn_rate" ) != 0.0f; if ( !allowTurn ) { ignorePush = true; } SetState( GetIntKey( "spawner" ) ? "state_Spawner" : "state_Begin" ); } void rvmCharacter::Destroy() { if ( listening_to_character.GetEntity() && listening_to_character.GetEntity()->IsType( idAI::Type ) ) { idAI* listeningAI = static_cast( listening_to_character.GetEntity() ); rvmCharacter* conversation = listeningAI->nativeAIObject ? listeningAI->nativeAIObject->Cast() : NULL; while ( conversation ) { if ( conversation->next_listener.GetEntity() == owner ) { conversation->next_listener = next_listener; break; } idAI* nextAI = conversation->next_listener.GetEntity() && conversation->next_listener.GetEntity()->IsType( idAI::Type ) ? static_cast( conversation->next_listener.GetEntity() ) : NULL; conversation = nextAI && nextAI->nativeAIObject ? nextAI->nativeAIObject->Cast() : NULL; } } rvmAIObject::Destroy(); } stateResult_t rvmCharacter::RunStateByName( const char* stateName, stateParms_t* parms ) { AI_DISPATCH_STATE( "Anim_Disable", Anim_Disable ); AI_DISPATCH_STATE( "Head_Idle", Head_Idle ); AI_DISPATCH_STATE( "Head_Dead", Head_Dead ); AI_DISPATCH_STATE( "Head_TalkHeadOnly", Head_TalkHeadOnly ); AI_DISPATCH_STATE( "Legs_Death", Legs_Death ); AI_DISPATCH_STATE( "Legs_Idle", Legs_Idle ); AI_DISPATCH_STATE( "Legs_Walk", Legs_Walk ); AI_DISPATCH_STATE( "Legs_Walk_Special", Legs_Walk_Special ); AI_DISPATCH_STATE( "Legs_Run", Legs_Run ); AI_DISPATCH_STATE( "Legs_TurnLeft", Legs_TurnLeft ); AI_DISPATCH_STATE( "Legs_TurnRight", Legs_TurnRight ); AI_DISPATCH_STATE( "Legs_Cower", Legs_Cower ); AI_DISPATCH_STATE( "Legs_CustomCycle", Legs_CustomCycle ); AI_DISPATCH_STATE( "Legs_CustomAnim", Legs_CustomAnim ); AI_DISPATCH_STATE( "state_Spawner", state_Spawner ); AI_DISPATCH_STATE( "state_Cower", state_Cower ); AI_DISPATCH_STATE( "state_Begin", state_Begin ); AI_DISPATCH_STATE( "state_TalkTrigger", state_TalkTrigger ); AI_DISPATCH_STATE( "state_Idle", state_Idle ); AI_DISPATCH_STATE( "state_Killed", state_Killed ); AI_DISPATCH_STATE( "idle_stand", idle_stand ); AI_DISPATCH_STATE( "idle_followPathEntities", idle_followPathEntities ); AI_DISPATCH_STATE( "idle_talk", idle_talk ); AI_DISPATCH_STATE( "checkBlocked", checkBlocked ); AI_DISPATCH_STATE( "path_corner", path_corner ); AI_DISPATCH_STATE( "path_anim", path_anim ); AI_DISPATCH_STATE( "path_cycleanim", path_cycleanim ); AI_DISPATCH_STATE( "path_turn", path_turn ); AI_DISPATCH_STATE( "path_wait", path_wait ); AI_DISPATCH_STATE( "path_waitfortrigger", path_waitfortrigger ); AI_DISPATCH_STATE( "path_hide", path_hide ); AI_DISPATCH_STATE( "path_show", path_show ); AI_DISPATCH_STATE( "path_conversation_listen", path_conversation_listen ); AI_DISPATCH_STATE( "path_conversation", path_conversation ); AI_DISPATCH_STATE( "path_headanim", path_headanim ); AI_DISPATCH_STATE( "path_waitforheadanim", path_waitforheadanim ); AI_DISPATCH_STATE( "path_talk", path_talk ); AI_DISPATCH_STATE( "path_talk_triggered", path_talk_triggered ); AI_DISPATCH_STATE( "path_talk_primary", path_talk_primary ); AI_DISPATCH_STATE( "path_talk_secondary", path_talk_secondary ); AI_DISPATCH_STATE( "path_lookat", path_lookat ); AI_DISPATCH_STATE( "state_FollowAlternatePath", state_FollowAlternatePath ); return rvmAIObject::RunStateByName( stateName, parms ); } stateResult_t rvmCharacter::Anim_Disable( stateParms_t* parms ) { return SRESULT_WAIT; } stateResult_t rvmCharacter::Head_Idle( stateParms_t* parms ) { if ( parms->stage == 0 ) { IdleAnim( ANIMCHANNEL_HEAD, "stand" ); parms->stage = 1; } return SRESULT_WAIT; } stateResult_t rvmCharacter::Head_Dead( stateParms_t* parms ) { if ( parms->stage == 0 ) { PlayAnim( ANIMCHANNEL_HEAD, "dead" ); parms->stage = 1; } return SRESULT_WAIT; } stateResult_t rvmCharacter::Head_TalkHeadOnly( stateParms_t* parms ) { if ( parms->stage == 0 ) { PlayAnim( ANIMCHANNEL_HEAD, customAnim.c_str() ); parms->stage = 1; } if ( AnimDone( ANIMCHANNEL_HEAD, idMath::Ftoi( customBlendOut ) ) ) { FinishAction( "talkAnim" ); SetAnimState( ANIMCHANNEL_HEAD, "Head_Idle", idMath::Ftoi( customBlendOut ) ); return SRESULT_DONE; } return SRESULT_WAIT; } stateResult_t rvmCharacter::Legs_Death( stateParms_t* parms ) { FinishAction( "dead" ); return SRESULT_WAIT; } stateResult_t rvmCharacter::Legs_Idle( stateParms_t* parms ) { if ( parms->stage == 0 ) { AllowMovement( false ); IdleAnim( ANIMCHANNEL_LEGS, "stand" ); parms->stage = 1; } if ( MovingForward() ) { if ( walkAnim.Length() ) { SetAnimState( ANIMCHANNEL_LEGS, "Legs_Walk_Special", 8 ); } else if ( run ) { SetAnimState( ANIMCHANNEL_LEGS, "Legs_Run", 8 ); } else { SetAnimState( ANIMCHANNEL_LEGS, "Legs_Walk", 8 ); } return SRESULT_DONE; } if ( allowTurn ) { const float delta = GetTurnDelta(); if ( delta > CHAR_MIN_TURN ) { SetAnimState( ANIMCHANNEL_LEGS, "Legs_TurnLeft", 4 ); return SRESULT_DONE; } if ( delta < -CHAR_MIN_TURN ) { SetAnimState( ANIMCHANNEL_LEGS, "Legs_TurnRight", 4 ); return SRESULT_DONE; } } return SRESULT_WAIT; } stateResult_t rvmCharacter::Legs_Walk( stateParms_t* parms ) { if ( parms->stage == 0 ) { AllowMovement( true ); PlayCycle( ANIMCHANNEL_LEGS, "walk" ); parms->stage = 1; } if ( !MovingForward() || walkAnim.Length() ) { SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 8 ); return SRESULT_DONE; } if ( run ) { const float delta = GetTurnDelta(); if ( delta < CHAR_WALKTURN && delta > -CHAR_WALKTURN ) { SetAnimState( ANIMCHANNEL_LEGS, "Legs_Run", 8 ); return SRESULT_DONE; } } return SRESULT_WAIT; } stateResult_t rvmCharacter::Legs_Walk_Special( stateParms_t* parms ) { if ( parms->stage == 0 ) { AllowMovement( true ); PlayCycle( ANIMCHANNEL_LEGS, walkAnim.c_str() ); parms->stage = 1; } if ( !MovingForward() || !walkAnim.Length() ) { SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 8 ); return SRESULT_DONE; } return SRESULT_WAIT; } stateResult_t rvmCharacter::Legs_Run( stateParms_t* parms ) { if ( parms->stage == 0 ) { AllowMovement( true ); PlayCycle( ANIMCHANNEL_LEGS, "run" ); parms->stage = 1; } const float delta = GetTurnDelta(); if ( !MovingForward() || !run || walkAnim.Length() ) { SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 8 ); return SRESULT_DONE; } if ( delta > CHAR_WALKTURN || delta < -CHAR_WALKTURN ) { SetAnimState( ANIMCHANNEL_LEGS, "Legs_Walk", 8 ); return SRESULT_DONE; } return SRESULT_WAIT; } stateResult_t rvmCharacter::Legs_TurnLeft( stateParms_t* parms ) { if ( parms->stage == 0 ) { AllowMovement( true ); if ( owner ) { owner->AnimTurn( 180.0f ); } PlayAnim( ANIMCHANNEL_LEGS, "turn_left" ); parms->stage = 1; } if ( !AnimDone( ANIMCHANNEL_LEGS, idMath::Ftoi( CHAR_BLEND_TURN_TO_IDLE ) ) ) { return SRESULT_WAIT; } if ( owner ) { owner->AnimTurn( 0.0f ); } SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", idMath::Ftoi( CHAR_BLEND_TURN_TO_IDLE ) ); return SRESULT_DONE; } stateResult_t rvmCharacter::Legs_TurnRight( stateParms_t* parms ) { if ( parms->stage == 0 ) { AllowMovement( true ); if ( owner ) { owner->AnimTurn( 180.0f ); } PlayAnim( ANIMCHANNEL_LEGS, "turn_right" ); parms->stage = 1; } if ( !AnimDone( ANIMCHANNEL_LEGS, idMath::Ftoi( CHAR_BLEND_TURN_TO_IDLE ) ) ) { return SRESULT_WAIT; } if ( owner ) { owner->AnimTurn( 0.0f ); } SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", idMath::Ftoi( CHAR_BLEND_TURN_TO_IDLE ) ); return SRESULT_DONE; } stateResult_t rvmCharacter::Legs_Cower( stateParms_t* parms ) { if ( parms->stage == 0 ) { if ( owner ) { owner->SetTalkTarget( NULL ); } StartSoundKey( "snd_cower", SND_CHANNEL_VOICE, false ); PlayAnim( ANIMCHANNEL_LEGS, "cower" ); parms->stage = 1; } LookAtEntity( Player(), 0.1f ); if ( AnimDone( ANIMCHANNEL_LEGS, idMath::Ftoi( CHAR_BLEND_COWER_TO_IDLE ) ) ) { if ( owner ) { owner->SetTalkTarget( NULL ); } SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", idMath::Ftoi( CHAR_BLEND_COWER_TO_IDLE ) ); return SRESULT_DONE; } return SRESULT_WAIT; } stateResult_t rvmCharacter::Legs_CustomCycle( stateParms_t* parms ) { if ( parms->stage == 0 ) { AllowMovement( true ); PlayCycle( ANIMCHANNEL_LEGS, customAnim.c_str() ); parms->stage = 1; } return SRESULT_WAIT; } stateResult_t rvmCharacter::Legs_CustomAnim( stateParms_t* parms ) { if ( parms->stage == 0 ) { AllowMovement( true ); PlayAnim( ANIMCHANNEL_LEGS, customAnim.c_str() ); parms->stage = 1; } if ( AnimDone( ANIMCHANNEL_LEGS, idMath::Ftoi( customBlendOut ) ) ) { FinishAction( "customAnim" ); SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", idMath::Ftoi( customBlendOut ) ); return SRESULT_DONE; } return SRESULT_WAIT; } stateResult_t rvmCharacter::state_Spawner( stateParms_t* parms ) { if ( parms->stage == 0 ) { HideOwner(); ClearActivated(); parms->stage = 1; } if ( Activated() && CanBecomeSolid() ) { ClearActivated(); if ( owner ) { idDict args; args = owner->spawnArgs; args.SetInt( "spawner", 0 ); gameLocal.SpawnEntityDef( args ); } } return SRESULT_WAIT; } stateResult_t rvmCharacter::state_Cower( stateParms_t* parms ) { if ( parms->stage == 0 ) { if ( owner ) { owner->StopSound( SND_CHANNEL_VOICE, false ); owner->SetTalkTarget( NULL ); } endHeadAnim( 6.0f ); SetAnimState( ANIMCHANNEL_LEGS, "Legs_Cower", 6 ); parms->stage = 1; } if ( InAnimState( ANIMCHANNEL_LEGS, "Legs_Cower" ) ) { return SRESULT_WAIT; } if ( owner ) { owner->SetTalkTarget( NULL ); } SetState( "state_Idle" ); return SRESULT_DONE; } stateResult_t rvmCharacter::state_Begin( stateParms_t* parms ) { enum { STAGE_INIT, STAGE_WAIT_TRIGGER, STAGE_WAIT_SOLID, STAGE_TRIGGER_ANIM, STAGE_TELEPORT_FX, STAGE_TELEPORT_ANIM, STAGE_POST_WAIT, STAGE_WAIT_EXPLICIT_TRIGGER }; switch ( parms->stage ) { case STAGE_INIT: { talkMode = CHAR_TALK_PRIMARY; followNodes = true; walkAnim.Clear(); run = false; SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); SetAnimState( ANIMCHANNEL_HEAD, "Head_Idle", 0 ); const char* triggerAnim = GetKey( "trigger_anim" ); if ( triggerAnim[0] ) { customAnim = triggerAnim; customBlendOut = 4.0f; HideOwner(); ClearActivated(); parms->stage = STAGE_WAIT_TRIGGER; return SRESULT_WAIT; } const int teleportType = GetIntKey( "teleport" ); if ( teleportType > 0 ) { HideOwner(); ClearActivated(); parms->stage = STAGE_WAIT_TRIGGER; return SRESULT_WAIT; } if ( GetIntKey( "hide" ) ) { HideOwner(); ClearActivated(); parms->stage = STAGE_WAIT_TRIGGER; return SRESULT_WAIT; } if ( GetIntKey( "trigger" ) ) { ClearActivated(); parms->stage = STAGE_WAIT_EXPLICIT_TRIGGER; return SRESULT_WAIT; } scratchEndTime = GetFloatKey( "wait" ) > 0.0f ? DelayTime( GetFloatKey( "wait" ) ) : 0.0f; parms->stage = STAGE_POST_WAIT; return SRESULT_WAIT; } case STAGE_WAIT_TRIGGER: if ( !Activated() ) { return SRESULT_WAIT; } ClearActivated(); ClearEnemy(); parms->stage = STAGE_WAIT_SOLID; return SRESULT_WAIT; case STAGE_WAIT_SOLID: if ( !CanBecomeSolid() ) { return SRESULT_WAIT; } if ( GetIntKey( "teleport" ) > 0 ) { BecomeSolid(); SetMoveType( MOVETYPE_STATIC ); const int teleportType = GetIntKey( "teleport" ); StartFx( teleportType == 1 ? "fx/teleporter1.fx" : teleportType == 2 ? "fx/teleporter2.fx" : teleportType == 3 ? "fx/teleporter3.fx" : "fx/teleporter.fx" ); scratchEndTime = DelayTime( teleportType == 1 ? 1.6f : teleportType == 2 ? 2.6f : teleportType == 3 ? 3.6f : 0.6f ); parms->stage = STAGE_TELEPORT_FX; return SRESULT_WAIT; } ShowOwner(); if ( customAnim.Length() ) { waitForCustomAnim( customAnim.c_str(), 0.0f, 4.0f ); parms->stage = STAGE_TRIGGER_ANIM; return SRESULT_WAIT; } scratchEndTime = GetFloatKey( "wait" ) > 0.0f ? DelayTime( GetFloatKey( "wait" ) ) : 0.0f; parms->stage = STAGE_POST_WAIT; return SRESULT_WAIT; case STAGE_TRIGGER_ANIM: if ( InAnimState( ANIMCHANNEL_LEGS, "Legs_CustomAnim" ) ) { return SRESULT_WAIT; } scratchEndTime = GetFloatKey( "wait" ) > 0.0f ? DelayTime( GetFloatKey( "wait" ) ) : 0.0f; parms->stage = STAGE_POST_WAIT; return SRESULT_WAIT; case STAGE_TELEPORT_FX: if ( Time() < scratchEndTime ) { return SRESULT_WAIT; } ShowOwner(); playCustomAnim( "teleport", 0.0f, 4.0f ); parms->stage = STAGE_TELEPORT_ANIM; return SRESULT_WAIT; case STAGE_TELEPORT_ANIM: if ( InAnimState( ANIMCHANNEL_LEGS, "Legs_CustomAnim" ) ) { return SRESULT_WAIT; } SetMoveType( MOVETYPE_ANIM ); scratchEndTime = GetFloatKey( "wait" ) > 0.0f ? DelayTime( GetFloatKey( "wait" ) ) : 0.0f; parms->stage = STAGE_POST_WAIT; return SRESULT_WAIT; case STAGE_WAIT_EXPLICIT_TRIGGER: if ( !Activated() ) { return SRESULT_WAIT; } ClearActivated(); scratchEndTime = GetFloatKey( "wait" ) > 0.0f ? DelayTime( GetFloatKey( "wait" ) ) : 0.0f; parms->stage = STAGE_POST_WAIT; return SRESULT_WAIT; case STAGE_POST_WAIT: if ( scratchEndTime > 0.0f && Time() < scratchEndTime ) { return SRESULT_WAIT; } SetState( GetIntKey( "talktrigger" ) ? "state_TalkTrigger" : "state_Idle" ); return SRESULT_DONE; } return SRESULT_ERROR; } stateResult_t rvmCharacter::state_TalkTrigger( stateParms_t* parms ) { if ( parms->stage == 0 ) { const char* animname = GetKey( "anim" ); if ( animname[0] ) { playCustomCycle( animname, 0.0f ); } ClearActivated(); parms->stage = 1; } check_cower(); if ( !Activated() ) { return SRESULT_WAIT; } SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 12 ); if ( owner ) { owner->SetTalkTarget( Player() ); } talkMode = CHAR_TALK_TRIGGERED; SetState( "state_Idle" ); return SRESULT_DONE; } stateResult_t rvmCharacter::state_Idle( stateParms_t* parms ) { if ( parms->stage == 0 ) { if ( canTalk && owner ) { owner->SetTalkState( TALK_OK ); } noCower = noCowerSaved; parms->stage = 1; } if ( owner && owner->AI_TALK ) { StartAction( "idle_talk" ); return SRESULT_WAIT; } if ( followNodes ) { if ( !current_path.GetEntity() ) { current_path = owner ? owner->RandomPath() : NULL; } if ( current_path.GetEntity() ) { executePathCommand( current_path.GetEntity() ); if ( GetIntKey( "follow_once" ) ) { followNodes = false; } return SRESULT_WAIT; } } check_cower(); if ( owner && owner->AI_PUSHED ) { getOutOfWay(); owner->AI_PUSHED = false; } StopMove(); return SRESULT_WAIT; } stateResult_t rvmCharacter::state_Killed( stateParms_t* parms ) { StopMove(); SetAnimState( ANIMCHANNEL_LEGS, "Legs_Death", 0 ); SetAnimState( ANIMCHANNEL_HEAD, "Head_Dead", 0 ); StopThinking(); return SRESULT_DONE; } stateResult_t rvmCharacter::idle_stand( stateParms_t* parms ) { StopMove(); check_cower(); if ( owner && owner->AI_PUSHED ) { getOutOfWay(); owner->AI_PUSHED = false; } return ( owner && owner->AI_TALK ) ? SRESULT_DONE : SRESULT_WAIT; } stateResult_t rvmCharacter::idle_followPathEntities( stateParms_t* parms ) { if ( !current_path.GetEntity() ) { current_path = owner ? owner->RandomPath() : NULL; } if ( current_path.GetEntity() ) { executePathCommand( current_path.GetEntity() ); } return SRESULT_DONE; } stateResult_t rvmCharacter::idle_talk( stateParms_t* parms ) { enum { STAGE_START, STAGE_TURN_WAIT, STAGE_PLAY, STAGE_TALKING, STAGE_AFTER_TALK }; idEntity* talkTo = owner ? owner->GetTalkTarget() : NULL; if ( !talkTo ) { if ( owner ) { owner->SetTalkTarget( NULL ); } return SRESULT_DONE; } const bool noTurn = GetIntKey( "talk_no_turn" ) != 0; const float talkRadius = GetFloatKey( "talkradius", 300.0f ); switch ( parms->stage ) { case STAGE_START: StopMove(); talkOriginalYaw = owner ? owner->GetAngles().y : 0.0f; if ( !noTurn && allowTurn ) { faceTowardsEntity( talkTo, 0.1f ); scratchEndTime = DelayTime( 0.5f ); parms->stage = STAGE_TURN_WAIT; return SRESULT_WAIT; } parms->stage = STAGE_PLAY; return SRESULT_WAIT; case STAGE_TURN_WAIT: LookAtEntity( talkTo, 0.1f ); if ( Time() < scratchEndTime || !InAnimState( ANIMCHANNEL_LEGS, "Legs_Idle" ) ) { if ( talkMode != CHAR_TALK_TRIGGERED ) { check_cower(); } return SRESULT_WAIT; } parms->stage = STAGE_PLAY; return SRESULT_WAIT; case STAGE_PLAY: { AllowMovement( true ); int blendIn = 8; if ( talkMode == CHAR_TALK_TRIGGERED ) { blendIn = GetIntKey( "talk0_blendin", 8 ); customBlendOut = GetIntKey( "talk0_blendout", 8 ); talkChannel = playHeadAnim( "talk_trigger", blendIn ); talkMode = CHAR_TALK_PRIMARY; } else if ( talkMode == CHAR_TALK_PRIMARY ) { blendIn = GetIntKey( "talk1_blendin", 8 ); customBlendOut = GetIntKey( "talk1_blendout", 8 ); talkChannel = playHeadAnim( "talk_primary", blendIn ); talkMode = CHAR_TALK_SECONDARY; } else { idStr anim = va( "talk_secondary%d", talkSecondaryIndex ); blendIn = GetIntKey( "talk2_blendin", 8 ); customBlendOut = GetIntKey( "talk2_blendout", 8 ); talkChannel = playHeadAnim( anim.c_str(), blendIn ); talkSecondaryIndex++; anim = va( "talk_secondary%d", talkSecondaryIndex ); if ( !HasAnim( ANIMCHANNEL_HEAD, anim.c_str() ) && !HasAnim( ANIMCHANNEL_LEGS, anim.c_str() ) ) { talkSecondaryIndex = 1; } } parms->stage = STAGE_TALKING; return SRESULT_WAIT; } case STAGE_TALKING: if ( owner && ( owner->GetPhysics()->GetOrigin() - talkTo->GetPhysics()->GetOrigin() ).Length() > talkRadius ) { owner->StopSound( SND_CHANNEL_VOICE, false ); parms->stage = STAGE_AFTER_TALK; return SRESULT_WAIT; } if ( !AnimDone( idMath::Ftoi( talkChannel ), idMath::Ftoi( customBlendOut ) ) ) { check_cower(); if ( noTurn || !allowTurn ) { LookAtEntity( talkTo, 0.1f ); } else { faceTowardsEntity( talkTo, 0.1f ); } return SRESULT_WAIT; } parms->stage = STAGE_AFTER_TALK; talkEndTime = DelayTime( GetFloatKey( "talktime", 10.0f ) ); endHeadAnim( customBlendOut ); if ( owner ) { owner->SetTalkTarget( NULL ); } return SRESULT_WAIT; case STAGE_AFTER_TALK: if ( Time() < talkEndTime && owner && ( owner->GetPhysics()->GetOrigin() - talkTo->GetPhysics()->GetOrigin() ).Length() <= talkRadius ) { check_cower(); if ( noTurn || !allowTurn ) { LookAtEntity( talkTo, 0.1f ); } else { faceTowardsEntity( talkTo, 0.1f ); } if ( owner->AI_TALK ) { AllowMovement( false ); return SRESULT_DONE; } if ( owner->AI_PUSHED ) { getOutOfWay(); owner->AI_PUSHED = false; } return SRESULT_WAIT; } LookAtEntity( NULL, 0.0f ); TurnTo( talkOriginalYaw ); AllowMovement( false ); return SRESULT_DONE; } return SRESULT_ERROR; } stateResult_t rvmCharacter::checkBlocked( stateParms_t* parms ) { if ( owner && MoveStatus() >= MOVE_STATUS_BLOCKED_BY_OBJECT ) { owner->KickObstacles( owner->GetObstacle(), GetFloatKey( "kick_force", 60.0f ) ); } return SRESULT_DONE; } stateResult_t rvmCharacter::path_corner( stateParms_t* parms ) { idEntity* node = current_path.GetEntity(); if ( !node ) { return SRESULT_DONE; } if ( parms->stage == 0 ) { run = node->spawnArgs.GetBool( "run", "0" ); walkAnim = node->spawnArgs.GetString( "anim", "" ); MoveToEntity( node ); parms->stage = 1; } check_cower(); checkBlocked( parms ); if ( owner && owner->AI_TALK ) { cancelPathCommand(); return SRESULT_DONE; } if ( MoveDone() || DestinationUnreachable() ) { walkAnim.Clear(); run = false; if ( DestinationUnreachable() && owner ) { gameLocal.Warning( "entity '%s' couldn't reach path_corner '%s'", owner->GetName(), node->GetName() ); } finishPathCommand(); return SRESULT_DONE; } MoveToEntity( node ); return SRESULT_WAIT; } stateResult_t rvmCharacter::path_anim( stateParms_t* parms ) { enum { STAGE_START, STAGE_TURN, STAGE_ANIM }; idEntity* node = current_path.GetEntity(); if ( !node ) { return SRESULT_DONE; } switch ( parms->stage ) { case STAGE_START: customAnim = node->spawnArgs.GetString( "anim", "" ); customBlendOut = node->spawnArgs.GetInt( "blend_out", "0" ); if ( node->spawnArgs.GetString( "angle", "" )[0] ) { TurnTo( node->spawnArgs.GetFloat( "angle" ) ); parms->stage = STAGE_TURN; return SRESULT_WAIT; } parms->stage = STAGE_ANIM; return SRESULT_WAIT; case STAGE_TURN: check_cower(); if ( !FacingIdeal() ) { return SRESULT_WAIT; } parms->stage = STAGE_ANIM; return SRESULT_WAIT; case STAGE_ANIM: if ( node->spawnArgs.GetBool( "head_anim", "0" ) ) { talkChannel = playHeadAnim( customAnim.c_str(), node->spawnArgs.GetInt( "blend_in", "0" ) ); parms->stage = 3; return SRESULT_WAIT; } playCustomAnim( customAnim.c_str(), node->spawnArgs.GetInt( "blend_in", "0" ), customBlendOut ); parms->stage = 4; return SRESULT_WAIT; case 3: check_cower(); if ( owner && owner->AI_TALK ) { endHeadAnim( customBlendOut ); cancelPathCommand(); return SRESULT_DONE; } if ( AnimDone( idMath::Ftoi( talkChannel ), idMath::Ftoi( customBlendOut ) ) ) { endHeadAnim( customBlendOut ); finishPathCommand(); return SRESULT_DONE; } return SRESULT_WAIT; case 4: check_cower(); if ( owner && owner->AI_TALK ) { endCustomAnim(); cancelPathCommand(); return SRESULT_DONE; } if ( inCustomAnim() ) { return SRESULT_WAIT; } finishPathCommand(); return SRESULT_DONE; } return SRESULT_ERROR; } stateResult_t rvmCharacter::path_cycleanim( stateParms_t* parms ) { enum { STAGE_START, STAGE_TURN, STAGE_HOLD }; idEntity* node = current_path.GetEntity(); if ( !node ) { return SRESULT_DONE; } switch ( parms->stage ) { case STAGE_START: customAnim = node->spawnArgs.GetString( "anim", "" ); customBlendOut = node->spawnArgs.GetInt( "blend_out", "0" ); if ( node->spawnArgs.GetString( "angle", "" )[0] ) { TurnTo( node->spawnArgs.GetFloat( "angle" ) ); parms->stage = STAGE_TURN; return SRESULT_WAIT; } parms->stage = STAGE_TURN; return SRESULT_WAIT; case STAGE_TURN: if ( !FacingIdeal() ) { check_cower(); return SRESULT_WAIT; } playCustomCycle( customAnim.c_str(), node->spawnArgs.GetInt( "blend_in", "0" ) ); scratchEndTime = node->spawnArgs.GetFloat( "wait", "0" ) > 0.0f ? DelayTime( node->spawnArgs.GetFloat( "wait" ) ) : 0.0f; ClearActivated(); parms->stage = STAGE_HOLD; return SRESULT_WAIT; case STAGE_HOLD: check_cower(); if ( owner && owner->AI_TALK ) { SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", idMath::Ftoi( customBlendOut ) ); cancelPathCommand(); return SRESULT_DONE; } if ( scratchEndTime > 0.0f ) { if ( Time() < scratchEndTime ) { return SRESULT_WAIT; } } else if ( !Activated() ) { return SRESULT_WAIT; } SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", idMath::Ftoi( customBlendOut ) ); finishPathCommand(); return SRESULT_DONE; } return SRESULT_ERROR; } stateResult_t rvmCharacter::path_turn( stateParms_t* parms ) { idEntity* node = current_path.GetEntity(); if ( !node ) { return SRESULT_DONE; } if ( parms->stage == 0 ) { TurnTo( node->GetPhysics()->GetAxis()[0].ToYaw() ); parms->stage = 1; } check_cower(); if ( FacingIdeal() ) { finishPathCommand(); return SRESULT_DONE; } return SRESULT_WAIT; } stateResult_t rvmCharacter::path_wait( stateParms_t* parms ) { idEntity* node = current_path.GetEntity(); if ( !node ) { return SRESULT_DONE; } if ( parms->stage == 0 ) { scratchEndTime = DelayTime( node->spawnArgs.GetFloat( "wait", "0" ) ); parms->stage = 1; } check_cower(); if ( Time() >= scratchEndTime ) { finishPathCommand(); return SRESULT_DONE; } return SRESULT_WAIT; } stateResult_t rvmCharacter::path_waitfortrigger( stateParms_t* parms ) { if ( parms->stage == 0 ) { ClearActivated(); parms->stage = 1; } check_cower(); if ( owner && owner->AI_TALK ) { cancelPathCommand(); return SRESULT_DONE; } if ( Activated() ) { ClearActivated(); finishPathCommand(); return SRESULT_DONE; } return SRESULT_WAIT; } stateResult_t rvmCharacter::path_hide( stateParms_t* parms ) { HideOwner(); finishPathCommand(); return SRESULT_DONE; } stateResult_t rvmCharacter::path_show( stateParms_t* parms ) { if ( !CanBecomeSolid() ) { return SRESULT_WAIT; } ShowOwner(); finishPathCommand(); return SRESULT_DONE; } stateResult_t rvmCharacter::path_conversation_listen( stateParms_t* parms ) { idEntity* node = current_path.GetEntity(); if ( !node ) { return SRESULT_DONE; } if ( parms->stage == 0 ) { StopMove(); listening_to_character = gameLocal.FindEntity( node->spawnArgs.GetString( "focus", "" ) ); customAnim = node->spawnArgs.GetString( "anim", "" ); customBlendOut = node->spawnArgs.GetInt( "blend_out", "0" ); if ( customAnim.Length() ) { playCustomCycle( customAnim.c_str(), node->spawnArgs.GetInt( "blend_in", "0" ) ); } parms->stage = 1; } if ( owner && owner->AI_TALK ) { cancelPathCommand(); return SRESULT_DONE; } if ( !listening_to_character.GetEntity() ) { if ( customAnim.Length() ) { SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", idMath::Ftoi( customBlendOut ) ); } finishPathCommand(); return SRESULT_DONE; } if ( !node->spawnArgs.GetBool( "no_look", "0" ) ) { LookAtEntity( listening_to_character.GetEntity(), 0.1f ); } check_cower(); return SRESULT_WAIT; } stateResult_t rvmCharacter::path_conversation( stateParms_t* parms ) { enum { STAGE_START, STAGE_ANIM, STAGE_WAIT }; idEntity* node = current_path.GetEntity(); if ( !node ) { return SRESULT_DONE; } idEntity* listener = gameLocal.FindEntity( node->spawnArgs.GetString( "focus", "" ) ); switch ( parms->stage ) { case STAGE_START: { StopMove(); const char* dialog = node->spawnArgs.GetString( "snd_dialog", "" ); if ( dialog[0] && owner ) { const idSoundShader* shader = declManager->FindSound( dialog ); owner->StartSoundShader( shader, SND_CHANNEL_VOICE, 0, false, NULL ); } customBlendOut = node->spawnArgs.GetInt( "blend_out", "0" ); talkChannel = playHeadAnim( node->spawnArgs.GetString( "anim", "" ), node->spawnArgs.GetInt( "blend_in", "0" ) ); parms->stage = STAGE_ANIM; return SRESULT_WAIT; } case STAGE_ANIM: if ( owner && owner->AI_TALK ) { break_out_of_conversation(); cancelPathCommand(); return SRESULT_DONE; } check_cower(); LookAtEntity( listener, 0.1f ); if ( !AnimDone( idMath::Ftoi( talkChannel ), idMath::Ftoi( customBlendOut ) ) ) { return SRESULT_WAIT; } endHeadAnim( customBlendOut ); scratchEndTime = node->spawnArgs.GetFloat( "wait", "0" ) > 0.0f ? DelayTime( node->spawnArgs.GetFloat( "wait" ) ) : 0.0f; parms->stage = STAGE_WAIT; return SRESULT_WAIT; case STAGE_WAIT: if ( owner && owner->AI_TALK ) { break_out_of_conversation(); cancelPathCommand(); return SRESULT_DONE; } check_cower(); LookAtEntity( listener, 0.1f ); if ( scratchEndTime > 0.0f && Time() < scratchEndTime ) { return SRESULT_WAIT; } while ( next_listener.GetEntity() ) { idAI* listenerAI = next_listener.GetEntity()->IsType( idAI::Type ) ? static_cast( next_listener.GetEntity() ) : NULL; rvmCharacter* charListener = listenerAI && listenerAI->nativeAIObject ? listenerAI->nativeAIObject->Cast() : NULL; if ( charListener ) { next_listener = charListener->next_listener; charListener->next_listener = NULL; charListener->listening_to_character = NULL; } else { next_listener = NULL; } } finishPathCommand(); return SRESULT_DONE; } return SRESULT_ERROR; } stateResult_t rvmCharacter::path_headanim( stateParms_t* parms ) { idEntity* node = current_path.GetEntity(); if ( !node ) { return SRESULT_DONE; } customBlendOut = node->spawnArgs.GetInt( "blend_out", "0" ); customAnim = node->spawnArgs.GetString( "anim", "" ); SetAnimState( ANIMCHANNEL_HEAD, "Head_TalkHeadOnly", node->spawnArgs.GetInt( "blend_in", "0" ) ); finishPathCommand(); return SRESULT_DONE; } stateResult_t rvmCharacter::path_waitforheadanim( stateParms_t* parms ) { idEntity* node = current_path.GetEntity(); idEntity* focus = node ? gameLocal.FindEntity( node->spawnArgs.GetString( "focus", "" ) ) : NULL; StopMove(); if ( owner && owner->AI_TALK ) { cancelPathCommand(); return SRESULT_DONE; } check_cower(); LookAtEntity( focus, 0.1f ); if ( InAnimState( ANIMCHANNEL_HEAD, "Head_TalkHeadOnly" ) ) { return SRESULT_WAIT; } finishPathCommand(); return SRESULT_DONE; } stateResult_t rvmCharacter::path_talk( stateParms_t* parms ) { if ( parms->stage == 0 ) { if ( owner ) { owner->SetTalkTarget( Player() ); } parms->stage = 1; } stateResult_t result = idle_talk( parms ); if ( result == SRESULT_DONE ) { finishPathCommand(); } return result; } stateResult_t rvmCharacter::path_talk_triggered( stateParms_t* parms ) { if ( parms->stage == 0 ) { talkMode = CHAR_TALK_TRIGGERED; if ( owner ) { owner->SetTalkTarget( Player() ); } parms->stage = 1; } stateResult_t result = idle_talk( parms ); if ( result == SRESULT_DONE ) { finishPathCommand(); } return result; } stateResult_t rvmCharacter::path_talk_primary( stateParms_t* parms ) { if ( parms->stage == 0 ) { talkMode = CHAR_TALK_PRIMARY; if ( owner ) { owner->SetTalkTarget( Player() ); } parms->stage = 1; } stateResult_t result = idle_talk( parms ); if ( result == SRESULT_DONE ) { finishPathCommand(); } return result; } stateResult_t rvmCharacter::path_talk_secondary( stateParms_t* parms ) { if ( parms->stage == 0 ) { talkMode = CHAR_TALK_SECONDARY; if ( owner ) { owner->SetTalkTarget( Player() ); } parms->stage = 1; } stateResult_t result = idle_talk( parms ); if ( result == SRESULT_DONE ) { finishPathCommand(); } return result; } stateResult_t rvmCharacter::path_lookat( stateParms_t* parms ) { enum { STAGE_START, STAGE_HOLD }; idEntity* node = current_path.GetEntity(); if ( !node ) { return SRESULT_DONE; } idEntity* focus = gameLocal.FindEntity( node->spawnArgs.GetString( "focus", "" ) ); switch ( parms->stage ) { case STAGE_START: customAnim = node->spawnArgs.GetString( "anim", "" ); customBlendOut = node->spawnArgs.GetInt( "blend_out", "0" ); if ( customAnim.Length() ) { playCustomCycle( customAnim.c_str(), node->spawnArgs.GetInt( "blend_in", "0" ) ); } scratchEndTime = node->spawnArgs.GetFloat( "wait", "0" ) > 0.0f ? DelayTime( node->spawnArgs.GetFloat( "wait" ) ) : 0.0f; ClearActivated(); parms->stage = STAGE_HOLD; return SRESULT_WAIT; case STAGE_HOLD: if ( owner && owner->AI_TALK ) { if ( customAnim.Length() ) { SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", idMath::Ftoi( customBlendOut ) ); } cancelPathCommand(); return SRESULT_DONE; } check_cower(); LookAtEntity( focus, 0.1f ); if ( Activated() || ( scratchEndTime > 0.0f && Time() >= scratchEndTime ) ) { if ( customAnim.Length() ) { SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", idMath::Ftoi( customBlendOut ) ); } finishPathCommand(); return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } stateResult_t rvmCharacter::state_FollowAlternatePath( stateParms_t* parms ) { if ( parms->stage == 0 ) { endHeadAnim( 8.0f ); executePathCommand( current_path.GetEntity() ); parms->stage = 1; } if ( InAction() ) { return SRESULT_WAIT; } SetState( "state_Idle" ); return SRESULT_DONE; } void rvmCharacter::Anim_Disable() { } void rvmCharacter::check_cower() { if ( !noCower && HeardSound( false ) ) { endHeadAnim( 6.0f ); cancelPathCommand(); SetState( "state_Cower" ); } } void rvmCharacter::target_talk() { cancelPathCommand(); if ( owner ) { owner->SetTalkTarget( Player() ); } StartAction( "idle_talk" ); SetState( "state_Idle" ); } void rvmCharacter::say_triggered() { cancelPathCommand(); talkMode = CHAR_TALK_TRIGGERED; if ( owner ) { owner->SetTalkTarget( Player() ); } SetState( "state_Idle" ); } void rvmCharacter::say_primary() { cancelPathCommand(); talkMode = CHAR_TALK_PRIMARY; if ( owner ) { owner->SetTalkTarget( Player() ); } SetState( "state_Idle" ); } void rvmCharacter::say_secondary() { cancelPathCommand(); talkMode = CHAR_TALK_SECONDARY; if ( owner ) { owner->SetTalkTarget( Player() ); } SetState( "state_Idle" ); } void rvmCharacter::faceTowardsEntity( idEntity* target, float duration ) { if ( !target || !owner ) { return; } idVec3 delta = target->GetPhysics()->GetOrigin() - GetOrigin(); idAngles targetAngles = delta.ToAngles(); const idVec3 currentAngles = owner->GetAngles(); const float yawDelta = idMath::AngleNormalize180( targetAngles.yaw - currentAngles.y ); if ( yawDelta < -CHAR_TURN_THRESHOLD || yawDelta > CHAR_TURN_THRESHOLD ) { TurnTo( targetAngles.yaw ); } LookAtEntity( target, duration ); } void rvmCharacter::playCustomCycle( const char* animname, float blendTime ) { customAnim = animname ? animname : ""; SetAnimState( ANIMCHANNEL_LEGS, "Legs_CustomCycle", idMath::Ftoi( blendTime ) ); } void rvmCharacter::playCustomAnim( const char* animname, float blendIn, float blendOut ) { customBlendOut = blendOut; customAnim = animname ? animname : ""; SetAnimState( ANIMCHANNEL_LEGS, "Legs_CustomAnim", idMath::Ftoi( blendIn ) ); } bool rvmCharacter::inCustomAnim() { return InAnimState( ANIMCHANNEL_LEGS, "Legs_CustomAnim" ); } void rvmCharacter::endCustomAnim() { SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", idMath::Ftoi( customBlendOut ) ); } void rvmCharacter::waitForCustomAnim( const char* animname, float blendIn, float blendOut ) { check_cower(); customBlendOut = blendOut; customAnim = animname ? animname : ""; SetAnimState( ANIMCHANNEL_LEGS, "Legs_CustomAnim", idMath::Ftoi( blendIn ) ); } void rvmCharacter::endHeadAnim( float blend_out ) { StopAnim( ANIMCHANNEL_HEAD, idMath::Ftoi( blend_out ) ); SetBlendFrames( ANIMCHANNEL_LEGS, idMath::Ftoi( blend_out ) ); IdleAnim( ANIMCHANNEL_LEGS, "stand" ); SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", idMath::Ftoi( blend_out ) ); SetAnimState( ANIMCHANNEL_HEAD, "Head_Idle", idMath::Ftoi( blend_out ) ); } float rvmCharacter::playHeadAnim( const char* animname, float blend_frames ) { SetAnimState( ANIMCHANNEL_LEGS, "Anim_Disable", idMath::Ftoi( blend_frames ) ); SetBlendFrames( ANIMCHANNEL_LEGS, idMath::Ftoi( blend_frames ) ); IdleAnim( ANIMCHANNEL_LEGS, "stand" ); const char* headAnim = ChooseAnim( ANIMCHANNEL_HEAD, animname ); if ( headAnim[0] ) { SetBlendFrames( ANIMCHANNEL_HEAD, idMath::Ftoi( blend_frames ) ); PlayAnim( ANIMCHANNEL_HEAD, headAnim ); const float headLength = AnimLength( ANIMCHANNEL_HEAD, headAnim ); const char* bodyAnim = ChooseAnim( ANIMCHANNEL_LEGS, animname ); if ( !bodyAnim[0] ) { SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", idMath::Ftoi( blend_frames ) ); } else if ( AnimLength( ANIMCHANNEL_LEGS, bodyAnim ) < headLength ) { PlayCycle( ANIMCHANNEL_LEGS, bodyAnim ); } else { PlayAnim( ANIMCHANNEL_LEGS, bodyAnim ); } return ANIMCHANNEL_HEAD; } if ( blend_frames >= 0.0f ) { SetBlendFrames( ANIMCHANNEL_LEGS, idMath::Ftoi( blend_frames ) ); } const char* bodyAnim = ChooseAnim( ANIMCHANNEL_LEGS, animname ); PlayAnim( ANIMCHANNEL_LEGS, bodyAnim[0] ? bodyAnim : "stand" ); return ANIMCHANNEL_LEGS; } void rvmCharacter::idle_followPathEntities( idEntity* pathnode ) { current_path = pathnode; StartAction( "idle_followPathEntities" ); } void rvmCharacter::executePathCommand( idEntity* pathnode ) { current_path = pathnode; next_path = idPathCorner::RandomPath( current_path.GetEntity(), NULL ); if ( current_path.GetEntity() ) { const char* classname = current_path.GetEntity()->spawnArgs.GetString( "classname", current_path.GetEntity()->GetEntityDefName() ); StartAction( classname ); } } void rvmCharacter::finishPathCommand() { idEntity* node = current_path.GetEntity(); if ( node ) { const char* triggername = node->spawnArgs.GetString( "trigger", "" ); if ( triggername[0] ) { idEntity* trigger = gameLocal.FindEntity( triggername ); if ( trigger ) { trigger->ActivateTargets( owner ); } } } current_path = next_path; skipOutOfPath = false; if ( canTalk && owner ) { owner->SetTalkState( TALK_OK ); } noCower = noCowerSaved; } void rvmCharacter::cancelPathCommand() { if ( skipOutOfPath ) { finishPathCommand(); } skipOutOfPath = false; if ( canTalk && owner ) { owner->SetTalkState( TALK_OK ); } noCower = noCowerSaved; } bool rvmCharacter::getOutOfWay() { if ( ignorePush || !owner ) { return false; } if ( ( Time() - lastPushTime ) > FrameTime() ) { lastPushTime = Time(); pushTime = lastPushTime + 0.35f; return false; } lastPushTime = Time() + FrameTime(); if ( Time() < pushTime ) { return false; } idVec3 org = owner->PushPointIntoNAV( GetOrigin() ); for ( float yaw = 0.0f; yaw < 360.0f; yaw += 45.0f ) { idAngles angles( 0.0f, yaw, 0.0f ); idVec3 pos = owner->PushPointIntoNAV( org + angles.ToForward() * 124.0f ); if ( TestMoveToPosition( pos ) ) { MoveToPosition( pos ); TurnTo( CurrentYaw() + 180.0f ); return true; } } return false; } void rvmCharacter::skip_conversation() { while ( current_path.GetEntity() ) { const char* classname = current_path.GetEntity()->spawnArgs.GetString( "classname", current_path.GetEntity()->GetEntityDefName() ); if ( idStr::Cmpn( classname, "path_conversation", 17 ) ) { break; } current_path = next_path; if ( next_path.GetEntity() ) { next_path = idPathCorner::RandomPath( next_path.GetEntity(), NULL ); } else { next_path = NULL; } } if ( owner ) { owner->StopSound( SND_CHANNEL_VOICE, false ); } } void rvmCharacter::break_out_of_conversation() { while ( next_listener.GetEntity() ) { idAI* listenerAI = next_listener.GetEntity()->IsType( idAI::Type ) ? static_cast( next_listener.GetEntity() ) : NULL; rvmCharacter* listener = listenerAI && listenerAI->nativeAIObject ? listenerAI->nativeAIObject->Cast() : NULL; if ( listener ) { next_listener = listener->next_listener; listener->skip_conversation(); listener->next_listener = NULL; listener->listening_to_character = NULL; } else { next_listener = NULL; } } skip_conversation(); } void rvmCharacter::follow_alternate_path1() { current_path = GetEntityKey( "alt_path1" ); SetState( "state_FollowAlternatePath" ); } void rvmCharacter::follow_alternate_path2() { current_path = GetEntityKey( "alt_path2" ); SetState( "state_FollowAlternatePath" ); } void rvmCharacter::follow_alternate_path3() { current_path = GetEntityKey( "alt_path3" ); SetState( "state_FollowAlternatePath" ); } void rvmCharacter::disable_turning() { allowTurn = false; } void rvmCharacter::enable_turning() { allowTurn = true; }