Files
dsrc/sku.0/sys.server/compiled/game/script/ai/ai_combat_movement.scriptlib
T

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