mirror of
https://bitbucket.org/seefoe/dsrc.git
synced 2026-07-31 01:15:55 -04:00
589 lines
14 KiB
Plaintext
589 lines
14 KiB
Plaintext
include library.ai_lib;
|
|
include library.chat;
|
|
include library.colors;
|
|
include library.combat;
|
|
include library.combat_consts;
|
|
include library.dot;
|
|
include library.factions;
|
|
include library.group;
|
|
include library.locations;
|
|
include library.pet_lib;
|
|
include library.pet_lib;
|
|
include library.posture;
|
|
include library.scout;
|
|
include library.utils;
|
|
|
|
// @TODO: Remove me
|
|
void doAttack(obj_id ai, obj_id target)
|
|
{
|
|
LOGC(aiLoggingEnabled(ai), "debug_ai", "ai_combat::doAttack() ai(" + ai + ")");
|
|
|
|
startCombat(ai, target);
|
|
}
|
|
|
|
// @TODO: Remove me
|
|
void attackBestTarget(obj_id ai, obj_id target)
|
|
{
|
|
LOGC(aiLoggingEnabled(ai), "debug_ai", "ai_combat::attackBestTarget() ai(" + ai + ")");
|
|
|
|
startCombat(ai, target);
|
|
}
|
|
|
|
// @TODO: Remove me
|
|
void equipRangedWeapon(obj_id ai)
|
|
{
|
|
LOGC(aiLoggingEnabled(ai), "debug_ai", "ai_combat::equipRangedWeapon() ai(" + ai + ")");
|
|
|
|
aiEquipPrimaryWeapon(ai);
|
|
}
|
|
|
|
// @TODO: Remove me
|
|
void equipMeleeWeapon(obj_id ai)
|
|
{
|
|
LOGC(aiLoggingEnabled(ai), "debug_ai", "ai_combat::equipMeleeWeapon() ai(" + ai + ")");
|
|
|
|
aiEquipPrimaryWeapon(ai);
|
|
}
|
|
|
|
boolean goodFaction( obj_id npc, obj_id target )
|
|
{
|
|
|
|
PROFILER_START("goodFaction");
|
|
if ( !pvpCanAttack( npc, target ) )
|
|
{
|
|
PROFILER_STOP("goodFaction");
|
|
return true;
|
|
}
|
|
if ( isPlayer(target) )
|
|
{
|
|
PROFILER_STOP("goodFaction");
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
string npcFaction = factions.getFaction(npc);
|
|
string targetFaction = factions.getFaction(target);
|
|
if (npcFaction != null && targetFaction != null)
|
|
{
|
|
if ( npcFaction.equals(targetFaction))
|
|
{
|
|
PROFILER_STOP("goodFaction");
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( factions.getFactionReaction( npc, target )==factions.REACTION_POSITIVE )
|
|
{
|
|
PROFILER_STOP("goodFaction");
|
|
return true;
|
|
}
|
|
|
|
PROFILER_STOP("goodFaction");
|
|
return false;
|
|
}
|
|
|
|
boolean isGoodTarget( obj_id npc, obj_id target )
|
|
{
|
|
if ( !isIdValid(target) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( ai_lib.isAiDead( target ) )
|
|
{
|
|
//if this is a player, we want to know if/when he recapacitates:
|
|
if ( isPlayer( target ) )
|
|
{
|
|
listenToMessage(target, "handleSawRecapacitation");
|
|
//but if I am a persisted object, I'll quit listening after a bit:
|
|
if ( isObjectPersisted(npc) )
|
|
{
|
|
dictionary parms = new dictionary();
|
|
parms.put( "player", target );
|
|
messageTo( npc, "handleStopListening", parms, 1200, false );
|
|
}
|
|
}
|
|
|
|
//debugSpeakMsg( npc, "too far to shoot");
|
|
return false;
|
|
}
|
|
|
|
if ( !exists( target ) || getDistance( npc, target ) > combat_engine.getMaxCombatRange() || !isInWorld( target ) )
|
|
{
|
|
//debugSpeakMsg( npc, "too far to shoot");
|
|
return false;
|
|
}
|
|
|
|
if ( pet_lib.isPet(npc) && pet_lib.isMyPet( npc, target ))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( goodFaction( npc, target ) || target == npc )
|
|
{
|
|
//debugSpeakMsg( npc, "same faction");
|
|
return false;
|
|
}
|
|
|
|
//I'm inside, he's outside, NPCs can't follow each other properly
|
|
if ( !isPlayer(target) )
|
|
{
|
|
obj_id myCell = ( getLocation(npc)).cell;
|
|
if ( isIdValid(myCell))
|
|
{
|
|
obj_id defenderCell = ( getLocation(target) ).cell;
|
|
if ( !isIdValid(defenderCell))
|
|
{
|
|
//debugSpeakMsg( npc, "i am inside and he is outside" );
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !pvpCanAttack(npc,target))
|
|
{
|
|
debugSpeakMsg( npc, "pvpCannotAttack");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void moveRandom( obj_id npc, obj_id target )
|
|
{
|
|
if ( isIndoors( npc ))
|
|
return;//never do this indoors
|
|
|
|
obj_id weapon = getCurrentWeapon( npc );
|
|
int weaponType = getWeaponType( weapon);
|
|
int weaponCat = combat.getWeaponCategory( weaponType );
|
|
if ( weaponCat != combat.RANGED_WEAPON )
|
|
{
|
|
PROFILER_STOP("moveRandom");
|
|
return;
|
|
}
|
|
|
|
int currentPosture = getPosture( npc );
|
|
if ( currentPosture == POSTURE_KNOCKED_DOWN )
|
|
{
|
|
removeObjVar( npc, "ai.combat.moveMode" );
|
|
PROFILER_STOP("moveRandom");
|
|
return;
|
|
}
|
|
else if ( currentPosture != POSTURE_UPRIGHT )
|
|
{
|
|
//debugSpeakMsg( npc, "stopping to stand so I can move randomly" );
|
|
stop( npc );
|
|
posture.stand(npc);
|
|
setObjVar( npc, "ai.combat.moveMode", ai_combat_movement.MOVEMODE_PREPARING_TO_STAND );
|
|
PROFILER_STOP("moveRandom");
|
|
return;
|
|
}
|
|
|
|
location npcLoc = new location( getLocation( npc ) );
|
|
location targetLoc = getLocation( target );
|
|
|
|
if ( targetLoc.x > npcLoc.x )
|
|
npcLoc.x -= rand( 5, 15 );
|
|
else
|
|
npcLoc.x += rand( 5, 15 );
|
|
|
|
if ( targetLoc.z > npcLoc.z )
|
|
npcLoc.z -= rand( 5, 15 );
|
|
else
|
|
npcLoc.z += rand( 5, 15 );
|
|
|
|
setObjVar( npc, "ai.combat.moveMode", ai_combat_movement.MOVEMODE_RANDOM );
|
|
messageTo( npc, "handleMoveRandomClear", null, 30, false );
|
|
PROFILER_START("pathTo");
|
|
pathTo( npc, npcLoc );
|
|
PROFILER_STOP("pathTo");
|
|
PROFILER_STOP("moveRandom");
|
|
}
|
|
|
|
//void aiThrowGrenade( obj_id npc, obj_id target )
|
|
//{
|
|
// int niche = ai_lib.aiGetNiche( npc );
|
|
// if ( niche != NICHE_NPC )
|
|
// {
|
|
// removeObjVar( npc, "ai.grenade" );
|
|
// removeObjVar( npc, "ai.grenadeType" );
|
|
// return;
|
|
// }
|
|
// obj_id grenade = getObjIdObjVar( npc, "ai.grenade" );
|
|
// if ( grenade == null )
|
|
// {
|
|
// removeObjVar( npc, "ai.grenade" );
|
|
// removeObjVar( npc, "ai.grenadeType" );
|
|
// return;
|
|
// }
|
|
// string parms = grenade.toString();
|
|
// queueCommand(npc, ##"throwGrenade", target , parms, COMMAND_PRIORITY_FRONT);
|
|
//}
|
|
|
|
void setCombatLocation( obj_id npc )
|
|
{
|
|
setObjVar( npc, "ai.combat.combatStartLoc", getLocation( npc ));
|
|
}
|
|
|
|
location getCombatLocation( obj_id npc )
|
|
{
|
|
return getLocationObjVar( npc, "ai.combat.combatStartLoc");
|
|
}
|
|
|
|
boolean attackInCircleFormation( obj_id npc, obj_id target )
|
|
{
|
|
//debugSpeakMsg( npc, "1" );
|
|
//if ( isRooted( npc ) )
|
|
//{
|
|
// //debugSpeakMsg( npc, "i am rooted" );
|
|
// return false;
|
|
//}
|
|
//debugSpeakMsg( npc, "2" );
|
|
if (!hasObjVar( npc, "ai.inFormation") && ( !hasObjVar( npc, "ai.combat.tempFormationCombat" )))
|
|
{
|
|
//debugSpeakMsg( npc, "i am not in a formation" );
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
4 1 5
|
|
2 x 0
|
|
6 3 7
|
|
*/
|
|
//debugSpeakMsg( npc, "3" );
|
|
if (hasObjVar( npc, "ai.combat.noRangedWeapon"))
|
|
{
|
|
//debugSpeakMsg( npc, "i have no ranged weapon" );
|
|
return false;
|
|
}
|
|
|
|
//debugSpeakMsg( npc, "4" );
|
|
//if you're in Melee combat but your opponent is not targetting you
|
|
// then equip a ranged weapon and get back in formation:
|
|
obj_id weapon = getCurrentWeapon( npc );
|
|
int weaponType = getWeaponType( weapon);
|
|
int weaponCat = combat.getWeaponCategory( weaponType );
|
|
if ( weaponCat != combat.RANGED_WEAPON )
|
|
{
|
|
if ( getHateTarget( target ) != npc )
|
|
{
|
|
//debugSpeakMsg( npc, "xx: 100" );
|
|
//unequipWeapon( npc );
|
|
}
|
|
}
|
|
|
|
weapon = getCurrentWeapon( npc );
|
|
float maxRange = getMaxRange( weapon )/2.0f;
|
|
float offset = 20.0f;
|
|
if ( maxRange < 20.0f )
|
|
offset = maxRange;
|
|
|
|
//debugSpeakMsg( npc, "best distance is " + offset );
|
|
|
|
swarm(npc, target, offset);
|
|
|
|
setObjVar( npc, "ai.combat.moveMode", ai_combat_movement.MOVEMODE_FOLLOW_FORMATION );
|
|
//debugSpeakMsg( npc, "i am now in formation!" );
|
|
return true;
|
|
}
|
|
|
|
boolean hasRangedWeapon( obj_id npc )
|
|
{
|
|
obj_id weapon = getCurrentWeapon( npc );
|
|
int weaponType = getWeaponType( weapon);
|
|
int weaponCat = combat.getWeaponCategory( weaponType );
|
|
return ( weaponCat == combat.RANGED_WEAPON );
|
|
}
|
|
|
|
void aiCombatFlee( obj_id npc, obj_id target, float minRange, float maxRange )
|
|
{
|
|
if (!isIdValid( npc ) || !exists( npc ) )
|
|
return;
|
|
|
|
if ( !isIdValid( target ) || !exists( npc ) )
|
|
return;
|
|
|
|
location myLoc = new location ( getLocation( npc ) );
|
|
if ( myLoc == null )
|
|
return;
|
|
|
|
if ( isIdValid( myLoc.cell ))
|
|
return;
|
|
|
|
location destLoc = new location( myLoc );
|
|
location targetLoc = new location( getLocation( target ));
|
|
if ( targetLoc == null )
|
|
return;
|
|
|
|
if ( targetLoc.x < destLoc.x )
|
|
destLoc.x+= rand(minRange,maxRange);
|
|
else
|
|
destLoc.x-= rand(minRange,maxRange);
|
|
if ( targetLoc.z < destLoc.z )
|
|
destLoc.z+= rand(minRange,maxRange);
|
|
else
|
|
destLoc.z-= rand(minRange,maxRange);
|
|
|
|
pathTo( npc, destLoc );
|
|
}
|
|
|
|
boolean switchToFormationCombat( obj_id npc, obj_id target )
|
|
{
|
|
if ( hasObjVar( npc, "ai.combat.tempFormationCombat" ))
|
|
return true;
|
|
|
|
if ( !isPlayer( target ) )
|
|
return false;//NPCs really suck when they try this on each other
|
|
|
|
int combatRound = utils.getIntScriptVar( npc, "aiCombatRoundCounter" );
|
|
if ( combatRound < 5 )
|
|
{
|
|
//debugSpeakMsg( npc, "waiting..." );
|
|
return false;//don't make this check for at least 5 rounds:
|
|
}
|
|
else if ( combatRound > 10 )
|
|
{
|
|
//debugSpeakMsg( npc, "not checking any more" );
|
|
return false;//stop checking after 10 rounds
|
|
}
|
|
|
|
// Decide whether to switch:
|
|
obj_id[] attackerList = getWhoIsTargetingMe( target );
|
|
if ( attackerList == null || attackerList.length < 2 )
|
|
return false;
|
|
|
|
int niche = ai_lib.aiGetNiche(npc);
|
|
if ( niche != NICHE_DROID )
|
|
{
|
|
chat.setAngryMood( npc );
|
|
ai_lib.barkString( npc, "assist" );//droid slang variants dont have this
|
|
}
|
|
|
|
for ( int i = 0; i < attackerList.length; i++ )
|
|
{
|
|
//flag them all, including me!
|
|
if ( !isPlayer( attackerList[i] ) )
|
|
{
|
|
removeObjVar( attackerList[i], "ai.combat.moveMode" );
|
|
setObjVar( attackerList[i], "ai.combat.tempFormationCombat", i );
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
boolean doHealing( obj_id npc, obj_id target )
|
|
{
|
|
utils.setScriptVar( npc, "ai.nextHeal", (getGameTime()+20) );
|
|
//obj_id yourTarget = getHateTarget( target );
|
|
//if ( yourTarget == npc )
|
|
// return false;// I'm busy being shot
|
|
|
|
obj_id[] myFriends = getWhoIsTargetingMe( target );
|
|
if ( myFriends == null || myFriends.length == 0 )
|
|
return false;// There's no way this could possible happen
|
|
|
|
//choose someone to heal:
|
|
|
|
obj_id healTarget = getHealTarget( npc, myFriends );
|
|
if ( !isIdValid( healTarget ))
|
|
return false;
|
|
|
|
if ( healTarget == npc )
|
|
{
|
|
//debugSpeakMsg( npc, "i heal myself!");
|
|
executeHealingMove( npc, npc );
|
|
}
|
|
else if ( getDistance( npc, healTarget ) > 2.0f )
|
|
{
|
|
//debugSpeakMsg( npc, "i heal " + getName(healTarget));
|
|
//debugSpeakMsg( npc, "not close enough, so I follow healTarget" );
|
|
setObjVar( npc, "ai.combat.ignoreCombat", true );
|
|
utils.setScriptVar( npc, "ai.combat.pathingToHeal", healTarget );
|
|
follow( npc, healTarget, 1, 1.5f );
|
|
setObjVar( npc, "ai.combat.moveMode", ai_combat_movement.MOVEMODE_MOVING_TO_HEAL );
|
|
}
|
|
return true;
|
|
}
|
|
|
|
obj_id getHealTarget( obj_id npc, obj_id[] myFriends )
|
|
{
|
|
for ( int i = 0; i < myFriends.length; i++ )
|
|
{
|
|
if ( getDistance( npc, myFriends[i] ) < 60.0f )
|
|
{
|
|
int healthDamage = getAttribDamage(myFriends[i], HEALTH);
|
|
if ( healthDamage < (getMaxAttrib( myFriends[i], HEALTH )/2) )
|
|
healthDamage = 0;
|
|
|
|
int actionDamage = getAttribDamage(myFriends[i], ACTION);
|
|
if ( actionDamage < (getMaxAttrib( myFriends[i], ACTION )/2) )
|
|
actionDamage = 0;
|
|
|
|
if ( healthDamage != 0 || actionDamage != 0 )
|
|
{
|
|
return myFriends[i];//you're wounded so I'll heal you
|
|
}
|
|
}
|
|
}
|
|
return null;//found no one to heal
|
|
}
|
|
|
|
void executeHealingMove( obj_id npc, obj_id target )
|
|
{
|
|
if (!isIdValid(npc) || ai_lib.aiIsDead(npc))
|
|
return;
|
|
|
|
if ( (!isIdValid( target )) || ai_lib.aiIsDead(target) )
|
|
{
|
|
//debugSpeakMsg( npc, "stopping because healing target is gone" );
|
|
removeObjVar( npc, "ai.combat.ignoreCombat" );
|
|
utils.removeScriptVar( npc, "ai.combat.pathingToHeal" );
|
|
stop(npc);
|
|
removeObjVar( npc, "ai.combat.moveMode" );
|
|
return;
|
|
}
|
|
//debugSpeakMsg( npc, "stopping to execute healing move" );
|
|
removeObjVar( npc, "ai.combat.moveMode" );
|
|
stop(npc);
|
|
utils.removeScriptVar( npc, "ai.combat.pathingToHeal" );
|
|
utils.setScriptVar( npc, "ai.nextHeal", (getGameTime()+20) );
|
|
|
|
if ( ai_lib.aiIsDead( target ) || ai_lib.aiIsDead(npc) )
|
|
{
|
|
//debugSpeakMsg( npc, "stopping because healing target is dead" );
|
|
stop(npc);
|
|
removeObjVar( npc, "ai.combat.ignoreCombat" );
|
|
utils.removeScriptVar( npc, "ai.combat.pathingToHeal" );
|
|
return;
|
|
}
|
|
|
|
if ( getDistance( npc, target ) > 2.0f )
|
|
{
|
|
setObjVar( npc, "ai.combat.ignoreCombat", true );
|
|
utils.setScriptVar( npc, "ai.combat.pathingToHeal", target );
|
|
setObjVar( npc, "ai.combat.moveMode", ai_combat_movement.MOVEMODE_MOVING_TO_HEAL );
|
|
follow( npc, target, 1, 2 );
|
|
return;
|
|
}
|
|
|
|
//debugSpeakMsg( npc, "healing " + getName( target ) );
|
|
|
|
removeObjVar( npc, "ai.combat.ignoreCombat" );
|
|
|
|
if ( target == npc )
|
|
{
|
|
doAnimationAction( npc, "heal_self" );
|
|
}
|
|
else
|
|
{
|
|
//faceTo(npc,target);
|
|
doAnimationAction( npc, "heal_other" );
|
|
}
|
|
|
|
location loc = getLocation( target );
|
|
obj_id[] players = getAllPlayers(loc, 45.0f);
|
|
if (players != null)
|
|
{
|
|
for (int i = 0; i < players.length; i++)
|
|
{
|
|
playClientEffectLoc(players[i], "clienteffect/healing_healdamage.cef" , loc, 0);
|
|
}
|
|
}
|
|
|
|
int healthDamage = getAttribDamage(target, HEALTH);
|
|
int actionDamage = getAttribDamage(target, ACTION);
|
|
|
|
int healAmt = (ai_lib.getLevel( npc ) * 20);
|
|
if ( healAmt < healthDamage )
|
|
addToAttrib(target, HEALTH, healAmt);
|
|
else
|
|
setAttrib(target, HEALTH, getMaxAttrib( target, HEALTH ));
|
|
|
|
if ( healAmt < actionDamage )
|
|
addToAttrib(target, ACTION, healAmt);
|
|
else
|
|
setAttrib(target, ACTION, getMaxAttrib( target, ACTION ));
|
|
|
|
utils.setScriptVar( npc, "ai.combat.movePause", 2 );
|
|
}
|
|
|
|
boolean fleeFromHouse( obj_id npc, obj_id target )
|
|
{
|
|
|
|
if ( !isIdValid(npc) || !isIdValid( target ) )
|
|
return false;
|
|
|
|
if ( pet_lib.isPet(npc) )
|
|
return false;//pets don't do this
|
|
|
|
location myLoc = getLocation( npc );
|
|
if ( myLoc == null )
|
|
return false;
|
|
|
|
location yourLoc = getLocation( target );
|
|
if ( yourLoc == null )
|
|
return false;
|
|
|
|
obj_id myCell = myLoc.cell;
|
|
obj_id yourCell = yourLoc.cell;
|
|
if (isIdValid( myCell )==false && isIdValid( yourCell ) )
|
|
{
|
|
//debugSpeakMsg( npc, "I am outside and you are inside" );
|
|
|
|
int niche = ai_lib.aiGetNiche( npc );
|
|
if ( niche == NICHE_NPC || niche == NICHE_DROID || niche == NICHE_ANDROID)
|
|
{
|
|
if (permissionsIsAllowed(yourCell, npc))
|
|
return false;//npcs and droids will follow into the building, if they can
|
|
}
|
|
|
|
//ai_combat.fleeFromCombat( npc, target );
|
|
pathTo( npc, aiGetHomeLocation( npc ));
|
|
messageTo(npc, "redoYaw", null, 30, false);
|
|
|
|
setAttrib( npc, HEALTH, getUnmodifiedMaxAttrib( npc, HEALTH ) );
|
|
setAttrib( npc, ACTION, getUnmodifiedMaxAttrib( npc, ACTION ) );
|
|
|
|
if ( isPlayer( target ) )
|
|
{
|
|
if ( getHateTarget(target)==npc);
|
|
queueCommand( target, ##"peace", npc, "", COMMAND_PRIORITY_IMMEDIATE);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
boolean doInteriorPathFinding( obj_id npc, obj_id target )
|
|
{
|
|
location npcLoc = getLocation( npc );
|
|
if (!isIdValid( npcLoc.cell ))
|
|
return false;//outdoors, so do the normal shit
|
|
|
|
return ( getDistance( npc, target ) > 6.0f );//too far away to trust the
|
|
// can-see check, so
|
|
}
|
|
|
|
boolean isIndoors( obj_id npc )
|
|
{
|
|
location npcLoc = getLocation( npc );
|
|
return (isIdValid( npcLoc.cell ));
|
|
}
|
|
|
|
boolean isInCombat(obj_id ai)
|
|
{
|
|
boolean result = (getState(ai, STATE_COMBAT) == 1);
|
|
|
|
if (!result)
|
|
{
|
|
deltadictionary dict = ai.getScriptVars();
|
|
|
|
result = (dict.getObjVar("ai.combat.pendingCombat") != null);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|