Files
dsrc/sku.0/sys.server/compiled/game/script/ai/creature_combat.script
T
2013-09-10 23:17:15 -07:00

1727 lines
41 KiB
Plaintext

include ai.ai_aggro;
include ai.ai_combat;
include ai.ai_combat_assist;
include ai.ai_combat_damage;
include ai.ai_combat_movement;
include ai.ai_combat_target;
include library.ai_lib;
include library.beast_lib;
include library.buff;
include library.colors;
include library.combat;
include library.combat_consts;
include library.factions;
include library.instance;
include library.movement;
include library.pclib;
include library.pet_lib;
include library.posture;
include library.scout;
include library.stealth;
include library.storyteller;
include library.sui;
include library.trial;
include library.utils;
include library.xp;
include library.stealth;
inherits systems.combat.combat_base;
/// value between 0 and 1. 1 being constant movement (not recommended). This number gets evaluated
/// 4 times a second, so it needs to be sufficiently low to not happen too often. Min and max gets interpolated
/// based on
const float MIN_MOVEMENT_DURING_COMBAT = 0.015f;
const float MAX_MOVEMENT_DURING_COMBAT = 0.05f;
const float TOO_CLOSE_DISTANCE = 3f;
void clog(string text)
{
if(text != null)
{
LOGC(aiLoggingEnabled(getSelf()), "debug_ai", "creature_combat::" + text);
}
}
trigger OnEnteredCombat()
{
//debugSpeakMsg(self, "OnEnteredCombat");
clog("OnEnteredCombat() ----- unit(" + self + ":" + getName(self) + ")");
clearCombatActions();
if (!stealth.isDecoy(self))
{
aiEquipPrimaryWeapon(self);
}
// Once the AI is in combat, there is no need to listen for these trigger volume breaches
removeTriggerVolume(ai_lib.ALERT_VOLUME_NAME);
removeTriggerVolume(ai_lib.AGGRO_VOLUME_NAME);
ai_combat_movement.aiClearMoveMode();
setMovementRun(self);
deltadictionary dict = self.getScriptVars();
dict.put("ai.combat.cover.foundTarget", isPlayer(getHateTarget(self)) ? false : true);
if(ai_combat_assist.isWaiting())
{
ai_combat_movement.aiIdle();
}
else
{
obj_id target = getHateTarget(self);
boolean targetCovered = stealth.hasInvisibleBuff(target);
boolean targetProne = posture.isProne(target);
if(targetCovered || targetProne)
showFlyText(self, new string_id("npc_reaction/flytext", "alert"), 2.0f, colors.STEELBLUE);
utils.setScriptVar(self, "creature_combat.lastCombatFrame", getGameTime());
if (ai_combat_assist.canCallForAssist())
ai_combat_assist.callForAssist();
if(beast_lib.isBeast(self) && getState(self, STATE_COVER) == 1)
{
stealth.makeCreatureVisible(self);
}
doCombatFrame();
}
messageTo(self, "checkForBeastSpecialsTakeOne", null, 1.0f, false);
setRegenRate(self, HEALTH, 0);
if(!beast_lib.isBeast(self))
{
setRegenRate(self, ACTION, 20);
}
else
{
//This was going to be a really cool BM regen experise so the beast would get a few extra attacks every few rounds
int actionRegen = 4;
obj_id master = getMaster(self);
int expertiseRegen = 0;
if(isIdValid(master) && exists(master))
{
expertiseRegen = getEnhancedSkillStatisticModifierUncapped(master, "expertise_bm_pet_regen");
}
if(expertiseRegen > 0)
{
actionRegen += (int)((float)actionRegen * ((float)expertiseRegen / 100.0f));
}
setRegenRate(self, ACTION, actionRegen);
}
if (combat.isPersistCombatMode(self))
{
messageTo(self, "persist_combat", trial.getSessionDict(self, combat.PERSIST_COMBAT), 5.0f, false);
}
return SCRIPT_CONTINUE;
}
trigger OnExitedCombat()
{
if(!isIdValid(self) || !exists(self))
{
return SCRIPT_CONTINUE;
}
//debugSpeakMsg(self, "OnExitedCombat");
clog("OnExitedCombat() ----- unit(" + self + ":" + getName(self) + ")");
ai_combat_target.clearTargetList();
ai_combat_damage.clearAttackerList();
ai_combat_movement.aiClearMoveMode();
ai_combat_assist.clearNoAssistCall();
setLookAtTarget(self, null);
// Makes sure no players keep their evasion buff on the creature, after tethering.
utils.removeScriptVarTree(self, "me_evasion");
if(!beast_lib.isBeast(self))
{
ai_lib.resetCombatTriggerVolumes();
float healthRegenMod = hasObjVar(self, "regen_mod.health") ? getFloatObjVar(self, "regen_mod.health") : 1.0f;
float actionRegenMod = hasObjVar(self, "regen_mod.action") ? getFloatObjVar(self, "regen_mod.action") : 1.0f;
float healthRegen = (getMaxAttrib(self, HEALTH) / 10.0f) * healthRegenMod;
float actionRegen = (getMaxAttrib(self, ACTION) / 10.0f) * actionRegenMod;
setRegenRate(self, HEALTH, (int)healthRegen); // 10% of maximum health regeneration rate
setRegenRate(self, ACTION, (int)actionRegen); // 10% of maximum action regeneration rate
}
else
{
int healthRegen = 150;
int actionRegen = 10;
obj_id master = getMaster(self);
int expertiseRegen = 0;
if(isIdValid(master) && exists(master))
{
expertiseRegen = getEnhancedSkillStatisticModifierUncapped(master, "expertise_bm_pet_regen");
}
if(expertiseRegen > 0)
{
actionRegen += (int)((float)actionRegen * ((float)expertiseRegen / 100.0f));
healthRegen += (int)((float)healthRegen * ((float)expertiseRegen / 100.0f));
}
setRegenRate(self, HEALTH, healthRegen);
setRegenRate(self, ACTION, actionRegen);
if(stealth.canBeastStealth(self))
{
stealth.makeBeastInvisible(getMaster(self), "appearance/pt_smoke_puff.prt");
}
}
if(!ai_lib.isAiDead(self))
{
if(posture.isKnockedDown(self))
{
queueCommand(self, ##"knockDownRecovery", self, "", COMMAND_PRIORITY_DEFAULT);
}
else if(!posture.isStanding(self))
{
posture.stand(self);
}
if(!beast_lib.isBeast(self))
{
if(pet_lib.isPet(self))
{
messageTo(self, "postCombatPathHome", null, 1, false);
}
else
{
if(!hasObjVar(self, "forceNoMovement"))
{
aiTether(self);
}
else
{
cleanupForTether(self);
pathTo(self, aiGetHomeLocation(self));
}
}
}
}
bumpSession(self);
trial.bumpSession(self, combat.PERSIST_COMBAT);
messageTo(self, "ai_assist_check", null, 0.25f, false);
return SCRIPT_CONTINUE;
}
messageHandler ai_assist_check()
{
if(!isIdValid(self) || !exists(self))
{
return SCRIPT_CONTINUE;
}
ai_combat_assist.callForAssist();
return SCRIPT_CONTINUE;
}
void cleanupForTether(obj_id self)
{
if(!isIdValid(self) || !exists(self))
{
return;
}
ai_lib.setMood(self, ai_lib.MOOD_CALM);
ai_lib.clearCombatData();
xp.cleanupCreditForKills();
}
trigger OnAiTetherStart()
{
// clog("ai::OnAiTetherStart() --- BEGIN --- self(" + self + ":" + getName(self) + ")");
if(!isIdValid(self) || !exists(self))
{
return SCRIPT_CONTINUE;
}
cleanupForTether(self);
// Speed up the AI running back to home
// clog("ai::OnAiTetherStart() --- END --- self(" + self + ")");
return SCRIPT_CONTINUE;
}
trigger OnAiTetherComplete()
{
clog("ai::OnAiTetherComplete() --- BEGIN --- self(" + self + ":" + getName(self) + ")");
ai_lib.resetAi();
clog("ai::OnAiTetherComplete() --- END --- self(" + self + ":" + getName(self) + ")");
return SCRIPT_CONTINUE;
}
trigger OnHateTargetChanged(obj_id target)
{
clog("OnHateTargetChanged() self(" + self + ":" + getName(self) + ") target(" + target + ")");
setLookAtTarget(self, target);
if(ai_lib.isInCombat(self))
{
ai_combat_assist.clearAssist();
}
if(pet_lib.isPet(self) && utils.hasScriptVar(self, "ai.combat.target"))
{
obj_id combatTarget = utils.getObjIdScriptVar(self, "ai.combat.target");
if(target != combatTarget)
{
float maxHate = getMaxHate(self);
setHate(self, combatTarget, maxHate + 5000);
}
}
return SCRIPT_CONTINUE;
}
trigger OnHateTargetAdded(obj_id target)
{
clog("creature_combat::OnHateTargetAdded() self(" + self + ":" + getName(self) + ") target(" + target + ")");
return SCRIPT_CONTINUE;
}
trigger OnHateTargetRemoved(obj_id target)
{
clog("creature_combat::OnHateTargetRemoved() self(" + self + ":" + getName(self) + ") target(" + target + ")");
if (!isIdValid(target) || !exists(target))
return SCRIPT_CONTINUE;
const int factionStatus = factions.getFactionStatus(self, target);
if(factionStatus == factions.STATUS_FRIEND || (factionStatus == factions.STATUS_NEUTRAL && !aiIsAggressive(self)))
{
// if we are a bounty hunter target, we always
// maintain a PEF on the bounty hunter
if(hasScript(self, "systems.missions.dynamic.mission_bounty_target"))
{
obj_id objHunter = getObjIdObjVar(self, "objHunter");
if(!isIdValid(objHunter) || (objHunter != target))
{
pvpRemovePersonalEnemyFlags(self, target);
}
}
else
{
pvpRemovePersonalEnemyFlags(self, target);
}
}
if(pet_lib.isPet(self) && utils.hasScriptVar(self, "ai.combat.target"))
{
obj_id combatTarget = utils.getObjIdScriptVar(self, "ai.combat.target");
if(target == combatTarget)
{
utils.removeScriptVar(self, "ai.combat.target");
}
}
return SCRIPT_CONTINUE;
}
trigger OnAiPrimaryWeaponEquipped(obj_id primaryWeapon)
{
clog("OnAiPrimaryWeaponEquipped() ai(" + self + ") primaryWeapon(" + primaryWeapon + ")");
clearCombatActions();
return SCRIPT_CONTINUE;
}
trigger OnAiSecondaryWeaponEquipped(obj_id secondaryWeapon)
{
clog("OnAiSecondaryWeaponEquipped() ai(" + self + ") secondaryWeapon(" + secondaryWeapon + ")");
clearCombatActions();
return SCRIPT_CONTINUE;
}
trigger OnAddedToWorld()
{
return SCRIPT_CONTINUE;
}
trigger OnAiCombatFrame()
{
// PROFILER_START("creature_combat.OnAiCombatFrame");
boolean assistWaiting = ai_combat_assist.isWaiting();
// clog("OnAiCombatFrame assistWaiting: " + assistWaiting);
if(assistWaiting || getSkillStatisticModifier(self, "stifle") > 0)
{
//debugSpeakMsg(self, "Waiting, skipping combat.");
const obj_id hateTarget = getHateTarget(self);
faceTo(self, hateTarget);
if(getCombatDuration(self) > ai_combat_assist.getWaitTime())
{
ai_combat_assist.clearAssist();
}
}
else
{
doCombatFrame();
}
// PROFILER_STOP("creature_combat.OnAiCombatFrame");
return SCRIPT_CONTINUE;
}
void doCombatFrame()
{
// PROFILER_START("creature_combat.doCombatFrame");
const obj_id self = getSelf();
const obj_id target = getBestTarget(self);
int lastFrame = utils.getIntScriptVar(self, "creature_combat.lastCombatFrame");
// clog("doCombatFrame lastFrame: " + lastFrame + " time elapsed: " + (getGameTime() - lastFrame));
utils.setScriptVar(self, "creature_combat.lastCombatFrame", getGameTime());
if(getState(self, STATE_COVER) == 1)
{
stealth.makeCreatureVisible(self);
}
if(utils.hasScriptVar(self, "petIgnoreAttacks"))
{
// PROFILER_STOP("creature_combat.doCombatFrame");
return;
}
if(!isIdValid(target))
{
// clog("aiCombatLoop() ai(" + self + ") target(" + target + ") Bailing due to invalid target");
// Hate list sanity check.
//////////////////////////////////////
obj_id[] haters = getHateList(self);
if (haters != null && haters.length > 0 && !utils.hasScriptVar(self, "hateListRetry"))
{
// We somehow have an invalid target, yet we still have people on our hate list.
// CustomerServiceLog("combat_errors: ", "Creature [" + self + "] has a bad target but still has a Hate List with entities in it. Dumping Hate List...");
// 1st let's build a list of valid people.
resizeable obj_id[] goodIds = new obj_id[0];
for(int i = 0; i < haters.length; ++i)
{
// CustomerServiceLog("combat_errors: ", "Creature [" + self + "] Hate List Entry [" + i + "] : " + haters[i]);
if(isIdValid(haters[i]) && exists(haters[i]) )
{
utils.addElement(goodIds, haters[i]);
}
}
if(goodIds != null & goodIds.length > 0)
{
// Pick a random new, good target.
int targetIndex = rand(0, goodIds.length - 1);
// CustomerServiceLog("combat_errors: ", "Creature [" + self + "] forcing Hate Target to [" + goodIds[targetIndex] + "]");
forceHateTarget(self, goodIds[targetIndex]);
// Tracking to prevent infinite loops.
utils.setScriptVar(self, "hateListRetry", 1);
// Force our combat frame to restart.
doCombatFrame();
}
}
if(utils.hasScriptVar(self, "hateListRetry"))
utils.removeScriptVar(self, "hateListRetry");
//////////////////////////////////////
if(beast_lib.isBeast(self))
{
obj_id master = getMaster(self);
if(isIdValid(master) && master.isLoaded())
{
beast_lib.beastFollowTarget(self, master);
}
else
{
aiSetHomeLocation(self, getLocation(self));
}
// PROFILER_STOP("creature_combat.doCombatFrame");
return;
}
if(pet_lib.isPet(self))
{
messageTo(self, "postCombatPathHome", null, 1, false);
return;
}
if(!hasObjVar(self, "forceNoMovement"))
{
aiTether(self);
}
else
{
cleanupForTether(self);
pathTo(self, aiGetHomeLocation(self));
}
// PROFILER_STOP("creature_combat.doCombatFrame");
return;
}
boolean checkVE = isIdValid(target) && exists(target) && isIdValid(self) && exists(self);
if(!storyteller.storytellerCombatCheck(self, target))
{
// debugSpeakMsgc(aiLoggingEnabled(self), self, "Removing non-STORYTELLER target(" + target + ")");
if(checkVE)
{
removeHateTarget(self, target);
}
// PROFILER_STOP("creature_combat.doCombatFrame");
return;
}
if(getState(target, STATE_FEIGN_DEATH) == 1)
{
// debugSpeakMsgc(aiLoggingEnabled(self), self, "Removing FEIGNED target(" + target + ")");
if(checkVE)
{
removeHateTarget(self, target);
}
// PROFILER_STOP("creature_combat.doCombatFrame");
return;
}
else if(!ai_combat_target.isValidTarget(target))
{
// debugSpeakMsgc(aiLoggingEnabled(self), self, "Removing INVALID target(" + target + ")");
if(checkVE)
{
removeHateTarget(self, target);
}
// PROFILER_STOP("creature_combat.doCombatFrame");
return;
}
const boolean targetCovered = stealth.hasInvisibleBuff(target);
const boolean targetProne = posture.isProne(target);
const int freeShotCount = targetCovered ? 3 : (targetProne ? 1 : 0);
deltadictionary dict = self.getScriptVars();
if(!dict.getBoolean("ai.combat.cover.foundTarget"))
{
if((getCombatDuration(self) > 10) || (freeShotCount == 0) || (ai_combat_damage.getAttackCount(target) > freeShotCount))
{
if(freeShotCount > 0 && !hasScript(self, "systems.dungeon_sequencer.ai_controller"))
{
showFlyText(self, new string_id("npc_reaction/flytext", "threaten"), 2.0f, colors.ORANGERED);
}
dict.put("ai.combat.cover.foundTarget", true);
}
}
//???????????
int combatRound = utils.getIntScriptVar(self, "aiCombatRoundCounter");
combatRound++;
utils.setScriptVar(self, "aiCombatRoundCounter", combatRound);
selectWeapon(target);
const boolean commandQueueClear = (getCurrentCommand(self) == 0);
if(commandQueueClear)
{
utils.removeScriptVar(self, "creature_combat.commandQueueTime");
if(posture.isKnockedDown(self))
{
//const deltadictionary dict = self.getScriptVars();
const int knockDownTime = dict.getInt("ai.combat.knockdown_time");
if((getGameTime() - knockDownTime) > aiGetKnockDownRecoveryTime(self))
{
// debugSpeakMsgc(aiLoggingEnabled(self), self, "KnockDownRecovery");
queueCommand(self, ##"knockDownRecovery", self, "", COMMAND_PRIORITY_DEFAULT);
}
}
else if(!posture.isStanding(self))
{
posture.stand(self);
}
else
{
if(dict.getBoolean("ai.combat.cover.foundTarget"))
{
attack(target);
}
}
}
else
{
if(!utils.hasScriptVar(self, "creature_combat.commandQueueTime"))
{
utils.setScriptVar(self, "creature_combat.commandQueueTime", getGameTime());
}
else
{
int time = utils.getIntScriptVar(self, "creature_combat.commandQueueTime");
// clog("commandQueueClear: " + commandQueueClear + " time since last command: " + (getGameTime() - time));
if(getGameTime() - time > 2)
{
clearCombatActions();
}
}
}
move(target);
// PROFILER_STOP("creature_combat.doCombatFrame");
}
obj_id getBestTarget(obj_id self)
{
obj_id primaryTarget = getHateTarget(self);
if(isIdValid(primaryTarget) && exists(primaryTarget) && !isDead(primaryTarget) && !stealth.hasInvisibleBuff(primaryTarget))
return primaryTarget;
obj_id[] hateList = getHateList(self);
if(hateList == null || hateList.length == 0)
return null;
float topHate = 0.0f;
primaryTarget = null;
for(int i = 0; i < hateList.length; i++)
{
if(!isIdValid(hateList[i]) || !exists(primaryTarget))
{
continue;
}
if(isDead(hateList[i]) || stealth.hasInvisibleBuff(hateList[i]))
{
removeHateTarget(self, hateList[i]);
continue;
}
if (hasObjVar(self, "noPursue") && !combat.cachedCanSee(self, hateList[i]))
continue;
if(getHate(self, hateList[i]) > topHate)
primaryTarget = hateList[i];
}
return primaryTarget;
}
void move(obj_id target)
{
// PROFILER_START("creature_combat.move");
const obj_id self = getSelf();
if(ai_combat_movement.isAiImmobile(self))
{
// PROFILER_STOP("creature_combat.move");
return;
}
// clog("move() !posture.isStanding(self): " + !posture.isStanding(self) + " !ai_lib.isWithinLeash(self): " + !ai_lib.isWithinLeash(self));
//debugSpeakMsg(self, "1");
if(!posture.isStanding(self))
{
// PROFILER_STOP("creature_combat.move");
return;
}
//debugSpeakMsg(self, "2");
if(!ai_lib.isWithinLeash(self))
{
stopCombat(self);
// clog("move() stopping combat with stopCombat(self)");
// PROFILER_STOP("creature_combat.move");
return;
}
//debugSpeakMsg(self, "3");
deltadictionary dict = self.getScriptVars();
setLookAtTarget(self, target);
const obj_id weapon = getCurrentWeapon(self);
const range_info weaponRange = aiGetWeaponRangeInfo(weapon);
const float distanceToTarget = getDistance(self, target);
const int weaponCategory = combat.getWeaponCategory(getWeaponType(weapon));
const boolean targetCovered = stealth.hasInvisibleBuff(target);
// @TODO: Make sure and have a check somewhere that the ranges are not inverted.
if(!canSee(self, target))
{
ai_combat_movement.aiMoveToSee(target);
//debugSpeakMsg(self, "4");
// PROFILER_STOP("creature_combat.move");
return;
}
if((weaponCategory == combat.RANGED_WEAPON) && (combat.getWeaponCategory(getWeaponType(aiGetPrimaryWeapon(self))) != combat.MELEE_WEAPON))
{
if((distanceToTarget < TOO_CLOSE_DISTANCE) && isPlayer(target))
{
ai_combat_movement.aiEvade(target, self, weaponRange, 1f, 1f);
// PROFILER_STOP("creature_combat.move");
return;
}
else if(!ai_combat_movement.aiIsFleeing(self));
{
const float midRange = (weaponRange.maxRange - weaponRange.minRange) / 2.0f;
const float rangeVariation = midRange * 0.2f;
const float minFollowDistance = (weaponRange.minRange + midRange - rangeVariation);
const float maxFollowDistance = (weaponRange.minRange + midRange + rangeVariation);
// clog("move() aiFollow target: " + target + " mindist: " + minFollowDistance + " maxdist: " + maxFollowDistance);
ai_combat_movement.aiFollow(target, minFollowDistance, maxFollowDistance);
faceTo(self, target);
}
}
else // MELEE WEAPON
{
float minFollowDistance = 1.5f;
float maxFollowDistance = 3.0f;
if(!hasObjVar(self, "ai.noFollow"))
{
ai_combat_movement.aiFollow(target, minFollowDistance, maxFollowDistance);
}
else
{
if(distanceToTarget > 64)
{
stopCombat(self);
resumeMovement(self);
// PROFILER_STOP("creature_combat.move");
return;
}
else
{
suspendMovement(self);
}
}
faceTo(self, target);
}
//debugSpeakMsg(self, "5");
ai_combat_movement.aiEvade(target, self, weaponRange, MIN_MOVEMENT_DURING_COMBAT, MAX_MOVEMENT_DURING_COMBAT);
// PROFILER_STOP("creature_combat.move");
}
void selectWeapon(obj_id target)
{
//@optimize: Only weapon switch every second.
const obj_id self = getSelf();
// Beasts do not switch weapons.
if(beast_lib.isBeast(self))
{
return;
}
if(!aiHasPrimaryWeapon(self))
{
// Can I just do a WARNING here?
//Thread.dumpStack();
}
// @TODO: Make sure grenades work for AI
// @TODO: Evaluate the weapon of the primary target
if(!aiHasSecondaryWeapon(self))
{
aiEquipPrimaryWeapon(self);
return;
}
boolean equipPrimary = true;
const float distanceToTarget = getDistance(self, target);
const obj_id primaryWeapon = aiGetPrimaryWeapon(self);
const obj_id secondaryWeapon = aiGetSecondaryWeapon(self);
if (!exists(primaryWeapon) || !exists(secondaryWeapon))
return;
const range_info primaryWeaponRange = aiGetWeaponRangeInfo(primaryWeapon);
const boolean primaryWeaponInRange = (distanceToTarget >= primaryWeaponRange.minRange) && (distanceToTarget <= primaryWeaponRange.maxRange);
const range_info secondaryWeaponRange = aiGetWeaponRangeInfo(secondaryWeapon);
const boolean secondaryWeaponInRange = (distanceToTarget >= secondaryWeaponRange.minRange) && (distanceToTarget <= secondaryWeaponRange.maxRange);
// Rifle/Unarmed
// Unarmed/Rifle
// Rifle/Pistol
// Pistol/Rifle
// Pseudo-code
// If I am using my primary weapon and
// I have a secondary weapon
// If my secondary weapon is in range
// If my primary weapon is not in range
// Switch to my secondary weapon
// If my primary weapon has a larger range than my secondary weapon
// Switch to my secondary weapon
// If I am using my secondary weapon
// and my primary weapon is in range
// If my secondary weapon is not in range
// Switch to my primary weapon
// If my secondary weapon has a larger range than my primary weapon
// Switch to my primary weapon
if(aiUsingPrimaryWeapon(self) && aiHasSecondaryWeapon(self))
{
if(secondaryWeaponInRange)
{
if(!primaryWeaponInRange)
{
aiEquipSecondaryWeapon(self);
}
else if(primaryWeaponRange.maxRange > secondaryWeaponRange.maxRange)
{
aiEquipSecondaryWeapon(self);
}
}
}
else if(aiUsingSecondaryWeapon(self) && primaryWeaponInRange)
{
if(!secondaryWeaponInRange)
{
aiEquipPrimaryWeapon(self);
}
else if(secondaryWeaponRange.maxRange > primaryWeaponRange.maxRange)
{
aiEquipPrimaryWeapon(self);
}
}
}
void attack(obj_id target)
{
// PROFILER_START("creature_combat.attack");
const obj_id self = getSelf();
string DEFAULT_ATTACK = combat.getAttackName(self);
//LOGC(aiLoggingEnabled(self), "debug_ai", ("creature_combat::attack() self(" + self + ") target(" + target + ")"));
String currentActionString = null;
String forcedActionString = getStringObjVar(self, "ai.combat.forcedAction");
String pendingActionString = getStringObjVar(self, "ai.combat.pendingAction");
string oneShotActionString = getStringObjVar(self, "ai.combat.oneShotAction");
if(oneShotActionString != null && !hasObjVar(self, "oneShotActionComplete"))
{
forcedActionString = oneShotActionString;
setObjVar(self, "oneShotActionComplete", 1);
}
else
{
if(oneShotActionString != null)
{
// PROFILER_STOP("creature_combat.attack");
return;
}
}
int pendingActionTime = getIntObjVar(self, "ai.combat.pendingActionTime");
currentActionString = DEFAULT_ATTACK;
// Determine the desired combat action this frame
clog("attack() pre-check forcedActionString: " + forcedActionString + " pendingActionString: " + pendingActionString + (pendingActionTime > 0 ? " pendingActionTime: " + (getGameTime() - pendingActionTime): ""));
if(forcedActionString != null)
{
currentActionString = forcedActionString;
}
else if(pendingActionString != null)
{
if(getGameTime() - pendingActionTime < 2)
{
currentActionString = pendingActionString;
}
else
{
removeObjVar(self, "ai.combat.pendingAction");
removeObjVar(self, "ai.combat.pendingActionTime");
currentActionString = DEFAULT_ATTACK;
}
}
else
{
pendingActionString = aiGetCombatAction(self);
if(pendingActionString != null)
{
currentActionString = pendingActionString;
// Store the pending action in case we need to re-attempt it next frame
setObjVar(self, "ai.combat.pendingAction", pendingActionString);
setObjVar(self, "ai.combat.pendingActionTime", getGameTime());
}
else
{
currentActionString = DEFAULT_ATTACK;
}
}
clog("attack() post-check currentActionString: " + currentActionString + " pendingActionString: " + pendingActionString);
int currentActionCrc = 0;
// Determine if we can perform the desired combat action
const int result = combat.canPerformAction(currentActionString, self);
clog("attack() post-check currentActionString: " + currentActionString + " pendingActionString: " + pendingActionString + " canPerformAction: " + result);
if(result == combat.ACTION_SUCCESS || oneShotActionString != null)
{
currentActionCrc = getStringCrc(currentActionString.toLowerCase());
}
else if(result == combat.ACTION_TOO_TIRED)
{
// If there is a forced action, do not use the default attack when the
// AI is too tired to do the current action
if(forcedActionString != null)
{
clog("attack() self(" + self + ") target(" + target + ") ACTION(" + forcedActionString + ") TOO TIRED - WAITING TO ATTEMPT FORCED ACTION");
}
else
{
if(pendingActionString != null)
{
clog("attack() self(" + self + ") target(" + target + ") ACTION(" + pendingActionString + ") TOO TIRED - ATTEMPTING DEFAULT ATTACK");
}
if(currentActionString != DEFAULT_ATTACK)
{
// If the action failed, the fallback is to attempt a default action until
// enough action is available to attempt the requested action.
if(combat.canPerformAction(DEFAULT_ATTACK, self) == combat.ACTION_SUCCESS)
{
currentActionString = DEFAULT_ATTACK;
currentActionCrc = getStringCrc(DEFAULT_ATTACK.toLowerCase());
}
}
}
}
else if(result == combat.ACTION_INVALID_WEAPON)
{
// This is somehow a bad action and we need to fix the data
clearCombatActions();
clog("attack() ERROR self(" + self + getName(self) + ") ACTION(" + currentActionString + ") weapon(" + (aiUsingPrimaryWeapon(self) ? "primary weapon" : "secondary weapon") + ") INVALID WEAPON FOR ACTION, FIX THE DATA");
}
else
{
// This is somehow a bad action and we need to fix the data
clearCombatActions();
clog("attack() ERROR self(" + self + getName(self) + ") ACTION(" + currentActionString + ") UNEXPECTED RESULT FROM combat.canPerformAction()");
}
// If we have a valid attack, lets do it
if(currentActionCrc != 0)
{
combat_data cd = combat_engine.getCombatData(currentActionString);
if(cd == null)
{
// PROFILER_STOP("creature_combat.attack");
return;
}
int groupCrc = getStringCrc(cd.cooldownGroup);
const float coolDownLeft = getCooldownTimeLeft(self, groupCrc);
if(coolDownLeft <= 0.0f)
{
const float distanceToTarget = getDistance(self, target);
const range_info weaponRange = aiGetWeaponRangeInfo(getCurrentWeapon(self));
if(distanceToTarget < weaponRange.maxRange || distanceToTarget < cd.maxRange)
{
if(canSee(self, target))
{
clog("attack() self(" + self + ") target(" + target + ") ACTION(" + currentActionCrc + ":" + currentActionString + ")");
queueCommand(self, currentActionCrc, target, "", COMMAND_PRIORITY_DEFAULT);
if(currentActionString == forcedActionString)
{
removeObjVar(self, "ai.combat.forcedAction");
}
else if(currentActionString == pendingActionString)
{
removeObjVar(self, "ai.combat.pendingAction");
}
}
}
}
}
// PROFILER_STOP("creature_combat.attack");
}
trigger OnCreatureDamaged(obj_id attacker, obj_id weapon, int[] damage)
{
int lastFrame = utils.getIntScriptVar(self, "creature_combat.lastCombatFrame");
if (!stealth.hasInvisibleBuff(attacker))
ai_combat_damage.addAttack(attacker);
// TODO: This is a temporary fix as of 4/30/2007 to keep creatures/npc's fighting in combat, due to the combat loop not firing.
if(getGameTime() - lastFrame > 2)
{
clog("OnCreatureDamaged() !!!COMBAT STUCK!!! lastFrame: " + lastFrame + " time elapsed: " + (getGameTime() - lastFrame));
doCombatFrame();
}
//Changed from a one time call to a persisted threat add on damage taken.
ai_lib.triggerAgroLinks(self, attacker);
if(!isPlayer(attacker) && isMob(attacker))
{
obj_id master = getMaster(attacker);
if(isIdValid(master) && exists(master))
{
attacker = master;
}
}
if(isIdValid(attacker) && exists(attacker) && isPlayer(attacker) && hasObjVar(self, "requirePlayerSpecialForces") && (pvpGetType(attacker) != PVPTYPE_DECLARED))
{
if(pvpNeutralIsMercenaryDeclared(attacker))
{
return SCRIPT_CONTINUE;
}
if(!utils.hasScriptVar(attacker, "gcw.specialForcesWarningTime"))
{
sui.msgbox(attacker, attacker, "@spam:warn_special_forces_prompt", sui.OK_ONLY, "@spam:warn_special_forces_title", "handleNothing");
utils.setScriptVar(attacker, "gcw.specialForcesWarningTime", getGameTime());
}
else
{
int warningTime = utils.getIntScriptVar(attacker, "gcw.specialForcesWarningTime");
int timeDelta = getGameTime() - warningTime;
if(timeDelta > 30)
{
if(timeDelta > 90)
{
sui.msgbox(attacker, attacker, "@spam:warn_special_forces_prompt", sui.OK_ONLY, "@spam:warn_special_forces_title", "handleNothing");
utils.setScriptVar(attacker, "gcw.specialForcesWarningTime", getGameTime());
}
else
{
if(0 != factions.pvpGetAlignedFaction(attacker))
{
factions.goOvertWithDelay(attacker, 0.0f);
}
else
{
int currentMercenaryFaction = factions.pvpNeutralGetMercenaryFaction(attacker);
if (##"imperial" == currentMercenaryFaction)
{
pvpNeutralSetMercenaryFaction(attacker, currentMercenaryFaction, true);
}
if (##"rebel" == currentMercenaryFaction)
{
pvpNeutralSetMercenaryFaction(attacker, currentMercenaryFaction, true);
}
}
utils.removeScriptVar(attacker, "gcw.specialForcesWarningTime");
}
}
}
}
return SCRIPT_CONTINUE;
}
trigger OnChangedPosture(int oldPosture, int newPosture)
{
if(newPosture == POSTURE_KNOCKED_DOWN)
{
const deltadictionary dict = self.getScriptVars();
dict.put("ai.combat.knockdown_time", getGameTime());
}
return SCRIPT_CONTINUE;
}
messageHandler vocalizeEndCombat()
{
return SCRIPT_CONTINUE;
}
messageHandler postCombatPathHome()
{
if(ai_lib.isInCombat(self))
{
return SCRIPT_CONTINUE;
}
if(pet_lib.isPet(self) || beast_lib.isBeast(self))
{
return SCRIPT_CONTINUE;
}
if(ai_lib.isFollowing(self))
{
ai_lib.resumeFollow(self);
}
else
{
pathTo(self, aiGetHomeLocation(self));
}
return SCRIPT_CONTINUE;
}
boolean isNearLair(obj_id npc)
{
obj_id myLair = getObjIdObjVar(npc, "npc_lair.target");
if(!hasObjVar(npc, "lairSpawn") && !isIdValid(myLair))
{
return false;
}
if(!isIdValid(myLair))
{
myLair = getObjIdObjVar(npc, "poi.baseObject");
}
if(isIdValid(myLair) && isInWorld(myLair) && exists(myLair))
{
if(getDistance(npc, myLair) < 40f)
{
return true;
}
}
return false;
}
void killPlayer(obj_id npc, obj_id target)
{
if(!isIdValid(target) || !isIncapacitated(target) || !isInWorld(target) || !exists(target) || ai_lib.isInCombat(npc))
{
return;
}
obj_id weapon = getCurrentWeapon(npc);
int weaponType = getWeaponType(weapon);
int weaponCat = combat.getWeaponCategory(weaponType);
float range = getDistance(npc, target);
if(range > 2.0f)
{
setObjVar(npc, "ai.pathingToKill", target);
ai_lib.pathNear(npc, getLocation(target), 2f);
return;
}
string skeleton = dataTableGetString("datatables/ai/species.iff", ai_lib.aiGetSpecies(npc), "Skeleton");
if(skeleton != null && !skeleton.equals("human"))
{
doAnimationAction(npc, "eat");
pclib.coupDeGrace(target, npc);
}
else
{
pclib.coupDeGrace(target, npc, true);
}
}
trigger OnFleeTargetLost(obj_id target)
{
if(!ai_lib.isInCombat(self))
{
return SCRIPT_CONTINUE;
}
clog("OnFleeTargetLost() self(" + self + ":" + getName(self) + ") target(" + target + ")");
stopCombat(self);
return SCRIPT_OVERRIDE;
}
trigger OnFleeWaypoint(obj_id target)
{
if(!ai_lib.isInCombat(self))
{
return SCRIPT_CONTINUE;
}
return SCRIPT_OVERRIDE;
}
trigger OnFleePathNotFound(obj_id target)
{
if(!ai_lib.isInCombat(self))
{
return SCRIPT_CONTINUE;
}
clog("OnFleePathNotFound() self(" + self + ":" + getName(self) + ") target(" + target + ")");
stopCombat(self);
return SCRIPT_OVERRIDE;
}
trigger OnFollowWaiting(obj_id target)
{
//debugSpeakMsg(self, "Follow Waiting");
if(!ai_lib.isInCombat(self))
{
return SCRIPT_CONTINUE;
}
if(utils.hasScriptVar(self, "ai.combat.pathingToHeal"))
{
//debugSpeakMsg(self, "i was pathing to heal");
obj_id healingTarget = utils.getObjIdScriptVar(self, "ai.combat.pathingToHeal");
//debugSpeakMsg(self, "healing " + getName(healingTarget));
ai_combat.executeHealingMove(self, healingTarget);
return SCRIPT_OVERRIDE;
}
faceTo(self, target);
return SCRIPT_OVERRIDE;
}
trigger OnFollowMoving(obj_id followTarget)
{
if(!ai_lib.isInCombat(self))
{
return SCRIPT_CONTINUE;
}
//clog("OnFollowMoving() self(" + self + ")");
//if moving to heal, then don't do anything
if(utils.hasScriptVar(self, "ai.combat.pathingToHeal"))
{
return SCRIPT_CONTINUE;
}
return SCRIPT_OVERRIDE;
}
trigger OnFollowTargetLost(obj_id target)
{
if(!ai_lib.isInCombat(self))
{
return SCRIPT_CONTINUE;
}
clog("OnFollowTargetLost() self(" + self + ":" + getName(self) + ") target(" + target + ")");
stopCombat(self);
return SCRIPT_OVERRIDE;
}
trigger OnFollowPathNotFound(obj_id target)
{
if(!ai_lib.isInCombat(self))
{
return SCRIPT_CONTINUE;
}
clog("OnFollowPathNotFound() self(" + self + ":" + getName(self) + ") target(" + target + ")");
stopCombat(self);
return SCRIPT_OVERRIDE;
}
trigger OnMovePathComplete()
{
if(!ai_lib.isInCombat(self))
{
return SCRIPT_CONTINUE;
}
clog("OnMovePathComplete() self(" + self + ") The AI is dead.");
if(ai_combat_movement.aiIsMovingToSee())
{
ai_combat_movement.aiClearMoveMode();
ai_combat_movement.aiMoveToSee(ai_combat_movement.aiGetMoveToSeeTarget());
}
else if(ai_combat_movement.aiIsMovingToDeathBlow())
{
// Need to make sure the target is incapped and valid
//killPlayer(self, ai_combat_movement.aiGetMoveToDeathBlowTarget(self));
}
return SCRIPT_CONTINUE;
}
trigger OnMoveMoving()
{
if(!ai_lib.isInCombat(self))
{
return SCRIPT_CONTINUE;
}
clog("OnMoveMoving() self(" + self + ") The AI is dead.");
return SCRIPT_OVERRIDE;
}
trigger OnMovePathNotFound()
{
if(!ai_lib.isInCombat(self))
{
return SCRIPT_CONTINUE;
}
if(ai_combat_movement.aiIsMovingToSee())
{
const obj_id target = ai_combat_movement.aiGetMoveToSeeTarget();
clog("OnMovePathNotFound() self(" + self + ") The AI was moving to see(" + target + ":" + getName(target) + ") and something went wrong so forcing tether.");
stopCombat(self);
}
else if(ai_combat_movement.aiIsMovingToDeathBlow())
{
// Need to make sure the target is incapped and valid
//killPlayer(self, ai_combat_movement.aiGetMoveToDeathBlowTarget(self));
}
return SCRIPT_OVERRIDE;
}
messageHandler handleMoveRandomClear()
{
return SCRIPT_CONTINUE;
}
messageHandler handleTaunt()
{
if(ai_lib.isInCombat(self))
{
return SCRIPT_CONTINUE;
}
obj_id target = params.getObjId("player");
if(!isIdValid(target))
{
return SCRIPT_CONTINUE;
}
utils.setScriptVar(self, "ai.combat.isTaunted", (getGameTime() + 20));
//debugSpeakMsg(self, "taunted");
stop(self);
//debugSpeakMsg(self, "setting target 1");
setTarget(self, target);
return SCRIPT_CONTINUE;
}
int getConfusionDuration(obj_id npc, obj_id attacker)
{
if(getDistance(npc, attacker) < 10.0f)
{
return 0;
}
int attackerPosture = getPosture(attacker);
int duration = 0;
if(attackerPosture == POSTURE_CROUCHED)
{
duration = 5;
}
else if(attackerPosture == POSTURE_PRONE)
{
duration = 10;
}
if(!ai_lib.isMonster(npc))
{
return duration;
}
boolean masked = false;
if(scout.isScentMasked(attacker, npc))
{
masked = true;
duration += 7;
}
return duration;
}
trigger OnDestroy()
{
clog("OnDestroy() unit(" + self + ":" + getName(self) + ")");
if(ai_lib.isAiDead(self))
{
return SCRIPT_CONTINUE;
}
int combatDestructionDelay = 0;
if(pet_lib.isPet(self) || beast_lib.isBeast(self))
{
return SCRIPT_CONTINUE;
}
//currently in combat, so try again much later:
if(ai_lib.isInCombat(self))
{
combatDestructionDelay = getGameTime() + 30;
utils.setScriptVar(self, "combatDestructionDelay", combatDestructionDelay);
messageTo(self, "handleCombatDestructionDelay", null, 60, false);//try again in 60
return SCRIPT_OVERRIDE;
}
if(!utils.hasScriptVar(self, "combatDestructionDelay"))
{
return SCRIPT_CONTINUE;
}
else
{
combatDestructionDelay = utils.getIntScriptVar(self, "combatDestructionDelay");
}
if(getGameTime() > combatDestructionDelay)
{
return SCRIPT_CONTINUE;//good to destroy
}
messageTo(self, "handleCombatDestructionDelay", null, 30, false);//try again in 30
return SCRIPT_OVERRIDE;
}
messageHandler handleCombatDestructionDelay()
{
destroyObject(self);
return SCRIPT_CONTINUE;
}
messageHandler OnDotPulse()
{
clog("OnDotPulse() self(" + self + ":" + getName(self) + ")");
if(ai_lib.isInCombat(self))
{
return SCRIPT_CONTINUE;
}
if(ai_lib.isAiDead(self))
{
return SCRIPT_CONTINUE;
}
if(pet_lib.isPet(self) || beast_lib.isBeast(self))
{
return SCRIPT_CONTINUE;
}
obj_id[] players = pvpGetEnemiesInRange(self, self, 128.0f);
if(players == null || players.length < 1)
{
return SCRIPT_CONTINUE;
}
for (int i=0; i < players.length; i++)
{
if(isIdValid(players[i]) && exists(players[i]) && players[i] != self)
{
startCombat(self, players[i]);
return SCRIPT_CONTINUE;
}
}
return SCRIPT_CONTINUE;
}
trigger OnIncapacitateTarget(obj_id target)
{
if(!isIdValid(target))
{
clog("OnIncapacitateTarget() self(" + self + ":" + getName(self) + ") target(" + target + ":" + getName(target) + ") INVALID TARGET");
return SCRIPT_CONTINUE;
}
if(!isPlayer(target) && !pet_lib.isPet(target) && !beast_lib.isBeast(target))
{
clog("OnIncapacitateTarget() self(" + self + ":" + getName(self) + ") target(" + target + ":" + getName(target) + ") TARGET IS NON-PLAYER");
return SCRIPT_CONTINUE;
}
if(aiIsKiller(self))
{
//debugSpeakMsgc(aiLoggingEnabled(self), self, "DEATH BLOW(" + target + ")");
if(pet_lib.isPet(target))
{
clog("OnIncapacitateTarget() self(" + self + ":" + getName(self) + ") target(" + target + ":" + getName(target) + ") DEATH BLOW PET");
pet_lib.killPet(target);
}
else if(beast_lib.isBeast(target))
{
clog("OnIncapacitateTarget() self(" + self + ":" + getName(self) + ") target(" + target + ":" + getName(target) + ") DEATH BLOW BEAST");
beast_lib.killBeast(target, self);
}
else
{
clog("OnIncapacitateTarget() self(" + self + ":" + getName(self) + ") target(" + target + ":" + getName(target) + ") DEATH BLOW PLAYER");
pclib.coupDeGrace(target, self);
}
}
return SCRIPT_CONTINUE;
}
trigger OnIncapacitated(obj_id killer)
{
obj_id[] myHateList = getHateList(self);
if (myHateList == null || myHateList.length == 0)
return SCRIPT_CONTINUE;
for (int i=0;i<myHateList.length;i++)
{
removeHateTarget(self, myHateList[i]);
removeHateTarget(myHateList[i], self);
}
return SCRIPT_CONTINUE;
}
messageHandler handleAggroCheck()
{
const obj_id target = params.getObjId("target");
if(!isIdValid(target) || !exists(target) || isDead(target))
return SCRIPT_CONTINUE;
ai_aggro.stopAttemptingAggroCheck(target);
ai_aggro.requestAggroCheck(target);
return SCRIPT_CONTINUE;
}
messageHandler handleAggroStart()
{
if(beast_lib.isBeast(self))
{
return SCRIPT_CONTINUE;
}
const obj_id target = params.getObjId("target");
if(isIdValid(target) && exists(target) && !isDead(target))
{
if(!stealth.hasServerCoverState(target) && canSee(self, target))
{
startCombatWithAssist(self, target);
}
}
return SCRIPT_CONTINUE;
}
messageHandler handleSawRecapacitation()
{
clog("handleSawRecapacitation() self(" + self + ":" + getName(self) + ")");
const obj_id player = params.getObjId("player");
stopListeningToMessage(player, "handleSawRecapacitation");
return SCRIPT_CONTINUE;
}
void clearCombatActions()
{
obj_id self = getSelf();
removeObjVar(self, "ai.combat.forcedAction");
removeObjVar(self, "ai.combat.pendingAction");
}
messageHandler checkForSpecials()
{
if (!verifyMessage(self, params))
return SCRIPT_CONTINUE;
string bestBeastAbility = beast_lib.getBestAutoRepeatAbility(self);
obj_id target = getTarget(self);
if (!isIdValid(target))
{
return SCRIPT_CONTINUE;
}
if (bestBeastAbility != "")
{
queueCommand(getMaster(self), getStringCrc(bestBeastAbility), target, "", COMMAND_PRIORITY_DEFAULT);
}
messageTo(self, "checkForSpecials", stampMessage(self), 1.0f, false);
return SCRIPT_CONTINUE;
}
dictionary stampMessage(obj_id self)
{
int session = getMessageSession(self);
utils.setScriptVar(self, "messageStamp", session);
dictionary dict = new dictionary();
dict.put("sessionId", session);
return dict;
}
int getMessageSession(obj_id self)
{
return utils.hasScriptVar(self, "messageStamp") ? utils.getIntScriptVar(self, "messageStamp") : 0;
}
boolean verifyMessage(obj_id self, dictionary params)
{
int messageId = params.getInt("sessionId");
int currentId = utils.getIntScriptVar(self, "messageStamp");
return messageId == currentId;
}
void bumpSession(obj_id self)
{
int session = getMessageSession(self);
session++;
utils.setScriptVar(self, "messageStamp", session);
}
//We have to fire off this ultra lame messageTo because in multimob encounters the exitedcombat and enteredcombat fire in the same frame
//leading to our stamps being out of sync
messageHandler checkForBeastSpecialsTakeOne()
{
if(beast_lib.isBeast(self))
{
messageTo(self, "checkForSpecials", stampMessage(self), 1.0f, false);
}
return SCRIPT_CONTINUE;
}
messageHandler persist_combat()
{
if (!trial.verifySession(self, params, combat.PERSIST_COMBAT))
return SCRIPT_CONTINUE;
if (!combat.isValidPersistCombat(self))
{
return SCRIPT_CONTINUE;
}
resetHateTimer(self);
messageTo(self, "persist_combat", trial.getSessionDict(self, combat.PERSIST_COMBAT), 5.0f, false);
return SCRIPT_CONTINUE;
}