mirror of
https://github.com/SWG-Source/dsrc.git
synced 2026-07-31 00:15:54 -04:00
469 lines
13 KiB
Plaintext
469 lines
13 KiB
Plaintext
include library.ai_lib;
|
|
include ai.ai_combat;
|
|
include library.factions;
|
|
include library.utils;
|
|
include library.attrib;
|
|
|
|
const string SOCIAL_VOLUME = "npc_socialization";
|
|
const float SOCIAL_RANGE = 15.0f;
|
|
const string ACTION_ALERT = "alert";
|
|
const string ACTION_THREATEN = "threaten";
|
|
const string CREATURE_TABLE = "datatables/mob/creatures.iff";
|
|
|
|
void initializeScript()
|
|
{
|
|
obj_id self = getSelf();
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "tusken_raider::initializeScript() self(" + self + ":" + getName(self) + ")");
|
|
|
|
createTriggerVolume(SOCIAL_VOLUME, SOCIAL_RANGE, false);
|
|
|
|
cancelFollowing(self);
|
|
|
|
setAttributeInterested(self, attrib.TUSKEN_RAIDER);
|
|
setAttributeInterested(self, attrib.HERBIVORE);
|
|
setAttributeAttained(self, attrib.TUSKEN_RAIDER);
|
|
setAttributeAttained(self, attrib.THUG);//so guard-types will attack them
|
|
}
|
|
|
|
/**
|
|
* This is called when a a script gets attached to the AI and the AI
|
|
* is not being restored from a persisted state. When an AI is restored
|
|
* from a persisted state, OnAddedToWorld() gets called.
|
|
*/
|
|
trigger OnAttach()
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "tusken_raider::OnAttach() self(" + self + ":" + getName(self) + ")");
|
|
|
|
initializeScript();
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
/**
|
|
* This is when an AI gets restored from a persisted state. This will
|
|
* not be called when an AI is first created because this script is
|
|
* not attached yet. When the object is first created, OnAttach() is
|
|
* called.
|
|
*/
|
|
trigger OnAddedToWorld()
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "tusken_raider::OnAddedToWorld() self(" + self + ":" + getName(self) + ")");
|
|
|
|
initializeScript();
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnAiTetherStart()
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "tusken_raider::OnAiTetherStart() self(" + self + ":" + getName(self) + ")");
|
|
|
|
removeTriggerVolume(SOCIAL_VOLUME);
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnAiTetherComplete()
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "tusken_raider::OnAiTetherComplete() self(" + self + ":" + getName(self) + ")");
|
|
|
|
if (getConfigSetting("GameServer", "disableAITriggerVolumes") == null)
|
|
{
|
|
createTriggerVolume(SOCIAL_VOLUME, SOCIAL_RANGE, false);
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnTriggerVolumeEntered( string volumeName, obj_id breacher )
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "tusken_raider::OnTriggerVolumeEntered() self(" + self + ":" + getName(self) + ") volumeName(" + volumeName + ") breacher(" + breacher + ":" + getName(breacher) + ")");
|
|
|
|
if (hasObjVar(breacher, "gm"))
|
|
return SCRIPT_CONTINUE;
|
|
|
|
if (ai_lib.isInCombat(self))
|
|
return SCRIPT_CONTINUE;
|
|
|
|
if ( breacher == self )
|
|
return SCRIPT_CONTINUE;
|
|
|
|
if ( isIncapacitated(self) )
|
|
return SCRIPT_CONTINUE;
|
|
|
|
if ( getIntObjVar( self, "ai.defaultCalmBehavior" ) == ai_lib.BEHAVIOR_SENTINEL )
|
|
return SCRIPT_CONTINUE;
|
|
|
|
if ( volumeName == ai_lib.ALERT_VOLUME_NAME )
|
|
{
|
|
if ( hasScript( breacher, "ai.tusken_raider") )
|
|
{
|
|
if ( getIntObjVar( breacher, "ai.defaultCalmBehavior" ) == ai_lib.BEHAVIOR_SENTINEL )
|
|
return SCRIPT_CONTINUE;
|
|
addTriggerVolumeEventSource(SOCIAL_VOLUME, breacher);
|
|
}
|
|
else
|
|
{
|
|
boolean isBantha = false;
|
|
const string creatureName = getCreatureName(breacher);
|
|
|
|
if (creatureName != null)
|
|
{
|
|
string species = dataTableGetString( CREATURE_TABLE, creatureName, "socialGroup" );
|
|
if ( (species != null) && species.equals( "bantha" ) )
|
|
isBantha = true;
|
|
}
|
|
|
|
if ( isBantha )
|
|
{
|
|
takeBantha( self, breacher );
|
|
return SCRIPT_OVERRIDE;
|
|
}
|
|
}
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
else if (volumeName == SOCIAL_VOLUME)
|
|
{
|
|
if (!ai_lib.isInCombat(breacher))
|
|
{
|
|
if (getIntObjVar(breacher, "ai.defaultCalmBehavior") != ai_lib.BEHAVIOR_SENTINEL)
|
|
{
|
|
initiateDialog(self, breacher);
|
|
}
|
|
}
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnTriggerVolumeExited(string volumeName, obj_id breacher)
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "tusken_raider::OnTriggerVolumeExited() self(" + self + ":" + getName(self) + ") volumeName(" + volumeName + ") breacher(" + breacher + ":" + getName(breacher) + ")");
|
|
|
|
if (volumeName == ai_lib.ALERT_VOLUME_NAME)
|
|
{
|
|
if (hasScript( breacher, "ai.tusken_raider"))
|
|
{
|
|
removeTriggerVolumeEventSource(SOCIAL_VOLUME, breacher);
|
|
}
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
void initiateDialog( obj_id talker, obj_id listener )
|
|
{
|
|
if ( !(isIdValid( talker ) && isIdValid( listener )) )
|
|
return;
|
|
|
|
//only talk to tuskens with a higher obj_id than your own:
|
|
if ( talker.getValue() < listener.getValue() )
|
|
return;
|
|
|
|
if ( utils.hasScriptVar( talker, "ai.speaking") || utils.hasScriptVar( listener, "ai.speaking" ))
|
|
return;
|
|
|
|
if ( getBehavior( talker ) >= BEHAVIOR_THREATEN || getBehavior( listener ) >= BEHAVIOR_THREATEN )
|
|
return;
|
|
|
|
if ( getIntObjVar( listener, "ai.defaultCalmBehavior" ) == ai_lib.BEHAVIOR_SENTINEL ||
|
|
getIntObjVar( talker, "ai.defaultCalmBehavior" ) == ai_lib.BEHAVIOR_SENTINEL )
|
|
return;
|
|
|
|
location pathToLoc = getLocation( listener );
|
|
location myLoc = getLocation( talker );
|
|
if ( pathToLoc == null || myLoc == null )
|
|
{
|
|
string msg = "ai.tusken_raider.initiateDialog(): ";
|
|
string msg2 = "Null location for listener (obj_id: " + listener + " pathToLoc: " + pathToLoc + ") and/or talker (obj_id: " + talker + " myLoc: " + myLoc + ")";
|
|
LOG("DESIGNER_FATAL", msg + msg2);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
utils.setScriptVar( talker, "ai.speaking", true );
|
|
utils.setScriptVar( listener, "ai.speaking", true );
|
|
stop( talker );
|
|
stop( listener );
|
|
faceTo( listener, talker );
|
|
utils.setScriptVar( talker, "ai.pathingToSocialize", listener );
|
|
utils.setScriptVar( listener, "ai.pathingToSocialize", talker );
|
|
|
|
if ( pathToLoc.x < myLoc.x )
|
|
pathToLoc.x += 1.5f;
|
|
else
|
|
pathToLoc.x -= 1.5f;
|
|
|
|
if ( pathToLoc.z < myLoc.z )
|
|
pathToLoc.z += 1.5f;
|
|
else
|
|
pathToLoc.z -= 1.5f;
|
|
pathTo( talker, pathToLoc );
|
|
|
|
//If in 45 seconds these objvars are still set, then we'll kill this
|
|
// socialization and resume default behavior:
|
|
messageTo( talker, "handleSocializingRecovery", null, 45, isObjectPersisted( talker) );
|
|
messageTo( listener, "handleSocializingRecovery", null, 45, isObjectPersisted(listener) );
|
|
}
|
|
}
|
|
|
|
messageHandler handleSocializingRecovery()
|
|
{
|
|
if ( utils.hasScriptVar( self, "ai.pathingToSocialize" ))
|
|
{
|
|
ai_lib.setMood( self, ai_lib.MOOD_CALM );
|
|
utils.removeScriptVar( self, "ai.pathingToSocialize" );
|
|
utils.removeScriptVar( self, "ai.speaking" );
|
|
messageTo( self, "resumeLoitering", null, 1, false );
|
|
}
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
|
|
trigger OnMovePathComplete()
|
|
{
|
|
if ( !utils.hasScriptVar( self, "ai.pathingToSocialize"))
|
|
return SCRIPT_CONTINUE;
|
|
|
|
obj_id listener = utils.getObjIdScriptVar( self, "ai.pathingToSocialize");
|
|
faceTo( self, listener);
|
|
faceTo( listener, self );
|
|
ai_lib.doAction( self, "conversation_1" );
|
|
ai_lib.doAction( listener, "conversation_1" );
|
|
ai_lib.setMood( self, ai_lib.MOOD_CALM );
|
|
messageTo( self, "handleEndSocializing", null, 10, false );
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler handleEndSocializing()
|
|
{
|
|
if ( !utils.hasScriptVar( self, "ai.pathingToSocialize"))
|
|
return SCRIPT_CONTINUE;
|
|
|
|
obj_id listener = utils.getObjIdScriptVar( self, "ai.pathingToSocialize");
|
|
endSocializing( self, listener );
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnMovePathNotFound()
|
|
{
|
|
if ( !utils.hasScriptVar( self, "ai.pathingToSocialize"))
|
|
return SCRIPT_CONTINUE;
|
|
|
|
obj_id listener = utils.getObjIdScriptVar( self, "ai.pathingToSocialize");
|
|
endSocializing( self, listener );
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
void endSocializing( obj_id talker, obj_id listener )
|
|
{
|
|
//BUG: Currently this make humans play the flute:
|
|
ai_lib.setMood( talker, ai_lib.MOOD_CALM );
|
|
ai_lib.setMood( listener, ai_lib.MOOD_CALM );
|
|
messageTo( talker, "resumeLoitering", null, 1, false );
|
|
//messageTo( listener, "resumeLoitering", null, 3, true );
|
|
followTusken( listener, talker );//rather than socializing, this sandperson becomes a follower.
|
|
utils.removeScriptVar( talker, "ai.pathingToSocialize" );
|
|
utils.removeScriptVar( listener, "ai.pathingToSocialize" );
|
|
utils.removeScriptVar( talker, "ai.speaking" );
|
|
removeTriggerVolumeEventSource(SOCIAL_VOLUME, listener);
|
|
//removeObjVar( listener, "ai.speaking"); by not removing this objvar,
|
|
// the sandperson will no longer
|
|
// initiate or be invited to socialize
|
|
}
|
|
|
|
trigger OnBehaviorChange( int newBehavior, int oldBehavior, int[] changeFlags )
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "tusken_raider::OnBehaviorChange() self(" + self + ":" + getName(self) + ")");
|
|
|
|
if ( isIncapacitated(self) )
|
|
return SCRIPT_CONTINUE;
|
|
|
|
if ( ai_lib.isInCombat( self ))
|
|
return SCRIPT_OVERRIDE;
|
|
|
|
if ( newBehavior > oldBehavior )
|
|
{
|
|
if ( utils.hasScriptVar( self, "ai.pathingToSocialize") )
|
|
{
|
|
obj_id listener = utils.getObjIdScriptVar( self, "ai.pathingToSocialize");
|
|
endSocializing( self, listener );
|
|
}
|
|
if ( newBehavior >= BEHAVIOR_ALERT && newBehavior < BEHAVIOR_ATTACK )
|
|
{
|
|
doAgitateBehavior( self, newBehavior );
|
|
return SCRIPT_OVERRIDE;//don't do the default AI thing
|
|
}
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
/*
|
|
if ( newBehavior <= BEHAVIOR_ALERT )
|
|
{
|
|
queueCommand(self, ##"combatTarget", null, "", COMMAND_PRIORITY_FRONT);
|
|
}
|
|
*/
|
|
|
|
if ( newBehavior == BEHAVIOR_CALM )
|
|
{
|
|
//debugSpeakMsg( self, "I AM CALM AGAIN" );
|
|
if ( utils.hasObjVar( self, "ai.isFollowing" ))
|
|
{
|
|
//debugSpeakMsg( self, "RESUMING FOLLOWING" );
|
|
//just resume following the guy:
|
|
followTusken( self, getObjIdObjVar( self, "ai.isFollowing" ));
|
|
return SCRIPT_OVERRIDE;//don't do the default AI thing
|
|
}
|
|
//debugSpeakMsg( self, "I AM CALM AGAIN and I AM THE LEADER" );
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
//do default behavior stuff:
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
void doAgitateBehavior( obj_id npc, int behavior )
|
|
{
|
|
if ( isInvulnerable( npc ) )
|
|
return;
|
|
|
|
if ( getConfigSetting("GameServer", "disableAICombat")!=null )
|
|
return;
|
|
|
|
//debugSpeakMsg( npc, "doing custom thing" );
|
|
//obj_id threat = ai_lib.getBehaviorTarget( npc, behavior );
|
|
//if ( threat == null )
|
|
return;
|
|
//else
|
|
// setObjVar( npc, "ai.threat", threat );
|
|
//
|
|
//switch ( behavior )
|
|
//{
|
|
// case BEHAVIOR_ALERT:
|
|
// stop( npc );
|
|
// ai_lib.setMood( npc, ai_lib.MOOD_NERVOUS );
|
|
// break;
|
|
// case BEHAVIOR_THREATEN:
|
|
// faceTo( npc, threat );
|
|
// doAnimationAction( npc, ACTION_THREATEN );
|
|
// //queueCommand(npc, ##"combatTarget", threat, "", COMMAND_PRIORITY_FRONT);
|
|
// ai_lib.setMood( npc, ai_lib.MOOD_THREATEN );
|
|
// break;
|
|
// case BEHAVIOR_FLEE:
|
|
// //queueCommand(npc, ##"combatTarget", null, "", COMMAND_PRIORITY_FRONT);
|
|
// cancelFollowing( npc );
|
|
// startCombat( npc, threat );
|
|
// break;
|
|
// case BEHAVIOR_PANIC:
|
|
// //queueCommand(npc, ##"combatTarget", null, "", COMMAND_PRIORITY_FRONT);
|
|
// cancelFollowing( npc );
|
|
// startCombat( npc, threat );
|
|
// break;
|
|
// default :
|
|
// //debugSpeakMsg( npc, "default: " + behavior );
|
|
// break;
|
|
//}
|
|
}
|
|
|
|
void followTusken( obj_id npc, obj_id target )
|
|
{
|
|
if ( !isIncapacitated(npc) )
|
|
{
|
|
removeTriggerVolumeEventSource(SOCIAL_VOLUME, target);
|
|
if ( !exists( target ) )
|
|
{
|
|
cancelFollowing( npc );
|
|
return;
|
|
}
|
|
|
|
//never follow anything with a lower obj_id that your own:
|
|
if ( target.getValue() < npc.getValue() )
|
|
return;
|
|
|
|
obj_id isFollowedBy = getObjIdObjVar( target, "ai.isFollowedBy" );
|
|
if ( isFollowedBy == null || isFollowedBy == npc )
|
|
{
|
|
setMovementWalk( npc );
|
|
follow( npc, target, 2f, 5f );
|
|
setObjVar( target, "ai.isFollowedBy", npc );
|
|
setObjVar( npc, "ai.isFollowing", target );
|
|
}
|
|
else
|
|
{
|
|
followTusken( npc, isFollowedBy );
|
|
}
|
|
}
|
|
}
|
|
|
|
messageHandler resumeLoitering()
|
|
{
|
|
obj_id isFollowing = getObjIdObjVar( self, "ai.isFollowing" );
|
|
if ( isFollowing == null )
|
|
{
|
|
if ( getBehavior(self) == BEHAVIOR_CALM )
|
|
messageTo( self, "resumeDefaultCalmBehavior", null, 1, false );
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
else
|
|
{
|
|
followTusken( self, isFollowing );
|
|
return SCRIPT_OVERRIDE;
|
|
}
|
|
}
|
|
|
|
void cancelFollowing( obj_id npc )
|
|
{
|
|
utils.removeScriptVar( npc, "ai.speaking" );
|
|
obj_id isFollowing = getObjIdObjVar( npc, "ai.isFollowing" );
|
|
if ( isFollowing != null )
|
|
messageTo( isFollowing, "stopBeingFollowed", null, 0, false );
|
|
|
|
removeObjVar( npc, "ai.isFollowing" );
|
|
}
|
|
|
|
messageHandler stopBeingFollowed()
|
|
{
|
|
removeObjVar( self, "ai.isFollowedBy" );
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler stopFollowing()
|
|
{
|
|
removeObjVar( self, "ai.isFollowing" );
|
|
cancelFollowing( self );
|
|
messageTo( self, "resumeLoitering", null, 3, false );
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnFollowTargetLost(obj_id oldTarget)
|
|
{
|
|
cancelFollowing( self );
|
|
messageTo( self, "resumeLoitering", null, 3, false );
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnFollowPathNotFound(obj_id target)
|
|
{
|
|
cancelFollowing( self );
|
|
messageTo( self, "resumeLoitering", null, 3, false );
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
void takeBantha( obj_id tusken, obj_id bantha )
|
|
{
|
|
if ( hasScript( bantha, "ai.tusken_bantha" ))
|
|
return;
|
|
|
|
if ( utils.hasScriptVar( tusken, "ai.bantha" ))
|
|
return;
|
|
|
|
utils.setScriptVar( tusken, "ai.bantha", bantha );
|
|
utils.setScriptVar( bantha, "ai.tusken", tusken );
|
|
attachScript( bantha, "ai.tusken_bantha" );
|
|
}
|
|
|
|
trigger OnIncapacitated(obj_id attacker)
|
|
{
|
|
removeTriggerVolume(SOCIAL_VOLUME);
|
|
|
|
return SCRIPT_CONTINUE;
|
|
} |