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
from . import ArrowKeys
from . import Ring
from . import RingTrack
from . import DivingGameGlobals
from . import RingGroup
from . import RingTrackGroups
import random
from . import DivingGameToonSD
from . import DivingFishSpawn
from . import DivingTreasure
import math
from . 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.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.loader.loadSfx('phase_4/audio/sfx/AV_ambient_water.mp3')
        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.mp3'
        crabSoundPath = 'phase_4/audio/sfx/%s' % crabSoundName
        self.crabSound = loader.loadSfx(crabSoundPath)
        treasureSoundName = 'SZ_DD_treasure.mp3'
        treasureSoundPath = 'phase_4/audio/sfx/%s' % treasureSoundName
        self.treasureSound = loader.loadSfx(treasureSoundPath)
        hitSoundName = 'diving_game_hit.mp3'
        hitSoundPath = 'phase_4/audio/sfx/%s' % hitSoundName
        self.hitSound = loader.loadSfx(hitSoundPath)
        self.music = base.loader.loadMusic('phase_4/audio/bgm/MG_Target.mid')
        self.addSound('dropGold', 'diving_treasure_drop_off.mp3', 'phase_4/audio/sfx/')
        self.addSound('getGold', 'diving_treasure_pick_up.mp3', 'phase_4/audio/sfx/')
        self.swimSound = loader.loadSfx('phase_4/audio/sfx/diving_swim_loop.wav')
        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 list(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.setH(0)
        camera.setX(0)
        base.camLens.setFov(45)
        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(aspect2d)
        self.mapModel.setScale(1.0 / self.mapScaleRatio)
        self.mapModel.setTransparency(1)
        self.mapModel.setPos(1.15, -0.5, -0.125)
        self.mapModel.setColorScale(1, 1, 1, 0.7)
        self.mapModel.hide()
        if None != 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 list(self.toonSDs.keys()):
            self.toonSDs[avId].exit()

        base.camLens.setFar(ToontownGlobals.DefaultCameraFar)
        base.camLens.setFov(ToontownGlobals.DefaultCameraFov)
        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 range(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 list(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 range(self.NUMCRABS):
            self.crabs.append(Actor.Actor(loadBase + 'kingCrab-zero.bam', {'anim': loadBase + 'kingCrab-swimLOOP.bam'}))

        for i in range(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 range(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(-1.19, 0, 0.75)
        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 range(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 range(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':
            self.sendUpdate('pickupTreasure', [chestId])

    def setTreasureDropped(self, avId, timestamp):
        if not hasattr(self, 'treasures'):
            return
        ts = globalClockDelta.localElapsedTime(timestamp)
        for i in range(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.mp3'
            elif fish.name == 'pbj':
                fishSoundName = 'PBJ_Fish.mp3'
            elif fish.name == 'balloon':
                fishSoundName = 'BalloonFish.mp3'
            elif fish.name == 'bear':
                fishSoundName = 'Bear_Acuda.mp3'
            elif fish.name == 'nurse':
                fishSoundName = 'Nurse_Shark.mp3'
            elif fish.name == 'piano':
                fishSoundName = 'Piano_Tuna.mp3'
            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]
        else:
            import pdb
            pdb.setTrace()
        return

    def setTreasureGrabbed(self, avId, chestId):
        if not self.hasLocalToon:
            return
        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 range(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 range(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
        if z < -25:
            z = -25
        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 range(self.NUMTREASURES):
            del self.chestIcons[i]

        del self.timer

    def enterCleanup(self):
        pass

    def exitCleanup(self):
        pass