def generateInit(self):
     DistributedPartyActivity.generateInit(self)
     self.taskNameFireCannon = self.taskName('fireCannon')
     self.taskNameShoot = self.taskName('shootTask')
     self.taskNameFly = self.taskName('flyTask')
     self.gui = CannonGui()
Пример #2
0
 def generateInit(self):
     DistributedPartyActivity.generateInit(self)
     self.taskNameFireCannon = self.taskName('fireCannon')
     self.taskNameShoot = self.taskName('shootTask')
     self.taskNameFly = self.taskName('flyTask')
     self.gui = CannonGui()
class DistributedPartyCannonActivity(DistributedPartyActivity):
    # I never go anywhere without my party cannon!
    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyCannonActivity')
    HIT_GROUND = 0
    HIT_TOWER = 1
    HIT_WATER = 2
    REACTIVATE_CLOUD_TASK = 'PartyActivity_ReactivateLastCloud'
    RULES_DONE_EVENT = 'DistributedPartyCannonActivity_RULES_DONE_EVENT'
    LOCAL_TOON_LANDED_EVENT = 'DistributedPartyCannonActivity_LOCAL_TOON_LANDED_EVENT'
    NetDivisor = 100
    TimeFactor = 0.75
    BroadcastPeriod = 0.2

    def __init__(self, cr):
        DistributedPartyActivity.__init__(self, cr, ActivityIds.PartyCannon, ActivityTypes.Continuous, wantRewardGui=True)
        self.gui = None
        self.firingCannon = None
        self.shadowNode = None
        self.partyDoId = None
        self.splash = None
        self.dustCloud = None
        self.lastWakeTime = 0
        self.localFlyingDropShadow = None
        self.localFlyingToon = None
        self.localFlyingToonId = 0
        self._lastBroadcastTime = -self.BroadcastPeriod
        self._dirtyNewVel = None
        self.hitBumper = 0
        self.hitCloud = 0
        self.lastPos = Vec3(0, 0, 0)
        self.lastVel = Vec3(0, 0, 0)
        self.vel = Vec3(0, 0, 0)
        self.landingPos = Vec3(0, 0, 0)
        self.t = 0
        self.lastT = 0
        self.deltaT = 0
        self._lastCloudHit = None
        self.cameraPos = Vec3(0, -15.0, -25.0)
        self.cameraSpeed = 5.0
        self.camNode = None
        self.flyingToonOffsetRotation = 0
        self.flyingToonOffsetAngle = 0
        self.flyingToonOffsetX = 0
        self.flyingToonOffsetY = 0
        self.flyingToonCloudsHit = 0
        self.initialFlyVel = 0
        self._localPlayedBefore = False
        self.hitTrack = None
        self.cTrav = None
        self.flyColNode = None
        self.flyColNodePath = None
        self._flyingCollisionTaskName = None


    def generateInit(self):
        DistributedPartyActivity.generateInit(self)
        self.taskNameFireCannon = self.taskName('fireCannon')
        self.taskNameShoot = self.taskName('shootTask')
        self.taskNameFly = self.taskName('flyTask')
        self.gui = CannonGui()

    def load(self):
        self.notify.debug('load')
        DistributedPartyActivity.load(self)
        base.cr.playGame.hood.loader.loadClouds()
        base.cr.playGame.hood.loader.setCloudSwitch(1)
        self.shadow = loader.loadModel('phase_3/models/props/drop_shadow')
        self.shadowNode = hidden.attachNewNode('dropShadow')
        self.shadow.copyTo(self.shadowNode)
        self.shadowNode.setColor(0, 0, 0, 0.5)
        self.shadowNode.setBin('fixed', 0, 1)
        self.splash = Splash.Splash(render)
        self.dustCloud = DustCloud.DustCloud(render)
        self.dustCloud.setBillboardPointEye()
        self.sndHitGround = base.loadSfx('phase_4/audio/sfx/MG_cannon_hit_dirt.ogg')
        self.sndHitWater = base.loadSfx('phase_4/audio/sfx/MG_cannon_splash.ogg')
        self.sndHitHouse = base.loadSfx('phase_5/audio/sfx/AA_drop_sandbag.ogg')
        self.sndBounce1 = base.loadSfx('phase_13/audio/sfx/bounce1.ogg')
        self.sndBounce2 = base.loadSfx('phase_13/audio/sfx/bounce2.ogg')
        self.sndBounce3 = base.loadSfx('phase_13/audio/sfx/bounce3.ogg')
        self.onstage()
        self.sign.reparentTo(hidden)
        self.sign.setPos(-6.0, 10.0, 0.0)
        self.accept(FireworksStartedEvent, self.__handleFireworksStarted)
        self.accept(FireworksFinishedEvent, self.__handleFireworksFinished)

    def generate(self):
        DistributedPartyActivity.generate(self)
        self._doneCannons = False
        self._avId2trajectoryInfo = {}
        self._remoteToonFlyTaskName = 'remoteToonFlyTask-%s' % self.doId
        taskMgr.add(self._remoteToonFlyTask, self._remoteToonFlyTaskName, priority=45)
        self.d_cloudsColorRequest()

    def unload(self):
        self.notify.debug('unload')
        DistributedPartyActivity.unload(self)
        if self.shadowNode is not None:
            self.shadowNode.removeNode()
            del self.shadowNode
        if self.splash is not None:
            self.splash.destroy()
            del self.splash
        if self.dustCloud is not None:
            self.dustCloud.destroy()
            del self.dustCloud
        del self.sndHitHouse
        del self.sndHitGround
        del self.sndHitWater
        del self.sndBounce1
        del self.sndBounce2
        del self.sndBounce3
        if self.localFlyingToon:
            self.__resetToon(self.localFlyingToon)
            self.localFlyingToon.loop('neutral')
            self.localFlyingToon.setPlayRate(1.0, 'run')
            self.localFlyingToon = None
        self.ignoreAll()
        return

    def onstage(self):
        self.notify.debug('onstage')
        self.splash.reparentTo(render)
        self.dustCloud.reparentTo(render)

    def offstage(self):
        self.notify.debug('offstage')
        if self.splash is not None:
            self.splash.reparentTo(hidden)
            self.splash.stop()
        if self.dustCloud is not None:
            self.dustCloud.reparentTo(hidden)
            self.dustCloud.stop()
        return

    def disable(self):
        taskMgr.remove(self._remoteToonFlyTaskName)
        if self._flyingCollisionTaskName:
            taskMgr.remove(self._flyingCollisionTaskName)
        taskMgr.remove(self.taskNameFireCannon)
        taskMgr.remove(self.taskNameShoot)
        taskMgr.remove(self.taskNameFly)
        taskMgr.remove(DistributedPartyCannonActivity.REACTIVATE_CLOUD_TASK)
        self.ignoreAll()
        if self.localFlyingToonId:
            self.__stopCollisionHandler(self.localFlyingToon)
            self.__stopLocalFlyTask(self.localFlyingToonId)
            self.setMovie(PartyGlobals.CANNON_MOVIE_CLEAR, self.localFlyingToonId)
        if self.hitTrack is not None:
            self.hitTrack.finish()
            del self.hitTrack
            self.hitTrack = None
        DistributedPartyActivity.disable(self)
        return

    def delete(self):
        self.offstage()
        DistributedPartyActivity.delete(self)

    def setMovie(self, mode, toonId):
        self.notify.debug('%s setMovie(%s, %s)' % (self.doId, toonId, mode))
        if toonId != base.localAvatar.doId:
            return
        if mode == PartyGlobals.CANNON_MOVIE_CLEAR:
            self.landToon(toonId)
        elif mode == PartyGlobals.CANNON_MOVIE_LANDED:
            self.landToon(toonId)
        elif mode == PartyGlobals.CANNON_MOVIE_FORCE_EXIT:
            self.landToon(toonId)

    def __handleAvatarGone(self):
        self.setMovie(PartyGlobals.CANNON_MOVIE_CLEAR, 0)

    def handleToonDisabled(self, toonId):
        self.notify.warning('handleToonDisabled no implementation yet')

    def handleToonJoined(self, toonId):
        self.notify.warning('handleToonJoined no implementation yet')

    def isLocalToon(self, av):
        return base.localAvatar == av

    def isLocalToonId(self, toonId):
        return base.localAvatar.doId == toonId

    def getTitle(self):
        return TTLocalizer.PartyCannonActivityTitle

    def getInstructions(self):
        return TTLocalizer.PartyCannonActivityInstructions

    def hasPlayedBefore(self):
        return self._localPlayedBefore

    def displayRules(self):
        self.startRules()

    def handleRulesDone(self):
        self.finishRules()
        self._localPlayedBefore = True
        messenger.send(DistributedPartyCannonActivity.RULES_DONE_EVENT)

    def setCannonWillFire(self, cannonId, zRot, angle):
        self.notify.debug('setCannonWillFire: %d %d %d' % (cannonId, zRot, angle))
        cannon = base.cr.doId2do.get(cannonId)
        if cannon is None:
            self.notify.warning("Cannon has not been created, but we got this message. Don't show firing.")
            return
        if not cannon.getToonInside():
            self.notify.warning("setCannonWillFire, but no toon insde. Don't show firing")
            return
        if self.isLocalToon(cannon.getToonInside()):
            self.localFlyingToon = base.localAvatar
            self.localFlyingToonId = base.localAvatar.doId
            self.localFiringCannon = cannon
            self.flyingToonCloudsHit = 0
        cannon.updateModel(zRot, angle)
        toonId = cannon.getToonInside().doId
        task = Task.Task(self.__fireCannonTask)
        task.toonId = toonId
        task.cannon = cannon
        taskMgr.add(task, self.taskNameFireCannon)
        self.toonIds.append(toonId)
        return

    def __fireCannonTask(self, task):
        launchTime = 0.0
        toonId = task.toonId
        cannon = task.cannon
        toon = cannon.getToonInside()
        self.notify.debug(str(self.doId) + ' FIRING CANNON FOR TOON ' + str(toonId))
        if not cannon.isToonInside():
            return Task.done
        if self.isLocalToonId(toonId):
            self.inWater = 0
            startPos, startHpr, startVel, trajectory = self.__calcFlightResults(cannon, toonId, launchTime)

            self.notify.debug('start position: ' + str(startPos))
            self.notify.debug('start velocity: ' + str(startVel))
            self.notify.debug('time of launch: ' + str(launchTime))
        cannon.removeToonReadyToFire()
        shootTask = Task.Task(self.__shootTask, self.taskNameShoot)
        shootTask.info = {'toonId': toonId,
         'cannon': cannon}
        if self.isLocalToonId(toonId):
            self.flyingToonOffsetRotation = 0
            self.flyingToonOffsetAngle = 0
            self.flyingToonOffsetX = 0
            self.flyingToonOffsetY = 0
            self.hitCloud = 0
            self.initialFlyVel = INITIAL_VELOCITY
            self.camNode = NodePath(self.uniqueName('flyingCamera'))
            self.camNode.setScale(0.5)
            self.camNode.setPos(self.localFlyingToon.getPos())
            self.camNode.setHpr(self.localFlyingToon.getHpr())
            self.camNode.reparentTo(render)
            self.lastStartVel = startVel
            place = base.cr.playGame.getPlace()
            place.fsm.request('activity')
            toon.dropShadow.hide()
            self.localFlyingDropShadow = self.shadowNode.copyTo(hidden)
            vel = startVel
            toon.lookAt(toon.getPos() + Vec3(vel[0], vel[1], vel[2]))
            toon.setP(localAvatar, -90)
            hpr = toon.getHpr()
            toon.d_setPosHpr(startPos[0], startPos[1], startPos[2], hpr[0], hpr[1], hpr[2])
            self.localFlyingToon.wrtReparentTo(render)
            info = {}
            info['toonId'] = toonId
            info['trajectory'] = trajectory
            info['launchTime'] = launchTime
            info['toon'] = self.localFlyingToon
            info['hRot'] = cannon.getRotation()
            base.camera.wrtReparentTo(self.localFlyingToon)
            flyTask = Task.Task(self.__localFlyTask, self.taskNameFly)
            flyTask.info = info
            seqTask = Task.sequence(shootTask, flyTask)
            self.__startCollisionHandler()
            self.notify.debug('Disable standard local toon controls.')
            base.localAvatar.disableAvatarControls()
            frameTime = globalClock.getFrameTime()
            netLaunchTime = globalClockDelta.localToNetworkTime(launchTime + frameTime, bits=31)
            self.sendUpdate('setToonTrajectoryAi', [netLaunchTime,
             startPos[0],
             startPos[1],
             startPos[2],
             startHpr[0],
             startHpr[1],
             startHpr[2],
             startVel[0],
             startVel[1],
             startVel[2]])
        else:
            seqTask = shootTask
        taskMgr.add(seqTask, self.taskName('flyingToon') + '-' + str(toonId))
        toon.startSmooth()
        return Task.done

    def setToonTrajectory(self, avId, launchTime, x, y, z, h, p, r, vx, vy, vz):
        if avId == localAvatar.doId:
            return
        startPos = Vec3(x, y, z)
        startHpr = Vec3(h, p, r)
        startVel = Vec3(vx, vy, vz)
        startT = globalClockDelta.networkToLocalTime(launchTime, bits=31) + 0.2
        trajectory = Trajectory.Trajectory(0.0, startPos, startVel)
        self._avId2trajectoryInfo[avId] = ScratchPad(startPos=startPos, startHpr=startHpr, startVel=startVel, startT=startT, trajectory=trajectory)

    def _remoteToonFlyTask(self, task = None):
        ids2del = []
        frameTime = globalClock.getFrameTime()
        for avId, trajInfo in self._avId2trajectoryInfo.iteritems():
            trajectory = trajInfo.trajectory
            startTime = trajInfo.startT
            groundTime = trajectory.calcTimeOfImpactOnPlane(0.0) / self.TimeFactor + startTime
            now = frameTime
            if now < startTime:
                now = startTime
            if now > groundTime:
                now = groundTime
            t = max(0.0, now - startTime)
            t *= self.TimeFactor
            toon = self.cr.getDo(avId)
            if toon is None:
                ids2del.append(avId)
            else:
                toon.setFluidPos(trajectory.getPos(t))
                vel = trajectory.getVel(t)
                toon.lookAt(toon.getPos() + Vec3(vel[0], vel[1], vel[2]))
                toon.setP(toon, -90)

        for avId in ids2del:
            del self._avId2trajectoryInfo[avId]

        return Task.cont

    def __calcFlightResults(self, cannon, toonId, launchTime):
        def quantizeVec(vec, divisor):
            quantize = lambda value, divisor: float(int(value * int(divisor))) / int(divisor)
            vec[0] = quantize(vec[0], divisor)
            vec[1] = quantize(vec[1], divisor)
            vec[2] = quantize(vec[2], divisor)

        startPos = cannon.getToonFirePos()
        startHpr = cannon.getToonFireHpr()
        startVel = cannon.getToonFireVel()
        quantizeVec(startPos, self.NetDivisor)
        quantizeVec(startHpr, self.NetDivisor)
        quantizeVec(startVel, self.NetDivisor)
        trajectory = Trajectory.Trajectory(launchTime, startPos, startVel)
        self.trajectory = trajectory
        return startPos, startHpr, startVel, trajectory

    def __shootTask(self, task):
        task.info['cannon'].fire()
        toonId = task.info['toonId']
        toon = base.cr.doId2do.get(toonId)
        if toon:
            toon.loop('swim')
        else:
            self.notify.debug('__shootTask avoided a crash, toon %d not found' % toonId)
        if self.isLocalToonId(task.info['toonId']):
            self.localFlyingDropShadow.reparentTo(render)
            self.gui.enableAimKeys()
        return Task.done

    def d_setLanded(self, toonId):
        self.notify.debug('d_setLanded %s' % toonId)
        if self.isLocalToonId(toonId):
            if self.cr:
                self.sendUpdate('setLanded', [toonId])
            else:
                self.notify.debug('we avoided crash 2')

    def setLanded(self, toonId):
        if toonId in self._avId2trajectoryInfo:
            del self._avId2trajectoryInfo[toonId]

    def landToon(self, toonId):
        self.notify.debug('%s landToon' % self.doId)
        toon = base.cr.doId2do.get(toonId)
        if toon is not None:
            toon.resetLOD()
            if toon == base.localAvatar:
                self.__stopCollisionHandler(base.localAvatar)
            toon.wrtReparentTo(render)
            self.__setToonUpright(toon)
            toon.setPlayRate(1.0, 'run')
            toon.startSmooth()
            toon.setScale(1.0)
            self.ignore(toon.uniqueName('disable'))
            self.__cleanupFlyingToonData(toon)
            toon.dropShadow.show()
        place = base.cr.playGame.getPlace()
        if place is not None:
            if not hasattr(place, 'fsm'):
                return
        if toon is not None and toon == base.localAvatar:
            self.__localDisplayLandedResults()
        return

    def __localDisplayLandedResults(self):
        if self.flyingToonCloudsHit > 0:
            self._doneCannons = True
        else:
            self.__localToonDoneLanding()

    def handleRewardDone(self):
        DistributedPartyActivity.handleRewardDone(self)
        if self._doneCannons:
            self.__localToonDoneLanding()

    def __localToonDoneLanding(self):
        base.cr.playGame.getPlace().fsm.request('walk')
        self.notify.debug('__localToonDoneLanding')
        base.localAvatar.collisionsOn()
        base.localAvatar.startPosHprBroadcast()
        base.localAvatar.enableAvatarControls()
        messenger.send(DistributedPartyCannonActivity.LOCAL_TOON_LANDED_EVENT)

    def __setToonUpright(self, toon, pos = None):
        if toon:
            if self.inWater:
                toon.setP(0)
                toon.setR(0)
                return
            if not pos:
                pos = toon.getPos(render)
            toon.setPos(render, pos)
            toon.loop('neutral')
            if self.localFiringCannon and hasattr(self.localFiringCannon, 'cannonNode'):
                if self.localFiringCannon.cannonNode:
                    toon.lookAt(self.localFiringCannon.cannonNode)
                else:
                    self.notify.debug('we avoided crash 1.')
            toon.setP(0)
            toon.setR(0)
            toon.setScale(1, 1, 1)

    def __resetToonToCannon(self, avatar):
        self.notify.debug('__resetToonToCannon')
        if not avatar and self.localFlyingToonId:
            avatar = base.cr.doId2do.get(self.localFlyingToonId, None)
        if avatar:
            self.__resetToon(avatar)
        return

    def __resetToon(self, avatar, pos = None):
        self.notify.debug('__resetToon')
        if avatar:
            self.__stopCollisionHandler(avatar)
            self.__setToonUpright(avatar, pos)
            if self.isLocalToonId(avatar.doId):
                self.notify.debug('toon setting position to %s' % pos)
                if pos:
                    base.localAvatar.setPos(pos)
                base.camera.reparentTo(avatar)
            self.d_setLanded(avatar.doId)

    def __updateFlightVelocity(self, trajectory):
        hpr = LRotationf(self.flyingToonOffsetRotation, 0, 0)
        newVel = hpr.xform(self.lastStartVel)
        hpr = LRotationf(0, self.flyingToonOffsetAngle, 0)
        zVel = hpr.xform(self.lastStartVel).getZ()
        if zVel < newVel.getZ():
            newVel.setZ(zVel)
        trajectory.setStartVel(newVel)
        now = globalClock.getFrameTime()
        if now - self._lastBroadcastTime >= self.BroadcastPeriod:
            self._dirtyNewVel = newVel
        if self._dirtyNewVel:
            self.sendUpdate('updateToonTrajectoryStartVelAi', [self._dirtyNewVel[0], self._dirtyNewVel[1], self._dirtyNewVel[2]])
            self._lastBroadcastTime = now
            self._dirtyNewVel = None
        return

    def updateToonTrajectoryStartVel(self, avId, vx, vy, vz):
        if avId == localAvatar.doId:
            return
        if avId in self._avId2trajectoryInfo:
            self._avId2trajectoryInfo[avId].trajectory.setStartVel(Vec3(vx, vy, vz))

    def __isFlightKeyPressed(self):
        return self.gui.leftPressed or self.gui.rightPressed or self.gui.upPressed or self.gui.downPressed

    def __moveFlyingToon(self, toon):
        toonP = toon.getP(render)
        isToonFlyingHorizontal = toonP > -150 and toonP < -30
        OFFSET = 0.25
        rotVel = 0
        if self.gui.leftPressed:
            if isToonFlyingHorizontal:
                rotVel += CANNON_ROTATION_VEL
            else:
                self.flyingToonOffsetX -= OFFSET
        if self.gui.rightPressed:
            if isToonFlyingHorizontal:
                rotVel -= CANNON_ROTATION_VEL
            else:
                self.flyingToonOffsetX += OFFSET
        self.flyingToonOffsetRotation += rotVel * globalClock.getDt()
        angVel = 0
        if self.gui.upPressed:
            if not isToonFlyingHorizontal:
                self.flyingToonOffsetY -= OFFSET
        if self.gui.downPressed:
            if isToonFlyingHorizontal:
                angVel += CANNON_ANGLE_VEL
            else:
                self.flyingToonOffsetY += OFFSET
        self.flyingToonOffsetAngle += angVel * globalClock.getDt()

    def __stopLocalFlyTask(self, toonId):
        taskMgr.remove(self.taskName('flyingToon') + '-' + str(toonId))
        self.gui.disableAimKeys()

    def __localFlyTask(self, task):
        toon = task.info['toon']
        if toon.isEmpty():
            self.__resetToonToCannon(self.localFlyingToon)
            return Task.done
        curTime = task.time + task.info['launchTime']
        t = curTime
        t *= self.TimeFactor
        self.lastT = self.t
        self.t = t
        deltaT = self.t - self.lastT
        self.deltaT = deltaT
        if self.hitBumper:
            pos = self.lastPos + self.lastVel * deltaT
            vel = self.lastVel
            self.lastVel += Vec3(0, 0, -32.0) * deltaT
            self.lastPos = pos
            toon.setFluidPos(pos)
            lastR = toon.getR()
            toon.setR(lastR - deltaT * self.angularVel * 2.0)
            cameraView = 0
        else:
            if not self.hitCloud and self.__isFlightKeyPressed():
                self.__moveFlyingToon(toon)
                self.__updateFlightVelocity(task.info['trajectory'])
            if self.hitCloud == 1:
                vel = task.info['trajectory'].getVel(t)
                startPos = toon.getPos(render)
                task.info['trajectory'].setStartTime(t)
                task.info['trajectory'].setStartPos(startPos)
                task.info['trajectory'].setStartVel(self.lastVel)
                toon.lookAt(toon.getPos() + vel)
                toon.setH(-toon.getH())
                now = globalClock.getFrameTime()
                netLaunchTime = globalClockDelta.localToNetworkTime(now, bits=31)
                hpr = toon.getHpr()
                self.sendUpdate('setToonTrajectoryAi', [netLaunchTime,
                 startPos[0],
                 startPos[1],
                 startPos[2],
                 hpr[0],
                 hpr[1],
                 hpr[2],
                 self.lastVel[0],
                 self.lastVel[1],
                 self.lastVel[2]])
                self._lastBroadcastTime = now
                self._dirtyNewVel = None
                self.flyingToonOffsetRotation = 0
                self.flyingToonOffsetAngle = 0
                self.flyingToonOffsetX = 0
                self.flyingToonOffsetY = 0
                self.hitCloud = 2
            pos = task.info['trajectory'].getPos(t)
            toon.setFluidPos(pos)
            toon.setFluidPos(toon, self.flyingToonOffsetX, self.flyingToonOffsetY, 0)
            vel = task.info['trajectory'].getVel(t)
            toon.lookAt(toon.getPos() + Vec3(vel[0], vel[1], vel[2]))
            toon.setP(toon.getP() - 90)
            cameraView = 2
            if self.hitCloud == 2:
                self.lastStartVel = vel
                self.hitCloud = 0
        shadowPos = toon.getPos()
        shadowPos.setZ(SHADOW_Z_OFFSET)
        self.localFlyingDropShadow.setPos(shadowPos)
        if pos.getZ() < -20 or pos.getZ() > 1000:
            self.notify.debug('stopping fly task toon.getZ()=%.2f' % pos.getZ())
            self.__resetToonToCannon(self.localFlyingToon)
            return Task.done
        self.__setFlyingCameraView(task.info['toon'], cameraView, deltaT)
        return Task.cont

    def __setFlyingCameraView(self, toon, view, deltaT):
        if toon != base.localAvatar:
            return
        lookAt = toon.getPos(render)
        hpr = toon.getHpr(render)
        if view == 0:
            base.camera.wrtReparentTo(render)
            base.camera.lookAt(lookAt)
        elif view == 1:
            base.camera.reparentTo(render)
            base.camera.setPos(render, 100, 100, 35.25)
            base.camera.lookAt(render, lookAt)
        elif view == 2:
            if base.camera.getParent() != self.camNode:
                base.camera.wrtReparentTo(self.camNode)
                base.camera.setPos(self.cameraPos)
                base.camera.lookAt(toon)
            self.camNode.setPos(toon.getPos(render))
            camHpr = self.camNode.getHpr(toon)
            vec = -Point3(0, 0, 0) - camHpr
            relativeSpeed = math.pow(vec.length() / 60.0, 2) + 0.1
            newHpr = camHpr + vec * deltaT * self.cameraSpeed * relativeSpeed
            self.camNode.setHpr(toon, newHpr)
            base.camera.lookAt(self.camNode)
            base.camera.setR(render, 0)

    def __cleanupFlyingToonData(self, toon):
        self.notify.debug('__cleanupFlyingToonData')
        if toon:
            toon.dropShadow.show()
            self.toonIds.remove(toon.doId)
            if self.isLocalToon(toon):
                if self.localFlyingDropShadow != None:
                    self.localFlyingDropShadow.removeNode()
                    self.localFlyingDropShadow = None
                self.hitBumper = 0
                self.angularVel = 0
                self.vel = Vec3(0, 0, 0)
                self.lastVel = Vec3(0, 0, 0)
                self.lastPos = Vec3(0, 0, 0)
                self.landingPos = Vec3(0, 0, 0)
                self.t = 0
                self.lastT = 0
                self.deltaT = 0
                self.lastWakeTime = 0
                self.localFlyingToon = None
                self.localFlyingToonId = 0
                self.localFiringCannon = None
                if hasattr(self, 'camNode') and self.camNode:
                    self.camNode.removeNode()
                    self.camNode = None
        return

    def __startCollisionHandler(self):
        self.flyColSphere = CollisionSphere(0, 0, self.localFlyingToon.getHeight() / 2.0, 1.0)
        self.flyColNode = CollisionNode(self.uniqueName('flySphere'))
        self.flyColNode.setCollideMask(ToontownGlobals.WallBitmask | ToontownGlobals.FloorBitmask)
        self.flyColNode.addSolid(self.flyColSphere)
        self.flyColNodePath = self.localFlyingToon.attachNewNode(self.flyColNode)
        self.flyColNodePath.setColor(1, 0, 0, 1)
        self._activeCollisions = set()
        self.handler = CollisionHandlerQueue()
        self._flyingCollisionTaskName = 'checkFlyingToonCollision-%s' % self.doId
        taskMgr.add(self._checkFlyingToonCollision, self._flyingCollisionTaskName)
        base.cTrav.addCollider(self.flyColNodePath, self.handler)

    def __stopCollisionHandler(self, avatar):
        self.notify.debug('%s __stopCollisionHandler' % self.doId)
        if self._flyingCollisionTaskName:
            taskMgr.remove(self._flyingCollisionTaskName)
            self._flyingCollisionTaskName = None
            self._activeCollisions = set()
        if avatar:
            avatar.loop('neutral')
            if self.flyColNode:
                self.flyColNode = None
            self.flyColSphere = None
            if self.flyColNodePath:
                base.cTrav.removeCollider(self.flyColNodePath)
                self.flyColNodePath.removeNode()
                self.flyColNodePath = None
            self.handler = None
        return

    def _checkFlyingToonCollision(self, task = None):
        curCollisions = set()
        if self.handler.getNumEntries():
            self.handler.sortEntries()
            i = self.handler.getNumEntries()
            activeEntry = None
            while i > 0:
                entry = self.handler.getEntry(i - 1)
                k = (str(entry.getFromNodePath()), str(entry.getIntoNodePath()))
                curCollisions.add(k)
                if activeEntry is None and k not in self._activeCollisions:
                    activeEntry = entry
                    self._activeCollisions.add(k)
                i -= 1

            if activeEntry is not None:
                self.__handleFlyingToonCollision(activeEntry)
            if self.handler:
                self.handler.clearEntries()
        for k in list(self._activeCollisions):
            if k not in curCollisions:
                self._activeCollisions.remove(k)

        return Task.cont

    def __handleFlyingToonCollision(self, collisionEntry):
        self.notify.debug('%s __handleToonCollision' % self.doId)
        if self.localFlyingToon == None or self.flyColNode == None:
            return
        hitNode = collisionEntry.getIntoNode().getName()
        self.notify.debug('hitNode = %s' % hitNode)
        self.notify.debug('hitNodePath.getParent = %s' % collisionEntry.getIntoNodePath().getParent())
        self.vel = self.trajectory.getVel(self.t)
        vel = self.trajectory.getVel(self.t)
        vel.normalize()
        if self.hitBumper:
            vel = self.lastVel * 1
            vel.normalize()
        self.notify.debug('normalized vel=%s' % vel)
        solid = collisionEntry.getInto()
        intoNormal = collisionEntry.getSurfaceNormal(collisionEntry.getIntoNodePath())
        self.notify.debug('old intoNormal = %s' % intoNormal)
        intoNormal = collisionEntry.getSurfaceNormal(render)
        self.notify.debug('new intoNormal = %s' % intoNormal)
        hitPylonAboveWater = False
        hitPylonBelowWater = False
        hitNormal = intoNormal
        if hitNode.find('cSphere') == 0 or hitNode.find('treasureSphere') == 0 or hitNode.find('prop') == 0 or hitNode.find('distAvatarCollNode') == 0 or hitNode.find('CannonSphere') == 0 or hitNode.find('plotSphere') == 0 or hitNode.find('flySphere') == 0 or hitNode.find('FishingSpotSphere') == 0 or hitNode.find('TrampolineTrigger') == 0 or hitNode == 'gagtree_collision' or hitNode == 'sign_collision' or hitNode == 'FlowerSellBox' or hitPylonBelowWater:
            self.notify.debug('--------------hit and ignoring %s' % hitNode)
            return
        if vel.dot(hitNormal) > 0 and not hitNode == 'collision_roof' and not hitNode == 'collision_fence':
            self.notify.debug('--------------hit and ignoring backfacing %s, dot=%s' % (hitNode, vel.dot(hitNormal)))
            return
        intoNode = collisionEntry.getIntoNodePath()
        bumperNodes = ['sky_collision'] + PartyCannonCollisions['bounce'] + PartyCannonCollisions['fence']
        cloudBumpers = PartyCannonCollisions['clouds']
        bumperNodes += cloudBumpers
        if hitNode in bumperNodes or hitNode.find('cogPie') == 0 or PartyCannonCollisions['trampoline_bounce'] in hitNode:
            if hitNode == 'sky_collision' or hitNode in PartyCannonCollisions['fence'] or hitNode.find('cogPie') == 0:
                self.__hitFence(self.localFlyingToon, collisionEntry)
            elif PartyCannonCollisions['trampoline_bounce'] in hitNode or hitNode in PartyCannonCollisions['bounce']:
                if hitNode == 'wall_collision':
                    hitSound = self.sndBounce2
                else:
                    hitSound = self.sndBounce3
                self.hitCloud = 1
                self.__hitBumper(self.localFlyingToon, collisionEntry, hitSound, kr=0.09, angVel=5)
                self.hitBumper = 0
            elif hitNode in cloudBumpers:
                self.__hitCloudPlatform(self.localFlyingToon, collisionEntry)
            elif hitNode == 'statuaryCol':
                self.__hitStatuary(self.localFlyingToon, collisionEntry)
            else:
                self.notify.debug('*************** hit something else ************')
            return
        else:
            self.__stopCollisionHandler(self.localFlyingToon)
            self.__stopLocalFlyTask(self.localFlyingToonId)
            self.notify.debug('stopping flying since we hit %s' % hitNode)
        if self.isLocalToonId(self.localFlyingToon.doId):
            base.camera.wrtReparentTo(render)
        if self.localFlyingDropShadow:
            self.localFlyingDropShadow.reparentTo(hidden)
        pos = collisionEntry.getSurfacePoint(render)
        hpr = self.localFlyingToon.getHpr()
        hitPos = collisionEntry.getSurfacePoint(render)
        pos = hitPos
        self.landingPos = pos
        self.notify.debug('hitNode, Normal = %s,%s' % (hitNode, intoNormal))
        track = Sequence()
        track.append(Func(self.localFlyingToon.wrtReparentTo, render))
        if self.isLocalToonId(self.localFlyingToon.doId):
            track.append(Func(self.localFlyingToon.collisionsOff))
        if hitNode in PartyCannonCollisions['ground']:
            track.append(Func(self.__hitGround, self.localFlyingToon, pos))
            track.append(Wait(1.0))
            track.append(Func(self.__setToonUpright, self.localFlyingToon, self.landingPos))
        elif hitNode in PartyCannonCollisions['fence']:
            track.append(Func(self.__hitFence, self.localFlyingToon, collisionEntry))
        elif hitNode == 'collision3':
            track.append(Func(self.__hitWater, self.localFlyingToon, pos, collisionEntry))
            track.append(Wait(2.0))
            track.append(Func(self.__setToonUpright, self.localFlyingToon, self.landingPos))
        elif hitNode.find('cloudSphere') == 0:
            track.append(Func(self.__hitCloudPlatform, self.localFlyingToon, collisionEntry))
        else:
            self.notify.warning('************* unhandled hitNode=%s parent =%s' % (hitNode, collisionEntry.getIntoNodePath().getParent()))
        track.append(Func(self.d_setLanded, self.localFlyingToonId))
        if self.isLocalToonId(self.localFlyingToonId):
            track.append(Func(self.localFlyingToon.collisionsOn))
        if self.hitTrack:
            self.hitTrack.finish()
        self.hitTrack = track
        self.hitTrack.start()
        return

    def __hitBumper(self, avatar, collisionEntry, sound, kr = 0.6, angVel = 1):
        self.hitBumper = 1
        base.playSfx(sound)
        hitP = avatar.getPos(render)
        self.lastPos = hitP
        normal = collisionEntry.getSurfaceNormal(render)
        self.notify.debug('normal = %s' % normal)
        vel = self.vel * 1
        speed = vel.length()
        vel.normalize()
        self.notify.debug('old vel = %s' % vel)
        if self.hitCloud:
            centerVec = Vec3(-avatar.getPos(self.getParentNodePath()))
            centerVec.setZ(0)
            d = centerVec.length() / 15.0
            centerVec.setZ(abs(centerVec.length() * math.sin(70.0)))
            centerVec.normalize()
            newVel = centerVec * d + normal * 0.2
            newVel = newVel * (kr * speed)
            self.initialFlyVel = kr * speed
        else:
            newVel = (normal * 2.0 + vel) * (kr * speed)
        self.lastVel = newVel
        self.notify.debug('new vel = %s' % newVel)
        self.angularVel = angVel * 360
        if self.hitCloud:
            return
        t = Sequence(Func(avatar.pose, 'lose', 110))
        t.start()

    def __hitGround(self, avatar, pos, extraArgs = []):
        self.notify.debug('__hitGround')
        hitP = avatar.getPos(render)
        self.notify.debug('hitGround pos = %s, hitP = %s' % (pos, hitP))
        self.notify.debug('avatar hpr = %s' % avatar.getHpr())
        avatar.setPos(pos[0], pos[1], pos[2] + avatar.getHeight() / 3.0)
        avatar.setHpr(avatar.getH(), -135, 0)
        self.notify.debug('parent = %s' % avatar.getParent())
        self.notify.debug('pos = %s, hpr = %s' % (avatar.getPos(render), avatar.getHpr(render)))
        self.__playDustCloud(avatar, pos)
        base.playSfx(self.sndHitGround)
        avatar.setPlayRate(2.0, 'run')
        avatar.loop('run')

    def __playDustCloud(self, toon, pos):
        self.dustCloud.setPos(render, pos[0], pos[1], pos[2] + toon.getHeight() / 3.0)
        self.dustCloud.setScale(0.35)
        self.dustCloud.play()

    def __hitFence(self, avatar, collisionEntry, extraArgs = []):
        self.notify.debug('__hitFence')
        self.__hitBumper(avatar, collisionEntry, self.sndHitHouse, kr=0.2, angVel=3)

    def __hitWater(self, avatar, pos, collisionEntry, extraArgs = []):
        hitP = avatar.getPos(render)
        if hitP[2] > ToontownGlobals.EstateWakeWaterHeight:
            self.notify.debug('we hit the ground before we hit water')
            self.__hitGround(avatar, pos, extraArgs)
            return
        self.notify.debug('hit water')
        hitP = avatar.getPos(render)
        avatar.loop('neutral')
        self.splash.setPos(hitP)
        self.splash.setZ(ToontownGlobals.EstateWakeWaterHeight)
        self.splash.setScale(2)
        self.splash.play()
        base.playSfx(self.sndHitWater)
        place = base.cr.playGame.getPlace()

    def __hitStatuary(self, avatar, collisionEntry, extraArgs = []):
        self.__hitBumper(avatar, collisionEntry, self.sndHitHouse, kr=0.4, angVel=5)

    def d_cloudsColorRequest(self):
        self.notify.debug('cloudsColorRequest')
        self.sendUpdate('cloudsColorRequest')

    def cloudsColorResponse(self, cloudColorList):
        self.notify.debug('cloudsColorResponse: %s' % cloudColorList)
        for cloudColor in cloudColorList:
            self.setCloudHit(*cloudColor)

    def d_requestCloudHit(self, cloudNumber, color):
        self.sendUpdate('requestCloudHit', [cloudNumber,
         color.getX(),
         color.getY(),
         color.getZ()])

    def setCloudHit(self, cloudNumber, r, g, b):
        cloud = render.find('**/cloud-%d' % cloudNumber)
        if not cloud.isEmpty():
            cloud.setColor(r, g, b, 1.0)
        else:
            self.notify.debug('Could not find cloud-%d' % cloudNumber)

    def __hitCloudPlatform(self, avatar, collisionEntry, extraArgs = []):
        if not self.hitBumper and not self.hitCloud:
            self.hitCloud = 1
            self.__hitBumper(avatar, collisionEntry, self.sndBounce1, kr=0.35, angVel=5)
            self.hitBumper = 0
            if self._lastCloudHit is None:
                cloud = collisionEntry.getIntoNodePath().getParent()
                self._lastCloudHit = cloud
                cloud.setColor(base.localAvatar.style.getHeadColor())
                cloudNumber = int(cloud.getNetTag('number'))
                self.d_requestCloudHit(cloudNumber, base.localAvatar.style.getHeadColor())
                self.__playDustCloud(avatar, collisionEntry.getSurfacePoint(render))
                self.flyingToonCloudsHit += 1
                taskMgr.doMethodLater(0.25, self.__reactivateLastCloudHit, DistributedPartyCannonActivity.REACTIVATE_CLOUD_TASK)
        return

    def __reactivateLastCloudHit(self, task):
        self._lastCloudHit = None
        return Task.done

    def __handleFireworksStarted(self):
        self.notify.debug('__handleFireworksStarted')
        base.cr.playGame.hood.loader.fadeClouds()

    def __handleFireworksFinished(self):
        self.notify.debug('__handleFireworksFinished')
        if self.__checkHoodValidity():
            base.cr.playGame.hood.loader.fadeClouds()
        else:
            self.notify.debug('Toon has left the party')

    def __checkHoodValidity(self):
        if hasattr(base.cr.playGame, 'hood') and base.cr.playGame.hood and hasattr(base.cr.playGame.hood, 'loader') and base.cr.playGame.hood.loader and hasattr(base.cr.playGame.hood.loader, 'geom') and base.cr.playGame.hood.loader.geom:
            return True
        else:
            return False

    def handleToonExited(self, toonId):
        self.notify.debug('DistributedPartyCannonActivity handleToonExited( toonId=%s ) ' % toonId)
        if toonId in self.cr.doId2do:
            self.notify.warning('handleToonExited is not defined')
Пример #4
0
class DistributedPartyCannonActivity(DistributedPartyActivity):
    # I never go anywhere without my party cannon!
    notify = DirectNotifyGlobal.directNotify.newCategory(
        'DistributedPartyCannonActivity')
    HIT_GROUND = 0
    HIT_TOWER = 1
    HIT_WATER = 2
    REACTIVATE_CLOUD_TASK = 'PartyActivity_ReactivateLastCloud'
    RULES_DONE_EVENT = 'DistributedPartyCannonActivity_RULES_DONE_EVENT'
    LOCAL_TOON_LANDED_EVENT = 'DistributedPartyCannonActivity_LOCAL_TOON_LANDED_EVENT'
    NetDivisor = 100
    TimeFactor = 0.75
    BroadcastPeriod = 0.2

    def __init__(self, cr):
        DistributedPartyActivity.__init__(self,
                                          cr,
                                          ActivityIds.PartyCannon,
                                          ActivityTypes.Continuous,
                                          wantRewardGui=True)
        self.gui = None
        self.firingCannon = None
        self.shadowNode = None
        self.partyDoId = None
        self.splash = None
        self.dustCloud = None
        self.lastWakeTime = 0
        self.localFlyingDropShadow = None
        self.localFlyingToon = None
        self.localFlyingToonId = 0
        self._lastBroadcastTime = -self.BroadcastPeriod
        self._dirtyNewVel = None
        self.hitBumper = 0
        self.hitCloud = 0
        self.lastPos = Vec3(0, 0, 0)
        self.lastVel = Vec3(0, 0, 0)
        self.vel = Vec3(0, 0, 0)
        self.landingPos = Vec3(0, 0, 0)
        self.t = 0
        self.lastT = 0
        self.deltaT = 0
        self._lastCloudHit = None
        self.cameraPos = Vec3(0, -15.0, -25.0)
        self.cameraSpeed = 5.0
        self.camNode = None
        self.flyingToonOffsetRotation = 0
        self.flyingToonOffsetAngle = 0
        self.flyingToonOffsetX = 0
        self.flyingToonOffsetY = 0
        self.flyingToonCloudsHit = 0
        self.initialFlyVel = 0
        self._localPlayedBefore = False
        self.hitTrack = None
        self.cTrav = None
        self.flyColNode = None
        self.flyColNodePath = None
        self._flyingCollisionTaskName = None

    def generateInit(self):
        DistributedPartyActivity.generateInit(self)
        self.taskNameFireCannon = self.taskName('fireCannon')
        self.taskNameShoot = self.taskName('shootTask')
        self.taskNameFly = self.taskName('flyTask')
        self.gui = CannonGui()

    def load(self):
        self.notify.debug('load')
        DistributedPartyActivity.load(self)
        base.cr.playGame.hood.loader.loadClouds()
        base.cr.playGame.hood.loader.setCloudSwitch(1)
        self.shadow = loader.loadModel('phase_3/models/props/drop_shadow')
        self.shadowNode = hidden.attachNewNode('dropShadow')
        self.shadow.copyTo(self.shadowNode)
        self.shadowNode.setColor(0, 0, 0, 0.5)
        self.shadowNode.setBin('fixed', 0, 1)
        self.splash = Splash.Splash(render)
        self.dustCloud = DustCloud.DustCloud(render)
        self.dustCloud.setBillboardPointEye()
        self.sndHitGround = base.loadSfx(
            'phase_4/audio/sfx/MG_cannon_hit_dirt.ogg')
        self.sndHitWater = base.loadSfx(
            'phase_4/audio/sfx/MG_cannon_splash.ogg')
        self.sndHitHouse = base.loadSfx(
            'phase_5/audio/sfx/AA_drop_sandbag.ogg')
        self.sndBounce1 = base.loadSfx('phase_13/audio/sfx/bounce1.ogg')
        self.sndBounce2 = base.loadSfx('phase_13/audio/sfx/bounce2.ogg')
        self.sndBounce3 = base.loadSfx('phase_13/audio/sfx/bounce3.ogg')
        self.onstage()
        self.sign.reparentTo(hidden)
        self.sign.setPos(-6.0, 10.0, 0.0)
        self.accept(FireworksStartedEvent, self.__handleFireworksStarted)
        self.accept(FireworksFinishedEvent, self.__handleFireworksFinished)

    def generate(self):
        DistributedPartyActivity.generate(self)
        self._doneCannons = False
        self._avId2trajectoryInfo = {}
        self._remoteToonFlyTaskName = 'remoteToonFlyTask-%s' % self.doId
        taskMgr.add(self._remoteToonFlyTask,
                    self._remoteToonFlyTaskName,
                    priority=45)
        self.d_cloudsColorRequest()

    def unload(self):
        self.notify.debug('unload')
        DistributedPartyActivity.unload(self)
        if self.shadowNode is not None:
            self.shadowNode.removeNode()
            del self.shadowNode
        if self.splash is not None:
            self.splash.destroy()
            del self.splash
        if self.dustCloud is not None:
            self.dustCloud.destroy()
            del self.dustCloud
        del self.sndHitHouse
        del self.sndHitGround
        del self.sndHitWater
        del self.sndBounce1
        del self.sndBounce2
        del self.sndBounce3
        if self.localFlyingToon:
            self.__resetToon(self.localFlyingToon)
            self.localFlyingToon.loop('neutral')
            self.localFlyingToon.setPlayRate(1.0, 'run')
            self.localFlyingToon = None
        self.ignoreAll()
        return

    def onstage(self):
        self.notify.debug('onstage')
        self.splash.reparentTo(render)
        self.dustCloud.reparentTo(render)

    def offstage(self):
        self.notify.debug('offstage')
        if self.splash is not None:
            self.splash.reparentTo(hidden)
            self.splash.stop()
        if self.dustCloud is not None:
            self.dustCloud.reparentTo(hidden)
            self.dustCloud.stop()
        return

    def disable(self):
        taskMgr.remove(self._remoteToonFlyTaskName)
        if self._flyingCollisionTaskName:
            taskMgr.remove(self._flyingCollisionTaskName)
        taskMgr.remove(self.taskNameFireCannon)
        taskMgr.remove(self.taskNameShoot)
        taskMgr.remove(self.taskNameFly)
        taskMgr.remove(DistributedPartyCannonActivity.REACTIVATE_CLOUD_TASK)
        self.ignoreAll()
        if self.localFlyingToonId:
            self.__stopCollisionHandler(self.localFlyingToon)
            self.__stopLocalFlyTask(self.localFlyingToonId)
            self.setMovie(PartyGlobals.CANNON_MOVIE_CLEAR,
                          self.localFlyingToonId)
        if self.hitTrack is not None:
            self.hitTrack.finish()
            del self.hitTrack
            self.hitTrack = None
        DistributedPartyActivity.disable(self)
        return

    def delete(self):
        self.offstage()
        DistributedPartyActivity.delete(self)

    def setMovie(self, mode, toonId):
        self.notify.debug('%s setMovie(%s, %s)' % (self.doId, toonId, mode))
        if toonId != base.localAvatar.doId:
            return
        if mode == PartyGlobals.CANNON_MOVIE_CLEAR:
            self.landToon(toonId)
        elif mode == PartyGlobals.CANNON_MOVIE_LANDED:
            self.landToon(toonId)
        elif mode == PartyGlobals.CANNON_MOVIE_FORCE_EXIT:
            self.landToon(toonId)

    def __handleAvatarGone(self):
        self.setMovie(PartyGlobals.CANNON_MOVIE_CLEAR, 0)

    def handleToonDisabled(self, toonId):
        self.notify.warning('handleToonDisabled no implementation yet')

    def handleToonJoined(self, toonId):
        self.notify.warning('handleToonJoined no implementation yet')

    def isLocalToon(self, av):
        return base.localAvatar == av

    def isLocalToonId(self, toonId):
        return base.localAvatar.doId == toonId

    def getTitle(self):
        return TTLocalizer.PartyCannonActivityTitle

    def getInstructions(self):
        return TTLocalizer.PartyCannonActivityInstructions

    def hasPlayedBefore(self):
        return self._localPlayedBefore

    def displayRules(self):
        self.startRules()

    def handleRulesDone(self):
        self.finishRules()
        self._localPlayedBefore = True
        messenger.send(DistributedPartyCannonActivity.RULES_DONE_EVENT)

    def setCannonWillFire(self, cannonId, zRot, angle):
        self.notify.debug('setCannonWillFire: %d %d %d' %
                          (cannonId, zRot, angle))
        cannon = base.cr.doId2do.get(cannonId)
        if cannon is None:
            self.notify.warning(
                "Cannon has not been created, but we got this message. Don't show firing."
            )
            return
        if not cannon.getToonInside():
            self.notify.warning(
                "setCannonWillFire, but no toon insde. Don't show firing")
            return
        if self.isLocalToon(cannon.getToonInside()):
            self.localFlyingToon = base.localAvatar
            self.localFlyingToonId = base.localAvatar.doId
            self.localFiringCannon = cannon
            self.flyingToonCloudsHit = 0
        cannon.updateModel(zRot, angle)
        toonId = cannon.getToonInside().doId
        task = Task.Task(self.__fireCannonTask)
        task.toonId = toonId
        task.cannon = cannon
        taskMgr.add(task, self.taskNameFireCannon)
        self.toonIds.append(toonId)
        return

    def __fireCannonTask(self, task):
        launchTime = 0.0
        toonId = task.toonId
        cannon = task.cannon
        toon = cannon.getToonInside()
        self.notify.debug(
            str(self.doId) + ' FIRING CANNON FOR TOON ' + str(toonId))
        if not cannon.isToonInside():
            return Task.done
        if self.isLocalToonId(toonId):
            self.inWater = 0
            startPos, startHpr, startVel, trajectory = self.__calcFlightResults(
                cannon, toonId, launchTime)

            self.notify.debug('start position: ' + str(startPos))
            self.notify.debug('start velocity: ' + str(startVel))
            self.notify.debug('time of launch: ' + str(launchTime))
        cannon.removeToonReadyToFire()
        shootTask = Task.Task(self.__shootTask, self.taskNameShoot)
        shootTask.info = {'toonId': toonId, 'cannon': cannon}
        if self.isLocalToonId(toonId):
            self.flyingToonOffsetRotation = 0
            self.flyingToonOffsetAngle = 0
            self.flyingToonOffsetX = 0
            self.flyingToonOffsetY = 0
            self.hitCloud = 0
            self.initialFlyVel = INITIAL_VELOCITY
            self.camNode = NodePath(self.uniqueName('flyingCamera'))
            self.camNode.setScale(0.5)
            self.camNode.setPos(self.localFlyingToon.getPos())
            self.camNode.setHpr(self.localFlyingToon.getHpr())
            self.camNode.reparentTo(render)
            self.lastStartVel = startVel
            place = base.cr.playGame.getPlace()
            place.fsm.request('activity')
            toon.dropShadow.hide()
            self.localFlyingDropShadow = self.shadowNode.copyTo(hidden)
            vel = startVel
            toon.lookAt(toon.getPos() + Vec3(vel[0], vel[1], vel[2]))
            toon.setP(localAvatar, -90)
            hpr = toon.getHpr()
            toon.d_setPosHpr(startPos[0], startPos[1], startPos[2], hpr[0],
                             hpr[1], hpr[2])
            self.localFlyingToon.wrtReparentTo(render)
            info = {}
            info['toonId'] = toonId
            info['trajectory'] = trajectory
            info['launchTime'] = launchTime
            info['toon'] = self.localFlyingToon
            info['hRot'] = cannon.getRotation()
            base.camera.wrtReparentTo(self.localFlyingToon)
            flyTask = Task.Task(self.__localFlyTask, self.taskNameFly)
            flyTask.info = info
            seqTask = Task.sequence(shootTask, flyTask)
            self.__startCollisionHandler()
            self.notify.debug('Disable standard local toon controls.')
            base.localAvatar.disableAvatarControls()
            frameTime = globalClock.getFrameTime()
            netLaunchTime = globalClockDelta.localToNetworkTime(launchTime +
                                                                frameTime,
                                                                bits=31)
            self.sendUpdate('setToonTrajectoryAi', [
                netLaunchTime, startPos[0], startPos[1], startPos[2],
                startHpr[0], startHpr[1], startHpr[2], startVel[0],
                startVel[1], startVel[2]
            ])
        else:
            seqTask = shootTask
        taskMgr.add(seqTask, self.taskName('flyingToon') + '-' + str(toonId))
        toon.startSmooth()
        return Task.done

    def setToonTrajectory(self, avId, launchTime, x, y, z, h, p, r, vx, vy,
                          vz):
        if avId == localAvatar.doId:
            return
        startPos = Vec3(x, y, z)
        startHpr = Vec3(h, p, r)
        startVel = Vec3(vx, vy, vz)
        startT = globalClockDelta.networkToLocalTime(launchTime, bits=31) + 0.2
        trajectory = Trajectory.Trajectory(0.0, startPos, startVel)
        self._avId2trajectoryInfo[avId] = ScratchPad(startPos=startPos,
                                                     startHpr=startHpr,
                                                     startVel=startVel,
                                                     startT=startT,
                                                     trajectory=trajectory)

    def _remoteToonFlyTask(self, task=None):
        ids2del = []
        frameTime = globalClock.getFrameTime()
        for avId, trajInfo in self._avId2trajectoryInfo.iteritems():
            trajectory = trajInfo.trajectory
            startTime = trajInfo.startT
            groundTime = trajectory.calcTimeOfImpactOnPlane(
                0.0) / self.TimeFactor + startTime
            now = frameTime
            if now < startTime:
                now = startTime
            if now > groundTime:
                now = groundTime
            t = max(0.0, now - startTime)
            t *= self.TimeFactor
            toon = self.cr.getDo(avId)
            if toon is None:
                ids2del.append(avId)
            else:
                toon.setFluidPos(trajectory.getPos(t))
                vel = trajectory.getVel(t)
                toon.lookAt(toon.getPos() + Vec3(vel[0], vel[1], vel[2]))
                toon.setP(toon, -90)

        for avId in ids2del:
            del self._avId2trajectoryInfo[avId]

        return Task.cont

    def __calcFlightResults(self, cannon, toonId, launchTime):
        def quantizeVec(vec, divisor):
            quantize = lambda value, divisor: float(int(value * int(divisor))
                                                    ) / int(divisor)
            vec[0] = quantize(vec[0], divisor)
            vec[1] = quantize(vec[1], divisor)
            vec[2] = quantize(vec[2], divisor)

        startPos = cannon.getToonFirePos()
        startHpr = cannon.getToonFireHpr()
        startVel = cannon.getToonFireVel()
        quantizeVec(startPos, self.NetDivisor)
        quantizeVec(startHpr, self.NetDivisor)
        quantizeVec(startVel, self.NetDivisor)
        trajectory = Trajectory.Trajectory(launchTime, startPos, startVel)
        self.trajectory = trajectory
        return startPos, startHpr, startVel, trajectory

    def __shootTask(self, task):
        task.info['cannon'].fire()
        toonId = task.info['toonId']
        toon = base.cr.doId2do.get(toonId)
        if toon:
            toon.loop('swim')
        else:
            self.notify.debug(
                '__shootTask avoided a crash, toon %d not found' % toonId)
        if self.isLocalToonId(task.info['toonId']):
            self.localFlyingDropShadow.reparentTo(render)
            self.gui.enableAimKeys()
        return Task.done

    def d_setLanded(self, toonId):
        self.notify.debug('d_setLanded %s' % toonId)
        if self.isLocalToonId(toonId):
            if self.cr:
                self.sendUpdate('setLanded', [toonId])
            else:
                self.notify.debug('we avoided crash 2')

    def setLanded(self, toonId):
        if toonId in self._avId2trajectoryInfo:
            del self._avId2trajectoryInfo[toonId]

    def landToon(self, toonId):
        self.notify.debug('%s landToon' % self.doId)
        toon = base.cr.doId2do.get(toonId)
        if toon is not None:
            toon.resetLOD()
            if toon == base.localAvatar:
                self.__stopCollisionHandler(base.localAvatar)
            toon.wrtReparentTo(render)
            self.__setToonUpright(toon)
            toon.setPlayRate(1.0, 'run')
            toon.startSmooth()
            toon.setScale(1.0)
            self.ignore(toon.uniqueName('disable'))
            self.__cleanupFlyingToonData(toon)
            toon.dropShadow.show()
        place = base.cr.playGame.getPlace()
        if place is not None:
            if not hasattr(place, 'fsm'):
                return
        if toon is not None and toon == base.localAvatar:
            self.__localDisplayLandedResults()
        return

    def __localDisplayLandedResults(self):
        if self.flyingToonCloudsHit > 0:
            self._doneCannons = True
        else:
            self.__localToonDoneLanding()

    def handleRewardDone(self):
        DistributedPartyActivity.handleRewardDone(self)
        if self._doneCannons:
            self.__localToonDoneLanding()

    def __localToonDoneLanding(self):
        base.cr.playGame.getPlace().fsm.request('walk')
        self.notify.debug('__localToonDoneLanding')
        base.localAvatar.collisionsOn()
        base.localAvatar.startPosHprBroadcast()
        base.localAvatar.enableAvatarControls()
        messenger.send(DistributedPartyCannonActivity.LOCAL_TOON_LANDED_EVENT)

    def __setToonUpright(self, toon, pos=None):
        if toon:
            if self.inWater:
                toon.setP(0)
                toon.setR(0)
                return
            if not pos:
                pos = toon.getPos(render)
            toon.setPos(render, pos)
            toon.loop('neutral')
            if self.localFiringCannon and hasattr(self.localFiringCannon,
                                                  'cannonNode'):
                if self.localFiringCannon.cannonNode:
                    toon.lookAt(self.localFiringCannon.cannonNode)
                else:
                    self.notify.debug('we avoided crash 1.')
            toon.setP(0)
            toon.setR(0)
            toon.setScale(1, 1, 1)

    def __resetToonToCannon(self, avatar):
        self.notify.debug('__resetToonToCannon')
        if not avatar and self.localFlyingToonId:
            avatar = base.cr.doId2do.get(self.localFlyingToonId, None)
        if avatar:
            self.__resetToon(avatar)
        return

    def __resetToon(self, avatar, pos=None):
        self.notify.debug('__resetToon')
        if avatar:
            self.__stopCollisionHandler(avatar)
            self.__setToonUpright(avatar, pos)
            if self.isLocalToonId(avatar.doId):
                self.notify.debug('toon setting position to %s' % pos)
                if pos:
                    base.localAvatar.setPos(pos)
                base.camera.reparentTo(avatar)
            self.d_setLanded(avatar.doId)

    def __updateFlightVelocity(self, trajectory):
        hpr = LRotationf(self.flyingToonOffsetRotation, 0, 0)
        newVel = hpr.xform(self.lastStartVel)
        hpr = LRotationf(0, self.flyingToonOffsetAngle, 0)
        zVel = hpr.xform(self.lastStartVel).getZ()
        if zVel < newVel.getZ():
            newVel.setZ(zVel)
        trajectory.setStartVel(newVel)
        now = globalClock.getFrameTime()
        if now - self._lastBroadcastTime >= self.BroadcastPeriod:
            self._dirtyNewVel = newVel
        if self._dirtyNewVel:
            self.sendUpdate('updateToonTrajectoryStartVelAi', [
                self._dirtyNewVel[0], self._dirtyNewVel[1],
                self._dirtyNewVel[2]
            ])
            self._lastBroadcastTime = now
            self._dirtyNewVel = None
        return

    def updateToonTrajectoryStartVel(self, avId, vx, vy, vz):
        if avId == localAvatar.doId:
            return
        if avId in self._avId2trajectoryInfo:
            self._avId2trajectoryInfo[avId].trajectory.setStartVel(
                Vec3(vx, vy, vz))

    def __isFlightKeyPressed(self):
        return self.gui.leftPressed or self.gui.rightPressed or self.gui.upPressed or self.gui.downPressed

    def __moveFlyingToon(self, toon):
        toonP = toon.getP(render)
        isToonFlyingHorizontal = toonP > -150 and toonP < -30
        OFFSET = 0.25
        rotVel = 0
        if self.gui.leftPressed:
            if isToonFlyingHorizontal:
                rotVel += CANNON_ROTATION_VEL
            else:
                self.flyingToonOffsetX -= OFFSET
        if self.gui.rightPressed:
            if isToonFlyingHorizontal:
                rotVel -= CANNON_ROTATION_VEL
            else:
                self.flyingToonOffsetX += OFFSET
        self.flyingToonOffsetRotation += rotVel * globalClock.getDt()
        angVel = 0
        if self.gui.upPressed:
            if not isToonFlyingHorizontal:
                self.flyingToonOffsetY -= OFFSET
        if self.gui.downPressed:
            if isToonFlyingHorizontal:
                angVel += CANNON_ANGLE_VEL
            else:
                self.flyingToonOffsetY += OFFSET
        self.flyingToonOffsetAngle += angVel * globalClock.getDt()

    def __stopLocalFlyTask(self, toonId):
        taskMgr.remove(self.taskName('flyingToon') + '-' + str(toonId))
        self.gui.disableAimKeys()

    def __localFlyTask(self, task):
        toon = task.info['toon']
        if toon.isEmpty():
            self.__resetToonToCannon(self.localFlyingToon)
            return Task.done
        curTime = task.time + task.info['launchTime']
        t = curTime
        t *= self.TimeFactor
        self.lastT = self.t
        self.t = t
        deltaT = self.t - self.lastT
        self.deltaT = deltaT
        if self.hitBumper:
            pos = self.lastPos + self.lastVel * deltaT
            vel = self.lastVel
            self.lastVel += Vec3(0, 0, -32.0) * deltaT
            self.lastPos = pos
            toon.setFluidPos(pos)
            lastR = toon.getR()
            toon.setR(lastR - deltaT * self.angularVel * 2.0)
            cameraView = 0
        else:
            if not self.hitCloud and self.__isFlightKeyPressed():
                self.__moveFlyingToon(toon)
                self.__updateFlightVelocity(task.info['trajectory'])
            if self.hitCloud == 1:
                vel = task.info['trajectory'].getVel(t)
                startPos = toon.getPos(render)
                task.info['trajectory'].setStartTime(t)
                task.info['trajectory'].setStartPos(startPos)
                task.info['trajectory'].setStartVel(self.lastVel)
                toon.lookAt(toon.getPos() + vel)
                toon.setH(-toon.getH())
                now = globalClock.getFrameTime()
                netLaunchTime = globalClockDelta.localToNetworkTime(now,
                                                                    bits=31)
                hpr = toon.getHpr()
                self.sendUpdate('setToonTrajectoryAi', [
                    netLaunchTime, startPos[0], startPos[1], startPos[2],
                    hpr[0], hpr[1], hpr[2], self.lastVel[0], self.lastVel[1],
                    self.lastVel[2]
                ])
                self._lastBroadcastTime = now
                self._dirtyNewVel = None
                self.flyingToonOffsetRotation = 0
                self.flyingToonOffsetAngle = 0
                self.flyingToonOffsetX = 0
                self.flyingToonOffsetY = 0
                self.hitCloud = 2
            pos = task.info['trajectory'].getPos(t)
            toon.setFluidPos(pos)
            toon.setFluidPos(toon, self.flyingToonOffsetX,
                             self.flyingToonOffsetY, 0)
            vel = task.info['trajectory'].getVel(t)
            toon.lookAt(toon.getPos() + Vec3(vel[0], vel[1], vel[2]))
            toon.setP(toon.getP() - 90)
            cameraView = 2
            if self.hitCloud == 2:
                self.lastStartVel = vel
                self.hitCloud = 0
        shadowPos = toon.getPos()
        shadowPos.setZ(SHADOW_Z_OFFSET)
        self.localFlyingDropShadow.setPos(shadowPos)
        if pos.getZ() < -20 or pos.getZ() > 1000:
            self.notify.debug('stopping fly task toon.getZ()=%.2f' %
                              pos.getZ())
            self.__resetToonToCannon(self.localFlyingToon)
            return Task.done
        self.__setFlyingCameraView(task.info['toon'], cameraView, deltaT)
        return Task.cont

    def __setFlyingCameraView(self, toon, view, deltaT):
        if toon != base.localAvatar:
            return
        lookAt = toon.getPos(render)
        hpr = toon.getHpr(render)
        if view == 0:
            base.camera.wrtReparentTo(render)
            base.camera.lookAt(lookAt)
        elif view == 1:
            base.camera.reparentTo(render)
            base.camera.setPos(render, 100, 100, 35.25)
            base.camera.lookAt(render, lookAt)
        elif view == 2:
            if base.camera.getParent() != self.camNode:
                base.camera.wrtReparentTo(self.camNode)
                base.camera.setPos(self.cameraPos)
                base.camera.lookAt(toon)
            self.camNode.setPos(toon.getPos(render))
            camHpr = self.camNode.getHpr(toon)
            vec = -Point3(0, 0, 0) - camHpr
            relativeSpeed = math.pow(vec.length() / 60.0, 2) + 0.1
            newHpr = camHpr + vec * deltaT * self.cameraSpeed * relativeSpeed
            self.camNode.setHpr(toon, newHpr)
            base.camera.lookAt(self.camNode)
            base.camera.setR(render, 0)

    def __cleanupFlyingToonData(self, toon):
        self.notify.debug('__cleanupFlyingToonData')
        if toon:
            toon.dropShadow.show()
            self.toonIds.remove(toon.doId)
            if self.isLocalToon(toon):
                if self.localFlyingDropShadow != None:
                    self.localFlyingDropShadow.removeNode()
                    self.localFlyingDropShadow = None
                self.hitBumper = 0
                self.angularVel = 0
                self.vel = Vec3(0, 0, 0)
                self.lastVel = Vec3(0, 0, 0)
                self.lastPos = Vec3(0, 0, 0)
                self.landingPos = Vec3(0, 0, 0)
                self.t = 0
                self.lastT = 0
                self.deltaT = 0
                self.lastWakeTime = 0
                self.localFlyingToon = None
                self.localFlyingToonId = 0
                self.localFiringCannon = None
                if hasattr(self, 'camNode') and self.camNode:
                    self.camNode.removeNode()
                    self.camNode = None
        return

    def __startCollisionHandler(self):
        self.flyColSphere = CollisionSphere(
            0, 0,
            self.localFlyingToon.getHeight() / 2.0, 1.0)
        self.flyColNode = CollisionNode(self.uniqueName('flySphere'))
        self.flyColNode.setCollideMask(ToontownGlobals.WallBitmask
                                       | ToontownGlobals.FloorBitmask)
        self.flyColNode.addSolid(self.flyColSphere)
        self.flyColNodePath = self.localFlyingToon.attachNewNode(
            self.flyColNode)
        self.flyColNodePath.setColor(1, 0, 0, 1)
        self._activeCollisions = set()
        self.handler = CollisionHandlerQueue()
        self._flyingCollisionTaskName = 'checkFlyingToonCollision-%s' % self.doId
        taskMgr.add(self._checkFlyingToonCollision,
                    self._flyingCollisionTaskName)
        base.cTrav.addCollider(self.flyColNodePath, self.handler)

    def __stopCollisionHandler(self, avatar):
        self.notify.debug('%s __stopCollisionHandler' % self.doId)
        if self._flyingCollisionTaskName:
            taskMgr.remove(self._flyingCollisionTaskName)
            self._flyingCollisionTaskName = None
            self._activeCollisions = set()
        if avatar:
            avatar.loop('neutral')
            if self.flyColNode:
                self.flyColNode = None
            self.flyColSphere = None
            if self.flyColNodePath:
                base.cTrav.removeCollider(self.flyColNodePath)
                self.flyColNodePath.removeNode()
                self.flyColNodePath = None
            self.handler = None
        return

    def _checkFlyingToonCollision(self, task=None):
        curCollisions = set()
        if self.handler.getNumEntries():
            self.handler.sortEntries()
            i = self.handler.getNumEntries()
            activeEntry = None
            while i > 0:
                entry = self.handler.getEntry(i - 1)
                k = (str(entry.getFromNodePath()),
                     str(entry.getIntoNodePath()))
                curCollisions.add(k)
                if activeEntry is None and k not in self._activeCollisions:
                    activeEntry = entry
                    self._activeCollisions.add(k)
                i -= 1

            if activeEntry is not None:
                self.__handleFlyingToonCollision(activeEntry)
            if self.handler:
                self.handler.clearEntries()
        for k in list(self._activeCollisions):
            if k not in curCollisions:
                self._activeCollisions.remove(k)

        return Task.cont

    def __handleFlyingToonCollision(self, collisionEntry):
        self.notify.debug('%s __handleToonCollision' % self.doId)
        if self.localFlyingToon == None or self.flyColNode == None:
            return
        hitNode = collisionEntry.getIntoNode().getName()
        self.notify.debug('hitNode = %s' % hitNode)
        self.notify.debug('hitNodePath.getParent = %s' %
                          collisionEntry.getIntoNodePath().getParent())
        self.vel = self.trajectory.getVel(self.t)
        vel = self.trajectory.getVel(self.t)
        vel.normalize()
        if self.hitBumper:
            vel = self.lastVel * 1
            vel.normalize()
        self.notify.debug('normalized vel=%s' % vel)
        solid = collisionEntry.getInto()
        intoNormal = collisionEntry.getSurfaceNormal(
            collisionEntry.getIntoNodePath())
        self.notify.debug('old intoNormal = %s' % intoNormal)
        intoNormal = collisionEntry.getSurfaceNormal(render)
        self.notify.debug('new intoNormal = %s' % intoNormal)
        hitPylonAboveWater = False
        hitPylonBelowWater = False
        hitNormal = intoNormal
        if hitNode.find('cSphere') == 0 or hitNode.find(
                'treasureSphere'
        ) == 0 or hitNode.find('prop') == 0 or hitNode.find(
                'distAvatarCollNode'
        ) == 0 or hitNode.find('CannonSphere') == 0 or hitNode.find(
                'plotSphere'
        ) == 0 or hitNode.find('flySphere') == 0 or hitNode.find(
                'FishingSpotSphere'
        ) == 0 or hitNode.find(
                'TrampolineTrigger'
        ) == 0 or hitNode == 'gagtree_collision' or hitNode == 'sign_collision' or hitNode == 'FlowerSellBox' or hitPylonBelowWater:
            self.notify.debug('--------------hit and ignoring %s' % hitNode)
            return
        if vel.dot(
                hitNormal
        ) > 0 and not hitNode == 'collision_roof' and not hitNode == 'collision_fence':
            self.notify.debug(
                '--------------hit and ignoring backfacing %s, dot=%s' %
                (hitNode, vel.dot(hitNormal)))
            return
        intoNode = collisionEntry.getIntoNodePath()
        bumperNodes = [
            'sky_collision'
        ] + PartyCannonCollisions['bounce'] + PartyCannonCollisions['fence']
        cloudBumpers = PartyCannonCollisions['clouds']
        bumperNodes += cloudBumpers
        if hitNode in bumperNodes or hitNode.find(
                'cogPie'
        ) == 0 or PartyCannonCollisions['trampoline_bounce'] in hitNode:
            if hitNode == 'sky_collision' or hitNode in PartyCannonCollisions[
                    'fence'] or hitNode.find('cogPie') == 0:
                self.__hitFence(self.localFlyingToon, collisionEntry)
            elif PartyCannonCollisions[
                    'trampoline_bounce'] in hitNode or hitNode in PartyCannonCollisions[
                        'bounce']:
                if hitNode == 'wall_collision':
                    hitSound = self.sndBounce2
                else:
                    hitSound = self.sndBounce3
                self.hitCloud = 1
                self.__hitBumper(self.localFlyingToon,
                                 collisionEntry,
                                 hitSound,
                                 kr=0.09,
                                 angVel=5)
                self.hitBumper = 0
            elif hitNode in cloudBumpers:
                self.__hitCloudPlatform(self.localFlyingToon, collisionEntry)
            elif hitNode == 'statuaryCol':
                self.__hitStatuary(self.localFlyingToon, collisionEntry)
            else:
                self.notify.debug(
                    '*************** hit something else ************')
            return
        else:
            self.__stopCollisionHandler(self.localFlyingToon)
            self.__stopLocalFlyTask(self.localFlyingToonId)
            self.notify.debug('stopping flying since we hit %s' % hitNode)
        if self.isLocalToonId(self.localFlyingToon.doId):
            base.camera.wrtReparentTo(render)
        if self.localFlyingDropShadow:
            self.localFlyingDropShadow.reparentTo(hidden)
        pos = collisionEntry.getSurfacePoint(render)
        hpr = self.localFlyingToon.getHpr()
        hitPos = collisionEntry.getSurfacePoint(render)
        pos = hitPos
        self.landingPos = pos
        self.notify.debug('hitNode, Normal = %s,%s' % (hitNode, intoNormal))
        track = Sequence()
        track.append(Func(self.localFlyingToon.wrtReparentTo, render))
        if self.isLocalToonId(self.localFlyingToon.doId):
            track.append(Func(self.localFlyingToon.collisionsOff))
        if hitNode in PartyCannonCollisions['ground']:
            track.append(Func(self.__hitGround, self.localFlyingToon, pos))
            track.append(Wait(1.0))
            track.append(
                Func(self.__setToonUpright, self.localFlyingToon,
                     self.landingPos))
        elif hitNode in PartyCannonCollisions['fence']:
            track.append(
                Func(self.__hitFence, self.localFlyingToon, collisionEntry))
        elif hitNode == 'collision3':
            track.append(
                Func(self.__hitWater, self.localFlyingToon, pos,
                     collisionEntry))
            track.append(Wait(2.0))
            track.append(
                Func(self.__setToonUpright, self.localFlyingToon,
                     self.landingPos))
        elif hitNode.find('cloudSphere') == 0:
            track.append(
                Func(self.__hitCloudPlatform, self.localFlyingToon,
                     collisionEntry))
        else:
            self.notify.warning(
                '************* unhandled hitNode=%s parent =%s' %
                (hitNode, collisionEntry.getIntoNodePath().getParent()))
        track.append(Func(self.d_setLanded, self.localFlyingToonId))
        if self.isLocalToonId(self.localFlyingToonId):
            track.append(Func(self.localFlyingToon.collisionsOn))
        if self.hitTrack:
            self.hitTrack.finish()
        self.hitTrack = track
        self.hitTrack.start()
        return

    def __hitBumper(self, avatar, collisionEntry, sound, kr=0.6, angVel=1):
        self.hitBumper = 1
        base.playSfx(sound)
        hitP = avatar.getPos(render)
        self.lastPos = hitP
        normal = collisionEntry.getSurfaceNormal(render)
        self.notify.debug('normal = %s' % normal)
        vel = self.vel * 1
        speed = vel.length()
        vel.normalize()
        self.notify.debug('old vel = %s' % vel)
        if self.hitCloud:
            centerVec = Vec3(-avatar.getPos(self.getParentNodePath()))
            centerVec.setZ(0)
            d = centerVec.length() / 15.0
            centerVec.setZ(abs(centerVec.length() * math.sin(70.0)))
            centerVec.normalize()
            newVel = centerVec * d + normal * 0.2
            newVel = newVel * (kr * speed)
            self.initialFlyVel = kr * speed
        else:
            newVel = (normal * 2.0 + vel) * (kr * speed)
        self.lastVel = newVel
        self.notify.debug('new vel = %s' % newVel)
        self.angularVel = angVel * 360
        if self.hitCloud:
            return
        t = Sequence(Func(avatar.pose, 'lose', 110))
        t.start()

    def __hitGround(self, avatar, pos, extraArgs=[]):
        self.notify.debug('__hitGround')
        hitP = avatar.getPos(render)
        self.notify.debug('hitGround pos = %s, hitP = %s' % (pos, hitP))
        self.notify.debug('avatar hpr = %s' % avatar.getHpr())
        avatar.setPos(pos[0], pos[1], pos[2] + avatar.getHeight() / 3.0)
        avatar.setHpr(avatar.getH(), -135, 0)
        self.notify.debug('parent = %s' % avatar.getParent())
        self.notify.debug('pos = %s, hpr = %s' %
                          (avatar.getPos(render), avatar.getHpr(render)))
        self.__playDustCloud(avatar, pos)
        base.playSfx(self.sndHitGround)
        avatar.setPlayRate(2.0, 'run')
        avatar.loop('run')

    def __playDustCloud(self, toon, pos):
        self.dustCloud.setPos(render, pos[0], pos[1],
                              pos[2] + toon.getHeight() / 3.0)
        self.dustCloud.setScale(0.35)
        self.dustCloud.play()

    def __hitFence(self, avatar, collisionEntry, extraArgs=[]):
        self.notify.debug('__hitFence')
        self.__hitBumper(avatar,
                         collisionEntry,
                         self.sndHitHouse,
                         kr=0.2,
                         angVel=3)

    def __hitWater(self, avatar, pos, collisionEntry, extraArgs=[]):
        hitP = avatar.getPos(render)
        if hitP[2] > ToontownGlobals.EstateWakeWaterHeight:
            self.notify.debug('we hit the ground before we hit water')
            self.__hitGround(avatar, pos, extraArgs)
            return
        self.notify.debug('hit water')
        hitP = avatar.getPos(render)
        avatar.loop('neutral')
        self.splash.setPos(hitP)
        self.splash.setZ(ToontownGlobals.EstateWakeWaterHeight)
        self.splash.setScale(2)
        self.splash.play()
        base.playSfx(self.sndHitWater)
        place = base.cr.playGame.getPlace()

    def __hitStatuary(self, avatar, collisionEntry, extraArgs=[]):
        self.__hitBumper(avatar,
                         collisionEntry,
                         self.sndHitHouse,
                         kr=0.4,
                         angVel=5)

    def d_cloudsColorRequest(self):
        self.notify.debug('cloudsColorRequest')
        self.sendUpdate('cloudsColorRequest')

    def cloudsColorResponse(self, cloudColorList):
        self.notify.debug('cloudsColorResponse: %s' % cloudColorList)
        for cloudColor in cloudColorList:
            self.setCloudHit(*cloudColor)

    def d_requestCloudHit(self, cloudNumber, color):
        self.sendUpdate(
            'requestCloudHit',
            [cloudNumber,
             color.getX(),
             color.getY(),
             color.getZ()])

    def setCloudHit(self, cloudNumber, r, g, b):
        cloud = render.find('**/cloud-%d' % cloudNumber)
        if not cloud.isEmpty():
            cloud.setColor(r, g, b, 1.0)
        else:
            self.notify.debug('Could not find cloud-%d' % cloudNumber)

    def __hitCloudPlatform(self, avatar, collisionEntry, extraArgs=[]):
        if not self.hitBumper and not self.hitCloud:
            self.hitCloud = 1
            self.__hitBumper(avatar,
                             collisionEntry,
                             self.sndBounce1,
                             kr=0.35,
                             angVel=5)
            self.hitBumper = 0
            if self._lastCloudHit is None:
                cloud = collisionEntry.getIntoNodePath().getParent()
                self._lastCloudHit = cloud
                cloud.setColor(base.localAvatar.style.getHeadColor())
                cloudNumber = int(cloud.getNetTag('number'))
                self.d_requestCloudHit(cloudNumber,
                                       base.localAvatar.style.getHeadColor())
                self.__playDustCloud(avatar,
                                     collisionEntry.getSurfacePoint(render))
                self.flyingToonCloudsHit += 1
                taskMgr.doMethodLater(
                    0.25, self.__reactivateLastCloudHit,
                    DistributedPartyCannonActivity.REACTIVATE_CLOUD_TASK)
        return

    def __reactivateLastCloudHit(self, task):
        self._lastCloudHit = None
        return Task.done

    def __handleFireworksStarted(self):
        self.notify.debug('__handleFireworksStarted')
        base.cr.playGame.hood.loader.fadeClouds()

    def __handleFireworksFinished(self):
        self.notify.debug('__handleFireworksFinished')
        if self.__checkHoodValidity():
            base.cr.playGame.hood.loader.fadeClouds()
        else:
            self.notify.debug('Toon has left the party')

    def __checkHoodValidity(self):
        if hasattr(base.cr.playGame,
                   'hood') and base.cr.playGame.hood and hasattr(
                       base.cr.playGame.hood,
                       'loader') and base.cr.playGame.hood.loader and hasattr(
                           base.cr.playGame.hood.loader,
                           'geom') and base.cr.playGame.hood.loader.geom:
            return True
        else:
            return False

    def handleToonExited(self, toonId):
        self.notify.debug(
            'DistributedPartyCannonActivity handleToonExited( toonId=%s ) ' %
            toonId)
        if toonId in self.cr.doId2do:
            self.notify.warning('handleToonExited is not defined')