mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2025-04-25 11:01:30 +00:00
1963 lines
40 KiB
Text
1963 lines
40 KiB
Text
/***********************************************************************
|
|
|
|
character.script
|
|
|
|
***********************************************************************/
|
|
|
|
#define CHAR_TALK_TRIGGERED 0
|
|
#define CHAR_TALK_PRIMARY 1
|
|
#define CHAR_TALK_SECONDARY 2
|
|
#define CHAR_WALKTURN 65
|
|
#define CHAR_MIN_TURN 10
|
|
#define CHAR_TURN_THRESHOLD 45
|
|
#define CHAR_GETOUTOFWAY_DIST 124
|
|
#define CHAR_ALLOW_WALK_DIST 180
|
|
#define CHAR_ALLOW_WALK_DIST_NPC 80
|
|
#define CHAR_WAIT_BLOCKED 6
|
|
#define CHAR_PUSH_DELAY 0.35
|
|
|
|
#define CHAR_BLEND_COWER_TO_IDLE 6
|
|
#define CHAR_BLEND_TURN_TO_IDLE 0
|
|
|
|
object character : ai {
|
|
entity head;
|
|
float talkMode;
|
|
float talk_secondary_index;
|
|
string customAnim;
|
|
float customBlendOut;
|
|
string walkAnim;
|
|
boolean allow_turn;
|
|
boolean run;
|
|
boolean can_talk;
|
|
boolean no_cower;
|
|
boolean no_cower_saved;
|
|
boolean ignore_push;
|
|
boolean skipOutOfPath;
|
|
character listening_to_character;
|
|
character next_listener;
|
|
float lastPushTime;
|
|
float pushTime;
|
|
|
|
entity current_path;
|
|
entity next_path;
|
|
|
|
boolean follow_nodes;
|
|
boolean dont_push_others;
|
|
|
|
float playHeadAnim( string animname, float blend_frames );
|
|
void endHeadAnim( float blend_out );
|
|
|
|
void executePathCommand( entity pathnode );
|
|
void finishPathCommand();
|
|
void cancelPathCommand();
|
|
|
|
void checkBlocked();
|
|
boolean getOutOfWay();
|
|
|
|
//
|
|
// States
|
|
//
|
|
void state_Begin();
|
|
void state_TalkTrigger();
|
|
void state_Idle();
|
|
void state_Killed();
|
|
|
|
//
|
|
// actions
|
|
//
|
|
void idle_stand();
|
|
void idle_followPathEntities( entity pathnode );
|
|
void idle_talk();
|
|
|
|
void init();
|
|
void destroy();
|
|
void faceTowardsEntity( entity target, float duration );
|
|
|
|
void playCustomCycle( string animname, float blendTime );
|
|
void playCustomAnim( string animname, float blendIn, float blendOut );
|
|
boolean inCustomAnim();
|
|
void endCustomAnim();
|
|
void waitForCustomAnim( string animname, float blendIn, float blendOut );
|
|
|
|
void state_Spawner();
|
|
void state_Cower();
|
|
void check_cower();
|
|
void target_talk();
|
|
void say_triggered();
|
|
void say_primary();
|
|
void say_secondary();
|
|
|
|
void skip_conversation();
|
|
|
|
void disable_turning();
|
|
void enable_turning();
|
|
|
|
// path following
|
|
void path_corner();
|
|
void path_anim();
|
|
void path_cycleanim();
|
|
void path_turn();
|
|
void path_wait();
|
|
void path_waitfortrigger();
|
|
void path_hide();
|
|
void path_show();
|
|
void path_conversation_listen();
|
|
void path_conversation();
|
|
void path_headanim();
|
|
void path_talk();
|
|
void path_talk_triggered();
|
|
void path_talk_primary();
|
|
void path_talk_secondary();
|
|
void path_lookat();
|
|
void state_FollowAlternatePath();
|
|
void follow_alternate_path1();
|
|
void follow_alternate_path2();
|
|
void follow_alternate_path3();
|
|
|
|
// head anim states
|
|
void Anim_Disable();
|
|
void Head_Idle();
|
|
void Head_Dead();
|
|
void Head_TalkHeadOnly();
|
|
|
|
// legs anim states
|
|
void Legs_Death();
|
|
void Legs_Idle();
|
|
void Legs_Walk();
|
|
void Legs_Walk_Special();
|
|
void Legs_Run();
|
|
void Legs_TurnLeft();
|
|
void Legs_TurnRight();
|
|
void Legs_Cower();
|
|
void Legs_CustomAnim();
|
|
void Legs_CustomCycle();
|
|
};
|
|
|
|
/***********************************************************************
|
|
|
|
Head animation control
|
|
|
|
***********************************************************************/
|
|
|
|
void character::Anim_Disable() {
|
|
}
|
|
|
|
void character::Head_Idle() {
|
|
idleAnim( ANIMCHANNEL_HEAD, "stand" );
|
|
}
|
|
|
|
void character::Head_Dead() {
|
|
playAnim( ANIMCHANNEL_HEAD, "dead" );
|
|
}
|
|
|
|
void character::Head_TalkHeadOnly() {
|
|
playAnim( ANIMCHANNEL_HEAD, customAnim );
|
|
while( !animDone( ANIMCHANNEL_HEAD, customBlendOut ) ) {
|
|
waitFrame();
|
|
}
|
|
finishAction( "talkAnim" );
|
|
animState( ANIMCHANNEL_HEAD, "Head_Idle", customBlendOut );
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
Legs animation control
|
|
|
|
***********************************************************************/
|
|
|
|
void character::Legs_Death() {
|
|
finishAction( "dead" );
|
|
}
|
|
|
|
void character::Legs_Idle() {
|
|
float delta;
|
|
|
|
if ( allow_turn && !AI_FORWARD ) {
|
|
delta = getTurnDelta();
|
|
if ( delta > CHAR_MIN_TURN ) {
|
|
Legs_TurnLeft();
|
|
}
|
|
if ( delta < -CHAR_MIN_TURN ) {
|
|
Legs_TurnRight();
|
|
}
|
|
}
|
|
|
|
allowMovement( false );
|
|
idleAnim( ANIMCHANNEL_LEGS, "stand" );
|
|
|
|
eachFrame {
|
|
if ( AI_FORWARD ) {
|
|
if ( walkAnim != "" ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Walk_Special", 8 );
|
|
} else if ( run ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Run", 8 );
|
|
} else {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Walk", 8 );
|
|
}
|
|
}
|
|
|
|
if ( allow_turn ) {
|
|
if ( getTurnDelta() > CHAR_MIN_TURN ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_TurnLeft", 4 );
|
|
}
|
|
if ( getTurnDelta() < -CHAR_MIN_TURN ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_TurnRight", 4 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void character::Legs_Walk() {
|
|
float delta;
|
|
|
|
allowMovement( true );
|
|
playCycle( ANIMCHANNEL_LEGS, "walk" );
|
|
|
|
while( AI_FORWARD && ( walkAnim == "" ) ) {
|
|
if ( run ) {
|
|
delta = getTurnDelta();
|
|
if ( ( delta < CHAR_WALKTURN ) && ( delta > -CHAR_WALKTURN ) ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Run", 8 );
|
|
}
|
|
}
|
|
waitFrame();
|
|
}
|
|
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", 8 );
|
|
}
|
|
|
|
void character::Legs_Walk_Special() {
|
|
allowMovement( true );
|
|
playCycle( ANIMCHANNEL_LEGS, walkAnim );
|
|
|
|
while( AI_FORWARD && ( walkAnim != "" ) ) {
|
|
waitFrame();
|
|
}
|
|
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", 8 );
|
|
}
|
|
|
|
void character::Legs_Run() {
|
|
float delta;
|
|
|
|
allowMovement( true );
|
|
playCycle( ANIMCHANNEL_LEGS, "run" );
|
|
|
|
while( AI_FORWARD && run && ( walkAnim == "" ) ) {
|
|
delta = getTurnDelta();
|
|
if ( ( delta > CHAR_WALKTURN ) || ( delta < -CHAR_WALKTURN ) ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Walk", 8 );
|
|
}
|
|
|
|
waitFrame();
|
|
}
|
|
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", 8 );
|
|
}
|
|
|
|
void character::Legs_TurnLeft() {
|
|
float delta;
|
|
|
|
allowMovement( true );
|
|
animTurn( 180 );
|
|
playAnim( ANIMCHANNEL_LEGS, "turn_left" );
|
|
while( !animDone( ANIMCHANNEL_LEGS, CHAR_BLEND_TURN_TO_IDLE ) ) {
|
|
waitFrame();
|
|
}
|
|
animTurn( 0 );
|
|
|
|
if ( allow_turn && !AI_FORWARD ) {
|
|
delta = getTurnDelta();
|
|
if ( delta > CHAR_MIN_TURN ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_TurnLeft", CHAR_BLEND_TURN_TO_IDLE );
|
|
}
|
|
if ( delta < -CHAR_MIN_TURN ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_TurnRight", CHAR_BLEND_TURN_TO_IDLE );
|
|
}
|
|
}
|
|
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", CHAR_BLEND_TURN_TO_IDLE );
|
|
}
|
|
|
|
void character::Legs_TurnRight() {
|
|
float delta;
|
|
|
|
allowMovement( true );
|
|
animTurn( 180 );
|
|
playAnim( ANIMCHANNEL_LEGS, "turn_right" );
|
|
while( !animDone( ANIMCHANNEL_LEGS, CHAR_BLEND_TURN_TO_IDLE ) ) {
|
|
waitFrame();
|
|
}
|
|
animTurn( 0 );
|
|
|
|
if ( allow_turn && !AI_FORWARD ) {
|
|
delta = getTurnDelta();
|
|
if ( delta > CHAR_MIN_TURN ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_TurnLeft", CHAR_BLEND_TURN_TO_IDLE );
|
|
}
|
|
if ( delta < -CHAR_MIN_TURN ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_TurnRight", CHAR_BLEND_TURN_TO_IDLE );
|
|
}
|
|
}
|
|
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", CHAR_BLEND_TURN_TO_IDLE );
|
|
}
|
|
|
|
void character::Legs_Cower() {
|
|
setTalkTarget( $null_entity );
|
|
startSound( "snd_cower", SND_CHANNEL_VOICE, false );
|
|
playAnim( ANIMCHANNEL_LEGS, "cower" );
|
|
|
|
while( !animDone( ANIMCHANNEL_LEGS, CHAR_BLEND_COWER_TO_IDLE ) ) {
|
|
lookAt( $player1, 0.1 );
|
|
waitFrame();
|
|
}
|
|
|
|
setTalkTarget( $null_entity );
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", CHAR_BLEND_COWER_TO_IDLE );
|
|
}
|
|
|
|
void character::Legs_CustomCycle() {
|
|
allowMovement( true );
|
|
playCycle( ANIMCHANNEL_LEGS, customAnim );
|
|
while( 1 ) {
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
void character::Legs_CustomAnim() {
|
|
allowMovement( true );
|
|
playAnim( ANIMCHANNEL_LEGS, customAnim );
|
|
|
|
while( !animDone( ANIMCHANNEL_LEGS, customBlendOut ) ) {
|
|
waitFrame();
|
|
}
|
|
|
|
finishAction( "customAnim" );
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", customBlendOut );
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
AI
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
=====================
|
|
character::init
|
|
=====================
|
|
*/
|
|
void character::init() {
|
|
float mod;
|
|
|
|
mod = getIntKey( "head_look" );
|
|
setBoneMod( mod );
|
|
|
|
lastPushTime = 0;
|
|
pushTime = 0;
|
|
|
|
dont_push_others = getIntKey( "dont_push_others" );
|
|
|
|
talkMode = CHAR_TALK_PRIMARY;
|
|
talk_secondary_index = 1;
|
|
follow_nodes = true;
|
|
walkAnim = "";
|
|
run = false;
|
|
|
|
can_talk = getIntKey( "talks" );
|
|
no_cower = getIntKey( "no_cower" );
|
|
ignore_push = getIntKey( "ignore_push" );
|
|
no_cower_saved = no_cower;
|
|
if ( getFloatKey( "turn_rate" ) != 0 ) {
|
|
allow_turn = true;
|
|
} else {
|
|
ignore_push = true;
|
|
}
|
|
|
|
self.setKey( "conversationNext", "" );
|
|
|
|
if ( getIntKey( "spawner" ) ) {
|
|
setNextState( "state_Spawner" );
|
|
} else {
|
|
setNextState( "state_Begin" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::destroy
|
|
=====================
|
|
*/
|
|
void character::destroy() {
|
|
character conversation_char;
|
|
|
|
// disconnect from anyone we're listening to
|
|
if ( listening_to_character ) {
|
|
conversation_char = listening_to_character;
|
|
while( conversation_char ) {
|
|
if ( self == conversation_char.next_listener ) {
|
|
// point the previous one in the list to the our next listener
|
|
conversation_char.next_listener = next_listener;
|
|
break;
|
|
}
|
|
|
|
conversation_char = conversation_char.next_listener;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::state_Spawner
|
|
=====================
|
|
*/
|
|
void character::state_Spawner() {
|
|
entity ent;
|
|
float triggerCount;
|
|
float maxSpawn;
|
|
float i;
|
|
string name;
|
|
|
|
maxSpawn = getIntKey( "spawner" );
|
|
name = getName();
|
|
|
|
hide();
|
|
|
|
triggerCount = 0;
|
|
AI_ACTIVATED = false;
|
|
while( 1 ) {
|
|
if ( AI_ACTIVATED ) {
|
|
triggerCount++;
|
|
AI_ACTIVATED = false;
|
|
}
|
|
|
|
if ( triggerCount ) {
|
|
if ( canBecomeSolid() ) {
|
|
for( i = 0; i < maxSpawn; i++ ) {
|
|
ent = sys.getEntity( name + "_" + i );
|
|
if ( !ent ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( i < maxSpawn ) {
|
|
triggerCount--;
|
|
sys.copySpawnArgs( self );
|
|
sys.setSpawnArg( "spawner", "0" );
|
|
sys.setSpawnArg( "name", name + "_" + i );
|
|
if ( getKey( "spawn_target" ) != "" ) {
|
|
sys.setSpawnArg( "target", getKey( "spawn_target" ) );
|
|
}
|
|
ent = sys.spawn( getKey( "classname" ) );
|
|
sys.trigger( ent );
|
|
}
|
|
}
|
|
}
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::state_Cower
|
|
=====================
|
|
*/
|
|
void character::state_Cower() {
|
|
float waittime;
|
|
|
|
stopSound( SND_CHANNEL_VOICE, false );
|
|
if ( head ) {
|
|
head.stopSound( SND_CHANNEL_VOICE, false );
|
|
}
|
|
|
|
endHeadAnim( 6 );
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Cower", 6 );
|
|
while( inAnimState( ANIMCHANNEL_LEGS, "Legs_Cower" ) ) {
|
|
waitFrame();
|
|
}
|
|
|
|
setTalkTarget( $null_entity );
|
|
setState( "state_Idle" );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::check_cower
|
|
=====================
|
|
*/
|
|
void character::check_cower() {
|
|
if ( !no_cower ) {
|
|
if ( heardSound( false ) ) {
|
|
endHeadAnim( 6 );
|
|
cancelPathCommand();
|
|
setState( "state_Cower" );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::target_talk
|
|
|
|
forces character to talk to the player
|
|
=====================
|
|
*/
|
|
void character::target_talk() {
|
|
cancelPathCommand();
|
|
setTalkTarget( $player1 );
|
|
idle_talk();
|
|
setState( "state_Idle" );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::say_triggered
|
|
|
|
forces character to say his triggered talk anim to the player
|
|
=====================
|
|
*/
|
|
void character::say_triggered() {
|
|
cancelPathCommand();
|
|
talkMode = CHAR_TALK_TRIGGERED;
|
|
setTalkTarget( $player1 );
|
|
setState( "state_Idle" );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::say_primary
|
|
|
|
forces character to say his primary talk anim to the player
|
|
=====================
|
|
*/
|
|
void character::say_primary() {
|
|
cancelPathCommand();
|
|
talkMode = CHAR_TALK_PRIMARY;
|
|
setTalkTarget( $player1 );
|
|
setState( "state_Idle" );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::say_secondary
|
|
|
|
forces character to say his secondary talk anim to the player
|
|
=====================
|
|
*/
|
|
void character::say_secondary() {
|
|
cancelPathCommand();
|
|
talkMode = CHAR_TALK_SECONDARY;
|
|
setTalkTarget( $player1 );
|
|
setState( "state_Idle" );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::faceTowardsEntity
|
|
=====================
|
|
*/
|
|
void character::faceTowardsEntity( entity target, float duration ) {
|
|
vector delta;
|
|
vector ang1;
|
|
vector ang2;
|
|
vector ang3;
|
|
|
|
delta = target.getOrigin();
|
|
delta = delta - getOrigin();
|
|
ang1 = sys.VecToAngles( delta );
|
|
ang2 = getAngles();
|
|
ang3 = anglemod180( ang1 - ang2 );
|
|
if ( ang3_y < -CHAR_TURN_THRESHOLD ) {
|
|
turnTo( ang1_y );
|
|
} else if ( ang3_y > CHAR_TURN_THRESHOLD ) {
|
|
turnTo( ang1_y );
|
|
}
|
|
|
|
lookAt( target, duration );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::playCustomCycle
|
|
=====================
|
|
*/
|
|
void character::playCustomCycle( string animname, float blendTime ) {
|
|
customAnim = animname;
|
|
animState( ANIMCHANNEL_LEGS, "Legs_CustomCycle", blendTime );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::playCustomAnim
|
|
=====================
|
|
*/
|
|
void character::playCustomAnim( string animname, float blendIn, float blendOut ) {
|
|
customBlendOut = blendOut;
|
|
customAnim = animname;
|
|
animState( ANIMCHANNEL_LEGS, "Legs_CustomAnim", blendIn );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::inCustomAnim
|
|
=====================
|
|
*/
|
|
boolean character::inCustomAnim() {
|
|
return inAnimState( ANIMCHANNEL_LEGS, "Legs_CustomAnim" );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::endCustomAnim
|
|
=====================
|
|
*/
|
|
void character::endCustomAnim() {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", customBlendOut );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::waitForCustomAnim
|
|
=====================
|
|
*/
|
|
void character::waitForCustomAnim( string animname, float blendIn, float blendOut ) {
|
|
check_cower();
|
|
customBlendOut = blendOut;
|
|
customAnim = animname;
|
|
animState( ANIMCHANNEL_LEGS, "Legs_CustomAnim", blendIn );
|
|
while( inAnimState( ANIMCHANNEL_LEGS, "Legs_CustomAnim" ) ) {
|
|
check_cower();
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::endHeadAnim
|
|
=====================
|
|
*/
|
|
void character::endHeadAnim( float blend_out ) {
|
|
if ( getHead() ) {
|
|
stopAnim( ANIMCHANNEL_HEAD, blend_out );
|
|
}
|
|
setBlendFrames( ANIMCHANNEL_LEGS, blend_out );
|
|
idleAnim( ANIMCHANNEL_LEGS, "stand" );
|
|
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", blend_out );
|
|
if ( getHead() ) {
|
|
animState( ANIMCHANNEL_HEAD, "Head_Idle", blend_out );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::playHeadAnim
|
|
=====================
|
|
*/
|
|
float character::playHeadAnim( string animname, float blend_frames ) {
|
|
string anim;
|
|
float headlength;
|
|
float bodylength;
|
|
|
|
animState( ANIMCHANNEL_LEGS, "Anim_Disable", blend_frames );
|
|
setBlendFrames( ANIMCHANNEL_LEGS, blend_frames );
|
|
idleAnim( ANIMCHANNEL_LEGS, "stand" );
|
|
waitFrame();
|
|
|
|
anim = chooseAnim( ANIMCHANNEL_HEAD, animname );
|
|
if ( anim != "" ) {
|
|
setBlendFrames( ANIMCHANNEL_HEAD, blend_frames );
|
|
playAnim( ANIMCHANNEL_HEAD, anim );
|
|
headlength = animLength( ANIMCHANNEL_HEAD, anim );
|
|
|
|
anim = chooseAnim( ANIMCHANNEL_LEGS, animname );
|
|
if ( anim == "" ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", blend_frames );
|
|
} else {
|
|
bodylength = animLength( ANIMCHANNEL_LEGS, anim );
|
|
if ( bodylength < headlength ) {
|
|
playCycle( ANIMCHANNEL_LEGS, anim );
|
|
} else {
|
|
playAnim( ANIMCHANNEL_LEGS, anim );
|
|
}
|
|
}
|
|
|
|
return ANIMCHANNEL_HEAD;
|
|
} else {
|
|
// play on legs
|
|
if ( blend_frames >= 0 ) {
|
|
setBlendFrames( ANIMCHANNEL_LEGS, blend_frames );
|
|
}
|
|
anim = chooseAnim( ANIMCHANNEL_LEGS, animname );
|
|
if ( anim == "" ) {
|
|
playAnim( ANIMCHANNEL_LEGS, "stand" );
|
|
} else {
|
|
playAnim( ANIMCHANNEL_LEGS, animname );
|
|
}
|
|
return ANIMCHANNEL_LEGS;
|
|
}
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
States
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
=====================
|
|
character::state_Begin
|
|
=====================
|
|
*/
|
|
void character::state_Begin() {
|
|
float doHide;
|
|
float teleportType;
|
|
string triggerAnim;
|
|
float movetype;
|
|
|
|
head = getHead();
|
|
|
|
talkMode = CHAR_TALK_PRIMARY;
|
|
follow_nodes = true;
|
|
walkAnim = "";
|
|
run = false;
|
|
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 );
|
|
if ( getHead() ) {
|
|
animState( ANIMCHANNEL_HEAD, "Head_Idle", 0 );
|
|
}
|
|
|
|
doHide = getIntKey( "hide" );
|
|
teleportType = getIntKey( "teleport" );
|
|
triggerAnim = getKey( "trigger_anim" );
|
|
if ( triggerAnim != "" ) {
|
|
//
|
|
// hide until triggered and then play a special animation
|
|
//
|
|
checkAnim( ANIMCHANNEL_LEGS, triggerAnim );
|
|
hide();
|
|
waitUntil( AI_ACTIVATED );
|
|
clearEnemy();
|
|
waitUntil( canBecomeSolid() );
|
|
show();
|
|
waitForCustomAnim( triggerAnim, 0, 4 );
|
|
} else if ( teleportType > 0 ) {
|
|
//
|
|
// teleport in when triggered
|
|
//
|
|
hide();
|
|
AI_ACTIVATED = false;
|
|
waitUntil( AI_ACTIVATED );
|
|
clearEnemy();
|
|
waitUntil( canBecomeSolid() );
|
|
becomeSolid();
|
|
movetype = getMoveType();
|
|
setMoveType( MOVETYPE_STATIC );
|
|
if ( teleportType == 1 ) {
|
|
startFx( "fx/teleporter1.fx" );
|
|
wait( 1.6 );
|
|
} else if ( teleportType == 2 ) {
|
|
startFx( "fx/teleporter2.fx" );
|
|
wait( 2.6 );
|
|
} else if ( teleportType == 3 ) {
|
|
startFx( "fx/teleporter3.fx" );
|
|
wait( 3.6 );
|
|
} else {
|
|
startFx( "fx/teleporter.fx" );
|
|
wait( 0.6 );
|
|
}
|
|
show();
|
|
playCustomAnim( "teleport", 0, 4 );
|
|
waitAction( "customAnim" );
|
|
setMoveType( movetype );
|
|
} else if ( doHide ) {
|
|
//
|
|
// hide until triggered
|
|
//
|
|
hide();
|
|
AI_ACTIVATED = false;
|
|
waitUntil( AI_ACTIVATED );
|
|
if ( doHide == 1 ) {
|
|
AI_ACTIVATED = false;
|
|
clearEnemy();
|
|
}
|
|
waitUntil( canBecomeSolid() );
|
|
show();
|
|
}
|
|
|
|
if ( getIntKey( "trigger" ) ) {
|
|
AI_ACTIVATED = false;
|
|
waitUntil( AI_ACTIVATED );
|
|
}
|
|
|
|
float waittime;
|
|
waittime = getFloatKey( "wait" );
|
|
if ( waittime > 0 ) {
|
|
sys.wait( waittime );
|
|
}
|
|
|
|
if ( getIntKey( "talktrigger" ) ) {
|
|
setState( "state_TalkTrigger" );
|
|
} else {
|
|
setState( "state_Idle" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::state_TalkTrigger
|
|
=====================
|
|
*/
|
|
void character::state_TalkTrigger() {
|
|
string animname;
|
|
entity activator;
|
|
|
|
animname = getKey( "anim" );
|
|
if ( animname != "" ) {
|
|
playCustomCycle( animname, 0 );
|
|
}
|
|
|
|
while( !AI_ACTIVATED ) {
|
|
check_cower();
|
|
waitFrame();
|
|
}
|
|
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", 12 );
|
|
|
|
setTalkTarget( $player1 );
|
|
talkMode = CHAR_TALK_TRIGGERED;
|
|
|
|
setState( "state_Idle" );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::state_Idle
|
|
=====================
|
|
*/
|
|
void character::state_Idle() {
|
|
entity node;
|
|
|
|
if ( can_talk ) {
|
|
setTalkState( TALK_OK );
|
|
}
|
|
no_cower = no_cower_saved;
|
|
|
|
while( 1 ) {
|
|
if ( AI_TALK ) {
|
|
idle_talk();
|
|
continue;
|
|
}
|
|
|
|
if ( follow_nodes ) {
|
|
if ( !current_path ) {
|
|
current_path = randomPath();
|
|
}
|
|
if ( current_path ) {
|
|
idle_followPathEntities( current_path );
|
|
if ( getIntKey( "follow_once" ) ) {
|
|
follow_nodes = false;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
idle_stand();
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::state_Killed
|
|
=====================
|
|
*/
|
|
void character::state_Killed() {
|
|
stopMove();
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Death", 0 );
|
|
if ( getHead() ) {
|
|
animState( ANIMCHANNEL_HEAD, "Head_Dead", 0 );
|
|
}
|
|
stopThinking();
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
Idle
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
=====================
|
|
character::idle_stand
|
|
=====================
|
|
*/
|
|
void character::idle_stand() {
|
|
stopMove();
|
|
while( !AI_TALK ) {
|
|
check_cower();
|
|
if ( AI_PUSHED ) {
|
|
getOutOfWay();
|
|
}
|
|
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::idle_talk
|
|
=====================
|
|
*/
|
|
void character::idle_talk() {
|
|
float ang;
|
|
float talktime;
|
|
float endtime;
|
|
float talkradius;
|
|
float blendin;
|
|
float blendout;
|
|
entity target;
|
|
vector originalAngles;
|
|
float currentTime;
|
|
boolean no_turn;
|
|
entity talkTo;
|
|
float channel;
|
|
|
|
talkTo = getTalkTarget();
|
|
|
|
no_turn = getIntKey( "talk_no_turn" );
|
|
|
|
originalAngles = getAngles();
|
|
stopMove();
|
|
|
|
if ( !no_turn && allow_turn ) {
|
|
faceTowardsEntity( talkTo, 0.1 );
|
|
sys.wait( 0.5 );
|
|
// wait for turn anim to finish
|
|
while( !inAnimState( ANIMCHANNEL_LEGS, "Legs_Idle" ) ) {
|
|
if ( talkMode != CHAR_TALK_TRIGGERED ) {
|
|
check_cower();
|
|
}
|
|
lookAt( talkTo, 0.1 );
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
allowMovement( true );
|
|
|
|
talkradius = getFloatKey( "talkradius" );
|
|
if ( talkMode == CHAR_TALK_TRIGGERED ) {
|
|
// triggered response
|
|
blendin = self.getIntKey( "talk0_blendin" );
|
|
blendout = self.getIntKey( "talk0_blendout" );
|
|
channel = playHeadAnim( "talk_trigger", blendin );
|
|
talkMode = CHAR_TALK_PRIMARY;
|
|
} else if ( talkMode == CHAR_TALK_PRIMARY ) {
|
|
// primary response
|
|
blendin = self.getIntKey( "talk1_blendin" );
|
|
blendout = self.getIntKey( "talk1_blendout" );
|
|
channel = playHeadAnim( "talk_primary", blendin );
|
|
talkMode = CHAR_TALK_SECONDARY;
|
|
} else if ( talkMode == CHAR_TALK_SECONDARY ) {
|
|
// secondary response
|
|
blendin = self.getIntKey( "talk2_blendin" );
|
|
blendout = self.getIntKey( "talk2_blendout" );
|
|
channel = playHeadAnim( "talk_secondary" + talk_secondary_index, blendin );
|
|
talk_secondary_index = talk_secondary_index + 1;
|
|
if ( !hasAnim( ANIMCHANNEL_HEAD, "talk_secondary" + talk_secondary_index ) && !hasAnim( ANIMCHANNEL_LEGS, "talk_secondary" + talk_secondary_index ) ) {
|
|
talk_secondary_index = 1;
|
|
}
|
|
}
|
|
|
|
while( !animDone( channel, blendout ) ) {
|
|
if ( distanceTo( talkTo ) > talkradius ) {
|
|
// stop talking
|
|
stopSound( SND_CHANNEL_VOICE, false );
|
|
if ( head ) {
|
|
head.stopSound( SND_CHANNEL_VOICE, false );
|
|
}
|
|
break;
|
|
}
|
|
|
|
check_cower();
|
|
if ( no_turn || !allow_turn ) {
|
|
lookAt( talkTo, 0.1 );
|
|
} else {
|
|
faceTowardsEntity( talkTo, 0.1 );
|
|
}
|
|
waitFrame();
|
|
}
|
|
endHeadAnim( blendout );
|
|
setTalkTarget( $null_entity );
|
|
|
|
talktime = getFloatKey( "talktime" );
|
|
currentTime = sys.getTime();
|
|
endtime = currentTime + talktime;
|
|
while( ( currentTime < endtime ) && ( distanceTo( talkTo ) <= talkradius ) ) {
|
|
check_cower();
|
|
if ( no_turn || !allow_turn ) {
|
|
lookAt( talkTo, 0.1 );
|
|
} else {
|
|
faceTowardsEntity( talkTo, 0.1 );
|
|
}
|
|
if ( AI_TALK ) {
|
|
allowMovement( false );
|
|
return;
|
|
}
|
|
if ( AI_PUSHED ) {
|
|
getOutOfWay();
|
|
}
|
|
waitFrame();
|
|
currentTime = sys.getTime();
|
|
}
|
|
|
|
lookAt( $null_entity, 0 );
|
|
turnTo( originalAngles_y );
|
|
allowMovement( false );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::idle_followPathEntities
|
|
=====================
|
|
*/
|
|
void character::idle_followPathEntities( entity pathnode ) {
|
|
current_path = pathnode;
|
|
while( current_path ) {
|
|
check_cower();
|
|
executePathCommand( current_path );
|
|
if ( AI_TALK ) {
|
|
setState( "state_Idle" );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::executePathCommand
|
|
=====================
|
|
*/
|
|
void character::executePathCommand( entity pathnode ) {
|
|
string nodeaction;
|
|
|
|
current_path = pathnode;
|
|
next_path = current_path.randomPath();
|
|
nodeaction = current_path.getKey( "classname" );
|
|
if ( !hasFunction( nodeaction ) ) {
|
|
sys.warning( "'" + getName() + "' encountered an unsupported path entity '" + nodeaction + "' on entity '" + current_path.getName() + "'\n" );
|
|
return;
|
|
}
|
|
|
|
//sys.print( getName() + " : " + current_path.getName() + " : " + nodeaction + "\n" );
|
|
|
|
skipOutOfPath = current_path.getIntKey( "skip" );
|
|
|
|
if ( current_path.getIntKey( "no_cower" ) ) {
|
|
no_cower = true;
|
|
}
|
|
if ( current_path.getIntKey( "no_talk" ) && can_talk ) {
|
|
setTalkState( TALK_BUSY );
|
|
}
|
|
|
|
callFunction( nodeaction );
|
|
|
|
if ( AI_TALK ) {
|
|
cancelPathCommand();
|
|
setState( "state_Idle" );
|
|
}
|
|
|
|
finishPathCommand();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::finishPathCommand
|
|
=====================
|
|
*/
|
|
void character::finishPathCommand() {
|
|
string triggername;
|
|
entity triggerent;
|
|
|
|
// trigger any entities the path had targeted
|
|
triggername = current_path.getKey( "trigger" );
|
|
if ( triggername != "" ) {
|
|
triggerent = sys.getEntity( triggername );
|
|
if ( triggerent ) {
|
|
triggerent.activate( self );
|
|
}
|
|
}
|
|
current_path = next_path;
|
|
|
|
skipOutOfPath = false;
|
|
if ( can_talk ) {
|
|
setTalkState( TALK_OK );
|
|
}
|
|
no_cower = no_cower_saved;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::cancelPathCommand
|
|
=====================
|
|
*/
|
|
void character::cancelPathCommand() {
|
|
if ( skipOutOfPath ) {
|
|
finishPathCommand();
|
|
}
|
|
|
|
skipOutOfPath = false;
|
|
if ( can_talk ) {
|
|
setTalkState( TALK_OK );
|
|
}
|
|
no_cower = no_cower_saved;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::getOutOfWay
|
|
=====================
|
|
*/
|
|
boolean character::getOutOfWay() {
|
|
vector org;
|
|
vector oldorigin;
|
|
vector pos;
|
|
vector ang;
|
|
vector dir;
|
|
|
|
if ( ignore_push ) {
|
|
return false;
|
|
}
|
|
|
|
// check if we were pushed in the last frame
|
|
if ( ( sys.getTime() - lastPushTime ) > GAME_FRAMETIME ) {
|
|
// start the push timer
|
|
lastPushTime = sys.getTime();
|
|
pushTime = lastPushTime + CHAR_PUSH_DELAY;
|
|
return false;
|
|
}
|
|
|
|
// update the last push time so next frame we know we were pushed this frame
|
|
lastPushTime = sys.getTime() + GAME_FRAMETIME;
|
|
|
|
// check if we've been pushed long enough
|
|
if ( sys.getTime() < pushTime ) {
|
|
return false;
|
|
}
|
|
|
|
// choose a point inside the aas
|
|
oldorigin = getOrigin();
|
|
org = pushPointIntoAAS( oldorigin );
|
|
|
|
// determine if there's a direction we can walk
|
|
setOrigin( org );
|
|
for( ang_y = 0; ang_y < 360; ang_y += 45 ) {
|
|
dir = sys.angToForward( ang );
|
|
pos = pushPointIntoAAS( org + dir * CHAR_GETOUTOFWAY_DIST );
|
|
if ( testMoveToPosition( pos ) ) {
|
|
break;
|
|
}
|
|
}
|
|
setOrigin( oldorigin );
|
|
|
|
if ( ang_y >= 360 ) {
|
|
// no direction that we can move in, so don't do anything
|
|
return false;
|
|
}
|
|
|
|
// make sure we're inside the aas
|
|
moveToPosition( org );
|
|
while( !AI_MOVE_DONE ) {
|
|
check_cower();
|
|
if ( AI_TALK ) {
|
|
setState( "state_Idle" );
|
|
}
|
|
waitFrame();
|
|
}
|
|
|
|
turnToPos( pos );
|
|
|
|
// slide him the rest of the way to make sure we're fully in the aas
|
|
if ( org != getOrigin() ) {
|
|
slideTo( org, 0.2 );
|
|
while( !AI_MOVE_DONE ) {
|
|
check_cower();
|
|
if ( AI_TALK ) {
|
|
setState( "state_Idle" );
|
|
}
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
// move out of the way
|
|
moveToPosition( pos );
|
|
while( !AI_MOVE_DONE ) {
|
|
check_cower();
|
|
if ( AI_TALK ) {
|
|
setState( "state_Idle" );
|
|
}
|
|
waitFrame();
|
|
}
|
|
|
|
// turn around so we face the way we came
|
|
turnTo( getCurrentYaw() + 180 );
|
|
|
|
stopMove();
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::checkBlocked
|
|
=====================
|
|
*/
|
|
void character::checkBlocked() {
|
|
entity obstacle;
|
|
vector playerMoveDir;
|
|
character npc;
|
|
float allow_walk_dist;
|
|
float end_time;
|
|
boolean waitUntilPathClear;
|
|
float channel;
|
|
vector obstacle_pos;
|
|
vector delta;
|
|
|
|
if ( dont_push_others ) {
|
|
if ( moveStatus() >= MOVE_STATUS_BLOCKED_BY_OBJECT ) {
|
|
// just wait for the path to be clear
|
|
obstacle = getObstacle();
|
|
|
|
if ( moveStatus() == MOVE_STATUS_BLOCKED_BY_OBJECT ) {
|
|
kickObstacles( getObstacle(), 60 );
|
|
return;
|
|
}
|
|
|
|
obstacle_pos = obstacle.getOrigin();
|
|
saveMove();
|
|
stopMove();
|
|
while( 1 ) {
|
|
delta = obstacle_pos - obstacle.getOrigin();
|
|
delta_z = 0;
|
|
if ( sys.vecLength( delta ) > 4 ) {
|
|
restoreMove();
|
|
if ( moveStatus() < MOVE_STATUS_BLOCKED_BY_OBJECT ) {
|
|
break;
|
|
}
|
|
if ( getObstacle() != obstacle ) {
|
|
break;
|
|
}
|
|
stopMove();
|
|
}
|
|
check_cower();
|
|
if ( AI_PUSHED ) {
|
|
getOutOfWay();
|
|
}
|
|
if ( AI_TALK ) {
|
|
setState( "state_Idle" );
|
|
}
|
|
waitFrame();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
waitUntilPathClear = false;
|
|
while( moveStatus() >= MOVE_STATUS_BLOCKED_BY_OBJECT ) {
|
|
obstacle = getObstacle();
|
|
npc = obstacle;
|
|
if ( npc ) {
|
|
if ( npc.ignore_push ) {
|
|
// can't push this guy
|
|
break;
|
|
}
|
|
} else if ( moveStatus() == MOVE_STATUS_BLOCKED_BY_OBJECT ) {
|
|
kickObstacles( getObstacle(), 60 );
|
|
break;
|
|
}
|
|
|
|
obstacle_pos = obstacle.getOrigin();
|
|
|
|
saveMove();
|
|
stopMove();
|
|
|
|
channel = playHeadAnim( "talk_excuseme", 8 );
|
|
|
|
// prevent an infinite loop if we don't have an excuse me anim
|
|
waitFrame();
|
|
|
|
while( !animDone( channel, 8 ) ) {
|
|
check_cower();
|
|
faceTowardsEntity( obstacle, 0.1 );
|
|
waitFrame();
|
|
}
|
|
endHeadAnim( 8 );
|
|
|
|
if ( npc ) {
|
|
npc.AI_PUSHED = true;
|
|
npc.lastPushTime = sys.getTime();
|
|
npc.pushTime = sys.getTime();
|
|
allow_walk_dist = CHAR_ALLOW_WALK_DIST_NPC;
|
|
} else {
|
|
allow_walk_dist = CHAR_ALLOW_WALK_DIST;
|
|
}
|
|
|
|
end_time = sys.getTime() + CHAR_WAIT_BLOCKED;
|
|
while( ( sys.getTime() < end_time ) || AI_PUSHED || waitUntilPathClear ) {
|
|
check_cower();
|
|
faceTowardsEntity( obstacle, 0.1 );
|
|
|
|
delta = obstacle_pos - obstacle.getOrigin();
|
|
delta_z = 0;
|
|
|
|
if ( sys.vecLength( delta ) < 4 ) {
|
|
if ( sys.getTime() > end_time ) {
|
|
break;
|
|
}
|
|
if ( AI_PUSHED ) {
|
|
if ( getOutOfWay() ) {
|
|
waitUntilPathClear = true;
|
|
end_time = sys.getTime() + CHAR_WAIT_BLOCKED;
|
|
}
|
|
}
|
|
} else {
|
|
restoreMove();
|
|
if ( !AI_OBSTACLE_IN_PATH ) {
|
|
if ( !waitUntilPathClear || ( sys.getTime() > end_time ) ) {
|
|
break;
|
|
}
|
|
} else if ( getObstacle() != obstacle ) {
|
|
break;
|
|
}
|
|
|
|
if ( distanceTo( obstacle ) > allow_walk_dist ) {
|
|
break;
|
|
}
|
|
if ( moveStatus() >= MOVE_STATUS_BLOCKED_BY_OBJECT ) {
|
|
if ( AI_PUSHED ) {
|
|
if ( getOutOfWay() ) {
|
|
waitUntilPathClear = true;
|
|
end_time = sys.getTime() + CHAR_WAIT_BLOCKED;
|
|
}
|
|
}
|
|
}
|
|
stopMove();
|
|
}
|
|
|
|
if ( AI_TALK ) {
|
|
setState( "state_Idle" );
|
|
}
|
|
|
|
waitFrame();
|
|
}
|
|
restoreMove();
|
|
}
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
path functions
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
=====================
|
|
character::path_corner
|
|
=====================
|
|
*/
|
|
void character::path_corner() {
|
|
run = current_path.getIntKey( "run" );
|
|
walkAnim = current_path.getKey( "anim" );
|
|
moveToEntity( current_path );
|
|
waitFrame();
|
|
while( !AI_MOVE_DONE && !AI_TALK ) {
|
|
check_cower();
|
|
checkBlocked();
|
|
moveToEntity( current_path );
|
|
waitFrame();
|
|
}
|
|
walkAnim = "";
|
|
run = false;
|
|
|
|
if ( AI_TALK ) {
|
|
cancelPathCommand();
|
|
setState( "state_Idle" );
|
|
}
|
|
if ( AI_DEST_UNREACHABLE ) {
|
|
// Can't reach
|
|
sys.warning( "entity '" + getName() + "' couldn't reach path_corner '" + current_path.getName() + "'" );
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::path_anim
|
|
=====================
|
|
*/
|
|
void character::path_anim() {
|
|
string animname;
|
|
float ang;
|
|
float blend_in;
|
|
float blend_out;
|
|
float channel;
|
|
|
|
animname = current_path.getKey( "anim" );
|
|
blend_in = current_path.getIntKey( "blend_in" );
|
|
blend_out = current_path.getIntKey( "blend_out" );
|
|
|
|
if ( current_path.getKey( "angle" ) != "" ) {
|
|
ang = current_path.getFloatKey( "angle" );
|
|
turnTo( ang );
|
|
while( !facingIdeal() ) {
|
|
check_cower();
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
if ( current_path.getIntKey( "head_anim" ) ) {
|
|
channel = playHeadAnim( animname, blend_in );
|
|
while( !animDone( channel, blend_out ) ) {
|
|
check_cower();
|
|
if ( AI_TALK ) {
|
|
endHeadAnim( blend_out );
|
|
sys.wait( blend_out / 24 );
|
|
cancelPathCommand();
|
|
setState( "state_Idle" );
|
|
}
|
|
waitFrame();
|
|
}
|
|
endHeadAnim( blend_out );
|
|
} else {
|
|
playCustomAnim( animname, blend_in, blend_out );
|
|
while( inCustomAnim() ) {
|
|
check_cower();
|
|
if ( AI_TALK ) {
|
|
endCustomAnim();
|
|
sys.wait( blend_out / 24 );
|
|
cancelPathCommand();
|
|
setState( "state_Idle" );
|
|
}
|
|
waitFrame();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::path_cycleanim
|
|
=====================
|
|
*/
|
|
void character::path_cycleanim() {
|
|
string animname;
|
|
float ang;
|
|
float blend_in;
|
|
float blend_out;
|
|
float waittime;
|
|
|
|
animname = current_path.getKey( "anim" );
|
|
blend_in = current_path.getIntKey( "blend_in" );
|
|
blend_out = current_path.getIntKey( "blend_out" );
|
|
|
|
waittime = current_path.getFloatKey( "wait" );
|
|
|
|
AI_ACTIVATED = false;
|
|
|
|
if ( current_path.getKey( "angle" ) != "" ) {
|
|
ang = current_path.getFloatKey( "angle" );
|
|
turnTo( ang );
|
|
while( !facingIdeal() ) {
|
|
if ( !waittime && AI_ACTIVATED ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", blend_out );
|
|
return;
|
|
}
|
|
check_cower();
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
playCustomCycle( animname, blend_in );
|
|
|
|
if ( waittime ) {
|
|
waittime += sys.getTime();
|
|
while( sys.getTime() < waittime ) {
|
|
check_cower();
|
|
if ( AI_TALK ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", blend_out );
|
|
sys.wait( blend_out / 24 );
|
|
cancelPathCommand();
|
|
setState( "state_Idle" );
|
|
}
|
|
waitFrame();
|
|
}
|
|
} else {
|
|
while( !AI_ACTIVATED ) {
|
|
check_cower();
|
|
if ( AI_TALK ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", blend_out );
|
|
sys.wait( blend_out / 24 );
|
|
cancelPathCommand();
|
|
setState( "state_Idle" );
|
|
return;
|
|
}
|
|
waitFrame();
|
|
}
|
|
}
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", blend_out );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::path_turn
|
|
=====================
|
|
*/
|
|
void character::path_turn() {
|
|
vector ang;
|
|
|
|
ang = current_path.getAngles();
|
|
turnTo( ang_y );
|
|
|
|
// wait 1 frame so that Legs_Idle can start turning.
|
|
waitFrame();
|
|
|
|
while( !facingIdeal() ) {
|
|
check_cower();
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::path_wait
|
|
=====================
|
|
*/
|
|
void character::path_wait() {
|
|
float waittime;
|
|
|
|
waittime = current_path.getFloatKey( "wait" );
|
|
waittime += sys.getTime();
|
|
while( sys.getTime() < waittime ) {
|
|
check_cower();
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::path_waitfortrigger
|
|
=====================
|
|
*/
|
|
void character::path_waitfortrigger() {
|
|
AI_ACTIVATED = false;
|
|
while( !AI_ACTIVATED ) {
|
|
check_cower();
|
|
if ( AI_TALK ) {
|
|
cancelPathCommand();
|
|
setState( "state_Idle" );
|
|
}
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::path_hide
|
|
=====================
|
|
*/
|
|
void character::path_hide() {
|
|
hide();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::path_show
|
|
=====================
|
|
*/
|
|
void character::path_show() {
|
|
waitUntil( canBecomeSolid() );
|
|
show();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::skip_conversation
|
|
=====================
|
|
*/
|
|
void character::skip_conversation() {
|
|
string classname;
|
|
|
|
classname = current_path.getKey( "classname" );
|
|
while( sys.strLeft( classname, 17 ) == "path_conversation" ) {
|
|
current_path = next_path;
|
|
next_path = next_path.randomPath();
|
|
if ( !next_path ) {
|
|
break;
|
|
}
|
|
classname = current_path.getKey( "classname" );
|
|
}
|
|
stopSound( SND_CHANNEL_VOICE, false );
|
|
if ( head ) {
|
|
head.stopSound( SND_CHANNEL_VOICE, false );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::break_out_of_conversation
|
|
=====================
|
|
*/
|
|
void character::break_out_of_conversation() {
|
|
character listener;
|
|
|
|
// tell our listeners to skip out
|
|
while( next_listener ) {
|
|
listener = next_listener;
|
|
next_listener = listener.next_listener;
|
|
listener.skip_conversation();
|
|
listener.next_listener = $null_entity;
|
|
listener.listening_to_character = $null_entity;
|
|
}
|
|
|
|
skip_conversation();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::path_conversation_listen
|
|
=====================
|
|
*/
|
|
void character::path_conversation_listen() {
|
|
string focus_character;
|
|
string anim;
|
|
boolean no_look;
|
|
float blend_in;
|
|
float blend_out;
|
|
|
|
stopMove();
|
|
|
|
focus_character = current_path.getKey( "focus" );
|
|
listening_to_character = sys.getEntity( focus_character );
|
|
|
|
anim = current_path.getKey( "anim" );
|
|
if ( anim != "" ) {
|
|
blend_in = current_path.getIntKey( "blend_in" );
|
|
blend_out = current_path.getIntKey( "blend_out" );
|
|
playCustomCycle( anim, blend_in );
|
|
}
|
|
|
|
no_look = current_path.getIntKey( "no_look" );
|
|
|
|
// link ourselves into the focus character's listeners
|
|
next_listener = listening_to_character.next_listener;
|
|
listening_to_character.next_listener = self;
|
|
|
|
while( listening_to_character ) {
|
|
if ( AI_TALK ) {
|
|
listening_to_character.break_out_of_conversation();
|
|
cancelPathCommand();
|
|
setState( "state_Idle" );
|
|
}
|
|
check_cower();
|
|
if ( !no_look ) {
|
|
lookAt( listening_to_character, 0.1 );
|
|
}
|
|
waitFrame();
|
|
}
|
|
|
|
if ( anim != "" ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", blend_out );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::path_conversation
|
|
=====================
|
|
*/
|
|
void character::path_conversation() {
|
|
string dialog;
|
|
string anim;
|
|
string focus_character;
|
|
float waitTime;
|
|
entity listener;
|
|
character char_listener;
|
|
float blend_in;
|
|
float blend_out;
|
|
float channel;
|
|
|
|
stopMove();
|
|
|
|
anim = current_path.getKey( "anim" );
|
|
focus_character = current_path.getKey( "focus" );
|
|
waitTime = current_path.getFloatKey( "wait" );
|
|
listener = sys.getEntity( focus_character );
|
|
blend_in = current_path.getIntKey( "blend_in" );
|
|
blend_out = current_path.getIntKey( "blend_out" );
|
|
|
|
// say the conversation line
|
|
dialog = current_path.getKey( "snd_dialog" );
|
|
if ( dialog != "" ) {
|
|
startSoundShader( dialog, SND_CHANNEL_VOICE );
|
|
}
|
|
|
|
setBlendFrames( ANIMCHANNEL_LEGS, blend_in );
|
|
if ( getHead() ) {
|
|
animState( ANIMCHANNEL_HEAD, "Anim_Disable", blend_in );
|
|
}
|
|
|
|
channel = playHeadAnim( anim, blend_in );
|
|
while( !animDone( channel, blend_out ) ) {
|
|
if ( AI_TALK ) {
|
|
break;
|
|
}
|
|
check_cower();
|
|
lookAt( listener, 0.1 );
|
|
waitFrame();
|
|
}
|
|
|
|
endHeadAnim( blend_out );
|
|
|
|
if ( AI_TALK ) {
|
|
break_out_of_conversation();
|
|
cancelPathCommand();
|
|
setState( "state_Idle" );
|
|
}
|
|
|
|
if ( waitTime ) {
|
|
// wait for a bit
|
|
waitTime += sys.getTime();
|
|
while( waitTime > sys.getTime() ) {
|
|
if ( AI_TALK ) {
|
|
break_out_of_conversation();
|
|
cancelPathCommand();
|
|
setState( "state_Idle" );
|
|
}
|
|
check_cower();
|
|
lookAt( listener, 0.1 );
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
// tell our listeners we're done
|
|
while( next_listener ) {
|
|
char_listener = next_listener;
|
|
next_listener = char_listener.next_listener;
|
|
char_listener.next_listener = $null_entity;
|
|
char_listener.listening_to_character = $null_entity;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::path_headanim
|
|
=====================
|
|
*/
|
|
void character::path_headanim() {
|
|
float blendIn;
|
|
|
|
blendIn = current_path.getIntKey( "blend_in" );
|
|
customBlendOut = current_path.getIntKey( "blend_out" );
|
|
customAnim = current_path.getKey( "anim" );
|
|
animState( ANIMCHANNEL_HEAD, "Head_TalkHeadOnly", blendIn );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::path_waitforheadanim
|
|
=====================
|
|
*/
|
|
void character::path_waitforheadanim() {
|
|
string focus_entity;
|
|
entity focus;
|
|
|
|
stopMove();
|
|
|
|
focus_entity = current_path.getKey( "focus" );
|
|
focus = sys.getEntity( focus_entity );
|
|
|
|
while( inAnimState( ANIMCHANNEL_HEAD, "Head_TalkHeadOnly" ) ) {
|
|
if ( AI_TALK ) {
|
|
cancelPathCommand();
|
|
setState( "state_Idle" );
|
|
}
|
|
check_cower();
|
|
lookAt( focus, 0.1 );
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::path_talk
|
|
|
|
forces character to talk to the player
|
|
=====================
|
|
*/
|
|
void character::path_talk() {
|
|
setTalkTarget( $player1 );
|
|
idle_talk();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::path_talk_triggered
|
|
|
|
forces character to say his triggered talk anim to the player
|
|
=====================
|
|
*/
|
|
void character::path_talk_triggered() {
|
|
talkMode = CHAR_TALK_TRIGGERED;
|
|
setTalkTarget( $player1 );
|
|
idle_talk();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::path_talk_primary
|
|
|
|
forces character to say his primary talk anim to the player
|
|
=====================
|
|
*/
|
|
void character::path_talk_primary() {
|
|
talkMode = CHAR_TALK_PRIMARY;
|
|
setTalkTarget( $player1 );
|
|
idle_talk();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::path_talk_secondary
|
|
|
|
forces character to say his secondary talk anim to the player
|
|
=====================
|
|
*/
|
|
void character::path_talk_secondary() {
|
|
talkMode = CHAR_TALK_SECONDARY;
|
|
setTalkTarget( $player1 );
|
|
idle_talk();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::path_lookat
|
|
=====================
|
|
*/
|
|
void character::path_lookat() {
|
|
string dialog;
|
|
string anim;
|
|
string focus_entity;
|
|
float waitTime;
|
|
entity focus;
|
|
float blend_in;
|
|
float blend_out;
|
|
|
|
stopMove();
|
|
|
|
anim = current_path.getKey( "anim" );
|
|
blend_in = current_path.getIntKey( "blend_in" );
|
|
blend_out = current_path.getIntKey( "blend_out" );
|
|
focus_entity = current_path.getKey( "focus" );
|
|
focus = sys.getEntity( focus_entity );
|
|
waitTime = current_path.getFloatKey( "wait" );
|
|
|
|
if ( anim != "" ) {
|
|
playCustomCycle( anim, blend_in );
|
|
}
|
|
|
|
if ( !waitTime ) {
|
|
AI_ACTIVATED = false;
|
|
while( !AI_ACTIVATED ) {
|
|
if ( AI_TALK ) {
|
|
cancelPathCommand();
|
|
if ( anim != "" ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", blend_out );
|
|
}
|
|
setState( "state_Idle" );
|
|
}
|
|
check_cower();
|
|
lookAt( focus, 0.1 );
|
|
waitFrame();
|
|
}
|
|
} else {
|
|
waitTime += sys.getTime();
|
|
AI_ACTIVATED = false;
|
|
while( waitTime > sys.getTime() ) {
|
|
if ( AI_ACTIVATED ) {
|
|
break;
|
|
}
|
|
if ( AI_TALK ) {
|
|
if ( anim != "" ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", blend_out );
|
|
}
|
|
cancelPathCommand();
|
|
setState( "state_Idle" );
|
|
}
|
|
check_cower();
|
|
lookAt( focus, 0.1 );
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
if ( anim != "" ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", blend_out );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::state_FollowAlternatePath
|
|
=====================
|
|
*/
|
|
void character::state_FollowAlternatePath() {
|
|
endHeadAnim( 8 );
|
|
|
|
idle_followPathEntities( current_path );
|
|
setState( "state_Idle" );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::follow_alternate_path1
|
|
=====================
|
|
*/
|
|
void character::follow_alternate_path1() {
|
|
current_path = getEntityKey( "alt_path1" );
|
|
setState( "state_FollowAlternatePath" );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::follow_alternate_path2
|
|
=====================
|
|
*/
|
|
void character::follow_alternate_path2() {
|
|
current_path = getEntityKey( "alt_path2" );
|
|
setState( "state_FollowAlternatePath" );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::follow_alternate_path3
|
|
=====================
|
|
*/
|
|
void character::follow_alternate_path3() {
|
|
current_path = getEntityKey( "alt_path3" );
|
|
setState( "state_FollowAlternatePath" );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::disable_turning
|
|
=====================
|
|
*/
|
|
void character::disable_turning() {
|
|
allow_turn = false;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
character::enable_turning
|
|
=====================
|
|
*/
|
|
void character::enable_turning() {
|
|
allow_turn = true;
|
|
}
|