from direct.directnotify import DirectNotifyGlobal
from panda3d.core import *
from panda3d.toontown import *
from toontown.toonbase.ToonBaseGlobal import *
from .DistributedMinigame import *
from direct.distributed.ClockDelta import *
from direct.interval.IntervalGlobal import *
from direct.fsm import ClassicFSM, State
from direct.fsm import State
from toontown.toonbase import ToontownGlobals
from toontown.toonbase import ToontownTimer
from direct.task.Task import Task
import math
from toontown.toon import ToonHead
from . import PhotoGameGlobals
from direct.gui.DirectGui import *
from panda3d.core import *
from toontown.toonbase import TTLocalizer
from toontown.golf import BuildGeometry
from toontown.toon import Toon
from toontown.toon import ToonDNA
from direct.interval.IntervalGlobal import *
import random
from direct.showbase import PythonUtil
import math
import time
from toontown.makeatoon import NameGenerator
from otp.otpbase import OTPGlobals
from toontown.battle import BattleParticles
from toontown.minigame import PhotoGameBase
WORLD_SCALE = 2.0
FAR_PLANE_DIST = 600 * WORLD_SCALE
STAGE_Z_OFFSET = 7.0
GOODROWS = 13
BADROWS = 4
RAYSPREADX = 0.08
RAYSPREADY = 0.06
ZOOMRATIO = 0.4
ZOOMTIME = 0.5
WANTDOTS = 1
NUMSTARS = PhotoGameGlobals.NUMSTARS
STARSIZE = 0.06
VIEWSIZEX = (GOODROWS - BADROWS) * RAYSPREADX
VIEWSIZEY = (GOODROWS - BADROWS) * RAYSPREADY

def toRadians(angle):
    return angle * 2.0 * math.pi / 360.0


def toDegrees(angle):
    return angle * 360.0 / (2.0 * math.pi)


class DistributedPhotoGame(DistributedMinigame, PhotoGameBase.PhotoGameBase):
    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPhotoGame')
    font = ToontownGlobals.getToonFont()
    LOCAL_PHOTO_MOVE_TASK = 'localPhotoMoveTask'
    FIRE_KEY = 'control'
    UP_KEY = 'arrow_up'
    DOWN_KEY = 'arrow_down'
    LEFT_KEY = 'arrow_left'
    RIGHT_KEY = 'arrow_right'
    INTRO_TASK_NAME = 'PhotoGameIntro'
    INTRO_TASK_NAME_CAMERA_LERP = 'PhotoGameIntroCamera'

    def __init__(self, cr):
        DistributedMinigame.__init__(self, cr)
        PhotoGameBase.PhotoGameBase.__init__(self)
        self.gameFSM = ClassicFSM.ClassicFSM('DistributedPhotoGame', [State.State('off', self.enterOff, self.exitOff, ['aim']),
         State.State('aim', self.enterAim, self.exitAim, ['showResults', 'cleanup', 'zoom']),
         State.State('zoom', self.enterZoom, self.exitZoom, ['showResults', 'cleanup', 'aim']),
         State.State('showResults', self.enterShowResults, self.exitShowResults, ['cleanup']),
         State.State('cleanup', self.enterCleanup, self.exitCleanup, [])], 'off', 'cleanup')
        self.addChildGameFSM(self.gameFSM)
        self.tripod = None
        self.leftPressed = 0
        self.rightPressed = 0
        self.upPressed = 0
        self.downPressed = 0
        self.photoMoving = 0
        self.introSequence = None
        self.subjects = []
        self.scenery = []
        self.subjectNode = render.attachNewNode('subjects')
        self.subjectTracks = {}
        self.nameCounter = 0
        self.zoomedView = 0
        self.zoomFlip = 1
        self.cameraTrack = None
        self.assignments = []
        self.currentAssignment = 0
        self.assignmentPanels = []
        self.toonList = []
        self.assignmentDataDict = {}
        self.starDict = {}
        self.starParentDict = {}
        self.textureBuffers = []
        self.filmCount = 20
        self.edgeUp = 0
        self.edgeRight = 0
        self.edgeDown = 0
        self.edgeLeft = 0
        self.scorePanel = None
        return

    def getTitle(self):
        return TTLocalizer.PhotoGameTitle

    def getInstructions(self):
        return TTLocalizer.PhotoGameInstructions

    def getMaxDuration(self):
        return PhotoGameGlobals.GAME_TIME

    def load(self):
        self.notify.debug('load')
        DistributedMinigame.load(self)
        PhotoGameBase.PhotoGameBase.load(self)
        self.filmCount = self.data['FILMCOUNT']
        self.safeZoneStorageDNAFile = self.data['DNA_TRIO'][0]
        self.storageDNAFile = self.data['DNA_TRIO'][1]
        self.dnaFile = self.data['DNA_TRIO'][2]
        self.dnaStore = DNAStorage()
        loader.loadDNAFile(self.dnaStore, 'phase_4/dna/storage.dna')
        loader.loadDNAFile(self.dnaStore, self.storageDNAFile)
        loader.loadDNAFile(self.dnaStore, self.safeZoneStorageDNAFile)
        node = loader.loadDNAFile(self.dnaStore, self.dnaFile)
        self.scene = hidden.attachNewNode(node)
        self.construct()
        purchaseModels = loader.loadModel('phase_4/models/gui/purchase_gui')
        self.filmImage = loader.loadModel('phase_4/models/minigames/photogame_filmroll')
        self.filmImage.reparentTo(hidden)
        self.tripodModel = loader.loadModel('phase_4/models/minigames/toon_cannon')
        self.filmPanel = DirectLabel(parent=hidden, relief=None, pos=(1.16, 0.0, 0.45), scale=0.65, text=str(self.filmCount), text_scale=0.2, text_fg=(0.95, 0.95, 0, 1), text_pos=(0.08, -0.15), text_font=ToontownGlobals.getSignFont(), image=self.filmImage, image_scale=Point3(1.0, 0.0, 0.85))
        self.filmPanelTitle = DirectLabel(parent=self.filmPanel, relief=None, pos=(0.08, 0, 0.04), scale=0.08, text=TTLocalizer.PhotoGameFilm, text_fg=(0.95, 0.95, 0, 1), text_shadow=(0, 0, 0, 1))
        self.music = base.loader.loadMusic('phase_4/audio/bgm/MG_cannon_game.ogg')
        self.sndPhotoMove = base.loader.loadSfx('phase_4/audio/sfx/MG_cannon_adjust.ogg')
        self.sndPhotoFire = base.loader.loadSfx('phase_4/audio/sfx/MG_cannon_fire_alt.ogg')
        self.sndWin = base.loader.loadSfx('phase_4/audio/sfx/MG_win.ogg')
        self.sndFilmTick = base.loader.loadSfx('phase_4/audio/sfx/Photo_instamatic.ogg')
        self.timer = ToontownTimer.ToontownTimer()
        self.timer.posInTopRightCorner()
        self.timer.hide()
        self.viewfinderNode = base.aspect2d.attachNewNode('camera node')
        self.viewfinderNode.setTransparency(TransparencyAttrib.MAlpha)
        self.viewfinderNode.setDepthWrite(1)
        self.viewfinderNode.setDepthTest(1)
        self.viewfinderNode.setY(-1.0)
        self.screenSizeMult = 0.5
        self.screenSizeX = (base.a2dRight - base.a2dLeft) * self.screenSizeMult
        self.screenSizeZ = (base.a2dTop - base.a2dBottom) * self.screenSizeMult
        viewfinderImage = loader.loadModel('phase_4/models/minigames/photo_game_viewfinder')
        viewfinderImage.reparentTo(self.viewfinderNode)
        viewfinderImage.setScale(0.55, 1.0, 0.55)
        self.blackoutNode = base.aspect2d.attachNewNode('blackout node')
        self.blackoutNode.setP(90)
        BuildGeometry.addSquareGeom(self.blackoutNode, self.screenSizeX * 2.2, self.screenSizeZ * 2.2, Vec4(1.0, 1.0, 1.0, 1.0))
        self.blackoutNode.setTransparency(TransparencyAttrib.MAlpha)
        self.blackoutNode.setColorScale(0.0, 0.0, 0.0, 0.5)
        self.blackoutNode.setDepthWrite(1)
        self.blackoutNode.setDepthTest(1)
        self.blackoutNode.hide()
        self.subjectToon = Toon.Toon()
        self.addSound('zoom', 'Photo_zoom.ogg', 'phase_4/audio/sfx/')
        self.addSound('snap', 'Photo_shutter.ogg', 'phase_4/audio/sfx/')
        return

    def __setupCapture(self):
        self.captureCam = NodePath(Camera('CaptureCamera'))
        self.captureCam.reparentTo(self.pointer)
        self.captureLens = PerspectiveLens()
        self.captureOutFOV = VIEWSIZEX / self.screenSizeX * self.outFov * 0.5
        self.captureZoomFOV = VIEWSIZEX / self.screenSizeX * self.zoomFov * 0.5
        self.captureLens.setFov(self.captureOutFOV)
        self.captureLens.setAspectRatio(1.33)
        self.captureCam.node().setLens(self.captureLens)

    def __removeCapture(self):
        del self.captureCam
        del self.captureLens

    def unload(self):
        self.notify.debug('unload')
        DistributedMinigame.unload(self)
        if self.cameraTrack:
            self.cameraTrack.finish()
            self.cameraTrack = None
        self.__removeCapture()
        for textureBuffer in self.textureBuffers:
            base.graphicsEngine.removeWindow(textureBuffer)

        del self.textureBuffers
        self.viewfinderNode.removeNode()
        self.blackoutNode.removeNode()
        for key in self.assignmentDataDict:
            assignmentData = self.assignmentDataDict[key]
            assignmentData[7].delete()

        del self.assignmentDataDict
        self.assignments = []
        for subject in self.subjects:
            subject.delete()

        self.subjects = []
        self.subjectToon.delete()
        self.destruct()
        for scenery in self.scenery:
            scenery.removeNode()

        self.scenery = None
        self.subjectNode.removeNode()
        self.subjectNode = None
        self.sky.removeNode()
        del self.sky
        self.photoRoot = None
        self.scene.removeNode()
        del self.scene
        self.pointer.removeNode()
        self.tripodModel.removeNode()
        del self.tripodModel
        for panel in self.assignmentPanels:
            panel.destroy()

        self.assignmentPanels = []
        if self.scorePanel:
            self.scorePanel.destroy()
        self.starDict = {}
        self.starParentDict = {}
        self.filmPanel.destroy()
        del self.filmPanel
        self.filmImage.removeNode()
        del self.filmImage
        del self.music
        del self.sndPhotoMove
        del self.sndPhotoFire
        del self.sndWin
        del self.sndFilmTick
        self.tripod.removeNode()
        del self.tripod
        del self.swivel
        self.timer.destroy()
        del self.timer
        self.removeChildGameFSM(self.gameFSM)
        del self.gameFSM
        self.ignoreAll()
        return

    def onstage(self):
        self.notify.debug('Onstage')
        DistributedMinigame.onstage(self)
        self.__createTripod()
        self.tripod.reparentTo(render)
        self.tripod.hide()
        self.__loadToonInTripod(self.localAvId)
        camera.reparentTo(render)
        self.__oldCamFar = base.camLens.getFar()
        base.camLens.setFar(FAR_PLANE_DIST)
        self.__setupSubjects()
        self.__startIntro()
        base.transitions.irisIn()
        base.playMusic(self.music, looping=1, volume=0.8)
        orgFov = base.camLens.getFov()
        self.outFov = orgFov.getX()
        self.zoomFov = orgFov.getX() * ZOOMRATIO
        self.currentFov = self.outFov
        self.__setupCapture()

    def offstage(self):
        self.notify.debug('offstage')
        self.sky.reparentTo(hidden)
        self.scene.reparentTo(hidden)
        for avId in self.avIdList:
            av = self.getAvatar(avId)
            if av:
                av.dropShadow.show()
                av.resetLOD()

        self.__stopIntro()
        base.camLens.setFar(self.__oldCamFar)
        self.timer.reparentTo(hidden)
        self.filmPanel.reparentTo(hidden)
        DistributedMinigame.offstage(self)
        for key in self.subjectTracks:
            track = self.subjectTracks[key][0]
            track.pause()
            del track

        self.subjectTracks = {}
        base.localAvatar.laffMeter.start()
        del self.soundTable

    def __setupCollisions(self):
        self.queue = CollisionHandlerQueue()
        self.traverser = CollisionTraverser('traverser name')
        self.rayArray = []
        vRange = (GOODROWS - BADROWS) // 2
        for row in range(-(GOODROWS // 2), GOODROWS // 2 + 1):
            for column in range(-(GOODROWS // 2), GOODROWS // 2 + 1):
                goodRange = list(range(-((GOODROWS - BADROWS) // 2), (GOODROWS - BADROWS) // 2 + 1))
                rayQuality = 'g'
                if row not in goodRange or column not in goodRange:
                    rayQuality = 'l'
                    if row > vRange:
                        rayQuality = 'r'
                    if column > vRange:
                        rayQuality = 't'
                    if column < -vRange:
                        rayQuality = 'b'
                columnString = '+%s' % column
                if column < 0:
                    columnString = '%s' % column
                rowString = '+%s' % row
                if row < 0:
                    rowString = '%s' % row
                pickerNode = CollisionNode('%s %s %s' % (rowString, columnString, rayQuality))
                pickerNP = camera.attachNewNode(pickerNode)
                pickerNode.setFromCollideMask(GeomNode.getDefaultCollideMask())
                pickerRay = CollisionRay()
                pickerNode.addSolid(pickerRay)
                self.rayArray.append((row,
                 column,
                 pickerNode,
                 pickerNP,
                 pickerRay))
                self.traverser.addCollider(pickerNP, self.queue)

        if WANTDOTS:
            self.markerDict = {}
            for rayEntry in self.rayArray:
                markerNode = self.viewfinderNode.attachNewNode('marker Node')
                BuildGeometry.addSquareGeom(markerNode, 0.01, 0.01, Vec4(1.0, 1.0, 1.0, 1.0))
                markerNode.setX(RAYSPREADX * rayEntry[0])
                markerNode.setY(RAYSPREADY * rayEntry[1])
                markerNode.setDepthWrite(1)
                markerNode.setZ(2.0)
                self.markerDict[rayEntry[0], rayEntry[1]] = markerNode

        self.lensNode = LensNode('photo taker')
        self.lensNode.setLens(base.camLens)

    def __moveViewfinder(self, task):
        if base.mouseWatcherNode.hasMouse():
            mpos = base.mouseWatcherNode.getMouse()
            self.viewfinderNode.setX(mpos.getX() * self.screenSizeX)
            self.viewfinderNode.setZ(mpos.getY() * self.screenSizeZ)
            horzAngle = self.viewfinderNode.getX() / self.screenSizeX * 0.5 * base.camLens.getFov()[0]
            vertAngle = self.viewfinderNode.getZ() / self.screenSizeZ * 0.5 * base.camLens.getFov()[1]
            horzPointFlat = self.viewfinderNode.getX() / self.screenSizeX
            vertPointFlat = self.viewfinderNode.getZ() / self.screenSizeZ
            horzLength = base.camLens.getFov()[0] * 0.5
            vertLength = base.camLens.getFov()[1] * 0.5
            horzRadianLength = toRadians(horzLength)
            vertRadianLength = toRadians(vertLength)
            hMRadian = math.atan(horzPointFlat * math.tan(horzRadianLength))
            vMRadian = math.atan(vertPointFlat * math.tan(vertRadianLength))
            hMDegree = toDegrees(hMRadian)
            vMDegree = toDegrees(vMRadian)
            self.pointer.setH(-hMDegree)
            self.pointer.setP(vMDegree)
            newRight = 0
            newLeft = 0
            newUp = 0
            newDown = 0
            if self.viewfinderNode.getX() > self.screenSizeX * 0.95:
                newRight = 1
            if self.viewfinderNode.getX() < self.screenSizeX * -0.95:
                newLeft = 1
            if self.viewfinderNode.getZ() > self.screenSizeZ * 0.95:
                newUp = 1
            if self.viewfinderNode.getZ() < self.screenSizeZ * -0.95:
                newDown = 1
            if not self.edgeRight and newRight:
                self.edgeRight = 1
                self.__rightPressed()
            elif self.edgeRight and not newRight:
                self.edgeRight = 0
                self.__rightReleased()
            if not self.edgeLeft and newLeft:
                self.edgeLeft = 1
                self.__leftPressed()
            elif self.edgeLeft and not newLeft:
                self.edgeLeft = 0
                self.__leftReleased()
            if not self.edgeUp and newUp:
                self.edgeUp = 1
                self.__upPressed()
            elif self.edgeUp and not newUp:
                self.edgeUp = 0
                self.__upReleased()
            if not self.edgeDown and newDown:
                self.edgeDown = 1
                self.__downPressed()
            elif self.edgeDown and not newDown:
                self.edgeDown = 0
                self.__downReleased()
        return task.cont

    def __testCollisions(self):
        self.notify.debug('\nSnapping Photo')
        self.playSound('snap')
        if self.filmCount <= 0:
            self.notify.debug('No Film')
            return
        for rayEntry in self.rayArray:
            posX = (self.viewfinderNode.getX() + RAYSPREADX * rayEntry[0]) / self.screenSizeX
            posY = (self.viewfinderNode.getZ() + RAYSPREADY * rayEntry[1]) / self.screenSizeZ
            rayEntry[4].setFromLens(self.lensNode, posX, posY)

        self.traverser.traverse(self.subjectNode)
        distDict = {}
        hitDict = {}
        centerDict = {}
        for i in range(self.queue.getNumEntries()):
            entry = self.queue.getEntry(i)
            object = None
            objectIndex = None
            subjectIndexString = entry.getIntoNodePath().getNetTag('subjectIndex')
            sceneryIndexString = entry.getIntoNodePath().getNetTag('sceneryIndex')
            if subjectIndexString:
                objectIndex = int(subjectIndexString)
                object = self.subjects[objectIndex]
            elif sceneryIndexString:
                objectIndex = int(sceneryIndexString)
                object = self.scenery[objectIndex]
            marker = 'g'
            if 'b' in entry.getFromNodePath().getName():
                marker = 'b'
            if 't' in entry.getFromNodePath().getName():
                marker = 't'
            if 'r' in entry.getFromNodePath().getName():
                marker = 'r'
            if 'l' in entry.getFromNodePath().getName():
                marker = 'l'
            if object:
                newEntry = (entry.getFromNode(), object)
                distance = Vec3(entry.getSurfacePoint(self.tripod)).lengthSquared()
                name = entry.getFromNode().getName()
                if name not in distDict:
                    distDict[name] = distance
                    hitDict[name] = (entry.getFromNode(), object, marker)
                elif distance < distDict[name]:
                    distDict[name] = distance
                    hitDict[name] = (entry.getFromNode(), object, marker)

        for key in hitDict:
            hit = hitDict[key]
            superParent = hit[1]
            marker = hit[2]
            onCenter = 0
            overB = 0
            overT = 0
            overR = 0
            overL = 0
            quality = -1
            if marker == 'b':
                overB = 1
            elif marker == 't':
                overT = 1
            elif marker == 'r':
                overR = 1
            elif marker == 'l':
                overL = 1
            else:
                quality = 1
                onCenter = 1
            if superParent not in centerDict:
                centerDict[superParent] = (onCenter,
                 overB,
                 overT,
                 overR,
                 overL)
            else:
                centerDict[superParent] = (onCenter + centerDict[superParent][0],
                 overB + centerDict[superParent][1],
                 overT + centerDict[superParent][2],
                 overR + centerDict[superParent][3],
                 overL + centerDict[superParent][4])

        if WANTDOTS:
            for key in self.markerDict:
                node = self.markerDict[key]
                node.setColorScale(Vec4(1, 1, 1, 1))

            for key in hitDict:
                entry = hitDict[key]
                name = entry[0].getName()
                xS = int(name[0:2])
                yS = int(name[3:5])
                node = self.markerDict[xS, yS]
                node.setColorScale(Vec4(1.0, 0.0, 0.0, 1.0))

        centerDictKeys = []
        for key in centerDict:
            centerDictKeys.append(key)

        for subject in centerDictKeys:
            score = self.judgePhoto(subject, centerDict)
            self.notify.debug('Photo is %s / 5 stars' % score)
            self.notify.debug('assignment compare %s %s' % (self.determinePhotoContent(subject), self.assignments[self.currentAssignment]))
            content = self.determinePhotoContent(subject)
            if content:
                photoAnalysisZero = (content[0], content[1])
                if score and photoAnalysisZero in self.assignments:
                    index = self.assignments.index(photoAnalysisZero)
                    assignment = self.assignments[index]
                    self.notify.debug('assignment complete')
                    if score >= self.assignmentDataDict[assignment][0]:
                        subjectIndex = self.subjects.index(subject)
                        texturePanel = self.assignmentDataDict[assignment][5]
                        texture = self.assignmentDataDict[assignment][6]
                        buffer = self.assignmentDataDict[assignment][4]
                        panelToon = self.assignmentDataDict[assignment][7]
                        panelToon.hide()
                        buffer.setActive(1)
                        texturePanel.show()
                        texturePanel.setColorScale(1, 1, 1, 1)
                        taskMgr.doMethodLater(0.2, buffer.setActive, 'capture Image', [0])
                        if score > self.assignmentDataDict[assignment][0]:
                            self.assignmentDataDict[assignment][0] = score
                            self.updateAssignmentPanels()
                            self.sendUpdate('newClientPhotoScore', [subjectIndex, content[1], score])
                else:
                    self.notify.debug('assignment not complete')

        horzAngle = self.viewfinderNode.getX() / self.screenSizeX * 0.5 * base.camLens.getFov()[0]
        vertAngle = self.viewfinderNode.getZ() / self.screenSizeZ * 0.5 * base.camLens.getFov()[1]
        horzPointFlat = self.viewfinderNode.getX() / self.screenSizeX
        vertPointFlat = self.viewfinderNode.getZ() / self.screenSizeZ
        horzLength = base.camLens.getFov()[0] * 0.5
        vertLength = base.camLens.getFov()[1] * 0.5
        horzRadianLength = toRadians(horzLength)
        vertRadianLength = toRadians(vertLength)
        hMRadian = math.atan(horzPointFlat * math.tan(horzRadianLength))
        vMRadian = math.atan(vertPointFlat * math.tan(vertRadianLength))
        hMDegree = toDegrees(hMRadian)
        vMDegree = toDegrees(vMRadian)
        self.__decreaseFilmCount()
        if self.filmCount == 0:
            self.sendUpdate('filmOut', [])
        self.notify.debug('Screen angles H:%s V:%s' % (self.swivel.getH(), self.swivel.getP()))
        self.notify.debug('Viewfinder to screen angles H:%s V:%s' % (horzAngle, vertAngle))
        self.notify.debug('Viewfinder to screen angles with math H:%s V:%s' % (hMDegree, vMDegree))
        return

    def newAIPhotoScore(self, playerId, assignmentIndex, score):
        if len(self.assignments) > assignmentIndex:
            assignment = self.assignments[assignmentIndex]
            assignmentData = self.assignmentDataDict[assignment]
            if score > assignmentData[2]:
                assignmentData[2] = score
                assignmentData[3] = playerId
                self.updateAssignmentPanels()

    def determinePhotoContent(self, subject):
        if self.getSubjectTrackState(subject):
            return [subject, self.getSubjectTrackState(subject)[2]]
        else:
            return None
        return None

    def judgePhoto(self, subject, centerDict):
        self.notify.debug('judgePhoto')
        self.notify.debug(subject.getName())
        self.notify.debug(str(centerDict[subject]))
        a1 = camera.getH(render) % 360
        a2 = subject.getH(render) % 360
        angle = abs((a1 + 180 - a2) % 360 - 180)
        self.notify.debug('angle camera:%s subject:%s between:%s' % (camera.getH(render), subject.getH(render), angle))
        self.notify.debug(str(angle))
        centering = centerDict[subject]
        if type(subject) == type(self.subjectToon):
            facing = angle / 180.0
            interest = self.getSubjectTrackState(subject)[3]
            quality = centering[0] - (centering[1] + centering[2] + centering[3] + centering[4])
            tooClose = centering[1] and centering[2] or centering[3] and centering[4]
            portrait = centering[1] and not (centering[2] or centering[3] or centering[4])
            self.notify.debug('angle %s facing %s' % (angle, facing))
            self.notify.debug('Interest %s' % interest)
            self.notify.debug('Quality %s' % quality)
            self.notify.debug('tooClose %s' % tooClose)
            if quality <= 0:
                return None
            else:
                score = 0
                if angle >= 135:
                    score += 2
                elif angle >= 90:
                    score += 1
                elif angle <= 60:
                    score -= 1
                score += interest
                if quality >= 5 and (not tooClose or portrait):
                    score += 1
                    if quality >= 10:
                        score += 1
                        if quality >= 15:
                            score += 1
                score -= 2
                if score > NUMSTARS:
                    score = float(NUMSTARS)
                if score <= 0:
                    return 1
                else:
                    return score
        return None

    def __toggleView(self):
        self.notify.debug('Toggle View')
        hCam = self.swivel.getH()
        vCam = self.swivel.getP()
        horzPointFlat = self.viewfinderNode.getX() / self.screenSizeX
        vertPointFlat = self.viewfinderNode.getZ() / self.screenSizeZ
        horzLength = base.camLens.getFov()[0] * 0.5
        vertLength = base.camLens.getFov()[1] * 0.5
        horzRadianLength = toRadians(horzLength)
        vertRadianLength = toRadians(vertLength)
        hMRadian = math.atan(horzPointFlat * math.tan(horzRadianLength))
        vMRadian = math.atan(vertPointFlat * math.tan(vertRadianLength))
        hMDegree = toDegrees(hMRadian)
        vMDegree = toDegrees(vMRadian)
        if self.zoomedView:
            self.zoomedView = 0
        else:
            self.zoomedView = 1
        if self.zoomedView:
            self.notify.debug('Zoom In')
            hMove = hMDegree * (1.0 - ZOOMRATIO)
            vMove = vMDegree * (1.0 - ZOOMRATIO)
            self.currentFov = self.zoomFov
            base.camLens.setFov(self.zoomFov)
            self.blackoutNode.show()
            self.swivel.setHpr(self.swivel, hMove * -self.zoomFlip, vMove * self.zoomFlip, 0)
        else:
            self.notify.debug('Zoom Out')
            hMove = hMDegree * ((1.0 - ZOOMRATIO) / ZOOMRATIO)
            vMove = vMDegree * ((1.0 - ZOOMRATIO) / ZOOMRATIO)
            self.currentFov = self.outFov
            base.camLens.setFov(self.outFov)
            self.blackoutNode.hide()
            self.swivel.setHpr(self.swivel, hMove * self.zoomFlip, vMove * -self.zoomFlip, 0)

    def __doZoom(self):
        self.notify.debug('Toggle View')
        self.playSound('zoom')
        hCam = self.swivel.getH()
        vCam = self.swivel.getP()
        horzPointFlat = self.viewfinderNode.getX() / self.screenSizeX
        vertPointFlat = self.viewfinderNode.getZ() / self.screenSizeZ
        horzLength = base.camLens.getFov()[0] * 0.5
        vertLength = base.camLens.getFov()[1] * 0.5
        horzRadianLength = toRadians(horzLength)
        vertRadianLength = toRadians(vertLength)
        hMRadian = math.atan(horzPointFlat * math.tan(horzRadianLength))
        vMRadian = math.atan(vertPointFlat * math.tan(vertRadianLength))
        hMDegree = toDegrees(hMRadian)
        vMDegree = toDegrees(vMRadian)
        if self.zoomedView:
            self.zoomedView = 0
        else:
            self.zoomedView = 1
        self.cameraTrack = Sequence()
        if self.zoomedView:
            self.notify.debug('Zoom In')
            hMove = hMDegree * (1.0 - ZOOMRATIO)
            vMove = vMDegree * (1.0 - ZOOMRATIO)
            self.currentFov = self.zoomFov
            base.camLens.setFov(self.zoomFov)
            self.blackoutNode.show()
            orgQuat = self.swivel.getQuat()
            self.swivel.setHpr(self.swivel, hMove * -self.zoomFlip, vMove * self.zoomFlip, 0)
            self.swivel.setR(0.0)
            newQuat = self.swivel.getQuat()
            self.swivel.setQuat(orgQuat)
            zoomTrack = Parallel()
            zoomTrack.append(LerpQuatInterval(self.swivel, ZOOMTIME, newQuat))
            zoomTrack.append(LerpFunc(base.camLens.setFov, fromData=self.outFov, toData=self.zoomFov, duration=ZOOMTIME))
            zoomTrack.append(LerpFunc(self.setBlackout, fromData=0.0, toData=0.5, duration=ZOOMTIME))
            self.cameraTrack.append(zoomTrack)
            self.cameraTrack.append(Func(self.finishZoom, 1))
        else:
            self.notify.debug('Zoom Out')
            hMove = hMDegree * ((1.0 - ZOOMRATIO) / ZOOMRATIO)
            vMove = vMDegree * ((1.0 - ZOOMRATIO) / ZOOMRATIO)
            self.currentFov = self.outFov
            base.camLens.setFov(self.outFov)
            orgQuat = self.swivel.getQuat()
            self.swivel.setHpr(self.swivel, hMove * self.zoomFlip, vMove * -self.zoomFlip, 0)
            self.swivel.setR(0.0)
            newQuat = self.swivel.getQuat()
            self.swivel.setQuat(orgQuat)
            zoomTrack = Parallel()
            zoomTrack.append(LerpQuatInterval(self.swivel, ZOOMTIME, newQuat))
            zoomTrack.append(LerpFunc(base.camLens.setFov, fromData=self.zoomFov, toData=self.outFov, duration=ZOOMTIME))
            zoomTrack.append(LerpFunc(self.setBlackout, fromData=0.5, toData=0.0, duration=ZOOMTIME))
            self.cameraTrack.append(zoomTrack)
            self.cameraTrack.append(Func(self.blackoutNode.hide))
            self.cameraTrack.append(Func(self.finishZoom, 0))
        self.cameraTrack.start()

    def setBlackout(self, black):
        self.blackoutNode.setColorScale(0.0, 0.0, 0.0, black)

    def getSubjectTrackState(self, subject):
        subjectTrack = self.subjectTracks.get(subject)
        if subjectTrack:
            interval = subjectTrack[0]
            timeline = subjectTrack[1]
            time = interval.getT()
            timeCount = time
            timelineIndex = 0
            while timeCount >= 0.0:
                timeCount -= timeline[timelineIndex][1]
                if timeCount >= 0.0:
                    timelineIndex += 1

            return timeline[timelineIndex]
        else:
            return None
        return None

    def __setupSubjects(self):
        self.__setupCollisions()
        self.subjects = []
        self.subjectTracks = {}
        self.photoRoot.reparentTo(self.subjectNode)
        self.photoRoot.setTag('sceneryIndex', '%s' % len(self.scenery))
        self.scenery.append(self.photoRoot)
        random.seed(time.time())
        namegen = NameGenerator.NameGenerator()
        for pathIndex in range(len(self.data['PATHS'])):
            path = self.data['PATHS'][pathIndex]
            subject = Toon.Toon()
            gender = random.choice(['m', 'f'])
            seed = int(random.random() * 571)
            if gender == 'm':
                boy = 1
                girl = 0
            else:
                boy = 0
                girl = 1
            subject.setName(namegen.randomNameMoreinfo(boy=boy, girl=girl)[-1])
            self.nameCounter += 1
            subject.setPickable(0)
            subject.setPlayerType(NametagGroup.CCNonPlayer)
            dna = ToonDNA.ToonDNA()
            dna.newToonRandom(seed, gender, 1)
            subject.setDNAString(dna.makeNetString())
            subject.animFSM.request('neutral')
            subject.setTag('subjectIndex', '%s' % len(self.subjects))
            self.subjects.append(subject)
            height = subject.getHeight()
            self.collSphere = CollisionSphere(0, 0, height * 0.5, height * 0.5)
            self.collSphere.setTangible(1)
            self.collNode = CollisionNode(self.uniqueName('subject Sphere'))
            self.collNode.setCollideMask(BitMask32.allOn())
            self.collNode.addSolid(self.collSphere)
            self.collNodePath = subject.attachNewNode(self.collNode)
            subject.reparentTo(self.subjectNode)
            subject.setPos(path[0])
            subject.lookAt(path[1])
            subject.show()
            subjectTrack = self.generateToonTrack(subject, path, pathIndex)
            subjectTrack[0].start()
            self.subjectTracks[subject] = subjectTrack

    def regenerateToonTrack(self, subject, path, pathIndex):
        if not hasattr(self, 'swivel'):
            return
        subjectTrack = self.generateToonTrack(subject, path, pathIndex)
        subjectTrack[0].start()
        self.subjectTracks[subject] = subjectTrack

    def generateToonTrack(self, subject, path, pathIndex):

        def getNextIndex(curIndex, path):
            return (curIndex + 1) % len(path)

        subjectTrack = Sequence()
        subjectTimeline = []
        timeAccum = 0.0
        pathPointIndex = 0
        orgPos = subject.getPos()
        orgQuat = subject.getQuat()
        while pathPointIndex < len(path):
            nextIndex = getNextIndex(pathPointIndex, path)
            curPoint = path[pathPointIndex]
            nextPoint = path[nextIndex]
            distance = self.slowDistance(curPoint, nextPoint)
            pointTime = distance * 0.25
            subject.setPos(curPoint)
            subject.lookAt(nextPoint)
            nextQuat = subject.getQuat()
            animSetIndex = self.data['PATHANIMREL'][pathIndex]
            animChoice = random.choice(self.data['ANIMATIONS'][animSetIndex])[0]
            movetype = random.choice(self.data['MOVEMODES'][animSetIndex])
            turnTime = 0.2
            if movetype[0] == 'swim':
                turnTime = 0.0
            nextInterval = LerpQuatInterval(subject, turnTime, quat=nextQuat)
            subjectTrack.append(nextInterval)
            subjectTimeline.append((timeAccum,
             nextInterval.getDuration(),
             'turn',
             1.0))
            timeAccum += nextInterval.getDuration()
            movetype = random.choice(self.data['MOVEMODES'][animSetIndex])
            pointTime = pointTime * movetype[1]
            if movetype[0] == 'swim':
                nextInterval = Sequence()
                startInterval = Func(subject.setP, -60)
                midInterval = Parallel(LerpPosInterval(subject, pointTime, nextPoint), ActorInterval(subject, movetype[0], loop=1, duration=pointTime))
                nextInterval.append(startInterval)
                nextInterval.append(midInterval)
            else:
                nextInterval = Sequence()
                startInterval = Func(subject.setP, 0)
                midInterval = Parallel(LerpPosInterval(subject, pointTime, nextPoint), ActorInterval(subject, movetype[0], loop=1, duration=pointTime))
                nextInterval.append(startInterval)
                nextInterval.append(midInterval)
            subjectTrack.append(nextInterval)
            subjectTimeline.append((timeAccum,
             nextInterval.getDuration(),
             movetype[0],
             1.0))
            timeAccum += nextInterval.getDuration()
            if animChoice:
                nextInterval = ActorInterval(subject, animChoice, loop=0)
                subjectTrack.append(nextInterval)
                subjectTimeline.append((timeAccum,
                 nextInterval.getDuration(),
                 animChoice,
                 2.0))
                timeAccum += nextInterval.getDuration()
            pathPointIndex += 1

        subject.setPos(orgPos)
        subject.setQuat(orgQuat)
        subjectTrack.append(Func(self.regenerateToonTrack, subject, path, pathIndex))
        return (subjectTrack, subjectTimeline)

    def slowDistance(self, point1, point2):
        dx = point1[0] - point2[0]
        dy = point1[1] - point2[1]
        dz = point1[2] - point2[2]
        distance = math.sqrt(dx * dx + dy * dy + dz * dz)
        return distance

    def getNextPoint(self, pointList, point):
        pointIndex = 0
        length = len(pointList)
        found = 0
        loop = 0
        while not found and loop < length:
            if pointList[index] == point:
                found = 1
            else:
                index += 1
                loop += 1

        if not found:
            return None
        nextPointIndex = loop + 1
        if nextPointIndex >= length:
            nextPointIndex = 0
        return pointList[nextPointIndex]

    def __createTripod(self):
        tripod = self.tripodModel.copyTo(hidden)
        swivel = tripod.find('**/cannon')
        self.tripod = tripod
        self.swivel = swivel
        self.pointer = self.swivel.attachNewNode('pointer')
        self.tripod.setPos(self.photoRoot.getPos())
        self.tripod.setPos(self.tripod.getPos() + self.data['TRIPOD_OFFSET'])

    def setGameReady(self):
        if not self.hasLocalToon:
            return
        self.notify.debug('setGameReady')
        if DistributedMinigame.setGameReady(self):
            return

    def setGameStart(self, timestamp):
        if not self.hasLocalToon:
            return
        self.notify.debug('setGameStart')
        DistributedMinigame.setGameStart(self, timestamp)
        self.__stopIntro()
        self.__putCameraOnTripod()
        if not base.config.GetBool('endless-cannon-game', 0):
            self.timer.show()
            self.timer.countdown(self.data['TIME'], self.__gameTimerExpired)
        self.filmPanel.reparentTo(aspect2d)
        self.scoreMult = MinigameGlobals.getScoreMult(self.cr.playGame.hood.id)
        self.clockStopTime = None
        self.gameFSM.request('aim')
        self.__putCameraOnTripod()
        self.currentAssignment = 0
        assignmentTemplates = self.generateAssignmentTemplates(PhotoGameGlobals.ONSCREENASSIGNMENTS)
        self.generateAssignments(assignmentTemplates)
        self.generateAssignmentPanels()
        self.scorePanel = self.makeScoreFrame()
        self.scorePanel.reparentTo(aspect2d)
        self.scorePanel.setPos(1.05, 0.0, -0.725)
        self.updateAssignmentPanels()
        for subject in self.subjects:
            subject.useLOD(1000)

        return

    def setGameExit(self):
        DistributedMinigame.setGameExit(self)
        self.__gameTimerExpired()

    def __gameTimerExpired(self):
        self.notify.debug('game timer expired')
        self.gameOver()

    def generateAssignments(self, assignmentTemplates):
        for template in assignmentTemplates:
            subject = self.subjects[template[0]]
            pose = template[1]
            score = 0.0
            panel = None
            topScore = 0.0
            topScorerId = None
            textureBuffer = None
            texturePanel = None
            texture = None
            panelToon = None
            assignment = (subject, pose)
            if assignment not in self.assignments:
                self.assignments.append(assignment)
                self.assignmentDataDict[assignment] = [score,
                 panel,
                 topScore,
                 topScorerId,
                 textureBuffer,
                 texturePanel,
                 texture,
                 panelToon]

        self.notify.debug('assignments')
        for assignment in self.assignments:
            self.notify.debug(str(assignment))

        return

    def generateAssignmentPanels(self):
        self.notify.debug('generateAssignmentPanels')
        for panel in self.assignmentPanels:
            panel.destroy()

        spacing = self.screenSizeX / PhotoGameGlobals.ONSCREENASSIGNMENTS * 1.61
        index = 0
        Xoff = self.screenSizeX - 0.735
        Zoff = -self.screenSizeZ + 0.25
        for assignment in self.assignments:
            self.notify.debug('made assignment panel %s' % str(assignment))
            panel, texturePanel, toon = self.makeAssignmentPanel(assignment)
            panel.setX(Xoff - spacing * index)
            panel.setZ(Zoff)
            texturePanel.setZ(0.065)
            rot = random.choice([0.0,
             2.0,
             -2.0,
             -4.0,
             6.0])
            panel.setR(rot)
            textureBuffer = base.win.makeTextureBuffer('Photo Capture', 128, 128)
            dr = textureBuffer.makeDisplayRegion()
            dr.setCamera(self.captureCam)
            texture = textureBuffer.getTexture()
            texturePanel.setTexture(texture)
            texturePanel.setColorScale(0, 0, 0, 0)
            textureBuffer.setActive(0)
            self.textureBuffers.append(textureBuffer)
            texturePanel.hide()
            self.assignmentPanels.append(panel)
            self.assignmentDataDict[assignment][1] = panel
            self.assignmentDataDict[assignment][4] = textureBuffer
            self.assignmentDataDict[assignment][5] = texturePanel
            self.assignmentDataDict[assignment][6] = texture
            self.assignmentDataDict[assignment][7] = toon
            index += 1

    def printAD(self):
        for assignment in self.assignmentDataDict:
            data = self.assignmentDataDict[assignment]
            print('Key:%s\nData:%s\n' % (str(assignment), data))

    def updateScorePanel(self):
        teamScore = 0.0
        bonusScore = 0.0
        for assignment in self.assignments:
            data = self.assignmentDataDict[assignment]
            teamScore += data[2]
            if data[3] == localAvatar.doId:
                bonusScore += 1.0

        self.scorePanel['text'] = TTLocalizer.PhotoGameScore % (int(teamScore), int(bonusScore), int(teamScore + bonusScore))

    def updateAssignmentPanels(self):
        for assignment in self.assignments:
            data = self.assignmentDataDict[assignment]
            leaderName = data[3]
            leader = base.cr.doId2do.get(data[3])
            if not leader:
                data[1]['text'] = ' '
            elif leader.doId == localAvatar.doId:
                data[1]['text'] = TTLocalizer.PhotoGameScoreYou
            else:
                leaderName = leader.getName()
                data[1]['text'] = TTLocalizer.PhotoGameScoreOther % leaderName
            starList = self.starDict[data[1]]
            starParent = self.starParentDict[data[1]]
            score = int(data[2])
            for index in range(NUMSTARS):
                if index < score:
                    starList[index].show()
                else:
                    starList[index].hide()

            starParent.setX(float(NUMSTARS - score) * STARSIZE * 0.5)

        self.updateScorePanel()

    def makeAssignmentPanel(self, assignment):
        if assignment != None:
            assignedToon = Toon.Toon()
            assignedToon.setDNA(assignment[0].getStyle())
        else:
            assignedToon = None
        model, ival = self.makeFrameModel(assignedToon)
        if assignedToon:
            assignedToon.loop(assignment[1])
        model.reparentTo(aspect2d)
        assignedToon.setH(172)
        assignedToon.setZ(-1.2)
        assignedToon.setY(100.0)
        if assignment[1] == 'swim':
            assignedToon.setP(-70)
            assignedToon.setH(160)
            assignedToon.setZ(-0.6)
        model.setH(0)
        model.setScale(1.0)
        model['text'] = ' '
        assignedToon.setY(-100.0)
        model.setY(-10.0)
        screen = model.attachNewNode('screen node')
        BuildGeometry.addSquareGeom(screen, 0.36, 0.27, Vec4(1.0, 1.0, 1.0, 1.0))
        screen.setHpr(0, 90, 0)
        screen.setDepthTest(1)
        starImage = loader.loadModel('phase_4/models/minigames/photogame_star')
        starParent = model.attachNewNode('star parent')
        self.starDict[model] = []
        for index in range(NUMSTARS):
            star = DirectLabel(parent=starParent, image=starImage, image_color=(1, 1, 1, 1), image_scale=Point3(STARSIZE, 0.0, STARSIZE), relief=None)
            star.setX(STARSIZE * -0.5 * float(NUMSTARS) + float(index + 0.5) * STARSIZE)
            star.setZ(-0.05 - STARSIZE)
            self.starDict[model].append(star)
            self.starParentDict[model] = starParent
            star.hide()

        return (model, screen, assignedToon)

    def makeFrameModel(self, model):
        frame = self.makeAssignmentFrame()
        ival = None
        if model:
            model.setDepthTest(1)
            model.setDepthWrite(1)
            scale = frame.attachNewNode('scale')
            model.reparentTo(scale)
            bMin, bMax = model.getTightBounds()
            center = (bMin + bMax) / 2.0
            model.setPos(-center[0], 2, -center[2])
            corner = Vec3(bMax - center)
            scaleFactor = self.screenSizeX / PhotoGameGlobals.ONSCREENASSIGNMENTS
            scale.setScale(0.4 * scaleFactor / max(corner[0], corner[1], corner[2]))
        return (frame, ival)

    def makeAssignmentFrame(self):
        from direct.gui.DirectGui import DirectFrame
        photoImage = loader.loadModel('phase_4/models/minigames/photo_game_pictureframe')
        size = 1.0
        assignmentScale = self.screenSizeX / PhotoGameGlobals.ONSCREENASSIGNMENTS
        frame = DirectFrame(parent=hidden, image=photoImage, image_color=(1, 1, 1, 1), image_scale=Point3(1.6 * assignmentScale, 0.0, 1.75 * assignmentScale), frameSize=(-size,
         size,
         -size,
         size), text='HC Score', textMayChange=1, text_wordwrap=9, text_pos=Point3(0.0, -0.135, 0.0), text_scale=0.045, relief=None)
        return frame

    def makeScoreFrame(self):
        from direct.gui.DirectGui import DirectFrame
        size = 1.0
        scoreImage = loader.loadModel('phase_4/models/minigames/photogame_camera')
        frame = DirectFrame(parent=hidden, image=scoreImage, image_color=(1, 1, 1, 1), image_scale=Point3(0.64, 0.0, 0.64), frameSize=(-size,
         size,
         -size,
         size), text='Score Frame', textMayChange=1, text_wordwrap=9, text_pos=Point3(0.0, 0.0, 0.0), text_scale=0.05, relief=None)
        return frame

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

    def exitOff(self):
        pass

    def enterAim(self):
        self.notify.debug('enterAim')
        self.__enableAimInterface()
        taskMgr.add(self.__moveViewfinder, 'photo game viewfinder Task')
        self.accept('mouse1', self.__handleMouseClick)
        base.localAvatar.laffMeter.stop()
        base.transitions.noIris()

    def exitAim(self):
        self.__disableAimInterface()
        taskMgr.remove('photo game viewfinder Task')
        self.ignore('mouse1')

    def enterZoom(self):
        self.notify.debug('enterZoom')
        taskMgr.add(self.__moveViewfinder, 'photo game viewfinder Task')
        self.__doZoom()

    def exitZoom(self):
        taskMgr.remove('photo game viewfinder Task')
        self.notify.debug('exitZoom')

    def finishZoom(self, zoomed = None, task = None):
        if zoomed:
            self.captureLens.setFov(self.captureZoomFOV)
        else:
            self.captureLens.setFov(self.captureOutFOV)
        self.gameFSM.request('aim')

    def enterShowResults(self):
        self.notify.debug('enterShowResults')
        for subject in self.subjects:
            subject.resetLOD()

    def exitShowResults(self):
        pass

    def enterCleanup(self):
        self.notify.debug('enterCleanup')
        self.music.stop()
        if hasattr(self, 'jarIval'):
            self.jarIval.finish()
            del self.jarIval
        for avId in self.avIdList:
            taskMgr.remove('firePhoto' + str(avId))
            taskMgr.remove('flyingToon' + str(avId))

    def exitCleanup(self):
        pass

    def __enableAimInterface(self):
        self.accept(self.FIRE_KEY, self.__fireKeyPressed)
        self.accept(self.UP_KEY, self.__upKeyPressed)
        self.accept(self.DOWN_KEY, self.__downKeyPressed)
        self.accept(self.LEFT_KEY, self.__leftKeyPressed)
        self.accept(self.RIGHT_KEY, self.__rightKeyPressed)
        self.__spawnLocalPhotoMoveTask()

    def __disableAimInterface(self):
        self.ignore(self.FIRE_KEY)
        self.ignore(self.UP_KEY)
        self.ignore(self.DOWN_KEY)
        self.ignore(self.LEFT_KEY)
        self.ignore(self.RIGHT_KEY)
        self.ignore(self.FIRE_KEY + '-up')
        self.ignore(self.UP_KEY + '-up')
        self.ignore(self.DOWN_KEY + '-up')
        self.ignore(self.LEFT_KEY + '-up')
        self.ignore(self.RIGHT_KEY + '-up')
        self.__killLocalPhotoMoveTask()

    def __fireKeyPressed(self):
        self.ignore(self.FIRE_KEY)
        self.accept(self.FIRE_KEY + '-up', self.__fireKeyReleased)
        self.__firePressed()

    def __upKeyPressed(self):
        self.ignore(self.UP_KEY)
        self.accept(self.UP_KEY + '-up', self.__upKeyReleased)
        self.__upPressed()

    def __downKeyPressed(self):
        self.ignore(self.DOWN_KEY)
        self.accept(self.DOWN_KEY + '-up', self.__downKeyReleased)
        self.__downPressed()

    def __leftKeyPressed(self):
        self.ignore(self.LEFT_KEY)
        self.accept(self.LEFT_KEY + '-up', self.__leftKeyReleased)
        self.__leftPressed()

    def __rightKeyPressed(self):
        self.ignore(self.RIGHT_KEY)
        self.accept(self.RIGHT_KEY + '-up', self.__rightKeyReleased)
        self.__rightPressed()

    def __fireKeyReleased(self):
        self.ignore(self.FIRE_KEY + '-up')
        self.accept(self.FIRE_KEY, self.__fireKeyPressed)
        self.__fireReleased()

    def __leftKeyReleased(self):
        self.ignore(self.LEFT_KEY + '-up')
        self.accept(self.LEFT_KEY, self.__leftKeyPressed)
        self.__leftReleased()

    def __rightKeyReleased(self):
        self.ignore(self.RIGHT_KEY + '-up')
        self.accept(self.RIGHT_KEY, self.__rightKeyPressed)
        self.__rightReleased()

    def __upKeyReleased(self):
        self.ignore(self.UP_KEY + '-up')
        self.accept(self.UP_KEY, self.__upKeyPressed)
        self.__upReleased()

    def __downKeyReleased(self):
        self.ignore(self.DOWN_KEY + '-up')
        self.accept(self.DOWN_KEY, self.__downKeyPressed)
        self.__downReleased()

    def __firePressed(self):
        self.notify.debug('fire pressed')

    def __fireReleased(self):
        self.gameFSM.request('zoom')

    def __upPressed(self):
        self.notify.debug('up pressed')
        self.upPressed = self.__enterControlActive(self.upPressed)

    def __downPressed(self):
        self.notify.debug('down pressed')
        self.downPressed = self.__enterControlActive(self.downPressed)

    def __leftPressed(self):
        self.notify.debug('left pressed')
        self.leftPressed = self.__enterControlActive(self.leftPressed)

    def __rightPressed(self):
        self.notify.debug('right pressed')
        self.rightPressed = self.__enterControlActive(self.rightPressed)

    def __upReleased(self):
        self.notify.debug('up released')
        self.upPressed = self.__exitControlActive(self.upPressed)

    def __downReleased(self):
        self.notify.debug('down released')
        self.downPressed = self.__exitControlActive(self.downPressed)

    def __leftReleased(self):
        self.notify.debug('left released')
        self.leftPressed = self.__exitControlActive(self.leftPressed)

    def __rightReleased(self):
        self.notify.debug('right released')
        self.rightPressed = self.__exitControlActive(self.rightPressed)

    def __handleMouseClick(self):
        self.notify.debug('mouse click')
        self.__testCollisions()

    def __enterControlActive(self, control):
        return control + 1

    def __exitControlActive(self, control):
        return max(0, control - 1)

    def __spawnLocalPhotoMoveTask(self):
        self.leftPressed = 0
        self.rightPressed = 0
        self.upPressed = 0
        self.downPressed = 0
        self.photoMoving = 0
        task = Task(self.__localPhotoMoveTask)
        task.lastPositionBroadcastTime = 0.0
        taskMgr.add(task, self.LOCAL_PHOTO_MOVE_TASK)

    def __killLocalPhotoMoveTask(self):
        taskMgr.remove(self.LOCAL_PHOTO_MOVE_TASK)
        if self.photoMoving:
            self.sndPhotoMove.stop()

    def __localPhotoMoveTask(self, task):
        if not hasattr(self, 'swivel'):
            return
        pos = [self.swivel.getHpr()[0], self.swivel.getHpr()[1], self.swivel.getHpr()[2]]
        oldRot = pos[0]
        oldAng = pos[1]
        rotVel = 0
        if self.leftPressed:
            rotVel += PhotoGameGlobals.PHOTO_ROTATION_VEL
        if self.rightPressed:
            rotVel -= PhotoGameGlobals.PHOTO_ROTATION_VEL
        pos[0] += rotVel * globalClock.getDt()
        angVel = 0
        if self.upPressed:
            angVel += PhotoGameGlobals.PHOTO_ANGLE_VEL
        if self.downPressed:
            angVel -= PhotoGameGlobals.PHOTO_ANGLE_VEL
        pos[1] += angVel * globalClock.getDt()
        if pos[1] < PhotoGameGlobals.PHOTO_ANGLE_MIN:
            pos[1] = PhotoGameGlobals.PHOTO_ANGLE_MIN
        elif pos[1] > PhotoGameGlobals.PHOTO_ANGLE_MAX:
            pos[1] = PhotoGameGlobals.PHOTO_ANGLE_MAX
        if oldRot != pos[0] or oldAng != pos[1]:
            if self.photoMoving == 0:
                self.photoMoving = 1
                base.playSfx(self.sndPhotoMove, looping=1)
            posVec = Vec3(pos[0], pos[1], pos[2])
            self.swivel.setHpr(posVec)
        elif self.photoMoving:
            self.photoMoving = 0
            self.sndPhotoMove.stop()
        return Task.cont

    def __putCameraOnTripod(self):
        camera.setPosHpr(0, 0.0, 0, 0, 0, 0)
        camera.reparentTo(self.swivel)
        self.swivel.setHpr(self.data['START_HPR'])

    def __loadToonInTripod(self, avId):
        toon = base.cr.doId2do.get(avId)
        if toon:
            toon.reparentTo(self.swivel)

    def __toRadians(self, angle):
        return angle * 2.0 * math.pi / 360.0

    def __toDegrees(self, angle):
        return angle * 360.0 / (2.0 * math.pi)

    def __decreaseFilmCount(self):
        curTime = self.getCurrentGameTime()
        score = self.filmCount - 1
        if not hasattr(self, 'curScore'):
            self.curScore = score
        self.filmPanel['text'] = str(score)
        if self.curScore != score:
            if hasattr(self, 'jarIval'):
                self.jarIval.finish()
            s = self.filmPanel.getScale()
            self.jarIval = Parallel(Sequence(self.filmPanel.scaleInterval(0.15, s * 3.0 / 4.0, blendType='easeOut'), self.filmPanel.scaleInterval(0.15, s, blendType='easeIn')), Sequence(Wait(0.25), SoundInterval(self.sndFilmTick)), name='photoGameFilmJarThrob')
            self.jarIval.start()
        self.curScore = score
        self.filmCount = score

    def __stopIntro(self):
        taskMgr.remove(self.INTRO_TASK_NAME)
        taskMgr.remove(self.INTRO_TASK_NAME_CAMERA_LERP)
        self.__putCameraOnTripod()
        if self.introSequence:
            self.introSequence.finish()
            self.introSequence = None
        return

    def __startIntro(self):
        camera.reparentTo(render)
        camera.setPos(self.data['CAMERA_INTIAL_POSTION'])
        camera.setHpr(0, 0, 0)
        camera.lookAt(self.tripod)
        lookatHpr = camera.getHpr()
        self.introSequence = LerpPosHprInterval(camera, 4.0, pos=self.tripod.getPos(render), hpr=lookatHpr, startPos=self.data['CAMERA_INTIAL_POSTION'], blendType='easeInOut')
        self.introSequence.start()

    def construct(self):
        zone = self.getSafezoneId()
        if zone == ToontownGlobals.ToontownCentral:
            self.constructTTC()
        elif zone == ToontownGlobals.DonaldsDock:
            self.constructDD()
        elif zone == ToontownGlobals.DaisyGardens:
            self.constructDG()
        elif zone == ToontownGlobals.MinniesMelodyland:
            self.constructMM()
        elif zone == ToontownGlobals.TheBrrrgh:
            self.constructBR()
        elif zone == ToontownGlobals.DonaldsDreamland:
            self.constructDL()

    def destruct(self):
        zone = self.getSafezoneId()
        if zone == ToontownGlobals.ToontownCentral:
            self.destructTTC()
        elif zone == ToontownGlobals.DonaldsDock:
            self.destructDD()
        elif zone == ToontownGlobals.DaisyGardens:
            self.destructDG()
        elif zone == ToontownGlobals.MinniesMelodyland:
            self.destructMM()
        elif zone == ToontownGlobals.TheBrrrgh:
            self.destructBR()
        elif zone == ToontownGlobals.DonaldsDreamland:
            self.destructDL()

    def constructTTC(self):
        self.photoRoot = self.scene.find('**/prop_gazebo*')
        self.sky = loader.loadModel('phase_3.5/models/props/TT_sky')
        self.sky.reparentTo(render)
        self.sky.setBin('background', -100)
        self.sky.find('**/cloud1').setBin('background', -99)
        self.sky.find('**/cloud2').setBin('background', -98)
        self.scene.reparentTo(render)
        self.makeDictionaries(self.dnaStore)
        self.createAnimatedProps(self.nodeList)
        self.startAnimatedProps()
        self.scene.find('**/door_trigger_22*').hide()
        self.scene.find('**/doorFrameHoleRight_0*').hide()
        self.scene.find('**/doorFrameHoleLeft_0*').hide()

    def destructTTC(self):
        self.stopAnimatedProps()
        self.deleteAnimatedProps()

    def constructDD(self):
        self.photoRoot = self.scene.find('**/center_island*')
        self.sky = loader.loadModel('phase_3.5/models/props/BR_sky')
        self.sky.reparentTo(render)
        self.sky.setBin('background', -100)
        self.sky.find('**/skypanel1').setBin('background', -98)
        self.sky.find('**/skypanel2').setBin('background', -97)
        self.sky.find('**/skypanel3').setBin('background', -96)
        self.sky.find('**/skypanel4').setBin('background', -95)
        self.sky.find('**/skypanel5').setBin('background', -94)
        self.sky.find('**/skypanel6').setBin('background', -93)
        self.scene.reparentTo(render)
        self.makeDictionaries(self.dnaStore)
        self.createAnimatedProps(self.nodeList)
        self.startAnimatedProps()
        boatGeom = self.scene.find('**/donalds_boat')
        self.photoRoot.setPos(-22, 0, 0)
        self.boat = self.photoRoot.attachNewNode('boat')
        boatGeom.reparentTo(self.boat)
        boatGeom.setX(45.0)
        boatGeom.setY(-5.0)
        boatGeom.setR(-0.0)
        boatGeom.setH(-8)
        self.bg = boatGeom
        self.boatTrack = Sequence()
        self.boatTrack.append(LerpHprInterval(self.boat, 90.0, Point3(360, 0, 0)))
        self.boatTrack.loop()
        self.boatTrack2 = Sequence()
        self.boatTrack2.append(LerpPosInterval(self.boat, 5.0, Point3(0, 0, 2.0), Point3(0, 0, 1.0), blendType='easeInOut'))
        self.boatTrack2.append(LerpPosInterval(self.boat, 5.0, Point3(0, 0, 1.0), Point3(0, 0, 2.0), blendType='easeInOut'))
        self.boatTrack2.loop()
        ddFog = Fog('DDFog Photo')
        ddFog.setColor(Vec4(0.8, 0.8, 0.8, 1.0))
        ddFog.setLinearRange(0.0, 400.0)
        render.setFog(ddFog)
        water = self.scene.find('**/top_surface')
        water.setTransparency(TransparencyAttrib.MAlpha)
        water.setColorScale(1.0, 1.0, 1.0, 0.8)
        water.setDepthWrite(1)
        water.setDepthTest(1)
        water.setBin('transparent', 0)

    def destructDD(self):
        self.bg = None
        self.boatTrack.finish()
        self.boatTrack2.finish()
        self.boat.removeNode()
        render.clearFog()
        self.stopAnimatedProps()
        self.deleteAnimatedProps()
        return

    def constructDG(self):
        self.photoRoot = render.attachNewNode('DG PhotoRoot')
        self.photoRoot.setPos(1.39, 92.91, 2.0)
        self.bigFlower = loader.loadModel('phase_8/models/props/DG_flower-mod.bam')
        self.bigFlower.reparentTo(self.photoRoot)
        self.bigFlower.setScale(2.5)
        self.sky = loader.loadModel('phase_3.5/models/props/TT_sky')
        self.sky.reparentTo(render)
        self.sky.setBin('background', -100)
        self.sky.find('**/cloud1').setBin('background', -99)
        self.sky.find('**/cloud2').setBin('background', -98)
        self.scene.reparentTo(render)
        self.scene.find('**/door_trigger_5*').hide()
        self.scene.find('**/doorFrameHoleRight_0*').hide()
        self.scene.find('**/doorFrameHoleLeft_0*').hide()
        for name in ['**/o10_2']:
            maze = self.scene.find(name)
            maze.reparentTo(self.subjectNode)
            maze.setTag('sceneryIndex', '%s' % len(self.scenery))
            self.scenery.append(maze)

        self.makeDictionaries(self.dnaStore)
        self.createAnimatedProps(self.nodeList)
        self.startAnimatedProps()

    def destructDG(self):
        self.bigFlower.removeNode()
        self.stopAnimatedProps()
        self.deleteAnimatedProps()

    def constructMM(self):
        self.photoRoot = render.attachNewNode('MM PhotoRoot')
        self.photoRoot.setPos(103.6, -61, -4.497)
        self.sky = loader.loadModel('phase_6/models/props/MM_sky')
        self.sky.reparentTo(render)
        self.sky.setBin('background', -100)
        self.scene.reparentTo(render)
        self.scene.find('**/door_trigger_8*').hide()
        self.scene.find('**/door_trigger_6*').hide()
        self.scene.find('**/door_trigger_1*').hide()
        self.scene.find('**/door_trigger_0*').hide()
        self.scene.find('**/door_trigger_3*').hide()
        self.scene.find('**/doorFrameHoleRight_0*').hide()
        self.scene.find('**/doorFrameHoleLeft_0*').hide()
        self.scene.find('**/doorFrameHoleRight_1*').hide()
        self.scene.find('**/doorFrameHoleLeft_1*').hide()
        self.scene.find('**/doorFrameHoleRight').hide()
        self.scene.find('**/doorFrameHoleLeft').hide()
        self.makeDictionaries(self.dnaStore)
        self.createAnimatedProps(self.nodeList)
        self.startAnimatedProps()
        lm = self.scene.findAllMatches('**/*landmark*')
        blocker = lm[2]
        blocker.reparentTo(self.subjectNode)
        blocker.setTag('sceneryIndex', '%s' % len(self.scenery))
        self.scenery.append(blocker)

    def destructMM(self):
        self.stopAnimatedProps()
        self.deleteAnimatedProps()

    def constructBR(self):
        self.photoRoot = render.attachNewNode('BR PhotoRoot')
        self.photoRoot.setPos(-110, -48, 8.567)
        self.sky = loader.loadModel('phase_3.5/models/props/BR_sky')
        self.sky.reparentTo(render)
        self.sky.setBin('background', -100)
        self.scene.reparentTo(render)
        self.scene.find('**/door_trigger_11*').hide()
        self.scene.find('**/doorFrameHoleRight_0*').hide()
        self.scene.find('**/doorFrameHoleLeft_0*').hide()
        self.makeDictionaries(self.dnaStore)
        self.createAnimatedProps(self.nodeList)
        self.startAnimatedProps()
        self.snow = BattleParticles.loadParticleFile('snowdisk.ptf')
        self.snow.setPos(0, 0, 5)
        self.snowRender = self.scene.attachNewNode('snowRender')
        self.snowRender.setDepthWrite(0)
        self.snowRender.setBin('fixed', 1)
        self.snowFade = None
        self.snow.start(camera, self.snowRender)
        return

    def destructBR(self):
        self.snow.cleanup()
        del self.snow
        del self.snowRender
        self.stopAnimatedProps()
        self.deleteAnimatedProps()

    def constructDL(self):
        self.photoRoot = render.attachNewNode('DL PhotoRoot')
        self.photoRoot.setPos(-70.228, 87.588, 4.397)
        self.sky = loader.loadModel('phase_8/models/props/DL_sky')
        self.sky.reparentTo(render)
        self.sky.setBin('background', -100)
        self.scene.reparentTo(render)
        self.scene.find('**/door_trigger_8*').hide()
        self.scene.find('**/doorFrameHoleRight_0*').hide()
        self.scene.find('**/doorFrameHoleLeft_0*').hide()
        self.scene.find('**/doorFrameHoleRight_1*').hide()
        self.scene.find('**/doorFrameHoleLeft_1*').hide()
        self.makeDictionaries(self.dnaStore)
        self.createAnimatedProps(self.nodeList)
        self.startAnimatedProps()

    def destructDL(self):
        self.stopAnimatedProps()
        self.deleteAnimatedProps()

    def makeDictionaries(self, dnaStore):
        self.nodeList = []
        for i in range(dnaStore.getNumDNAVisGroups()):
            groupFullName = dnaStore.getDNAVisGroupName(i)
            groupName = base.cr.hoodMgr.extractGroupName(groupFullName)
            groupNode = self.scene.find('**/' + groupFullName)
            if groupNode.isEmpty():
                self.notify.error('Could not find visgroup')
            self.nodeList.append(groupNode)

    def startAnimatedProps(self):
        for animPropListKey in self.animPropDict:
            animPropList = self.animPropDict[animPropListKey]
            for animProp in animPropList:
                animProp.enter()

    def stopAnimatedProps(self):
        for animPropListKey in self.animPropDict:
            animPropList = self.animPropDict[animPropListKey]
            for animProp in animPropList:
                animProp.exit()

    def createAnimatedProps(self, nodeList):
        self.animPropDict = {}
        for i in nodeList:
            animPropNodes = i.findAllMatches('**/animated_prop_*')
            numAnimPropNodes = animPropNodes.getNumPaths()
            for j in range(numAnimPropNodes):
                animPropNode = animPropNodes.getPath(j)
                if animPropNode.getName().startswith('animated_prop_generic'):
                    className = 'GenericAnimatedProp'
                else:
                    className = animPropNode.getName()[14:-8]
                symbols = {}
                base.cr.importModule(symbols, 'toontown.hood', [className])
                classObj = getattr(symbols[className], className)
                animPropObj = classObj(animPropNode)
                animPropList = self.animPropDict.setdefault(i, [])
                animPropList.append(animPropObj)

            interactivePropNodes = i.findAllMatches('**/interactive_prop_*')
            numInteractivePropNodes = interactivePropNodes.getNumPaths()
            for j in range(numInteractivePropNodes):
                interactivePropNode = interactivePropNodes.getPath(j)
                className = 'GenericAnimatedProp'
                symbols = {}
                base.cr.importModule(symbols, 'toontown.hood', [className])
                classObj = getattr(symbols[className], className)
                interactivePropObj = classObj(interactivePropNode)
                animPropList = self.animPropDict.get(i)
                if animPropList is None:
                    animPropList = self.animPropDict.setdefault(i, [])
                animPropList.append(interactivePropObj)

        return

    def deleteAnimatedProps(self):
        for animPropListKey in self.animPropDict:
            animPropList = self.animPropDict[animPropListKey]
            for animProp in animPropList:
                animProp.delete()

        del self.animPropDict

    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):
        if hasattr(self, 'soundTable'):
            self.soundTable[name].setVolume(volume)
            self.soundTable[name].play()