def switchVisualMode(self, mode, skipHide = False):
        if self.altVisType == mode:
            return None

        if mode == 'chickenFantifico':
            if not skipHide:
                self.getGeomNode().hide()

            self.isGhost = 0
            self.altVisType = mode
            self.altVisNode = Rooster()
            self.altVisNode.setAvatarType(AvatarTypes.Rooster)
            self.altVisNode.setScale(1.5)
            self.oldVisNode = self.getGeomNode()
            self.setGeomNode(self.attachNewNode(ModelNode('actorGeom')))
            self.altVisNode.reparentTo(self.getGeomNode())
            self.motionFSM.setAnimInfo(self.getAnimInfo('LandRoam'))
            self.style = None
            self.noticeAnim1 = 'crow'
            self.noticeAnim2 = ''
            self.greetingAnim = ''
        elif mode == 'notChickenFantifico':
            self.clearVisualMode(mode)
            self.isGhost = 1
            self.altVisType = mode
class DistributedBattleNPC(DistributedBattleAvatar.DistributedBattleAvatar):
    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBattleNPC')
    deferrable = True

    def __init__(self, cr):
        NodePath.__init__(self, 'BattleNPC')
        DistributedBattleAvatar.DistributedBattleAvatar.__init__(self, cr)
        self.headTrack = None
        self.setNpc(1)
        self.lowEnd = base.options.getCharacterDetailSetting() == 0
        self.level = None
        self.cRay = None
        self.cRayNode = None
        self.cRayNodePath = None
        self.cRayBitMask = None
        self.lifter = None
        self.cTrav = None
        self.pusher = None
        self.floorChecksEnabled = False
        self.canCheckFloors = True
        self.cAggro = None
        self.cAggroNode = None
        self.cAggroNodePath = None
        self.cNotice = None
        self.cNoticeNode = None
        self.cNoticeNodePath = None
        self.noticeDistance = 35.0
        self.aggroSphereSize = None
        self.noticeSphereSize = None
        self.closeNoticeDistance = self.noticeDistance
        self.noticeIval = None
        self.noticeFlag = 0
        self.noticeSpeed = 0.5
        self.doneThreat = 0
        self.moveNoticeFlag = 0
        self.isMovingDontNotice = 0
        self.lastMovedTimeStamp = None
        self.shouldTurnToNotice = 1
        self.hasTurnedToNotice = 0
        self.localAvatarHasBeenNoticed = 0
        self.needNoticeGroundTracking = 0
        self.preselectedReaction = None
        self.usingPropNoNotice = 0
        self.noticeIdle = None
        self.respondedToLocalAttack = 0
        self.handler = None
        self.pendingBoardVehicle = None
        self.spawnIvals = []
        self.headingNode = self.attachNewNode('headingNode')
        self.enableZPrint = False
        self.animProp = None
        self.freezeTask = None
        self.animSet = 'default'
        self.animSetSetup = False
        self.loaded = 0
        self.lastSmoothPosUpdateTime = 0
        self.wantsActive = 0
        self.inInvasion = False
        self.isAlarmed = False
        self.alarmedAggroRadius = 0
        self.questMod = None
        self.altVisNode = None
        self.altVisType = None
        self.oldVisNode = None
        self.nearCallbacks = []
        self.skipLocalSmooth = False


    def __repr__(self):
        return '%s (Lvl %s)' % (self.getName(), self.level)


    def setAssociatedQuests(self, quests):
        self._associatedQuests = quests


    def getAnimStyleExtras(self):
        return []


    def setupCustomAnims(self, extraAnims = []):
        if self.animSet != 'default':
            allAnims = copy.copy(CustomAnims.INTERACT_ANIMS.get(self.animSet))
            noticeAnims = []
            self.noticeAnim1 = allAnims.get('notice1', [
                self.noticeAnim1])[0]
            self.noticeAnim2 = allAnims.get('notice2', [
                self.noticeAnim2])[0]
            self.greetingAnim = allAnims.get('greeting', [
                self.greetingAnim])[0]
            if self.noticeAnim1:
                noticeAnims.append(self.noticeAnim1)

            if self.noticeAnim2:
                noticeAnims.append(self.noticeAnim2)

            if self.greetingAnim:
                noticeAnims.append(self.greetingAnim)

            if allAnims:
                if allAnims.has_key('walk'):
                    allAnims['walk'] = allAnims['walk'][:1]

                if allAnims.has_key('run'):
                    allAnims['run'] = allAnims['run'][:1]

                if allAnims.has_key('props'):
                    del allAnims['props']

                if allAnims.has_key('idles'):
                    for currIdle in allAnims['idles']:
                        allAnims[currIdle] = [
                            currIdle]

                    del allAnims['idles']

                if allAnims.has_key('extraAnims'):
                    for currAnim in allAnims['extraAnims']:
                        allAnims[currAnim] = [
                            currAnim]

                    del allAnims['extraAnims']

                if self.startState != 'Idle':
                    allAnims.setdefault('walk', [
                        'walk'])
                    allAnims.setdefault('walk_back_diagonal_left', [
                        'walk_back_diagonal_left'])
                    allAnims.setdefault('walk_back_diagonal_right', [
                        'walk_back_diagonal_right'])
                    allAnims.setdefault('run', [
                        'run'])
                    allAnims.setdefault('run_diagonal_left', [
                        'run_diagonal_left'])
                    allAnims.setdefault('run_diagonal_right', [
                        'run_diagonal_right'])


            for anim in noticeAnims:
                allAnims[anim] = [
                    anim]

            for anim in self.getAnimStyleExtras():
                allAnims[anim] = [
                    anim]

            if extraAnims:
                for anim in extraAnims:
                    allAnims[anim] = [
                        anim]


            if allAnims:
                self.makeMyAnimDict(self.style.gender, allAnims)

            yieldThread('make Anims')



    def setupStyle(self):
        if self.style:
            self.setupCustomAnims()
            self.generateMyself()
            yieldThread('finished Gen')



    def addCustomAnimations(self, animList = []):
        if self.style:
            self.setupCustomAnims(extraAnims = animList)
            self.generateMyself()
            yieldThread('finished Gen')



    def makeMyAnimDict(self, gender, animNames):
        pass


    def generateMyself(self):
        pass


    def setupActorAnims(self):
        if self.animSet != 'default' and self.animSetSetup == False:
            self.animSetSetup = True
            allAnims = CustomAnims.INTERACT_ANIMS.get(self.animSet)
            if allAnims == None:
                return None

            allIdles = allAnims.get('idles')
            if type(allIdles) is dict:
                allAnims = allIdles.get(ItemGlobals.getSkillCategory(self.currentWeaponId))
                allIdles = allAnims.get('idles')

            allProps = allAnims.get('props')
            currChoice = random.choice(allIdles)
            animInfoCopy = copy.copy(Biped.Biped.animInfo)
            self.animInfo = animInfoCopy
            animStateNames = [
                'LandRoam',
                'BayonetLandRoam']
            for currAnimState in animStateNames:
                oldAnimInfo = animInfoCopy.get(currAnimState)
                if oldAnimInfo == None:
                    continue

                (newWalk, newWalkScale) = allAnims.get('walk', [
                    oldAnimInfo[1][0],
                    oldAnimInfo[1][1]])
                (newRun, newRunScale) = allAnims.get('run', [
                    oldAnimInfo[2][0],
                    oldAnimInfo[2][1]])
                newAnimInfo = ((currChoice, oldAnimInfo[0][1]),) + ((newWalk, newWalkScale),) + ((newRun, newRunScale),) + oldAnimInfo[3:]
                if self.motionFSM.motionAnimFSM.state == currAnimState:
                    self.motionFSM.setAnimInfo(newAnimInfo)

                self.animInfo[currAnimState] = newAnimInfo
                yieldThread('custom anim info')

            self.holdAnimProp(allProps)
            self.noticeIdle = allAnims.get('noticeIdle', [
                None])[0]
            if self.canMove == False:
                self.canCheckFloors = False

            yieldThread('hold anim prop')



    def announceGenerate(self):
        self.checkQuestObjMod()
        DistributedBattleAvatar.DistributedBattleAvatar.announceGenerate(self)
        if not self.loaded:
            self.setupStyle()

        self.setupActorAnims()
        self.setName(self.name)
        self.setPickable(0)
        self.accept('noticeStateChanged', self.handleNoticeChanged)
        self.accept('Local_Efficiency_Set', self.setEfficiency)
        if __dev__ and base.config.GetBool('show-aggro-radius', 0):
            if self.isBattleable():
                size = self.getAggroSphereSize()
                sphere = loader.loadModel('models/effects/explosion_sphere')
                sphere.reparentTo(self)
                sphere.setTransparency(1)
                sphere.setAlphaScale(0.29999999999999999)
                sphere.setScale(render, size)


        if self.shadowPlacer:
            self.shadowPlacer.resetToOrigin()
            self.shadowPlacer.off()

        self.accept('questInterestChange-%s' % self.getUniqueId(), self.checkQuestObjMod, extraArgs = [
            True,
            True])
        self.accept('SwitchAgrroModel', self.handleAggroModelSwitch)
        self.setEfficiency(localAvatar.getEfficiency())


    def handleAggroModelSwitch(self):
        print 'handleAggroModelSwitch'
        self.updateCollisions()


    def checkQuestObjMod(self, doMod = True, interestChange = False):
        if self._associatedQuests and doMod:
            self.questMod = QuestBase.questObjMod(self._associatedQuests, self, localAvatar, self.cr)
            if self.questMod == 'hide':
                self.hide(invisibleBits = PiratesGlobals.INVIS_QUEST)
                self.disableBodyCollisions()
                self.disableBattleCollisions()
                self.setIgnoreProximity(True)
            elif self.questMod == 'show':
                self.show(invisibleBits = PiratesGlobals.INVIS_QUEST)
                self.enableBattleCollisions()
                self.updateCollisions()
            elif self.questMod and not interestChange:

                try:
                    eval('self.' + self.questMod[0] + '(' + self.questMod[1] + ')')
                except:
                    self.notify.warning('error executing npc mod funcion %s for quest %s' % (self.questMod, self._associatedQuests))



        return self.questMod


    def switchVisualMode(self, mode, skipHide = False):
        if self.altVisType == mode:
            return None

        if mode == 'chickenFantifico':
            if not skipHide:
                self.getGeomNode().hide()

            self.isGhost = 0
            self.altVisType = mode
            self.altVisNode = Rooster()
            self.altVisNode.setAvatarType(AvatarTypes.Rooster)
            self.altVisNode.setScale(1.5)
            self.oldVisNode = self.getGeomNode()
            self.setGeomNode(self.attachNewNode(ModelNode('actorGeom')))
            self.altVisNode.reparentTo(self.getGeomNode())
            self.motionFSM.setAnimInfo(self.getAnimInfo('LandRoam'))
            self.style = None
            self.noticeAnim1 = 'crow'
            self.noticeAnim2 = ''
            self.greetingAnim = ''
        elif mode == 'notChickenFantifico':
            self.clearVisualMode(mode)
            self.isGhost = 1
            self.altVisType = mode



    def clearVisualMode(self, visType = None):
        if self.oldVisNode:
            self.setGeomNode(self.oldVisNode)
            self.oldVisNode = None

        self.getGeomNode().show()
        self.altVisType = visType
        if self.altVisNode:
            self.altVisNode.removeNode()
            self.altVisNode = None

        self.motionFSM.setAnimInfo(self.getAnimInfo('LandRoam'))


    def turnAggro(self):
        self.show(invisibleBits = PiratesGlobals.INVIS_QUEST)
        self.enableBattleCollisions()
        self.updateCollisions()
        self.setIgnoreProximity(False)
        self.nearCallbacks.append([
            self.sendUpdate,
            [
                'requestHostilize']])


    def turnFriendlyAndHide(self):
        self.hide(invisibleBits = PiratesGlobals.INVIS_QUEST)
        self.disableBodyCollisions()
        self.disableBattleCollisions()
        self.setIgnoreProximity(True)
        self.nearCallbacks = []


    def freezeShadow(self):
        self.shadowPlacer.off()
        self.freezeTask = None


    def generate(self):
        DistributedBattleAvatar.DistributedBattleAvatar.generate(self)
        self.lastFloorCheckedXYZ = [
            0,
            0,
            0]


    def reparentTo(self, parent):
        DistributedBattleAvatar.DistributedBattleAvatar.reparentTo(self, parent)


    def wrtReparentTo(self, parent):
        DistributedBattleAvatar.DistributedBattleAvatar.wrtReparentTo(self, parent)


    def createGameFSM(self):
        self.gameFSM = BattleNPCGameFSM.BattleNPCGameFSM(self)


    def disable(self):
        if self.freezeTask:
            self.freezeTask.remove()
            self.freezeTask = None

        if self.pendingBoardVehicle:
            base.cr.relatedObjectMgr.abortRequest(self.pendingBoardVehicle)
            self.pendingBoardVehicle = None

        taskMgr.remove(self.uniqueName('removeCollisions'))
        taskMgr.remove(self.uniqueName('delayHelpCall'))
        self.ignore('Local_Efficiency_Set')
        self.ignoreAll()
        self.stopLookAt()
        self.disableBodyCollisions()
        if self.noticeIval:
            self.noticeIval.pause()

        self.noticeIval = None
        for currSpawnIval in self.spawnIvals:
            currSpawnIval.finish()

        self.spawnIvals = []
        self.nearCallbacks = []
        self.clearVisualMode()
        DistributedBattleAvatar.DistributedBattleAvatar.disable(self)


    def delete(self):
        DistributedBattleAvatar.DistributedBattleAvatar.delete(self)


    def isDistributed(self):
        return 1


    def requestGameState(self, *args):
        self.gameFSM.request(*args)


    def setSpawnPos(self, x, y, z):
        pass


    def lookAtTarget(self, task = None):
        if self.currentTarget:
            self.headsUp(self.currentTarget)
            return Task.cont
        else:
            return Task.done


    def getUpdateLookAtTaskName(self):
        return self.taskName('lookAtTarget')


    def startLookAt(self):
        taskMgr.add(self.lookAtTarget, self.getUpdateLookAtTaskName())


    def stopLookAt(self):
        taskMgr.remove(self.getUpdateLookAtTaskName())


    def setLevel(self, level):
        if self.level is not None:
            return None

        self.level = level
        enemyScale = self.getEnemyScale()
        self.height *= enemyScale
        self.setAvatarScale(self.scale * enemyScale)


    def setMonsterNameTag(self):
        if self.isInInvasion():
            name = self.name
            if self.getNameText():
                self.getNameText()['text'] = name

        else:
            DistributedBattleAvatar.DistributedBattleAvatar.setMonsterNameTag(self)


    def setState(self, stateName, timeStamp):
        self.request(stateName)


    def boardVehicle(self, vehicleDoId):
        if self.pendingBoardVehicle:
            base.cr.relatedObjectMgr.abortRequest(self.pendingBoardVehicle)
            self.pendingBoardVehicle = None

        self.pendingBoardVehicle = base.cr.relatedObjectMgr.requestObjects([
            vehicleDoId], eachCallback = self.boardExistingVehicle)


    def boardExistingVehicle(self, vehicle):
        self.reparentTo(vehicle.getModel())
        self.pendingBoardVehicle = None


    def initializeBodyCollisions(self, collIdStr):
        pass


    def updateCollisions(self):
        self.cTrav = base.localAvatar.cTrav
        if self.collisionMode & PiratesGlobals.COLL_MODE_FLOORS_CL:
            if self.cRay == None:
                self.cRay = CollisionRay(0.0, 0.0, 4000.0, 0.0, 0.0, -1.0)
                self.cRayNode = CollisionNode(self.taskName('cRay'))
                self.cRayNode.addSolid(self.cRay)
                self.cRayNode.setFromCollideMask(PiratesGlobals.FloorBitmask | PiratesGlobals.ShipFloorBitmask)
                self.cRayNode.setIntoCollideMask(BitMask32.allOff())
                self.cRayNode.setBounds(BoundingSphere())
                self.cRayNode.setFinal(1)
                self.cRayNodePath = self.attachNewNode(self.cRayNode)
                self.lifter = CollisionHandlerGravity()
                self.lifter.setGravity(32.173999999999999 * 4.0)
                self.lifter.setReach(self.getFloorRayReach())
                self.lifter.setMaxVelocity(64.0)
                self.lifter.setInPattern('enterFloor%fn')
                self.lifter.setAgainPattern('againFloor%fn')
                hitFloorEvent = self.taskName('enterFloorcRay')
                againFloorEvent = self.taskName('againFloorcRay')
                self.accept(hitFloorEvent, self._hitFloorCallback)
                self.accept(againFloorEvent, self._hitFloorCallback)


        aggroSphereSize = self.getInstantAggroSphereSize()
        if aggroSphereSize != self.aggroSphereSize:
            if self.cAggro:
                self.cAggroNodePath.remove()
                self.cAggroNodePath = None
                self.cAggroNode = None
                self.cAggro = None

            self.aggroSphereSize = aggroSphereSize

        if self.cAggro == None and self.isBattleable() and aggroSphereSize > 0:
            self.cAggro = CollisionSphere(0, 0, 0, aggroSphereSize)
            self.cAggro.setTangible(0)
            self.cAggroNode = CollisionNode(self.uniqueName('AggroSphere'))
            self.cAggroNode.setFromCollideMask(BitMask32.allOff())
            self.cAggroNode.setIntoCollideMask(PiratesGlobals.WallBitmask)
            self.cAggroNode.addSolid(self.cAggro)
            self.cAggroNodePath = self.attachNewNode(self.cAggroNode)
            if base.config.GetBool('show-aggro-radius', 0):
                self.cAggroNodePath.show()

            if base.config.GetBool('npcs-auto-target', 1):
                enterCollEvent = self.uniqueName('enter' + 'AggroSphere')
                self.accept(enterCollEvent, self._handleEnterAggroSphere)
                if base.cr.gameStatManager.aggroModelIndex == 1:
                    self.accept('helpMeAggroThisPunk', self._handleAggroHelpRequested)



        noticeSphereSize = self.getEffectiveNoticeDistance()
        if noticeSphereSize != self.noticeSphereSize:
            if self.cNotice:
                self.cNoticeNodePath.remove()
                self.cAggroNodePath = None
                self.cNoticeNode = None
                self.cNotice = None

            self.noticeSphereSize = noticeSphereSize

        if self.cNotice == None and noticeSphereSize > 0:
            self.cNotice = CollisionSphere(0, 0, 0, noticeSphereSize)
            self.cNotice.setTangible(0)
            self.cNoticeNode = CollisionNode(self.uniqueName('NoticeSphere'))
            self.cNoticeNode.setFromCollideMask(BitMask32.allOff())
            if base.noticeSystemOn:
                self.cNoticeNode.setIntoCollideMask(PiratesGlobals.WallBitmask)
            else:
                self.cNoticeNode.setIntoCollideMask(BitMask32.allOff())
            self.cNoticeNode.addSolid(self.cNotice)
            self.cNoticeNodePath = self.attachNewNode(self.cNoticeNode)
            if base.config.GetBool('show-aggro-radius', 0):
                self.cNoticeNodePath.show()

            if base.config.GetBool('npcs-auto-target', 1):
                enterCollEvent = self.uniqueName('enter' + 'NoticeSphere')
                self.accept(enterCollEvent, self._handleEnterNoticeSphere)




    def getEffectiveNoticeDistance(self):
        if base.cr.gameStatManager.aggroModelIndex == 1 and self.getTeam() not in [
            0,
            4]:
            return self.getInstantAggroSphereSize() + self.noticeDistance
        else:
            return self.noticeDistance


    def handleNoticeChanged(self):
        if base.noticeSystemOn:
            self.cNoticeNode.setIntoCollideMask(PiratesGlobals.WallBitmask)
        else:
            self.cNoticeNode.setIntoCollideMask(BitMask32.allOff())
            self.abortNotice()


    def disableFloorChecks(self):
        if self.floorChecksEnabled and self.lifter:
            self.floorChecksEnabled = False
            self.cTrav.removeCollider(self.cRayNodePath)
            self.lifter.removeCollider(self.cRayNodePath)



    def enableFloorChecks(self):
        if self.canCheckFloors and self.floorChecksEnabled == False and self.lifter:
            if not (self.ship) or self.ship is localAvatar.ship:
                self.floorChecksEnabled = True
                self.lifter.addCollider(self.cRayNodePath, self)
                self.cTrav.addCollider(self.cRayNodePath, self.lifter)



    def performQuickFloorCheck(self, customReach = None):
        if self.cRayNodePath == None:
            self.notify.debug('aborting quick floor check for %s' % self.name)
            return None

        self.notify.debug('performing quick floor check %s' % self.cRayNodePath)
        self.notify.debug('  myPos is %s' % self.getPos())
        if localAvatar.ship and self.ship is not localAvatar.ship:
            localAvatar.ship.stashPlaneCollisions()

        cTrav = CollisionTraverser('quickFloorCheck')
        floorRay = CollisionHandlerFloor()
        floorRay.setOffset(OTPGlobals.FloorOffset)
        if customReach != None:
            rayReach = customReach
        else:
            rayReach = self.getFloorRayReach()
        floorRay.setReach(rayReach)
        floorRay.addCollider(self.cRayNodePath, self)
        cTrav.addCollider(self.cRayNodePath, floorRay)
        cTrav.traverse(render)
        cTrav.removeCollider(self.cRayNodePath)
        floorRay.removeCollider(self.cRayNodePath)
        self.notify.debug('  quick floor check hasContact: %s' % floorRay.hasContact())
        self.notify.debug('  myPos NEW is %s' % self.getPos())
        self.lastFloorCheckedXYZ = [
            self.getX(),
            self.getY(),
            self.getZ()]
        if localAvatar.ship and self.ship is not localAvatar.ship:
            localAvatar.ship.unstashPlaneCollisions()



    def disableBodyCollisions(self):
        if self.cRayNodePath:
            self.cTrav.removeCollider(self.cRayNodePath)
            self.floorChecksEnabled = False
            self.cRayNodePath.removeNode()
            self.cRayNodePath = None

        if self.cRayNode:
            self.cRayNode = None

        if self.cRay:
            self.cRay = None

        if self.lifter:
            self.lifter = None

        if self.cAggroNodePath:
            self.cTrav.removeCollider(self.cAggroNodePath)
            self.cAggroNodePath.removeNode()
            self.cAggroNodePath = None

        if self.cAggroNode:
            self.cAggroNode = None

        if self.cAggro:
            self.cAggro = None

        if self.handler:
            self.handler = None

        if self.cNoticeNodePath:
            self.cTrav.removeCollider(self.cNoticeNodePath)
            self.cNoticeNodePath.removeNode()
            self.cNoticeNodePath = None

        if self.cNoticeNode:
            self.cNoticeNode = None

        if self.cNotice:
            self.cNotice = None



    def setIsAlarmed(self, isAlarmed, aggroRadius = 0.0):
        self.isAlarmed = isAlarmed
        self.alarmedAggroRadious = aggroRadius
        if self.cAggro:
            if self.isAlarmed:
                self.cAggro.setRadius(aggroRadius)
            else:
                self.cAggro.setRadius(self.getInstantAggroSphereSize())



    def getIsAlarmed(self):
        return self.isAlarmed


    def sendRequestClientAggro(self):
        self.sendUpdate('requestClientAggro', [])


    def handleEnterProximity(self, collEntry):
        if self.nearCallbacks:
            for currCallback in self.nearCallbacks:
                currCallback[0](*currCallback[1])

            return None

        DistributedBattleAvatar.DistributedBattleAvatar.handleEnterProximity(self, collEntry)


    def _handleEnterAggroSphere(self, collEntry):
        if localAvatar.getSiegeTeam():
            return None

        if localAvatar.getGameState() in ('BenchRepair', 'PotionCrafting', 'Fishing'):
            return None

        if not self.isBattleable():
            return None

        skillEffects = self.getSkillEffects()
        if WeaponGlobals.C_SPAWN in skillEffects:
            return None

        playerLevel = localAvatar.getLevel()
        if playerLevel <= EnemyGlobals.NEWBIE_AGGRO_LEVEL and not (self.isAlarmed):
            return None

        if base.cr.gameStatManager.aggroModelIndex == 1:
            self.delayedCallForHelp()

        self.sendRequestClientAggro()


    def delayedCallForHelp(self):
        taskMgr.doMethodLater(3.0, self.callForHelp, self.uniqueName('delayHelpCall'))


    def localAttackedMe(self):
        if self.respondedToLocalAttack:
            return None
        else:
            self.respondedToLocalAttack = 1
            self.delayedCallForHelp()


    def callForHelp(self, task = None):
        messenger.send('helpMeAggroThisPunk', [
            self.doId])
        if task:
            return task.done



    def _handleAggroHelpRequested(self, allyDoId):
        if self.doId == allyDoId or self.respondedToLocalAttack:
            return None

        ally = base.cr.doId2do.get(allyDoId)
        if ally:
            allyDistance = self.getDistance(ally)
            if allyDistance < EnemyGlobals.CALL_FOR_HELP_DISTANCE and self.getTeam() == ally.getTeam():
                self.sendRequestClientAggro()




    def _handleEnterNoticeSphere(self, collEntry):
        otherCollNode = collEntry.getFromNodePath()
        myCollNode = collEntry.getIntoNodePath()
        if not base.config.GetBool('want-npc-notice', 1):
            return None

        if localAvatar.isInvisible():
            return None

        if localAvatar.creatureTransformation and self.avatarType.isA(AvatarTypes.LandCreature):
            return None

        skillEffects = self.getSkillEffects()
        if WeaponGlobals.C_SPAWN in skillEffects:
            return None

        if self.isInInvasion():
            return None

        self.firstNoticeLocalAvatar()
        self.noticeLocalAvatar()


    def shouldNotice(self):
        return localAvatar.getGameState() not in [
            'NPCInteract',
            'Emote']


    def firstNoticeLocalAvatar(self):
        if not self.playNoticeAnims():
            return None

        self.hasTurnedToNotice = 0
        self.localAvatarHasBeenNoticed = 1


    def abortNotice(self):
        if self.noticeIval:
            self.noticeFlag = 1
            self.noticeIval.pause()
            self.noticeFlag = 0
            self.doneThreat = 0

        self.noticeIval = None
        self.localAvatarHasBeenNoticed = 0


    def endNotice(self):
        self.doneThreat = 0
        self.localAvatarHasBeenNoticed = 0


    def stateOkayForNotice(self):
        if self.getGameState() in [
            'LandRoam']:
            return 1

        return 0


    def noticeLocalAvatar(self):
        if self.isInInvasion() or self.getGameState() in [
            'Emote']:
            return None

        if not self.shouldNotice() and self.isMovingDontNotice or self.noticeFlag:
            return None

        if self.getDistance(localAvatar) > self.getEffectiveNoticeDistance() + 2.0 or not self.stateOkayForNotice():
            if self.gameFSM:
                pass
            1
            self.endNotice()
            return None

        heading = self.getHpr()
        if base.localAvatar.getGameState() == 'NPCInteract':
            if hasattr(self, 'dialogProcessMaster'):
                pass
            if not (self.dialogProcessMaster):
                self.headsUp(base.camera)
            else:
                self.headsUp(localAvatar)
        if self.needNoticeGroundTracking:
            self.trackTerrain()

        noticeHeading = self.getHpr()
        self.setHpr(heading)
        angle = PythonUtil.fitDestAngle2Src(heading[0], noticeHeading[0])
        if self.needNoticeGroundTracking:
            newHpr = Vec3(angle, noticeHeading[1], noticeHeading[2])
        else:
            newHpr = Vec3(angle, heading[1], heading[2])
        noticeDif = abs(angle - heading[0])
        turnAnim = self.getTurnAnim(noticeDif)
        if self.noticeIval:
            self.noticeFlag = 1
            self.noticeIval.finish()
            self.noticeFlag = 0
            self.noticeIval = None

        if self.getDistance(localAvatar) > self.getEffectiveNoticeDistance() + 2.0:
            if self.hasTurnedToNotice:
                self.endNotice()
            else:
                self.noticeIval = Sequence(Wait(self.noticeSpeed), Func(self.noticeLocalAvatar))
                self.noticeIval.start()
        elif abs(noticeDif) > 15.0 and self.shouldTurnToNotice:
            if turnAnim:
                self.noticeIval = Sequence(Func(self.startShuffle, turnAnim), LerpHprInterval(self, duration = self.noticeSpeed, hpr = newHpr), Func(self.midShuffle), Wait(self.noticeSpeed), Func(self.endShuffle), Func(self.noticeLocalAvatar))
                self.noticeIval.start()
            else:
                self.noticeIval = Sequence(LerpHprInterval(self, duration = self.noticeSpeed, hpr = newHpr), Wait(self.noticeSpeed), Func(self.noticeLocalAvatar))
                self.noticeIval.start()
            self.doNoticeFX()
            self.hasTurnedToNotice = 1
        elif abs(noticeDif) < 45.0:
            duration = self.presetNoticeAnimation()
            if duration == None or self.doneThreat == 1:
                duration = self.noticeSpeed

            self.noticeIval = Sequence(Func(self.startNoticeLoop), Func(self.playNoticeAnim), Wait(duration), Func(self.endNoticeLoop), Func(self.noticeLocalAvatar))
            self.noticeIval.start()
            self.doNoticeFX()



    def doNoticeFX(self):
        pass


    def presetNoticeAnimation(self):
        return 1.0


    def usableAnimInfo(self):
        if not hasattr(self, 'animInfo'):
            return None

        if self.altVisNode:
            return None

        return self.animInfo


    def getTurnAnim(self, noticeDif):
        turnAnim = 'walk'
        if self.usableAnimInfo():
            if noticeDif < 0:
                if len(self.animInfo['LandRoam']) + 1 >= PiratesGlobals.SPIN_LEFT_INDEX:
                    turnAnim = self.animInfo['LandRoam'][PiratesGlobals.SPIN_LEFT_INDEX][0]

            elif len(self.animInfo['LandRoam']) + 1 >= PiratesGlobals.SPIN_RIGHT_INDEX:
                turnAnim = self.animInfo['LandRoam'][PiratesGlobals.SPIN_RIGHT_INDEX][0]


        if turnAnim == 'idle':
            turnAnim = 'walk'

        return turnAnim


    def startShuffle(self, turnAnim):
        if self.playNoticeAnims():
            self.loop(turnAnim, blendDelay = 0.29999999999999999)
            self.motionFSM.motionAnimFSM.interruptSplash()



    def playNoticeAnims(self):
        if base.noticeSystemOn == 0:
            return 0

        if not (self.isMovingDontNotice):
            pass
        return self.motionFSM.motionAnimFSM.state == 'Idle'


    def midShuffle(self):
        if self.playNoticeAnims():
            if self.noticeIdle:
                self.loop(self.noticeIdle, blendDelay = 0.29999999999999999)
            else:
                self.loop('idle', blendDelay = 0.29999999999999999)



    def endShuffle(self):
        idleAnimInfo = self.animInfo['LandRoam'][PiratesGlobals.STAND_INDEX]
        if self.getCurrentAnim() == idleAnimInfo[0]:
            return None


        try:
            self.loop(idleAnimInfo[0], blendDelay = 0.29999999999999999, rate = idleAnimInfo[1])
        except TypeError:
            e = None
            self.notify.error('Invalid animation %s for %s|isInInvasion = %s|isGenerated = %s|mixer = %s' % (idleAnimInfo, `self`, self.isInInvasion(), self.isGenerated(), self._UsesAnimationMixer__mixer.__class__.__name__))



    def startNoticeLoop(self):
        pass


    def endNoticeLoop(self):
        pass


    def playNoticeAnim(self):
        if not self.doneThreat:
            self.doneThreat = 1



    def _hitFloorCallback(self, collEntry):
        self.floorNorm = collEntry.getInto().getNormal()
        self.disableFloorChecks()


    def canAggro(self):
        if self.aggroMode == EnemyGlobals.AGGRO_MODE_NEVER:
            return False

        return True


    def setupDebugCollisions(self):
        self.debugCSphere = CollisionSphere(0.0, 0.0, 0.0, 5)
        self.debugCSphere.setTangible(0)
        cSphereNode.addSolid(self.debugCSphere)
        self.debugCSphereNodePath = self.attachNewNode(cSphereNode)


    def cleanupDebugcollisions(self):
        if self.debugCSphereNodePath:
            self.debugCSphereNodePath.removeNode()
            del self.debugCSphereNodePath
            self.debugCSphereNodePath = None

        if self.debugCSphere:
            del self.debugCSphere
            self.debugCSphere = None



    def _handleEnterSphereTest(self, collEntry):
        otherCollNode = collEntry.getFromNodePath()
        myCollNode = collEntry.getIntoNodePath()
        print 'NPC colliding me %s other %s' % (str(myCollNode), str(otherCollNode))


    def _handleAgainSphereTest(self, collEntry):
        print 'NPC colliding'


    def _handleExitSphereTest(self, collEntry):
        print 'NPC colliding'


    def updateMyAnimState(self, forwardVel, rotationVel, lateralVel):
        self.motionFSM.motionAnimFSM.updateNPCAnimState(forwardVel, rotationVel, lateralVel)


    def setSkipLocalSmooth(self, skip):
        self.skipLocalSmooth = skip


    def getSkipLocalSmooth(self):
        return self.skipLocalSmooth


    def smoothPosition(self):
        if self.skipLocalSmooth:
            if self.getGameState() not in ('Injured', 'Dying', 'Healing'):
                self.updateMyAnimState(0, 0, 0)

            return None

        if not (self.invisibleMask & PiratesGlobals.INVIS_QUEST).isZero():
            return None

        if self.lowEnd:
            DistributedBattleAvatar.DistributedBattleAvatar.smoothPosition(self)
            return None

        cantMove = False
        if not self.canMove:
            cantMove = True

        parentObj = self.getParent()
        if cantMove == False:
            oldZ = self.getZ(parentObj)
            oldH = self.getH(parentObj)
            oldPos = self.getPos(parentObj)

        DistributedBattleAvatar.DistributedBattleAvatar.smoothPosition(self)
        if cantMove == False:
            if (self.getPos(parentObj) - oldPos).length() < 0.10000000000000001:
                self.motionFSM.motionAnimFSM.updateNPCAnimState(0, 0, 0)
                if base.config.GetBool('want-npc-notice', 1) and abs(self.getH() - oldH) < 0.10000000000000001:
                    if self.moveNoticeFlag and self.lastMovedTimeStamp and globalClock.getFrameTime() - self.lastMovedTimeStamp > 0.5 and not (self.localAvatarHasBeenNoticed):
                        self.isMovingDontNotice = 0
                        self.firstNoticeLocalAvatar()
                        self.noticeLocalAvatar()
                        self.moveNoticeFlag = 0

                else:
                    self.lastMovedTimeStamp = globalClock.getFrameTime()
                return None
            else:
                self.lastMovedTimeStamp = globalClock.getFrameTime()
                self.isMovingDontNotice = 1
                self.moveNoticeFlag = 1
                self.abortNotice()
            self.headingNode.reparentTo(parentObj)
            self.headingNode.setPos(oldPos)
            self.headingNode.setH(oldH)
            newPos = self.getPos(parentObj)
            if self.collisionMode & PiratesGlobals.COLL_MODE_FLOORS_CL:
                currZ = self.getZ()
                if abs(oldZ - currZ) <= 8:
                    self.setZ(parentObj, oldZ)
                else:
                    customReach = None
                    if currZ > oldZ:
                        customReach = self.getFloorRayReach() + (currZ - oldZ)
                        self.performQuickFloorCheck(customReach)

                self.headingNode.setZ(oldZ)

            if self.curAttackAnim:
                pass
            inAttack = self.curAttackAnim.isPlaying()
            if inAttack:
                self.setH(parentObj, oldH)

            if self.enableZPrint:
                print '%s:  new z is %s, old z is %s' % (self.doId, newPos[2], oldZ)

            headingNodePos = self.headingNode.getPos()
            xDiff = abs(newPos[0] - headingNodePos[0])
            yDiff = abs(newPos[1] - headingNodePos[1])
            diffChangeLimitF = 0.01
            diffChangeLimitH = 0.074999999999999997
            if self.getGameState() == 'Battle':
                diffChangeLimitH = 2.0

            if xDiff > diffChangeLimitF or yDiff > diffChangeLimitF:
                self.enableFloorChecks()
                if (xDiff > diffChangeLimitH or yDiff > diffChangeLimitH) and not inAttack:
                    self.headsUp(self.headingNode)
                    self.setH(self.getH() + 180)


            animTime = globalClock.getFrameTime()
            deltaTime = animTime - self.lastSmoothPosUpdateTime
            distMoved = self.headingNode.getDistance(self)
            if deltaTime <= 0:
                speed = 0
            else:
                speed = distMoved / deltaTime
            self.lastSmoothPosUpdateTime = animTime
            slideScale = 0.0
            if base.config.GetBool('npc-sidestep', 0) and distMoved > 0.0050000000000000001:
                moveVec = self.headingNode.getPos() - self.getPos(parentObj)
                self.headingNode.reparentTo(self)
                self.headingNode.setPos(0, 1, 0)
                self.headingNode.wrtReparentTo(parentObj)
                headVec = self.headingNode.getPos() - self.getPos(parentObj)
                moveAngle = headVec.relativeAngleRad(moveVec)
                cosVal = math.sin(moveAngle)
                slideScale = cosVal

            self.headingNode.reparentTo(hidden)
            if inAttack:
                hChange = 0.0
            else:
                hChange = self.getH(parentObj) - oldH
                if hChange and hChange < 0.10000000000000001 and hChange > -0.10000000000000001:
                    hChange = 0.0

            self.motionFSM.motionAnimFSM.updateNPCAnimState(speed, hChange, slideScale)

        if self.canCheckFloors == True and self.floorChecksEnabled == False:
            if self.getX() != self.lastFloorCheckedXYZ[0] and self.getY() != self.lastFloorCheckedXYZ[1] or self.getZ() != self.lastFloorCheckedXYZ[2]:
                self.performQuickFloorCheck()

        self.trackTerrain()


    def getSpawnTrack(self):
        if self.checkQuestObjMod(doMod = False) == 'hide':
            return None

        return DistributedBattleAvatar.DistributedBattleAvatar.getSpawnTrack(self)


    def setSpawnIn(self, timestamp):
        t = globalClockDelta.localElapsedTime(timestamp, bits = 32)
        if t < 10:
            ival = self.getSpawnTrack()
            if ival:
                ival.start()
                self.spawnIvals.append(ival)
            else:
                ival = self.getFadeInTrack()
                if ival:
                    ival.start()
                    self.spawnIvals.append(ival)

        else:
            ival = self.getFadeInTrack()
            if ival:
                ival.start()
                self.spawnIvals.append(ival)



    def startLookAroundTask(self):
        pass


    def stopLookAroundTask(self):
        pass


    def b_setChat(self, chatString, chatFlags):
        messenger.send('wakeup')
        self.setChatAbsolute(chatString, chatFlags)
        self.d_setChat(chatString, chatFlags)


    def d_setChat(self, chatString, chatFlags):
        self.sendUpdate('setChat', [
            chatString,
            chatFlags])


    def setChat(self, chatString, chatFlags):
        chatFlags &= ~(CFQuicktalker | CFPageButton | CFQuitButton)
        if chatFlags & CFThought:
            chatFlags &= ~(CFSpeech | CFTimeout)
        else:
            chatFlags |= CFSpeech | CFTimeout
        self.setChatAbsolute(chatString, chatFlags)


    def getAggroRadius(self):
        if base.cr.activeWorld:
            return base.cr.activeWorld.getAggroRadius()

        return 0


    def getAggroSphereSize(self):
        if not self.canAggro():
            return 0

        playerLevel = base.localAvatar.getLevel()
        enemyLevel = self.getLevel()
        levelDiff = max(1, abs(playerLevel - enemyLevel) - EnemyGlobals.AGGRO_RADIUS_LEVEL_BUFFER)
        searchDist = self.getAggroRadius() / max(1.0, levelDiff / EnemyGlobals.AGGRO_RADIUS_FALLOFF_RATE)
        return max(searchDist, EnemyGlobals.MIN_SEARCH_RADIUS)


    def getInstantAggroSphereSize(self):
        if self.aggroRadius == EnemyGlobals.USE_DEFAULT_AGGRO:
            return self.getEffectiveAggroRadius()

        return self.aggroRadius


    def swapFloorCollideMask(self, oldMask, newMask):
        if self.cRayNode:
            collideMask = self.cRayNode.getFromCollideMask()
            collideMask = collideMask & ~oldMask
            collideMask |= newMask
            self.cRayNode.setFromCollideMask(collideMask)



    def onShipWithLocalAv(self, sameShip):
        if sameShip:
            self.enableFloorChecks()
        else:
            self.disableFloorChecks()


    def handleShipArrive(self, ship):
        self.onShipWithLocalAv(localAvatar.ship is ship)
        self.swapFloorCollideMask(PiratesGlobals.FloorBitmask, PiratesGlobals.ShipFloorBitmask)


    def handleShipLeave(self, ship):
        self.swapFloorCollideMask(PiratesGlobals.ShipFloorBitmask, PiratesGlobals.FloorBitmask)


    def holdAnimProp(self, availProps):
        if self.isWeaponDrawn:
            return None

        if availProps == None or len(availProps) == 0:
            return None

        self.clearAnimProp()
        propPath = random.choice(availProps)
        propType = CustomAnims.PROP_TYPE_DYNAMIC
        if type(propPath) is types.ListType:
            propType = propPath[1]
            propPath = propPath[0]

        if propType == CustomAnims.PROP_TYPE_PERSIST:
            return None

        if 'gator_high' in propPath:
            prop = None
        else:
            prop = self.getProp(propPath)
        if prop and not prop.isEmpty():
            if self.getName() == 'Captain Barbossa':
                handNode = self.leftHandNode
            else:
                handNode = self.rightHandNode
            if handNode == None:
                self.notify.warning('could not find hand to place prop %s in' % propPath)
                return None

            prop.reparentTo(handNode)
            self.animProp = prop
            self.animPropType = propType
        else:
            self.notify.warning('could not load prop %s to be used with DistInteractiveProp' % propPath)


    def getProp(self, propPath):
        model = propCache.get(propPath)
        if model:
            return model.copyTo(NodePath())
        else:
            prop = loader.loadModel(propPath)
            motion_blur = prop.find('**/motion_blur')
            if not motion_blur.isEmpty():
                motion_blur.stash()

            prop.flattenStrong()
            propCache[propPath] = prop
            return prop.copyTo(NodePath())


    def clearAnimProp(self):
        if self.animProp:
            animPropName = self.animProp.getName()
            allPropNodes = self.findAllMatches('**/' + animPropName)
            for node in allPropNodes:
                node.removeNode()

            self.animProp = None



    def battleFX(self, effect):
        pass


    def setAggroMode(self, val):
        DistributedBattleAvatar.DistributedBattleAvatar.setAggroMode(self, val)
        if self.aggroMode == EnemyGlobals.AGGRO_MODE_DEFAULT or self.aggroMode == EnemyGlobals.AGGRO_MODE_CUSTOM:
            if self.cAggroNodePath:
                if self.cAggroNodePath.isStashed():
                    self.cAggroNodePath.unstash()


        elif self.cAggroNodePath:
            self.cAggroNodePath.stash()



    def resetAnimProp(self):
        allAnims = CustomAnims.INTERACT_ANIMS.get(self.animSet)
        if allAnims:
            allProps = allAnims.get('props')
            self.holdAnimProp(allProps)



    def motionFSMEnterState(self, state):
        if state == 'Idle':
            if self.animSet != 'default':
                allAnims = CustomAnims.INTERACT_ANIMS.get(self.animSet)
                if allAnims:
                    allProps = allAnims.get('props')
                    self.holdAnimProp(allProps)





    def motionFSMExitState(self, state):
        if state == 'Idle':
            self.clearAnimProp()



    def setAnimSet(self, animSet):
        self.animSet = animSet


    def setActorAnims(self, animSet, notice1, notice2, greet):
        self.animSet = animSet
        self.noticeAnim1 = notice1
        self.noticeAnim2 = notice2
        self.greetingAnim = greet
        if self.isGenerated():
            self.setupActorAnims()



    def requestAnimSet(self, animSet):
        if CustomAnims.INTERACT_ANIMS.has_key(animSet):
            idleAnim = CustomAnims.INTERACT_ANIMS.get(animSet).get('idles')[0]
            self.clearAnimProp()
            self.animSetSetup = False
            self.setupStyle()
            self.setupActorAnims()
            self.loop(idleAnim)



    def setCollisionMode(self, collisionMode):
        self.collisionMode = collisionMode
        self.updateCollisions()


    def setInitZ(self, z):
        self.initZ = z


    def playSkillMovie(self, skillId, ammoSkillId, skillResult, charge, targetId = 0, areaIdList = []):
        if self.currentTarget:
            self.headsUp(self.currentTarget)

        if targetId == 0 and self.currentTarget:
            targetId = self.currentTarget.doId

        DistributedBattleAvatar.DistributedBattleAvatar.playSkillMovie(self, skillId, ammoSkillId, skillResult, charge, targetId, areaIdList)


    def preprocessAttackAnim(self):
        if self.currentAttack[0] >= InventoryType.begin_WeaponSkillGrenade and self.currentAttack[0] < InventoryType.end_WeaponSkillGrenade:
            skillInfo = WeaponGlobals.getSkillAnimInfo(EnemySkills.EnemySkills.GRENADE_RELOAD)
            if not skillInfo:
                return None

            anim = skillInfo[WeaponGlobals.PLAYABLE_INDEX]
            reloadAnim = getattr(self.cr.combatAnims, anim)(self, EnemySkills.EnemySkills.GRENADE_RELOAD, 0, 0, None, 1)
            if reloadAnim:
                self.curAttackAnim = Sequence(self.curAttackAnim, reloadAnim)




    def checkWeaponSwitch(self, currentWeaponId, isWeaponDrawn):
        if isWeaponDrawn == self.isWeaponDrawn and currentWeaponId == self.currentWeaponId:
            self.setWalkForWeapon()

        DistributedBattleAvatar.DistributedBattleAvatar.checkWeaponSwitch(self, currentWeaponId, isWeaponDrawn)


    def getFloorRayReach(self):
        if self.ship:
            return 1000.0
        else:
            return 8.0


    def setShipId(self, shipId):
        DistributedBattleAvatar.DistributedBattleAvatar.setShipId(self, shipId)
        if self.lifter:
            self.lifter.setReach(self.getFloorRayReach())



    def setChatAbsolute(self, chatString, chatFlags, dialogue = None, interrupt = 1):
        if not hasattr(self, 'nametag'):
            self.notify.warning('setChatAbsolute: no nametag, been deleted, but still trying to say something')
            printStack()
            return None

        DistributedBattleAvatar.DistributedBattleAvatar.setChatAbsolute(self, chatString, chatFlags, dialogue, interrupt)


    def setGameState(self, gameState, timestamp = None, localArgs = [], localChange = 0):
        DistributedBattleAvatar.DistributedBattleAvatar.setGameState(self, gameState, timestamp, localArgs)
        if self.dropShadow:
            self.dropShadow.setPos(self, (0, 0, 0))



    def setInInvasion(self, value):
        DistributedBattleAvatar.DistributedBattleAvatar.setInInvasion(self, value)
        if value:
            taskMgr.doMethodLater(5, self.removeCollisions, self.uniqueName('removeCollisions'))
            self.enableReducedMixing()
            self.setClipPlane(base.farCull)
            self.setMonsterNameTag()
            if base.config.GetBool('want-invasion-npc-minimap', 1):
                self.destroyMinimapObject()




    def removeCollisions(self, task = None):
        if self.battleTubeNodePaths:
            for np in self.battleTubeNodePaths:
                np.node().setIntoCollideMask(np.node().getIntoCollideMask() & ~(PiratesGlobals.WallBitmask))




    def d_suggestResync(self, avId, timestampA, timestampB, serverTime, uncertainty):
        self.cr.timeManager.synchronize('suggested by %d' % avId)


    def setEfficiency(self, efficiency):
        if self.efficiency != efficiency:
            self.efficiency = efficiency
            if efficiency:
                self.enableReducedMixing()
            else:
                self.enableMixing()