class CogdoFlyingLegalEagle(DirectObject, FSM):
    CollSphereName = 'CogdoFlyingLegalEagleSphere'
    CollisionEventName = 'CogdoFlyingLegalEagleCollision'
    InterestCollName = 'CogdoFlyingLegalEagleInterestCollision'
    RequestAddTargetEventName = 'CogdoFlyingLegalEagleRequestTargetEvent'
    RequestAddTargetAgainEventName = 'CogdoFlyingLegalEagleRequestTargetAgainEvent'
    RequestRemoveTargetEventName = 'CogdoFlyingLegalEagleRemoveTargetEvent'
    ForceRemoveTargetEventName = 'CogdoFlyingLegalEagleForceRemoveTargetEvent'
    EnterLegalEagle = 'CogdoFlyingLegalEagleDamageToon'
    ChargingToAttackEventName = 'LegalEagleChargingToAttack'
    LockOnToonEventName = 'LegalEagleLockOnToon'
    CooldownEventName = 'LegalEagleCooldown'
    notify = DirectNotifyGlobal.directNotify.newCategory(
        'CogdoFlyingLegalEagle')

    def __init__(self, nest, index, suitDnaName='le'):
        FSM.__init__(self, 'CogdoFlyingLegalEagle')
        self.defaultTransitions = {
            'Off': ['Roost'],
            'Roost': ['TakeOff', 'Off'],
            'TakeOff': ['LockOnToon', 'LandOnNest', 'Off'],
            'LockOnToon': ['RetreatToNest', 'ChargeUpAttack', 'Off'],
            'ChargeUpAttack': ['RetreatToNest', 'Attack', 'Off'],
            'Attack': ['RetreatToSky', 'Off'],
            'RetreatToSky': ['Cooldown', 'Off'],
            'Cooldown': ['LockOnToon', 'LandOnNest', 'Off'],
            'RetreatToNest': ['LandOnNest', 'Off'],
            'LandOnNest': ['Roost', 'Off']
        }
        self.index = index
        self.nest = nest
        self.target = None
        self.isEagleInterested = False
        self.collSphere = None
        self.suit = Suit.Suit()
        d = SuitDNA.SuitDNA()
        d.newSuit(suitDnaName)
        self.suit.setDNA(d)
        self.suit.reparentTo(render)
        swapAvatarShadowPlacer(self.suit, 'legalEagle-%sShadowPlacer' % index)
        self.suit.setPos(self.nest.getPos(render))
        self.suit.setHpr(-180, 0, 0)
        self.suit.stash()
        self.prop = None
        self.attachPropeller()
        head = self.suit.find('**/joint_head')
        self.interestConeOrigin = self.nest.attachNewNode('fakeHeadNodePath')
        self.interestConeOrigin.setPos(
            render,
            head.getPos(render) +
            Vec3(0, Globals.LegalEagle.InterestConeOffset, 0))
        self.attackTargetPos = None
        self.startOfRetreatToSkyPos = None
        pathModel = CogdoUtil.loadFlyingModel('legalEaglePaths')
        self.chargeUpMotionPath = Mopath.Mopath(name='chargeUpMotionPath-%i' %
                                                self.index)
        self.chargeUpMotionPath.loadNodePath(pathModel.find('**/charge_path'))
        self.retreatToSkyMotionPath = Mopath.Mopath(
            name='retreatToSkyMotionPath-%i' % self.index)
        self.retreatToSkyMotionPath.loadNodePath(
            pathModel.find('**/retreat_path'))
        audioMgr = base.cogdoGameAudioMgr
        self._screamSfx = audioMgr.createSfx('legalEagleScream', self.suit)
        self.initIntervals()
        return

    def attachPropeller(self):
        if self.prop == None:
            self.prop = BattleProps.globalPropPool.getProp('propeller')
            head = self.suit.find('**/joint_head')
            self.prop.reparentTo(head)
        return

    def detachPropeller(self):
        if self.prop:
            self.prop.cleanup()
            self.prop.removeNode()
            self.prop = None
        return

    def _getAnimationIval(self,
                          animName,
                          startFrame=0,
                          endFrame=None,
                          duration=1):
        if endFrame == None:
            self.suit.getNumFrames(animName) - 1
        frames = endFrame - startFrame
        frameRate = self.suit.getFrameRate(animName)
        newRate = frames / duration
        playRate = newRate / frameRate
        ival = Sequence(ActorInterval(self.suit, animName, playRate=playRate))
        return ival

    def initIntervals(self):
        dur = Globals.LegalEagle.LiftOffTime
        nestPos = self.nest.getPos(render)
        airPos = nestPos + Vec3(0.0, 0.0, Globals.LegalEagle.LiftOffHeight)
        self.takeOffSeq = Sequence(Parallel(
            Sequence(
                Wait(dur * 0.6),
                LerpPosInterval(self.suit,
                                dur * 0.4,
                                startPos=nestPos,
                                pos=airPos,
                                blendType='easeInOut'))),
                                   Wait(1.5),
                                   Func(self.request, 'next'),
                                   name='%s.takeOffSeq-%i' %
                                   (self.__class__.__name__, self.index))
        self.landOnNestPosLerp = LerpPosInterval(self.suit,
                                                 1.0,
                                                 startPos=airPos,
                                                 pos=nestPos,
                                                 blendType='easeInOut')
        self.landingSeq = Sequence(Func(self.updateLandOnNestPosLerp),
                                   Parallel(self.landOnNestPosLerp),
                                   Func(self.request, 'next'),
                                   name='%s.landingSeq-%i' %
                                   (self.__class__.__name__, self.index))
        dur = Globals.LegalEagle.ChargeUpTime
        self.chargeUpPosLerp = LerpFunc(
            self.moveAlongChargeUpMopathFunc,
            fromData=0.0,
            toData=self.chargeUpMotionPath.getMaxT(),
            duration=dur,
            blendType='easeInOut')
        self.chargeUpAttackSeq = Sequence(
            Func(self.updateChargeUpPosLerp),
            self.chargeUpPosLerp,
            Func(self.request, 'next'),
            name='%s.chargeUpAttackSeq-%i' %
            (self.__class__.__name__, self.index))
        dur = Globals.LegalEagle.RetreatToNestTime
        self.retreatToNestPosLerp = LerpPosInterval(self.suit,
                                                    dur,
                                                    startPos=Vec3(0, 0, 0),
                                                    pos=airPos,
                                                    blendType='easeInOut')
        self.retreatToNestSeq = Sequence(Func(self.updateRetreatToNestPosLerp),
                                         self.retreatToNestPosLerp,
                                         Func(self.request, 'next'),
                                         name='%s.retreatToNestSeq-%i' %
                                         (self.__class__.__name__, self.index))
        dur = Globals.LegalEagle.RetreatToSkyTime
        self.retreatToSkyPosLerp = LerpFunc(
            self.moveAlongRetreatMopathFunc,
            fromData=0.0,
            toData=self.retreatToSkyMotionPath.getMaxT(),
            duration=dur,
            blendType='easeOut')
        self.retreatToSkySeq = Sequence(Func(self.updateRetreatToSkyPosLerp),
                                        self.retreatToSkyPosLerp,
                                        Func(self.request, 'next'),
                                        name='%s.retreatToSkySeq-%i' %
                                        (self.__class__.__name__, self.index))
        dur = Globals.LegalEagle.PreAttackTime
        self.preAttackLerpXY = LerpFunc(self.updateAttackXY,
                                        fromData=0.0,
                                        toData=1.0,
                                        duration=dur)
        self.preAttackLerpZ = LerpFunc(self.updateAttackZ,
                                       fromData=0.0,
                                       toData=1.0,
                                       duration=dur,
                                       blendType='easeOut')
        dur = Globals.LegalEagle.PostAttackTime
        self.postAttackPosLerp = LerpPosInterval(self.suit,
                                                 dur,
                                                 startPos=Vec3(0, 0, 0),
                                                 pos=Vec3(0, 0, 0))
        self.attackSeq = Sequence(
            Parallel(self.preAttackLerpXY, self.preAttackLerpZ),
            Func(self.updatePostAttackPosLerp),
            self.postAttackPosLerp,
            Func(self.request, 'next'),
            name='%s.attackSeq-%i' % (self.__class__.__name__, self.index))
        dur = Globals.LegalEagle.CooldownTime
        self.cooldownSeq = Sequence(Wait(dur),
                                    Func(self.request, 'next'),
                                    name='%s.cooldownSeq-%i' %
                                    (self.__class__.__name__, self.index))
        self.propTrack = Sequence(
            ActorInterval(self.prop, 'propeller', startFrame=0, endFrame=14))
        self.hoverOverNestSeq = Sequence(
            ActorInterval(self.suit,
                          'landing',
                          startFrame=10,
                          endFrame=20,
                          playRate=0.5),
            ActorInterval(self.suit,
                          'landing',
                          startFrame=20,
                          endFrame=10,
                          playRate=0.5))

    def initCollision(self):
        self.collSphere = CollisionSphere(0, 0, 0, 0)
        self.collSphere.setTangible(0)
        self.collNode = CollisionNode('%s-%s' %
                                      (self.CollSphereName, self.index))
        self.collNode.setIntoCollideMask(ToontownGlobals.WallBitmask)
        self.collNode.addSolid(self.collSphere)
        self.collNodePath = self.suit.attachNewNode(self.collNode)
        self.collNodePath.hide()
        self.accept('enter%s-%s' % (self.CollSphereName, self.index),
                    self.handleEnterSphere)
        self.setCollSphereToNest()

    def getInterestConeLength(self):
        return Globals.LegalEagle.InterestConeLength + Globals.LegalEagle.InterestConeOffset

    def isToonInView(self, toon):
        distanceThreshold = self.getInterestConeLength()
        angleThreshold = Globals.LegalEagle.InterestConeAngle
        toonPos = toon.getPos(render)
        nestPos = self.nest.getPos(render)
        distance = toon.getDistance(self.interestConeOrigin)
        if distance > distanceThreshold:
            return False
        if toonPos[1] > nestPos[1]:
            return False
        a = toon.getPos(render) - self.interestConeOrigin.getPos(render)
        a.normalize()
        b = Vec3(0, -1, 0)
        dotProduct = a.dot(b)
        angle = math.degrees(math.acos(dotProduct))
        if angle <= angleThreshold / 2.0:
            return True
        else:
            return False

    def update(self, dt, localPlayer):
        if Globals.Dev.NoLegalEagleAttacks:
            return
        inView = self.isToonInView(localPlayer.toon)
        if inView and not self.isEagleInterested:
            self.handleEnterInterest()
        elif inView and self.isEagleInterested:
            self.handleAgainInterest()
        elif not inView and self.isEagleInterested:
            self.handleExitInterest()

    def updateLockOnTask(self):
        dt = globalClock.getDt()
        targetPos = self.target.getPos(render)
        suitPos = self.suit.getPos(render)
        nestPos = self.nest.getPos(render)
        attackPos = Vec3(targetPos)
        attackPos[1] = nestPos[1] + Globals.LegalEagle.LockOnDistanceFromNest
        attackPos[2] += Globals.LegalEagle.VerticalOffset
        if attackPos[2] < nestPos[2]:
            attackPos[2] = nestPos[2]
        attackChangeVec = (attackPos -
                           suitPos) * Globals.LegalEagle.LockOnSpeed
        self.suit.setPos(suitPos + attackChangeVec * dt)
        return Task.cont

    def updateAttackXY(self, value):
        if Globals.LegalEagle.EagleAttackShouldXCorrect:
            x = self.readyToAttackPos.getX() + (self.attackTargetPos.getX(
            ) - self.readyToAttackPos.getX()) * value
            self.suit.setX(x)
        y = self.readyToAttackPos.getY() + (
            self.attackTargetPos.getY() - self.readyToAttackPos.getY()) * value
        self.suit.setY(y)

    def updateAttackZ(self, value):
        z = self.readyToAttackPos.getZ() + (
            self.attackTargetPos.getZ() - self.readyToAttackPos.getZ()) * value
        self.suit.setZ(z)

    def moveAlongChargeUpMopathFunc(self, value):
        self.chargeUpMotionPath.goTo(self.suit, value)
        self.suit.setPos(self.suit.getPos() + self.startOfChargeUpPos)

    def moveAlongRetreatMopathFunc(self, value):
        self.retreatToSkyMotionPath.goTo(self.suit, value)
        self.suit.setPos(self.suit.getPos() + self.startOfRetreatToSkyPos)

    def updateChargeUpPosLerp(self):
        self.startOfChargeUpPos = self.suit.getPos(render)

    def updateLandOnNestPosLerp(self):
        self.landOnNestPosLerp.setStartPos(self.suit.getPos())

    def updateRetreatToNestPosLerp(self):
        self.retreatToNestPosLerp.setStartPos(self.suit.getPos())

    def updateRetreatToSkyPosLerp(self):
        self.startOfRetreatToSkyPos = self.suit.getPos(render)

    def updatePostAttackPosLerp(self):
        suitPos = self.suit.getPos(render)
        finalPos = suitPos + Vec3(0, -Globals.LegalEagle.PostAttackLength, 0)
        self.postAttackPosLerp.setStartPos(suitPos)
        self.postAttackPosLerp.setEndPos(finalPos)

    def handleEnterSphere(self, collEntry):
        self.notify.debug('handleEnterSphere:%i' % self.index)
        messenger.send(CogdoFlyingLegalEagle.EnterLegalEagle,
                       [self, collEntry])

    def handleEnterInterest(self):
        self.notify.debug('handleEnterInterestColl:%i' % self.index)
        self.isEagleInterested = True
        messenger.send(CogdoFlyingLegalEagle.RequestAddTargetEventName,
                       [self.index])

    def handleAgainInterest(self):
        self.isEagleInterested = True
        messenger.send(CogdoFlyingLegalEagle.RequestAddTargetAgainEventName,
                       [self.index])

    def handleExitInterest(self):
        self.notify.debug('handleExitInterestSphere:%i' % self.index)
        self.isEagleInterested = False
        messenger.send(CogdoFlyingLegalEagle.RequestRemoveTargetEventName,
                       [self.index])

    def hasTarget(self):
        if self.target != None:
            return True
        else:
            return False
        return

    def setTarget(self, toon, elapsedTime=0.0):
        self.notify.debug('Setting eagle %i to target: %s, elapsed time: %s' %
                          (self.index, toon.getName(), elapsedTime))
        self.target = toon
        if self.state == 'Roost':
            self.request('next', elapsedTime)
        if self.state == 'ChargeUpAttack':
            messenger.send(CogdoFlyingLegalEagle.ChargingToAttackEventName,
                           [self.target.doId])

    def clearTarget(self, elapsedTime=0.0):
        self.notify.debug('Clearing target from eagle %i, elapsed time: %s' %
                          (self.index, elapsedTime))
        messenger.send(CogdoFlyingLegalEagle.CooldownEventName,
                       [self.target.doId])
        self.target = None
        if self.state in ['LockOnToon']:
            self.request('next', elapsedTime)
        return

    def leaveCooldown(self, elapsedTime=0.0):
        if self.state in ['Cooldown']:
            self.request('next', elapsedTime)

    def shouldBeInFrame(self):
        if self.state in ['TakeOff', 'LockOnToon', 'ChargeUpAttack']:
            return True
        elif self.state == 'Attack':
            distance = self.suit.getDistance(self.target)
            threshold = Globals.LegalEagle.EagleAndTargetDistCameraTrackThreshold
            suitPos = self.suit.getPos(render)
            targetPos = self.target.getPos(render)
            if distance > threshold and suitPos[1] > targetPos[1]:
                return True
        return False

    def getTarget(self):
        return self.target

    def onstage(self):
        self.suit.unstash()
        self.request('Roost')

    def offstage(self):
        self.suit.stash()
        self.request('Off')

    def gameStart(self, gameStartTime):
        self.gameStartTime = gameStartTime
        self.initCollision()

    def gameEnd(self):
        self.shutdownCollisions()

    def shutdownCollisions(self):
        self.ignoreAll()
        if self.collSphere != None:
            del self.collSphere
            self.collSphere = None
        if self.collNodePath != None:
            self.collNodePath.removeNode()
            del self.collNodePath
            self.collNodePath = None
        if self.collNode != None:
            del self.collNode
            self.collNode = None
        return

    def destroy(self):
        self.request('Off')
        self.detachPropeller()
        del self._screamSfx
        self.suit.cleanup()
        self.suit.removeNode()
        self.suit.delete()
        self.interestConeOrigin.removeNode()
        del self.interestConeOrigin
        self.nest = None
        self.target = None
        taskMgr.remove('updateLockOnTask-%i' % self.index)
        taskMgr.remove('exitLockOnToon-%i' % self.index)
        self.propTrack.clearToInitial()
        del self.propTrack
        del self.chargeUpMotionPath
        del self.retreatToSkyMotionPath
        self.takeOffSeq.clearToInitial()
        del self.takeOffSeq
        del self.landOnNestPosLerp
        self.landingSeq.clearToInitial()
        del self.landingSeq
        del self.chargeUpPosLerp
        self.chargeUpAttackSeq.clearToInitial()
        del self.chargeUpAttackSeq
        del self.retreatToNestPosLerp
        self.retreatToNestSeq.clearToInitial()
        del self.retreatToNestSeq
        del self.retreatToSkyPosLerp
        self.retreatToSkySeq.clearToInitial()
        del self.retreatToSkySeq
        del self.postAttackPosLerp
        self.attackSeq.clearToInitial()
        del self.attackSeq
        self.cooldownSeq.clearToInitial()
        del self.cooldownSeq
        self.hoverOverNestSeq.clearToInitial()
        del self.hoverOverNestSeq
        del self.preAttackLerpXY
        del self.preAttackLerpZ
        return

    def requestNext(self):
        self.request('next')

    def setCollSphereToNest(self):
        if hasattr(self, 'collSphere') and self.collSphere is not None:
            radius = Globals.LegalEagle.OnNestDamageSphereRadius
            self.collSphere.setCenter(
                Point3(0.0, -Globals.Level.LaffPowerupNestOffset[1],
                       self.suit.getHeight() / 2.0))
            self.collSphere.setRadius(radius)
        return

    def setCollSphereToTargeting(self):
        if hasattr(self, 'collSphere') and self.collSphere is not None:
            radius = Globals.LegalEagle.DamageSphereRadius
            self.collSphere.setCenter(Point3(0, 0, radius * 2))
            self.collSphere.setRadius(radius)
        return

    def enterRoost(self):
        self.notify.info("enter%s: '%s' -> '%s'" %
                         (self.newState, self.oldState, self.newState))
        self.hoverOverNestSeq.loop()
        self.propTrack.loop()
        self.setCollSphereToNest()

    def filterRoost(self, request, args):
        self.notify.debug("filter%s( '%s', '%s' )" %
                          (self.state, request, args))
        if request == self.state:
            return None
        elif request == 'next':
            return 'TakeOff'
        else:
            return self.defaultFilter(request, args)
        return None

    def exitRoost(self):
        self.notify.debug("exit%s: '%s' -> '%s'" %
                          (self.oldState, self.oldState, self.newState))
        self.hoverOverNestSeq.pause()
        self.setCollSphereToTargeting()

    def enterTakeOff(self, elapsedTime=0.0):
        self.notify.info(
            "enter%s: '%s' -> '%s', elapsedTime:%s" %
            (self.newState, self.oldState, self.newState, elapsedTime))
        self.takeOffSeq.start(elapsedTime)
        self.hoverOverNestSeq.loop()

    def filterTakeOff(self, request, args):
        self.notify.debug("filter%s( '%s', '%s' )" %
                          (self.state, request, args))
        if request == self.state:
            return None
        elif request == 'next':
            if self.hasTarget():
                return 'LockOnToon'
            else:
                return 'LandOnNest'
        else:
            return self.defaultFilter(request, args)
        return None

    def exitTakeOff(self):
        self.notify.debug("exit%s: '%s' -> '%s'" %
                          (self.oldState, self.oldState, self.newState))
        self.takeOffSeq.clearToInitial()
        self.hoverOverNestSeq.pause()

    def enterLockOnToon(self, elapsedTime=0.0):
        self.notify.info(
            "enter%s: '%s' -> '%s', elapsedTime:%s" %
            (self.newState, self.oldState, self.newState, elapsedTime))
        taskName = 'updateLockOnTask-%i' % self.index
        taskMgr.add(self.updateLockOnTask, taskName, 45, extraArgs=[])
        messenger.send(CogdoFlyingLegalEagle.LockOnToonEventName,
                       [self.target.doId])
        range = self.target.getDistance(
            self.interestConeOrigin) / self.getInterestConeLength()
        range = clamp(range, 0.0, 1.0)
        dur = Globals.LegalEagle.LockOnTime
        if self.oldState == 'TakeOff':
            dur *= range
        else:
            dur += Globals.LegalEagle.ExtraPostCooldownTime
        taskName = 'exitLockOnToon-%i' % self.index
        taskMgr.doMethodLater(dur, self.requestNext, taskName, extraArgs=[])

    def filterLockOnToon(self, request, args):
        self.notify.debug("filter%s( '%s', '%s' )" %
                          (self.state, request, args))
        if request == self.state:
            return None
        elif request == 'next':
            if self.hasTarget():
                return 'ChargeUpAttack'
            else:
                return 'RetreatToNest'
        else:
            return self.defaultFilter(request, args)
        return None

    def exitLockOnToon(self):
        self.notify.debug("exit%s: '%s' -> '%s'" %
                          (self.oldState, self.oldState, self.newState))
        taskMgr.remove('updateLockOnTask-%i' % self.index)
        taskMgr.remove('exitLockOnToon-%i' % self.index)

    def enterChargeUpAttack(self, elapsedTime=0.0):
        self.notify.info(
            "enter%s: '%s' -> '%s', elapsedTime:%s" %
            (self.newState, self.oldState, self.newState, elapsedTime))
        self.chargeUpAttackSeq.start(elapsedTime)
        messenger.send(CogdoFlyingLegalEagle.ChargingToAttackEventName,
                       [self.target.doId])

    def filterChargeUpAttack(self, request, args):
        self.notify.debug("filter%s( '%s', '%s' )" %
                          (self.state, request, args))
        if request == self.state:
            return None
        elif request == 'next':
            if self.hasTarget():
                return 'Attack'
            else:
                return 'RetreatToNest'
        else:
            return self.defaultFilter(request, args)
        return None

    def exitChargeUpAttack(self):
        self.notify.debug("exit%s: '%s' -> '%s'" %
                          (self.oldState, self.oldState, self.newState))
        self.chargeUpAttackSeq.clearToInitial()

    def enterAttack(self, elapsedTime=0.0):
        self.notify.info(
            "enter%s: '%s' -> '%s', elapsedTime:%s" %
            (self.newState, self.oldState, self.newState, elapsedTime))
        self.attackTargetPos = self.target.getPos(render)
        targetState = self.target.animFSM.getCurrentState().getName()
        self._screamSfx.play()
        if targetState == 'jumpAirborne':
            self.attackTargetPos[2] += Globals.LegalEagle.VerticalOffset
        else:
            self.attackTargetPos[
                2] += Globals.LegalEagle.PlatformVerticalOffset
        self.readyToAttackPos = self.suit.getPos(render)
        self.attackSeq.start(elapsedTime)

    def filterAttack(self, request, args):
        self.notify.debug("filter%s( '%s', '%s' )" %
                          (self.state, request, args))
        if request == self.state:
            return None
        elif request == 'next':
            return 'RetreatToSky'
        else:
            return self.defaultFilter(request, args)
        return None

    def exitAttack(self):
        self.notify.debug("exit%s: '%s' -> '%s'" %
                          (self.oldState, self.oldState, self.newState))
        self.attackSeq.clearToInitial()
        taskMgr.remove('updateAttackPosTask-%i' % self.index)

    def enterRetreatToSky(self, elapsedTime=0.0):
        self.notify.info(
            "enter%s: '%s' -> '%s', elapsedTime:%s" %
            (self.newState, self.oldState, self.newState, elapsedTime))
        self.retreatToSkySeq.start(elapsedTime)

    def filterRetreatToSky(self, request, args):
        self.notify.debug("filter%s( '%s', '%s' )" %
                          (self.state, request, args))
        if request == self.state:
            return None
        elif request == 'next':
            return 'Cooldown'
        else:
            return self.defaultFilter(request, args)
        return None

    def exitRetreatToSky(self):
        self.notify.debug("exit%s: '%s' -> '%s'" %
                          (self.oldState, self.oldState, self.newState))
        self.retreatToSkySeq.clearToInitial()

    def enterCooldown(self):
        if self.target != None:
            messenger.send(CogdoFlyingLegalEagle.CooldownEventName,
                           [self.target.doId])
        self.suit.stash()
        self.notify.info("enter%s: '%s' -> '%s'" %
                         (self.newState, self.oldState, self.newState))
        return

    def filterCooldown(self, request, args):
        self.notify.debug("filter%s( '%s', '%s' )" %
                          (self.state, request, args))
        if request == self.state:
            return None
        elif request == 'next':
            if self.hasTarget():
                return 'LockOnToon'
            else:
                return 'LandOnNest'
        else:
            return self.defaultFilter(request, args)
        return None

    def exitCooldown(self):
        self.notify.debug("exit%s: '%s' -> '%s'" %
                          (self.oldState, self.oldState, self.newState))
        self.suit.unstash()
        self.cooldownSeq.clearToInitial()
        if self.newState != 'Off':
            heightOffNest = Globals.LegalEagle.PostCooldownHeightOffNest
            nestPos = self.nest.getPos(render)
            if self.newState in ['LandOnNest']:
                self.suit.setPos(nestPos + Vec3(0, 0, heightOffNest))
            else:
                targetPos = self.target.getPos(render)
                attackPos = Vec3(targetPos)
                attackPos[1] = nestPos[1]
                attackPos[2] = nestPos[2] + heightOffNest
                self.suit.setPos(attackPos)

    def enterRetreatToNest(self, elapsedTime=0.0):
        self.notify.info(
            "enter%s: '%s' -> '%s', elapsedTime:%s" %
            (self.newState, self.oldState, self.newState, elapsedTime))
        self.retreatToNestSeq.start(elapsedTime)

    def filterRetreatToNest(self, request, args):
        self.notify.debug("filter%s( '%s', '%s' )" %
                          (self.state, request, args))
        if request == self.state:
            return None
        elif request == 'next':
            return 'LandOnNest'
        else:
            return self.defaultFilter(request, args)
        return None

    def exitRetreatToNest(self):
        self.retreatToNestSeq.clearToInitial()

    def enterLandOnNest(self, elapsedTime=0.0):
        self.notify.info(
            "enter%s: '%s' -> '%s', elapsedTime:%s" %
            (self.newState, self.oldState, self.newState, elapsedTime))
        self.landingSeq.start(elapsedTime)

    def filterLandOnNest(self, request, args):
        self.notify.debug("filter%s( '%s', '%s' )" %
                          (self.state, request, args))
        if request == self.state:
            return None
        elif request == 'next':
            if self.hasTarget():
                return 'TakeOff'
            else:
                return 'Roost'
        else:
            return self.defaultFilter(request, args)
        return None

    def exitLandOnNest(self):
        self.landingSeq.clearToInitial()
Beispiel #2
0
class GameContainer(ShowBase):
    def __init__(self):

        ShowBase.__init__(self)

        ########## Window configuration #########

        wp = WindowProperties()

        wp.setSize(1024, 860)
        wp.setTitle("")
        wp.setOrigin(-2, -2)

        self.win.requestProperties(wp)

        self.win.movePointer(0, wp.getXSize() / 2, wp.getYSize() / 2)
        print wp.getXSize() / 2, wp.getYSize() / 2

        ########## Gameplay settings #########

        self.gameMode = {"display": PLAY, "play": TERRAIN}

        self.level = 1.5

        self.mode_initialized = False

        ######### Camera #########

        self.disableMouse()

        self.mainCamera = Camera(self.camera)

        self.mainCamera.camObject.setHpr(0, 0, 0)

        self.loadLevel()

        ######### Events #########

        self.taskMgr.add(self.gameLoop, "gameLoop", priority=35)

        self.keys = {"w": 0, "s": 0, "a": 0, "d": 0, "space": 0, "escape": 0}

        self.accept("w", self.setKey, ["w", 1])
        self.accept("w-up", self.setKey, ["w", 0])
        self.accept("s", self.setKey, ["s", 1])
        self.accept("s-up", self.setKey, ["s", 0])
        self.accept("a", self.setKey, ["a", 1])
        self.accept("a-up", self.setKey, ["a", 0])
        self.accept("d", self.setKey, ["d", 1])
        self.accept("d-up", self.setKey, ["d", 0])
        self.accept("space", self.setKey, ["space", 1])
        self.accept("space-up", self.setKey, ["space", 0])
        self.accept("escape", self.setKey, ["escape", 1])
        self.accept("escape-up", self.setKey, ["escape", 0])
        self.accept("wheel_up", self.zoomCamera, [-1])
        self.accept("wheel_down", self.zoomCamera, [1])

        self.accept("window-event", self.handleWindowEvent)

        ######### GUI #########

        #self.fonts = {"failure" : loader.loadFont('myfont.ttf')}

        self.guiElements = []

        self._GCLK = None
        self._FT = None

        #Trigger game chain

        #self.enableParticles()

        #self.buildMainMenu()

    def setKey(self, key, value):

        self.keys[key] = value

    def zoomCamera(self, direction):

        if self.gameMode["play"] == TERRAIN:

            Camera.AVATAR_DIST += direction

    def toggleCursor(self, state):

        props = WindowProperties()
        props.setCursorHidden(state)
        base.win.requestProperties(props)

    def handleWindowEvent(self, window=None):

        wp = window.getProperties()

        self.win_center_x = wp.getXSize() / 2
        self.win_center_y = wp.getYSize() / 2

    def processKeys(self):

        if self.keys["escape"]:

            if self.gameMode["display"] == PLAY:

                self.switchDisplayMode(IN_GAME_MENU)

            elif self.gameMode["display"] == IN_GAME_MENU:

                self.switchDisplayMode(PLAY)

            self.setKey("escape", 0)

    ######### Level specific features #########

    def maintainTurrets(self):

        pass

    def switchDisplayMode(self, newGameMode):

        self.cleanupGUI()

        if self.gameMode["display"] == MAIN_MENU:

            pass

        elif self.gameMode["display"] == IN_GAME_MENU:

            if newGameMode == PLAY:

                render.clearFog()

                self.togglePhysicsPause()

            elif newGameMode == MAIN_MENU:

                pass

        elif self.gameMode["display"] == PLAY:

            if newGameMode == IN_GAME_MENU:

                self.togglePhysicsPause()

        self.gameMode["display"] = newGameMode

        self.mode_initialized = False

    def advanceLevel(self):

        self.level += .5

        self.loadLevel()

    def evenButtonPositions(self, button_spacing, button_height, num_buttons):

        centerOffset = (button_spacing / (2.0) if
                        (num_buttons % 2 == 0) else 0)

        buttonPositions = []

        current_pos = centerOffset + ((num_buttons - 1) / 2) * button_spacing

        for i in range(0, num_buttons):

            buttonPositions.append(current_pos + (button_height / 2.0))

            current_pos -= button_spacing

        return buttonPositions

    def buildInGameMenu(self):

        self.toggleCursor(False)

        resume_button = DirectButton(
            text="Resume",
            scale=.1,
            command=(lambda: self.switchDisplayMode(PLAY)),
            rolloverSound=None)
        main_menu_button = DirectButton(text="Main Menu",
                                        scale=.1,
                                        command=None,
                                        rolloverSound=None)
        options_button = DirectButton(text="Settings",
                                      scale=.1,
                                      command=None,
                                      rolloverSound=None)
        exit_button = DirectButton(text="Exit",
                                   scale=.1,
                                   command=exit,
                                   rolloverSound=None)

        BUTTON_SPACING = .2
        BUTTON_HEIGHT = resume_button.getSy()

        button_positions = self.evenButtonPositions(BUTTON_SPACING,
                                                    BUTTON_HEIGHT, 4)

        resume_button.setPos(Vec3(0, 0, button_positions[0]))
        main_menu_button.setPos(Vec3(0, 0, button_positions[1]))
        options_button.setPos(Vec3(0, 0, button_positions[2]))
        exit_button.setPos(Vec3(0, 0, button_positions[3]))

        self.guiElements.append(resume_button)
        self.guiElements.append(main_menu_button)
        self.guiElements.append(options_button)
        self.guiElements.append(exit_button)

    def buildMainMenu(self):

        self.toggleCursor(False)

        start_game_button = DirectButton(text="Start", scale=.1, command=None)
        select_level_button = DirectButton(text="Select Level",
                                           scale=.1,
                                           command=None)
        game_options_button = DirectButton(text="Settings",
                                           scale=.1,
                                           command=None)
        exit_button = DirectButton(text="Exit", scale=.1, command=exit)

        BUTTON_SPACING = .2
        BUTTON_HEIGHT = start_game_button.getSy()

        button_positions = self.evenButtonPositions(BUTTON_SPACING,
                                                    BUTTON_HEIGHT, 4)

        start_game_button.setPos(Vec3(0, 0, button_positions[0]))
        select_level_button.setPos(Vec3(0, 0, button_positions[1]))
        game_options_button.setPos(Vec3(0, 0, button_positions[2]))
        exit_button.setPos(Vec3(0, 0, button_positions[3]))

        self.guiElements.append(start_game_button)
        self.guiElements.append(select_level_button)
        self.guiElements.append(game_options_button)
        self.guiElements.append(exit_button)

        particles = Particles()
        particles.setPoolSize(1000)
        particles.setBirthRate(.1)
        particles.setLitterSize(10)
        particles.setLitterSpread(3)
        particles.setFactory("PointParticleFactory")
        particles.setRenderer("PointParticleRenderer")
        particles.setEmitter("SphereVolumeEmitter")
        particles.enable()

        self.effect = ParticleEffect("peffect", particles)
        self.effect.reparentTo(render)
        #self.effect.setPos(self.avatar.objectNP.getX(), self.avatar.objectNP.getY(), self.avatar.objectNP.getZ() + 5)
        self.effect.setPos(-1, 0, 0)
        self.effect.enable()

    def buildDeathScreen(self):

        self.toggleCursor(False)

        backFrame = DirectFrame(frameColor=(1, 0, 0, .7),
                                frameSize=(-.5, .5, -.3, .3),
                                pos=(0, 0, 0))

        deadMessage = DirectLabel(text="MISSION FAILURE",
                                  scale=.1,
                                  pos=(0, 0, .16),
                                  relief=None,
                                  text_font=None)

        restartButton = DirectButton(text="Restart",
                                     scale=.1,
                                     pos=(0, 0, -.1),
                                     command=self.resetLevel)

        deadMessage.reparentTo(backFrame)
        restartButton.reparentTo(backFrame)

        self.guiElements.append(backFrame)
        self.guiElements.append(deadMessage)
        self.guiElements.append(restartButton)

    def cleanupGUI(self):

        for guiElement in self.guiElements:

            guiElement.destroy()

    def loadSpaceTexture(self, level):

        if level < 10: return 'textures/space#.jpg'
        elif level < 15: pass

    def resetLevel(self):

        self.switchDisplayMode(PLAY)

        self.loadLevel(True)

    def loadLevel(self, reset=False):

        #Resets

        self.avatarActor = Actor("models/panda", {"walk": "models/panda-walk"})
        self.avatarActor.setScale(.5, .5, .5)
        self.avatarActor.setHpr(180, 0, 0)
        self.avatarActor.setCollideMask(BitMask32.allOff())

        self.asteroidManager = AsteroidManager()

        self.cTrav = CollisionTraverser()

        #Alternate modes

        if int(self.level) == self.level: self.gameMode["play"] = TERRAIN

        else: self.gameMode["play"] = SPACE

        #Specifics

        if self.gameMode["play"] == SPACE:

            if reset:

                self.avatar.reset()

            else:

                self.avatar = Avatar(self.avatarActor, self.level)

                self.avatar.objectNP.reparentTo(render)

            ########## Sky #########

            cubeMap = loader.loadCubeMap(self.loadSpaceTexture(self.level))
            self.spaceSkyBox = loader.loadModel('models/box')
            self.spaceSkyBox.setScale(100)
            self.spaceSkyBox.setBin('background', 0)
            self.spaceSkyBox.setDepthWrite(0)
            self.spaceSkyBox.setTwoSided(True)
            self.spaceSkyBox.setTexGen(TextureStage.getDefault(),
                                       TexGenAttrib.MWorldCubeMap)
            self.spaceSkyBox.setTexture(cubeMap, 1)
            parentNP = render.attachNewNode('parent')
            self.spaceSkyBox.reparentTo(parentNP)
            self.spaceSkyBox.setPos(-self.spaceSkyBox.getSx() / 2,
                                    -self.spaceSkyBox.getSy() / 2,
                                    -self.spaceSkyBox.getSz() / 2)

            ########## Collisions #########

            bound = self.avatarActor.getBounds()

            self.pandaBodySphere = CollisionSphere(
                bound.getCenter()[0] / self.avatar.objectNP.getSx() -
                self.avatar.objectNP.getX(),
                bound.getCenter()[1] / self.avatar.objectNP.getSx() -
                self.avatar.objectNP.getY(),
                bound.getCenter()[2] / self.avatar.objectNP.getSx() -
                self.avatar.objectNP.getZ(), 5)

            self.pandaBodySphere.setRadius(bound.getRadius() + 1)

            self.pandaBodySphereNode = CollisionNode("playerBodyRay")
            self.pandaBodySphereNode.addSolid(self.pandaBodySphere)
            self.pandaBodySphereNode.setFromCollideMask(BitMask32.bit(0))
            self.pandaBodySphereNode.setIntoCollideMask(BitMask32.allOff())

            self.pandaBodySphereNodepath = self.avatar.objectNP.attachNewNode(
                self.pandaBodySphereNode)
            self.pandaBodySphereNodepath.show()

            self.collisionNotifier = CollisionHandlerEvent()
            self.collisionNotifier.addInPattern("%fn-in")
            self.collisionNotifier.addOutPattern("%fn-out")

            self.cTrav.addCollider(self.pandaBodySphereNodepath,
                                   self.collisionNotifier)

            self.accept("playerGroundRayJumping-in",
                        self.avatar.handleCollisionEvent, ["in"])
            self.accept("playerGroundRayJumping-out",
                        self.avatar.handleCollisionEvent, ["out"])
            self.accept("playerBodyRay-in", self.avatar.handleCollisionEvent,
                        ["in"])

            self.asteroidManager.initialize(self.level)

        elif self.gameMode["play"] == TERRAIN:

            ########## Terrain #########

            #self.environ = loader.loadModel("../mystuff/test.egg")
            self.environ = loader.loadModel("models/environment")
            self.environ.setName("terrain")
            self.environ.reparentTo(render)
            self.environ.setPos(0, 0, 0)
            self.environ.setCollideMask(BitMask32.bit(0))

            ######### Physics #########

            self.enableParticles()

            gravityForce = LinearVectorForce(0, 0, -9.81)
            gravityForce.setMassDependent(False)
            gravityFN = ForceNode("world-forces")
            gravityFN.addForce(gravityForce)
            render.attachNewNode(gravityFN)
            base.physicsMgr.addLinearForce(gravityForce)

            self.avatarPhysicsActorNP = render.attachNewNode(
                ActorNode("player"))
            self.avatarPhysicsActorNP.node().getPhysicsObject().setMass(50.)
            self.avatarActor.reparentTo(self.avatarPhysicsActorNP)
            base.physicsMgr.attachPhysicalNode(
                self.avatarPhysicsActorNP.node())

            self.avatarPhysicsActorNP.setPos(15, 10, 5)

            ######### Game objects #########

            self.avatar = Avatar(self.avatarPhysicsActorNP, self.level)

            ######### Collisions #########

            self.pandaBodySphere = CollisionSphere(0, 0, 4, 3)

            self.pandaBodySphereNode = CollisionNode("playerBodySphere")
            self.pandaBodySphereNode.addSolid(self.pandaBodySphere)
            self.pandaBodySphereNode.setFromCollideMask(BitMask32.bit(0))
            self.pandaBodySphereNode.setIntoCollideMask(BitMask32.allOff())

            self.pandaBodySphereNodepath = self.avatar.objectNP.attachNewNode(
                self.pandaBodySphereNode)
            self.pandaBodySphereNodepath.show()

            self.pandaBodyCollisionHandler = PhysicsCollisionHandler()
            self.pandaBodyCollisionHandler.addCollider(
                self.pandaBodySphereNodepath, self.avatar.objectNP)

            #Keep player on ground

            self.pandaGroundSphere = CollisionSphere(0, 0, 1, 1)

            self.pandaGroundSphereNode = CollisionNode("playerGroundRay")
            self.pandaGroundSphereNode.addSolid(self.pandaGroundSphere)
            self.pandaGroundSphereNode.setFromCollideMask(BitMask32.bit(0))
            self.pandaGroundSphereNode.setIntoCollideMask(BitMask32.allOff())

            self.pandaGroundSphereNodepath = self.avatar.objectNP.attachNewNode(
                self.pandaGroundSphereNode)
            self.pandaGroundSphereNodepath.show()

            self.pandaGroundCollisionHandler = PhysicsCollisionHandler()
            self.pandaGroundCollisionHandler.addCollider(
                self.pandaGroundSphereNodepath, self.avatar.objectNP)

            #Notify when player lands

            self.pandaGroundRayJumping = CollisionSphere(0, 0, 1, 1)

            self.pandaGroundRayNodeJumping = CollisionNode(
                "playerGroundRayJumping")
            self.pandaGroundRayNodeJumping.addSolid(self.pandaGroundRayJumping)
            self.pandaGroundRayNodeJumping.setFromCollideMask(BitMask32.bit(0))
            self.pandaGroundRayNodeJumping.setIntoCollideMask(
                BitMask32.allOff())

            self.pandaGroundRayNodepathJumping = self.avatar.objectNP.attachNewNode(
                self.pandaGroundRayNodeJumping)
            self.pandaGroundRayNodepathJumping.show()

            self.collisionNotifier = CollisionHandlerEvent()
            self.collisionNotifier.addInPattern("%fn-in")
            self.collisionNotifier.addOutPattern("%fn-out")

            self.cTrav.addCollider(self.pandaGroundSphereNodepath,
                                   self.pandaGroundCollisionHandler)
            self.cTrav.addCollider(self.pandaGroundRayNodepathJumping,
                                   self.collisionNotifier)
            self.cTrav.addCollider(self.pandaBodySphereNodepath,
                                   self.pandaBodyCollisionHandler)

            self.accept("playerGroundRayJumping-in",
                        self.avatar.handleCollisionEvent, ["in"])
            self.accept("playerGroundRayJumping-out",
                        self.avatar.handleCollisionEvent, ["out"])
            self.accept("playerBodyRay-in", self.avatar.handleCollisionEvent,
                        ["in"])

    def togglePhysicsPause(self):

        if (self._GCLK == None):

            self.disableParticles()

            self._GCLK = ClockObject.getGlobalClock()
            self._FT = self._GCLK.getFrameTime()
            self._GCLK.setMode(ClockObject.MSlave)

        else:

            self._GCLK.setRealTime(self._FT)
            self._GCLK.setMode(ClockObject.MNormal)

            self.enableParticles()

            self._GCLK = None

    def gameLoop(self, task):

        dt = globalClock.getDt()

        self.processKeys()

        if self.gameMode["display"] == MAIN_MENU:

            if not self.mode_initialized:

                self.buildMainMenu()

                self.mode_initialized = True

        if self.gameMode["display"] == IN_GAME_MENU:

            if not self.mode_initialized:

                #Fog out background

                inGameMenuFogColor = (50, 150, 50)

                inGameMenuFog = Fog("inGameMenuFog")

                inGameMenuFog.setMode(Fog.MExponential)
                inGameMenuFog.setColor(*inGameMenuFogColor)
                inGameMenuFog.setExpDensity(.01)

                render.setFog(inGameMenuFog)

                self.buildInGameMenu()

                self.mode_initialized = True

        if self.gameMode["display"] == DEAD:

            if not self.mode_initialized:

                self.buildDeathScreen()

                self.mode_initialized = True

        if self.gameMode["display"] == PLAY:

            alive = self.avatar.states["alive"]

            if not self.mode_initialized:

                self.toggleCursor(True)

                self.last_mouse_x = self.win.getPointer(0).getX()
                self.last_mouse_y = self.win.getPointer(0).getY()

                self.mode_initialized = True

            if self.gameMode["play"] == TERRAIN:

                if alive:

                    self.maintainTurrets()
                    self.avatar.move(dt)

                else:
                    self.switchDisplayMode(DEAD)

            elif self.gameMode["play"] == SPACE:

                if alive:

                    self.asteroidManager.maintainAsteroidField(
                        self.avatar.objectNP.getPos(), self.avatar.speed,
                        Camera.AVATAR_DIST, dt)

                else:
                    self.switchDisplayMode(DEAD)

            if alive:

                #Handle keyboard input

                self.avatar.handleKeys(self.keys, self.gameMode["play"])

                ########## Mouse-based viewpoint rotation ##########

                mouse_pos = self.win.getPointer(0)

                current_mouse_x = mouse_pos.getX()
                current_mouse_y = mouse_pos.getY()

                #Side to side

                if self.gameMode["play"] == TERRAIN:

                    mouse_shift_x = current_mouse_x - self.last_mouse_x
                    self.last_mouse_x = current_mouse_x

                    if current_mouse_x < 5 or current_mouse_x >= (
                            self.win_center_x * 1.5):

                        base.win.movePointer(0, self.win_center_x,
                                             current_mouse_y)
                        self.last_mouse_x = self.win_center_x

                    yaw_shift = -((mouse_shift_x) * Camera.ROT_RATE[0])

                    self.avatar.yawRot += yaw_shift

                    self.avatar.objectNP.setH(self.avatar.yawRot)

                #Up and down

                mouse_shift_y = current_mouse_y - self.last_mouse_y
                self.last_mouse_y = current_mouse_y

                if current_mouse_y < 5 or current_mouse_y >= (
                        self.win_center_y * 1.5):

                    base.win.movePointer(0, current_mouse_x, self.win_center_y)
                    self.last_mouse_y = self.win_center_y

                pitch_shift = -((mouse_shift_y) * Camera.ROT_RATE[1])

                self.mainCamera.pitchRot += pitch_shift

                if self.mainCamera.pitchRot > Camera.FLEX_ROT_BOUND[0]:

                    self.mainCamera.pitchRot = Camera.FLEX_ROT_BOUND[0]

                elif self.mainCamera.pitchRot < -Camera.FLEX_ROT_BOUND[0]:

                    self.mainCamera.pitchRot = -Camera.FLEX_ROT_BOUND[0]

                xy_plane_cam_dist = Camera.AVATAR_DIST

                cam_x_adjust = xy_plane_cam_dist * sin(
                    radians(self.avatar.yawRot))
                cam_y_adjust = xy_plane_cam_dist * cos(
                    radians(self.avatar.yawRot))
                cam_z_adjust = Camera.ELEVATION

                self.mainCamera.camObject.setH(self.avatar.yawRot)
                self.mainCamera.camObject.setP(self.mainCamera.pitchRot)

                self.mainCamera.camObject.setPos(
                    self.avatar.objectNP.getX() + cam_x_adjust,
                    self.avatar.objectNP.getY() - cam_y_adjust,
                    self.avatar.objectNP.getZ() + cam_z_adjust)

                #Find collisions

                self.cTrav.traverse(render)

        return Task.cont