mirror of
https://bitbucket.org/seefoe/dsrc.git
synced 2026-07-31 01:15:55 -04:00
3195 lines
80 KiB
Plaintext
3195 lines
80 KiB
Plaintext
include ai.ai_aggro;
|
|
include ai.ai_combat;
|
|
include ai.ai_combat_assist;
|
|
include java.lang.Math
|
|
include library.ai_lib;
|
|
include library.attrib;
|
|
include library.beast_lib;
|
|
include library.buff;
|
|
include library.callable;
|
|
include library.camping;
|
|
include library.chat;
|
|
include library.collection;
|
|
include library.colors;
|
|
include library.corpse;
|
|
include library.create;
|
|
include library.factions;
|
|
include library.gcw;
|
|
include library.group;
|
|
include library.groundquests;
|
|
include library.instance;
|
|
include library.locations;
|
|
include library.loot;
|
|
include library.performance;
|
|
include library.permissions;
|
|
include library.pet_lib;
|
|
include library.posture;
|
|
include library.prose;
|
|
include library.resource;
|
|
include library.scheduled_drop;
|
|
include library.scenario;
|
|
include library.scout;
|
|
include library.static_item;
|
|
include library.stealth;
|
|
include library.storyteller;
|
|
include library.sui;
|
|
include library.utils;
|
|
include library.xp;
|
|
|
|
const boolean LOGGING_ON = false;
|
|
const string LOGGING_CATEGORY = "ai_script";
|
|
|
|
const string MENU_FILE = "pet/pet_menu";
|
|
|
|
const float CORPSE_CLEANUP_DELAY = 300.0f;
|
|
|
|
const string CREATURE_TABLE = "datatables/mob/creatures.iff";
|
|
|
|
const string_id SID_NOTIFY_STALKED = new string_id("skl_use","notify_stalked");
|
|
const string_id SID_CANT_MILK = new string_id("skl_use","milk_cant");
|
|
const string_id SID_MILK_NOT_HIDDEN = new string_id("skl_use","milk_not_hidden");
|
|
const string_id SID_MILK_BEGIN = new string_id("skl_use","milk_begin");
|
|
const string_id SID_MILK_TOO_FAR = new string_id("skl_use","milk_too_far");
|
|
const string_id SID_CANT_MILK_THE_DEAD = new string_id("skl_use","milk_cant_milk_the_dead");
|
|
const string_id SID_MILK_CONTINUE = new string_id("skl_use","milk_continue");
|
|
const string_id SID_MILK_SUCCESS = new string_id("skl_use","milk_success");
|
|
const string_id SID_BEING_MILKED = new string_id("skl_use","being_milked");
|
|
const string_id SID_CANT_MILK_MOUNTED = new string_id("skl_use","milk_mounted");
|
|
const string_id SID_CANT_MILK_COMBAT = new string_id("skl_use","milk_combat");
|
|
const string_id SID_CANT_MILK_INV_FULL = new string_id("skl_use","milk_inventory_full");
|
|
|
|
//Meatlump Messages
|
|
const string_id SID_ALREADY_RECRUITED = new string_id("collection", "already_recruited");
|
|
const string_id SID_ENEMY_RECRUIT = new string_id("collection", "enemy_recruit");
|
|
const string_id SID_GAVE_RECRUIT_ITEM = new string_id("collection", "gave_recruit_item");
|
|
const string_id SID_NPC_MEATLUMP_SPEAK = new string_id("collection", "npc_meatlump_speak");
|
|
const string_id SID_NO_RECRUIT_REB_IMP = new string_id("collection", "no_recruit_reb_imp");
|
|
|
|
void initializeScript()
|
|
{
|
|
obj_id self = getSelf();
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::initializeScript() self(" + self + ":" + getName(self) + ")");
|
|
|
|
if ( hasObjVar(self, "ai.threat") )
|
|
{
|
|
removeObjVar(self, "ai.threat");
|
|
}
|
|
|
|
// This makes the dot on the radar show red for this AI
|
|
|
|
if(ai_lib.isAggro(self))
|
|
{
|
|
setCondition(self, CONDITION_AGGRESSIVE);
|
|
}
|
|
|
|
location loc = getLocation(self);
|
|
boolean setHomeLoc = true;
|
|
if(loc == null)
|
|
{
|
|
setHomeLoc = false;
|
|
LOG("debug_ai", "WARNING: got null location in ai.initializeScript for creature " + self);
|
|
}
|
|
else if(loc.area == null)
|
|
{
|
|
setHomeLoc = false;
|
|
LOG("debug_ai", "WARNING: null scene from getLocation in ai.initializeScript for creature " + self);
|
|
}
|
|
else if(loc.area.length() == 0)
|
|
{
|
|
setHomeLoc = false;
|
|
LOG("debug_ai", "WARNING: empty scene from getLocation in ai.initializeScript for creature " + self);
|
|
}
|
|
|
|
if ( setHomeLoc )
|
|
aiSetHomeLocation(self, loc);
|
|
|
|
float ai_yaw = getYaw(self);
|
|
|
|
setWantSawAttackTriggers(self, aiIsAssist(self) || aiIsGuard(self));
|
|
|
|
// If a previous script already set the default behavior, lets not overwrite it!
|
|
if(!hasObjVar(self, "ai.defaultCalmBehavior"))
|
|
{
|
|
setObjVar(self, "ai.defaultCalmBehavior", ai_lib.BEHAVIOR_LOITER);
|
|
}
|
|
|
|
if(!hasObjVar(self, "ai.yaw"))
|
|
{
|
|
setObjVar(self, "ai.yaw", ai_yaw);
|
|
}
|
|
|
|
ai_lib.resetAi();
|
|
}
|
|
|
|
/**
|
|
* 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", "ai::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", "ai::OnAddedToWorld() self(" + self + ":" + getName(self) + ")");
|
|
|
|
messageTo(self, "handleOnAddedToWorldDelay", null, 2, false);
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler handleOnAddedToWorldDelay()
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnAddedToWorld() self(" + self + ":" + getName(self) + ")");
|
|
|
|
initializeScript();
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnRemovingFromWorld()
|
|
{
|
|
if(ai_lib.isAiDead(self) && !pet_lib.isPet(self) && !beast_lib.isBeast(self) && isObjectPersisted(self))
|
|
{
|
|
destroyObject(self);
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnFleeTargetLost(obj_id oldTarget)
|
|
{
|
|
if(aiLoggingEnabled(self))
|
|
{
|
|
debugSpeakMsg(self, "OnFleeTargetLost(" + oldTarget + ")");
|
|
}
|
|
|
|
removeObjVar(self, "ai.threat");
|
|
doDefaultCalmBehavior(self);
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnFleePathNotFound(obj_id oldTarget)
|
|
{
|
|
if(aiLoggingEnabled(self))
|
|
{
|
|
debugSpeakMsg(self, "OnFleePathNotFound(" + oldTarget + ")");
|
|
}
|
|
|
|
doDefaultCalmBehavior(self);
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnFleeWaypoint(obj_id target)
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler resumeDefaultCalmBehavior()
|
|
{
|
|
if(isInNpcConversation(self) || aiIsTethered(self))
|
|
{
|
|
if (!hasMessageTo(self, "resumeDefaultCalmBehavior"))
|
|
messageTo(self, "resumeDefaultCalmBehavior", null, 5, false);
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::resumeDefaultCalmBehavior() self(" + self + getName(self) + ")");
|
|
|
|
doDefaultCalmBehavior(self);
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
void doDefaultCalmBehavior(obj_id npc)
|
|
{
|
|
if(!npc.isLoaded() || !exists(npc))
|
|
{
|
|
LOGC(aiLoggingEnabled(npc), "debug_ai", "ai::doDefaultCalmBehavior() npc(?) ISN'T LOADED!");
|
|
return;
|
|
}
|
|
|
|
if(ai_lib.isInCombat(npc))
|
|
{
|
|
LOGC(aiLoggingEnabled(npc), "debug_ai", "ai::doDefaultCalmBehavior() npc(" + npc + ") I THINK I AM IN COMBAT");
|
|
return;
|
|
}
|
|
|
|
if(ai_lib.isAiDead(npc))
|
|
{
|
|
LOGC(aiLoggingEnabled(npc), "debug_ai", "ai::doDefaultCalmBehavior() npc(" + npc + ") I THINK I AM INCAPACITATED");
|
|
return;
|
|
}
|
|
|
|
removeObjVar(npc, "ai.threat");
|
|
ai_lib.setMood(npc, ai_lib.MOOD_CALM);
|
|
|
|
if(!hasObjVar(npc, "ai.rangedOnly"))
|
|
{
|
|
//debugSpeakMsg(npc, "xx: ai");
|
|
//ai_combat.unequipWeapon(npc);
|
|
}
|
|
|
|
messageTo(npc, "redoYaw", null, 30, false);
|
|
|
|
// NOTE: uncomment this if we start using suspendMovement() for all movement and not just patrols
|
|
// if(hasSuspendedMovement(npc))
|
|
// {
|
|
// if(getPosture(npc) != POSTURE_UPRIGHT)
|
|
// posture.stand(npc);
|
|
// resumeMovement(npc);
|
|
// return;
|
|
// }
|
|
|
|
/*----------- FOLLOWING -----------------------*/
|
|
if(ai_lib.isFollowing(npc))
|
|
{
|
|
LOGC(aiLoggingEnabled(npc), "debug_ai", "ai::doDefaultCalmBehavior() npc(" + npc + ") FOLLOWING");
|
|
|
|
obj_id target = ai_lib.getFollowTarget(npc);
|
|
|
|
if(isIdValid(target) && exists(target) && !ai_lib.isAiDead(target) && isInWorld(target))
|
|
{
|
|
ai_lib.resumeFollow(npc);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
removeObjVar(npc, "ai.persistantFollowing");
|
|
}
|
|
}
|
|
|
|
/*----------- IN FORMATION -----------------------*/
|
|
if(hasObjVar(npc, "ai.inFormation"))
|
|
{
|
|
LOGC(aiLoggingEnabled(npc), "debug_ai", "ai::doDefaultCalmBehavior() npc(" + npc + ") IN FORMATION");
|
|
|
|
//debugSpeakMsg(npc, "i am in formation!");
|
|
if(getPosture(npc) != POSTURE_UPRIGHT)
|
|
{
|
|
posture.stand(npc);
|
|
}
|
|
|
|
ai_lib.resumeFormationFollowing(npc);
|
|
return;
|
|
}
|
|
|
|
//debugSpeakMsg(npc, "i am NOT in formation!");
|
|
|
|
/*----------- PATROLING -----------------------*/
|
|
if(ai_lib.canPatrol(npc))
|
|
{
|
|
LOGC(aiLoggingEnabled(npc), "debug_ai", "ai::doDefaultCalmBehavior() npc(" + npc + ") PATROLING");
|
|
|
|
if(getPosture(npc) != POSTURE_UPRIGHT)
|
|
{
|
|
posture.stand(npc);
|
|
}
|
|
|
|
ai_lib.resumePatrol(npc);
|
|
return;
|
|
}
|
|
|
|
/*----------- PATHING -----------------------*/
|
|
if(hasObjVar(npc, "ai.persistentPathing"))
|
|
{
|
|
LOGC(aiLoggingEnabled(npc), "debug_ai", "ai::doDefaultCalmBehavior() npc(" + npc + ") PATHING");
|
|
|
|
if(getPosture(npc) != POSTURE_UPRIGHT)
|
|
{
|
|
posture.stand(npc);
|
|
}
|
|
|
|
location pathLoc = getLocationObjVar(npc, "ai.persistentPathing");
|
|
pathTo(npc, pathLoc);
|
|
return;
|
|
}
|
|
|
|
if(hasObjVar(npc, "ai.persistentPathingWaypoint"))
|
|
{
|
|
LOGC(aiLoggingEnabled(npc), "debug_ai", "ai::doDefaultCalmBehavior() npc(" + npc + ") ai.persistentPathingWaypoint");
|
|
|
|
if(getPosture(npc) != POSTURE_UPRIGHT)
|
|
{
|
|
posture.stand(npc);
|
|
}
|
|
|
|
string pathWaypoint = getStringObjVar(npc, "ai.persistentPathingWaypoint");
|
|
pathTo(npc, pathWaypoint);
|
|
return;
|
|
}
|
|
|
|
if(getConfigSetting("GameServer", "disableAILoitering")!=null)
|
|
{
|
|
LOGC(aiLoggingEnabled(npc), "debug_ai", "ai::doDefaultCalmBehavior() npc(" + npc + ") disableAILoitering");
|
|
return;
|
|
}
|
|
|
|
const int defaultBehavior = ai_lib.getDefaultCalmBehavior(npc);
|
|
|
|
switch (defaultBehavior)
|
|
{
|
|
case ai_lib.BEHAVIOR_STOP:
|
|
case ai_lib.BEHAVIOR_SENTINEL:
|
|
LOGC(aiLoggingEnabled(npc), "debug_ai", "ai::doDefaultCalmBehavior() npc(" + npc + getName(npc) + ") BEHAVIOR_STOP");
|
|
stop(npc);
|
|
|
|
break;
|
|
|
|
default:
|
|
case ai_lib.BEHAVIOR_LOITER:
|
|
case ai_lib.BEHAVIOR_WANDER:
|
|
if(pet_lib.isPet(npc) || beast_lib.isBeast(npc))
|
|
{
|
|
aiSetHomeLocation(npc, getLocation(npc));
|
|
}
|
|
|
|
const float collisionRadius = getObjectCollisionRadius(npc);
|
|
float minDistance = 0.0f;
|
|
float maxDistance = 0.0f;
|
|
|
|
if(hasObjVar(npc, "ai.loiterMinRange"))
|
|
{
|
|
minDistance = getFloatObjVar(npc, "ai.loiterMinRange");
|
|
maxDistance = getFloatObjVar(npc, "ai.loiterMaxRange");
|
|
}
|
|
else
|
|
{
|
|
// If the AI is in the world, let it loiter at a greater distance
|
|
|
|
const float cellDistanceModifier = !isIdValid(getLocation(npc).cell) ? 3.0f : 1.0f;
|
|
|
|
maxDistance = Math.min(128.0f, collisionRadius * 7.0f * cellDistanceModifier);
|
|
minDistance = 0.0f;
|
|
}
|
|
|
|
const float minDelay = Math.min(Math.max(8.0f, collisionRadius * 2.0f), 16.0f);
|
|
const float maxDelay = Math.min(Math.max(8.0f, collisionRadius * 4.0f), 16.0f);
|
|
|
|
loiterLocation(npc, aiGetHomeLocation(npc), minDistance, maxDistance, minDelay, maxDelay);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
trigger OnFollowMoving(obj_id target)
|
|
{
|
|
if(ai_lib.isInCombat(self))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
if(ai_lib.isAiDead(self))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
if(getPosture(self) != POSTURE_UPRIGHT)
|
|
{
|
|
stop(self);
|
|
removeObjVar(self, "ai.combat.moveMode");
|
|
posture.stand(self);
|
|
|
|
messageTo(self, "resumeDefaultCalmBehavior", null, 2, false);
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnFollowTargetLost(obj_id oldTarget)
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnFollowTargetLost() self(" + self + ":" + getName(self) + ")");
|
|
|
|
if(!ai_lib.isInCombat(self))
|
|
{
|
|
ai_lib.aiStopFollowing(self);
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnExitedCombat()
|
|
{
|
|
if(utils.hasScriptVar(self, "sentAllyDistressCall"))
|
|
{
|
|
utils.removeScriptVar(self, "sentAllyDistressCall");
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnFollowWaiting(obj_id target)
|
|
{
|
|
if(!target.isLoaded() || !exists(target))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
if(hasObjVar(target, "gm"))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
if(ai_lib.isInCombat(self))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
if(!isIdValid(target) || ai_lib.isAiDead(target))
|
|
{
|
|
ai_lib.aiStopFollowing(self);
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnFollowPathNotFound(obj_id target)
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnFollowPathNotFound() self(" + self + ":" + getName(self) + ")");
|
|
|
|
if(!ai_lib.isInCombat(self))
|
|
{
|
|
ai_lib.aiStopFollowing(self);
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnMovePathComplete()
|
|
{
|
|
if(!isValidId(self) || !exists(self))
|
|
return SCRIPT_CONTINUE;
|
|
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnMovePathComplete() self(" + self + ")");
|
|
|
|
if(hasObjVar(self, "ai.persistentPathing"))
|
|
{
|
|
location pathLoc = getLocationObjVar(self, "ai.persistentPathing");
|
|
if(utils.getDistance(self, pathLoc) < 2.0f)
|
|
{
|
|
removeObjVar(self, "ai.persistentPathing");
|
|
}
|
|
}
|
|
|
|
if(hasObjVar(self, "ai.persistentWaypoint"))
|
|
{
|
|
removeObjVar(self, "ai.persistentPathingWaypoint");
|
|
}
|
|
|
|
if(!ai_lib.isInCombat(self))
|
|
{
|
|
//if(hasObjVar(self, "ai.stalking"))
|
|
// maintainStalking(self);
|
|
|
|
if(getBehavior(self) <= BEHAVIOR_CALM)
|
|
{
|
|
ai_lib.resumePatrol(self);
|
|
}
|
|
|
|
if(utils.hasScriptVar(self, "pathingToAttack"))
|
|
{
|
|
obj_id target = utils.getObjIdScriptVar(self, "pathingToAttack");
|
|
|
|
if(isIdValid(target))
|
|
{
|
|
//debugSpeakMsg(self, "attack 1");
|
|
startCombat(self, target);
|
|
}
|
|
|
|
utils.removeScriptVar(self, "pathingToAttack");
|
|
}
|
|
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnMovePathNotFound()
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnMovePathNotFound() self(" + self + ":" + getName(self) + ")");
|
|
|
|
if(utils.hasScriptVar(self, "pathingToAttack"))
|
|
{
|
|
utils.removeScriptVar(self, "pathingToAttack");
|
|
}
|
|
|
|
//debugSpeakMsg(self, "move path not found");
|
|
stop(self);
|
|
|
|
if(getBehavior(self) <= BEHAVIOR_CALM && !ai_lib.isAiDead(self))
|
|
{
|
|
setLocation(self, aiGetHomeLocation(self));
|
|
messageTo(self, "resumeDefaultCalmBehavior", null, 5, false);
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnTriggerVolumeEntered(string volumeName, obj_id breacher)
|
|
{
|
|
// Set non-aggro wherever a pet is initialized and remove this scriptvar
|
|
if(utils.hasScriptVar(self, "petBeingInitialized"))
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnTriggerVolumeEntered(" + volumeName + ") self(" + self + ":" + getName(self) + ") PET BEING INITIALIZED");
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
if(!isMob(breacher))
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnTriggerVolumeEntered(" + volumeName + ") SCRIPT_CONTINUE - The breacher is not a mob");
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
if(breacher == self)
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnTriggerVolumeEntered(" + volumeName + ") SCRIPT_CONTINUE - The breacher == self");
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
if(ai_lib.isAiDead(self))
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "WARNING: ai::OnTriggerVolumeEntered(" + volumeName + ") self(" + self + ":" + getName(self) + ") I AM DEAD");
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
if(beast_lib.isBeast(self) && (!beast_lib.getBeastDefensive(self) || !hasCommand(getMaster(self), beast_lib.BM_COMMAND_ATTACK)))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnTriggerVolumeEntered(" + volumeName + ") self(" + self + ":" + getName(self) + ") radius(" + getTriggerVolumeRadius(self, volumeName) + ") breacher(" + breacher + ":" + getName(breacher) + ")");
|
|
|
|
const boolean breacherIsAPlayer = isPlayer(breacher);
|
|
|
|
if(volumeName.equals(ai_lib.ALERT_VOLUME_NAME))
|
|
{
|
|
if(isPlayer(breacher) && !ai_lib.isMonster(self))
|
|
{
|
|
ai_lib.greet(self, breacher);
|
|
}
|
|
|
|
if(hasTriggerVolume(self, ai_lib.AGGRO_VOLUME_NAME))
|
|
{
|
|
boolean aggro = false;
|
|
|
|
if(pvpIsEnemy(self, breacher))
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnTriggerVolumeEntered(" + volumeName + ") Breacher is a threat (pvp enemy)");
|
|
aggro = true;
|
|
}
|
|
else
|
|
{
|
|
if(isPlayer(breacher))
|
|
{
|
|
// AI vs Player
|
|
const int factionStatus = factions.getFactionStatus(self, breacher);
|
|
|
|
if(factionStatus == factions.STATUS_ENEMY)
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnTriggerVolumeEntered(" + volumeName + ") Player breacher is a threat (faction enemy)");
|
|
aggro = true;
|
|
}
|
|
|
|
else if((factionStatus == factions.STATUS_NEUTRAL) && aiIsAggressive(self))
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnTriggerVolumeEntered(" + volumeName + ") Player breacher is a threat (faction neutral, I am aggressive)");
|
|
aggro = true;
|
|
}
|
|
else if(pvpGetAlignedFaction(self) != 0)
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnTriggerVolumeEntered(" + volumeName + ") Player breacher is a NOT currently a threat (but could be with faction alignment change)");
|
|
aggro = true;
|
|
}
|
|
else
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnTriggerVolumeEntered(" + volumeName + ") Player breacher is a NOT a threat");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(aiIsGuard(self))
|
|
{
|
|
// Guard AI vs AI
|
|
|
|
const string breacherFaction = factions.getFaction(breacher);
|
|
|
|
if( (breacherFaction != null)
|
|
&& (breacherFaction.equals("townsperson") || breacherFaction.equals("ImperialCitizen") || breacherFaction.equals("RebelCitizen")))
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnTriggerVolumeEntered(" + volumeName + ") guard: AI breacher is a NOT a threat (townsperson)");
|
|
}
|
|
else
|
|
{
|
|
if(ai_lib.isSameSocialGroup(self, breacher))
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnTriggerVolumeEntered(" + volumeName + ") guard: AI breacher is NOT a threat (same social group)");
|
|
}
|
|
else if(pet_lib.isPet(breacher) || beast_lib.isBeast(breacher))
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnTriggerVolumeEntered(" + volumeName + ") guard: AI breacher is NOT a threat (pet)");
|
|
}
|
|
else
|
|
{
|
|
if(ai_lib.isAnimal(breacher))
|
|
{
|
|
String creatureType = getStringObjVar(self, "creature_type");
|
|
if(creatureType.startsWith("gcw_city"))
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnTriggerVolumeEntered(" + volumeName + ") guard: AI breacher is NOT a threat (gcw_city_invasion npc)");
|
|
}
|
|
else
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnTriggerVolumeEntered(" + volumeName + ") guard: AI breacher is a threat (animal)");
|
|
aggro = true;
|
|
}
|
|
}
|
|
else if(factions.getFactionStatus(self, breacher) == factions.STATUS_ENEMY)
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnTriggerVolumeEntered(" + volumeName + ") guard: AI breacher is a threat (factional enemy)");
|
|
aggro = true;
|
|
}
|
|
else if(hasAttributeAttained(breacher, attrib.THUG))
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnTriggerVolumeEntered(" + volumeName + ") guard: AI breacher is a threat (thug)");
|
|
aggro = true;
|
|
}
|
|
else
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnTriggerVolumeEntered(" + volumeName + ") guard: AI breacher is NOT a threat");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// AI vs AI
|
|
|
|
if(ai_lib.isSameSocialGroup(self, breacher))
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnTriggerVolumeEntered(" + volumeName + ") AI breacher is NOT a threat (same social group)");
|
|
}
|
|
else
|
|
{
|
|
const int factionStatus = factions.getFactionStatus(self, breacher);
|
|
|
|
if(factionStatus == factions.STATUS_ENEMY)
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnTriggerVolumeEntered(" + volumeName + ") AI breacher is a threat (factional enemy)");
|
|
aggro = true;
|
|
}
|
|
else
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnTriggerVolumeEntered(" + volumeName + ") AI breacher is NOT a threat");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// storyteller aggro check
|
|
if(hasObjVar(self, "storytellerid"))
|
|
{
|
|
if(!utils.hasScriptVar(breacher, "storytellerid"))
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnTriggerVolumeEntered(" + volumeName + ") Player breacher is a NOT a member of my Story");
|
|
aggro = false;
|
|
}
|
|
else
|
|
{
|
|
obj_id selfStoryTellerId = getObjIdObjVar(self, "storytellerid");
|
|
obj_id breacherStoryTellerId = utils.getObjIdScriptVar(breacher, "storytellerid");
|
|
if( selfStoryTellerId != breacherStoryTellerId )
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnTriggerVolumeEntered(" + volumeName + ") Player breacher is a NOT a member of my Story (but he is a member of another Story)");
|
|
aggro = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(aggro)
|
|
{
|
|
addTriggerVolumeEventSource(ai_lib.AGGRO_VOLUME_NAME, breacher);
|
|
//pvpSetPermanentPersonalEnemyFlag(self, breacher);
|
|
//pvpAttackPerformed(self, breacher);
|
|
}
|
|
}
|
|
}
|
|
else if(volumeName.equals(ai_lib.AGGRO_VOLUME_NAME))
|
|
{
|
|
if(!isPlayer(breacher))
|
|
{
|
|
ai_aggro.requestAggroCheck(breacher);
|
|
}
|
|
else
|
|
{
|
|
if(scout.isScentMasked(breacher, self))
|
|
{
|
|
//debugSpeakMsgc(aiLoggingEnabled(self), self, "handleAggroCheck(" + breacher + ") SCENT IS MASKED");
|
|
}
|
|
else
|
|
{
|
|
if(isIncapacitated(breacher))
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::requestSawRecapacitation() self(" + self + ":" + getName(self) + ") breacher(" + breacher + ":" + getName(breacher) + ") Listening for breacher recapacitation");
|
|
|
|
listenToMessage(breacher, "handleSawRecapacitation");
|
|
}
|
|
|
|
ai_aggro.requestAggroCheck(breacher);
|
|
}
|
|
}
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnTriggerVolumeExited(string volumeName, obj_id breacher)
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnTriggerVolumeExited(" + volumeName + ") self(" + self + ":" + getName(self) + ") radius(" + getTriggerVolumeRadius(self, volumeName) + ") breacher(" + breacher + ":" + getName(breacher) + ")");
|
|
|
|
// Set non-aggro wherever a pet is initialized and remove this scriptvar
|
|
|
|
if(utils.hasScriptVar(self, "petBeingInitialized"))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
if(volumeName.equals(ai_lib.ALERT_VOLUME_NAME))
|
|
{
|
|
|
|
if( isPlayer(breacher)
|
|
&& !ai_lib.isMonster(self))
|
|
{
|
|
ai_lib.dismiss(self, breacher);
|
|
}
|
|
|
|
removeTriggerVolumeEventSource(ai_lib.AGGRO_VOLUME_NAME, breacher);
|
|
|
|
// 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 != breacher)
|
|
{
|
|
pvpRemovePersonalEnemyFlags(self, breacher);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pvpRemovePersonalEnemyFlags(self, breacher);
|
|
}
|
|
}
|
|
else if(volumeName.equals(ai_lib.AGGRO_VOLUME_NAME))
|
|
{
|
|
if(isPlayer(breacher))
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::requestSawRecapacitation() self(" + self + ":" + getName(self) + ") breacher(" + breacher + ":" + getName(breacher) + ") Stop listening for breacher recapacitation");
|
|
|
|
stopListeningToMessage(breacher, "handleSawRecapacitation");
|
|
}
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnLoiterWaiting(modifiable_float time)
|
|
{
|
|
// LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnLoiterWaiting() self(" + self + ":" + getName(self) + ") time(" + time + ")");
|
|
|
|
// AI needs to possibly sit or lie down during the loiter wait times using the following functions
|
|
// ai_lib.isMonster(self)
|
|
// ai_lib.canLieDown(self)
|
|
// ai_lib.canSit(self, POSTURE_SITTING)
|
|
// ai_lib.canLieDown(self)
|
|
// ai_lib.aiSetPosture(self, POSTURE_LYING_DOWN)
|
|
|
|
if(ai_lib.isHerdingCreature(self))
|
|
{
|
|
ai_lib.doAction(self, "eat");
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
boolean isThreat(obj_id npc, obj_id breacher)
|
|
{
|
|
if(hasObjVar(breacher, "gm"))
|
|
{
|
|
LOGC(aiLoggingEnabled(npc), "debug_ai", "ai::isThreat() npc(" + npc + ") breacher(" + breacher + ") breacher is a gm");
|
|
return false;
|
|
}
|
|
|
|
if(!isValidId(breacher))
|
|
{
|
|
LOGC(aiLoggingEnabled(npc), "debug_ai", "ai::isThreat() npc(" + npc + ") breacher(" + breacher + ") invalid breacher id");
|
|
return false;
|
|
}
|
|
|
|
if(!isMob(breacher))
|
|
{
|
|
LOGC(aiLoggingEnabled(npc), "debug_ai", "ai::isThreat() npc(" + npc + ") breacher(" + breacher + ") breacher is not a mob");
|
|
return false;
|
|
}
|
|
|
|
if(ai_lib.isAiDead(npc))
|
|
{
|
|
LOGC(aiLoggingEnabled(npc), "debug_ai", "ai::isThreat() npc(" + npc + ") breacher(" + breacher + ") npc is dead");
|
|
return false;
|
|
}
|
|
|
|
if(!isPlayer(breacher) && ai_lib.isAiDead(breacher))
|
|
{
|
|
LOGC(aiLoggingEnabled(npc), "debug_ai", "ai::isThreat() npc(" + npc + ") breacher(" + breacher + ") breacher is dead ai");
|
|
return false;
|
|
}
|
|
|
|
if(isInvulnerable(npc))
|
|
{
|
|
LOGC(aiLoggingEnabled(npc), "debug_ai", "ai::isThreat() npc(" + npc + ") breacher(" + breacher + ") npc is invulnerable");
|
|
return false;
|
|
}
|
|
|
|
if(isInvulnerable(breacher))
|
|
{
|
|
LOGC(aiLoggingEnabled(npc), "debug_ai", "ai::isThreat() npc(" + npc + ") breacher(" + breacher + ") breacher is invulnerable");
|
|
return false;
|
|
}
|
|
|
|
if(!pvpCanAttack(npc, breacher))
|
|
{
|
|
LOGC(aiLoggingEnabled(npc), "debug_ai", "ai::isThreat() !pvpCanAttack - npc(" + npc + ":" + getName(npc) + ") npcPvpType(" + pvpGetType(npc) + ") npcPvpAlignedFaction(" + pvpGetAlignedFaction(npc) + ") breacher(" + breacher + ":" + getName(breacher) + ") breacherPvpType(" + pvpGetType(breacher) + ") breacherPvpAlignedFaction(" + pvpGetAlignedFaction(breacher) + ")");
|
|
return false;
|
|
}
|
|
|
|
obj_id master = getMaster(breacher);
|
|
|
|
if(isIdValid(master) && exists(master))
|
|
{
|
|
LOGC(aiLoggingEnabled(npc), "debug_ai", "ai::isThreat() npc(" + npc + ") breacher(" + breacher + ") breacher is a pet/beast with a master");
|
|
return false;
|
|
}
|
|
|
|
return (ai_lib.isAggroToward(npc, breacher) || ai_lib.isAggroToward(breacher, npc));
|
|
}
|
|
|
|
void goAlert(obj_id npc)
|
|
{
|
|
//debugSpeakMsg(npc, "going alert!");
|
|
if(ai_lib.isAiDead(npc))
|
|
return;
|
|
|
|
if(ai_lib.isInCombat(npc))
|
|
return;
|
|
|
|
//showFlyText(npc, new string_id("npc_reaction/flytext", "alert"), 2.0f, colors.MEDIUMSLATEBLUE);
|
|
|
|
if(!hasObjVar(npc, "ai.pathingAwayFrom"))
|
|
{
|
|
stop(npc);
|
|
}
|
|
|
|
ai_lib.doAction(npc, ai_lib.ACTION_ALERT);
|
|
ai_lib.setMood(npc, ai_lib.MOOD_NERVOUS);
|
|
|
|
obj_id threat = getObjIdObjVar(npc, "ai.threat");
|
|
|
|
if(isIdValid(threat))
|
|
{
|
|
debugSpeakMsg(npc, "FEAR 7");
|
|
addToMentalStateToward(npc, threat, FEAR, -5.0f);
|
|
}
|
|
|
|
}
|
|
|
|
void goThreaten(obj_id npc)
|
|
{
|
|
//debugSpeakMsg(npc, "threatening something");
|
|
if(ai_lib.isAiDead(npc))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(ai_lib.isInCombat(npc))
|
|
{
|
|
return;
|
|
}
|
|
|
|
obj_id threat = getObjIdObjVar(npc, "ai.threat");
|
|
|
|
if(!isIdValid(threat))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(hasObjVar(threat, "gm"))
|
|
{
|
|
return;
|
|
}
|
|
|
|
//showFlyText(npc, new string_id("npc_reaction/flytext", "threaten"), 2.0f, colors.ORANGERED);
|
|
|
|
stop(npc);
|
|
faceTo(npc, threat);
|
|
ai_lib.doThreatenAnimation(npc);
|
|
debugSpeakMsg(npc, "FEAR 8");
|
|
addToMentalStateToward(npc, threat, FEAR, -5.0f);
|
|
|
|
if(!isPlayer(threat))
|
|
{
|
|
debugSpeakMsg(threat, "FEAR 9");
|
|
addToMentalStateToward(threat, npc, FEAR, 10.0f);
|
|
}
|
|
|
|
ai_lib.setMood(npc, ai_lib.MOOD_NERVOUS);
|
|
}
|
|
|
|
trigger OnBehaviorChange(int newBehavior, int oldBehavior, int[] changeFlags)
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler handleSetDefaultBehavior()
|
|
{
|
|
doDefaultCalmBehavior(self);
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnGetAttributes(obj_id player, string[] names, string[] attribs)
|
|
{
|
|
int idx = utils.getValidAttributeIndex(names);
|
|
|
|
if(idx == -1)
|
|
{
|
|
blog("idx was negative 1");
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
string milkType = ai_lib.getMilkType(self);
|
|
|
|
if(milkType != null && ai_lib.isMonster(self) && !beast_lib.isBeast(self))
|
|
{
|
|
names[idx] = "got_milk";
|
|
string_id strIdMilkType = new string_id("resource/resource_names", milkType);
|
|
attribs[idx] = utils.packStringId(strIdMilkType);
|
|
idx++;
|
|
}
|
|
|
|
const String creatureName = getCreatureName(self);
|
|
|
|
if(creatureName == null)
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
dictionary creatureKnowledge = beast_lib.getBeastMasterExamineInfo(player, self, names, attribs, idx);
|
|
names = creatureKnowledge.getStringArray("names");
|
|
attribs = creatureKnowledge.getStringArray("attribs");
|
|
idx = creatureKnowledge.getInt("idx");
|
|
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
// MUCH OF THIS MESSAGE HANDLER HAS BEEN DEPRECATED AS WELL. KEEPING IT AROUND ONLY FOR THE FLY TEXT
|
|
//
|
|
// Called when snare effect turns off.
|
|
messageHandler snareEffectOff()
|
|
{
|
|
// Fly over text.
|
|
string_id strFlyText = new string_id("combat_effects", "no_snare");
|
|
color colFlyText = colors.TOMATO;
|
|
showFlyText(self, strFlyText, 1.0f, colFlyText);
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnDefenderCombatAction(obj_id attacker, obj_id weapon, int combatResult)
|
|
{
|
|
if(ai_lib.isAiDead(self) || isInvulnerable(self))
|
|
{
|
|
return SCRIPT_OVERRIDE;
|
|
}
|
|
|
|
//check for Flee Percentage here to either shout for Help, or to Flee.
|
|
if(combatResult == COMBAT_RESULT_HIT)
|
|
{
|
|
chat.setAngryMood(self);
|
|
ai_lib.barkString(self, "hit");
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnSawAttack(obj_id defender, obj_id[] attackers)
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnSawAttack() self(" + self + ":" + getName(self) + ") defender (" + defender + ") attackers.length(" + attackers.length + ")");
|
|
|
|
if(beast_lib.isBeast(self))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
// Lair Assist - MSB: Not sure why lair assist is here yet. It should be in some lair_ai script on the mob spawned by the lair
|
|
if(defendLair(self, defender, attackers, false))
|
|
{
|
|
if(hasScript(self, "npc.converse.npc_convo"))
|
|
{
|
|
if(!isInNpcConversation(self) && !pet_lib.isPet(self) && !beast_lib.isBeast(self))
|
|
{
|
|
detachScript(self, "npc.converse.npc_convo");
|
|
clearCondition(self, CONDITION_CONVERSABLE);
|
|
}
|
|
}
|
|
|
|
debugSpeakMsgc(aiLoggingEnabled(self), self, "lair defense");
|
|
}
|
|
else
|
|
{
|
|
if(pet_lib.isPet(self) && pet_lib.isGuarding(self, defender))
|
|
{
|
|
for (int i = 0; i < attackers.length; ++i)
|
|
{
|
|
const obj_id attacker = attackers[i];
|
|
|
|
if(isIdValid(attacker))
|
|
{
|
|
startCombat(self, attacker);
|
|
|
|
if(!ai_lib.isInCombat(self) && (defender == getMaster(self)))
|
|
{
|
|
ai_lib.barkString(self, "ally");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if(aiIsGuard(self))
|
|
{
|
|
boolean assistDefender = false;
|
|
const string defenderFaction = factions.getFaction(defender);
|
|
|
|
if(defenderFaction != null)
|
|
{
|
|
if(defenderFaction.equals("townsperson") || defenderFaction.equals("ImperialCitizen") || defenderFaction.equals("RebelCitizen"))
|
|
{
|
|
assistDefender = true;
|
|
}
|
|
}
|
|
|
|
if(!assistDefender && ai_lib.isSameSocialGroup(self, defender))
|
|
{
|
|
assistDefender = true;
|
|
}
|
|
|
|
for (int i = 0; i < attackers.length; ++i)
|
|
{
|
|
const obj_id attacker = attackers[i];
|
|
|
|
if(isIdValid(attacker))
|
|
{
|
|
// Guards assist with thugs and any animals that are not pets
|
|
|
|
if(assistDefender || hasAttributeAttained(attacker, attrib.THUG) ||
|
|
(ai_lib.isMonster(attacker) && (!pet_lib.isPet(attacker))))
|
|
{
|
|
debugSpeakMsgc(aiLoggingEnabled(self), self, "OnSawAttackGuard() attacking(" + attacker + ")");
|
|
|
|
ai_combat_assist.assist(self, attacker);
|
|
break;
|
|
}
|
|
else if(ai_lib.isSameSocialGroup(self, attacker))
|
|
{
|
|
debugSpeakMsgc(aiLoggingEnabled(self), self, "OnSawAttack() attacking(" + defender + ")");
|
|
ai_combat_assist.assist(self, defender);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//if(hasScript(self, "npc.converse.npc_convo"))
|
|
//{
|
|
// if(!isInNpcConversation(self) && !pet_lib.isPet(self))
|
|
// {
|
|
// detachScript(self, "npc.converse.npc_convo");
|
|
// clearCondition(self, CONDITION_CONVERSABLE);
|
|
// }
|
|
//}
|
|
for (int i = 0; i < attackers.length; ++i)
|
|
{
|
|
const obj_id attacker = attackers[i];
|
|
|
|
if(isIdValid(attacker))
|
|
{
|
|
if( hasObjVar(self, "storytellerid") )
|
|
{
|
|
if( !utils.hasScriptVar(defender, "storytellerid") && !utils.hasScriptVar(attacker, "storytellerid") )
|
|
{
|
|
debugSpeakMsgc(aiLoggingEnabled(self), self, "OnSawAttack() not attacking(" + defender + ") or (" + attacker + ") as they are not a part of my story");
|
|
break;
|
|
}
|
|
else if( hasObjVar(defender, "storytellerid") && utils.hasScriptVar(attacker, "storytellerid") )
|
|
{
|
|
obj_id attackerStorytellerId = utils.getObjIdScriptVar(attacker, "storytellerid");
|
|
obj_id defenderStorytellerId = getObjIdObjVar(defender, "storytellerid");
|
|
obj_id myStorytellerId = getObjIdObjVar(self, "storytellerid");
|
|
|
|
if(myStorytellerId == defenderStorytellerId && ai_lib.isSameSocialGroup(self, defender))
|
|
{
|
|
debugSpeakMsgc(aiLoggingEnabled(self), self, "OnSawAttack() attacking(" + attacker + ")");
|
|
ai_combat_assist.assist(self, defender);
|
|
break;
|
|
}
|
|
|
|
}
|
|
else if( utils.hasScriptVar(defender, "storytellerid") && hasObjVar(attacker, "storytellerid") )
|
|
{
|
|
obj_id attackerStorytellerId = getObjIdObjVar(attacker, "storytellerid");
|
|
obj_id defenderStorytellerId = utils.getObjIdScriptVar(defender, "storytellerid");
|
|
obj_id myStorytellerId = getObjIdObjVar(self, "storytellerid");
|
|
|
|
if( myStorytellerId == attackerStorytellerId && ai_lib.isSameSocialGroup(self, attacker) )
|
|
{
|
|
debugSpeakMsgc(aiLoggingEnabled(self), self, "OnSawAttack() attacking(" + defender + ")");
|
|
ai_combat_assist.assist(self, attacker);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if(ai_lib.isSameSocialGroup(self, defender))
|
|
{
|
|
debugSpeakMsgc(aiLoggingEnabled(self), self, "OnSawAttack() attacking(" + attacker + ")");
|
|
ai_combat_assist.assist(self, attacker);
|
|
break;
|
|
}
|
|
else if(ai_lib.isSameSocialGroup(self, attacker))
|
|
{
|
|
debugSpeakMsgc(aiLoggingEnabled(self), self, "OnSawAttack() attacking(" + defender + ")");
|
|
ai_combat_assist.assist(self, defender);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
boolean defendLair(obj_id npc, obj_id defender, obj_id[] attackers, boolean isInCombat)
|
|
{
|
|
//LOGC(aiLoggingEnabled(self), "debug_ai", "ai::defendLair() npc(" + npc + ") defender(" + defender + ")");
|
|
|
|
//debugSpeakMsg(npc, "lair defense?");
|
|
|
|
//they can't be attacking my lair if the defender is a mobile.
|
|
if(isMob(defender))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//debugSpeakMsg(npc, "defender is a mobile");
|
|
|
|
//DO I know that I do NOT have a lair?
|
|
if(utils.hasScriptVar(npc, "ai.noLair"))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
obj_id myPOI = ai_lib.getLair(npc);
|
|
|
|
if(!isIdValid(myPOI))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(!hasObjVar(defender, "poi.baseObject"))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//debugSpeakMsg(npc, "you're attacking my lair:");
|
|
|
|
//debugSpeakMsg(npc, "myPOI is " + myPOI);
|
|
//debugSpeakMsg(npc, "defender baseObj is " + getObjIdObjVar(defender, "poi.baseObject"));
|
|
|
|
if(getObjIdObjVar(defender, "poi.baseObject") == myPOI)
|
|
{
|
|
|
|
int curHP = getHitpoints(defender);
|
|
int maxHP = getMaxHitpoints(defender);
|
|
|
|
if(curHP >= maxHP)
|
|
{
|
|
return true; //although we're not doing anything here, it's only
|
|
// because the lair hasn't been damaged yet - the
|
|
// first time we get an OnSawAttack after the lair
|
|
// has taken damage, then we will respond appropriately:
|
|
}
|
|
|
|
if(pvpGetAlignedFaction(npc) == ##"nonaggressive")
|
|
{
|
|
factions.setFaction(npc, "angry");
|
|
}
|
|
|
|
int endLoop = attackers.length;
|
|
|
|
if(endLoop > 20)
|
|
{
|
|
endLoop = 20;
|
|
}
|
|
|
|
for (int i = 0; i < endLoop; i++)
|
|
{
|
|
if(isIdValid(attackers[i]))
|
|
{
|
|
if(!isInCombat)
|
|
{
|
|
//debugSpeakMsg(npc, "attack 27");
|
|
|
|
startCombat(npc, attackers[i]);
|
|
|
|
if(utils.hasScriptVar(npc, "pathingToAttack"))
|
|
{
|
|
utils.removeScriptVar(npc, "pathingToAttack");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
messageHandler stopFleeing()
|
|
{
|
|
debugSpeakMsg(self, "stopping fleeing");
|
|
//utils.removeScriptVar(self, "ai.fleeingFromCombat");
|
|
|
|
if(!ai_lib.isInCombat(self))
|
|
{
|
|
if(getDistance(getLocation(self), aiGetHomeLocation(self)) > 40.0f)
|
|
{
|
|
pathTo(self, aiGetHomeLocation(self));
|
|
}
|
|
}
|
|
|
|
messageTo(self, "redoYaw", null, 30, false);
|
|
|
|
if(getBehavior(self) <= BEHAVIOR_CALM)
|
|
{
|
|
messageTo(self, "resumeDefaultCalmBehavior", null, 5, false);
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
//--------------- DEATH -----------------------//
|
|
trigger OnIncapacitated(obj_id attacker)
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", ("ai::OnIncapacitated() self(" + self + ":" + getName(self) + ") attacker(" + attacker + ")"));
|
|
|
|
debugSpeakMsgc(aiLoggingEnabled(self), self, "OnIncapacitated");
|
|
|
|
chat.setBadMood(self);
|
|
ai_lib.barkString(self, "death");
|
|
|
|
//pets don't die, unless they should, in which case ai.pet handles it.
|
|
if(!pet_lib.isPet(self) && !beast_lib.isBeast(self) && !utils.hasScriptVar(self, "petBeingInitialized"))
|
|
{
|
|
// Ensure all weapons are destroyed, except for pet weapons, this is to prevent players from looting these items from AI
|
|
|
|
obj_id weapon = getCurrentWeapon(self);
|
|
|
|
if( isIdValid(weapon) && !isDefaultWeapon(weapon) )
|
|
{
|
|
destroyObject(weapon);
|
|
}
|
|
|
|
//clear corpse inventory
|
|
|
|
obj_id cInv = utils.getInventoryContainer(self);
|
|
|
|
if(isIdValid(cInv))
|
|
{
|
|
utils.emptyContainerExceptStorytellerLoot(cInv);
|
|
}
|
|
|
|
ai_lib.clearCombatData();
|
|
|
|
setObjVar(self, xp.VAR_LANDED_DEATHBLOW, attacker);
|
|
|
|
if(!kill(self))
|
|
{
|
|
obj_id[] haters = getHateList(self);
|
|
|
|
if (haters != null || haters.length > 0)
|
|
{
|
|
for(int i=0;i<haters.length;i++)
|
|
{
|
|
removeHateTarget(haters[i], self);
|
|
}
|
|
}
|
|
|
|
destroyObject(self);
|
|
}
|
|
|
|
clearCondition(self, CONDITION_INTERESTING);
|
|
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
|
|
trigger OnDeath(obj_id killer, obj_id corpseId)
|
|
{
|
|
if (isIdValid(killer) && exists(killer) && isIdValid(self) && exists(self))
|
|
{
|
|
removeHateTarget(killer, self);
|
|
}
|
|
|
|
obj_id player = utils.getObjIdScriptVar(self, "ai.listeningTo");
|
|
if(!isValidId(player) || !exists(player))
|
|
return SCRIPT_CONTINUE;
|
|
|
|
//This is for GCW entertainer quests and NPC cleanup
|
|
if(hasObjVar(self, gcw.GCW_ENTERTAINMENT_FLAG) && utils.hasScriptVar(self, gcw.GCW_SCRIPTVAR_PARENT + ".isGcwEntertained"))
|
|
{
|
|
LOG("gcw_entertainer","NPC is saving time player stopped entertaining");
|
|
utils.setScriptVar(self, gcw.GCW_STOPPED_ENTERTAINING, getGameTime());
|
|
|
|
//Close the count down UI if possible.
|
|
int pid = sui.getPid(player, gcw.ENTERTAIN_GCW_TROOPS_PID);
|
|
if(pid > 0)
|
|
{
|
|
forceCloseSUIPage(pid);
|
|
}
|
|
|
|
if(hasObjVar(self, gcw.GCW_PATROL_OBJ))
|
|
{
|
|
obj_id patrolObject = getObjIdObjVar(self, gcw.GCW_PATROL_OBJ);
|
|
if(isValidId(patrolObject))
|
|
{
|
|
dictionary webster = new dictionary();
|
|
webster.put("npc",self);
|
|
webster.put("player",player);
|
|
messageTo(patrolObject, "handleOffensiveEntertainedNpcCleanUp", webster, 1, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
|
|
messageHandler xpDelegated()
|
|
{
|
|
obj_id cInv = utils.getInventoryContainer(self);
|
|
|
|
if(isIdValid(cInv))
|
|
{
|
|
setOwner(cInv, obj_id.NULL_ID);
|
|
}
|
|
|
|
//attach corpse script
|
|
if(!beast_lib.isBeast(self))
|
|
{
|
|
attachScript(self, corpse.SCRIPT_AI_CORPSE);
|
|
|
|
//message that corpse is prepared
|
|
messageTo(self, "aiCorpsePrepared", null, 0, false);
|
|
}
|
|
else
|
|
{
|
|
xp.cleanupCreditForKills();
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler aiCorpsePrepared()
|
|
{
|
|
// Try to drop a TCG Promotion Card
|
|
obj_id [] killers = getObjIdArrayObjVar(self, scenario.VAR_PRIMARY_KILLERS);
|
|
|
|
obj_id [] pks = null;
|
|
|
|
if(killers != null && killers.length > 0)
|
|
{
|
|
pks = xp.getPlayersFromKillerList(killers);
|
|
}
|
|
|
|
if(pks == null || pks.length <= 0)
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
//spawn loot (if any)
|
|
boolean hasLoot = loot.addLoot(self);
|
|
|
|
// *********************************************
|
|
// checking whether to drop chronicles loot
|
|
// if the player has set chronicles loot drop off, we do not bother to attempt to create any
|
|
// if one member of a group has their chronicles loot drop on, then we attempt to drop chronicles loot
|
|
boolean hasChanceToDropChroniclesLoot = false;
|
|
if ( hasObjVar(self, xp.VAR_TOP_GROUP) )
|
|
{
|
|
obj_id killCredit = getObjIdObjVar(self, xp.VAR_TOP_GROUP);
|
|
if ( group.isGroupObject(killCredit) )
|
|
{
|
|
obj_id[] killList = getGroupMemberIds(killCredit);
|
|
for ( int k = 0; k < killList.length; k++ )
|
|
{
|
|
if ( !loot.hasToggledChroniclesLootOff(killList[k]) )
|
|
{
|
|
hasChanceToDropChroniclesLoot = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( !loot.hasToggledChroniclesLootOff(killCredit) )
|
|
{
|
|
hasChanceToDropChroniclesLoot = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( hasChanceToDropChroniclesLoot )
|
|
{
|
|
hasLoot |= loot.addChronicleLoot(self);
|
|
}
|
|
// *********************************************
|
|
|
|
|
|
int corpseLevel = getLevel(self);
|
|
boolean doNotDropCard = false;
|
|
int difficultyClass = getIntObjVar(self, "difficultyClass");
|
|
int sourceSystem = scheduled_drop.SYSTEM_COMBAT_NORMAL;
|
|
|
|
switch(difficultyClass)
|
|
{
|
|
|
|
case 0:
|
|
sourceSystem = scheduled_drop.SYSTEM_COMBAT_NORMAL;
|
|
break;
|
|
case 1:
|
|
sourceSystem = scheduled_drop.SYSTEM_COMBAT_ELITE;
|
|
break;
|
|
case 2:
|
|
sourceSystem = scheduled_drop.SYSTEM_COMBAT_BOSS;
|
|
break;
|
|
}
|
|
|
|
|
|
int delayCount = 0;
|
|
|
|
// Check the levels of the killers to make sure it is in a valid range for dropping a card.
|
|
// Make sure free trial players do not get cards, too.
|
|
// Also, count the number of killers that have a delay on the next card to drop.
|
|
|
|
for(int i = 0, j = pks.length; i < j; i++)
|
|
{
|
|
if(isIdValid(pks[i]) && (corpseLevel + 5 < getLevel(pks[i]) || utils.isFreeTrial(pks[i])))
|
|
{
|
|
doNotDropCard = true;
|
|
}
|
|
|
|
|
|
if(isIdValid(pks[i]) && scheduled_drop.hasCardDelay(pks[i], sourceSystem))
|
|
{
|
|
delayCount++;
|
|
}
|
|
}
|
|
|
|
// If half or more killers of this creature have a delay on them, then do not drop a card for the group of killers.
|
|
if(delayCount > pks.length / 2)
|
|
{
|
|
doNotDropCard = true;
|
|
}
|
|
|
|
boolean canDrop = scheduled_drop.canDropCard(sourceSystem);
|
|
// Set the delay on the killers for the next card to drop, since they all had a chance for this card drop.
|
|
for(int i = 0, j = pks.length; i < j; i++)
|
|
{
|
|
if(isIdValid(pks[i]))
|
|
{
|
|
utils.setScriptVar(pks[i], scheduled_drop.PLAYER_SCRIPTVAR_DROP_TIME, getGameTime());
|
|
|
|
//if you are in god mode and have the override objvar
|
|
//and doNotDropCard is not false make sure you get it no matter what
|
|
if(isGod(pks[i]) && hasObjVar(pks[i], "qa_tcg_always_drop"))
|
|
{
|
|
if(!doNotDropCard)
|
|
{
|
|
canDrop = true;
|
|
}
|
|
}
|
|
|
|
if(isGod(pks[i]) && hasObjVar(pks[i], "qa_tcg"))
|
|
{
|
|
sendSystemMessageTestingOnly(pks[i], "QA TCG COMBAT. Do not drop card? " + doNotDropCard + " hasCardDelay? " + scheduled_drop.hasCardDelay(pks[i], sourceSystem) + " isTrial? " + utils.isFreeTrial(pks[i]) + " bad level? " + (corpseLevel + 5 < getLevel(pks[i])) );
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!doNotDropCard)
|
|
{
|
|
obj_id inv = utils.getInventoryContainer(self);
|
|
|
|
if(isIdValid(inv) && canDrop)
|
|
{
|
|
scheduled_drop.dropCard(sourceSystem, inv);
|
|
hasLoot = true;
|
|
}
|
|
}
|
|
|
|
// END TCG Promotion code
|
|
|
|
if(hasLoot)
|
|
{
|
|
corpse.showLootMeParticle(self);
|
|
}
|
|
|
|
setObjVar(self, "readyToLoot", true);
|
|
|
|
xp.cleanupCreditForKills();
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler corpseCleanup()
|
|
{
|
|
destroyObject(self);
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnStartNpcConversation (obj_id speaker)
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", ("ai::OnStartNpcConversation() self(" + self + ":" + getName(self) + ") speaker(" + speaker + ":" + getName(speaker) + ")"));
|
|
|
|
if(!isInNpcConversation(self))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
if(ai_lib.isAiDead(self))
|
|
{
|
|
return SCRIPT_OVERRIDE;
|
|
}
|
|
|
|
if(ai_lib.isInCombat(self))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
if(ai_lib.isInCombat(speaker))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
ai_lib.setMood(self, ai_lib.MOOD_CALM);
|
|
stop(self);
|
|
faceTo(self, speaker);
|
|
|
|
//debugSpeakMsg(self, "SETTING AI CONVERSING TO FALSE");
|
|
messageTo(self, "handleAbortConversation", null, 30, false);
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler handleAbortConversation()
|
|
{
|
|
if(!isInNpcConversation(self))
|
|
{
|
|
if(getBehavior(self) <= BEHAVIOR_CALM)
|
|
{
|
|
messageTo(self, "resumeDefaultCalmBehavior", null, 5, false);
|
|
}
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnEndNpcConversation(obj_id speaker)
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", ("ai::OnEndNpcConversation() self(" + self + ":" + getName(self) + ") speaker(" + speaker + ":" + getName(speaker) + ")"));
|
|
|
|
if(ai_lib.isAiDead(self))
|
|
{
|
|
return SCRIPT_OVERRIDE;
|
|
}
|
|
|
|
if(getBehavior(self) <= BEHAVIOR_CALM)
|
|
{
|
|
messageTo(self, "resumeDefaultCalmBehavior", null, 5, false);
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler disableAI()
|
|
{
|
|
string[] scriptList = getScriptList(self);
|
|
|
|
for (int i = 0; i < scriptList.length; i++)
|
|
{
|
|
if(scriptList[i].startsWith("ai."))
|
|
{
|
|
detachScript(self, scriptList[i]);
|
|
}
|
|
}
|
|
|
|
stop(self);
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
|
|
messageHandler handleClearPathingFlag()
|
|
{
|
|
removeObjVar(self, "ai.pathingAwayFrom");
|
|
|
|
if(getBehavior(self) <= BEHAVIOR_CALM)
|
|
{
|
|
messageTo(self, "resumeDefaultCalmBehavior", null, 5, false);
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler handleAnimationAction()
|
|
{
|
|
//debugSpeakMsg(self, "doing thing");
|
|
string anim = params.getString("anim");
|
|
|
|
if(anim != null)
|
|
{
|
|
//debugSpeakMsg(self, "doing action");
|
|
ai_lib.doAction(self, anim);
|
|
}
|
|
else
|
|
{
|
|
//debugSpeakMsg(self, "doing animMood");
|
|
string animMood = params.getString("animMood");
|
|
|
|
if(animMood != null)
|
|
{
|
|
ai_lib.setMood(self, animMood);
|
|
}
|
|
}
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
// PET STUFF (Beasts do not use handleAddMaster() or handleRemoveMaster())
|
|
messageHandler handleAddMaster()
|
|
{
|
|
obj_id pet = params.getObjId("pet");
|
|
obj_id controlDevice = params.getObjId("controlDevice");
|
|
|
|
if(!isIdValid(controlDevice))
|
|
{
|
|
controlDevice = callable.getCallableCD(pet);
|
|
}
|
|
|
|
if(!isIdValid(controlDevice))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
if(!isIdValid(pet))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
if((pet != self))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
obj_id master = params.getObjId("master");
|
|
|
|
if(!isIdValid(master))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
int petType = pet_lib.getPetType(self);
|
|
|
|
if(pet_lib.hasMaxPets(master, petType, pet) && callable.hasCallableCD(self))
|
|
{
|
|
destroyObject(self); //this thing was created and now it can't be made a pet, so get rid of it
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
if(!hasScript(self, "ai.pet"))
|
|
{
|
|
setMaster(self, master);
|
|
setOwner(utils.getInventoryContainer(self), master);
|
|
attachScript(self, "ai.pet");
|
|
setObjVar(pet, "ai.pet.masterName", getEncodedName(master));
|
|
|
|
if(utils.hasScriptVar(self, "petBeingInitialized"))
|
|
{
|
|
utils.removeScriptVar(self, "petBeingInitialized");
|
|
}
|
|
}
|
|
|
|
if(!ai_lib.isMonster(self))
|
|
{
|
|
pet_lib.setupDefaultCommands(self);
|
|
}
|
|
|
|
pet_lib.addToPetList(master, self);
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler handleRemoveMaster()
|
|
{
|
|
obj_id pet = params.getObjId("pet");
|
|
|
|
if(pet != self || !isIdValid(pet))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
if(!pet_lib.hasMaster(self))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
obj_id master = getMaster(self);
|
|
params.put("master", master);
|
|
|
|
const String creatureName = getCreatureName(self);
|
|
|
|
if(creatureName != null)
|
|
{
|
|
params.put("ai.creatureName", creatureName);
|
|
}
|
|
|
|
obj_id group = getGroupObject(pet);
|
|
queueCommand(pet, ##"disband", group, "", COMMAND_PRIORITY_DEFAULT);
|
|
|
|
removeObjVar(pet, "ai.pet.masterName");
|
|
|
|
ai_lib.aiStopFollowing(pet);
|
|
stop(pet);
|
|
|
|
setMaster(pet, null);
|
|
setOwner(utils.getInventoryContainer(self), obj_id.NULL_ID);
|
|
|
|
removeTriggerVolume(ai_lib.ALERT_VOLUME_NAME);
|
|
|
|
removeObjVar(pet, "ai.pet.friendList");
|
|
removeObjVar(pet, "ai.pet.guarding");
|
|
removeObjVar(pet, "ai.inFormation");
|
|
|
|
pet_lib.removeFromPetList(pet);
|
|
|
|
if(hasScript(pet, "ai.pet"))
|
|
{
|
|
detachScript(pet, "ai.pet");
|
|
}
|
|
|
|
messageTo(pet, "destroyAbandonedPet", null, 300, true);
|
|
|
|
ai_lib.setDefaultCalmBehavior(pet, ai_lib.BEHAVIOR_LOITER);
|
|
|
|
if(ai_lib.isMonster(pet))
|
|
{
|
|
messageTo(pet, "handleWailingPet", null, 5, false);
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler handleWailingPet()
|
|
{
|
|
if(pet_lib.hasMaster(self) || ai_lib.isAiDead(self))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
switch(rand(1,3))
|
|
{
|
|
case 1 :
|
|
stop(self);
|
|
ai_lib.doAction(self, "ashamed");
|
|
break;
|
|
case 2 :
|
|
stop(self);
|
|
ai_lib.doAction(self, "vocalize");
|
|
break;
|
|
case 3 :
|
|
if(getBehavior(self) <= BEHAVIOR_CALM)
|
|
{
|
|
doDefaultCalmBehavior(self);
|
|
}
|
|
break;
|
|
}
|
|
|
|
messageTo(self, "handleWailingPet", null, 15, false);
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler destroyAbandonedPet()
|
|
{
|
|
if(!pet_lib.hasMaster(self))
|
|
{
|
|
destroyObject(self);
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
boolean crossPortalBreach(obj_id npc, obj_id breacher)
|
|
{
|
|
obj_id myCell = getLocation(npc).cell;
|
|
obj_id yourCell = getLocation(breacher).cell;
|
|
|
|
if(isIdValid(myCell) == isIdValid(yourCell))
|
|
{
|
|
if(!canSee(npc,breacher))
|
|
{
|
|
return true;//treat as a cross portal breach, since I can't see you
|
|
}
|
|
else
|
|
{
|
|
return false;//we're either both inside or both outside
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//the person is very close, but still is outside, while I am inside:
|
|
void manageCrossPortalBreach(obj_id npc, obj_id breacher)
|
|
{
|
|
if(!isPlayer(breacher))
|
|
{
|
|
return;
|
|
}
|
|
|
|
dictionary params = new dictionary();
|
|
params.put("breacher", breacher);
|
|
messageTo(npc, "handleMonitorMovement", params, 5, false);
|
|
return;
|
|
}
|
|
|
|
messageHandler handleMonitorMovement()
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::handleMonitorMovement() self(" + self + ")");
|
|
|
|
if(ai_lib.isAiDead(self))
|
|
{
|
|
return SCRIPT_CONTINUE;//I'm busy
|
|
}
|
|
|
|
obj_id breacher = params.getObjId("breacher");
|
|
|
|
if(!isIdValid(breacher) || !exists(breacher) || !isInWorld(breacher))
|
|
{
|
|
return SCRIPT_CONTINUE;//you're gone
|
|
}
|
|
|
|
if(ai_lib.isAiDead(breacher))
|
|
{
|
|
return SCRIPT_CONTINUE;//you're dead
|
|
}
|
|
|
|
obj_id[] myContents = getTriggerVolumeContents(self, ai_lib.ALERT_VOLUME_NAME);
|
|
|
|
if(utils.getElementPositionInArray(myContents, breacher) == -1)
|
|
{
|
|
return SCRIPT_CONTINUE;//you left
|
|
}
|
|
|
|
obj_id myCell = getLocation(self).cell;
|
|
obj_id yourCell = getLocation(breacher).cell;
|
|
|
|
if(!isIdValid(yourCell))
|
|
{
|
|
return SCRIPT_CONTINUE;//you're still outside
|
|
}
|
|
|
|
if(yourCell != myCell)
|
|
{
|
|
messageTo(self, "handleMonitorMovement", params, 3, false);//you're still not here, check again later
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
if(ai_lib.isInCombat(self))
|
|
{
|
|
messageTo(self, "handleMonitorMovement", params, 3, false);//I'm busy, so check later
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
startCombat(self, breacher);
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnAboutToBeTransferred(obj_id destContainer, obj_id transferer)
|
|
{
|
|
if(pet_lib.isPet(self))
|
|
{
|
|
return SCRIPT_CONTINUE;//let pets do whatever
|
|
}
|
|
|
|
if(beast_lib.isBeast(self))
|
|
{
|
|
//return SCRIPT_OVERRIDE;
|
|
return SCRIPT_CONTINUE;//let beasts do whatever
|
|
}
|
|
|
|
obj_id bldg = getTopMostContainer(destContainer);
|
|
|
|
if(isPlayer(bldg))
|
|
{
|
|
return SCRIPT_CONTINUE;//this is a creature being xfer'd to a player's inventory
|
|
}
|
|
|
|
if(!isIdValid(destContainer))
|
|
{
|
|
return SCRIPT_CONTINUE;//transferring to the world
|
|
}
|
|
|
|
boolean wontEnter = false;
|
|
int niche = ai_lib.aiGetNiche(self);
|
|
|
|
if(niche == NICHE_VEHICLE)
|
|
{
|
|
wontEnter = true;//vehicles won't enter bldgs
|
|
}
|
|
|
|
if(!permissionsIsAllowed(destContainer, self))
|
|
{
|
|
wontEnter = true;//and nothing will enter a building if it cannot
|
|
}
|
|
|
|
if(wontEnter)
|
|
{
|
|
if(ai_lib.isInCombat(self))
|
|
{
|
|
removeObjVar(self, "ai.combat.moveMode");
|
|
stop(self);
|
|
}
|
|
else
|
|
{
|
|
stop(self);
|
|
pathTo(self, aiGetHomeLocation(self));
|
|
messageTo(self, "resumeDefaultCalmBehavior", null, 20, false);
|
|
}
|
|
|
|
return SCRIPT_OVERRIDE;
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
boolean fightNotFlee(obj_id npc, obj_id threat)
|
|
{
|
|
LOGC(aiLoggingEnabled(npc), "debug_ai", "ai::fightNotFlee() npc(" + npc + ") threat(" + threat + ")");
|
|
|
|
if(hasObjVar(threat, "gm"))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(!ai_lib.isInCombat(npc))
|
|
{
|
|
if(factions.isAggro(npc) && factions.getFactionReaction(npc, threat)==factions.REACTION_NEGATIVE)
|
|
{
|
|
//debugSpeakMsg(npc, "you have low faction standing with me");
|
|
//debugSpeakMsg(npc, "attack 31");
|
|
startCombat(npc, threat);
|
|
|
|
return true;
|
|
}
|
|
|
|
if(pvpIsEnemy(npc, threat) && pvpCanAttack(npc, threat))
|
|
{
|
|
//debugSpeakMsg(npc, "you are a pvp enemy");
|
|
//debugSpeakMsg(npc, "attack 32");
|
|
startCombat(npc, threat);
|
|
|
|
return true;
|
|
}
|
|
|
|
if(ai_lib.isMonster(npc) && ai_lib.isMonster(threat) && ai_lib.isAggro(npc))
|
|
{
|
|
//debugSpeakMsg(npc, "attack 33");
|
|
startCombat(npc, threat);
|
|
|
|
return true;
|
|
}
|
|
|
|
//carnivores and predators attack herbivores:
|
|
if((ai_lib.aiGetNiche(npc) == NICHE_CARNIVORE || ai_lib.aiGetNiche(npc) == NICHE_PREDATOR) && ai_lib.aiGetNiche(threat) == NICHE_HERBIVORE)
|
|
{
|
|
//debugSpeakMsg(npc, "attack 34");
|
|
startCombat(npc, threat);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
messageHandler handleEntertainmentStart()
|
|
{
|
|
obj_id player = params.getObjId("entertainer");
|
|
|
|
if(!isIdValid(player))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
if(ai_lib.isInCombat(self) || ai_lib.aiIsDead(self))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
if(isInNpcConversation(self))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
if(!ai_lib.isInSameBuilding(self,player))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
if(hasScript (self, "ai.soldier") && !hasObjVar(self, gcw.GCW_ENTERTAINMENT_FLAG))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
if (instance.isInInstanceArea(self))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
if(hasObjVar(self, performance.NPC_ENTERTAINMENT_NO_ENTERTAIN))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
int niche = ai_lib.aiGetNiche(self);
|
|
|
|
if(niche != NICHE_NPC)
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
// Poor Darth doesn't have time to appreciate music or dance.
|
|
string npcType = getCreatureName(self);
|
|
|
|
if(npcType.equals("darth_vader"))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
//If this is a GCW Troop that can be entertained, lets make sure the player can entertain them
|
|
if(hasObjVar(self, gcw.GCW_ENTERTAINMENT_FLAG) && !gcw.canEntertainGcwNonPlayingCharacter(player, self))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
if(hasObjVar(self, gcw.GCW_ENTERTAINMENT_FLAG) && utils.hasScriptVar(player, gcw.GCW_SCRIPTVAR_PARENT + ".gcwNpc"))
|
|
{
|
|
//already entertaining some other NPC
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
|
|
string entertainmentType = params.getString("perf_type");
|
|
string skillName = "healing_music_ability";
|
|
|
|
if(entertainmentType != null)
|
|
{
|
|
if(entertainmentType.equals(performance.PERFORMANCE_TYPE_DANCE))
|
|
{
|
|
skillName = "healing_dance_ability";
|
|
}
|
|
else if(entertainmentType.equals(performance.PERFORMANCE_TYPE_JUGGLE))
|
|
{
|
|
skillName = "healing_juggle_ability";
|
|
}
|
|
}
|
|
|
|
int skillMod = rand(1,100);
|
|
|
|
if(entertainmentType != null)
|
|
{
|
|
skillMod = getSkillStatisticModifier(player, skillName);
|
|
}
|
|
|
|
if(skillMod == 0)
|
|
{
|
|
skillMod = rand(1,100); //must be a bogus entertainmentType
|
|
}
|
|
|
|
if(utils.hasScriptVar(self, "ai.listeningTo"))
|
|
{
|
|
//I'm already listening to someone...
|
|
int oldSkillMod = utils.getIntScriptVar(self, "ai.oldEntertainerSkillMod");
|
|
|
|
if(skillMod < oldSkillMod)
|
|
{
|
|
return SCRIPT_CONTINUE;//...and he's better than you
|
|
}
|
|
}
|
|
|
|
//If the NPC is a valid 'GCW Rally quest NPC' and the player is on the quest
|
|
if(hasObjVar(self, "faction_recruit_quest"))
|
|
{
|
|
|
|
if(!groundquests.isQuestActive(player, gcw.GCW_ENTERTAIN_RALLY))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
if((factions.isImperialorImperialHelper(player) && factions.isImperialorImperialHelper(self) && !factions.isOnLeave(player))
|
|
|| (factions.isRebelorRebelHelper(player) && factions.isRebelorRebelHelper(self) && !factions.isOnLeave(player)))
|
|
{
|
|
utils.setScriptVar(self, "faction_ent",player);
|
|
//Send readyToTalk message with a delay. If the entertainer is still entertaining
|
|
//when the message arrives they will be able to talk to the NPC
|
|
messageTo(self, "readyToTalk", params, 20, false);
|
|
//Send lostInterest message. If the player isent finished when the message arrives
|
|
//they've lost their chance and the NPC will reset
|
|
params.put("lostInterestTime", getGameTime());
|
|
utils.setScriptVar(self, "lostInterestTime", getGameTime());
|
|
messageTo(self, "lostInterest", params, 60, false );
|
|
}
|
|
else
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
}
|
|
|
|
utils.setScriptVar(self, "ai.oldEntertainerSkillMod", skillMod);
|
|
|
|
if(!hasObjVar(self, "ai.oldYaw"))
|
|
{
|
|
setObjVar(self, "ai.oldYaw", getYaw(self));//mind not to stomp my original Yaw
|
|
}
|
|
|
|
utils.setScriptVar(self, "ai.listeningTo", player);
|
|
faceToBehavior(self, player);
|
|
|
|
|
|
|
|
if(skillMod < 20)
|
|
{
|
|
if(rand(1,2)==1)
|
|
{
|
|
setAnimationMood(self, "npc_sad");
|
|
}
|
|
else
|
|
{
|
|
setAnimationMood(self, "bored");
|
|
}
|
|
}
|
|
|
|
if(skillMod < 50)
|
|
{
|
|
if(rand(1,2)==1)
|
|
{
|
|
setAnimationMood(self, "bored");
|
|
}
|
|
else
|
|
{
|
|
setAnimationMood(self, "entertained");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
setAnimationMood(self, "entertained");
|
|
}
|
|
|
|
listenToMessage(player, "handlePerformerStopPerforming");
|
|
messageTo(self, "handleNoEntertainment", null, 30, false);
|
|
|
|
if(hasObjVar(self, gcw.GCW_ENTERTAINMENT_FLAG))
|
|
{
|
|
gcw.setEntertainGcwNonPlayerCharacter(player, self);
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler handleNoEntertainment()
|
|
{
|
|
boolean stopListening = false;
|
|
|
|
if(!utils.hasScriptVar(self, "ai.listeningTo"))
|
|
{
|
|
return SCRIPT_CONTINUE;//must have quit listening already
|
|
}
|
|
|
|
obj_id player = utils.getObjIdScriptVar(self, "ai.listeningTo");
|
|
|
|
if(!isIdValid(player))
|
|
{
|
|
stopListening = true;
|
|
}
|
|
else if(!exists(player) || !isInWorld(player) || !ai_lib.isInSameBuilding(self,player) || getPerformanceType(player) == 0)
|
|
{
|
|
stopListening = true;
|
|
}
|
|
|
|
if(stopListening)
|
|
{
|
|
//well, you left:
|
|
utils.removeScriptVar(self, "ai.oldEntertainerSkillMod");
|
|
utils.removeScriptVar(self, "ai.listeningTo");//stop listening to this guy
|
|
stop(self);//cancel that faceToBehavior
|
|
messageTo(self, "resumeDefaultCalmBehavior", null, 0, false);//this will setYaw back to the original pos.
|
|
}
|
|
else
|
|
{
|
|
//check again in 30:
|
|
messageTo(self, "handleNoEntertainment", null, 30, false);
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;//dunno where you went, so...
|
|
}
|
|
|
|
messageHandler handlePerformerStopPerforming()
|
|
{
|
|
// This forces the "handleEntertainmentStop" to run immediately without using messageTo()
|
|
utils.notifyObject(self, "handleEntertainmentStop", params);
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler handleEntertainmentStop()
|
|
{
|
|
|
|
obj_id player = obj_id.NULL_ID;
|
|
if(params.containsKey("entertainer"))
|
|
{
|
|
player = params.getObjId("entertainer");
|
|
}
|
|
else if(params.containsKey("performer"))
|
|
{
|
|
player = params.getObjId("performer");
|
|
}
|
|
|
|
if(!isIdValid(player))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
obj_id entertainer = utils.getObjIdScriptVar(self, "ai.listeningTo");
|
|
|
|
if(entertainer != player)
|
|
{
|
|
return SCRIPT_CONTINUE;//not listening to this guy
|
|
}
|
|
|
|
if(ai_lib.aiIsDead(self) || ai_lib.isInCombat(self))
|
|
{
|
|
return SCRIPT_CONTINUE;//something bad happened to me
|
|
}
|
|
|
|
setAnimationMood(self, "calm");
|
|
|
|
int skillMod = utils.getIntScriptVar(self, "ai.oldEntertainerSkillMod");
|
|
|
|
if(skillMod == 0)
|
|
{
|
|
skillMod = rand(1,100);//must be a bogus entertainmentType
|
|
}
|
|
if(skillMod < 20)
|
|
{
|
|
switch (rand(1,4))
|
|
{
|
|
case 1 :
|
|
doAnimationAction(self, "hold_nose");
|
|
break;
|
|
case 2 :
|
|
doAnimationAction(self, "cover_ears_mocking");
|
|
break;
|
|
case 3 :
|
|
doAnimationAction(self, "point_away");
|
|
break;
|
|
case 4 :
|
|
doAnimationAction(self, "shake_head_no");
|
|
break;
|
|
}
|
|
}
|
|
else if(skillMod < 40)
|
|
{
|
|
switch (rand(1,4))
|
|
{
|
|
case 1 :
|
|
doAnimationAction(self, "shake_head_no");
|
|
break;
|
|
case 2 :
|
|
doAnimationAction(self, "applause_polite");
|
|
break;
|
|
case 3 :
|
|
doAnimationAction(self, "yes");
|
|
break;
|
|
case 4 :
|
|
doAnimationAction(self, "snap_finger1");
|
|
break;
|
|
}
|
|
}
|
|
else if(skillMod < 60)
|
|
{
|
|
switch(rand(1,4))
|
|
{
|
|
case 1 :
|
|
doAnimationAction(self, "snap_finger1");
|
|
break;
|
|
case 2 :
|
|
doAnimationAction(self, "applause_polite");
|
|
break;
|
|
case 3 :
|
|
doAnimationAction(self, "yes");
|
|
break;
|
|
case 4 :
|
|
doAnimationAction(self, "applause_excited");
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (rand(1,3))
|
|
{
|
|
case 1 :
|
|
doAnimationAction(self, "applause_excited");
|
|
break;
|
|
case 2 :
|
|
doAnimationAction(self, "clap_rousing");
|
|
break;
|
|
case 3 :
|
|
doAnimationAction(self, "implore");
|
|
break;
|
|
case 4 :
|
|
doAnimationAction(self, "applause_polite");
|
|
break;
|
|
}
|
|
}
|
|
|
|
utils.removeScriptVar(self, "ai.oldEntertainerSkillMod");
|
|
utils.removeScriptVar(self, "ai.listeningTo");//stop listening to this guy
|
|
stop(self);//cancel that faceToBehavior
|
|
messageTo(self, "resumeDefaultCalmBehavior", null, 5, false);//this will setYaw back to the original pos.
|
|
// and get them back into the conversing mood
|
|
// or whatever it was they were doing
|
|
|
|
//This is for GCW entertainer quests and NPC cleanup
|
|
if(hasObjVar(self, gcw.GCW_ENTERTAINMENT_FLAG) && utils.hasScriptVar(self, gcw.GCW_SCRIPTVAR_PARENT + ".isGcwEntertained"))
|
|
{
|
|
LOG("gcw_entertainer","NPC is saving time player stopped entertaining");
|
|
utils.setScriptVar(self, gcw.GCW_STOPPED_ENTERTAINING, getGameTime());
|
|
if(hasObjVar(self, gcw.GCW_PATROL_OBJ))
|
|
{
|
|
obj_id patrolObject = getObjIdObjVar(self, gcw.GCW_PATROL_OBJ);
|
|
if(isValidId(patrolObject))
|
|
{
|
|
dictionary webster = new dictionary();
|
|
webster.put("npc",self);
|
|
webster.put("player",player);
|
|
messageTo(patrolObject, "handleOffensiveEntertainedNpcCleanUp", webster, 1, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnEnteredCombat()
|
|
{
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnChangedPosture(int before, int after)
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
messageHandler dizzyCheckFailed()
|
|
{
|
|
ai_lib.aiSetPosture(self, POSTURE_KNOCKED_DOWN);
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler applyModTrap()
|
|
{
|
|
if(params == null)
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
if(utils.hasScriptVar(self, "trapmod.enable"))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
java.util.Enumeration keys = params.keys();
|
|
|
|
while(keys.hasMoreElements())
|
|
{
|
|
string key = (string)keys.nextElement();
|
|
int mod = params.getInt(key);
|
|
|
|
if(!key.equals("prefix"))
|
|
{
|
|
utils.setScriptVar(self, "trapmod."+key, mod);
|
|
}
|
|
}
|
|
|
|
utils.setScriptVar(self, "trapmod.enable", 1);
|
|
|
|
messageTo(self, "removeModTrap", params, 60.f, false);
|
|
|
|
string prefix = params.getString("prefix");
|
|
showFlyText(self, new string_id("trap/trap", prefix+"_on"), 1.2f, colors.ORANGERED);
|
|
|
|
obj_id[] players = getPlayerCreaturesInRange(self, 50.f);
|
|
|
|
for (int i=0; i<players.length; i++)
|
|
{
|
|
playClientEffectLoc(players[i], "clienteffect/combat_trap_"+prefix+".cef", getLocation(self), 0f);
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler removeModTrap()
|
|
{
|
|
utils.removeScriptVar(self, "trapmod");
|
|
string prefix = params.getString("prefix");
|
|
|
|
showFlyText(self, new string_id("trap/trap", prefix+"_off"), 1.2f, colors.ORANGERED);
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
//temp pet clean-up:
|
|
trigger OnObjectMenuRequest(obj_id player, menu_info mi)
|
|
{
|
|
if(pet_lib.isPet(self) || beast_lib.isBeast(self))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
location here = getLocation(self);
|
|
|
|
if(here == null)
|
|
{
|
|
LOG("designer_fatal", "ai.ai::OnObjectMenuRequest() -> getLocation(self) == null. WTF?!");
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
if(isIdValid(here.cell))
|
|
{
|
|
return SCRIPT_CONTINUE;//I'm in a building
|
|
}
|
|
|
|
obj_id myContainer = getContainedBy(self);
|
|
|
|
if(!isIdValid(myContainer))
|
|
{
|
|
return SCRIPT_CONTINUE;//Not contained by anything
|
|
}
|
|
|
|
//if I am not a pet, not in a building, but I am in Something, then
|
|
// I must be a hosed pet in someone's inventory.
|
|
// Goodbye!
|
|
destroyObject(self);
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnObjectMenuSelect(obj_id player, int item)
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnDestroy()
|
|
{
|
|
obj_id group = getGroupObject(self);
|
|
|
|
if(isIdValid(group))
|
|
{
|
|
queueCommand(self, ##"disband", group, "", COMMAND_PRIORITY_DEFAULT);
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler forceAnimalAttack()
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::forceAnimalAttack() self(" + self + ")");
|
|
|
|
if(ai_lib.aiIsDead(self))
|
|
return SCRIPT_CONTINUE;
|
|
|
|
obj_id target = params.getObjId("target");
|
|
if(!isIdValid(target))
|
|
return SCRIPT_CONTINUE;
|
|
|
|
if(!pvpCanAttack(self, target))
|
|
return SCRIPT_CONTINUE;
|
|
|
|
utils.setScriptVar(self, "ai.combat.isTaunted", (getGameTime() + 20));
|
|
|
|
if(ai_lib.isInCombat(self))
|
|
{
|
|
removeObjVar(self, "ai.combat.moveMode");
|
|
stop(self);
|
|
setLookAtTarget(self, target);
|
|
}
|
|
else
|
|
{
|
|
//debugSpeakMsg(self, "attack 7");
|
|
startCombat(self, target);
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler attemptMilkHandler()
|
|
{
|
|
if(params == null)
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
obj_id player = params.getObjId("player");
|
|
|
|
attemptMilk(player, self);
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
void attemptMilk(obj_id player, obj_id self)
|
|
{
|
|
// A player wants to milk us.
|
|
obj_id playerCurrentMount = getMountId (player);
|
|
obj_id pInv = utils.getInventoryContainer(player);
|
|
if(!isValidId(pInv) || !exists(pInv))
|
|
return;
|
|
if(getVolumeFree(pInv) <= 0)
|
|
{
|
|
sendSystemMessage(self, SID_CANT_MILK_INV_FULL);
|
|
return;
|
|
}
|
|
// Can't milk while you are mounted.
|
|
if(isIdValid(playerCurrentMount))
|
|
{
|
|
sendSystemMessage(player, SID_CANT_MILK_MOUNTED);
|
|
return;
|
|
}
|
|
// Are we being milked?
|
|
if(utils.hasScriptVar(self, "milk.attempts"))
|
|
{
|
|
sendSystemMessage(player, SID_BEING_MILKED);
|
|
return;
|
|
}
|
|
// Are we a pet?
|
|
if(pet_lib.isPet(self) || beast_lib.isBeast(self))
|
|
{
|
|
showFlyText(self, new string_id("npc_reaction/flytext", "threaten"), 2.0f, colors.ORANGERED);
|
|
sendSystemMessage(player, SID_CANT_MILK);
|
|
return;
|
|
}
|
|
// Is he scent masked?
|
|
if(stealth.hasInvisibleBuff(player))
|
|
{
|
|
showFlyText(self, new string_id("npc_reaction/flytext", "threaten"), 2.0f, colors.ORANGERED);
|
|
sendSystemMessage(player, SID_MILK_NOT_HIDDEN);
|
|
return;
|
|
}
|
|
// When were we last milked?
|
|
if(utils.hasScriptVar(self, "milk.lasttime"))
|
|
{
|
|
int milktime = utils.getIntScriptVar(self, "milk.lasttime");
|
|
|
|
if(getGameTime() - milktime < 3600)
|
|
{
|
|
showFlyText(self, new string_id("npc_reaction/flytext", "threaten"), 2.0f, colors.ORANGERED);
|
|
sendSystemMessage(player, SID_CANT_MILK);
|
|
return;
|
|
}
|
|
}
|
|
|
|
float milkStunModified = 0.0f;
|
|
|
|
//creature_milking_buff is an ability given to players
|
|
//that complete the exceptional milk collection
|
|
if(buff.hasBuff(player, "creature_milking_buff") || buff.hasBuff(player, "drink_starshine_surprise"))
|
|
{
|
|
milkStunModified += ((float)getEnhancedSkillStatisticModifierUncapped(player, "milk_stun_modified"));
|
|
CustomerServiceLog("milking_and_lair_search", "handleMilking: Player: "+getName(player)+" OID: "+player+" has a buff that gives a stun chance of: "+milkStunModified+".");
|
|
}
|
|
|
|
if(milkStunModified > 0)
|
|
{
|
|
int randStunRoll = rand(1, 100);
|
|
if(randStunRoll < milkStunModified)
|
|
{
|
|
CustomerServiceLog("milking_and_lair_search", "handleMilking: Player: "+getName(player)+" OID: "+player+" has successfully stunned creature "+self+". The creature will stand still for a few seconds.");
|
|
|
|
messageTo(self, "milkingCreatureStunned", null, 0, false);
|
|
messageTo(self, "milkingCreatureStunRemoval", null, 4, false);
|
|
}
|
|
else
|
|
{
|
|
messageTo(self, "milkingCreatureStunFail", null, 0, false);
|
|
CustomerServiceLog("milking_and_lair_search", "handleMilking: Player: "+getName(player)+" OID: "+player+" failed to stun the creature.");
|
|
}
|
|
}
|
|
|
|
// The player wants to milk us!
|
|
showFlyText(self, new string_id("npc_reaction/flytext", "alert"), 2.0f, colors.MEDIUMSLATEBLUE);
|
|
|
|
sendSystemMessage(player, SID_MILK_BEGIN);
|
|
|
|
utils.setScriptVar(self, "milk.attempts", 0);
|
|
dictionary params = new dictionary();
|
|
params.put("player", player);
|
|
messageTo(self, "handleMilking", params, 7, false);
|
|
}
|
|
|
|
messageHandler handleMilking()
|
|
{
|
|
obj_id player = params.getObjId("player");
|
|
|
|
//stop milking if the player killed the creature during the milking process.
|
|
if(isDead(self))
|
|
{
|
|
sendSystemMessage(player, SID_CANT_MILK_THE_DEAD);
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
if(params == null)
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
obj_id playerCurrentMount = getMountId (player);
|
|
|
|
if(isIdValid(playerCurrentMount))
|
|
{
|
|
sendSystemMessage(player, SID_CANT_MILK_MOUNTED);
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
float dist = getDistance(self, player);
|
|
|
|
if((dist < -3) || (dist > 3))
|
|
{
|
|
showFlyText(self, new string_id("npc_reaction/flytext", "threaten"), 2.0f, colors.ORANGERED);
|
|
sendSystemMessage(player, SID_MILK_TOO_FAR);
|
|
utils.removeScriptVar(self, "milk.attempts");
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
if(stealth.hasInvisibleBuff(player) || ai_lib.isInCombat(self) || ai_lib.isInCombat(player))
|
|
{
|
|
showFlyText(self, new string_id("npc_reaction/flytext", "threaten"), 2.0f, colors.ORANGERED);
|
|
sendSystemMessage(player, SID_MILK_NOT_HIDDEN);
|
|
utils.removeScriptVar(self, "milk.attempts");
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
int attempts = utils.getIntScriptVar(self, "milk.attempts");
|
|
attempts++;
|
|
utils.setScriptVar(self, "milk.attempts", attempts);
|
|
|
|
boolean shortenAttempts = false;
|
|
float milkQuantityModified = 0.0f;
|
|
float milkExceptionalModified = 0.0f;
|
|
float milkStunModified = 0.0f;
|
|
|
|
//creature_milking_buff is an ability given to players
|
|
//that complete the exceptional milk collection
|
|
if(buff.hasBuff(player, "creature_milking_buff") || buff.hasBuff(player, "drink_starshine_surprise"))
|
|
{
|
|
if(buff.hasBuff(player, "creature_milking_buff"))
|
|
CustomerServiceLog("buff", "creature_milking_buff Buff used by player: "+player+" Name: " +getName(player)+ " has creature_milking_buff ");
|
|
else if(buff.hasBuff(player, "drink_starshine_surprise"))
|
|
CustomerServiceLog("buff", "creature_milking_buff Buff used by player: "+player+" Name: " +getName(player)+ " has drink_starshine_surprise buff ");
|
|
|
|
milkExceptionalModified += ((float)getEnhancedSkillStatisticModifierUncapped(player, "milk_exceptional_modified"));
|
|
CustomerServiceLog("buff", "creature_milking_buff Buff used by player: "+player+" Name: " +getName(player)+ " has a exceptional milk modifier of: "+milkExceptionalModified);
|
|
milkQuantityModified += ((float)getEnhancedSkillStatisticModifierUncapped(player, "milk_quantity_modified"));
|
|
CustomerServiceLog("buff", "creature_milking_buff Buff used by player: "+player+" Name: " +getName(player)+ " has a milk quantity modifier of: "+milkQuantityModified);
|
|
shortenAttempts = true;
|
|
}
|
|
|
|
if(attempts == 3 || shortenAttempts)
|
|
{
|
|
// Get the player's inventory.
|
|
obj_id pInv = utils.getInventoryContainer(player);
|
|
if(!isValidId(pInv) || !exists(pInv))
|
|
return SCRIPT_CONTINUE;
|
|
//check the volume
|
|
else if(getVolumeFree(pInv) <= 0)
|
|
{
|
|
sendSystemMessage(self, SID_CANT_MILK_INV_FULL);
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
int lev = ai_lib.getLevel(self);
|
|
if(lev > 50)
|
|
lev = 50;
|
|
int mlev = lev/2;
|
|
if(mlev < 1)
|
|
mlev = 1;
|
|
int amt = rand(mlev*10, lev*10);
|
|
|
|
//buff modifier
|
|
if(shortenAttempts && milkQuantityModified > 0)
|
|
amt = amt + (int)(amt * milkQuantityModified);
|
|
|
|
string resourceType = ai_lib.getMilkType(self);
|
|
if(resourceType == null || resourceType.equals(""))
|
|
{
|
|
CustomerServiceLog("milking_and_lair_search", "handleMilking: Player: "+getName(player)+" OID: "+player+" attempted to milk but could not retrieve a valid milk type on " + self + " "+getName(self));
|
|
blog("ai.handleMilking: cannot get resource type from ai_lib.getMilkType");
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
string sceneName = getCurrentSceneName();
|
|
if(sceneName == null || sceneName.equals(""))
|
|
{
|
|
CustomerServiceLog("milking_and_lair_search", "handleMilking: Player: "+getName(player)+" OID: "+player+" attempted to milk but could not retrieve a valid scene name (planet name) while milking " + self + " "+getName(self));
|
|
blog("ai.handleMilking: cannot get scene name");
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
string rsrcMapTable = "datatables/creature_resource/resource_scene_map.iff";
|
|
if(rsrcMapTable == null || rsrcMapTable.equals(""))
|
|
{
|
|
blog("ai.handleMilking: cannot get resource map table data");
|
|
CustomerServiceLog("milking_and_lair_search", "handleMilking: Player: "+getName(player)+" OID: "+player+" attempted to milk but could not retrieve resource map table data to receive the milk resources while milking " + self + " "+getName(self));
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
string correctedPlanetName = dataTableGetString(rsrcMapTable, sceneName, 1);
|
|
if(correctedPlanetName == null || correctedPlanetName.equals(""))
|
|
{
|
|
correctedPlanetName = "tatooine";
|
|
CustomerServiceLog("milking_and_lair_search", "handleMilking: Player: "+getName(player)+" OID: "+player+" attempted to milk but received an invlaid Planet Name. As a result the milk resource planet attrib has been defaulted to TATOOINE.");
|
|
}
|
|
|
|
resourceType = resourceType + "_" + correctedPlanetName;
|
|
|
|
int useDistMap = dataTableGetInt(rsrcMapTable, sceneName, "useDistributionMap");
|
|
if(useDistMap < 0)
|
|
{
|
|
blog("ai.handleMilking: cannot get int data from distribution data table");
|
|
CustomerServiceLog("milking_and_lair_search", "handleMilking: Player: "+getName(player)+" OID: "+player+" attempted to milk but could not retrieve resource distribution data while milking " + self + " "+getName(self));
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
location worldLoc = getWorldLocation(self);
|
|
if(worldLoc == null)
|
|
{
|
|
blog("ai.handleMilking: cannot get world location from cpp");
|
|
CustomerServiceLog("milking_and_lair_search", "handleMilking: Player: "+getName(player)+" OID: "+player+" attempted to milk but could not retrieve world location while milking " + self + " "+getName(self));
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
if(useDistMap == 0)
|
|
{
|
|
worldLoc.area = correctedPlanetName;
|
|
}
|
|
|
|
obj_id[] resourceList = resource.createRandom(resourceType, amt, worldLoc, pInv, player, 2);
|
|
if(resourceList == null)
|
|
{
|
|
blog("ai.handleMilking: cannot get resource data from resource.createRandom function");
|
|
CustomerServiceLog("milking_and_lair_search", "handleMilking: Player: "+getName(player)+" OID: "+player+" attempted to milk but could not resource data from resource.createRandom function while milking " + self + " "+getName(self));
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
//used in loop below for proper stacking of the
|
|
//Milk in player inventory
|
|
location curloc = getLocation(player);
|
|
if(curloc == null)
|
|
{
|
|
blog("ai.handleMilking: cannot get resource data from getLocation cpp");
|
|
CustomerServiceLog("milking_and_lair_search", "handleMilking: Player: "+getName(player)+" OID: "+player+" attempted to milk but could not retrieve location data while milking " + self + " "+getName(self));
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
//This is for proper stacking of the Milk in player inventory
|
|
for (int i = 0; i < resourceList.length; i++)
|
|
{
|
|
//this makes the resource auto stack in player inventory
|
|
blog(""+resourceList[i]);
|
|
setLocation(resourceList[i], curloc);
|
|
putIn(resourceList[i], pInv, player);
|
|
}
|
|
|
|
sendSystemMessage(player, SID_MILK_SUCCESS);
|
|
utils.setScriptVar(self, "milk.lasttime", getGameTime());
|
|
utils.removeScriptVar(self, "milk.attempts");
|
|
|
|
//Collection Milk
|
|
if(!collection.collectionResource(player, "milk", (int)milkExceptionalModified))
|
|
{
|
|
blog("ai.handleMilking: cannot get resource data from getLocation cpp");
|
|
CustomerServiceLog("milking_and_lair_search", "handleMilking: Player: "+getName(player)+" OID: "+player+" did not receive an exceptional milk from creature " + self + " "+getName(self)+". Check the collection logs for additional details.");
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
else
|
|
{
|
|
sendSystemMessage(player, SID_MILK_CONTINUE);
|
|
}
|
|
|
|
messageTo(self, "handleMilking", params, 7, false);
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler handleSpawnParameters()
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::handleSpawnParameters() self(" + self + ")");
|
|
|
|
if(params == null || params.isEmpty())
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
int actionCode = params.getInt("actionCode");
|
|
obj_id target = params.getObjId("actionTarget");
|
|
|
|
switch (actionCode)
|
|
{
|
|
case gcw.AC_ATTACK:
|
|
if(isIdValid(target))
|
|
{
|
|
startCombat(self, target);
|
|
}
|
|
break;
|
|
|
|
case gcw.AC_SCAN:
|
|
int scanInterests = params.getInt("scanInterests");
|
|
|
|
if(scanInterests == 0)
|
|
{
|
|
gcw.assignScanInterests(self);
|
|
}
|
|
else
|
|
{
|
|
utils.setScriptVar(self, gcw.SCRIPTVAR_SCAN_INTEREST, scanInterests);
|
|
}
|
|
|
|
gcw.harass(self, target);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler playDelayedClientEffect()
|
|
{
|
|
string clientEffect = params.getString("effect");
|
|
obj_id target = params.getObjId("target");
|
|
|
|
playClientEffectObj(target, clientEffect, target, "");
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler clearScriptVar()
|
|
{
|
|
string scriptVarName = params.getString("name");
|
|
|
|
utils.removeScriptVar(self, scriptVarName);
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnInvulnerableChanged(boolean invulnerable)
|
|
{
|
|
LOGC(aiLoggingEnabled(self), "debug_ai", "ai::OnInvulnerableChanged() self(" + self + ") invulerable(" + invulnerable + ")");
|
|
|
|
if(invulnerable)
|
|
{
|
|
removeTriggerVolume(ai_lib.ALERT_VOLUME_NAME);
|
|
removeTriggerVolume(ai_lib.AGGRO_VOLUME_NAME);
|
|
}
|
|
else
|
|
{
|
|
ai_lib.resetCombatTriggerVolumes();
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
|
|
trigger OnIncapacitateTarget(obj_id victim)
|
|
{
|
|
if(!utils.hasScriptVar(self,"experienced"))
|
|
{
|
|
if(isIdValid(victim))
|
|
{
|
|
if(isPlayer(victim))
|
|
{
|
|
if(rand(1,1000)==999)
|
|
{
|
|
ai_lib.creatureLevelUp(self, victim);
|
|
utils.setScriptVar(self,"experienced",1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
//Collections:
|
|
//This messageHandler clears the Camera particle from the creature
|
|
//that had its picture taken.
|
|
messageHandler clearCollectionCameraParticle()
|
|
{
|
|
//Get the target's ID
|
|
obj_id target = params.getObjId("target");
|
|
//Clear the particle and ScriptVar from the creature
|
|
stopClientEffectObjByLabel(target, "root_buff");
|
|
utils.removeScriptVar(target, "picture_taken_debuff");
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
trigger OnGiveItem (obj_id item, obj_id giver)
|
|
{
|
|
if(!hasCompletedCollectionSlot(giver, "meatlump_recruiter_starter"))
|
|
return SCRIPT_CONTINUE;
|
|
if(!isIdValid(giver) || !exists(giver))
|
|
return SCRIPT_CONTINUE;
|
|
if(!isIdValid(item) || !exists(item))
|
|
return SCRIPT_CONTINUE;
|
|
|
|
//make sure it is the correct item being given
|
|
string itemName = getStaticItemName(item);
|
|
if(!itemName.equals("col_meatlump_recruit_manual_02_01"))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
//get the mob's socialGroup - if they're meatlumps, deny!
|
|
// storyteller_meatlump / meatlump / angry_meatlump
|
|
string mobName = getCreatureName(self);
|
|
string socialMeatlump = dataTableGetString(CREATURE_TABLE, mobName, "socialGroup");
|
|
|
|
if(socialMeatlump.startsWith("meatlump") || socialMeatlump.endsWith("meatlump"))
|
|
{
|
|
if(!ai_lib.isNpc(self))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
else
|
|
{
|
|
sendSystemMessage(giver, SID_ALREADY_RECRUITED);
|
|
}
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
if(toLower(socialMeatlump).equals("rebel") || toLower(socialMeatlump).equals("rebel"))
|
|
{
|
|
if(!ai_lib.isNpc(self))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
else
|
|
{
|
|
sendSystemMessage(giver, SID_NO_RECRUIT_REB_IMP);
|
|
}
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
//If has completed the collection - BAIL
|
|
if(hasCompletedCollection(giver, "col_meatlump_recruiter_01"))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
if(utils.hasScriptVar(self, "collection.recruited"))
|
|
{
|
|
sendSystemMessage(giver, SID_ALREADY_RECRUITED);
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
if(utils.hasScriptVar(self, "collection.investigator"))
|
|
{
|
|
sendSystemMessage(giver, SID_ENEMY_RECRUIT);
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
//make sure this NPC is an NPC
|
|
if(!ai_lib.isNpc(self))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
//make sure this NPC is invulnerable
|
|
if(!isInvulnerable(self))
|
|
{
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
//player has passed all checks, let's see if someone attacks
|
|
int attackRoll = rand(1,100);
|
|
if(attackRoll <= 10)
|
|
{
|
|
chat.chat(self, SID_NPC_MEATLUMP_SPEAK);
|
|
|
|
float searchSize = 12.0f;
|
|
//initialize mob
|
|
obj_id mob = null;
|
|
//mob level minus 2 levels due to Traders
|
|
int mobLevel = getLevel(giver) -2;
|
|
//get the player's location
|
|
location currentLoc = getLocation(giver);
|
|
|
|
location locGoodLocation = locations.getGoodLocationAroundLocationAvoidCollidables(currentLoc, searchSize, searchSize, searchSize, searchSize, false, false, 6.0f);
|
|
if(locGoodLocation==null)
|
|
{
|
|
locGoodLocation = (location)currentLoc.clone();
|
|
}
|
|
|
|
int mobCount = rand(1, 3);
|
|
//if(isProfession(obj_id player, int profession))
|
|
if(utils.isProfession(giver, utils.TRADER))
|
|
{
|
|
//Give them an easy kill - no silver elite for them.
|
|
mobCount = 2;
|
|
}
|
|
|
|
if(mobCount == 1)
|
|
{
|
|
mob = create.object("mtp_recruiter_corsec_detective", locGoodLocation, mobLevel);
|
|
}
|
|
if(mobCount == 2)
|
|
{
|
|
mob = create.object("mtp_recruiter_corsec_investigator", locGoodLocation, mobLevel);
|
|
}
|
|
if(mobCount == 3)
|
|
{
|
|
mob = create.object("mtp_recruiter_corsec_agent", locGoodLocation, mobLevel);
|
|
}
|
|
addHate(mob, giver, 1000.0f);
|
|
|
|
//set scriptvar lockout - and send messageTo
|
|
utils.setScriptVar(self, "collection.investigator", 1);
|
|
messageTo(self, "removeMeatlumpInvestigatorScriptVar", null, 82800, false);
|
|
}
|
|
else
|
|
{
|
|
//tell the player
|
|
sendSystemMessage(giver, SID_GAVE_RECRUIT_ITEM);
|
|
//Decrement the stack count of the Meatlump Manifesto -
|
|
int count = getCount(item);
|
|
count--;
|
|
|
|
if(count <= 0)
|
|
{
|
|
//remove the 'noDestroy' script first!
|
|
detachScript(item, "item.special.nodestroy");
|
|
destroyObject(item);
|
|
}
|
|
else
|
|
{
|
|
setCount(item, count);
|
|
}
|
|
|
|
//Modify the slot value
|
|
modifyCollectionSlotValue(giver, "meatlump_recruiter_01", 1);
|
|
//set scriptvar lockout - and send messageTo
|
|
utils.setScriptVar(self, "collection.recruited", 1);
|
|
messageTo(self, "removeMeatlumpRecruitmentScriptVar", null, 82800, false);
|
|
}
|
|
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler removeMeatlumpRecruitmentScriptVar()
|
|
{
|
|
if(utils.hasScriptVar(self, "collection.recruited"))
|
|
{
|
|
utils.removeScriptVar(self, "collection.recruited");
|
|
}
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler removeMeatlumpInvestigatorScriptVar()
|
|
{
|
|
if(utils.hasScriptVar(self, "collection.investigator"))
|
|
{
|
|
utils.removeScriptVar(self, "collection.investigator");
|
|
}
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler milkingCreatureStunned()
|
|
{
|
|
showFlyText(self, new string_id("npc_reaction/flytext", "stun"), 2.0f, colors.ORANGERED);
|
|
|
|
ai_lib.setDefaultCalmBehavior(self, ai_lib.BEHAVIOR_SENTINEL);
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler milkingCreatureStunFail()
|
|
{
|
|
showFlyText(self, new string_id("npc_reaction/flytext", "stun_fail"), 2.0f, colors.ORANGERED);
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler milkingCreatureStunRemoval()
|
|
{
|
|
showFlyText(self, new string_id("npc_reaction/flytext", "stun_expired"), 2.0f, colors.ORANGERED);
|
|
ai_lib.setDefaultCalmBehavior(self, ai_lib.BEHAVIOR_LOITER);
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
messageHandler redoYaw()
|
|
{
|
|
obj_id target = params.getObjId("target");
|
|
if(hasObjVar(target, "ai.yaw"))
|
|
{
|
|
float yaw_ai = getFloatObjVar(target, "ai.yaw");
|
|
setYaw(target, yaw_ai);
|
|
}
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
|
|
boolean blog(string msg)
|
|
{
|
|
if(LOGGING_ON && !msg.equals(""))
|
|
LOG(LOGGING_CATEGORY, msg);
|
|
return true;
|
|
}
|