def moveCameraToToon(self):
     camPInt = LerpPosInterval(camera,
                               duration=3.0,
                               pos=self.localAv.getPos(render) + (0, 15, 3),
                               startPos=(camera.getPos(render)),
                               blendType="easeInOut")
     camQInt = camera.quatInterval(3.0,
                                   hpr=Vec3(180, 0, 0),
                                   blendType="easeInOut")
     camPInt.start()
     camQInt.start()
Beispiel #2
0
class FuncElevatorAI(DistributedEntityAI):

    StateStopped = 0
    StateMoving = 1

    def __init__(self, air, dispatch):
        DistributedEntityAI.__init__(self, air, dispatch)
        self.startPoint = None
        self.endPoint = None
        self.moveTrack = None
        self.speed = 0.0
        
    def clearMoveTrack(self):
        if self.moveTrack:
            self.moveTrack.pause()
            self.moveTrack = None
        
    def setEntityState(self, state):
        DistributedEntityAI.setEntityState(self, state)
        
        self.clearMoveTrack()
        
        if state == self.StateMoving:
            
            if (not self.startPoint) or (not self.endPoint):
                print("FuncElevatorAI ERROR: Missing either start or end point")
                return
                
            startPos = self.startPoint.cEntity.getOrigin()
            endPos = self.endPoint.cEntity.getOrigin()
            dist = (endPos - startPos).length()
            duration = (dist * 16) / self.speed
            self.moveTrack = LerpPosInterval(self, duration, endPos, startPos)
            self.moveTrack.start()
            
            self.dispatchOutput("OnStartMove")
            
    def Move(self):
        self.b_setEntityState(self.StateMoving)
            
    def loadEntityValues(self):
        startPointName = self.getEntityValue("startPoint")
        endPointName = self.getEntityValue("endPoint")
        self.startPoint = self.bspLoader.getPyEntityByTargetName(startPointName)
        self.endPoint = self.bspLoader.getPyEntityByTargetName(endPointName)
        self.speed = self.getEntityValueFloat("speed")
        
    def announceGenerate(self):
        DistributedEntityAI.announceGenerate(self)
        self.startPosHprBroadcast()
        render.ls()
 def moveCameraToTop(self):
     camera.setPos(camera.getPos(self.localAv))
     camera.reparentTo(self.localAv)
     oldPos = camera.getPos()
     camera.setPos(self.raceCamPos)
     oldHpr = camera.getHpr()
     camera.lookAt(self.localAv.getPart('head'))
     newHpr = camera.getHpr()
     camera.setHpr(oldHpr)
     camera.setPos(oldPos)
     camPInt = LerpPosInterval(camera,
                               duration=3.0,
                               pos=self.raceCamPos,
                               startPos=oldPos,
                               blendType="easeInOut")
     camQInt = camera.quatInterval(3.0, hpr=newHpr, blendType="easeInOut")
     camPInt.start()
     camQInt.start()
Beispiel #4
0
class Weapon(DirectObject):
    def __init__(self, _main, _name, _fireRate, _dmg=20,_mountSlot=0, weaponType="Pistol"):
        self.main = _main
        self.name = _name
        self.fireRate = _fireRate
        self.dmg = _dmg
        self.weaponType = weaponType
        self.mountSlot = _mountSlot

        self.muzzleFlash = loader.loadModel("muzzleflash")
        if weaponType == "Pistol":
            self.style = "OneHand"
            self.model = loader.loadModel("Pistol")
            self.muzzleFlash.setZ(0.65)
            self.muzzleFlash.setX(-0.04)
            self.muzzleFlash.setScale(0.25)
            self.muzzleFlash.find('**/+SequenceNode').node().setFrameRate(20)
        else:
            self.style = "TwoHand"
            self.model = loader.loadModel("MG")
            self.muzzleFlash.setZ(0.65)
            self.muzzleFlash.setX(0.08)
            self.muzzleFlash.setScale(0.3)
            self.muzzleFlash.find('**/+SequenceNode').node().setFrameRate(20)
        self.model.setY(2)
        self.muzzleFlash.reparentTo(self.model)
        self.muzzleFlash.find('**/+SequenceNode').node().stop()
        self.muzzleFlash.hide()

        # Load bullet model
        self.bullet = loader.loadModel("Bullet")
        self.bullet.setP(-90)
        self.bullet.setH(180)
        #self.bullet.setPos(0, 0.5, 0)

        # Control
        self.isFiring = False

        # Collision Stuff
        self.wepRay = None
        # Make weapon ray
        self.setupRay()
        self.model.show()

    def setAmmo(self):
        pass

    def setupRay(self):
        self.shootTraverser = CollisionTraverser()
        self.shootingQH = CollisionHandlerQueue()
        #self.shootingEH = CollisionHandlerEvent()
        #self.shootingEH.addInPattern('into-%in')
        # Create a collision Node
        shootNode = CollisionNode('WeaponRay')
        # set the nodes collision bitmask
        shootNode.setFromCollideMask(BitMask32.bit(1))
        # create a collision segment (ray like)
        self.shootRay = CollisionSegment()
        shootNode.addSolid(self.shootRay)
        #self.pickerNP = self.main.player.model.attachNewNode(pickerNode)
        self.shootNP = render.attachNewNode(shootNode)
        #self.shootTraverser.addCollider(self.shootNP, self.shootingEH)
        self.shootTraverser.addCollider(self.shootNP, self.shootingQH)
        #self.shootNP.show()

    def doFire(self, _toPos=(0, 0, 0)):
        self.isFiring = True

        if self.weaponType == "Pistol":
            self.muzzleFlash.find('**/+SequenceNode').node().play(0, 1)
        else:
            self.muzzleFlash.find('**/+SequenceNode').node().loop(True)
        self.muzzleFlash.show()

        # For some reason the mouse ray end up at posZ -1 (which causes a problem when we make the enemy spheres smaller in radius)
        # so here for now.. ill make a quick fix.
        adjustedZ = (_toPos[0], _toPos[1], 0)

        self.shootRay.setPointA(self.main.player.model.getPos())
        self.shootRay.setPointB(adjustedZ)

        fromPos = self.main.player.model.getPos() #self.model.getPos()
        #self.setProjectile(fromPos, adjustedZ)#_toPos)

        self.shootTraverser.traverse(self.main.enemyParent)
        if self.shootingQH.getNumEntries() > 0:
            self.shootingQH.sortEntries()
            enemyCol = self.shootingQH.getEntry(0).getIntoNodePath().node().getName()
            base.messenger.send("into-" + enemyCol, [self.dmg])

    def stopFire(self):
        if self.weaponType == "Pistol" and \
               self.muzzleFlash.find('**/+SequenceNode').node().isPlaying():
            taskMgr.add(self.waitForFrame, "waitForFrame")
            return
        self.muzzleFlash.find('**/+SequenceNode').node().stop()
        self.muzzleFlash.hide()

    def waitForFrame(self, task):
        if self.muzzleFlash.find('**/+SequenceNode').node().isPlaying():
            return task.cont
        self.muzzleFlash.find('**/+SequenceNode').node().stop()
        self.muzzleFlash.hide()

    def reload(self):
        pass

    def setProjectile(self, _from, _to):
        self.bullet.reparentTo(render)#self.model)
        # setup the projectile interval
        #self.bulletProjectile = ProjectileInterval(self.bullet,
        #                                startPos = Point3(_from),
        #                                duration = 1,
        #                                endPos = Point3(_to))
        #self.bulletProjectile = self.bullet.posInterval(1.0, Point3(_to), startPos=Point3(_from))
        #self.bulletProjectile = LerpPosInterval(self.bullet, 2.0, _to, _from)
        print "POSITIONS:"
        print _to
        print _from
        frm = render.getPos(self.main.player.model)
        print frm

        self.bulletProjectile = LerpPosInterval(self.bullet, 1.0, _to, _from)
        self.bulletProjectile.start()
Beispiel #5
0
class ParticleAttack(Attack):
    notify = directNotify.newCategory('ParticleAttack')
    attack = 'particleattack'
    particleIvalDur = 1
    shooterDistance = 50

    def __init__(self, attacksClass, suit):
        Attack.__init__(self, attacksClass, suit)
        self.particles = []
        self.handObj = None
        self.shootOutCollNP = None
        self.particleSound = None
        self.particleMoveIval = None
        self.targetX = None
        self.targetY = None
        self.targetZ = None
        return

    def handleWeaponTouch(self):
        pass

    def handleCollision(self, entry):
        if self.suit:
            self.suit.sendUpdate('toonHitByWeapon', [self.getAttackId(self.attack), base.localAvatar.doId])
            base.localAvatar.b_handleSuitAttack(self.getAttackId(self.attack), self.suit.doId)

    def doAttack(self, particlePaths, track_name, particleCollId, animation_name, delayUntilRelease, animationSpeed = 1, handObjPath = None, handObjParent = None, startRightAway = True, ts = 0):
        for path in particlePaths:
            particle = ParticleLoader.loadParticleEffect(path)
            self.particles.append(particle)

        sphere = CollisionSphere(0, 0, 0, 1)
        sphere.setTangible(0)
        node = CollisionNode(particleCollId)
        node.addSolid(sphere)
        node.setCollideMask(CIGlobals.WallBitmask)
        self.targetX = self.attacksClass.target.getX(render)
        self.targetY = self.attacksClass.target.getY(render)
        self.targetZ = self.attacksClass.target.getZ(render)
        if len(self.particles) == 1:
            self.shootOutCollNP = self.particles[0].attachNewNode(node)
        else:
            self.shootOutCollNP = self.suit.attachNewNode(node)
        if handObjPath and handObjParent:
            self.handObj = loader.loadModel(handObjPath)
            self.handObj.reparentTo(handObjParent)
        self.suit.setPlayRate(animationSpeed, animation_name)
        self.suit.play(animation_name)
        if hasattr(self.suit, 'uniqueName'):
            track_name = self.suit.uniqueName(track_name)
            particleCollId = self.suit.uniqueName(particleCollId)
        self.suitTrack = Sequence(name=track_name)
        self.suitTrack.append(Wait(delayUntilRelease))
        self.suitTrack.append(Func(self.releaseAttack))
        self.suitTrack.append(Wait(self.particleIvalDur))
        self.suitTrack.setDoneEvent(self.suitTrack.getName())
        self.acceptOnce(self.suitTrack.getDoneEvent(), self.finishedAttack)
        if startRightAway:
            self.suitTrack.start(ts)

    def releaseAttack(self, releaseFromJoint, onlyMoveColl = True, blendType = 'noBlend'):
        startNP = releaseFromJoint.attachNewNode('startNP')
        if None not in [self.targetX, self.targetY, self.targetZ]:
            startNP.lookAt(render, self.targetX, self.targetY, self.targetZ + 2)
            pathNP = NodePath('path')
            pathNP.reparentTo(startNP)
            pathNP.setScale(render, 1.0)
            pathNP.setPos(0, self.shooterDistance, 0)
            for particle in self.particles:
                if not onlyMoveColl:
                    particle.start(render)
                else:
                    particle.start(self.suit)
                particle.lookAt(pathNP)
                if self.attack == 'razzledazzle':
                    particle.setP(particle, 90)

            if onlyMoveColl:
                target = self.shootOutCollNP
                target.wrtReparentTo(render)
            else:
                target = self.particles[0]
            self.particleMoveIval = LerpPosInterval(target, duration=self.particleIvalDur, pos=pathNP.getPos(render), startPos=startNP.getPos(render), blendType=blendType)
            self.particleMoveIval.start()
            self.acceptOnce('enter' + self.shootOutCollNP.node().getName(), self.handleCollision)
            pathNP.removeNode()
            startNP.removeNode()
            del pathNP
            del startNP
        self.playParticleSound()
        return

    def playParticleSound(self):
        if self.particleSound:
            base.audio3d.attachSoundToObject(self.particleSound, self.suit)
            base.playSfx(self.particleSound)

    def cleanup(self):
        Attack.cleanup(self)
        self.targetX = None
        self.targetY = None
        self.targetZ = None
        if self.particles:
            for particle in self.particles:
                particle.cleanup()

        self.particles = None
        if self.handObj:
            self.handObj.removeNode()
            self.handObj = None
        if self.shootOutCollNP:
            self.ignore('enter' + self.shootOutCollNP.node().getName())
            self.shootOutCollNP.removeNode()
            self.shootOutCollNP = None
        if self.particleMoveIval:
            self.particleMoveIval.pause()
            self.particleMoveIval = None
        self.particleSound = None
        self.particleIvalDur = None
        return
Beispiel #6
0
class HangUpAttack(Attack):
    notify = directNotify.newCategory('HangUpAttack')
    attack = 'hangup'

    def __init__(self, attacksClass, suit):
        Attack.__init__(self, attacksClass, suit)
        self.phone = None
        self.receiver = None
        self.collNP = None
        self.phoneSfx = None
        self.hangupSfx = None
        self.shootIval = None
        self.cord = None
        self.receiverOutCord = None
        self.phoneOutCord = None
        return

    def loadAttack(self):
        self.phone = loader.loadModel('phase_3.5/models/props/phone.bam')
        self.phone.setHpr(0, 0, 180)
        if self.suit.type == 'B':
            self.phone.setPos(0.7, 0.15, 0)
        elif self.suit.type == 'C':
            self.phone.setPos(0.25, 0, 0)
        self.receiver = loader.loadModel('phase_3.5/models/props/receiver.bam')
        self.receiver.reparentTo(self.phone)
        self.cord = Rope()
        self.cord.ropeNode.setUseVertexColor(1)
        self.cord.ropeNode.setUseVertexThickness(1)
        self.cord.setup(3, ({'node': self.phone,
          'point': (0.8, 0, 0.2),
          'color': (0, 0, 0, 1),
          'thickness': 1000}, {'node': self.phone,
          'point': (2, 0, 0),
          'color': (0, 0, 0, 1),
          'thickness': 1000}, {'node': self.receiver,
          'point': (1.1, 0.25, 0.5),
          'color': (0, 0, 0, 1),
          'thickness': 1000}), [])
        self.cord.setH(180)
        self.phoneSfx = base.audio3d.loadSfx('phase_3.5/audio/sfx/SA_hangup.mp3')
        base.audio3d.attachSoundToObject(self.phoneSfx, self.phone)
        self.hangupSfx = base.audio3d.loadSfx('phase_3.5/audio/sfx/SA_hangup_place_down.mp3')
        base.audio3d.attachSoundToObject(self.hangupSfx, self.phone)
        collSphere = CollisionSphere(0, 0, 0, 2)
        collSphere.setTangible(0)
        collNode = CollisionNode('phone_shootout')
        collNode.addSolid(collSphere)
        collNode.setCollideMask(CIGlobals.WallBitmask)
        self.collNP = self.phone.attachNewNode(collNode)

    def doAttack(self, ts = 0):
        self.loadAttack()
        if hasattr(self.suit, 'uniqueName'):
            name = self.suit.uniqueName('doHangupAttack')
        else:
            name = 'doHangupAttack'
        if self.suit.type == 'A':
            delay2playSound = 1.0
            delayAfterSoundToPlaceDownReceiver = 0.2
            delayAfterShootToIgnoreCollisions = 1.0
            delay2PickUpReceiver = 1.0
            receiverInHandPos = Point3(-0.5, 0.5, -1)
        elif self.suit.type == 'B':
            delay2playSound = 1.5
            delayAfterSoundToPlaceDownReceiver = 0.7
            delayAfterShootToIgnoreCollisions = 1.0
            delay2PickUpReceiver = 1.5
            receiverInHandPos = Point3(-0.3, 0.5, -0.8)
        elif self.suit.type == 'C':
            delay2playSound = 1.0
            delayAfterSoundToPlaceDownReceiver = 1.15
            delayAfterShootToIgnoreCollisions = 1.0
            delay2PickUpReceiver = 1.5
            receiverInHandPos = Point3(-0.3, 0.5, -0.8)
        self.suitTrack = Parallel(name=name)
        self.suitTrack.append(ActorInterval(self.suit, 'phone'))
        self.suitTrack.append(Sequence(Wait(delay2playSound), SoundInterval(self.phoneSfx, duration=2.1), Wait(delayAfterSoundToPlaceDownReceiver), Func(self.receiver.setPos, 0, 0, 0), Func(self.receiver.setH, 0.0), Func(self.receiver.reparentTo, self.phone), Func(self.acceptOnce, 'enter' + self.collNP.node().getName(), self.handleCollision), Func(self.shootOut), Parallel(SoundInterval(self.hangupSfx), Sequence(Wait(delayAfterShootToIgnoreCollisions), Func(self.ignore, 'enter' + self.collNP.node().getName())))))
        self.suitTrack.append(Sequence(Func(self.phone.reparentTo, self.suit.find('**/joint_Lhold')), Func(self.cord.reparentTo, render), Wait(delay2PickUpReceiver), Func(self.receiver.reparentTo, self.suit.find('**/joint_Rhold')), Func(self.receiver.setPos, receiverInHandPos), Func(self.receiver.setH, 270.0)))
        self.suitTrack.setDoneEvent(self.suitTrack.getName())
        self.acceptOnce(self.suitTrack.getDoneEvent(), self.finishedAttack)
        self.suitTrack.delayDelete = DelayDelete.DelayDelete(self.suit, name)
        self.suitTrack.start(ts)

    def handleCollision(self, entry):
        if self.suit:
            self.suit.sendUpdate('toonHitByWeapon', [self.getAttackId(self.attack), base.localAvatar.doId])
            base.localAvatar.b_handleSuitAttack(self.getAttackId(self.attack), self.suit.doId)

    def shootOut(self):
        pathNode = NodePath('path')
        pathNode.reparentTo(self.suit)
        pathNode.setPos(0, 50, self.phone.getZ(self.suit))
        self.collNP.reparentTo(render)
        self.shootIval = LerpPosInterval(self.collNP, duration=1.0, pos=pathNode.getPos(render), startPos=self.phone.getPos(render))
        self.shootIval.start()
        pathNode.removeNode()
        del pathNode

    def cleanup(self):
        Attack.cleanup(self)
        if self.shootIval:
            self.shootIval.pause()
            self.shootIval = None
        if self.cord:
            self.cord.removeNode()
            self.cord = None
        if self.phone:
            self.phone.removeNode()
            self.phone = None
        if self.receiver:
            self.receiver.removeNode()
            self.receiver = None
        if self.collNP:
            self.collNP.node().clearSolids()
            self.collNP.removeNode()
            self.collNP = None
        if self.phoneSfx:
            self.phoneSfx.stop()
            self.phoneSfx = None
        return
Beispiel #7
0
class ThrowAttack(Attack):
    notify = directNotify.newCategory('ThrowAttack')
    attack = 'throw'

    def __init__(self, attacksClass, suit):
        Attack.__init__(self, attacksClass, suit)
        self.weapon_state = None
        self.weapon = None
        self.wss = None
        self.wsnp = None
        self.suitTrack = None
        self.weaponSfx = None
        self.throwTrajectory = None
        self.targetX = None
        self.targetY = None
        self.targetZ = None
        self.startNP = None
        return

    def handleWeaponCollision(self, entry):
        if self.suit:
            self.suit.sendUpdate('toonHitByWeapon', [self.getAttackId(self.attack), base.localAvatar.doId])
            base.localAvatar.b_handleSuitAttack(self.getAttackId(self.attack), self.suit.doId)
            self.suit.b_handleWeaponTouch()

    def doAttack(self, weapon_path, weapon_scale, track_name, animation_name, collsphere_radius, weapon_coll_id, weapon_h = 0, weapon_p = 0, weapon_r = 0, weapon_x = 0, weapon_y = 0, weapon_z = 0, ts = 0):
        self.weapon_state = 'start'
        if hasattr(self.suit, 'uniqueName'):
            track_name = self.suit.uniqueName(track_name)
            weapon_coll_id = self.suit.uniqueName(weapon_coll_id)
        self.weapon = loader.loadModel(weapon_path)
        self.weapon.setScale(weapon_scale)
        self.weapon.setHpr(weapon_h, weapon_p, weapon_r)
        self.weapon.setPos(weapon_x, weapon_y, weapon_z)
        self.wss = CollisionSphere(0, 0, 0, collsphere_radius)
        self.wss.setTangible(0)
        self.targetX = self.attacksClass.target.getX(render)
        self.targetY = self.attacksClass.target.getY(render)
        self.targetZ = self.attacksClass.target.getZ(render)
        self.suitTrack = Sequence(name=track_name)
        if self.attack not in ('glowerpower',):
            self.weapon.reparentTo(self.suit.find('**/joint_Rhold'))
            self.suitTrack.append(Wait(1.2))
            self.suitTrack.append(Func(self.suit.setPlayRate, 1.0, animation_name))
            if self.suit.type == 'C':
                self.suitTrack.append(Wait(0))
            else:
                self.suitTrack.append(Wait(0.7))
            self.suit.setPlayRate(2.0, animation_name)
            self.suitTrack.append(Func(self.throwObject))
            self.suitTrack.append(Wait(1.0))
            self.suitTrack.append(Func(self.delWeapon))
        else:
            self.suitTrack.append(Wait(1))
            self.suitTrack.append(Func(self.throwObject))
            self.suitTrack.append(Wait(0.5))
            self.suitTrack.append(Func(self.delWeapon))
        self.suit.play(animation_name)
        wsnode = CollisionNode(weapon_coll_id)
        wsnode.addSolid(self.wss)
        wsnode.setCollideMask(CIGlobals.WallBitmask)
        self.wsnp = self.weapon.attachNewNode(wsnode)
        self.suitTrack.setDoneEvent(self.suitTrack.getName())
        self.acceptOnce(self.suitTrack.getDoneEvent(), self.finishedAttack)
        self.suitTrack.delayDelete = DelayDelete.DelayDelete(self.suit, track_name)
        self.suitTrack.start(ts)

    def playWeaponSound(self):
        if self.weapon and self.weaponSfx:
            base.audio3d.attachSoundToObject(self.weaponSfx, self.suit)
            self.weaponSfx.play()

    def throwObject(self, projectile = True):
        if not self.weapon:
            return
        self.acceptOnce('enter' + self.wsnp.node().getName(), self.handleWeaponCollision)
        self.playWeaponSound()
        if self.weapon:
            self.weapon.wrtReparentTo(render)
            self.weapon.setHpr(Vec3(0, 0, 0))
        if self.attack not in ('glowerpower',):
            parent = self.suit.find('**/joint_Rhold')
        else:
            parent = self.suit.find('**/joint_head')
        startNP = parent.attachNewNode('startNp')
        startNP.lookAt(render, self.targetX, self.targetY, self.targetZ)
        pathNP = NodePath('throwPath')
        pathNP.reparentTo(startNP)
        pathNP.setScale(render, 1.0)
        pathNP.setPos(0, 50, 0)
        if self.attack in ('clipontie', 'powertie', 'halfwindsor'):
            self.weapon.setHpr(pathNP.getHpr(render))
        if projectile == True:
            self.throwTrajectory = ProjectileInterval(self.weapon, startPos=self.suit.find('**/joint_Rhold').getPos(render), endPos=pathNP.getPos(render), gravityMult=0.7, duration=1.0)
        else:
            self.weapon.setH(pathNP.getH(render))
            self.throwTrajectory = LerpPosInterval(self.weapon, duration=0.5, pos=pathNP.getPos(render), startPos=startNP.getPos(render) + (0, 3, 0))
        self.throwTrajectory.start()
        self.weapon_state = 'released'
        startNP.removeNode()
        del startNP
        pathNP.removeNode()
        del pathNP

    def interruptAttack(self):
        if self.throwTrajectory:
            if self.throwTrajectory.isStopped():
                self.delWeapon()

    def handleWeaponTouch(self):
        if self.throwTrajectory:
            self.throwTrajectory.pause()
            self.throwTrajectory = None
        self.delWeapon()
        return

    def delWeapon(self):
        if self.weapon:
            self.weapon.removeNode()
            self.weapon = None
        return

    def cleanup(self):
        Attack.cleanup(self)
        self.targetX = None
        self.targetY = None
        self.targetZ = None
        self.weapon_state = None
        if self.weaponSfx:
            self.weaponSfx.stop()
            self.weaponSfx = None
        if self.throwTrajectory:
            self.throwTrajectory.pause()
            self.throwTrajectory = None
        self.delWeapon()
        self.wss = None
        if self.wsnp:
            self.wsnp.node().clearSolids()
            self.wsnp.removeNode()
            self.wsnp = None
        return
class DistributedEagleSuit(DistributedSuit):
    notify = directNotify.newCategory('DistributedEagleSuit')

    def __init__(self, cr):
        DistributedSuit.__init__(self, cr)
        self.eagleCry = base.audio3d.loadSfx(
            'phase_5/audio/sfx/tt_s_ara_cfg_eagleCry.ogg')
        base.audio3d.attachSoundToObject(self.eagleCry, self)
        self.fallWhistle = base.audio3d.loadSfx(
            'phase_5/audio/sfx/incoming_whistleALT.ogg')
        base.audio3d.attachSoundToObject(self.fallWhistle, self)
        self.explode = base.audio3d.loadSfx(
            'phase_3.5/audio/sfx/ENC_cogfall_apart.ogg')
        base.audio3d.attachSoundToObject(self.explode, self)
        self.eventSphereNodePath = None
        self.fallingPropeller = None
        self.fallingPropProjectile = None
        self.mg = None
        self.flySpeed = 0.0
        return

    def enterNeutral(self, ts=0):
        self.show()
        self.timestampAnimTrack = Sequence(Wait(ts),
                                           Func(self.loop, 'flyNeutral'))
        self.timestampAnimTrack.start()

    def makeStateDict(self):
        self.suitFSM.addState(
            State('eagleFly', self.enterEagleFly, self.exitEagleFly))
        self.suitFSM.addState(
            State('eagleFall', self.enterEagleFall, self.exitEagleFall))
        self.stateIndex2suitState = {
            0: self.suitFSM.getStateNamed('off'),
            1: self.suitFSM.getStateNamed('walking'),
            2: self.suitFSM.getStateNamed('flyingDown'),
            3: self.suitFSM.getStateNamed('flyingUp'),
            4: self.suitFSM.getStateNamed('lured'),
            5: self.suitFSM.getStateNamed('eagleFly'),
            6: self.suitFSM.getStateNamed('eagleFall')
        }
        self.suitState2stateIndex = {}
        for stateId, state in self.stateIndex2suitState.items():
            self.suitState2stateIndex[state.getName()] = stateId

    def setFlySpeed(self, value):
        self.flySpeed = value

    def getFlySpeed(self):
        return self.flySpeed

    def enterEagleFly(self, startIndex, endIndex, ts=0.0):
        durationFactor = self.getFlySpeed()
        if startIndex > -1:
            startPos = EGG.EAGLE_FLY_POINTS[startIndex]
        else:
            startPos = self.getPos(render)
        endPos = EGG.EAGLE_FLY_POINTS[endIndex]
        if self.moveIval:
            self.moveIval.pause()
            self.moveIval = None
        self.moveIval = NPCWalkInterval(self,
                                        endPos,
                                        durationFactor=durationFactor,
                                        startPos=startPos,
                                        fluid=1)
        self.moveIval.start(ts)
        return

    def exitEagleFly(self):
        if self.moveIval:
            self.moveIval.pause()
            self.moveIval = None
        return

    def enterEagleFall(self, startIndex, endIndex, ts=0.0):
        self.moveIval = LerpPosInterval(self,
                                        duration=4.0,
                                        pos=self.getPos(render) - (0, 0, 75),
                                        startPos=self.getPos(render),
                                        blendType='easeIn')
        self.moveIval.start(ts)

    def exitEagleFall(self):
        if self.moveIval:
            self.moveIval.finish()
            self.moveIval = None
        return

    def fallAndExplode(self):
        self.cleanupPropeller()
        self.fallingPropeller = Actor(
            'phase_4/models/props/propeller-mod.bam',
            {'chan': 'phase_4/models/props/propeller-chan.bam'})
        self.fallingPropeller.reparentTo(render)
        self.fallingPropeller.loop('chan', fromFrame=0, toFrame=3)
        parentNode = self.attachNewNode('fallingPropParentNode')
        h = random.randint(0, 359)
        parentNode.setH(h)
        dummyNode = parentNode.attachNewNode('dummyNode')
        dummyNode.setPos(0, 10, -50)
        self.fallingPropProjectile = FlightProjectileInterval(
            self.fallingPropeller,
            startPos=self.find('**/joint_head').getPos(render),
            endPos=dummyNode.getPos(render),
            duration=5.0,
            gravityMult=0.25)
        self.fallingPropProjectile.start()
        dummyNode.removeNode()
        del dummyNode
        parentNode.removeNode()
        del parentNode
        self.updateHealthBar(0)
        self.ignoreHit()
        base.playSfx(self.fallWhistle, node=self)
        taskMgr.doMethodLater(4.0, self.doExplodeSound,
                              self.uniqueName('DEagleSuit-doExplodeSound'))

    def doExplodeSound(self, task):
        base.playSfx(self.explode, node=self)
        return Task.done

    def __initializeEventSphere(self):
        sphere = CollisionSphere(0, 0, 0, 2)
        sphere.setTangible(0)
        node = CollisionNode(self.uniqueName('DEagleSuit-eventSphere'))
        node.addSolid(sphere)
        node.setCollideMask(CIGlobals.WallBitmask)
        np = self.attachNewNode(node)
        np.setSz(2.5)
        np.setZ(5.5)
        self.eventSphereNodePath = np

    def removeEventSphere(self):
        if self.eventSphereNodePath:
            self.eventSphereNodePath.removeNode()
            self.eventSphereNodePath = None
        return

    def acceptHit(self):
        self.acceptOnce('enter' + self.eventSphereNodePath.node().getName(),
                        self.__handleHit)

    def ignoreHit(self):
        self.ignore('enter' + self.eventSphereNodePath.node().getName())

    def __handleHit(self, entry):
        messenger.send(EGG.EAGLE_HIT_EVENT, [self.doId])

    def setSuit(self, arg, variant):
        DistributedSuit.setSuit(self, arg, 3)
        self.deleteShadow()
        self.disableBodyCollisions()
        self.disableRay()
        self.__initializeEventSphere()
        self.show()
        self.setAnimState('flyNeutral')

    def __doEagleCry(self, task):
        base.playSfx(self.eagleCry, node=self)
        task.delayTime = random.uniform(3, 30)
        return Task.again

    def announceGenerate(self):
        DistributedSuit.announceGenerate(self)
        taskMgr.doMethodLater(random.uniform(5, 25), self.__doEagleCry,
                              self.uniqueName('DEagleSuit-doEagleCry'))
        self.acceptHit()

    def disable(self):
        self.ignoreHit()
        self.removeEventSphere()
        taskMgr.remove(self.uniqueName('DEagleSuit-doExplodeSound'))
        taskMgr.remove(self.uniqueName('DEagleSuit-doEagleCry'))
        if self.fallingPropProjectile:
            self.fallingPropProjectile.finish()
            self.fallingPropProjectile = None
        if self.fallingPropeller:
            self.fallingPropeller.cleanup()
            self.fallingPropeller = None
        base.audio3d.detachSound(self.fallWhistle)
        del self.fallWhistle
        base.audio3d.detachSound(self.explode)
        del self.explode
        base.audio3d.detachSound(self.eagleCry)
        del self.eagleCry
        self.mg = None
        DistributedSuit.disable(self)
        return
class DistributedSuit(Suit, DistributedAvatar, DistributedSmoothNode,
                      DelayDeletable):
    notify = directNotify.newCategory('DistributedSuit')

    def __init__(self, cr):
        Suit.__init__(self)
        DistributedAvatar.__init__(self, cr)
        DistributedSmoothNode.__init__(self, cr)

        self.anim = None
        self._state = SuitState.ALIVE
        self.dept = None
        self.variant = None
        self.suitPlan = None
        self.level = None
        self.moveIval = None
        self.hpFlash = None

        # For PythonCTMusicManager:
        # Are we in range of the localAvatar?
        self.isInRange = False

        self.chaseTarget = 0

        self.suitFSM = ClassicFSM('DistributedSuit', [
            State('off', self.enterSuitOff, self.exitSuitOff),
            State('walking', self.enterWalking, self.exitWalking),
            State('flyingDown', self.enterFlyingDown, self.exitFlyingDown),
            State('flyingUp', self.enterFlyingUp, self.exitFlyingUp),
            State('lured', self.enterLured, self.exitLured)
        ], 'off', 'off')
        self.stateIndex2suitState = {}
        self.suitFSM.enterInitialState()
        self.makeStateDict()

    def setChaseTarget(self, avId):
        if avId != base.localAvatar.doId:
            if self.chaseTarget == base.localAvatar.doId:
                messenger.send(PCTMM.getCogLostTargetEvent())
        else:
            messenger.send(PCTMM.getCogChasingEvent())

        self.chaseTarget = avId

    def setWalkPath(self, path, timestamp):
        elapsedT = globalClockDelta.localElapsedTime(timestamp)
        self.suitFSM.request('walking', [path, elapsedT])

    def showAvId(self):
        self.setDisplayName(self.getName() + "\n" + str(self.doId))

    def showName(self):
        self.setDisplayName(self.getName())

    def setDisplayName(self, name):
        self.setupNameTag(tempName=name)

    def enterWalking(self, path, elapsedT):
        # path: A list of point2s.
        #
        # We will make a sequence of NPCWalkIntervals for each point2 in the path.

        self.clearMoveTrack()
        self.moveIval = getMoveIvalFromPath(self, path, elapsedT, True,
                                            'suitMoveIval')
        self.moveIval.start(elapsedT)

    def clearMoveTrack(self):
        if self.moveIval:
            self.ignore(self.moveIval.getDoneEvent())
            self.moveIval.pause()
            self.moveIval = None
        if not self.isDead():
            self.animFSM.request('neutral')

    def exitWalking(self):
        self.clearMoveTrack()
        if not self.isDead():
            self.animFSM.request('neutral')

    def enterFlyingDown(self, startIndex, endIndex, ts=0.0):
        if self.getHood() != '' and startIndex != -1 and endIndex != -1:
            duration = 3.5
            startPoint = CIGlobals.SuitSpawnPoints[
                self.getHood()].keys()[startIndex]
            startPos = CIGlobals.SuitSpawnPoints[
                self.getHood()][startPoint] + (0, 0, 6.5 * 4.8)
            endPoint = CIGlobals.SuitSpawnPoints[
                self.getHood()].keys()[endIndex]
            endPos = CIGlobals.SuitSpawnPoints[self.getHood()][endPoint]
            self.stopMoving(finish=1)
            groundF = 28
            dur = self.getDuration('land')
            fr = self.getFrameRate('land')
            if fr:
                animTimeInAir = groundF / fr
            else:
                animTimeInAir = groundF
            impactLength = dur - animTimeInAir
            timeTillLanding = 6.5 - impactLength
            self.moveIval = LerpPosInterval(self,
                                            duration=timeTillLanding,
                                            pos=endPos,
                                            startPos=startPos,
                                            fluid=1)
            self.moveIval.start(ts)
        self.animFSM.request('flyDown', [ts])

    def exitFlyingDown(self):
        self.stopMoving(finish=1)
        self.animFSM.request('neutral')

    def enterFlyingUp(self, startIndex, endIndex, ts=0.0):
        if self.getHood() != '':
            duration = 3
            if startIndex > -1:
                startPoint = CIGlobals.SuitSpawnPoints[
                    self.getHood()].keys()[startIndex]
                startPos = CIGlobals.SuitSpawnPoints[
                    self.getHood()][startPoint]
            else:
                startPos = self.getPos(render)
            if endIndex > -1:
                endPoint = CIGlobals.SuitSpawnPoints[
                    self.getHood()].keys()[endIndex]
                endPos = CIGlobals.SuitSpawnPoints[
                    self.getHood()][endPoint] + (0, 0, 6.5 * 4.8)
            else:
                endPos = self.getPos(render) + (0, 0, 6.5 * 4.8)

            self.stopMoving(finish=1)
            groundF = 28
            dur = self.getDuration('land')
            fr = self.getFrameRate('land')
            if fr:
                animTimeInAir = groundF / fr
            else:
                animTimeInAir = groundF
            impactLength = dur - animTimeInAir
            timeTillLanding = 6.5 - impactLength
            self.moveIval = Sequence(
                Wait(impactLength),
                LerpPosInterval(self,
                                duration=timeTillLanding,
                                pos=endPos,
                                startPos=startPos,
                                fluid=1))
            self.moveIval.start(ts)
        self.animFSM.request('flyAway', [ts, 1])

    def exitFlyingUp(self):
        if self.moveIval:
            self.moveIval.finish()
            self.moveIval = None
        self.animFSM.request('neutral')

    def enterLured(self, _, __, ___):
        self.loop('lured')

    def exitLured(self):
        self.stop()

    def enterSuitOff(self, foo1=None, foo2=None, foo3=None):
        pass

    def exitSuitOff(self):
        pass

    def setName(self, name):
        Suit.setName(self, name, self.suitPlan.getName())

    def setLevel(self, level):
        self.level = level
        if self.level == 12:
            self.maxHealth = 200
        elif self.level > 0:
            self.maxHealth = (self.level + 1) * (self.level + 2)
        else:
            self.maxHealth = 1
        self.health = self.maxHealth
        self.updateHealthBar(self.health)

    def getLevel(self):
        return self.level

    def startMoveInterval(self, startX, startY, startZ, endX, endY, endZ,
                          duration):
        self.stopMoving()
        endPos = Point3(endX, endY, endZ)
        self.moveIval = NPCWalkInterval(self,
                                        endPos,
                                        durationFactor=duration,
                                        fluid=1)
        self.moveIval.start()

    def stopMoveInterval(self, andTurnAround=0):
        if self.moveIval:
            self.moveIval.pause()
            self.moveIval = None
        if andTurnAround == 1:
            if self.health > 0:
                self.animFSM.request('neutral')
            self.setH(self.getH() - 180)

    def toggleRay(self, ray=1):
        if ray:
            Suit.initializeRay(self, self.avatarType, 2)
        else:
            Suit.disableRay(self)

    def startProjInterval(self,
                          startX,
                          startY,
                          startZ,
                          endX,
                          endY,
                          endZ,
                          duration,
                          gravityMult,
                          ts=0):
        if isinstance(ts, int) and ts != 0:
            ts = globalClockDelta.localElapsedTime(ts)

        self.disableRay()
        self.stopMoveInterval()
        startPos = Point3(startX, startY, startZ)
        endPos = Point3(endX, endY, endZ)
        oldHpr = self.getHpr(render)
        self.headsUp(endPos)
        newHpr = self.getHpr(render)
        self.setHpr(oldHpr)
        self.moveIval = Parallel(
            LerpHprInterval(self,
                            duration=0.5,
                            hpr=newHpr,
                            startHpr=oldHpr,
                            blendType='easeInOut'),
            Sequence(Func(self.animFSM.request, 'flyAway', [ts]), Wait(3.5),
                     Func(self.animFSM.request, 'flyDown', [1.0])),
            Sequence(
                Wait(2.0), Func(self.headsUp, endPos),
                ProjectileInterval(self,
                                   startPos=startPos,
                                   endPos=endPos,
                                   gravityMult=gravityMult,
                                   duration=duration)))
        self.moveIval.start(ts)

    def startPosInterval(self,
                         startX,
                         startY,
                         startZ,
                         endX,
                         endY,
                         endZ,
                         duration,
                         blendType,
                         ts=0.0):
        if ts != 0.0:
            ts = globalClockDelta.localElapsedTime(ts)
        self.stopMoveInterval()
        startPos = Point3(startX, startY, startZ)
        endPos = Point3(endX, endY, endZ)
        self.moveIval = LerpPosInterval(self,
                                        duration=duration,
                                        pos=endPos,
                                        startPos=startPos,
                                        blendType=blendType)
        self.moveIval.start(ts)

    def stopMoving(self, finish=0):
        if self.moveIval:
            if finish:
                self.moveIval.finish()
            else:
                self.moveIval.pause()
            self.moveIval = None

    def d_disableMovement(self, wantRay=False):
        self.sendUpdate('disableMovement', [])
        self.interruptAttack()
        self.stopMoving()
        if not wantRay:
            Suit.disableRay(self)

    def d_enableMovement(self):
        self.sendUpdate('enableMovement', [])
        Suit.initializeRay(self, self.avatarType, 2)

    def startRay(self):
        Suit.initializeRay(self, self.avatarType, 2)

    def setHealth(self, health):
        if health > self.health:
            # We got an hp boost. Flash green.
            flashColor = VBase4(0, 1, 0, 1)
        elif health < self.health:
            # We got an hp loss. Flash red.
            flashColor = VBase4(1, 0, 0, 1)
        DistributedAvatar.setHealth(self, health)

        def doBossFlash():
            if not self.isEmpty():
                LerpColorScaleInterval(self, 0.2, flashColor).start()

        def clearBossFlash():
            if not self.isEmpty():
                self.clearColorScale()

        if self.isDead():
            self.setChaseTarget(0)
            base.taskMgr.remove(self.uniqueName('monitorLocalAvDistance'))
            if self.isInRange:
                messenger.send(PCTMM.getCogOutOfRangeEvent())
                self.isInRange = False
            self.interruptAttack()

        if self.getLevel() > 12:
            if self.hpFlash:
                self.hpFlash.finish()
                self.hpFlash = None
            self.hpFlash = Sequence(Func(doBossFlash), Wait(0.2),
                                    Func(clearBossFlash))
            self.hpFlash.start()
        self.updateHealthBar(health)

    def announceHealth(self, level, hp):
        DistributedAvatar.announceHealth(self, level, hp)
        if level == 1:
            healthSfx = base.audio3d.loadSfx(SuitGlobals.healedSfx)
            base.audio3d.attachSoundToObject(healthSfx, self)
            SoundInterval(healthSfx, node=self).start()
            del healthSfx

    #
    #    'setSuit' sets the suit type and generates it.
    #    'arg' is an id for a SuitPlan as defined in SuitBank or
    #        an instance of SuitPlan.
    #    'variant' is an optional argument that sets the variant.
    #        It takes an id for the variant or an instance of Variant.
    #        Default is Variant.NORMAL.

    def setSuit(self, arg, variant=0):
        if isinstance(arg, SuitPlan):
            plan = arg
        else:
            plan = SuitBank.getSuitById(arg)

        voice = Voice.NORMAL
        if variant:
            if isinstance(variant, (int, long, float, complex)):
                variant = Variant.getVariantById(variant)

        if plan.getForcedVoice():
            voice = plan.getForcedVoice()

        Suit.generate(self, plan, variant, voice=voice)
        self.suitPlan = plan
        self.variant = Variant.getVariantById(variant)

    def getSuit(self):
        return tuple((self.suitPlan, self.variant))

    def spawn(self, startIndex, endIndex, spawnMode=SpawnMode.FLYDOWN):
        if spawnMode == SpawnMode.FLYDOWN:
            startPoint = CIGlobals.SuitSpawnPoints[
                self.getHood()].keys()[startIndex]
            startPos = CIGlobals.SuitSpawnPoints[
                self.getHood()][startPoint] + (0, 0, 50)
            endPoint = CIGlobals.SuitSpawnPoints[
                self.getHood()].keys()[endIndex]
            endPos = CIGlobals.SuitSpawnPoints[self.getHood()][endPoint]
            if self.moveIval:
                self.moveIval.finish()
                self.moveIval = None
            self.moveIval = LerpPosInterval(self,
                                            duration=3,
                                            pos=endPos,
                                            startPos=startPos,
                                            fluid=1)

    def makeStateDict(self):
        self.stateIndex2suitState = {
            0: self.suitFSM.getStateNamed('off'),
            1: self.suitFSM.getStateNamed('walking'),
            2: self.suitFSM.getStateNamed('flyingDown'),
            3: self.suitFSM.getStateNamed('flyingUp'),
            4: self.suitFSM.getStateNamed('lured')
        }
        self.suitState2stateIndex = {}
        for stateId, state in self.stateIndex2suitState.items():
            self.suitState2stateIndex[state.getName()] = stateId

    def setSuitState(self, index, startPoint, endPoint, timestamp=None):
        if timestamp != None:
            ts = globalClockDelta.localElapsedTime(timestamp)
        else:
            ts = 0.0

        self.suitState = self.stateIndex2suitState[index]
        self.startPoint = startPoint
        self.endPoint = endPoint

        self.suitFSM.request(self.suitState, [startPoint, endPoint, ts])

    def getSuitState(self):
        return self.suitState

    def setAnimState(self, anim, loop=1, timestamp=None):
        prevAnim = self.anim
        self.anim = anim

        if timestamp == None:
            ts = 0.0
        else:
            ts = globalClockDelta.localElapsedTime(timestamp)

        if type(anim) == types.IntType:
            if anim != 44 and anim != 45:
                anim = SuitGlobals.getAnimById(anim)
                animName = anim.getName()
            elif anim == 44:
                animName = 'die'
            elif anim == 45:
                animName = 'flyNeutral'
        elif type(anim) == types.StringType:
            animName = anim

        if self.animFSM.hasStateNamed(animName):
            self.animFSM.request(animName, [ts])
        else:
            if loop:
                self.loop(animName)
            else:
                self.play(animName)
        messenger.send(SuitGlobals.animStateChangeEvent % (self.uniqueName),
                       [anim, prevAnim])

    def doAttack(self, attackId, avId, timestamp=None):
        if timestamp == None:
            ts = 0.0
        else:
            ts = globalClockDelta.localElapsedTime(timestamp)
        attackName = SuitAttacks.SuitAttackLengths.keys()[attackId]
        attackTaunt = CIGlobals.SuitAttackTaunts[attackName][random.randint(
            0,
            len(CIGlobals.SuitAttackTaunts[attackName]) - 1)]
        avatar = self.cr.doId2do.get(avId)
        shouldChat = 0
        if self.suitPlan in [SuitBank.VicePresident, SuitBank.LucyCrossbill]:
            shouldChat = random.randint(0, 2)
        if shouldChat == 0:
            self.setChat(attackTaunt)

        self.animFSM.request('attack', [attackName, avatar, 0.0])

    def throwObject(self):
        self.acceptOnce('enter' + self.wsnp.node().getName(),
                        self.__handleWeaponCollision)
        Suit.throwObject(self)

    def __handleWeaponCollision(self, entry):
        self.sendUpdate('toonHitByWeapon',
                        [self.attack, base.localAvatar.doId])
        base.localAvatar.handleHitByWeapon(self.attack, self)
        self.b_handleWeaponTouch()

    def b_handleWeaponTouch(self):
        self.sendUpdate('handleWeaponTouch', [])
        self.handleWeaponTouch()

    def __monitorLocalAvDistance(self, task):
        if self.getDistance(base.localAvatar) <= PCTMM.getCogInRangeDistance(
        ) and not self.isInRange:
            self.isInRange = True
            messenger.send(PCTMM.getCogInRangeEvent())
        elif self.getDistance(base.localAvatar) > PCTMM.getCogInRangeDistance(
        ) and self.isInRange:
            self.isInRange = False
            messenger.send(PCTMM.getCogOutOfRangeEvent())

        return task.cont

    def announceGenerate(self):
        DistributedAvatar.announceGenerate(self)
        self.setAnimState('neutral')
        base.taskMgr.add(self.__monitorLocalAvDistance,
                         self.uniqueName('monitorLocalAvDistance'))

    def generate(self):
        DistributedAvatar.generate(self)
        DistributedSmoothNode.generate(self)

    def disable(self):
        base.taskMgr.remove(self.uniqueName('monitorLocalAvDistance'))
        self.anim = None
        self._state = None
        self.dept = None
        self.variant = None
        self.suitPlan = None
        if self.hpFlash:
            self.hpFlash.finish()
            self.hpFlash = None
        if self.moveIval:
            self.moveIval.pause()
            self.moveIval = None
        Suit.disable(self)
        DistributedAvatar.disable(self)

    def delete(self):
        Suit.delete(self)
        del self.anim
        del self._state
        del self.dept
        del self.variant
        del self.suitPlan
        del self.moveIval
        DistributedAvatar.delete(self)
        DistributedSmoothNode.delete(self)
class InventoryGui(DirectObject):
    directNotify = DirectNotify().newCategory('InventoryGui')
    HiddenPos = (0.2, 0, 0)
    VisiblePos = (-0.1725, 0, 0)
    SwitchTime = 0.3
    AutoShowTime = 1.5
    DELETED = False

    def __init__(self):
        DirectObject.__init__(self)
        self.backpack = base.localAvatar.backpack
        if not self.backpack:
            return
        self.backpack.loadoutGUI = self
        self.oneSlotPos = [(0, 0, 0)]
        self.twoSlotsPos = [(0, 0, 0.3), (0, 0, -0.2)]
        self.threeSlotsPos = [(0, 0, 0.5), (0, 0, 0), (0, 0, -0.5)]
        self.fourSlotPos = [(0, 0, 0.5), (0, 0, 0.15), (0, 0, -0.2),
                            (0, 0, -0.55)]
        self.availableSlot = 0
        self.slots = []
        self.activeSlot = None
        self.defaultSlots = 3
        self.prevSlot = None
        self.ammoLabel = None
        self.inventoryFrame = DirectFrame(parent=base.a2dRightCenter,
                                          pos=(-0.1725, 0, 0))
        self.visibilityBtn = DirectButton(text='',
                                          relief=None,
                                          text_bg=(1, 1, 1, 0),
                                          parent=base.a2dRightCenter,
                                          pos=(-0.1725, 0, 0),
                                          frameSize=(-0.2, 0.2, -0.725, 0.7),
                                          clickSound=None,
                                          rolloverSound=None)
        self.visibilityBtn.bind(DGG.WITHIN, self.__handleVisEnter)
        self.visibilityBtn.bind(DGG.WITHOUT, self.__handleVisExit)
        self.visibilityBtn.setBin('background', 10)
        self.visibilityBtn = None
        self.visibilityBtnStatus = 0
        self.switchSound = True
        self.switchSoundSfx = base.loadSfx(
            'phase_3/audio/sfx/GUI_balloon_popup.ogg')
        self.visibilityFSM = ClassicFSM('InventoryGui-VisibilityFSM', [
            State('off', self.enterOff, self.exitOff),
            State('hidden', self.enterHidden, self.exitHidden),
            State('hidden2visible', self.enterHidden2Visible,
                  self.exitHidden2Visible),
            State('visible', self.enterVisible, self.exitVisible),
            State('visible2hidden', self.enterVisible2Hidden,
                  self.exitVisible2Hidden)
        ], 'off', 'off')
        self.visibilityFSM.enterInitialState()
        self.visibilityFSM.request('hidden')
        return

    def enterOff(self):
        pass

    def exitOff(self):
        pass

    def enterHidden(self):
        self.inventoryFrame.setPos(InventoryGui.HiddenPos)
        self.inventoryFrame.hide()

    def exitHidden(self):
        pass

    def enterVisible(self, autoShow=False):
        self.inventoryFrame.setPos(InventoryGui.VisiblePos)
        self.inventoryFrame.show()
        if self.visibilityBtnStatus == 0:
            if autoShow is False:
                self.visibilityFSM.request('visible2hidden')

    def exitVisible(self):
        pass

    def enterHidden2Visible(self, autoShow=False):
        self.inventoryFrame.show()
        self.moveIval = LerpPosInterval(self.inventoryFrame,
                                        duration=InventoryGui.SwitchTime,
                                        pos=InventoryGui.VisiblePos,
                                        startPos=InventoryGui.HiddenPos)
        self.moveIval.setDoneEvent('hidden2visible')
        self.acceptOnce('hidden2visible', self.visibilityFSM.request,
                        ['visible', [autoShow]])
        self.moveIval.start()

    def exitHidden2Visible(self):
        self.ignore('hidden2visible')
        self.moveIval.finish()
        del self.moveIval

    def enterVisible2Hidden(self):
        self.moveIval = LerpPosInterval(self.inventoryFrame,
                                        duration=InventoryGui.SwitchTime,
                                        pos=InventoryGui.HiddenPos,
                                        startPos=InventoryGui.VisiblePos)
        self.moveIval.setDoneEvent('visible2hidden')
        self.acceptOnce('visible2hidden', self.visibilityFSM.request,
                        ['hidden'])
        self.moveIval.start()

    def exitVisible2Hidden(self):
        self.ignore('visible2hidden')
        self.moveIval.finish()
        del self.moveIval

    def click_setWeapon(self, slot, cmd):
        self.setWeapon(slot, playSound=False)

    def setWeapon(self, slot, playSound=True, showUpIfHidden=False):
        if isinstance(slot, str):
            for iSlot in self.slots:
                if iSlot.getGag():
                    if iSlot.getGag().getID() == slot:
                        slot = iSlot

        if self.activeSlot and slot != self.activeSlot:
            self.activeSlot.setOutlineImage('idle')
            self.prevSlot = self.activeSlot
        if slot.getGag() and self.backpack.getSupply(slot.getGag().getID(
        )) > 0 and not slot.getGag().getState() == GagState.RECHARGING:
            if self.activeSlot != slot:
                gagId = slot.getGag().getID()
                base.localAvatar.needsToSwitchToGag = gagId
                if base.localAvatar.gagsTimedOut == False:
                    base.localAvatar.b_equip(gagId)
                    base.localAvatar.enableGagKeys()
                slot.setOutlineImage('selected')
                self.activeSlot = slot
            else:
                if self.activeSlot == slot and slot.getGag().getState() in [
                        GagState.LOADED, GagState.RECHARGING
                ]:
                    base.localAvatar.needsToSwitchToGag = 'unequip'
                    if base.localAvatar.gagsTimedOut == False:
                        base.localAvatar.b_unEquip()
                        base.localAvatar.enableGagKeys()
                    self.activeSlot = None
            self.update()
            if self.switchSound and playSound:
                base.playSfx(self.switchSoundSfx)
        if showUpIfHidden:
            base.taskMgr.remove('showUpIfHidden')
            self.__autoVisEnter()
            base.taskMgr.doMethodLater(InventoryGui.AutoShowTime,
                                       self.__autoVisExitTask,
                                       'showUpIfHidden')
        return

    def __autoVisExitTask(self, task):
        if self.visibilityBtnStatus == 0:
            self.__handleVisExit(None, updateBtnStatus=False)
        return task.done

    def __autoVisEnter(self):
        self.__handleVisEnter(None, True, False)
        return

    def __handleVisEnter(self, foo, autoShow=False, updateBtnStatus=True):
        if updateBtnStatus:
            self.visibilityBtnStatus = 1
        if self.visibilityFSM.getCurrentState().getName() == 'hidden':
            self.visibilityFSM.request('hidden2visible', [autoShow])
        else:
            if self.visibilityFSM.getCurrentState().getName(
            ) == 'visible2hidden':
                self.visibilityFSM.request('visible')

    def __handleVisExit(self, foo, updateBtnStatus=True):
        if updateBtnStatus:
            self.visibilityBtnStatus = 0
        base.taskMgr.remove('showUpIfHidden')
        if self.visibilityFSM.getCurrentState().getName() == 'visible':
            self.visibilityFSM.request('visible2hidden')

    def createGui(self):
        self.deleteGui()
        posGroup = self.threeSlotsPos
        if self.defaultSlots == 4:
            posGroup = self.fourSlotPos
        for slot in range(len(posGroup) + 1):
            if slot == 3:
                posGroup = self.fourSlotPos
            slotObj = Slot(self, slot + 1, posGroup[slot], self.inventoryFrame)
            self.slots.append(slotObj)
            if slot == 3:
                slotObj.hide()

        self.ammoLabel = DirectLabel(text='Ammo: 0',
                                     text_fg=(1, 1, 1, 1),
                                     relief=None,
                                     text_shadow=(0, 0, 0, 1),
                                     text_scale=0.08,
                                     pos=(0.2, 0, 0.35),
                                     parent=base.a2dBottomLeft)
        self.ammoLabel.hide()
        self.enableWeaponSwitch()
        self.resetScroll()
        self.update()
        return

    def deleteGui(self):
        self.disableWeaponSwitch()
        for slot in self.slots:
            slot.destroy()

        self.slots = []
        if self.ammoLabel:
            self.ammoLabel.destroy()
            self.ammoLabel = None
        self.DELETED = True
        return

    def resetScroll(self):
        nextGag = 0
        prevGag = -1
        curGag = -1
        if self.prevSlot:
            prevGag = self.slots.index(self.prevSlot)
        if self.activeSlot:
            curGag = self.slots.index(self.activeSlot)
        if curGag == len(self.slots) - 1:
            nextGag = 0
            prevGag = curGag - 1
        else:
            if curGag == 0:
                nextGag = 1
                prevGag = len(self.slots) - 1
            else:
                if curGag == -1:
                    prevGag = len(self.slots) - 1
                else:
                    nextGag = curGag + 1
                    prevGag = curGag - 1
        self.accept('wheel_down',
                    self.setWeapon,
                    extraArgs=[self.slots[nextGag], True, True])
        self.accept('wheel_up',
                    self.setWeapon,
                    extraArgs=[self.slots[prevGag], True, True])

    def update(self):
        if not self.backpack:
            return
        for element in [self.ammoLabel, self.inventoryFrame]:
            if not element:
                return

        updateSlots = list(self.slots)
        for slot in self.slots:
            gag = slot.getGag()
            if not gag:
                updateSlots.remove(slot)
                slot.hide()
                continue
            supply = self.backpack.getSupply(gag.getID())
            index = self.slots.index(slot)
            if not gag and len(self.backpack.getGags()) - 1 >= index:
                gag = self.backpack.getGagByIndex(index)
                slot.setGag(gag)
                if self.backpack.getSupply(gag.getID(
                )) > 0 and not gag.getState() == GagState.RECHARGING:
                    slot.setOutlineImage('idle')
                else:
                    slot.setOutlineImage('no_ammo')
            elif slot == self.activeSlot:
                self.ammoLabel['text_fg'] = (1, 1, 1, 1)
                if supply > 0 and not gag.getState() == GagState.RECHARGING:
                    slot.setOutlineImage('selected')
                else:
                    if supply <= 0:
                        self.ammoLabel['text_fg'] = (0.9, 0, 0, 1)
                    slot.setOutlineImage('no_ammo')
                    self.activeSlot = None
                self.ammoLabel.show()
                self.ammoLabel['text'] = 'Ammo: %s' % self.backpack.getSupply(
                    slot.getGag().getID())
            elif self.backpack.getSupply(slot.getGag().getID(
            )) > 0 and not gag.getState() == GagState.RECHARGING:
                slot.setOutlineImage('idle')
            else:
                slot.setOutlineImage('no_ammo')

        numSlots = len(updateSlots)
        posGroup = {
            1: self.oneSlotPos,
            2: self.twoSlotsPos,
            3: self.threeSlotsPos,
            4: self.fourSlotPos
        }.get(numSlots)
        for i in xrange(len(updateSlots)):
            updateSlots[i].setPos(posGroup[i])
            updateSlots[i].show()

        if self.activeSlot == None:
            self.ammoLabel.hide()
            self.ammoLabel['text'] = 'Ammo: 0'
        self.resetScroll()
        return

    def setBackpack(self, backpack):
        self.backpack = backpack

    def updateLoadout(self):
        if self.backpack:
            loadout = self.backpack.getLoadout()
            if len(loadout) <= 3:
                self.reseatSlots()
            else:
                if len(loadout) == 4:
                    self.reseatSlots(slots=4)
            for i in range(len(self.slots)):
                slot = self.slots[i]
                if i < len(loadout):
                    slot.setGag(loadout[i])
                else:
                    slot.setGag(None)

            self.update()
        return

    def reseatSlots(self, slots=3):
        for slot in range(len(self.slots) - 1):
            if slots == 4:
                self.slots[slot].setPos(self.fourSlotPos[slot])
            else:
                self.slots[slot].setPos(self.threeSlotsPos[slot])

    def enableWeaponSwitch(self):
        for index in range(len(self.slots)):
            self.accept(str(index + 1),
                        self.setWeapon,
                        extraArgs=[self.slots[index], True, True])

    def disableWeaponSwitch(self):
        for key in ['1', '2', '3', '4', 'wheel_down', 'wheel_up']:
            self.ignore(key)

    def getSlots(self):
        return self.slots

    def getActiveSlot(self):
        return self.activeSlot

    def isDeleted(self):
        return self.DELETED
Beispiel #11
0
class GUnit(GEntity):
   
   def __init__(self,conf):
      GEntity.__init__(self,conf)
      self.p3dobject.reparentTo(self.gmap.units_node)
      self.p3dobject.setTransparency(TransparencyAttrib.MAlpha)
      #to be put under condition for non pickable units (bonuses npc for instance)
      self.p3dobject.setTag('GUnit-pickable','1')
      self.p3dobject.setPos(self.gmap.root.find('**/tile_'+str(conf['tileid'])),0,0,0)
      #supposedly already a float, but will screw up if not, so just making sure.
      self.move_speed=float(conf['move_speed'])
      self.path=[]
      self.popout_when_move_over=False
      self.pid=conf['pid']
      #highlight
      self.ts_highlighted=TextureStage('ts_highlighted')
      self.ts_highlighted.setMode(TextureStage.MDecal)
      self.ts_highlighted.setSort(2)
      #highlight
      self.ts_selected=TextureStage('ts_selected')
      self.ts_selected.setMode(TextureStage.MDecal)
      self.ts_selected.setSort(3)
               
   @staticmethod
   def load_resources():
      GUnit.textures={   'highlighted':loader.loadTexture('data/models/highlighted.tex.png'),
                        'selected':loader.loadTexture('data/models/selected.tex.png'),
                     }
      
   def dispose(self):
      '''del method'''
      GEntity.dispose(self)
      self.popout_sequence.finish()
      del self.popout_sequence

   def add_path(self,data):
      '''
      adds tile to pass by.
      '''
      #check for data completeness
      if not 'path' in data:
         out('WARNING in GUnit.add_path: incomplete data:\n'+str(data))
         return
      elif not isinstance(data['path'],list):
         out('WARNING in GUnit.add_path: invalid data:\n'+str(data))
         return
      #data considered valid
      self.path.extend([self.instances[eid] for eid in data['path']])
      if not self.update_move in update_list: 
         update_list.append(self.update_move)
      #out('GUnit.add_path:'+str(data))
   
   def finish_move_to(self,data):
      '''triggered by server side unit, to indicate the need to popout at end of move.'''
      out('GUnit.finish_move_to()'+str(data))
      if self.update_move in update_list:
         self.popout_when_move_over=True
      else:
         self.popout()
         
   def popout(self):
      '''sets up the popout animation at end of unit's mission'''
      scale=self.p3dobject.scaleInterval(.5,(.1,.1,.1))
      finish=Func(lambda:dispose_list.append(self))
      self.popout_sequence=Sequence(scale,finish)
      self.popout_sequence.start()
      
   def set_highlighted(self):
      self.p3dobject.setTexture(self.ts_highlighted,self.textures['highlighted'])
      
   def unset_highlighted(self):
      self.p3dobject.clearTexture(self.ts_highlighted)
      
   def set_selected(self):
      self.p3dobject.setTexture(self.ts_selected,self.textures['selected'])
      
   def unset_selected(self):
      self.p3dobject.clearTexture(self.ts_selected)
      
   def update_move(self):
      '''called every frame during while a move.'''
      if len(self.path)==0:
         out('WARNING in GUnit.update_move: path is empty, but method still called. removing it.')
         update_list.remove(self.update_move)
         return
      if not hasattr(self,'move_interval'):
         #start moving
         #first 3 args=model,duration,pos, the duration=1/... is relative to server side tile side size
         self.move_interval=LerpPosInterval(self.p3dobject,
                                    (1/(self.move_speed*ConfigVariableDouble('clock-frame-rate').getValue())),
                                    self.path[0].p3dobject.getPos(),
                                    name='interval_unit_move_'+str(self.eid)
                                    )
         self.p3dobject.lookAt(self.path[0].p3dobject.getPos())
         self.p3dobject.loop('run')
         self.move_interval.start()
      else:
         #is move ~over ?
         #t=self.move_interval.getT()
         #d=self.move_interval.getDuration()
         #d=d-t
         d=dist3(self.p3dobject,self.path[0].p3dobject)
         #out('client '+str(t*100./d)+'%')
         #arrived
         if d<self.move_speed:
            #out('client '+str(self.path[0].eid)+'@'+str(self.frame_no))
            self.p3dobject.setPos(self.path[0].p3dobject,0,0,0)
            self.path.pop(0)
            if len(self.path)==0:
               self.p3dobject.stop()
               self.move_interval.finish()
               del self.move_interval
               update_list.remove(self.update_move)
               if self.popout_when_move_over:
                  self.popout()
            else:
               #first 3 args=model,duration,pos
               self.move_interval.finish()
               self.move_interval=LerpPosInterval(self.p3dobject,
                                          (1/(self.move_speed*ConfigVariableDouble('clock-frame-rate').getValue())),
                                          self.path[0].p3dobject.getPos(),
                                          name='interval_unit_move_'+str(self.eid)
                                          )
               self.p3dobject.lookAt(self.path[0].p3dobject.getPos())
               self.move_interval.start()
Beispiel #12
0
class DistributedEagleSuit(DistributedSuit):
    notify = directNotify.newCategory('DistributedEagleSuit')

    def __init__(self, cr):
        DistributedSuit.__init__(self, cr)
        self.eagleCry = base.audio3d.loadSfx('phase_5/audio/sfx/tt_s_ara_cfg_eagleCry.mp3')
        base.audio3d.attachSoundToObject(self.eagleCry, self)
        self.fallWhistle = base.audio3d.loadSfx('phase_5/audio/sfx/incoming_whistleALT.mp3')
        base.audio3d.attachSoundToObject(self.fallWhistle, self)
        self.explode = base.audio3d.loadSfx('phase_3.5/audio/sfx/ENC_cogfall_apart.mp3')
        base.audio3d.attachSoundToObject(self.explode, self)
        self.eventSphereNodePath = None
        self.fallingPropeller = None
        self.fallingPropProjectile = None
        self.mg = None
        self.flySpeed = 0.0
        return

    def enterNeutral(self, ts = 0):
        self.show()
        self.timestampAnimTrack = Sequence(Wait(ts), Func(self.loop, 'flyNeutral'))
        self.timestampAnimTrack.start()

    def makeStateDict(self):
        self.suitFSM.addState(State('eagleFly', self.enterEagleFly, self.exitEagleFly))
        self.suitFSM.addState(State('eagleFall', self.enterEagleFall, self.exitEagleFall))
        self.stateIndex2suitState = {0: self.suitFSM.getStateNamed('off'),
         1: self.suitFSM.getStateNamed('walking'),
         2: self.suitFSM.getStateNamed('flyingDown'),
         3: self.suitFSM.getStateNamed('flyingUp'),
         4: self.suitFSM.getStateNamed('lured'),
         5: self.suitFSM.getStateNamed('eagleFly'),
         6: self.suitFSM.getStateNamed('eagleFall')}
        self.suitState2stateIndex = {}
        for stateId, state in self.stateIndex2suitState.items():
            self.suitState2stateIndex[state.getName()] = stateId

    def setFlySpeed(self, value):
        self.flySpeed = value

    def getFlySpeed(self):
        return self.flySpeed

    def enterEagleFly(self, startIndex, endIndex, ts = 0.0):
        durationFactor = self.getFlySpeed()
        if startIndex > -1:
            startPos = EGG.EAGLE_FLY_POINTS[startIndex]
        else:
            startPos = self.getPos(render)
        endPos = EGG.EAGLE_FLY_POINTS[endIndex]
        if self.moveIval:
            self.moveIval.pause()
            self.moveIval = None
        self.moveIval = NPCWalkInterval(self, endPos, durationFactor=durationFactor, startPos=startPos, fluid=1)
        self.moveIval.start(ts)
        return

    def exitEagleFly(self):
        if self.moveIval:
            self.moveIval.pause()
            self.moveIval = None
        return

    def enterEagleFall(self, startIndex, endIndex, ts = 0.0):
        self.moveIval = LerpPosInterval(self, duration=4.0, pos=self.getPos(render) - (0, 0, 75), startPos=self.getPos(render), blendType='easeIn')
        self.moveIval.start(ts)

    def exitEagleFall(self):
        if self.moveIval:
            self.moveIval.finish()
            self.moveIval = None
        return

    def fallAndExplode(self):
        self.cleanupPropeller()
        self.fallingPropeller = Actor('phase_4/models/props/propeller-mod.bam', {'chan': 'phase_4/models/props/propeller-chan.bam'})
        self.fallingPropeller.reparentTo(render)
        self.fallingPropeller.loop('chan', fromFrame=0, toFrame=3)
        parentNode = self.attachNewNode('fallingPropParentNode')
        h = random.randint(0, 359)
        parentNode.setH(h)
        dummyNode = parentNode.attachNewNode('dummyNode')
        dummyNode.setPos(0, 10, -50)
        self.fallingPropProjectile = FlightProjectileInterval(self.fallingPropeller, startPos=self.find('**/joint_head').getPos(render), endPos=dummyNode.getPos(render), duration=5.0, gravityMult=0.25)
        self.fallingPropProjectile.start()
        dummyNode.removeNode()
        del dummyNode
        parentNode.removeNode()
        del parentNode
        self.updateHealthBar(0)
        self.ignoreHit()
        self.fallWhistle.play()
        taskMgr.doMethodLater(4.0, self.doExplodeSound, self.uniqueName('DEagleSuit-doExplodeSound'))

    def doExplodeSound(self, task):
        self.explode.play()
        return Task.done

    def __initializeEventSphere(self):
        sphere = CollisionSphere(0, 0, 0, 2)
        sphere.setTangible(0)
        node = CollisionNode(self.uniqueName('DEagleSuit-eventSphere'))
        node.addSolid(sphere)
        node.setCollideMask(CIGlobals.WallBitmask)
        np = self.attachNewNode(node)
        np.setSz(2.5)
        np.setZ(5.5)
        self.eventSphereNodePath = np

    def removeEventSphere(self):
        if self.eventSphereNodePath:
            self.eventSphereNodePath.removeNode()
            self.eventSphereNodePath = None
        return

    def acceptHit(self):
        self.acceptOnce('enter' + self.eventSphereNodePath.node().getName(), self.__handleHit)

    def ignoreHit(self):
        self.ignore('enter' + self.eventSphereNodePath.node().getName())

    def __handleHit(self, entry):
        messenger.send(EGG.EAGLE_HIT_EVENT, [self.doId])

    def setSuit(self, arg, variant):
        DistributedSuit.setSuit(self, arg, 3)
        self.deleteShadow()
        self.disableBodyCollisions()
        self.disableRay()
        self.__initializeEventSphere()
        self.show()
        self.setAnimState('flyNeutral')

    def __doEagleCry(self, task):
        self.eagleCry.play()
        task.delayTime = random.uniform(3, 30)
        return Task.again

    def announceGenerate(self):
        DistributedSuit.announceGenerate(self)
        taskMgr.doMethodLater(random.uniform(5, 25), self.__doEagleCry, self.uniqueName('DEagleSuit-doEagleCry'))
        self.acceptHit()

    def disable(self):
        self.ignoreHit()
        self.removeEventSphere()
        taskMgr.remove(self.uniqueName('DEagleSuit-doExplodeSound'))
        taskMgr.remove(self.uniqueName('DEagleSuit-doEagleCry'))
        if self.fallingPropProjectile:
            self.fallingPropProjectile.finish()
            self.fallingPropProjectile = None
        if self.fallingPropeller:
            self.fallingPropeller.cleanup()
            self.fallingPropeller = None
        base.audio3d.detachSound(self.fallWhistle)
        del self.fallWhistle
        base.audio3d.detachSound(self.explode)
        del self.explode
        base.audio3d.detachSound(self.eagleCry)
        del self.eagleCry
        self.mg = None
        DistributedSuit.disable(self)
        return
class ParticleAttack(Attack):
    notify = directNotify.newCategory('ParticleAttack')
    attack = 'particleattack'
    particleIvalDur = 1
    shooterDistance = 50

    def __init__(self, attacksClass, suit):
        Attack.__init__(self, attacksClass, suit)
        self.particles = []
        self.handObj = None
        self.shootOutCollNP = None
        self.particleSound = None
        self.particleMoveIval = None
        self.targetX = None
        self.targetY = None
        self.targetZ = None
        return

    def handleWeaponTouch(self):
        pass

    def handleCollision(self, entry):
        if self.suit:
            self.suit.sendUpdate('toonHitByWeapon', [self.getAttackId(self.attack), base.localAvatar.doId])
            base.localAvatar.b_handleSuitAttack(self.getAttackId(self.attack), self.suit.doId)

    def doAttack(self, particlePaths, track_name, particleCollId, animation_name, delayUntilRelease, animationSpeed=1, handObjPath=None, handObjParent=None, startRightAway=True, ts=0):
        for path in particlePaths:
            particle = ParticleLoader.loadParticleEffect(path)
            self.particles.append(particle)

        sphere = CollisionSphere(0, 0, 0, 1)
        sphere.setTangible(0)
        node = CollisionNode(particleCollId)
        node.addSolid(sphere)
        node.setCollideMask(CIGlobals.WallBitmask)
        self.targetX = self.attacksClass.target.getX(render)
        self.targetY = self.attacksClass.target.getY(render)
        self.targetZ = self.attacksClass.target.getZ(render)
        if len(self.particles) == 1:
            self.shootOutCollNP = self.particles[0].attachNewNode(node)
        else:
            self.shootOutCollNP = self.suit.attachNewNode(node)
        if handObjPath and handObjParent:
            self.handObj = loader.loadModel(handObjPath)
            self.handObj.reparentTo(handObjParent)
        self.suit.setPlayRate(animationSpeed, animation_name)
        self.suit.play(animation_name)
        if hasattr(self.suit, 'uniqueName'):
            track_name = self.suit.uniqueName(track_name)
            particleCollId = self.suit.uniqueName(particleCollId)
        self.suitTrack = Sequence(name=track_name)
        self.suitTrack.append(Wait(delayUntilRelease))
        self.suitTrack.append(Func(self.releaseAttack))
        self.suitTrack.append(Wait(self.particleIvalDur))
        self.suitTrack.setDoneEvent(self.suitTrack.getName())
        self.acceptOnce(self.suitTrack.getDoneEvent(), self.finishedAttack)
        if startRightAway:
            self.suitTrack.start(ts)

    def releaseAttack(self, releaseFromJoint, onlyMoveColl=True, blendType='noBlend'):
        startNP = releaseFromJoint.attachNewNode('startNP')
        if None not in [self.targetX, self.targetY, self.targetZ]:
            startNP.lookAt(render, self.targetX, self.targetY, self.targetZ + 2)
            pathNP = NodePath('path')
            pathNP.reparentTo(startNP)
            pathNP.setScale(render, 1.0)
            pathNP.setPos(0, self.shooterDistance, 0)
            for particle in self.particles:
                if not onlyMoveColl:
                    particle.start(render)
                else:
                    particle.start(self.suit)
                particle.lookAt(pathNP)
                if self.attack == 'razzledazzle':
                    particle.setP(particle, 90)

            if onlyMoveColl:
                target = self.shootOutCollNP
                target.wrtReparentTo(render)
            else:
                target = self.particles[0]
            self.particleMoveIval = LerpPosInterval(target, duration=self.particleIvalDur, pos=pathNP.getPos(render), startPos=startNP.getPos(render), blendType=blendType)
            self.particleMoveIval.start()
            self.acceptOnce('enter' + self.shootOutCollNP.node().getName(), self.handleCollision)
            pathNP.removeNode()
            startNP.removeNode()
            del pathNP
            del startNP
        self.playParticleSound()
        return

    def playParticleSound(self):
        if self.particleSound:
            base.audio3d.attachSoundToObject(self.particleSound, self.suit)
            base.playSfx(self.particleSound)

    def cleanup(self):
        Attack.cleanup(self)
        self.targetX = None
        self.targetY = None
        self.targetZ = None
        if self.particles:
            for particle in self.particles:
                particle.cleanup()

        self.particles = None
        if self.handObj:
            self.handObj.removeNode()
            self.handObj = None
        if self.shootOutCollNP:
            self.ignore('enter' + self.shootOutCollNP.node().getName())
            self.shootOutCollNP.removeNode()
            self.shootOutCollNP = None
        if self.particleMoveIval:
            self.particleMoveIval.pause()
            self.particleMoveIval = None
        self.particleSound = None
        self.particleIvalDur = None
        return
class HangUpAttack(Attack):
    notify = directNotify.newCategory('HangUpAttack')
    attack = 'hangup'

    def __init__(self, attacksClass, suit):
        Attack.__init__(self, attacksClass, suit)
        self.phone = None
        self.receiver = None
        self.collNP = None
        self.phoneSfx = None
        self.hangupSfx = None
        self.shootIval = None
        self.cord = None
        self.receiverOutCord = None
        self.phoneOutCord = None
        return

    def loadAttack(self):
        self.phone = loader.loadModel('phase_3.5/models/props/phone.bam')
        self.phone.setHpr(0, 0, 180)
        if self.suit.type == 'B':
            self.phone.setPos(0.7, 0.15, 0)
        else:
            if self.suit.type == 'C':
                self.phone.setPos(0.25, 0, 0)
        self.receiver = loader.loadModel('phase_3.5/models/props/receiver.bam')
        self.receiver.reparentTo(self.phone)
        self.cord = Rope()
        self.cord.ropeNode.setUseVertexColor(1)
        self.cord.ropeNode.setUseVertexThickness(1)
        self.cord.setup(3, ({'node': self.phone, 'point': (0.8, 0, 0.2), 'color': (0, 0, 0, 1), 'thickness': 1000}, {'node': self.phone, 'point': (2, 0, 0), 'color': (0, 0, 0, 1), 'thickness': 1000}, {'node': self.receiver, 'point': (1.1, 0.25, 0.5), 'color': (0, 0, 0, 1), 'thickness': 1000}), [])
        self.cord.setH(180)
        self.phoneSfx = base.audio3d.loadSfx('phase_3.5/audio/sfx/SA_hangup.ogg')
        base.audio3d.attachSoundToObject(self.phoneSfx, self.phone)
        self.hangupSfx = base.audio3d.loadSfx('phase_3.5/audio/sfx/SA_hangup_place_down.ogg')
        base.audio3d.attachSoundToObject(self.hangupSfx, self.phone)
        collSphere = CollisionSphere(0, 0, 0, 2)
        collSphere.setTangible(0)
        collNode = CollisionNode('phone_shootout')
        collNode.addSolid(collSphere)
        collNode.setCollideMask(CIGlobals.WallBitmask)
        self.collNP = self.phone.attachNewNode(collNode)

    def doAttack(self, ts=0):
        self.loadAttack()
        if hasattr(self.suit, 'uniqueName'):
            name = self.suit.uniqueName('doHangupAttack')
        else:
            name = 'doHangupAttack'
        if self.suit.type == 'A':
            delay2playSound = 1.0
            delayAfterSoundToPlaceDownReceiver = 0.2
            delayAfterShootToIgnoreCollisions = 1.0
            delay2PickUpReceiver = 1.0
            receiverInHandPos = Point3(-0.5, 0.5, -1)
        else:
            if self.suit.type == 'B':
                delay2playSound = 1.5
                delayAfterSoundToPlaceDownReceiver = 0.7
                delayAfterShootToIgnoreCollisions = 1.0
                delay2PickUpReceiver = 1.5
                receiverInHandPos = Point3(-0.3, 0.5, -0.8)
            else:
                if self.suit.type == 'C':
                    delay2playSound = 1.0
                    delayAfterSoundToPlaceDownReceiver = 1.15
                    delayAfterShootToIgnoreCollisions = 1.0
                    delay2PickUpReceiver = 1.5
                    receiverInHandPos = Point3(-0.3, 0.5, -0.8)
        self.suitTrack = Parallel(name=name)
        self.suitTrack.append(ActorInterval(self.suit, 'phone'))
        self.suitTrack.append(Sequence(Wait(delay2playSound), SoundInterval(self.phoneSfx, duration=2.1), Wait(delayAfterSoundToPlaceDownReceiver), Func(self.receiver.setPos, 0, 0, 0), Func(self.receiver.setH, 0.0), Func(self.receiver.reparentTo, self.phone), Func(self.acceptOnce, 'enter' + self.collNP.node().getName(), self.handleCollision), Func(self.shootOut), Parallel(SoundInterval(self.hangupSfx), Sequence(Wait(delayAfterShootToIgnoreCollisions), Func(self.ignore, 'enter' + self.collNP.node().getName())))))
        self.suitTrack.append(Sequence(Func(self.phone.reparentTo, self.suit.find('**/joint_Lhold')), Func(self.cord.reparentTo, render), Wait(delay2PickUpReceiver), Func(self.receiver.reparentTo, self.suit.find('**/joint_Rhold')), Func(self.receiver.setPos, receiverInHandPos), Func(self.receiver.setH, 270.0)))
        self.suitTrack.setDoneEvent(self.suitTrack.getName())
        self.acceptOnce(self.suitTrack.getDoneEvent(), self.finishedAttack)
        self.suitTrack.delayDelete = DelayDelete.DelayDelete(self.suit, name)
        self.suitTrack.start(ts)

    def handleCollision(self, entry):
        if self.suit:
            self.suit.sendUpdate('toonHitByWeapon', [self.getAttackId(self.attack), base.localAvatar.doId])
            base.localAvatar.b_handleSuitAttack(self.getAttackId(self.attack), self.suit.doId)

    def shootOut(self):
        pathNode = NodePath('path')
        pathNode.reparentTo(self.suit)
        pathNode.setPos(0, 50, self.phone.getZ(self.suit))
        self.collNP.reparentTo(render)
        self.shootIval = LerpPosInterval(self.collNP, duration=1.0, pos=pathNode.getPos(render), startPos=self.phone.getPos(render))
        self.shootIval.start()
        pathNode.removeNode()
        del pathNode

    def cleanup(self):
        Attack.cleanup(self)
        if self.shootIval:
            self.shootIval.pause()
            self.shootIval = None
        if self.cord:
            self.cord.removeNode()
            self.cord = None
        if self.phone:
            self.phone.removeNode()
            self.phone = None
        if self.receiver:
            self.receiver.removeNode()
            self.receiver = None
        if self.collNP:
            self.collNP.node().clearSolids()
            self.collNP.removeNode()
            self.collNP = None
        if self.phoneSfx:
            self.phoneSfx.stop()
            self.phoneSfx = None
        return
class ThrowAttack(Attack):
    notify = directNotify.newCategory('ThrowAttack')
    attack = 'throw'

    def __init__(self, attacksClass, suit):
        Attack.__init__(self, attacksClass, suit)
        self.weapon_state = None
        self.weapon = None
        self.wss = None
        self.wsnp = None
        self.suitTrack = None
        self.weaponSfx = None
        self.throwTrajectory = None
        self.targetX = None
        self.targetY = None
        self.targetZ = None
        self.startNP = None
        return

    def handleWeaponCollision(self, entry):
        if self.suit:
            self.suit.sendUpdate('toonHitByWeapon', [self.getAttackId(self.attack), base.localAvatar.doId])
            base.localAvatar.b_handleSuitAttack(self.getAttackId(self.attack), self.suit.doId)
            self.suit.b_handleWeaponTouch()

    def doAttack(self, weapon_path, weapon_scale, track_name, animation_name, collsphere_radius, weapon_coll_id, weapon_h=0, weapon_p=0, weapon_r=0, weapon_x=0, weapon_y=0, weapon_z=0, ts=0):
        self.weapon_state = 'start'
        if hasattr(self.suit, 'uniqueName'):
            track_name = self.suit.uniqueName(track_name)
            weapon_coll_id = self.suit.uniqueName(weapon_coll_id)
        self.weapon = loader.loadModel(weapon_path)
        self.weapon.setScale(weapon_scale)
        self.weapon.setHpr(weapon_h, weapon_p, weapon_r)
        self.weapon.setPos(weapon_x, weapon_y, weapon_z)
        self.wss = CollisionSphere(0, 0, 0, collsphere_radius)
        self.wss.setTangible(0)
        self.targetX = self.attacksClass.target.getX(render)
        self.targetY = self.attacksClass.target.getY(render)
        self.targetZ = self.attacksClass.target.getZ(render)
        self.suitTrack = Sequence(name=track_name)
        if self.attack not in ('glowerpower', ):
            self.weapon.reparentTo(self.suit.find('**/joint_Rhold'))
            self.suitTrack.append(Wait(1.2))
            self.suitTrack.append(Func(self.suit.setPlayRate, 1.0, animation_name))
            if self.suit.type == 'C':
                self.suitTrack.append(Wait(0))
            else:
                self.suitTrack.append(Wait(0.7))
            self.suit.setPlayRate(2.0, animation_name)
            self.suitTrack.append(Func(self.throwObject))
            self.suitTrack.append(Wait(1.0))
            self.suitTrack.append(Func(self.delWeapon))
        else:
            self.suitTrack.append(Wait(1))
            self.suitTrack.append(Func(self.throwObject))
            self.suitTrack.append(Wait(0.5))
            self.suitTrack.append(Func(self.delWeapon))
        self.suit.play(animation_name)
        wsnode = CollisionNode(weapon_coll_id)
        wsnode.addSolid(self.wss)
        wsnode.setCollideMask(CIGlobals.WallBitmask)
        self.wsnp = self.weapon.attachNewNode(wsnode)
        self.suitTrack.setDoneEvent(self.suitTrack.getName())
        self.acceptOnce(self.suitTrack.getDoneEvent(), self.finishedAttack)
        self.suitTrack.delayDelete = DelayDelete.DelayDelete(self.suit, track_name)
        self.suitTrack.start(ts)

    def playWeaponSound(self):
        if self.weapon and self.weaponSfx:
            base.audio3d.attachSoundToObject(self.weaponSfx, self.suit)
            self.weaponSfx.play()

    def throwObject(self, projectile=True):
        if not self.weapon:
            return
        self.acceptOnce('enter' + self.wsnp.node().getName(), self.handleWeaponCollision)
        self.playWeaponSound()
        if self.weapon:
            self.weapon.wrtReparentTo(render)
            self.weapon.setHpr(Vec3(0, 0, 0))
        if self.attack not in ('glowerpower', ):
            parent = self.suit.find('**/joint_Rhold')
        else:
            parent = self.suit.find('**/joint_head')
        startNP = parent.attachNewNode('startNp')
        startNP.lookAt(render, self.targetX, self.targetY, self.targetZ)
        pathNP = NodePath('throwPath')
        pathNP.reparentTo(startNP)
        pathNP.setScale(render, 1.0)
        pathNP.setPos(0, 50, 0)
        if self.attack in ('clipontie', 'powertie', 'halfwindsor'):
            self.weapon.setHpr(pathNP.getHpr(render))
        if projectile == True:
            self.throwTrajectory = ProjectileInterval(self.weapon, startPos=self.suit.find('**/joint_Rhold').getPos(render), endPos=pathNP.getPos(render), gravityMult=0.7, duration=1.0)
        else:
            self.weapon.setH(pathNP.getH(render))
            self.throwTrajectory = LerpPosInterval(self.weapon, duration=0.5, pos=pathNP.getPos(render), startPos=startNP.getPos(render) + (0,
                                                                                                                                            3,
                                                                                                                                            0))
        self.throwTrajectory.start()
        self.weapon_state = 'released'
        startNP.removeNode()
        del startNP
        pathNP.removeNode()
        del pathNP

    def interruptAttack(self):
        if self.throwTrajectory:
            if self.throwTrajectory.isStopped():
                self.delWeapon()

    def handleWeaponTouch(self):
        if self.throwTrajectory:
            self.throwTrajectory.pause()
            self.throwTrajectory = None
        self.delWeapon()
        return

    def delWeapon(self):
        if self.weapon:
            self.weapon.removeNode()
            self.weapon = None
        return

    def cleanup(self):
        Attack.cleanup(self)
        self.targetX = None
        self.targetY = None
        self.targetZ = None
        self.weapon_state = None
        if self.weaponSfx:
            self.weaponSfx.stop()
            self.weaponSfx = None
        if self.throwTrajectory:
            self.throwTrajectory.pause()
            self.throwTrajectory = None
        self.delWeapon()
        self.wss = None
        if self.wsnp:
            self.wsnp.node().clearSolids()
            self.wsnp.removeNode()
            self.wsnp = None
        return
class EnemyUnit(Unit):
    """Base class of enemy units.

    Args:
        id_ (int): Enemy unit id.
        class_ (str): Enemy class name.
        class_data (dict): Enemy class description.
        model (actor.Actor): Enemy character model.
        y_positions (list): Free positions along Y.
        enemy_handler (CollisionHandlerEvent): Enemy collisions handler.
    """

    def __init__(self, id_, class_, class_data, model, y_positions, enemy_handler):
        Unit.__init__(self, "enemy_" + str(id_), class_, class_data)

        self.transport_snd = None
        self._move_int = None
        self._rb_node = None
        self._tooltip = "Skinhead - " + self.class_

        self._y_positions = y_positions
        self._y_pos = take_random(self._y_positions)

        self._x_range = (-0.3, 0.4) if self.class_data["part"] == "side" else (0.6, 1.3)

        self.node = render.attachNewNode(self.id + "_node")  # noqa: F821
        self.node.setPos(self._io_dist, -7, 0)

        self.model = model
        self.model.pose("ride", 1)
        self.model.reparentTo(self.node)

        # organize movement and aiming tasks
        time_to_overtake = random.randint(33, 50)

        self._move(time_to_overtake, (self._y_pos, random.uniform(*self._x_range), 0))
        taskMgr.doMethodLater(  # noqa: F821
            time_to_overtake + 2, self._float_move, self.id + "_float_move"
        )

    @abc.abstractmethod
    def _explode(self):
        raise NotImplementedError(
            "Every enemy unit class must implement _explode() method."
        )

    @property
    def _io_dist(self):
        """Enemy Y-distance for approach and back off."""
        if self._y_pos > 0:
            return self._y_pos + 0.45
        return self._y_pos - 0.45

    @property
    def clear_delay(self):
        """Delay between this unit death and its clearing.

        Returns:
            int: Seconds to hold the unit before delete.
        """
        return 15

    @property
    def shooting_speed(self):
        """Delay between shots of this unit.

        Returns:
            float: Delay between shots in seconds.
        """
        return 1.7 + random.uniform(0.1, 0.9)

    @property
    def tooltip(self):
        """Tooltip to show on mouse pointing to this enemy.

        Returns:
            str: This unit fraction and class.
        """
        return self._tooltip

    def _die(self):
        """Make this enemy unit die.

        Play death sequence of movements and sounds,
        stop all the tasks for this enemy, plan clearing.

        Returns:
            bool: True, if the unit dies for the first time.
        """
        if not Unit._die(self):
            return False

        self.model.setColorScale(1, 1, 1, 1)
        self._stop_tasks("_float_move")
        self._move_int.pause()

        self.model.play("die")
        if self.id in base.world.enemy.active_units:  # noqa: F821
            base.world.enemy.active_units.pop(self.id)  # noqa: F821
            if self.current_part:
                self.current_part.enemies.remove(self)

        self._explode()
        self.transport_snd.stop()
        self._y_positions.append(self._y_pos)

        base.add_head(self.class_data["class"].__name__)  # noqa: F821
        return True

    def _float_move(self, task):
        """Make enemy floatly move along the Train."""
        if chance(80):
            shift = random.choice((-0.05, 0.05))
            if self._y_pos + shift in self._y_positions:
                self._y_positions.append(self._y_pos)
                self._y_pos = self._y_pos + shift
                self._y_positions.remove(self._y_pos)

        self._move(
            random.randint(3, 6), (self._y_pos, random.uniform(*self._x_range), 0)
        )
        task.delayTime = random.randint(7, 9)
        return task.again

    def _move(self, period, new_pos):
        """Run a new movement interval with the given parameters.

        Args:
            period (tuple): Interval duration bounds.
            new_pos (tuple): New enemy position.
        """
        if self._move_int is not None:
            self._move_int.pause()

        self._move_int = LerpPosInterval(
            self.node, period, new_pos, blendType="easeInOut"
        )
        self._move_int.start()

    def capture_train(self):
        """The Train got critical damage - stop near it."""
        self._stop_tasks("_float_move")

    def clear(self, task=None):
        """Clear all the graphical, physical and sound data of this unit."""
        base.sound_mgr.detach_sound(self.transport_snd)  # noqa: F821
        if getattr(self, "_cry_snd", None):
            base.sound_mgr.detach_sound(self._cry_snd)  # noqa: F821

        self._move_int.finish()
        self.model.cleanup()
        self.node.removeNode()

        if self._rb_node is not None:
            base.world.phys_mgr.removeRigidBody(self._rb_node)  # noqa: F821

        if task is not None:
            return task.done

    def get_damage(self, damage):
        """Take damage points and change model color.

        The more damage the unit taken the more red it'll be.

        Args:
            damage (int): Damage points to get.
        """
        Unit.get_damage(self, damage)
        self.model.setColorScale(self.model.getColorScale()[0] + 0.018, 1, 1, 1)

    def stop(self):
        """Smoothly stop this unit following the Train."""
        self._stop_tasks("_float_move")
        self._move(random.randint(9, 11), (self._io_dist, -7, 0))
        self._y_positions.append(self._y_pos)

    def stop_ride(self):
        """Stop riding actions."""
        self.transport_snd.stop()