mirror of
https://github.com/SWG-Source/dsrc.git
synced 2026-07-31 00:15:54 -04:00
504 lines
13 KiB
Plaintext
504 lines
13 KiB
Plaintext
include library.instance;
|
|
include library.utils;
|
|
|
|
const int MOVEMODE_INVALID = 0;
|
|
const int MOVEMODE_FOLLOW = 1;
|
|
const int MOVEMODE_RANDOM = 2;
|
|
const int MOVEMODE_PREPARING_TO_MOVE = 3;
|
|
const int MOVEMODE_PATHING_HOME = 4;
|
|
const int MOVEMODE_PREPARING_TO_STAND = 5;
|
|
const int MOVEMODE_MOVING_TO_HEAL = 7;
|
|
const int MOVEMODE_IDLE = 9;
|
|
const int MOVEMODE_FLEE = 10;
|
|
const int MOVEMODE_FOLLOW_FORMATION = 11;
|
|
const int MOVEMODE_MOVING_TO_SEE = 12;
|
|
const int MOVEMODE_MOVING_TO_DEATHBLOW = 13;
|
|
const int MOVEMODE_SWARM = 14;
|
|
const int MOVEMOVE_EVADE = 15;
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// MOVEMODE_INVALID
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
void aiClearMoveMode()
|
|
{
|
|
const obj_id self = getSelf();
|
|
//debugSpeakMsg(self, "Clear Movement.");
|
|
deltadictionary dict = self.getScriptVars();
|
|
|
|
dict.put("ai.combat.moveMode", MOVEMODE_INVALID);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// MOVEMODE_IDLE
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
void aiIdle()
|
|
{
|
|
if (aiIsIdle())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const obj_id self = getSelf();
|
|
|
|
// stop whatever the ai was doing
|
|
// NOTE: Currently only doing this for patrols. suspendMovement() will work for all movement types, but there
|
|
// may need to be extra code (setting/clearing scriptvars, for instance) written that I'm not aware of. SAJ
|
|
if ( aiGetMovementState(self) == MOVEMENT_PATROL && !hasSuspendedMovement(self) )
|
|
{
|
|
suspendMovement(self);
|
|
}
|
|
|
|
deltadictionary dict = self.getScriptVars();
|
|
dict.put("ai.combat.moveMode", MOVEMODE_IDLE);
|
|
|
|
stop(self);
|
|
}
|
|
|
|
boolean aiIsIdle()
|
|
{
|
|
const obj_id self = getSelf();
|
|
const deltadictionary dict = self.getScriptVars();
|
|
|
|
return (dict.getInt("ai.combat.moveMode") == MOVEMODE_IDLE);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// MOVEMODE_MOVING_TO_SEE
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
void aiMoveToSee(obj_id target)
|
|
{
|
|
if (aiIsMovingToSee(target))
|
|
{
|
|
return;
|
|
}
|
|
|
|
const obj_id self = getSelf();
|
|
|
|
// stop whatever the ai was doing (see aiIdle() above)
|
|
if ( aiGetMovementState(self) == MOVEMENT_PATROL && !hasSuspendedMovement(self) )
|
|
{
|
|
suspendMovement(self);
|
|
}
|
|
|
|
deltadictionary dict = self.getScriptVars();
|
|
dict.put("ai.combat.moveMode", MOVEMODE_MOVING_TO_SEE);
|
|
dict.put("ai.combat.moveToSee.target", target);
|
|
|
|
pathTo(self, getLocation(target));
|
|
}
|
|
|
|
boolean aiIsMovingToSee(obj_id target)
|
|
{
|
|
if(aiIsMovingToSee() && aiGetMoveToSeeTarget() == target)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
boolean aiIsMovingToSee()
|
|
{
|
|
const obj_id self = getSelf();
|
|
const deltadictionary dict = self.getScriptVars();
|
|
|
|
return (dict.getInt("ai.combat.moveMode") == MOVEMODE_MOVING_TO_SEE);
|
|
}
|
|
|
|
obj_id aiGetMoveToSeeTarget()
|
|
{
|
|
const obj_id self = getSelf();
|
|
const deltadictionary dict = self.getScriptVars();
|
|
|
|
return dict.getObjId("ai.combat.moveToSee.target");
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// MOVEMODE_MOVING_TO_DEATHBLOW
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
void aiMoveToDeathBlow(obj_id target)
|
|
{
|
|
if ( aiIsMovingToDeathBlow(target)
|
|
|| !isIncapacitated(target))
|
|
{
|
|
return;
|
|
}
|
|
|
|
const obj_id self = getSelf();
|
|
|
|
// stop whatever the ai was doing (see aiIdle() above)
|
|
if ( aiGetMovementState(self) == MOVEMENT_PATROL && !hasSuspendedMovement(self) )
|
|
{
|
|
suspendMovement(self);
|
|
}
|
|
|
|
LOGC(aiLoggingEnabled(self) && !isIncapacitated(target), "debug_ai", "ai_combat_movement::aiMoveToDeathBlow() self(" + self + ":" + getName(self) + ") target(" + target + ") Trying to death blow a non-incapacitated target.");
|
|
|
|
deltadictionary dict = self.getScriptVars();
|
|
dict.put("ai.combat.moveMode", MOVEMODE_MOVING_TO_DEATHBLOW);
|
|
dict.put("ai.combat.moveToDeathBlow.target", target);
|
|
|
|
pathTo(self, getLocation(target));
|
|
}
|
|
|
|
boolean aiIsMovingToDeathBlow(obj_id target)
|
|
{
|
|
if ( (aiIsMovingToDeathBlow())
|
|
&& (aiGetMoveToDeathBlowTarget() == target))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
boolean aiIsMovingToDeathBlow()
|
|
{
|
|
const obj_id self = getSelf();
|
|
const deltadictionary dict = self.getScriptVars();
|
|
|
|
return (dict.getInt("ai.combat.moveMode") == MOVEMODE_MOVING_TO_DEATHBLOW);
|
|
}
|
|
|
|
obj_id aiGetMoveToDeathBlowTarget()
|
|
{
|
|
const obj_id self = getSelf();
|
|
const deltadictionary dict = self.getScriptVars();
|
|
|
|
return dict.getObjId("ai.combat.moveToDeathBlow.target");
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// MOVEMODE_FOLLOW
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
void aiFollow(obj_id target, float minDistance, float maxDistance)
|
|
{
|
|
if (aiIsFollowing(target, minDistance, maxDistance))
|
|
{
|
|
return;
|
|
}
|
|
|
|
const obj_id self = getSelf();
|
|
|
|
// stop whatever the ai was doing (see aiIdle() above)
|
|
if ( aiGetMovementState(self) == MOVEMENT_PATROL && !hasSuspendedMovement(self) )
|
|
{
|
|
suspendMovement(self);
|
|
}
|
|
|
|
deltadictionary dict = self.getScriptVars();
|
|
dict.put("ai.combat.moveMode", MOVEMODE_FOLLOW);
|
|
dict.put("ai.combat.follow.target", target);
|
|
dict.put("ai.combat.follow.minDistance", minDistance);
|
|
dict.put("ai.combat.follow.maxDistance", maxDistance);
|
|
|
|
follow(self, target, minDistance, maxDistance);
|
|
}
|
|
|
|
void aiSwarm(obj_id target, float minDistance, float maxDistance)
|
|
{
|
|
if (aiIsSwarming(target, minDistance, maxDistance))
|
|
return;
|
|
|
|
const obj_id self = getSelf();
|
|
// stop whatever the ai was doing (see aiIdle() above)
|
|
if ( aiGetMovementState(self) == MOVEMENT_PATROL && !hasSuspendedMovement(self) )
|
|
{
|
|
suspendMovement(self);
|
|
}
|
|
|
|
deltadictionary dict = self.getScriptVars();
|
|
dict.put("ai.combat.moveMode", MOVEMODE_SWARM);
|
|
dict.put("ai.combat.follow.target", target);
|
|
dict.put("ai.combat.follow.minDistance", minDistance);
|
|
dict.put("ai.combat.follow.maxDistance", maxDistance);
|
|
swarm(self,target );
|
|
}
|
|
|
|
|
|
boolean aiIsFollowing(obj_id target, float minDistance, float maxDistance)
|
|
{
|
|
const obj_id self = getSelf();
|
|
const deltadictionary dict = self.getScriptVars();
|
|
|
|
if ( (dict.getInt("ai.combat.moveMode") == MOVEMODE_FOLLOW)
|
|
&& (dict.getObjId("ai.combat.follow.target") == target)
|
|
&& (dict.getFloat("ai.combat.follow.minDistance") == minDistance)
|
|
&& (dict.getFloat("ai.combat.follow.maxDistance") == maxDistance))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
boolean aiIsSwarming(obj_id target, float minDistance, float maxDistance)
|
|
{
|
|
const obj_id self = getSelf();
|
|
const deltadictionary dict = self.getScriptVars();
|
|
|
|
if ( (dict.getInt("ai.combat.moveMode") == MOVEMODE_SWARM)
|
|
&& (dict.getObjId("ai.combat.follow.target") == target)
|
|
&& (dict.getFloat("ai.combat.follow.minDistance") == minDistance)
|
|
&& (dict.getFloat("ai.combat.follow.maxDistance") == maxDistance))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// MOVEMODE_EVADE
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
float getActualEvadeChance(obj_id ai, float min, float max)
|
|
{
|
|
float rslt = max;
|
|
float difference = max - min;
|
|
float maxHealth = (float)getMaxHealth(ai);
|
|
float curHealth = (float)getHealth(ai);
|
|
|
|
rslt -= (difference * (curHealth / maxHealth));
|
|
|
|
return rslt;
|
|
}
|
|
|
|
//
|
|
// Runs the AI to some location around the target, given the distances
|
|
// @chance is a number between 1 and 0. if >=1 is passed in, the ai
|
|
// will attempt an evasion attempt 100% of the time (not recommended as
|
|
// it will make AI run around like Benny Hill during combat since the
|
|
// movement code (including this function) is triggered by the server
|
|
// every frame (up to 4 times a second)
|
|
//
|
|
boolean aiEvade(obj_id target, obj_id ai, range_info aiWeaponRanges, float minChance, float maxChance)
|
|
{
|
|
obj_id primaryWeapon = aiGetPrimaryWeapon(ai);
|
|
obj_id secondaryWeapon = aiGetSecondaryWeapon(ai);
|
|
|
|
float primaryRange = 0;
|
|
float secondaryRange = 0;
|
|
|
|
obj_id master = getMaster(ai);
|
|
|
|
// Pets do not evade.
|
|
if(isIdValid(master))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Am I in a heroic instance? Do not evade!
|
|
if(hasScript(ai, "systems.dungeon_sequencer.ai_controller"))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(isIdValid(primaryWeapon) && exists(primaryWeapon))
|
|
{
|
|
const range_info primaryWeaponRange = aiGetWeaponRangeInfo(primaryWeapon);
|
|
primaryRange = primaryWeaponRange.maxRange;
|
|
}
|
|
|
|
if(isIdValid(secondaryWeapon) && exists(secondaryWeapon))
|
|
{
|
|
const range_info secondaryWeaponRange = aiGetWeaponRangeInfo(secondaryWeapon);
|
|
secondaryRange = secondaryWeaponRange.maxRange;
|
|
}
|
|
|
|
if(primaryRange <= 10f && secondaryRange <= 10f)
|
|
{
|
|
return false; // Melee doesn't evade.
|
|
}
|
|
|
|
const int now = getGameTime();
|
|
const int then = utils.getIntScriptVar(ai, "aiIsEvading");
|
|
|
|
if(then != 0 && (now - then) < 15)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
float minDistance = 0; // min distance to run away
|
|
float maxDistance = 1f; // max distance to run away
|
|
|
|
if(getTopMostContainer(ai) == ai) // outside with a gun
|
|
{
|
|
minDistance = 15f;
|
|
maxDistance = 20f;
|
|
}
|
|
else // inside with a gun
|
|
{
|
|
minDistance = 4f;
|
|
maxDistance = 10f;
|
|
}
|
|
|
|
if(isPlayer(target))
|
|
{
|
|
float mod = 0;
|
|
range_info playerRange = getWeaponRangeInfo(getCurrentWeapon(target));
|
|
|
|
if(playerRange != null && playerRange.maxRange < 10)
|
|
{
|
|
if(minChance == 1f)
|
|
{
|
|
mod = 0.985f; // when fighting a player with a melee weapon, less chance to evade, even if player is too close
|
|
}
|
|
else
|
|
{
|
|
mod = 0.9f;
|
|
}
|
|
|
|
minDistance = 5f;
|
|
|
|
if(rand(1, 100) < 30) // n% chance to run out of player's range
|
|
{
|
|
maxDistance = playerRange.maxRange + 5;
|
|
}
|
|
else
|
|
{
|
|
maxDistance = playerRange.maxRange - 1;
|
|
}
|
|
|
|
if(minDistance > maxDistance)
|
|
{
|
|
minDistance = maxDistance;
|
|
}
|
|
}
|
|
|
|
maxChance -= (maxChance * mod);
|
|
|
|
if(minChance > maxChance)
|
|
{
|
|
minChance = maxChance;
|
|
}
|
|
//debugSpeakMsg(ai, "MinEvade=" + minChance + " MaxEvade=" + maxChance);
|
|
}
|
|
float chance = getActualEvadeChance(ai, minChance, maxChance);
|
|
|
|
if(chance < 1f && rand(0.0f, 1.0f) > chance)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Get valid away location gets a location from current location to a radius of minDistance to maxDistance.
|
|
// If the location can not be seen by the target, then it returns null.
|
|
// The away location is not away from the target. It is a random circular location from its current location.
|
|
location evasionTargetLoc = getValidAwayLocation(target, minDistance, maxDistance);
|
|
|
|
if(evasionTargetLoc == null || getLocation(ai).cell != evasionTargetLoc.cell)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If the ai cannot see the location, then return false. The target has already checked its visibility.
|
|
if(!canSee(ai, evasionTargetLoc))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
utils.setScriptVar(ai, "aiIsEvading", getGameTime());
|
|
pathTo(ai, evasionTargetLoc);
|
|
faceTo(ai, target);
|
|
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// this function will fail frequently (return null), but its designed to be called potentially every
|
|
// frame; so frames where failures are returned are just discarded
|
|
//
|
|
location getValidAwayLocation(obj_id mob, float minDistance, float maxDistance)
|
|
{
|
|
location from = getLocation(mob);
|
|
//debugSpeakMsg(mob, "" + minDistance + "/" + maxDistance);
|
|
location evasionTarget = utils.getRandomAwayLocation(from, minDistance, maxDistance);
|
|
evasionTarget.y += 0.5f; // have to do this because canSee will fail due to ground geometry collision otherwise
|
|
|
|
if(!canSee(mob, evasionTarget))
|
|
{
|
|
//playClientEffectLoc(target, "appearance/" + "pt_fireworks_01.prt", evasionTarget, 0.0f);
|
|
//debugSpeakMsg(mob, "Can't see evasion target. Not Evading");
|
|
return null;
|
|
}
|
|
evasionTarget.y -= 0.5f;
|
|
return evasionTarget;
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// MOVEMODE_FLEE
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
void aiFlee(obj_id target, float distance)
|
|
{
|
|
if (aiIsFleeing(target, distance))
|
|
{
|
|
return;
|
|
}
|
|
|
|
const obj_id self = getSelf();
|
|
|
|
// stop whatever the ai was doing (see aiIdle() above)
|
|
if ( aiGetMovementState(self) == MOVEMENT_PATROL && !hasSuspendedMovement(self) )
|
|
{
|
|
suspendMovement(self);
|
|
}
|
|
|
|
deltadictionary dict = self.getScriptVars();
|
|
dict.put("ai.combat.moveMode", MOVEMODE_FLEE);
|
|
dict.put("ai.combat.flee.target", target);
|
|
dict.put("ai.combat.flee.distance", distance);
|
|
|
|
flee(self, target, distance, distance);
|
|
}
|
|
|
|
boolean aiIsFleeing(obj_id ai)
|
|
{
|
|
const deltadictionary dict = ai.getScriptVars();
|
|
return dict.getInt("ai.combat.moveMode") == MOVEMODE_FLEE;
|
|
}
|
|
|
|
boolean aiIsFleeing(obj_id target, float distance)
|
|
{
|
|
const obj_id self = getSelf();
|
|
const deltadictionary dict = self.getScriptVars();
|
|
|
|
if ( (dict.getInt("ai.combat.moveMode") == MOVEMODE_FLEE)
|
|
&& (dict.getObjId("ai.combat.flee.target") == target)
|
|
&& (dict.getFloat("ai.combat.flee.distance") == distance))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
//Additional Move Modes
|
|
boolean isAiImmobile(obj_id subject)
|
|
{
|
|
return hasObjVar(subject, "isImmobile") ? getBooleanObjVar(subject, "isImmobile") : false;
|
|
} |