doom3-bfg/base/script/ai_character.script
2022-08-27 13:19:00 +02:00

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;
}