from direct.directnotify import DirectNotifyGlobal
from pandac.PandaModules 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
import PhotoGameGlobals
from direct.gui.DirectGui import *
from pandac.PandaModules import *
from toontown.toonbase import TTLocalizer
from toontown.golf import BuildGeometry
from toontown.toon import Toon
from toontown.toon import ToonDNA
from toontown.dna.DNAParser import *
from toontown.nametag import NametagGlobals
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()
        files = ('phase_4/dna/storage.pdna', self.storageDNAFile, self.safeZoneStorageDNAFile)
        dnaBulk = DNABulkLoader(self.dnaStore, files)
        dnaBulk.loadDNAFiles()
        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=(-0.23, -1.2, -0.55), 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.loadMusic('phase_4/audio/bgm/MG_cannon_game.ogg')
        self.sndPhotoMove = base.loadSfx('phase_4/audio/sfx/MG_cannon_adjust.ogg')
        self.sndPhotoFire = base.loadSfx('phase_4/audio/sfx/MG_cannon_fire_alt.ogg')
        self.sndWin = base.loadSfx('phase_4/audio/sfx/MG_win.ogg')
        self.sndFilmTick = base.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 xrange(-(GOODROWS / 2), GOODROWS / 2 + 1):
            for column in xrange(-(GOODROWS / 2), GOODROWS / 2 + 1):
                goodRange = 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 xrange(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 not name 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.setMinFov(self.zoomFov/(4./3.))
            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.setMinFov(self.outFov/(4./3.))
            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.setMinFov(self.zoomFov/(4./3.))
            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.setMinFov, fromData=self.outFov/(4./3.), toData=self.zoomFov/(4./3.), 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.setMinFov(self.outFov/(4./3.))
            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.setMinFov, fromData=self.zoomFov/(4./3.), toData=self.outFov/(4./3.), 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 xrange(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(NametagGlobals.CCSpeedChat)
            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(base.a2dTopRight)
        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(base.a2dBottomRight)
        self.scorePanel.setPos(-0.3, 0, 0.3)
        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 xrange(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 xrange(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()
        #Causes a crash, disabling has seemingly no bad effect.
        #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 xrange(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 xrange(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 xrange(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()