from direct.showbase.ShowBaseGlobal import *
from toontown.toonbase.ToonBaseGlobal import *
from direct.interval.IntervalGlobal import *
from toontown.toonbase import ToontownTimer
from DistributedMinigame import *
from direct.distributed.ClockDelta import *
from direct.fsm import ClassicFSM
from direct.fsm import State
from direct.task import Task
from direct.actor import Actor
from toontown.toon import LaffMeter
from direct.distributed import DistributedSmoothNode
import ArrowKeys
import Ring
import RingTrack
import DivingGameGlobals
import RingGroup
import RingTrackGroups
import random
import DivingGameToonSD
import DivingFishSpawn
import DivingTreasure
import math
import TreasureScorePanel
from otp.distributed.TelemetryLimiter import TelemetryLimiter, TLGatherAllAvs
from toontown.toonbase import ToontownGlobals
from toontown.toonbase import TTLocalizer

class DivingGameRotationLimiter(TelemetryLimiter):

    def __init__(self, h, p):
        self._h = h
        self._p = p

    def __call__(self, obj):
        obj.setHpr(self._h, self._p, obj.getR())


class DistributedDivingGame(DistributedMinigame):
    COLLISION_WATCH_TASK = 'DivingGameCollisionWatchTask'
    TREASURE_BOUNDS_TASK = 'DivingGameTreasureBoundsTask'
    CRAB_TASK = 'DivingGameCrabTask'
    UPDATE_LOCALTOON_TASK = 'DivingGameUpdateLocalToonTask'
    COLLISION_DETECTION_PRIORITY = 5
    MAP_DIV = 2.8
    MAP_OFF = 14.0
    LAG_COMP = 1.25

    def __init__(self, cr):
        DistributedMinigame.__init__(self, cr)
        self.gameFSM = ClassicFSM.ClassicFSM('DistributedDivingGame', [State.State('off', self.enterOff, self.exitOff, ['swim']), State.State('swim', self.enterSwim, self.exitSwim, ['cleanup']), State.State('cleanup', self.enterCleanup, self.exitCleanup, [])], 'off', 'cleanup')
        self.addChildGameFSM(self.gameFSM)
        self.iCount = 0
        self.reachedFlag = 0
        self.grabbingTreasure = -1
        self.dead = 0

    def getTitle(self):
        return TTLocalizer.DivingGameTitle

    def getInstructions(self):
        p = self.avIdList.index(self.localAvId)
        if self.isSinglePlayer():
            text = TTLocalizer.DivingInstructionsSinglePlayer
        else:
            text = TTLocalizer.DivingInstructionsMultiPlayer
        return text

    def load(self):
        self.notify.debug('load')
        DistributedMinigame.load(self)
        loadBase = 'phase_4/models/minigames/'
        loadBaseShip = 'phase_5/models/props/'
        self.sndAmbience = base.loadSfx('phase_4/audio/sfx/AV_ambient_water.ogg')
        self.environModel = loader.loadModel(loadBase + 'diving_game.bam')
        self.boatModel = self.environModel.find('**/boat')
        self.skyModel = self.environModel.find('**/sky')
        self.waterModel = self.environModel.find('**/seawater')
        self.frontMap = self.environModel.find('**/sea_front')
        self.frontMap.setY(3)
        self.frontMap.setBin('fixed', 0)
        self.frontMap.setDepthTest(0)
        self.waterModel.setY(1.0)
        bubbleModel = self.environModel.find('**/bubbles1')
        bubbleModel.setY(1.0)
        bubbleModel = self.environModel.find('**/bubbles2')
        bubbleModel.setY(1.0)
        bubbleModel = self.environModel.find('**/bubbles3')
        bubbleModel.setY(1.0)
        bubbleModel = self.environModel.find('**/bubbles4')
        bubbleModel.setY(1.0)
        bubbleModel = self.environModel.find('**/bubbles5')
        bubbleModel.setY(1.0)
        self.mapModel = loader.loadModel(loadBase + 'diving_game.bam')
        boatMap = self.mapModel.find('**/boat')
        skyMap = self.mapModel.find('**/sky')
        frontMap = self.mapModel.find('**/sea_front')
        skyMap.hide()
        frontMap.hide()
        boatMap.setZ(28.5)
        self.crabs = []
        self.spawners = []
        self.toonSDs = {}
        avId = self.localAvId
        toonSD = DivingGameToonSD.DivingGameToonSD(avId, self)
        self.toonSDs[avId] = toonSD
        toonSD.load()
        crabSoundName = 'King_Crab.ogg'
        crabSoundPath = 'phase_4/audio/sfx/%s' % crabSoundName
        self.crabSound = loader.loadSfx(crabSoundPath)
        treasureSoundName = 'SZ_DD_treasure.ogg'
        treasureSoundPath = 'phase_4/audio/sfx/%s' % treasureSoundName
        self.treasureSound = loader.loadSfx(treasureSoundPath)
        hitSoundName = 'diving_game_hit.ogg'
        hitSoundPath = 'phase_4/audio/sfx/%s' % hitSoundName
        self.hitSound = loader.loadSfx(hitSoundPath)
        self.music = base.loadMusic('phase_4/audio/bgm/MG_Target.ogg')
        self.addSound('dropGold', 'diving_treasure_drop_off.ogg', 'phase_4/audio/sfx/')
        self.addSound('getGold', 'diving_treasure_pick_up.ogg', 'phase_4/audio/sfx/')
        self.swimSound = loader.loadSfx('phase_4/audio/sfx/diving_swim_loop.ogg')
        self.swimSound.setVolume(0.0)
        self.swimSound.setPlayRate(1.0)
        self.swimSound.setLoop(True)
        self.swimSound.play()

    def addSound(self, name, soundName, path = None):
        if not hasattr(self, 'soundTable'):
            self.soundTable = {}
        if path:
            self.soundPath = path
        soundSource = '%s%s' % (self.soundPath, soundName)
        self.soundTable[name] = loader.loadSfx(soundSource)

    def playSound(self, name, volume = 1.0):
        self.soundTable[name].setVolume(1.0)
        self.soundTable[name].play()

    def unload(self):
        self.notify.debug('unload')
        DistributedMinigame.unload(self)
        self.mapModel.removeNode()
        del self.mapModel
        if hasattr(self, 'soundTable'):
            del self.soundTable
        del self.sndAmbience
        del self.hitSound
        del self.crabSound
        del self.treasureSound
        self.swimSound.stop()
        del self.swimSound
        self.environModel.removeNode()
        del self.environModel
        self.removeChildGameFSM(self.gameFSM)
        for avId in self.toonSDs.keys():
            toonSD = self.toonSDs[avId]
            toonSD.unload()

        del self.toonSDs
        del self.gameFSM
        del self.music

    def fishCollision(self, collEntry):
        avId = int(collEntry.getFromNodePath().getName())
        toonSD = self.toonSDs[avId]
        name = collEntry.getIntoNodePath().getName()
        if len(name) >= 7:
            if name[0:6] == 'crabby':
                self.sendUpdate('handleCrabCollision', [avId, toonSD.status])
        else:
            spawnerId = int(name[2])
            spawnId = int(name[3:len(name)])
            if spawnId in self.spawners[spawnerId].fishArray:
                self.sendUpdate('handleFishCollision', [avId,
                 spawnId,
                 spawnerId,
                 toonSD.status])

    def fishSpawn(self, timestamp, fishcode, spawnerId, offset):
        if self.dead is 1:
            return
        ts = globalClockDelta.localElapsedTime(timestamp)
        if not hasattr(self, 'spawners'):
            return
        if abs(self.spawners[spawnerId].lastSpawn - timestamp) < 150:
            return
        fish = self.spawners[spawnerId].createFish(fishcode)
        fish.offset = offset
        fish.setPos(self.spawners[spawnerId].position)
        func = Func(self.fishRemove, fish.code)
        self.spawners[spawnerId].lastSpawn = timestamp
        iName = '%s %s' % (fish.name, self.iCount)
        self.iCount += 1
        if fish.name == 'clown':
            fish.moveLerp = Sequence(LerpPosInterval(fish, duration=8 * self.SPEEDMULT * self.LAG_COMP, startPos=self.spawners[spawnerId].position, pos=self.spawners[spawnerId].position + Point3(50 * self.spawners[spawnerId].direction, 0, (offset - 4) / 2.0), name=iName), func)
            fish.specialLerp = Sequence()
        elif fish.name == 'piano':
            fish.moveLerp = Sequence(LerpPosInterval(fish, duration=5 * self.SPEEDMULT * self.LAG_COMP, startPos=self.spawners[spawnerId].position, pos=self.spawners[spawnerId].position + Point3(50 * self.spawners[spawnerId].direction, 0, (offset - 4) / 2.0), name=iName), func)
            fish.specialLerp = Sequence()
        elif fish.name == 'pbj':
            fish.moveLerp = Sequence(LerpFunc(fish.setX, duration=12 * self.SPEEDMULT * self.LAG_COMP, fromData=self.spawners[spawnerId].position.getX(), toData=self.spawners[spawnerId].position.getX() + 50 * self.spawners[spawnerId].direction, name=iName), func)
            fish.specialLerp = LerpFunc(self.pbjMove, duration=5 * self.SPEEDMULT * self.LAG_COMP, fromData=0, toData=2.0 * 3.14159, extraArgs=[fish, self.spawners[spawnerId].position.getZ()], blendType='easeInOut')
        elif fish.name == 'balloon':
            fish.moveLerp = Sequence(LerpPosInterval(fish, duration=10 * self.SPEEDMULT * self.LAG_COMP, startPos=self.spawners[spawnerId].position, pos=self.spawners[spawnerId].position + Point3(50 * self.spawners[spawnerId].direction, 0, (offset - 4) / 2.0), name=iName), func)
            fish.specialLerp = Sequence(Wait(offset / 10.0 * 2 + 1.5), Parallel(LerpScaleInterval(fish, duration=0.3, startScale=Vec3(2, 2, 2), scale=Vec3(5, 3, 5), blendType='easeInOut')), Wait(1.0), Parallel(LerpScaleInterval(fish, duration=0.4, startScale=Vec3(5, 3, 5), scale=Vec3(2, 2, 2), blendType='easeInOut')))
        elif fish.name == 'bear' or fish.name == 'nurse':
            fish.moveLerp = Sequence(LerpPosInterval(fish, duration=20 * self.LAG_COMP, startPos=self.spawners[spawnerId].position, pos=self.spawners[spawnerId].position + Point3(50 * self.spawners[spawnerId].direction, 0, 0), name=iName), func)
            fish.specialLerp = Sequence()
        fish.moveLerp.start(ts)
        fish.specialLerp.loop(ts)

    def pbjMove(self, x, fish, Z):
        z = math.sin(x + fish.offset * 3) * 3
        fish.setZ(z + Z)

    def getIntroMovie(self):
        seq = Sequence()
        seq.append(Wait(2.0))
        seq.append(LerpFunc(camera.setZ, duration=5, fromData=36, toData=-23, blendType='easeInOut', name='intro'))
        seq.append(Wait(2.0))
        seq.append(LerpFunc(camera.setZ, duration=5, fromData=-23, toData=36 + 3, blendType='easeInOut', name='intro'))
        return seq

    def onstage(self):
        self.notify.debug('onstage')
        DistributedMinigame.onstage(self)
        base.localAvatar.collisionsOff()
        DistributedSmoothNode.activateSmoothing(1, 1)
        numToons = self.numPlayers
        self.NUMTREASURES = numToons
        camera.reparentTo(render)
        camera.setZ(36)
        camera.setHpr(0,0,0)
        camera.setX(0)
        base.camLens.setMinFov(31/(4./3.))
        camera.setY(-54)
        base.camLens.setFar(1500)
        self.introMovie = self.getIntroMovie()
        self.introMovie.start()
        self.accept('FishHit', self.fishCollision)
        toonSD = self.toonSDs[self.localAvId]
        toonSD.enter()
        toonSD.fsm.request('normal')
        toon = base.localAvatar
        toon.reparentTo(render)
        toon.setPos(-9, -1, 36)
        self.__placeToon(self.localAvId)
        self.arrowKeys = ArrowKeys.ArrowKeys()
        self.xVel = 0
        self.zVel = 0
        self.orientNode = toon.attachNewNode('orientNode')
        self.orientNode.setPos(0, 0, 1)
        self.orientNode2 = toon.attachNewNode('orientNode')
        self.orientNode2.setPos(0, 0, -1)
        self.environNode = render.attachNewNode('environNode')
        self.environModel.reparentTo(self.environNode)
        self.environModel.setScale(2.8, 2.8, 2.73)
        self.environModel.setPos(0, 0.5, -41)
        self.skyModel.setScale(1.3, 1.0, 1.0)
        boatoff = 6.75
        self.boatModel.reparentTo(self.environNode)
        self.boatModel.setPos(0, 3.0, 40 - boatoff)
        self.boatModel.setScale(2.8)
        cSphere = CollisionSphere(0.0, 0.0, 0.0 + 2.0, 3.0)
        cSphere.setTangible(0)
        name = 'boat'
        cSphereNode = CollisionNode(name)
        cSphereNode.setIntoCollideMask(DivingGameGlobals.CollideMask)
        cSphereNode.addSolid(cSphere)
        self.boatNode = cSphereNode
        self.boatCNP = self.boatModel.attachNewNode(cSphereNode)
        self.accept('reach-boat', self.__boatReached)
        self.boatTilt = Sequence(LerpFunc(self.boatModel.setR, duration=5, fromData=5, toData=-5, blendType='easeInOut', name='tilt'), LerpFunc(self.boatModel.setR, duration=5, fromData=-5, toData=5, blendType='easeInOut', name='tilt'))
        self.boatTilt.loop()
        self.mapScaleRatio = 40
        self.mapModel.reparentTo(base.a2dTopRight)
        self.mapModel.setScale(1.0 / self.mapScaleRatio)
        self.mapModel.setTransparency(1)
        self.mapModel.setPos(-0.22, 0.0, -1.30)
        self.mapModel.setColorScale(1, 1, 1, 0.7)
        self.mapModel.hide()
        if self.sndAmbience:
            self.sndAmbience.setLoop(True)
            self.sndAmbience.play()
            self.sndAmbience.setVolume(0.01)
        return

    def offstage(self):
        self.notify.debug('offstage')
        DistributedMinigame.offstage(self)
        self.introMovie.finish()
        self.boatTilt.finish()
        self.mapModel.hide()
        DistributedSmoothNode.activateSmoothing(1, 0)
        for avId in self.toonSDs.keys():
            self.toonSDs[avId].exit()

        base.camLens.setFar(ToontownGlobals.DefaultCameraFar)
        base.camLens.setMinFov(settings['fov']/(4./3.))
        base.setBackgroundColor(ToontownGlobals.DefaultBackgroundColor)
        self.arrowKeys.destroy()
        del self.arrowKeys
        self.environNode.removeNode()
        del self.environNode
        if None != self.sndAmbience:
            self.sndAmbience.stop()
        for avId in self.avIdList:
            av = self.getAvatar(avId)
            if av:
                av.dropShadow.show()
                av.resetLOD()
                av.setAnimState('neutral', 1.0)

        self.dead = 1
        self.__killCrabTask()
        for spawner in self.spawners:
            spawner.destroy()
            del spawner

        del self.spawners
        for crab in self.crabs:
            crab.moveLerp.finish()
            crab.moveLerp = None
            crab.removeNode()
            del crab

        if hasattr(self, 'treasures') and self.treasures:
            for i in xrange(self.NUMTREASURES):
                self.treasures[i].destroy()

            del self.treasures
        if hasattr(self, 'cSphereNodePath1'):
            self.cSphereNodePath1.removeNode()
            del self.cSphereNodePath1
        if hasattr(self, 'cSphereNodePath1'):
            self.cSphereNodePath2.removeNode()
            del self.cSphereNodePath2
        if hasattr(self, 'remoteToonCollNPs'):
            for np in self.remoteToonCollNPs.values():
                np.removeNode()

            del self.remoteToonCollNPs
        self.pusher = None
        self.cTrav = None
        self.cTrav2 = None
        base.localAvatar.collisionsOn()
        return

    def handleDisabledAvatar(self, avId):
        self.dead = 1
        self.notify.debug('handleDisabledAvatar')
        self.notify.debug('avatar ' + str(avId) + ' disabled')
        self.toonSDs[avId].exit(unexpectedExit=True)
        del self.toonSDs[avId]

    def __placeToon(self, avId):
        toon = self.getAvatar(avId)
        i = self.avIdList.index(avId)
        numToons = float(self.numPlayers)
        x = -10 + i * 5
        toon.setPos(x, -1, 36)
        toon.setHpr(180, 180, 0)

    def getTelemetryLimiter(self):
        return TLGatherAllAvs('DivingGame', Functor(DivingGameRotationLimiter, 180, 180))

    def setGameReady(self):
        self.notify.debug('setGameReady')
        if not self.hasLocalToon:
            return
        if DistributedMinigame.setGameReady(self):
            return
        self.dead = 0
        self.difficultyPatterns = {ToontownGlobals.ToontownCentral: [1,
                                           1.5,
                                           65,
                                           3],
         ToontownGlobals.DonaldsDock: [1,
                                       1.3,
                                       65,
                                       1],
         ToontownGlobals.DaisyGardens: [2,
                                        1.2,
                                        65,
                                        1],
         ToontownGlobals.MinniesMelodyland: [2,
                                             1.0,
                                             65,
                                             1],
         ToontownGlobals.TheBrrrgh: [3,
                                     1.0,
                                     65,
                                     1],
         ToontownGlobals.DonaldsDreamland: [3,
                                            1.0,
                                            65,
                                            1]}
        pattern = self.difficultyPatterns[self.getSafezoneId()]
        self.NUMCRABS = pattern[0]
        self.SPEEDMULT = pattern[1]
        self.TIME = pattern[2]
        loadBase = 'phase_4/models/char/'
        for i in xrange(self.NUMCRABS):
            self.crabs.append(Actor.Actor(loadBase + 'kingCrab-zero.bam', {'anim': loadBase + 'kingCrab-swimLOOP.bam'}))

        for i in xrange(len(self.crabs)):
            crab = self.crabs[i]
            crab.reparentTo(render)
            crab.name = 'king'
            crab.crabId = i
            cSphere = CollisionSphere(0.0, 0.0, 1, 1.3)
            cSphereNode = CollisionNode('crabby' + str(i))
            cSphereNode.addSolid(cSphere)
            cSphereNode.setFromCollideMask(BitMask32.allOff())
            cSphereNode.setIntoCollideMask(DivingGameGlobals.CollideMask)
            cSphereNodePath = crab.attachNewNode(cSphereNode)
            cSphereNodePath.setScale(1, 3, 1)
            self.accept('hitby-' + 'crabby' + str(i), self.fishCollision)
            if i % 2 is 0:
                crab.setPos(20, 0, -40)
                crab.direction = -1
            else:
                crab.setPos(-20, 0, -40)
                crab.direction = 1
            crab.loop('anim')
            crab.setScale(1, 0.3, 1)
            crab.moveLerp = Sequence()

        self.collHandEvent = CollisionHandlerEvent()
        self.cTrav = CollisionTraverser('DistributedDiverGame')
        self.cTrav2 = CollisionTraverser('DistributedDiverGame')
        self.collHandEvent.addInPattern('reach-%in')
        self.collHandEvent.addAgainPattern('reach-%in')
        self.collHandEvent.addInPattern('into-%in')
        self.collHandEvent.addInPattern('hitby-%in')
        loadBase = 'phase_4/models/minigames/'
        self.treasures = []
        self.chestIcons = {}
        for i in xrange(self.NUMTREASURES):
            self.chestIcons[i] = loader.loadModel(loadBase + 'treasure_chest.bam')
            self.chestIcons[i].reparentTo(self.mapModel)
            self.chestIcons[i].setScale(1.5)
            treasure = DivingTreasure.DivingTreasure(i)
            self.accept('grab-' + str(i), self.__treasureGrabbed)
            self.collHandEvent.addInPattern('grab-%in')
            self.collHandEvent.addAgainPattern('grab-%in')
            self.treasures.append(treasure)

        self.cTrav.traverse(render)
        spawnX = 24 * self.LAG_COMP
        spawnY = 0.6
        self.spawners.append(DivingFishSpawn.DivingFishSpawn(0, 1, Point3(-spawnX, spawnY, 25), self.collHandEvent))
        self.spawners.append(DivingFishSpawn.DivingFishSpawn(1, -1, Point3(spawnX, spawnY, 16), self.collHandEvent))
        self.spawners.append(DivingFishSpawn.DivingFishSpawn(2, 1, Point3(-spawnX, spawnY, 6), self.collHandEvent))
        self.spawners.append(DivingFishSpawn.DivingFishSpawn(3, -1, Point3(spawnX, spawnY, -4), self.collHandEvent))
        self.spawners.append(DivingFishSpawn.DivingFishSpawn(4, 1, Point3(-spawnX, spawnY, -15), self.collHandEvent))
        self.spawners.append(DivingFishSpawn.DivingFishSpawn(5, -1, Point3(spawnX, spawnY, -23), self.collHandEvent))
        for spawner in self.spawners:
            spawner.lastSpawn = 0

        cSphere = CollisionSphere(0.0, 0.0, 0.0, 1.4)
        cSphereNode = CollisionNode('%s' % self.localAvId)
        cSphereNode.addSolid(cSphere)
        cSphereNode.setFromCollideMask(DivingGameGlobals.CollideMask)
        cSphereNode.setIntoCollideMask(BitMask32.allOff())
        headparts = base.localAvatar.getHeadParts()
        pos = headparts[2].getPos()
        self.cSphereNodePath1 = base.localAvatar.attachNewNode(cSphereNode)
        self.cSphereNodePath1.setPos(pos + Point3(0, 1.5, 1))
        self.cTrav.addCollider(self.cSphereNodePath1, self.collHandEvent)
        cSphere = CollisionSphere(0.0, 0.0, 0.0, 1.4)
        cSphereNode = CollisionNode('%s' % self.localAvId)
        cSphereNode.addSolid(cSphere)
        cSphereNode.setFromCollideMask(DivingGameGlobals.CollideMask)
        cSphereNode.setFromCollideMask(BitMask32.allOff())
        cSphereNode.setIntoCollideMask(BitMask32.allOff())
        headparts = base.localAvatar.getHeadParts()
        pos = headparts[2].getPos()
        self.cSphereNodePath2 = base.localAvatar.attachNewNode(cSphereNode)
        self.cSphereNodePath2.setPos(pos + Point3(0, 1.5, -1))
        self.cTrav.addCollider(self.cSphereNodePath2, self.collHandEvent)
        self.pusher = CollisionHandlerPusher()
        self.pusher.addCollider(self.cSphereNodePath1, base.localAvatar)
        self.pusher.addCollider(self.cSphereNodePath2, base.localAvatar)
        self.pusher.setHorizontal(0)
        self.cTrav2.addCollider(self.cSphereNodePath1, self.pusher)
        self.cTrav2.addCollider(self.cSphereNodePath2, self.pusher)
        self.remoteToonCollNPs = {}
        for avId in self.remoteAvIdList:
            toon = self.getAvatar(avId)
            if toon:
                headparts = toon.getHeadParts()
                pos = headparts[2].getPos()
                cSphere = CollisionSphere(0.0, 0.0, 0.0, 1.4)
                cSphereNode = CollisionNode('%s' % avId)
                cSphereNode.addSolid(cSphere)
                cSphereNode.setCollideMask(DivingGameGlobals.CollideMask)
                cSphereNP = toon.attachNewNode(cSphereNode)
                cSphereNP.setPos(pos + Point3(0, 1.5, 1))
                self.remoteToonCollNPs[int(str(avId) + str(1))] = cSphereNP
                cSphere = CollisionSphere(0.0, 0.0, 0.0, 1.4)
                cSphereNode = CollisionNode('%s' % avId)
                cSphereNode.addSolid(cSphere)
                cSphereNode.setCollideMask(DivingGameGlobals.CollideMask)
                cSphereNP = toon.attachNewNode(cSphereNode)
                cSphereNP.setPos(pos + Point3(0, 1.5, -1))
                self.remoteToonCollNPs[int(str(avId) + str(1))] = cSphereNP
                toonSD = DivingGameToonSD.DivingGameToonSD(avId, self)
                self.toonSDs[avId] = toonSD
                toonSD.load()
                toonSD.enter()
                toonSD.fsm.request('normal')

        for avId in self.remoteAvIdList:
            toon = self.getAvatar(avId)
            if toon:
                toon.reparentTo(render)
                self.__placeToon(avId)
                toon.startSmooth()

        self.remoteToons = {}
        for avId in self.remoteAvIdList:
            toon = self.getAvatar(avId)
            self.remoteToons[avId] = toon

    def setGameStart(self, timestamp):
        if not self.hasLocalToon:
            return
        DistributedMinigame.setGameStart(self, timestamp)
        self.notify.debug('setGameStart')
        self.treasurePanel = TreasureScorePanel.TreasureScorePanel()
        self.treasurePanel.setPos(0.145, 0, -0.27)
        self.treasurePanel.reparentTo(base.a2dTopLeft)
        self.treasurePanel.makeTransparent(0.7)
        self.introMovie.finish()
        self.gameFSM.request('swim')

    def enterOff(self):
        self.notify.debug('enterOff')

    def exitOff(self):
        pass

    def enterSwim(self):
        self.notify.debug('enterSwim')
        base.playMusic(self.music, looping=1, volume=0.9)
        self.localLerp = Sequence()
        self.timer = ToontownTimer.ToontownTimer()
        self.timer.posInTopRightCorner()
        self.timer.setTime(self.TIME)
        self.timer.countdown(self.TIME, self.timerExpired)
        self.mapModel.show()
        self.mapAvatars = {}
        avatarScale = 0.025 * self.mapScaleRatio
        for avId in self.remoteAvIdList:
            avatar = base.cr.doId2do.get(avId, False)
            if avatar != False:
                self.mapAvatars[avId] = LaffMeter.LaffMeter(avatar.style, avatar.hp, avatar.maxHp)
                self.mapAvatars[avId].reparentTo(self.mapModel)
                self.mapAvatars[avId].setScale(avatarScale)
                self.mapAvatars[avId].start()

        avatar = base.cr.doId2do[self.localAvId]
        self.mapAvatars[self.localAvId] = LaffMeter.LaffMeter(avatar.style, avatar.hp, avatar.maxHp)
        self.mapAvatars[self.localAvId].reparentTo(self.mapModel)
        self.mapAvatars[self.localAvId].setScale(avatarScale)
        self.mapAvatars[self.localAvId].start()
        self.accept('resetClock', self.__resetClock)
        self.__spawnUpdateLocalToonTask()
        self.__spawnCrabTask()
        self.__spawnTreasureBoundsTask()

    def __resetClock(self, tOffset):
        self.notify.debug('resetClock')
        self.gameStartTime += tOffset
        self.timer.countdown(self.timer.currentTime + tOffset, self.timerExpired)

    def timerExpired(self):
        self.notify.debug('local timer expired')
        self.dead = 1
        self.gameOver()

    def __initPosBroadcast(self):
        self.__posBroadcastPeriod = 0.2
        self.__timeSinceLastPosBroadcast = 0.0
        self.__lastPosBroadcast = self.getAvatar(self.localAvId).getPos()
        self.__storeStop = 0
        lt = self.getAvatar(self.localAvId)
        lt.d_clearSmoothing()
        lt.sendCurrentPosition()

    def __posBroadcast(self, dt):
        self.__timeSinceLastPosBroadcast += dt
        if self.__timeSinceLastPosBroadcast > self.__posBroadcastPeriod:
            self.__timeSinceLastPosBroadcast -= self.__posBroadcastPeriod
            self.getAvatar(self.localAvId).cnode.broadcastPosHprFull()

    def __spawnTreasureBoundsTask(self):
        taskMgr.remove(self.TREASURE_BOUNDS_TASK)
        taskMgr.add(self.__treasureBoundsTask, self.TREASURE_BOUNDS_TASK)

    def __killTreasureBoundsTask(self):
        taskMgr.remove(self.TREASURE_BOUNDS_TASK)

    def __treasureBoundsTask(self, task):
        for i in xrange(self.NUMTREASURES):
            self.chestIcons[i].setPos(self.treasures[i].chest.getPos(render) / self.MAP_DIV)
            self.chestIcons[i].setZ(self.chestIcons[i].getZ() + self.MAP_OFF)
            if self.treasures[i].treasureNode.getZ() < -36:
                self.treasures[i].treasureNode.setZ(-36)
            if self.treasures[i].treasureNode.getX() < -20:
                self.treasures[i].treasureNode.setX(-20)
            if self.treasures[i].treasureNode.getX() > 20:
                self.treasures[i].treasureNode.setX(20)

        return Task.cont

    def incrementScore(self, avId, newSpot, timestamp):
        if not self.hasLocalToon:
            return
        newSpot += -15
        ts = globalClockDelta.localElapsedTime(timestamp)
        toonSD = self.toonSDs[avId]
        if avId == self.localAvId:
            self.reachedFlag = 0
        if toonSD.status == 'treasure' and self.treasures and self.chestIcons:
            for i in xrange(self.NUMTREASURES):
                if self.treasures[i].grabbedId == avId:
                    self.treasures[i].treasureNode.wrtReparentTo(render)
                    self.treasures[i].grabbedId = 0
                    seq = Sequence()
                    shrink = LerpScaleInterval(self.treasures[i].treasureNode, duration=1.0, startScale=self.treasures[i].treasureNode.getScale(), scale=Vec3(0.001, 0.001, 0.001), blendType='easeIn')
                    shrinkIcon = LerpScaleInterval(self.chestIcons[i], duration=1.0, startScale=self.chestIcons[i].getScale(), scale=Vec3(0.001, 0.001, 0.001), blendType='easeIn')
                    jump = ProjectileInterval(self.treasures[i].treasureNode, duration=1.0, startPos=self.treasures[i].treasureNode.getPos(), endPos=Point3(0, 0, 40), gravityMult=0.7)
                    shrinkJump = Parallel(shrink, shrinkIcon, jump)
                    toonSD.fsm.request('normal')
                    grow = LerpScaleInterval(self.treasures[i].treasureNode, duration=1.0, scale=self.treasures[i].treasureNode.getScale(), startScale=Vec3(0.001, 0.001, 0.001), blendType='easeIn')
                    growIcon = LerpScaleInterval(self.chestIcons[i], duration=1.0, scale=self.chestIcons[i].getScale(), startScale=Vec3(0.001, 0.001, 0.001), blendType='easeIn')
                    place = Parallel(Func(self.treasures[i].treasureNode.setPos, Vec3(newSpot, 0.25, -36)), Func(self.treasures[i].treasureNode.setHpr, Vec3(0, 0, 0)))
                    growItems = Parallel(grow, growIcon)
                    resetChest = Func(self.treasures[i].chestNode.setIntoCollideMask, DivingGameGlobals.CollideMask)
                    seq = Sequence(shrinkJump, Wait(1.5), place, growItems, resetChest)
                    self.treasures[i].moveLerp.pause()
                    self.treasures[i].moveLerp = seq
                    self.treasures[i].moveLerp.start(ts)
                    self.playSound('dropGold')
                    self.treasurePanel.incrScore()

    def __boatReached(self, collEntry):
        toonSD = self.toonSDs[self.localAvId]
        if toonSD.status == 'treasure' and not self.reachedFlag:
            self.sendUpdate('treasureRecovered')
            self.reachedFlag = 1

    def __treasureGrabbed(self, collEntry):
        avId = int(collEntry.getFromNodePath().getName())
        chestId = int(collEntry.getIntoNodePath().getName())
        toonSD = self.toonSDs[avId]
        if toonSD.status == 'normal' and self.grabbingTreasure == -1:
            self.grabbingTreasure = chestId
            self.sendUpdate('pickupTreasure', [chestId])

    def setTreasureDropped(self, avId, timestamp):
        if not hasattr(self, 'treasures'):
            return
        ts = globalClockDelta.localElapsedTime(timestamp)
        for i in xrange(self.NUMTREASURES):
            if self.treasures[i].grabbedId == avId:
                self.treasures[i].grabbedId = 0
                toonSD = self.toonSDs[avId]
                dist = abs(36.0 + self.treasures[i].treasureNode.getZ(render))
                delta = dist / 72.0
                dur = 10 * delta
                self.treasures[i].treasureNode.wrtReparentTo(render)
                self.treasures[i].chestNode.setIntoCollideMask(BitMask32.allOff())
                resetChest = Func(self.treasures[i].chestNode.setIntoCollideMask, DivingGameGlobals.CollideMask)
                self.treasures[i].moveLerp.pause()
                self.treasures[i].moveLerp = Parallel(Sequence(Wait(1.0), resetChest), LerpFunc(self.treasures[i].treasureNode.setZ, duration=dur, fromData=self.treasures[i].treasureNode.getZ(render), toData=-36, blendType='easeIn'))
                self.treasures[i].moveLerp.start(ts)

    def performCrabCollision(self, avId, timestamp):
        if not self.hasLocalToon:
            return
        ts = globalClockDelta.localElapsedTime(timestamp)
        toonSD = self.toonSDs[avId]
        toon = self.getAvatar(avId)
        distance = base.localAvatar.getDistance(toon)
        volume = 0
        soundRange = 15.0
        if distance < soundRange:
            volume = (soundRange - distance) / soundRange
        if toonSD.status == 'normal' or toonSD.status == 'treasure':
            self.localLerp.finish()
            self.localLerp = Sequence(Func(toonSD.fsm.request, 'freeze'), Wait(3.0), Func(toonSD.fsm.request, 'normal'))
            self.localLerp.start(ts)
            self.hitSound.play()
            self.hitSound.setVolume(volume)

    def performFishCollision(self, avId, spawnId, spawnerId, timestamp):
        if not hasattr(self, 'spawners'):
            return
        toonSD = self.toonSDs[avId]
        ts = globalClockDelta.localElapsedTime(timestamp)
        toon = self.getAvatar(avId)
        distance = base.localAvatar.getDistance(toon)
        volume = 0
        soundRange = 15.0
        if distance < soundRange:
            volume = (soundRange - distance) / soundRange
        if toonSD.status == 'normal' or toonSD.status == 'treasure':
            self.localLerp.finish()
            self.localLerp = Sequence(Func(toonSD.fsm.request, 'freeze'), Wait(3.0), Func(toonSD.fsm.request, 'normal'))
            self.localLerp.start(ts)
        if spawnId in self.spawners[spawnerId].fishArray:
            fish = self.spawners[spawnerId].fishArray[spawnId]
            endX = self.spawners[spawnerId].position.getX()
            if fish.name == 'clown':
                fishSoundName = 'Clownfish.ogg'
            elif fish.name == 'pbj':
                fishSoundName = 'PBJ_Fish.ogg'
            elif fish.name == 'balloon':
                fishSoundName = 'BalloonFish.ogg'
            elif fish.name == 'bear':
                fishSoundName = 'Bear_Acuda.ogg'
            elif fish.name == 'nurse':
                fishSoundName = 'Nurse_Shark.ogg'
            elif fish.name == 'piano':
                fishSoundName = 'Piano_Tuna.ogg'
            else:
                fishSoundName = ' '
            fishSoundPath = 'phase_4/audio/sfx/%s' % fishSoundName
            fish.sound = loader.loadSfx(fishSoundPath)
            if fish.sound:
                fish.sound.play()
                fish.sound.setVolume(volume)
                self.hitSound.play()
                self.hitSound.setVolume(volume)
            if fish.name is 'bear' or fish.name is 'nurse':
                return
            colList = fish.findAllMatches('**/fc*')
            for col in colList:
                col.removeNode()

            fish.moveLerp.pause()
            if fish.name == 'clown' or fish.name == 'piano':
                if fish.name != 'piano':
                    endHpr = Vec3(fish.getH() * -1, 0, 0)
                elif fish.direction == -1:
                    endHpr = Vec3(180, 0, 0)
                else:
                    endHpr = Vec3(0, 0, 0)
                fish.moveLerp = Sequence(LerpHprInterval(fish, duration=0.4, startHpr=fish.getHpr(), hpr=endHpr), LerpFunc(fish.setX, duration=1.5, fromData=fish.getX(), toData=endX), Func(self.fishRemove, str(spawnerId) + str(spawnId)))
            elif fish.name == 'pbj':
                fish.moveLerp = Sequence(LerpFunc(fish.setX, duration=2, fromData=fish.getX(), toData=endX), Func(self.fishRemove, str(spawnerId) + str(spawnId)))
            elif fish.name == 'balloon':
                fish.specialLerp.pause()
                anim = Func(fish.play, 'anim', fromFrame=110, toFrame=200)
                fish.setH(180)
                speed = Func(fish.setPlayRate, 3.0, 'anim')
                fish.moveLerp = Sequence(Func(fish.stop, 'anim'), speed, anim, Wait(1.0), LerpScaleInterval(fish, duration=0.8, startScale=fish.getScale, scale=0.001, blendType='easeIn'), Func(self.fishRemove, str(spawnerId) + str(spawnId)))
                fish.sound.setTime(11.5)
            fish.moveLerp.start(ts)

    def fishRemove(self, code):
        spawnId = int(code[1:len(code)])
        spawnerId = int(code[0])
        if spawnId in self.spawners[spawnerId].fishArray:
            fish = self.spawners[spawnerId].fishArray[spawnId]
            fish.specialLerp.finish()
            fish.moveLerp.finish()
            fish.specialLerp = None
            fish.moveLerp = None
            fish.removeNode()
            del fish
            del self.spawners[spawnerId].fishArray[spawnId]
        return

    def setTreasureGrabbed(self, avId, chestId):
        if not self.hasLocalToon:
            return
        if self.grabbingTreasure == chestId:
            self.grabbingTreasure = -1
        toonSD = self.toonSDs.get(avId)
        if toonSD and toonSD.status == 'normal':
            toonSD.fsm.request('treasure')
            self.treasures[chestId].moveLerp.pause()
            self.treasures[chestId].moveLerp = Sequence()
            self.treasures[chestId].chestNode.setIntoCollideMask(BitMask32.allOff())
            self.treasures[chestId].treasureNode.reparentTo(self.getAvatar(avId))
            headparts = self.getAvatar(avId).getHeadParts()
            pos = headparts[2].getPos()
            self.treasures[chestId].treasureNode.setPos(pos + Point3(0, 0.2, 3))
            self.treasures[chestId].grabbedId = avId
            timestamp = globalClockDelta.getFrameNetworkTime()
            self.playSound('getGold')

    def __spawnCrabTask(self):
        taskMgr.remove(self.CRAB_TASK)
        taskMgr.add(self.__crabTask, self.CRAB_TASK)

    def __killCrabTask(self):
        taskMgr.remove(self.CRAB_TASK)

    def __crabTask(self, task):
        dt = globalClock.getDt()
        for crab in self.crabs:
            if not crab.moveLerp.isPlaying():
                crab.moveLerp = Wait(1.0)
                crab.moveLerp.loop()
                self.sendUpdate('getCrabMoving', [crab.crabId, crab.getX(), crab.direction])
                return Task.cont

        return Task.cont

    def setCrabMoving(self, crabId, timestamp, rand1, rand2, crabX, dir):
        if self.dead == 1:
            self.__killCrabTask()
            return
        if not hasattr(self, 'crabs'):
            return
        crab = self.crabs[crabId]
        ts = globalClockDelta.localElapsedTime(timestamp)
        x = 0
        for i in xrange(self.NUMTREASURES):
            x += self.treasures[i].treasureNode.getX(render)

        x /= self.NUMTREASURES
        goalX = int(x + dir * (rand1 / 10.0) * 12 + 4.0)
        goalZ = -40 + 5 + 5 * (rand2 / 10.0)
        xTime = 1 + rand1 / 10.0 * 2
        zTime = 0.5 + rand2 / 10.0
        wait = rand1 / 10.0 + rand2 / 10.0 + 1
        crab.direction *= -1
        if goalX > 20:
            goalX = 20
        elif goalX < -20:
            goalX = 20
        loc = crab.getPos(render)
        distance = base.localAvatar.getDistance(crab)
        crabVolume = 0
        soundRange = 25.0
        if distance < soundRange:
            crabVolume = (soundRange - distance) / soundRange
        crabSoundInterval = SoundInterval(self.crabSound, loop=0, duration=1.6, startTime=0.0)
        seq = Sequence(Wait(wait), LerpPosInterval(crab, duration=xTime, startPos=Point3(crabX, 0, -40), pos=Point3(goalX, 0, -40), blendType='easeIn'), Parallel(Func(self.grabCrapVolume, crab), LerpPosInterval(crab, duration=zTime, startPos=Point3(goalX, 0, -40), pos=Point3(goalX, 0, goalZ), blendType='easeOut')), LerpPosInterval(crab, duration=zTime, startPos=Point3(goalX, 0, goalZ), pos=Point3(goalX, 0, -40), blendType='easeInOut'))
        crab.moveLerp.pause()
        crab.moveLerp = seq
        crab.moveLerp.start(ts)

    def grabCrapVolume(self, crab):
        if crab:
            distance = base.localAvatar.getDistance(crab)
            self.crabVolume = 0
            soundRange = 25.0
            if distance < soundRange:
                crabVolume = (soundRange - distance) / soundRange
                crabSoundInterval = SoundInterval(self.crabSound, loop=0, duration=1.6, startTime=0.0, volume=crabVolume)
                crabSoundInterval.start()

    def __spawnUpdateLocalToonTask(self):
        self.__initPosBroadcast()
        taskMgr.remove(self.UPDATE_LOCALTOON_TASK)
        taskMgr.add(self.__updateLocalToonTask, self.UPDATE_LOCALTOON_TASK)

    def __killUpdateLocalToonTask(self):
        taskMgr.remove(self.UPDATE_LOCALTOON_TASK)

    def __updateLocalToonTask(self, task):
        dt = globalClock.getDt()
        toonPos = base.localAvatar.getPos()
        toonHpr = base.localAvatar.getHpr()
        self.xVel *= 0.99
        self.zVel *= 0.99
        pos = [toonPos[0], toonPos[1], toonPos[2]]
        hpr = [toonHpr[0], toonHpr[1], toonHpr[2]]
        r = 0
        toonSD = self.toonSDs[self.localAvId]
        if toonSD.status == 'normal' or toonSD.status == 'treasure':
            if self.arrowKeys.leftPressed():
                r -= 80
            if self.arrowKeys.rightPressed():
                r += 80
            hpr[2] += r * dt
            pos1 = self.orientNode.getPos(render)
            pos2 = self.orientNode2.getPos(render)
            upVec = Vec2(pos1[0], pos1[2])
            bkVec = Vec2(pos2[0], pos2[2])
            forVec = upVec - Vec2(pos[0], pos[2])
            bckVec = bkVec - Vec2(pos[0], pos[2])
            r = 0
            if self.arrowKeys.upPressed():
                r += 20
                self.xVel = forVec[0] * 8
                self.zVel = forVec[1] * 8
            elif self.arrowKeys.downPressed():
                r -= 20
                self.xVel = bckVec[0] * 4
                self.zVel = bckVec[1] * 4
            if self.xVel > 20:
                self.xVel = 20
            elif self.xVel < -20:
                self.xVel = -20
            if self.zVel > 10:
                self.zVel = 10
            elif self.zVel < -10:
                self.zVel = -10
        swimVolume = (abs(self.zVel) + abs(self.xVel)) / 15.0
        self.swimSound.setVolume(swimVolume)
        pos[0] += self.xVel * dt
        pos[1] = -2
        pos[2] += self.zVel * dt
        found = 0
        for i in xrange(self.NUMTREASURES):
            if self.treasures[i].grabbedId == self.localAvId:
                found = 1
                i = self.NUMTREASURES + 1
                pos[2] -= 0.8 * dt

        if found == 0:
            pos[2] += 0.8 * dt
        if pos[2] < -38:
            pos[2] = -38
        elif pos[2] > 36:
            pos[2] = 36
        if pos[0] < -20:
            pos[0] = -20
        elif pos[0] > 20:
            pos[0] = 20
        base.localAvatar.setPos(pos[0], pos[1], pos[2])
        base.localAvatar.setHpr(hpr[0], hpr[1], hpr[2])
        posDiv = self.MAP_DIV
        self.mapAvatars[self.localAvId].setPos(pos[0] / posDiv, pos[1] / posDiv, pos[2] / posDiv + self.MAP_OFF)
        for avId in self.remoteAvIdList:
            toon = self.getAvatar(avId)
            if toon:
                pos = toon.getPos()
                self.mapAvatars[avId].setPos(pos / posDiv)
                self.mapAvatars[avId].setZ(self.mapAvatars[avId].getZ() + self.MAP_OFF)

        self.cTrav.traverse(render)
        self.cTrav2.traverse(render)
        self.__posBroadcast(dt)
        z = self.getAvatar(self.localAvId).getZ() + 3
        camBottom = math.tan(base.camLens.getVfov()/2.0*math.pi/180)*54
        z = max(z, -42+camBottom)
        camera.setZ(z)
        ambVolume = abs(z - 25.0) / 50.0 + 0.1
        if ambVolume < 0.0:
            ambVolume = 0.0
        if ambVolume > 1.0:
            ambVolume = 1.0
        ambVolume = pow(ambVolume, 0.75)
        self.sndAmbience.setVolume(ambVolume)
        return Task.cont

    def exitSwim(self):
        self.music.stop()
        self.ignore('resetClock')
        self.__killUpdateLocalToonTask()
        self.__killCrabTask()
        self.__killTreasureBoundsTask()
        self.timer.stop()
        self.timer.destroy()
        self.localLerp.finish()
        self.introMovie.finish()
        self.boatTilt.finish()
        self.treasurePanel.cleanup()
        self.mapAvatars[self.localAvId].destroy()
        del self.mapAvatars
        for i in xrange(self.NUMTREASURES):
            del self.chestIcons[i]

        del self.timer

    def enterCleanup(self):
        pass

    def exitCleanup(self):
        pass
